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 http://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 listfunction:


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.

______________________

Joey Bernard has a background in both physics and computer science. This serves him well in his day job as a computational research consultant at the University of New Brunswick. He also teaches computational physics and parallel programming.