Schnittstellen#

Ein zentraler Aspekt von Sage ist, dass es Berechnungen mit Objekten vieler verschiedener Computer Algebra Systeme unter einem Dach durch eine einheitliche Schnittstelle und Programmiersprache vereinigt.

Die console und interact Methoden einer Schnittstelle unterstützen viele verschiedene Dinge. Zum Beispiel, anhand von GAP:

  1. gap.console(): Öffnet die GAP Konsole und übergibt GAP die Kontrolle. Hier ist Sage nichts weiter als ein praktischer Programmstarter, ähnlich einer Linux-Bash-Konsole.

  2. gap.interact(): Ist eine einfache Art mit einer GAP Instanz zu interagieren, die Sage Objekte enthalten kann. Sie können Sage Objekte in diese GAP Sitzung importieren (sogar von der interaktiven Schnittstelle aus), usw.

GP/PARI#

PARI ist ein kompaktes, sehr ausgereiftes, stark optimiertes C-Programm, dessen primärer Fokus Zahlentheorie ist. Es gibt zwei sehr verschiedene Schnittstellen, die Sie in Sage nutzen können:

  • gp – Der „G o P ARI“ Interpreter und

  • pari – Die PARI-C-Bibliothek.

Die folgenden Zeilen zum Beispiel sind zwei Wege, genau das gleiche zu tun. Sie sehen identisch aus, aber die Ausgabe ist verschieden, und was hinter den Kulissen passiert ist völlig unterschiedlich.

sage: gp('znprimroot(10007)')
Mod(5, 10007)
sage: pari('znprimroot(10007)')
Mod(5, 10007)
>>> from sage.all import *
>>> gp('znprimroot(10007)')
Mod(5, 10007)
>>> pari('znprimroot(10007)')
Mod(5, 10007)

Im ersten Fall wird eine separate Kopie des GP-Interpreters als Server gestartet, die Zeichenkette 'znprimroot(10007)' übergeben, von GP ausgewertet und das Ergebnis wird einer Variable in GP zugewiesen (was Platz im Speicher des GP-Unterprozesses benötigt, der nicht wieder freigegeben wird). Dann wird der Wert der Variablen erst angezeigt. Im zweiten Fall wird kein separates Programm gestartet, stattdessen wird die Zeichenkette 'znprimroot(10007)' von einer bestimmten PARI-C-Bibliotheksfunktion ausgewertet. Das Ergebnis wird im Speicher von Python gehalten, welcher freigegeben wird wenn die Variable nicht mehr referenziert wird. Die Objekte haben außerdem verschiedene Typen:

sage: type(gp('znprimroot(10007)'))
<class 'sage.interfaces.gp.GpElement'>
sage: type(pari('znprimroot(10007)'))
<class 'cypari2.gen.Gen'>
>>> from sage.all import *
>>> type(gp('znprimroot(10007)'))
<class 'sage.interfaces.gp.GpElement'>
>>> type(pari('znprimroot(10007)'))
<class 'cypari2.gen.Gen'>

Welche Variante sollten Sie also nutzen? Das kommt darauf an was Sie tun. Die GP-Schnittstelle kann alles was ein normales GP/PARI-Konsolenprogramm könnte, da es das Programm startet. Genauergesagt könnten Sie komplizierte PARI-Programme laden und laufen lassen. Im Gegensatz dazu ist die PARI-Schnittstelle (mittels C-Bibliothek) wesentlich restriktiver. Zuerst einmal sind nicht alle Unterfunktionen implementiert. Außerdem wird relativ viel Quellcode nicht in der PARI-Schnittstelle funktionieren, z.B. numerisches Integrieren. Abgesehen davon ist die PARI-Schnittstelle wesentlich schneller und robuster als die von GP.

(Wenn der GP-Schnittstelle der Speicher ausgeht beim Auswerten einer Zeile, wird sie automatisch und unbemerkt den Speicherbereich verdoppeln und das Auswerten erneut versuchen. Dadurch wird Ihre Berechnung nicht abstürzen, falls Sie den benötigen Speicher falsch eingeschätzt haben. Das ist eine hilfreiche Erweiterung, die der gewöhnliche GP-Interpreter nicht bietet. Die PARI-C-Bibliothek hingegen kopiert jedes neue Objekt sofort vom PARI-Stack, daher wird der Stapel nicht größer. Allerdings muss jedes Objekt kleiner als 100 MB sein, da ansonsten der Stapel „überläuft“, wenn das Objekt erstellt wird. Dieses zusätzliche Kopieren erzeugt allerdings ein wenig Leistungseinbußen.)

Zusammengefasst nutzt Sage also die PARI-C-Bibliothek um Funktionalitäten eines GP/PARI-Interpreters bereitzustellen, allerdings mit einer anderen komplizierten Speicherverwaltung und der Programmiersprache Python.

Zuerst erstellen wir eine PARI-Liste aus einer Python-Liste.

sage: v = pari([1,2,3,4,5])
sage: v
[1, 2, 3, 4, 5]
sage: type(v)
<class 'cypari2.gen.Gen'>
>>> from sage.all import *
>>> v = pari([Integer(1),Integer(2),Integer(3),Integer(4),Integer(5)])
>>> v
[1, 2, 3, 4, 5]
>>> type(v)
<class 'cypari2.gen.Gen'>

Jedes PARI-Objekt ist vom Typ Gen. Den PARI Typ des vorliegenden Objekts können Sie mit der type Unterfunktion herausfinden.

sage: v.type()
't_VEC'
>>> from sage.all import *
>>> v.type()
't_VEC'

Um eine elliptische Kurve in PARI zu erstellen geben Sie ellinit([1,2,3,4,5]) ein. Bei Sage ist es ähnlich, nur dass ellinit eine Methode ist, die von jedem PARI-Objekt aus aufgerufen werden kann, z.B. unser t_VEC \(v\).

sage: e = v.ellinit()
sage: e.type()
't_VEC'
sage: pari(e)[:13]
[1, 2, 3, 4, 5, 9, 11, 29, 35, -183, -3429, -10351, 6128487/10351]
>>> from sage.all import *
>>> e = v.ellinit()
>>> e.type()
't_VEC'
>>> pari(e)[:Integer(13)]
[1, 2, 3, 4, 5, 9, 11, 29, 35, -183, -3429, -10351, 6128487/10351]

Jetzt haben wir eine elliptische Kurve als Objekt und können einige Dinge mit ihr berechnen.

sage: e.elltors()
[1, [], []]
sage: e.ellglobalred()
[10351, [1, -1, 0, -1], 1, [11, 1; 941, 1], [[1, 5, 0, 1], [1, 5, 0, 1]]]
sage: f = e.ellchangecurve([1,-1,0,-1])
sage: f[:5]
[1, -1, 0, 4, 3]
>>> from sage.all import *
>>> e.elltors()
[1, [], []]
>>> e.ellglobalred()
[10351, [1, -1, 0, -1], 1, [11, 1; 941, 1], [[1, 5, 0, 1], [1, 5, 0, 1]]]
>>> f = e.ellchangecurve([Integer(1),-Integer(1),Integer(0),-Integer(1)])
>>> f[:Integer(5)]
[1, -1, 0, 4, 3]

GAP#

Sage enthält ausserdem GAP für diskrete Mathematik, insbesondere Gruppentheorie.

Hier ist ein Beispiel für GAP’s IdGroup-Funktion, die die optionale kleine Gruppen Datenbank benötigt, die separat installiert werden muss, wie unten beschrieben.

sage: G = gap('Group((1,2,3)(4,5), (3,4))')
sage: G
Group( [ (1,2,3)(4,5), (3,4) ] )
sage: G.Center()
Group( () )
sage: G.IdGroup()
[ 120, 34 ]
sage: G.Order()
120
>>> from sage.all import *
>>> G = gap('Group((1,2,3)(4,5), (3,4))')
>>> G
Group( [ (1,2,3)(4,5), (3,4) ] )
>>> G.Center()
Group( () )
>>> G.IdGroup()
[ 120, 34 ]
>>> G.Order()
120

Wir können die gleiche Berechnung in Sage durchführen ohne vorher explizit die GAP-Schnittstelle aufzurufen:

sage: G = PermutationGroup([[(1,2,3),(4,5)],[(3,4)]])
sage: G.center()
Subgroup generated by [()] of (Permutation Group with generators [(3,4), (1,2,3)(4,5)])
sage: G.group_id()
[120, 34]
sage: n = G.order(); n
120
>>> from sage.all import *
>>> G = PermutationGroup([[(Integer(1),Integer(2),Integer(3)),(Integer(4),Integer(5))],[(Integer(3),Integer(4))]])
>>> G.center()
Subgroup generated by [()] of (Permutation Group with generators [(3,4), (1,2,3)(4,5)])
>>> G.group_id()
[120, 34]
>>> n = G.order(); n
120

Nach Installation eine optionale Sage-Pakete mit folgendem Befehl sind weitere GAP-Funktionen verfügbar:

sage -i gap_packages

Singular#

Singular bietet eine sehr gute, ausgereifte Bibliothek für Gröbnerbasen, größte gemeinsame Teiler von mehrdimensionalen Polynomen, Basen von Riemann-Roch Räumen einer planaren Kurve und Faktorisierungen unter anderem. Wir zeigen hier die Faktorisierung mehrdimensionaler Polynome mit Sages Singular-Schnittstelle (ohne die ....:):

sage: R1 = singular.ring(0, '(x,y)', 'dp')
sage: R1
polynomial ring, over a field, global ordering
//   coefficients: QQ
//   number of vars : 2
//        block   1 : ordering dp
//                  : names    x y
//        block   2 : ordering C
sage: f = singular('9*y^8 - 9*x^2*y^7 - 18*x^3*y^6 - 18*x^5*y^6 +'
....:     '9*x^6*y^4 + 18*x^7*y^5 + 36*x^8*y^4 + 9*x^10*y^4 - 18*x^11*y^2 -'
....:     '9*x^12*y^3 - 18*x^13*y^2 + 9*x^16')
>>> from sage.all import *
>>> R1 = singular.ring(Integer(0), '(x,y)', 'dp')
>>> R1
polynomial ring, over a field, global ordering
//   coefficients: QQ
//   number of vars : 2
//        block   1 : ordering dp
//                  : names    x y
//        block   2 : ordering C
>>> f = singular('9*y^8 - 9*x^2*y^7 - 18*x^3*y^6 - 18*x^5*y^6 +'
...     '9*x^6*y^4 + 18*x^7*y^5 + 36*x^8*y^4 + 9*x^10*y^4 - 18*x^11*y^2 -'
...     '9*x^12*y^3 - 18*x^13*y^2 + 9*x^16')

Wir haben also das Polynom \(f\) definiert, nun geben wir es aus und faktorisieren es.

sage: f
9*x^16-18*x^13*y^2-9*x^12*y^3+9*x^10*y^4-18*x^11*y^2+36*x^8*y^4+18*x^7*y^5-18*x^5*y^6+9*x^6*y^4-18*x^3*y^6-9*x^2*y^7+9*y^8
sage: f.parent()
Singular
sage: F = f.factorize(); F
[1]:
   _[1]=9
   _[2]=x^6-2*x^3*y^2-x^2*y^3+y^4
   _[3]=-x^5+y^2
[2]:
   1,1,2
sage: F[1][2]
x^6-2*x^3*y^2-x^2*y^3+y^4
>>> from sage.all import *
>>> f
9*x^16-18*x^13*y^2-9*x^12*y^3+9*x^10*y^4-18*x^11*y^2+36*x^8*y^4+18*x^7*y^5-18*x^5*y^6+9*x^6*y^4-18*x^3*y^6-9*x^2*y^7+9*y^8
>>> f.parent()
Singular
>>> F = f.factorize(); F
[1]:
   _[1]=9
   _[2]=x^6-2*x^3*y^2-x^2*y^3+y^4
   _[3]=-x^5+y^2
[2]:
   1,1,2
>>> F[Integer(1)][Integer(2)]
x^6-2*x^3*y^2-x^2*y^3+y^4

Genau wie im GAP Beispiel in GAP, können wir diese Faktorisierung berechnen ohne explizit die Singular-Schnittstelle zu nutzen. (Dennoch nutzt Sage im Hintergrund die Singular-Schnittstelle für die Berechnung.) Bitte geben Sie ein ohne ....::

sage: x, y = QQ['x, y'].gens()
sage: f = (9*y^8 - 9*x^2*y^7 - 18*x^3*y^6 - 18*x^5*y^6 + 9*x^6*y^4
....:     + 18*x^7*y^5 + 36*x^8*y^4 + 9*x^10*y^4 - 18*x^11*y^2 - 9*x^12*y^3
....:     - 18*x^13*y^2 + 9*x^16)
sage: factor(f)
(9) * (-x^5 + y^2)^2 * (x^6 - 2*x^3*y^2 - x^2*y^3 + y^4)
>>> from sage.all import *
>>> x, y = QQ['x, y'].gens()
>>> f = (Integer(9)*y**Integer(8) - Integer(9)*x**Integer(2)*y**Integer(7) - Integer(18)*x**Integer(3)*y**Integer(6) - Integer(18)*x**Integer(5)*y**Integer(6) + Integer(9)*x**Integer(6)*y**Integer(4)
...     + Integer(18)*x**Integer(7)*y**Integer(5) + Integer(36)*x**Integer(8)*y**Integer(4) + Integer(9)*x**Integer(10)*y**Integer(4) - Integer(18)*x**Integer(11)*y**Integer(2) - Integer(9)*x**Integer(12)*y**Integer(3)
...     - Integer(18)*x**Integer(13)*y**Integer(2) + Integer(9)*x**Integer(16))
>>> factor(f)
(9) * (-x^5 + y^2)^2 * (x^6 - 2*x^3*y^2 - x^2*y^3 + y^4)

Maxima#

Das in Lisp implementierte Maxima ist ein Teil von Sage. Hingegen wird das gnuplot-Paket (welches Maxima standardmäßig zum plotten nutzt) als optionales Sage-Paket angeboten. Neben anderen Dingen rechnet Maxima mit Symbolen. Maxima integriert und differenziert Funktionen symbolisch, löst gewöhnliche Differentialgleichungen ersten Grades sowie viele lineare Differentialgleichungen zweiten Grades und besitzt eine Methode zur Laplace Transformation linearer Differentialgleichungen von beliebigem Grad. Maxima kennt eine große Zahl spezieller Funktionen, plottet mittels gnuplot und hat Methoden, um Polynomgleichungen oder Matrizen zu lösen oder zu verändern (z.B. Zeilenelimination oder Eigenwerte und Eigenvektoren berechnen).

Wir zeigen die Sage/Maxima Schnittstelle, indem wir die Matrix konstruieren, deren \(i,j\) Eintrag gerade \(i/j\) ist, für \(i,j=1,\ldots,4\).

sage: f = maxima.eval('ij_entry[i,j] := i/j')
sage: A = maxima('genmatrix(ij_entry,4,4)'); A
matrix([1,1/2,1/3,1/4],[2,1,2/3,1/2],[3,3/2,1,3/4],[4,2,4/3,1])
sage: A.determinant()
0
sage: A.echelon()
matrix([1,1/2,1/3,1/4],[0,0,0,0],[0,0,0,0],[0,0,0,0])
sage: A.eigenvalues()
[[0,4],[3,1]]
sage: A.eigenvectors().sage()
[[[0, 4], [3, 1]], [[[1, 0, 0, -4], [0, 1, 0, -2], [0, 0, 1, -4/3]], [[1, 2, 3, 4]]]]
>>> from sage.all import *
>>> f = maxima.eval('ij_entry[i,j] := i/j')
>>> A = maxima('genmatrix(ij_entry,4,4)'); A
matrix([1,1/2,1/3,1/4],[2,1,2/3,1/2],[3,3/2,1,3/4],[4,2,4/3,1])
>>> A.determinant()
0
>>> A.echelon()
matrix([1,1/2,1/3,1/4],[0,0,0,0],[0,0,0,0],[0,0,0,0])
>>> A.eigenvalues()
[[0,4],[3,1]]
>>> A.eigenvectors().sage()
[[[0, 4], [3, 1]], [[[1, 0, 0, -4], [0, 1, 0, -2], [0, 0, 1, -4/3]], [[1, 2, 3, 4]]]]

Hier ein anderes Beispiel:

sage: A = maxima("matrix ([1, 0, 0], [1, -1, 0], [1, 3, -2])")
sage: eigA = A.eigenvectors()
sage: V = VectorSpace(QQ,3)
sage: eigA
[[[-2,-1,1],[1,1,1]],[[[0,0,1]],[[0,1,3]],[[1,1/2,5/6]]]]
sage: v1 = V(sage_eval(repr(eigA[1][0][0]))); lambda1 = eigA[0][0][0]
sage: v2 = V(sage_eval(repr(eigA[1][1][0]))); lambda2 = eigA[0][0][1]
sage: v3 = V(sage_eval(repr(eigA[1][2][0]))); lambda3 = eigA[0][0][2]

sage: M = MatrixSpace(QQ,3,3)
sage: AA = M([[1,0,0],[1, - 1,0],[1,3, - 2]])
sage: b1 = v1.base_ring()
sage: AA*v1 == b1(lambda1)*v1
True
sage: b2 = v2.base_ring()
sage: AA*v2 == b2(lambda2)*v2
True
sage: b3 = v3.base_ring()
sage: AA*v3 == b3(lambda3)*v3
True
>>> from sage.all import *
>>> A = maxima("matrix ([1, 0, 0], [1, -1, 0], [1, 3, -2])")
>>> eigA = A.eigenvectors()
>>> V = VectorSpace(QQ,Integer(3))
>>> eigA
[[[-2,-1,1],[1,1,1]],[[[0,0,1]],[[0,1,3]],[[1,1/2,5/6]]]]
>>> v1 = V(sage_eval(repr(eigA[Integer(1)][Integer(0)][Integer(0)]))); lambda1 = eigA[Integer(0)][Integer(0)][Integer(0)]
>>> v2 = V(sage_eval(repr(eigA[Integer(1)][Integer(1)][Integer(0)]))); lambda2 = eigA[Integer(0)][Integer(0)][Integer(1)]
>>> v3 = V(sage_eval(repr(eigA[Integer(1)][Integer(2)][Integer(0)]))); lambda3 = eigA[Integer(0)][Integer(0)][Integer(2)]

>>> M = MatrixSpace(QQ,Integer(3),Integer(3))
>>> AA = M([[Integer(1),Integer(0),Integer(0)],[Integer(1), - Integer(1),Integer(0)],[Integer(1),Integer(3), - Integer(2)]])
>>> b1 = v1.base_ring()
>>> AA*v1 == b1(lambda1)*v1
True
>>> b2 = v2.base_ring()
>>> AA*v2 == b2(lambda2)*v2
True
>>> b3 = v3.base_ring()
>>> AA*v3 == b3(lambda3)*v3
True

Zuletzt noch ein Beispiel wie man Sage zum Plotten mittels openmath nutzt. Einige von ihnen wurden (verändert) aus dem Maxima Benutzerhandbuch entnommen.

Ein 2D-Plot verschiedener Funktionen (ohne ....: eingeben):

sage: maxima.plot2d('[cos(7*x),cos(23*x)^4,sin(13*x)^3]','[x,0,1]', # not tested
....:     '[plot_format,openmath]')
>>> from sage.all import *
>>> maxima.plot2d('[cos(7*x),cos(23*x)^4,sin(13*x)^3]','[x,0,1]', # not tested
...     '[plot_format,openmath]')

Ein „live“ 3D-Plot, den man mit der Maus bewegen kann:

sage: maxima.plot3d ("2^(-u^2 + v^2)", "[u, -3, 3]", "[v, -2, 2]", # not tested
....:     '[plot_format, openmath]')
sage: maxima.plot3d("atan(-x^2 + y^3/4)", "[x, -4, 4]", "[y, -4, 4]", # not tested
....:     "[grid, 50, 50]",'[plot_format, openmath]')
>>> from sage.all import *
>>> maxima.plot3d ("2^(-u^2 + v^2)", "[u, -3, 3]", "[v, -2, 2]", # not tested
...     '[plot_format, openmath]')
>>> maxima.plot3d("atan(-x^2 + y^3/4)", "[x, -4, 4]", "[y, -4, 4]", # not tested
...     "[grid, 50, 50]",'[plot_format, openmath]')

Der nächste Plot ist das berühmte Möbiusband:

sage: maxima.plot3d("[cos(x)*(3 + y*cos(x/2)), sin(x)*(3 + y*cos(x/2)), y*sin(x/2)]", # not tested
....:     "[x, -4, 4]", "[y, -4, 4]",
....:     '[plot_format, openmath]')
>>> from sage.all import *
>>> maxima.plot3d("[cos(x)*(3 + y*cos(x/2)), sin(x)*(3 + y*cos(x/2)), y*sin(x/2)]", # not tested
...     "[x, -4, 4]", "[y, -4, 4]",
...     '[plot_format, openmath]')

Und der letzte ist die berühmte Kleinsche Flasche:

sage: _ = maxima("expr_1: 5*cos(x)*(cos(x/2)*cos(y) + sin(x/2)*sin(2*y)+ 3.0) - 10.0")
sage: _ = maxima("expr_2: -5*sin(x)*(cos(x/2)*cos(y) + sin(x/2)*sin(2*y)+ 3.0)")
sage: _ = maxima("expr_3: 5*(-sin(x/2)*cos(y) + cos(x/2)*sin(2*y))")
sage: maxima.plot3d ("[expr_1, expr_2, expr_3]", "[x, -%pi, %pi]", # not tested
....:     "[y, -%pi, %pi]", "['grid, 40, 40]",
....:     '[plot_format, openmath]')
>>> from sage.all import *
>>> _ = maxima("expr_1: 5*cos(x)*(cos(x/2)*cos(y) + sin(x/2)*sin(2*y)+ 3.0) - 10.0")
>>> _ = maxima("expr_2: -5*sin(x)*(cos(x/2)*cos(y) + sin(x/2)*sin(2*y)+ 3.0)")
>>> _ = maxima("expr_3: 5*(-sin(x/2)*cos(y) + cos(x/2)*sin(2*y))")
>>> maxima.plot3d ("[expr_1, expr_2, expr_3]", "[x, -%pi, %pi]", # not tested
...     "[y, -%pi, %pi]", "['grid, 40, 40]",
...     '[plot_format, openmath]')