
A.1.2 Writing procedures and libraries
The user may add their own commands to the commands already available in
SINGULAR by writing SINGULAR procedures. There are basically
two kinds of procedures:
 procedures written in the SINGULAR programming language (which are
usually collected in SINGULAR libraries).
 procedures written in C/C++ (collected in dynamic modules).
At this point, we restrict ourselves to describing the first kind of
(library) procedures, which are sufficient for most applications. The
syntax and general structure of a library (procedure) is described in
Procedures, and Libraries.
The probably most efficient way of
writing a new library is to use one of the official SINGULAR
libraries, say ring.lib as a sample. On a Unixlike operating
system, type LIB "ring.lib"; to get information on where the
libraries are stored on your disk.
SINGULAR provides several commands and tools, which may be useful
when writing a procedure, for instance, to have a look at intermediate
results (see Debugging tools).
If such a libarary should be contributed to SINGULAR some formal
requirements are needed:

the library header must explain the purpose of the library and
(for nontrivial algorithm) a pointer to the algorithm (text book, article, etc.)

all global procedures must have a help string and an example which shows its usage.

it is strongly recommend also to provide test scripts which test the functionality: one should test the essential functionality of the
library/command in a relatively short time (say, in no more than 30s),
other tests should check the functionality of the
library/command in detail so that, if possible, all relevant
cases/results are tested. Nevertheless, such a test should not run
longer than, say, 10 minutes.
We give short examples of procedures to demonstrate the following:

Write procedures which return an integer (ring independent), see also
Milnor and Tjurina number. (Here we restrict ourselves to the main
body of the procedures).

The procedure
milnorNumber must be called with one parameter, a
polynomial.
The name g is local to the procedure and is killed automatically when
leaving the procedure.
milnorNumber returns the Milnor number (and displays a comment).

The procedure
tjurinaNumber has no specified number of
parameters. Here, the parameters are referred to by #[1] for
the 1st, #[2] for the 2nd parameter, etc.
tjurinaNumber returns the Tjurina number (and displays a comment).

the procedure
milnor_tjurina which returns a list consisting of two
integers, the Milnor and the Tjurina number.

Write a procedure which creates a new ring and returns data dependent on
this new ring (two numbers) and an int. In this example, we also show
how to write a help text for the procedure (which is optional, but
recommended).
 proc milnorNumber (poly g)
{
"Milnor number:";
return(vdim(std(jacob(g))));
}
proc tjurinaNumber
{
"Tjurina number:";
return(vdim(std(jacob(#[1])+#[1])));
}
proc milnor_tjurina (poly f)
{
ideal j=jacob(f);
list L=vdim(std(j)),vdim(std(j+f));
return(L);
}
proc real_sols (number b, number c)
"USAGE: real_sols (b,c); b,c number
ASSUME: active basering has characteristic 0
RETURN: list: first entry is an integer (the number of different real
solutions). If this number is nonnegative, the list has as second
entry a ring in which the list SOL of real solutions of x^2+bx+c=0
is stored (as floating point number, precision 30 digits).
NOTE: This procedure calls laguerre_solve from solve.lib.
"
{
def oldring = basering; // assign name to the ring active when
// calling the procedure
number disc = b^24*c;
if (disc>0) { int n_of_sols = 2; }
if (disc==0) { int n_of_sols = 1; }
string s = nameof(var(1)); // name of first ring variable
if (disc>=0) {
execute("ring rinC =(complex,30),("+s+"),lp;");
if (not(defined(laguerre_solve))) { LIB "solve.lib"; }
poly f = x2+imap(oldring,b)*x+imap(oldring,c);
// f is a local ringdependent variable
list SOL = laguerre_solve(f,30);
export SOL; // make SOL a global ringdependent variable
// such variables are still accessible when the
// ring is among the return values of the proc
setring oldring;
return(list(n_of_sols,rinC));
}
else {
return(list(0));
}
}
//
// We now apply the procedures which are defined by the
// lines of code above:
//
ring r = 0,(x,y),ds;
poly f = x7+y7+(xy)^2*x2y2;
milnorNumber(f);
==> Milnor number:
==> 28
tjurinaNumber(f);
==> Tjurina number:
==> 24
milnor_tjurina(f); // a list containing Milnor and Tjurina number
==> [1]:
==> 28
==> [2]:
==> 24
def L=real_sols(2,1);
L[1]; // number of real solutions of x^2+2x+1
==> 1
def R1=L[2];
setring R1;
listvar(R1); // only global ringdependent objects are still alive
==> // R1 [0] *ring
==> // SOL [0] list, size: 2
SOL; // the real solutions
==> [1]:
==> 1
==> [2]:
==> 1
setring r;
L=real_sols(1,1);
L[1]; // number of reals solutions of x^2+x+1
==> 0
setring r;
L=real_sols(1,5);
L[1]; // number of reals solutions of x^2+x5
==> 2
def R3=L[2];
setring R3; SOL; // the real solutions
==> [1]:
==> 2.791287847477920003294023596864
==> [2]:
==> 1.791287847477920003294023596864

Writing a dynamic module is not as simple as writing a library
procedure, since it does not only require some knowledge of C/C++, but
also about the way the SINGULAR kernel works.
See also Dynamic modules.
