Open Axiom
Several computer algebra systems are available to Linux users. I even have looked at a few of them in this column, but for this issue, I discuss OpenAxiom. OpenAxiom actually is a fork of Axiom. Axiom originally was developed at IBM under the name ScratchPad. Development started in 1971, so Axiom is as old as I am, and almost as smart. In the 1990s, it was sold off to the Numerical Algorithms Group (NAG). In 2001, NAG removed it from commercial sale and released it as free software. Since then, it has forked into OpenAxiom and FriCAS. Axiom still is available. The system is specified in the book AXIOM: the Scientific Computation System by Richard Jenks and Robert Sutor. This book is available on-line at https://wiki.axiom-developer.org/axiom-website/hyperdoc/axbook/book-contents.xhtml, and it makes up the core documentation for OpenAxiom.
Most Linux distributions should have a package for OpenAxiom. For example, with Debian-based distributions, you can install OpenAxiom with:
sudo apt-get install openaxiom
If you want to build OpenAxiom from source, you need to have a Lisp engine installed. There are several to choose from on Linux, such as CLisp or GNU Common Lisp. Building is a straightforward:
./configure; make; make install
To use OpenAxiom, simply execute open-axiom
on the command line. This
will give you an interactive OpenAxiom session. If you have a script of
commands you want to run as a complete unit, you can do so with:
open-axiom --script myfile.input
where the file "myfile.input" contains the OpenAxiom commands to be executed.
So, what can you actually do with OpenAxiom? OpenAxiom has many different data types. There are algebraic ones (like polynomials, matrices and power series) and data structures (like lists and dictionaries). You can combine them into any reasonable combinations, like polynomials of matrices or matrices of polynomials. These data types are defined by programs in OpenAxiom. These data type programs also include the operations that can be applied to the particular data type. The entire system is polymorphic by design. You also can extend the entire data type system by writing your own data type programs. There are a large number of different numeric types to handle almost any type of operation as well.
The simplest use of OpenAxiom is as a calculator. For example, you can find the cosine of 1.2 with:
cos(1.2)
This will give you the result with 20 digits, by default. You can change
the number of digits being used with the digits()
function. OpenAxiom
also will give you the type of this answer. This is useful when you are
doing more experimental calculations in order to check your work. In
the above example, the type would be Float
. If you try
this:
4/6
the result is 2/3
, and you will see a new type,
Fraction Integer
. If
you have used a commercial system like Maple before, this should
be familiar.
OpenAxiom has data types to try to keep results as exact
values. If you have a reason to use a particular type, you can do a
conversion with the ::
operator. So, you could redo the above division
and get the answer as a float with:
(4/6)::Float
It even can go backward and calculate the closest fraction that matches a given float with the command:
%::Fraction Integer
The %
character refers to the most recent result that you
calculated. The answer you get from this command may not match the
original fraction, due to various rounding errors.
There are functions
that allow you to work with various parts of numbers. You can
round()
or
truncate()
floating-point numbers. You even can get just the fractional
part with fractionPart()
.
One slightly unique thing in OpenAxiom is a
set of test functions. You can check for oddness and evenness with the
functions odd?()
and even?()
. You
even can check whether a number is
prime with prime?()
. And, of course, you still have all of the standard
functions, like the trigonometric ones, and the standard operators,
like addition and multiplication.
OpenAxiom handles general expressions too. In order to use them, you
need to assign them to a variable name. The assignment operator
is :=
. One thing to keep in mind is that this operator will execute
whatever is on the right-hand side and assign the result to the name on
the left-hand side. This may not be what you want to have happen. If so,
you can use the delayed assignment operator ==
. Let's say you want to
calculate the square of some numbers. You can create an expression with:
xSquared := x**2
In order to use this expression, you need to use the eval
function:
eval(xSquared, x=4)
You also can have multiple parameters in your expression. Say you wanted to calculate area. You could use something like this:
xyArea := x * y eval(xyArea, [x=2, y=10])
The last feature I want to look at in this article is how OpenAxiom handles data structures. The most basic data structure is a list. Lists in OpenAxiom are homogeneous, so all of the elements need to be the same data type. You define a list directly by putting a comma-separated group in square brackets—for example:
[1,2,3,4]
This can be done equivalently with the list
function:
list(1,2,3,4)
You can put two lists together with the append
function:
append([1,2],[3,4])
If you want to add a single element to the front of a list, you can use
the cons
function:
cons(1, [2,3,4])
List addressing is borrowed from the concepts in Lisp. So the most
basic addressing functions to get elements are the functions
first
and rest
. Using the basic list from above, the function:
first([1,2,3,4])
will return the number 1, and the function:
rest([1,2,3,4])
will return the list [2,3,4]. Using these functions and creative use
of loops, you can get any element in a given list. But, this is very
inconvenient, so OpenAxiom provides a simpler interface. If you had
assigned the above list to the variable mylist
, you could get the third
element with:
mylist.3
or, equivalently:
mylist(3)
These index values are 1-based, as opposed to 0-based indexing in languages like C.
A really unique type of list available is the infinite list. Say you want to have a list of all integers. You can do that with:
myints := [i for i in 1..]
This list will contain all possible integers, and they are calculated only when you need the value in question. You can have more complicated examples, like a list of prime numbers:
myprimes := [i for i in 1.. | prime?(i)]
One issue with lists is that access times depend on how big the list is. Accessing the last element of a list varies, depending on how big said list is. This is because lists can vary in length. If you have a piece of code that deals with lists that won't change in length, you can improve performance by using an array. You can create a one-dimensional array with the function:
oneDimensionalArray([2,3,4,5])
This assigns a fixed area of memory to store the data, and access time now becomes uniform, regardless of the size of the list. Arrays also are used as the base data structure for strings and vectors. You even can create a bits data structure. You could create a group of eight 1-bits with:
bits(8,true)
In this way, you can begin to do some rather abstract computational work.
As you have seen, OpenAxiom, and its variants, are very powerful systems for doing scientific computations. I covered only the very basic functionality available here, but it should give you a feeling for what you can do. In the near future, I plan to take another look at OpenAxiom and see what more advanced techniques are possible, including writing your own functions and programs.