4.3 The PARI C-library Interface

(This chapter was written by Martin Albrecht.)

Here is the step-by-step guide to adding a new PARI functions to Sage. We use the Frobenius form of a matrix as an example.

Some heavy lifting for matrices over integers is implemented using the PARI library. To compute the Frobenius form in PARI the matfrobenius function is used.

There are two ways to interact with the PARI library from Sage: The gp interface uses the gp interpreter, and the PARI interface uses direct calls to the PARI C functions -- this is the preferred way as it is much faster. Thus this section focuses on using PARI.

So we will add a new method to the gen class: this is the abstract representation of all PARI library objects. That means that once we add a method to this class, every PARI object, whether it is a number, polynomial or matrix, will have our new method. So you can do pari(1).matfrobenius(), but since PARI wants to apply matfrobenius to matrices, not numbers, you will receive a PariError in this case.

The gen class is defined in SAGE_ROOT/devel/sage/sage/libs/pari/gen.pyx, and this is where we add the method matfrobenius:

    def matfrobenius(self, flag=0):
        """
        matfrobenius(M,{flag}): Return the Frobenius form of the
        square matrix M. If flag is 1, return only the elementary
        divisors. If flag is 2, return a two-components vector [F,B]
        where F is the Frobenius form and B is the basis change
        so that M=B^-1*F*B.
        """
        _sig_on
        return self.new_gen(matfrobenius(self.g, flag))

The _sig_on statement is some magic to prevent SIGSEGVs from the PARI C library to crash the Sage interpreter by catching these signals. Note that self.new_gen() calls a closing _sig_off macro. These two must always come in pairs, i.e. every _sig_on must be matched by a closing _sig_off. The self.new_gen() call constructs a new SAGE-python-gen object from a given pari-C-gen where the pari-C-gen is stored as the SAGE-python-gen.g attribute. The matfrobenius call is just a call to the PARI C library function matfrobenius with the appropriate parameters.

The information about which function to call and how to call it can be retrieved from the PARI user's manual (note: Sage includes the development version of PARI, so check that version of the user's manual). Looking for matfrobenius you can find: "The library syntax is matfrobenius(M,flag)".

In case you are familiar with gp, please note that the PARI C function may have a different name than the corresponding gp function (for example, see mathnf), so always check the manual.

At this point we are done with the PARI interface and can add some more interfaces around it for convenience: first we add a functional representation of the method to SAGE_ROOT/devel/sage/sage/libs/pari/functional.py so we can do matfrobenius(<some.pari.gen>) as well as <some.pari.gen>.matfrobenius():

def matfrobenius(self): return pari(self).matfrobenius()

Then we also add a frobenius(flag) method to the matrix_integer class where we call the matfrobenius() method on the PARI object associated to the matrix after doing some sanity checking. Then we convert output from PARI to Sage objects:

    def frobenius(self,flag=0):
        """
	If flag is 0 (the default value), return the Frobenius 
            form of this matrix.
        If flag is 1, return only the elementary divisors.
        If flag is 2, return a two-component vector [F,B]
            where F is the Frobenius form and B is the basis change
            so that M=B^-1*F*B.

        INPUT:
           flag -- 0,1 or 2 as described above

        ALGORITHM: uses pari's matfrobenius()

        EXAMPLE:
           sage: A = MatrixSpace(IntegerRing(), 3)(range(9))
           sage: A.frobenius(0)
           [ 0  0  0]
           [ 1  0 18]
           [ 0  1 12]
           sage: A.frobenius(1)
           [x3 - 12*x2 - 18*x]
           sage: A.frobenius(2)
           ([ 0  0  0]
           [ 1  0 18]
           [ 0  1 12],
           [    -1      2     -1]
           [     0  23/15 -14/15]
           [     0  -2/15   1/15])
        """
        if self.nrows()!=self.ncols():
            raise ArithmeticError, \
            "frobenius matrix of non-square matrix not defined." 
        v = self._pari_().matfrobenius(flag)
        if flag==0:
            return self.matrix_space()(v.python())
        elif flag==1:
            r = polynomial_ring.PolynomialRing(self.base_ring())
            #BUG: this should be handled in PolynomialRing not here
            return [eval(str(x).replace("^","**"),{},r.gens_dict()) 
                    for x in v.python_list()] 
        elif flag==2:
            F = matrix_space.MatrixSpace(rational_field.RationalField(),
                                         self.nrows())(v[0].python()) 
            B = matrix_space.MatrixSpace(rational_field.RationalField(),
                                         self.nrows())(v[1].python()) 
            return F,B

See About this document... for information on suggesting changes.