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 :
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.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 », etpari
– 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)
gp('znprimroot(10007)') pari('znprimroot(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'>
type(gp('znprimroot(10007)')) type(pari('znprimroot(10007)'))
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'>
v = pari([1,2,3,4,5]) v type(v)
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'
v.type()
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]
e = v.ellinit() e.type() pari(e)[:13]
À 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]
e.elltors() e.ellglobalred() f = e.ellchangecurve([1,-1,0,-1]) f[:5]
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
G = gap('Group((1,2,3)(4,5), (3,4))') G G.Center() G.IdGroup() G.Order()
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
G = PermutationGroup([[(1,2,3),(4,5)],[(3,4)]]) G.center() G.group_id() n = G.order(); n
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')
R1 = singular.ring(0, '(x,y)', 'dp') R1 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
f f.parent() F = f.factorize(); F F[1][2]
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)
x, y = QQ['x, y'].gens() 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) factor(f)
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]]]]
f = maxima.eval('ij_entry[i,j] := i/j') A = maxima('genmatrix(ij_entry,4,4)'); A A.determinant() A.echelon() A.eigenvalues() A.eigenvectors().sage()
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
A = maxima("matrix ([1, 0, 0], [1, -1, 0], [1, 3, -2])") eigA = A.eigenvectors() V = VectorSpace(QQ,3) eigA v1 = V(sage_eval(repr(eigA[1][0][0]))); lambda1 = eigA[0][0][0] v2 = V(sage_eval(repr(eigA[1][1][0]))); lambda2 = eigA[0][0][1] v3 = V(sage_eval(repr(eigA[1][2][0]))); lambda3 = eigA[0][0][2] M = MatrixSpace(QQ,3,3) AA = M([[1,0,0],[1, - 1,0],[1,3, - 2]]) b1 = v1.base_ring() AA*v1 == b1(lambda1)*v1 b2 = v2.base_ring() AA*v2 == b2(lambda2)*v2 b3 = v3.base_ring() AA*v3 == b3(lambda3)*v3
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]')
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]')
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]')
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]')
_ = 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]')