Subsections

8. Programming SAGE using Singular

(The first version of this chapter was written by David Joyner.)

Using Singular functions from SAGE is not much different conceptually than using GAP functions from SAGE. This can range from very easy to hard, depending on how much of the data structure of the output of the Singular function is already present in SAGE.

First, some terminology. For us, a curve $ X$ over a finite field $ F$ is a equation of the form $ f(x,y)=0$ , where $ f\in F[x,y]$ is a polynomial. It may or may not be singular. A place of degree $ d$ is a Galois orbit of $ d$ points in $ X(E)$ , where $ E/F$ is degree $ d$ . For example, a place of degree $ 1$ is also a place of degree $ 3$ , but a place of degree $ 2$ is not since no degree $ 3$ extension of $ F$ contains a degree $ 2$ extension. Places of degree $ 1$ are also called $ F$ -rational points.

As an example of the SAGE-Singular interface, we will explain how to wrap Singular's NSplaces, which computes places on a curve over a finite field. (The command closed_points also does this in some cases.) This is ``easy'' since no new Python classes are needed in SAGE to carry this out.

Here's an example of how to use this command in Singular:

 A Computer Algebra System for Polynomial Computations   /   version 3-0-0
                                                       0<
     by: G.-M. Greuel, G. Pfister, H. Schoenemann        \   May 2005
FB Mathematik der Universitaet, D-67653 Kaiserslautern    \
> LIB "brnoeth.lib";
[...]
> ring s=5,(x,y),lp;
> poly f=y^2-x^9-x;
> list X1=Adj_div(f);
Computing affine singular points ...
Computing all points at infinity ...
Computing affine singular places ...
Computing singular places at infinity ...
Computing non-singular places at infinity ...
Adjunction divisor computed successfully

The genus of the curve is 4
> list X2=NSplaces(1,X1);
Computing non-singular affine places of degree 1 ...
> list X3=extcurve(1,X2);

Total number of rational places : 6

> def R=X3[1][5];
> setring R;
> POINTS;
[1]:
   [1]:
      0
   [2]:
      1
   [3]:
      0
[2]:
   [1]:
      -2
   [2]:
      1
   [3]:
      1
[3]:
   [1]:
      -2
   [2]:
      1
   [3]:
      1
[4]:
   [1]:
      -2
   [2]:
      -1
   [3]:
      1
[5]:
   [1]:
      2
   [2]:
      -2
   [3]:
      1
[6]:
   [1]:
      0
   [2]:
      0
   [3]:
      1

Here's one way of doing this same calculation in the SAGE interface to Singular:

sage: singular.LIB("brnoeth.lib")
sage: singular.ring(5,'(x,y)','lp')
    //   characteristic : 5
    //   number of vars : 2
    //        block   1 : ordering lp
    //                  : names    x y
    //        block   2 : ordering C
sage: f = singular('y^2-x^9-x')
sage: print singular.eval("list X1=Adj_div(%s);"%f.name())
Computing affine singular points ...
Computing all points at infinity ...
Computing affine singular places ...
Computing singular places at infinity ...
Computing non-singular places at infinity ...
Adjunction divisor computed successfully
<BLANKLINE>
The genus of the curve is 4
sage: print singular.eval("list X2=NSplaces(1,X1);")
Computing non-singular affine places of degree 1 ...
sage: print singular.eval("list X3=extcurve(1,X2);")
<BLANKLINE>
Total number of rational places : 6
<BLANKLINE>
sage: singular.eval("def R=X3[1][5];")
''
sage: singular.eval("setring R;")
''
sage: L = singular.eval("POINTS;")
sage: print L
[1]:
   [1]:
      0
   [2]:
      1
   [3]:
      0
[2]:
   [1]:
      0
   [2]:
      0
   [3]:
      1
[3]:
   [1]:
      -2
   [2]:
      1
   [3]:
      1
[4]:
   [1]:
      2
   [2]:
      -2
   [3]:
      1
[5]:
   [1]:
      2
   [2]:
      2
   [3]:
      1
[6]:
   [1]:
      -2
   [2]:
      -1
   [3]:
      1

>From looking at the output, notice that our wrapper program will need to parse the string represented by $ L$ above.

Let us write a separate program to do just that. This requires figuring out how to determine where the coordinates of the points are placed in the string L. Python has some very useful string manipulation commands to do just that.

def points_parser(string_points,F):
    """
    This program will parse a string of points
    of X over a finite field F returned by Singular's NSplaces 
    command into a Python list of points with entries from F.
    EXAMPLE
    sage: F = GF(5)
    sage: points_parser(L,F)
          ((0, 1, 0), (3, 4, 1), (0, 0, 1), (2, 3, 1), (3, 1, 1), (2, 2, 1))
    """
    Pts=[]
    n=len(L)
    #print n
    #start block to compute a pt
    L1=L
    while len(L1)>32:
        idx=L1.index("     ")
        pt=[]
        ## start block1 for compute pt
        idx=L1.index("     ")
        idx2=L1[idx:].index("\n")
        L2=L1[idx:idx+idx2]
        #print L2
        pt.append(F(eval(L2)))
        # end block1 to compute pt
        L1=L1[idx+8:] # repeat block 2 more times
        #print len(L1)
        ## start block2 for compute pt
        idx=L1.index("     ")
        idx2=L1[idx:].index("\n")
        L2=L1[idx:idx+idx2]
        pt.append(F(eval(L2)))
        # end block2 to compute pt
        L1=L1[idx+8:] # repeat block 1 more time
        ## start block3 for compute pt
        idx=L1.index("     ")
        if "\n" in L1[idx:]:
            idx2=L1[idx:].index("\n")
        else:
            idx2=len(L1[idx:])
        L2=L1[idx:idx+idx2]
        pt.append(F(eval(L2)))
        #print pt
        # end block3 to compute pt
        #end block to compute a pt
        Pts.append(tuple(pt))  # repeat until no more pts
        L1=L1[idx+8:] # repeat block 2 more times
    return tuple(Pts)

Now it is an easy matter to put these ingredients together into a SAGE function which takes as input a triple $ (f,F,d)$ : finite field $ F$ of prime order, a polynomial $ f$ in $ F[x,y]$ defining $ X: f(x,y)=0$ (note the variables $ x,y$ must be used), and the degree $ d$ . The output is the number of points of places in $ X$ of degree $ d=1$ over $ F$ . At the moment, their is no ``translation'' between elements of $ GF(p^d)$ in Singular and SAGE unless $ d=1$ . So, for this reason, we restrict ourselves to points of degree one.

def places_on_curve(f,F):
    """
    INPUT:
        f -- element of F[x,y], defining X: f(x,y)=0
        F -- a finite field of *prime order*
        
    OUTPUT:
        integer -- the number of places in X of degree d=1 over F

    EXAMPLES:
        sage: F=GF(5)
        sage: R=MPolynomialRing(F,2,names=["x","y"])
        sage: x,y=R.gens()
        sage: f=y^2-x^9-x
        sage: places_on_curve(f,F)
        ((0, 1, 0), (3, 4, 1), (0, 0, 1), (2, 3, 1), (3, 1, 1), (2, 2, 1))
    """
    d = 1
    p = F.characteristic()
    singular.eval('LIB "brnoeth.lib";')
    singular.eval("ring s="+str(p)+",(x,y),lp;")
    singular.eval("poly f="+str(f))
    singular.eval("list X1=Adj_div(f);") 
    singular.eval("list X2=NSplaces("+str(d)+",X1);") 
    singular.eval("list X3=extcurve("+str(d)+",X2);") 
    singular.eval("def R=X3[1][5];") 
    singular.eval("setring R;") 
    L = singular.eval("POINTS;")
    return points_parser(L,F)
The triple quoted string documentation is important! It not only helps users understand your function's use and syntax but also, if the function is included in SAGE, will be reproduced in the printed and online documentation, and automatically tested (even the output) before each SAGE release.

Note also that the ordering returned by this SAGE function is exactly the same as the ordering in the Singular variable POINTS.

Some examples:

sage: F = GF(5)
sage: R = MPolynomialRing(F,2,names = ["x","y"])
sage: x,y = R.gens()
sage: f = y^2-x^9-x
sage: places_on_curve(f,F)
      ((0, 1, 0), (3, 4, 1), (0, 0, 1), (2, 3, 1), (3, 1, 1), (2, 2, 1))

sage: F = GF(2)
sage: R = MPolynomialRing(F,2,names = ["x","y"])
sage: x,y = R.gens()
sage: f = x^3*y+y^3+x
sage: places_on_curve(f,F)
      ((0, 1, 0), (1, 0, 0), (0, 0, 1))

8.1 Another Approach

There is also a more python-like interface to Singular. Using this the code is much simpler, as illustrated below. First we demonstrate computing the places on a curve in a particular case.

sage: singular.lib('brnoeth.lib')
sage: R = singular.ring(5, '(x,y)', 'lp')
sage: f = singular.new('y^2 - x^9 - x')
sage: X1 = f.Adj_div()
sage: X2 = singular.NSplaces(1, X1)
sage: X3 = singular.extcurve(1, X2)
sage: R = X3[1][5]
sage: singular.set_ring(R)
sage: L = singular.new('POINTS')
sage: [(L[i][1], L[i][2], L[i][3]) for i in range(1,7)]
      [(0, 1, 0), (-2, 1, 1), (0, 0, 1), (2, 2, 1), (-2, -1, 1), (2, -2, 1)]
The name method of a Singular object returns the name of that object in the Singular interpreter, so that it can be used as input to a Singular function.

Next we implement the general function (we omit the docstring, which is the same as above). Note that the point_parser function is not required.

def places_on_curve(f,F):
    p = F.characteristic()
    if F.degree() > 1:
        raise NotImplementedError
    singular.lib('brnoeth.lib')
    R = singular.ring(5, '(x,y)', 'lp')
    f = singular.new('y^2 - x^9 - x')
    X1 = f.Adj_div()
    X2 = singular.NSplaces(1, X1)
    X3 = singular.extcurve(1, X2)
    R = X3[1][5]
    singular.setring(R)
    L = singular.new('POINTS')
    return [(int(L[i][1]), int(L[i][2]), int(L[i][3])) \
             for i in range(1,int(L.size())+1)]

This code is much shorter, nice, and more readable. However, it depends on certain functions, e.g., singular.setring having been implemented in the SAGE/Singular interface, whereas the code in the previous section used only the barest minimum of that interface.

8.2 Projects

For the interested reader, here are a few ideas for some other wrappers, which are not yet implemented in SAGE.

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