To be able to use PARI in , you must write a C program and link it
to the PARI library and the PARI include files. See the installation guide
(given in Appendix A) on how to create and install the PARI
library and include files. A sample Makefile is also given in Appendix B.
Probably the best way to understand how programming is done is to work through a complete example. We will write such a program in section 4.3. Before doing this, a few explanations are in order.
Initializations and universal objects.
First, one must explain to the outside world what kind of objects and programs we are going to use. This is done simply with the statement
#include <genpari.h>
Note that this is usually a link, created when you make the library, to a file called either genpari68k.h (for 680x0-based machines with x at least 2) or genpariother.h (for all other machines).
This file , first exports all the necessary constants, variables
and functions, defines some important macros, and also defines the fundamental
type for all PARI objects: the type
, which is simply
a pointer to long.
Technical note: we would have liked to define a type GEN to be a pointer on itself. This unfortunately is not possible in C, except by using structures, but then the names become unwieldy. On the other hand, by a simple trick, it can be done in Pascal for example. The result of this is that when we will use a component of a PARI object, it will be a long, hence will need to be typecasted to a GEN again if we want to avoid warnings from the compiler. This will sometimes be quite tedious, but of course is trivially done.
To take an example, a polynomial P of degree 2 will be represented by a chunk of memory pointed to by the GEN P. P[0] and P[1] contain code information, in particular the type of the object, the degree of P, etc.... P[2], P[3], P[4] contain pointers to the coefficients of degree 0, 1, and 2 of P respectively (note the ascending order). This is where typecasting will be necessary: in principle P[i] (for i = 2, 3, 4) is a long, but we will want to use it as a GEN. The coefficients P[i] themselves are in chunks of memory whose complexity depends on the types of the coefficients, and so on.
Now we must state the most important law about programming in PARI, which must be respected if one wants to avoid disasters:
Apart from universal objects (see below) the chunks of memory used by a given PARI object must be in consecutive memory locations.
Don't panic: let's see the reason and the meaning of this, and how it can be achieved.
When doing large computations, unwanted intermediate results clutter up memory very fast so some kind of garbage collecting is needed. Most large systems do garbage collecting when the cluttering gets heavy, and this slows down the performance. In PARI we have taken a different approach: you must do your own cleaning up as soon as the intermediate results are not needed. Special purpose routines have been written to do this, but the primary requirement is exactly as stated above: a PARI object must be (essentially) connected. As a consequence of this explanation, one also sees that there is an evident exception to the above law: if your computation is small enough so that you don't need to do any garbage collecting, then just go ahead, PARI won't mind disconnected objects in most cases. However, since PARI routines do their own garbage collecting, watch carefully what you are doing.
The notion of alluded to above is quite simple:
during the execution of your program, a number of objects will have been
defined (by the system or by yourself) with the idea that they stay permanently
with the same values. Examples are the integer 1, the fraction
,
the polynomial X, or a prime p which is used as a base modulus for
integermods or p-adics. These universal objects are of course allowed to be
disconnected from the other PARI objects.
After declaring the use of the file genpari.h, the first executable statement
of a main program should be to initialize the PARI system, and in particular
the PARI stack which will contain all the computations. This is simply done
with a call to the two variable function , like
init(4000000,100000).
The first argument (here 4 million) is the number of bytes given to PARI to
work with (it should not reasonably drop under 500000), and the second is the
upper limit on a precomputed prime number table. If you don't want prime numbers,
just put 2, but put an argument anyway because init() expects one.
We have now at our disposal:
• the following universal objects: the integer 0 ( as a GEN,
as a long), the integer 1 (
as a GEN,
as a long), the integer 2
(
as a GEN,
as a long), the fraction
(
as
a GEN,
as a long), the complex number i,
as a GEN. In addition, space is
reserved for the polynomials 1 (
as a GEN,
as a long),
and the polynomials xv, (
as GENs,
as longs), where
xv is the name of variable number v, where
0≤v≤255. However,
they are not created upon initialization, and it is the programmer's
responsibility to fill them before use. Since this is not very easy, we
advise the user to use the function
which has essentially
the same effect as
except that it can execute a sequence of
expressions and not only a single expression. For example, to prepare for use
the variables a,b,c,x, write
Note that polun and polx are arrays, the index being the polynomial variable number.
• a large PARI containing nothing but (in the present version)
the 167 long words (668 bytes) of the predefined universal objects.
• a which is dealt with in a different way from the stack,
and will contain other permanent universal objects.
• a table of primes.
• access to all the built-in functions of the PARI library.
We have already described many of these functions in the preceding chapters. However some of them are specific to library mode and thus will be explained in this chapter.
Input and output.
Two important aspects have not yet been explained since they are specific to library mode: input and output of PARI objects.
For , PARI provides you with two powerful high level functions which enables
you to input your objects as if you were under GP. In fact, the second
one is
essentially the GP syntactical parser, hence you can use it not only for
input but for any computation that you can do under GP. These functions are
called
and
. The first one has the following syntax:
GEN lisexpr(char* s);
Its effect is to analyze the input string s and to compute the result as in GP. However it is limited to one expression. If you want to read and evaluate a sequence of expressions, use
GEN lisseq(char* s);
Warning: there is a slight difference between these functions and the GP syntactical parser: the expressions and sequences which you use must not contain any spaces.
Once in a while, it may be useful to have the evaluation of the string
involving
a call to a function you have defined in C. The function
allows you to give a name to a function taking 0, 1, 2 or 3 GEN arguments and
returning a
single GEN. The syntax is
void install(GEN (*f)(), char *name, int valence)
where f is the (address of) the function, name its new name, and valence the number of its arguments, an integer between 0 and 3.
For , there exist four different functions. First, you can
use the function
with the following syntax:
void sor(GEN x,char format,long dec,long field);
Here format is either 'e', 'f' or 'g' corresponding to the three output formats of GP, dec is the number of printed significant digits for real numbers, and should be put equal to -1 if all of them are wanted, and field corresponds to the field width of GP used for printing integers.
A default use of this function is to use the macro (GEN x) which is
equivalent to sor(x,'g',-1,0).
The second format corresponds to the ``raw'' format of GP (see section 2.2.5)
and is obtained by using the function with the following syntax:
void brute(GEN x,char format,long dec);
A default use of this function is to use the macro (GEN x) which
is equivalent to brute(x,'g',-1).
The third format corresponds to the texprint function of GP, and gives a TEX output of the result. It is obtained by using the function texe with the following syntax:
void (GEN x,char format,long dec);
Finally, you can use the corresponding to the GP
command
\x using the function
with the following syntax:
void voir(GEN x,-1);
Again the last parameter must be given and put equal to -1. In principle this last type of output is used only for debugging purposes.