# Axiom Notes

The question is if we ("we" meaning maybe a GSoC student) can implement the rest of the Risch algorithm by translating it from Axiom, where Bronstein implemented the full thing. Here are my (Aaron's) notes on playing with Axiom and trying to understand the Risch code there.

To use Axiom, use the Fricas fork. There is a GitHub mirror of the sources at https://github.com/fricas/fricas.

Compiling Fricas requires sbcl.

I personally couldn't get it to compile on macOS, but the binary at http://fricas.sourceforge.net/download.html worked.

The best resource to understand the Axiom source code is the Axiom book. I highly recommend reading sections 5 and 6 of the book to understand the Aldor language that Axiom is written it.

For whatever reason, the Axiom book uses `**` for exponentiation but it does not work in Fricas. You have to use `^` instead.

The Risch algorithm is defined in several files (mainly the ones that start with `int`, `rde`, and `efstruc`.

Axiom's entrypoint to the Risch algorihtm is the `integrate` function. It works very similar to SymPy:

``````
(4) -> f := (exp(x) + 1)/(2*x^2 - 1)

x
%e  + 1
(4)  -------
2
2x  - 1
Type: Expression(Integer)
(5) -> integrate(f, x)

(5)
+-+                                             +-+ 2
\|2                                             \|2
----       2      +-+               +-+         ----         +-+
2     (2x  + 1)\|2  - 4x       - \|2  + 2x      2         \|2  + 2x
%e    log(------------------) + Ei(-----------)(%e    )  - Ei(---------)
2                     2                         2
2x  - 1
------------------------------------------------------------------------
+-+
\|2
----
+-+    2
2\|2 %e
Type: Union(Expression(Integer),...)

``````

Every time you evaluate an expression interactively in Axiom, it shows you the type of the expression. Polynomials expand automatically:

``````(6) -> (x + 1)^2

2
(6)  x  + 2x + 1
Type: Polynomial(Integer)
``````

`Polynomial(Integer)` means a polynomial with integer coefficients. `Integer` is the ring in question. The field of rational numbers is `Fraction(Integer)`.

Commands to the Axiom system start with a `)`, for instance, `)help`.

Axiom uses a global namespace of functions. Functions are defined by their name and arity. For example, `f(x)`, and `f(x, y)` can both exist at the same time. The same function can exist in many forms for different types of the inputs. Functions are either defined for a specific type (like `f(x: Integer): Integer = x + 1`, or defined generically (like `f(x) == x + 1`). If it's defined generically a different version is compiled for each type the first time it is used with that type.

The operators `::`, `@`, and `\$` after an expression manipulate the type. From the book (section 0.4.5):

`::` means explicitly convert X to type T if possible. `\$` means use the available operators for type T to compute X. `@` means choose operators to compute X so that the result is of type T.

In particular, `\$` is used to call a function from a specific package. This is needed to access functions that aren't "exposed". You can also expose all functions from a module using `)set expose add con` (`con` is short for `constructor`)

``````(8) -> )set expose add con RDEaux
RDEaux is now explicitly exposed in frame frame1
``````

Note that a single file can contain multiple packages. The `RDEaux` package comes from `src/algebra/intpar.spad`, and is defined in the middle of the file (search for `)abbrev package RDEAUX RDEaux`).

Packages take parameters, which template the types used in the package. For instance, `RDEaux` takes a parameter `F`, which is the field used in the package (like `RDEaux(Fraction(Integer))`). You can use `multi_SPDE(...)\$RDEaux(Fraction(Integer))` for instance (or just expose `RDEaux` as above).

Axiom does not seem to have tests for these internal functions, only for the publicly exposed functions (`integrate`). I did not yet figure out how to use a debugger to see what internal functions are being called from `integrate`.

The functions in Axiom roughly correspond to the layout of Bronstein's book, but they don't follow the pseudocode exactly. I haven't yet found an exact duplicate function Axiom <-> SymPy that can be used to

Types convert automatically in most cases.

Aldor compiles to lisp. I'm not sure how easy it is to manipulate/debug at the lisp level.

## Syntax notes

Read sections 5 and 6 of the book. They explain the syntax really well and concisely.

Fricas requires using `^` instead of `**`. It looks like you can easily fix this by running

``````x**y == x^y
``````

Functions of one argument can be called with spaces. `f x` is the same as `f(x)`. This does not work for functions of more than one argument.

`:` defined type information. `f(x: Integer): Fraction(Integer)` is a function that takes an integer and returns a rational number.

See above for information on `::`, `\$`, and `@`.

`+->` is an anonymous function (lambda). For instance, `x +-> x + 1` is a function that adds one to the argument.

`:=` defines variables. `==` defines functions. `=` is a symbolic equality (like `Eq` in SymPy).

`=>` exits the current block. If the block is the function it returns the value. It can also be used as a "continue" when in a loop. For instance, from the book (5.4.4):

``````i := 1
repeat
i := i + 1
i > 3 => i
output(i)
``````

`=>` breaks out of the "block", which is just the lines

``````i := i + 1
i > 3 => i
output(i)
``````

but not the `repeat`. So it will loop forever (but only call `output` once).

This is clearer if you write it in one line:

``````i := 1
repeat (i := i + 1; i>3 => i; output i)
``````

The `(... ; ...; ...)` bit is the "block".

`=>` is often used to do `if x: return ...` constructs. For example, from `intpar.spad`:

``````multi_SPDE(a, b, lc, d, der) ==
d <\$Z 0 => [[0, c]\$RSOL for c in lc]
every?(zero?, lc) => [[0, 0]\$RSOL for c in lc]
...
``````

`[1, 2]`, and `(1, 2)` are called "lists" and "tuples", resp., just like in Python (this is important when looking at the type information, for instance, `List(UP)` means a list of elements of type `UP`).

Undefined names become variables automatically.

`++` creates a comment. This is where you should look for hints as to what the internal functions do, mathematically.

## Open questions

• How are the `der` derivation functions defined in the Risch code? I tried for instance,

``````)set expose add con RDEaux
a := t^2 + x*t*2 + x^2
b := t^2/x^2 + (2/x - 1)*t
c := t^2/x^2 + (2/x - 1)*t
multi_SPDE(a, b, [c], 0, t +-> t)
``````

Trying to emulate the test from SymPy, but it crashed Fricas. (I'm not sure if this is a correct correspondence either, as `c` must be a list in `multi_SPDE`)

The arguments `a`, etc. are expected to be of type UP. Those are univariate polynomials of an anonymous variable (like PurePoly in SymPy). They should probably be created by something like `univariate(a, t)`. (`t` stands here for the 'kernel' `exp(x)`.) The derivative should be of type `UP -> UP`. There is an example in `intef.spad` (an `expintegrate` call in `expint`):

``````         (x1 : UP) : UP+->differentiate(x1,
(x2 : F) : F+->differentiate(x2, x),
monomial(differentiate(eta, x), 1))
``````

Here `eta` is the argument of `t = exp(x)`, so `differentiate(eta, x)` is equal to 1, and `monomial(differentiate(eta, x), 1)` is the derivative of `exp(x)` (i.e. `exp(x)`) as a polynomial. `(x2 : F) : F+->differentiate(x2, x)` says that the coefficients of the polynomial are differentiated with respect to `x`. (The outer differentiate is a three-argument function of polynomials.)

(I have not found an explanation for the repeated appearance of `UP` and `F`. Maybe the latter is the expected result type.)

• Is it possible to implement the rest of the Risch algorithm in SymPy by copying the code from Fricas?

##### Clone this wiki locally
You can’t perform that action at this time.