Interfaces

L’un des aspects essentiels de Sage est qu’il permet d’effectuer des calculs utilisant des objets issus de nombreux systèmes de calcul formel de façon unifiée, avec une interface commune et un langage de programmation sain.

Les méthodes console et interact d’une interface avec un programme externe font des choses tout-à-fait différentes. Prenons l’exemple de GAP :

  1. gap.console() : Cette commande ouvre la console GAP. Cela transfère le contrôle à GAP ; Sage n’est dans ce cas qu’un moyen commode de lancer des programmes, un peu comme le shell sous Unix.

  2. gap.interact() : Cette commande permet d’interagir avec une instance de GAP en cours d’exécution, et éventuellement « remplie d’objets Sage ». Il est possible d’importer des objets Sage dans la session GAP (y compris depuis l’interface interactive), etc.

GP/PARI

PARI est un programme C compact, mature, fortement optimisé et spécialisé en théorie des nombres. Il possède deux interfaces très différentes utilisables depuis Sage :

  • gp – l’interpréteur « G o P ARI », et

  • pari – la bibliothèque C PARI.

Ainsi, les deux commandes suivantes font le même calcul de deux façons différentes. Les deux sorties ont l’air identiques, mais elles ne le sont pas en réalité, et ce qui ce passe en coulisses est radicalement différent.

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)

Dans le premier exemple, on démarre une instance de l’interpréteur GP et on lui envoie la chaîne 'znprimroot(10007)'. Il l’évalue, et affecte le résultat à une variable GP (ce qui occupe un espace qui ne sera pas libéré dans la mémoire du processus fils GP). La valeur de la variable est ensuite affichée. Dans le second cas, nul programme séparé n’est démarré, la chaîne 'znprimroot(10007)' est évaluée par une certaine fonction de la bibliothèque C PARI. Le résultat est stocké sur le tas de l’interpréteur Python, et la zone de mémoire utilisée est libérée lorsque son contenu n’est plus utilisé. Les objets renvoyés par ces deux commandes sont de types différents :

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'>

Alors, laquelle des interfaces utiliser ? Tout dépend de ce que vous cherchez à faire. L’interface GP permet de faire absolument tout ce que vous pourriez faire avec la ligne de commande GP/PARI habituelle, puisqu’elle fait appel à celle-ci. En particulier, vous pouvez l’utiliser pour charger et exécuter des scripts PARI compliqués. L’interface PARI (via la bibliothèque C) est nettement plus restrictive. Tout d’abord, toutes les méthodes ne sont pas implémentées. Deuxièmement, beaucoup de code utilisant par exemple l’intégration numérique ne fonctionne pas via l’interface PARI. Ceci dit, l’interface PARI est souvent considérablement plus rapide et robuste que l’interface GP.

(Si l’interface GP manque de mémoire pour évaluer une ligne d’entrée donnée, elle double silencieusement la taille de la pile et réessaie d’évaluer la ligne. Ainsi votre calcul ne plantera pas même si vous n’avez pas évalué convenablement l’espace qu’il nécessite. C’est une caractéristique commode que l’interpréteur GP habituel ne semble pas fournir. L’interface PARI, quant à elle, déplace immédiatement les objets créés hors de la pile de PARI, de sorte que celle-ci ne grossit pas. Cependant, la taille de chaque objet est limitée à 100 Mo, sous peine que la pile ne déborde à la création de l’objet. Par ailleurs, cette copie supplémentaire a un léger impact sur les performances.)

En résumé, Sage fait appel à la bibliothèque C PARI pour fournir des fonctionnalités similaires à celle de l’interpréteur PARI/GP, mais depuis le langage Python, et avec un gestionnaire de mémoire plus perfectionné.

Commençons par créer une liste PARI à partir d’une liste Python.

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'>

En Sage, les objets PARI sont de type Gen. Le type PARI de l’objet sous-jacent est donné par la méthode type.

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

Pour créer une courbe elliptique en PARI, on utiliserait ellinit([1,2,3,4,5]). La syntaxe Sage est semblable, à ceci près que ellinit devient une méthode qui peut être appelée sur n’importe quel objet PARI, par exemle notre 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]

À présent que nous disposons d’une courbe elliptique, faisons quelques calculs avec.

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

Pour les mathématiques discrètes effectives et principalement la théorie des groupes, Sage utilise GAP.

Voici un exemple d’utilisation de la fonction GAP IdGroup, qui nécessite une base de données optionnelle de groupes de petit ordre, à installer séparément comme décrit plus bas.

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

On peut faire le même calcul en SAGE sans invoquer explicitement l’interface GAP comme suit :

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

Pour utiliser certaines fonctionnalités de GAP, vous devez installer un paquet Sage optionnel. Cela peut être fait avec la commande:

sage -i gap_packages

Singular

Singular fournit une bibliothèque consistante et mature qui permet, entre autres, de calculer des pgcd de polynômes de plusieurs variables, des factorisations, des bases de Gröbner ou encore des bases d’espaces de Riemann-Roch de courbes planes. Considérons la factorisation de polynômes de plusieurs variables à l’aide de l’interface à Singular fournie par Sage (n’entrez pas les ....:) :

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')

Maintenant que nous avons défini \(f\), affichons-le puis factorisons-le.

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

Comme avec GAP dans la section GAP, nous pouvons aussi calculer la factorisation sans utiliser explicitement l’interface Singular (Sage y fera tout de même appel en coulisses pour le calcul).

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

Le système de calcul formel Maxima est fourni avec Sage accompagné d’une implémentation du langage Lisp. Le logiciel gnuplot (que Maxima utilise par défaut pour tracer des graphiques) est disponible comme paquet optionnel. Maxima fournit notamment des routines de calcul sur des expressions formelles. Il permet de calculer des dérivées, primitives et intégrales, de résoudre des équations différentielles d’ordre 1 et souvent d’ordre 2, et de résoudre par transformée de Laplace les équations différentielles linéaires d’ordre quelconque. Maxima dispose aussi d’un grand nombre de fonctions spéciales, permet de tracer des graphes de fonctions via gnuplot, et de manipuler des matrices (réduction en lignes, valeurs propres, vecteurs propres…) ou encore des équations polynomiales.

Utilisons par exemple l’interface Sage/Maxima pour construire la matrice dont le coefficient d’indice \(i,j\) vaut \(i/j\), pour \(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]]]]

Un deuxième exemple :

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

Voici enfin quelques exemples de tracés de graphiques avec openmath depuis Sage. Un grand nombre de ces exemples sont des adaptations de ceux du manuel de référence de Maxima.

Tracé en 2D de plusieurs fonctions (n’entrez pas les ....:) :

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]')

Un graphique 3D interactif, que vous pouvez déplacer à la souris (n’entrez pas les ....:) :

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]')

Le célèbre ruban de Möbius (n’entrez pas les ....:) :

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]')

Et la fameuse bouteille de Klein (n’entrez pas les ....:):

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]')