Moyens:
Premiers calculs avec Sage
:
1 + 1
2
( 1 + 2 * (3 + 5) ) * 2
34
2^3
8
2**3
8
20/6
10/3
2^10
1024
2^100
1267650600228229401496703205376
2^1000
10715086071862673209484250490600018105614048117055336074437\ 50388370351051124936122493198378815695858127594672917553146\ 82518714528569231404359845775746985748039345677748242309854\ 21074605062371141877954182153046474983581941267398767559165\ 54394607706291457119647768654216766042983165262438683720566\ 8069376
20.0 / 14
1.42857142857143
numerical_approx(20/14)
1.42857142857143
numerical_approx(2^1000)
1.071508607186267e301
numerical_approx(20/14, digits=60)
1.42857142857142857142857142857142857142857142857142857142857
Premier exemple d'instabilité numérique:
(1 + 10^50) - 10^50
1
(1.0 + 10^50) - 10^50
0.000000000000000
Quelques exemples supplémentaires:
factorial(100)
93326215443944152681699238856266700490715968264381621\ 46859296389521759999322991560894146397615651828625369\ 7920827223758251185210916864000000000000000000000000
factor(2^(2^5)+1)
641 * 6700417
Calcul formel avec des fonctions et constantes usuelles:
arccos( sin(pi/3))
arccos(1/2*sqrt(3))
sqrt(2)
sqrt(2)
exp(I*pi/6)
e^(1/6*I*pi)
simplify(arccos(sin(pi/3)))
1/6*pi
simplify(exp(i*pi/6))
1/2*sqrt(3) + 1/2*I
numerical_approx( 6*arccos( sin(pi/3)), digits=60 )
3.14159265358979323846264338327950288419716939937510582097494
numerical_approx( sqrt(2), digits=60 )
1.41421356237309504880168872420969807856967187537694807317668
m = 7 % 4; m
3
3 * m + 1
10
Et si l'on veut faire tout les calculs suivants modulo $4$ :
Z4 = IntegerModRing(4); Z4
Ring of integers modulo 4
m = Z4(7); m
3
Par la suite, tous les calculs faisant intervenir m
sont fait modulo
$4$. Ainsi, dans l'exemple suivants, $3$ et $1$ sont automatiquement
convertis dans $\ZZ/n\ZZ$ :
3 * m + 1
2
Corps finis:
Z3 = GF(3); Z3
Finite Field of size 3
a = matrix(QQ, [[1,2,3],[2,4,8],[3,9,27]])
(a^2 + 1) * a^(-1)
[ -5 13/2 7/3] [ 7 1 25/3] [ 2 19/2 27]
P = QQ['x']; P
Univariate Polynomial Ring in x over Rational Field
F = P.fraction_field(); F
Fraction Field of Univariate Polynomial Ring in x over Rational Field
p = P(x+1) * P(x); p
x^2 + x
p + 1/p
(x^4 + 2*x^3 + x^2 + 1)/(x^2 + x)
parent(p + 1/p)
Fraction Field of Univariate Polynomial Ring in x over Rational Field
k.<a> = NumberField(x^3 + x + 1)
a^3
-a - 1
a^4+3*a
-a^2 + 2*a
demo-symbolics
Calcul formel =
Calcul mathématique =
Systèmes généralistes:
Systèmes spécialisés:
var('a,b,c,d,e,f,g')
F = a + b * c + d * e * sin(f)^g
F.operands()
[sin(f)^g*d*e, b*c, a]
Que se passe-t'il lorsque l'on fait:
F = 0
(comptage de références ou glaneur de cellule)
Listes, ensembles et tables d'association
sage: liste = [sin(1+x), 3, sin(1+x)]; liste
sage: ensemble = { sin(1+x), 3, sin(1+x) }; ensemble
sage: tableAssociative = { sin(1+x) : 1, 3 : 2 }
sage: tableAssociative[3] 2
sage: tableAssociative[sin(1+x)] 1
Exécution conditionnelle, boucles, fonctions, ...
Sage
est orienté objet¶Python
et Sage
utilisent fortement la programmation orientée objet.
Même si cela reste relativement transparent pour une utilisation
occasionnelle, il est utile d’en savoir un minimum, d’autant que ce fait
est très naturel dans un contexte mathématique.
Le paradigme de la programmation orientée objet s’appuie sur un
principe: «toute entité du monde physique ou mathématique que l’on
souhaite manipuler avec l’ordinateur est modélisé par un objet»; de
plus cette objet est une instance d’une classe. Ainsi, le nombre
rationnel $o=12/35$ est modélisé par un objet qui est une instance de la
classe Rational
:
o = 12/35
type(o)
<type 'sage.rings.rational.Rational'>
Noter que cette classe est vraiment associée à l’objet $12/35$, et non
seulement à la variable o
qui le contient:
type(12/35)
<type 'sage.rings.rational.Rational'>
Précisons les définitions. Un objet est une portion de la mémoire de l’ordinateur qui contient l’information nécessaire pour représenter l’entité qu’il modélise. La classe quant à elle définit deux choses:
Rational
stipule qu’un nombre rationel comme $12/35$ est
représenté, en gros, par deux nombres entiers: son numérateur et
son dénominateur.numer
, abs
,
{__mult__}, {__add__}, ...).Pour factoriser un nombre entier $o$, on va donc appeller la méthode
factor
avec la syntaxe suivante:
o = 720
o.factor()
2^4 * 3^2 * 5
que l’on peut lire comme: «prendre la valeur de o
et lui appliquer la
méthode factor
sans autre argument». Sous le capot, effectue le calcul
suivant:
type(o).factor(o)
2^4 * 3^2 * 5
De gauche à droite: «demander à la classe de (la valeur de) o
(type(o)
) la méthode appropriée de factorisation (type(o).factor
),
et l’appliquer à o
».
Notons au passage que l’on peut appliquer une opération à une valeur sans passer par une variable:
720.factor()
2^4 * 3^2 * 5
et donc en particulier enchaîner les opérations, de la gauche vers la droite. Ici, on prend le numérateur d’un nombre rationnel, que l’on factorise ensuite:
o = 720 / 133
o.numerator().factor()
2^4 * 3^2 * 5
En quoi cela nous concerne-t-il? Tout d’abord, l’orientation objet
permet le polymorphisme: quelque soit l’objet o
que l’on veut
factoriser, on peut toujours utiliser la notation o.factor()
(ou son
raccourci factor(o)
). De même, calquant les notations mathématiques
usuelles, le produit de deux objets a
et b
peut toujours être noté
a*b
même si l’algorithme utilisé dans chaque cas est différent (Pour
une opération arithmétique binaire comme le produit, la procédure de
sélection de la méthode appropriée est un peu plus compliquée que ce qui
a été décrit précédemment. En effet elle doit gérer des opérations
mixtes comme la somme $2 + 3/4$ d’un entier et d’un nombre rationnel. En
l’occurence, $2$ sera converti en nombre rationnel $2/1$ avant
l’addition. C’est le modèle de coercion de Sage
qui est en charge de
cela.). Voici un produit de deux nombres entiers:
3 * 7
21
un produit de deux nombres rationnels, obtenu par produit des numérateurs et dénominateurs puis réduction:
(2/3) * (6/5)
4/5
Un produit de deux nombres complexes, utilisant $I^2=-1$ :
(1 + I) * (1 - I)
2
des produits commutatifs formels de deux expressions:
(x + 2) * (x + 1)
(x + 1)*(x + 2)
(x + 1) * (x + 2)
(x + 1)*(x + 2)
Outre la simplicité de notation, cela permet d’écrire des programmes génériques comme:
def puissance_quatre(a):
... a = a * a ... a = a * a ... return a
qui s’appliquent à tout objet admettant les opérations utilisées (ici la multiplication):
puissance_quatre(2)
16
puissance_quatre(3/2)
81/16
puissance_quatre(I)
1
puissance_quatre(x+1)
(x + 1)^4
M = matrix([[0,-1],[1,0]]); M
[ 0 -1] [ 1 0]
puissance_quatre(M)
[1 0] [0 1]
Plus prosaïquement, l’orientation objet permet l’introspection: on peut ainsi accéder à l’aide en ligne spécifique à la factorisation des nombres entiers avec:
o = 720
x.factor
... Definition: o.factor(self, algorithm='pari', proof=None, ...) Docstring: Return the prime factorization of this integer as a list of pairs (p, e), where p is prime and e is a positive integer. ...
voire à l’implantation de cette fonction, précédée de son aide en ligne:
o.factor
... def factor(self, algorithm='pari', proof=None, ...) ... if algorithm == 'pari': ... elif algorithm in ['kash', 'magma']: ...
En passant au dessus des détails techniques, on distingue bien que
Sage
délègue le calcul à d’autres logiciels (Pari
, Kash
, ...).
Enfin, on peut utiliser la complétion automatique pour demander
interactivement à un objet o
quelles sont toutes les opérations que
l’on peut lui appliquer:
o.<tab>
o.N o.__abs__ o.__add__ o.__and__ ...
Ici, il y en a beaucoup; voici celles qui commencent par n
:
o.n<tab>
o.n o.nbits o.ndigits o.next_prime o.next_probable_prime o.nth_root o.numerator o.numerical_approx
Dans la section précédente, nous avons vu la notion technique de
classe d’un objet. Dans la pratique, il est suffisant de savoir que
cette notion existe; on a rarement besoin de regarder explicitement le
type d’un objet. En revanche Sage
introduit une contrepartie plus
conceptuelle de cette notion que nous allons aborder maintenant: celle
du parent d’un objet.
Supposons par exemple que l’on veuille déterminer si un élément $a$ est inversible. La réponse ne va pas seulement dépendre de l’élément lui-même, mais de l’ensemble $A$ auquel il est considéré appartenir. Par exemple, le nombre $5$ n’est pas inversible dans l’ensemble $\ZZ$ des entiers, son inverse $1/5$ n’étant pas un entier:
a = 5; a
5
a.is_unit()
False
En revanche, il est inversible dans l’ensemble des rationnels:
a = 5/1; a
5
a.is_unit()
True
Comme nous l’avons vu dans la section précédente, Sage
répond
différemment à ces deux questions car les éléments $5$ et $5/1$ sont
dans des classes différentes:
type(5)
<type 'sage.rings.integer.Integer'>
type(5/1)
<type 'sage.rings.rational.Rational'>
Dans certains systèmes de calcul formel orientés objet, tels que MuPAD
ou Axiom
l’ensemble $X$ auquel $x$ est considéré appartenir (ici $\ZZ$
ou $\QQ$) est simplement modélisé par la classe de $x$. Sage
suit
l’approche de Magma
, et modélise $X$ par un objet supplémentaire
associé à $x$, et appelé son parent:
parent(5)
Integer Ring
parent(5/1)
Rational Field
On peut retrouver ces deux ensembles avec les raccourcis:
ZZ
Integer Ring
QQ
Rational Field
et les utiliser pour convertir aisément un élément de l’un à l’autre lorsque cela a un sens:
QQ(5).parent()
Rational Field
ZZ(5/1).parent()
Integer Ring
ZZ(1/5)
Traceback (most recent call last): ... TypeError: no conversion of this rational to integer
Voici $1$ en tant qu’entier $1\in\ZZ$, nombre rationnel $1\in\QQ$, et approximations flottantes $1{,}0\in\RR$ et $1{,}0+0{,}0i \in\CC$ :
ZZ(1), QQ(1), RR(1), CC(1)
(1, 1, 1.00000000000000, 1.00000000000000)
Selon le même principe, lorsque l'on demande toutes les partitions de l'entier 5, le résultat est un objet qui modélise cet ensemble:
P = Partitions(5); P
Partitions of the integer 5
Pour obtenir la liste de ces objets, il faut le demander explicitement:
P.list()
[[5], [4, 1], [3, 2], [3, 1, 1], [2, 2, 1], [2, 1, 1, 1], [1, 1, 1, 1, 1]]
Cela permet de manipuler formellement des grands ensembles:
Partitions(100000).cardinality()
27493510569775696512677516320986352688173429315980054758203125984302147328114964173055050741660736621590157844774296248940493063070200461792764493033510116079342457190155718943509725312466108452006369558934464248716828789832182345009262853831404597021307130674510624419227311238999702284408609370935531629697851569569892196108480158600569421098519
Et de calculer paresseusement avec. Ici, on tire au hasard une main de cinq cartes à jouer:
Symboles = Set(["Coeur", "Carreau", "Pique", "Trefle"])
Valeurs = Set([2, 3, 4, 5, 6, 7, 8, 9, 10, "Valet", "Dame", "Roi", "As"])
Cartes = CartesianProduct(Valeurs, Symboles).map(tuple)
Mains = Subsets(Cartes, 5)
Mains.cardinality()
2598960
Mains.random_element() # random
{(2, 'Coeur'), (6, 'Pique'), (10, 'Carreau'), ('As', 'Pique'), ('Valet', 'Coeur')}
et là on manipule un mot infini défini comme point fixe d'un morphisme:
m = WordMorphism('a->acabb,b->bcacacbb,c->baba')
m.fixed_point('a')
word: acabbbabaacabbbcacacbbbcacacbbbcacacbbac...
Les parents étant eux-même des objets, on peut leur appliquer des opérations. Ainsi, on peut construire le produit cartésien $\QQ^2$ :
cartesian_product([QQ, QQ])
The cartesian product of (Rational Field, Rational Field)
retrouver $\QQ$ comme corps des fractions de $\ZZ$ :
ZZ.fraction_field()
Rational Field
construire l’anneau des polynômes en $x$ à coefficients dans $\ZZ$ :
ZZ['x']
Univariate Polynomial Ring in x over Integer Ring
Par empilement successif, on peut construire des structure algébriques avancées comme l’espace des matrices $3\times 3$ à coefficients polynomiaux sur un corps fini:
Z5 = GF(5); Z5
Finite Field of size 5
P = Z5['x']; P
Univariate Polynomial Ring in x over Finite Field of size 5
M = MatrixSpace(P, 3, 3); M
Full MatrixSpace of 3 by 3 dense matrices over Univariate Polynomial Ring in x over Finite Field of size 5
dont voici un élément:
M.random_element() # random
[2*x^2 + 3*x + 4 4*x^2 + 2*x + 2 4*x^2 + 2*x] [ 3*x 2*x^2 + x + 3 3*x^2 + 4*x] [ 4*x^2 + 3 3*x^2 + 2*x + 4 2*x + 4]
M.det()
Dans les exemples ci-dessous, nous ferons de l'algèbre linéaire sur le corps fini $\ZZ/7\ZZ$ :
K = GF(7); K
Finite Field of size 7
list(K)
[0, 1, 2, 3, 4, 5, 6]
Nous avons choisi ce corps à titre d'illustration pour avoir des résultats lisibles. On aurait pu prendre des coefficients entiers, rationnels, ou numériques à plus ou moins haute précision. Les aspects numériques seront abordés plus en détail dans l'exposé suivant. Notons au passage que, même en calcul exact, il est possible de manipuler de relativement grosses matrices:
M = random_matrix(K, 10000, sparse=True, density=3/10000)
M.rank() # random
9278
Définissons donc une matrice à coefficients dans $\ZZ/7\ZZ$ :
A = matrix(K, 4, [5,5,4,3,0,3,3,4,0,1,5,4,6,0,6,3]); A
[5 5 4 3] [0 3 3 4] [0 1 5 4] [6 0 6 3]
Calculons le polynôme caractéristique de cette matrice:
P = A.characteristic_polynomial(); P
x^4 + 5*x^3 + 6*x + 2
On vérifie le théorème de Cayley-Hamilton sur cet exemple:
P(A)
[0 0 0 0] [0 0 0 0] [0 0 0 0] [0 0 0 0]
Notons que l'information sur le corps de base est préservée:
P.parent()
Univariate Polynomial Ring in x over Finite Field of size 7
ce qui influe directement sur la factorisation de ce polynôme:
factor(P)
(x + 3) * (x + 6) * (x + 5)^2
factor(x^4 + 5*x^3 + 6*x + 2)
x^4 + 5*x^3 + 6*x + 2
Le calcul ci-dessus nous donne les valeurs propres: -3=4,-6=1 et -5=2. Quels sont les espaces propres?
A.eigenspaces_left()
[ (4, Vector space of degree 4 and dimension 1 over Finite Field of size 7 User basis matrix: [1 4 6 1]), (1, Vector space of degree 4 and dimension 1 over Finite Field of size 7 User basis matrix: [1 3 3 4]), (2, Vector space of degree 4 and dimension 2 over Finite Field of size 7 User basis matrix: [1 0 2 3] [0 1 6 0]) ]
Récupérons ces espaces propres:
E = dict(A.eigenspaces_left())
E[2]
Vector space of degree 4 and dimension 2 over Finite Field of size 7 User basis matrix: [1 0 2 3] [0 1 6 0]
E[2]
n'est pas une liste de vecteurs ni une matrice, mais un objet
qui modélise l'espace propre $E_2$, comme le sous-espace de
$(\ZZ/7\ZZ)^4$ décrit par sa base échelon réduite. On peut donc lui
poser des questions:
E[2].dimension()
2
E[2].basis()
[ (1, 0, 2, 3), (0, 1, 6, 0) ]
V = E[2].ambient_vector_space(); V
Vector space of dimension 4 over Finite Field of size 7
Voire faire des calculs avec:
E[2] + E[4]
Vector space of degree 4 and dimension 3 over Finite Field of size 7 Basis matrix: [1 0 0 0] [0 1 0 5] [0 0 1 5]
v = V([1,2,0,3])
v in E[2]
True
E[2].echelon_coordinates(v)
[1, 2]
E[2].is_subspace(E[4])
False
E[2].is_subspace(V)
True
Q = V/E[2]; Q
Vector space quotient V/W of dimension 2 over Finite Field of size 7 where V: Vector space of dimension 4 over Finite Field of size 7 W: Vector space of degree 4 and dimension 2 over Finite Field of size 7 User basis matrix: [1 0 2 3] [0 1 6 0]
Q( V([0,0,0,1]) )
(2, 4)
On veut maintenant manipuler $A$ comme un morphisme sur $V$ :
phi = End(V)(A); phi
Free module morphism defined by the matrix [5 5 4 3] [0 3 3 4] [0 1 5 4] [6 0 6 3] Domain: Vector space of dimension 4 over Finite Field of size 7 Codomain: Vector space of dimension 4 over Finite Field of size 7
v = V.an_element()
v
(1, 0, 0, 0)
phi(v)
(5, 5, 4, 3)
(phi^-1)(v)
(1, 2, 3, 4)
phi^4 + 5*phi^3 + 6*phi + 2
Free module morphism defined by the matrix [0 0 0 0] [0 0 0 0] [0 0 0 0] [0 0 0 0] Domain: Vector space of dimension 4 over Finite Field of size 7 Codomain: Vector space of dimension 4 over Finite Field of size 7
(phi - 1).image()
Vector space of degree 4 and dimension 3 over Finite Field of size 7 Basis matrix: [1 0 0 0] [0 1 0 5] [0 0 1 5]
(phi - 1).kernel() == E[1]
True
phi.restrict(E[2])
Free module morphism defined by the matrix [2 0] [0 2] Domain: Vector space of degree 4 and dimension 2 over Finite Field of ... Codomain: Vector space of degree 4 and dimension 2 over Finite Field of ...
Un parent n’a, en général, pas lui-même un parent, mais une catégorie qui indique ses propriétés:
QQ.category()
Category of quotient fields
De fait Sage
sait que $\QQ$ est un corps:
QQ in Fields()
True
et donc, par exemple, un groupe additif commutatif (voir Figure {fig:premierspas:catégories}):
QQ in CommutativeAdditiveGroups()
True
Il en déduit que $\QQ[x]$ est un anneau euclidien:
QQ['x'].category()
Category of euclidean domains
Toutes ces propriétés sont utilisées pour calculer rigoureusement et plus efficacement sur les éléments de ces ensembles.
Dans cette section, nous donnons quelques exemples typiques pour lesquel il est important de contrôler le domaine de calcul. En première lecture, on peut passer rapidement sur les exemples plus avancés pour arriver directement à la synthèse de fin de section {premierpas:expressions_versus_domaines_synthèse}.
Soit $c$ une expression un tout petit peu compliquée:
a = var('a')
c = (a+1)^2 - (a^2+2*a+1)
et cherchons à résoudre l’équation en $x$ donnée par $cx=0$ :
eq = c * x == 0
L’utilisateur imprudent pourrait être tenté de simplifier cette équation par $c$ avant de la résoudre:
eq2 = eq / c; eq2
x == 0
solve(eq2, x)
[x == 0]
Heureusement, Sage
ne fait pas cette erreur:
solve(eq, x)
[x == x]
Ici, Sage
a pu résoudre correctement le système car le coefficient $c$
est une expression polynomiale. Il est donc facile de tester si $c$ est
nul; il suffit de le développer:
expand(c)
0
Et d’utiliser le fait que deux polynômes sous forme développée identiques sont égaux. On dit que la forme développée d’un polynôme est une forme normale.
En revanche, sur un exemple à peine plus compliqué, Sage
commet une
erreur:
c = cos(a)^2 + sin(a)^2 - 1
eq = c*x == 0
solve(eq, x)
[x == 0]
alors même qu’il sait faire la simplification et même le test à zéro correctement:
c.simplify_trig()
0
c.is_zero()
True
Cet exemple illustre l’importance du test de nullité, et plus généralement des formes normales, dans un domaine de calcul. Sans lui, tout calcul faisant intervenir une division devient hasardeux. Les algorithmes comme le pivot de Gauss en algèbre linéaire sont particulièrement sensibles à ces considérations.
Construisons l’anneau $\QQ[x_1,x_2,x_3,x_4]$ des polynômes en $4$ variables:
R = QQ['x1,x2,x3,x4']; R
Multivariate Polynomial Ring in x1, x2, x3, x4 over Rational Field
x1, x2, x3, x4 = R.gens()
Les éléments de $R$ sont automatiquement représentés sous forme développée:
x1 * (x2 - x3)
x1*x2 - x1*x3
qui comme nous l’avons vu est une forme normale. On dit alors que $R$ est à représentation normale. En particulier le test à zéro y est immédiat:
(x1+x2)*(x1-x2) - (x1^2 -x2^2)
0
Mais ce n’est pas toujours un avantage. Par exemple, si l’on construit le déterminant de Vandermonde $\prod_{1\leq i < j \leq n} (x_i-x_j)$ :
prod( (a-b) for (a,b) in Subsets([x1,x2,x3,x4],2) )
x1^3*x2^2*x3 - x1^2*x2^3*x3 - x1^3*x2*x3^2 + x1*x2^3*x3^2 + x1^2*x2*x3^3 - x1*x2^2*x3^3 - x1^3*x2^2*x4 + x1^2*x2^3*x4 + x1^3*x3^2*x4 - x2^3*x3^2*x4 - x1^2*x3^3*x4 + x2^2*x3^3*x4 + x1^3*x2*x4^2 - x1*x2^3*x4^2 - x1^3*x3*x4^2 + x2^3*x3*x4^2 + x1*x3^3*x4^2 - x2*x3^3*x4^2 - x1^2*x2*x4^3 + x1*x2^2*x4^3 + x1^2*x3*x4^3 - x2^2*x3*x4^3 - x1*x3^2*x4^3 + x2*x3^2*x4^3
on obtient $4!=24$ termes. Alors que la même construction avec une expression reste sous forme factorisée qui est ici beaucoup plus compacte et lisible:
x1, x2, x3, x4 = var('x1, x2, x3, x4')
prod( (a-b) for (a,b) in Subsets([x1,x2,x3,x4],2) )
(x3 - x4)*(x2 - x4)*(x2 - x3)*(x1 - x4)*(x1 - x3)*(x1 - x2)
De même, une représentation factorisée ou partiellement factorisée permet des calculs de { pgcd} bien plus rapides. Réciproquement, il ne serait pas judicieux de mettre automatiquement tout polynôme sous forme factorisée, même s’il s’agit aussi d’une forme normale, car la factorisation est coûteuse et non compatible avec l’addition.
De manière générale, selon le type de calcul voulu, la représentation idéale d’un élément n’est pas toujours sa forme normale. Cela amène les systèmes de calcul formel à un compromis avec les expressions. Un certain nombre de simplifications basiques, comme la réduction des rationnels ou la multiplication par zéro, y sont effectuées automatiquement; les autres transformations sont laissées à l’initiative de l’utilisateur auquel des commandes spécialisées sont proposées.
Considérons la factorisation de l’expression polynomiale suivante:
x = var('x')
p = 54*x^4+36*x^3-102*x^2-72*x-12
factor(p)
6*(3*x + 1)^2*(x^2 - 2)
Cette réponse est-elle satisfaisante? Il s’agit bien d’une factorisation
de $p$, mais son optimalité dépend fortement du contexte! Pour le moment
Sage
considère p
comme une expression symbolique, qui se trouve être
polynomiale. Il ne peut pas savoir si l’on souhaite factoriser $p$ en
tant que produit de polynômes à coefficients entiers ou à coefficients
rationnels (par exemple). Pour prendre le contrôle, nous allons préciser
dans quel ensemble (domaine de calcul?) nous souhaitons considérer $p$.
Pour commencer, nous allons considérer $p$ comme un polynôme à
coefficient entiers. Nous définissons donc l’anneau $R=\ZZ[x]$ de ces
polynômes:
R = ZZ['x']; R
Univariate Polynomial Ring in x over Integer Ring
Puis nous convertissons $p$ dans cet anneau:
q = R(p); q
54*x^4 + 36*x^3 - 102*x^2 - 72*x - 12
À l’affichage on ne voit pas de différence, mais $q$ sait qu’il est un élément de $R$ :
parent(q)
Univariate Polynomial Ring in x over Integer Ring
Du coup, sa factorisation est sans ambiguïté:
factor(q)
2 * 3 * (3*x + 1)^2 * (x^2 - 2)
On procède de même sur le corps des rationels:
R = QQ['x']; R
Univariate Polynomial Ring in x over Rational Field
q = R(p); q
54*x^4 + 36*x^3 - 102*x^2 - 72*x - 12
factor(R(p))
(54) * (x + 1/3)^2 * (x^2 - 2)
Dans ce nouveau contexte, la factorisation est encore non ambiguë; mais
différente de précédemment. Notons au passage que Sage
sait que $R$
est un anneau euclidien:
R.category()
Category of euclidean domains
et donc en particulier un anneau où la factorisation est unique (voir Figure {fig:premierspas:catégories}).
Cherchons maintenant une factorisation complète sur les nombres complexes. Une première option est de s’autoriser une approximation numérique des nombres complexes avec 16 bits de précision:
R = ComplexField(16)['x']; R
Univariate Polynomial Ring in x over Complex Field with 16 bits of precision
q = R(p); q
54.00*x^4 + 36.00*x^3 - 102.0*x^2 - 72.00*x - 12.00
factor(R(p))
(54.00) * (x - 1.414) * (x + 0.3333)^2 * (x + 1.414)
Une autre est d’agrandir un peu le corps des rationnels; ici, on va rajouter $\sqrt{2}$.
R = QQ[sqrt(2)]['x']; R
Univariate Polynomial Ring in x over Number Field in sqrt2 with defining polynomial x^2 - 2
q = R(p); q
54*x^4 + 36*x^3 - 102*x^2 - 72*x - 12
factor(R(p))
(54) * (x - sqrt2) * (x + sqrt2) * (x + 1/3)^2
Enfin, peut-être souhaite-t’on que les coefficients soient considérés modulo $5$?
R = GF(5)['x']; R
Univariate Polynomial Ring in x over Finite Field of size 5
q = R(p); q
4*x^4 + x^3 + 3*x^2 + 3*x + 3
factor(R(p))
(4) * (x + 2)^2 * (x^2 + 3)
Dans les exemples précédents, nous avons illustré comment l’utilisateur peut contrôler le niveau de rigueur dans ses calculs. D’un côté il peut utiliser les expressions symboliques. Ces expressions vivent dans l’anneau des expressions symboliques:
parent(sin(x))
Symbolic Ring
que l’on peut aussi obtenir avec:
SR
Symbolic Ring
Les propriétés de cet anneau sont assez floues; il est commutatif:
SR.category()
Category of commutative rings
et les règles de calcul font en gros l’hypothèse que toutes les
variables symboliques sont à valeur dans $\CC$. Le domaine de calcul
(expressions polynomiale? rationnelles? trigonométriques?) n’étant pas
spécifié explicitement, le résultat d’un calcul nécessite le plus
souvent des transformations manuelles pour être mis sous la forme
désirée (voir {sec:calculus:simplifications}), en utilisant par exemple
expand
, combine
, collect
et simplify
. Pour bien utiliser ces
fonctions, il faut savoir quel type de transformations elles effectuent
et à quel domaine de calcul ces transformations s’appliquent. Ainsi,
l’usage aveugle de la fonction simplify
peut conduire à des résultats
faux. Des variantes de simplify
permettent alors de préciser la
simplification à effectuer.
D’un autre côté, l’utilisateur peut construire un parent qui va spécifier explicitement le domaine de calcul. Cela est particulièrement intéressant lorsque ce parent est à forme normale: c’est-à-dire que deux objets éléments sont mathématiquement égaux si et seulement si ils ont la même représentation.
Pour résumer, la souplesse est l’avantage principal des expressions:
Les avantages de la déclaration explicite du domaine de calcul sont:
Sage
n’est
pas un système de calcul certifié; il peut donc toujours y avoir
un bogue informatique; mais il n’y aura pas d’utilisation
d’hypothèse implicite).