tpwiedemann.mw

TP5: un gros déterminant 

I. Approche directe 

Q1. Calcul de la matrice 

> A:=proc(n,type)
local M, i, k;
   M:=Matrix(n,n,storage=sparse,datatype=type);
   for i to n do M[i,i]:=ithprime(i) od;
   k:=1;
   while k<=n do
      for i to n-k do M[k+i,i]:=1; M[i,k+i]:=1 od;
      k:=2*k
   od;
   M
end:   
 

> A(10,integer);
 

Matrix(%id = 4329640320)
 

Q2. Temps de calcul du déterminant 

> M:=A(1500,float):
 

> time(LinearAlgebra[Determinant](M));
 

1.172
 

> M:=A(3000,float):
 

> time(LinearAlgebra[Determinant](M));
 

8.990
 

> %/%%%;
 

7.670648464
 

Le temps est multiplié par environ 8 lorsque la taille est multipliée par 2, on observe une complexité cubique typique des méthodes d'algèbre linéaire à base de réduction de Gauss. 

> M:=A(250,integer):time(LinearAlgebra[Determinant](M));
 

1.036
 

> M:=A(500,integer):time(LinearAlgebra[Determinant](M));
 

14.930
 

> %/%%%;
 

14.41119691
 

> M:=A(600,integer):time(LinearAlgebra[Determinant](M));
 

42.902
 

> M:=A(300,integer):time(LinearAlgebra[Determinant](M));
 

3.212
 

> %%%/%;
 

13.35678705
 

Pour des coefficients entiers, la complexité est un peu plus délicate à observer, mais elle est grosso modo en `*`(`^`(n, 4)). La différence provient de la taille du déterminant à calculer, dont le nombre de chiffres est un peu plus que linéaire en n. 

II. La méthode de Wiedemann 

Q3. Calcul de la série 

> getseries:=proc(A,z)
  local n, v, w, s, j;
  n:=LinearAlgebra[ColumnDimension](A);
  v:=LinearAlgebra[RandomVector][column](n);
  w:=LinearAlgebra[RandomVector][row](n);
  s[0]:= w.v;
  for j to 2*n do
       v:=A.v;
       s[j]:=w.v
  od;
  series(add(s[j]*z^j,j=0..2*n)+O(z^(2*n+1)),z,2*n+1)
end:
 

> S:=getseries(A(10,integer),z);
 

series(`+`(`-`(18097), `-`(`*`(374883, `*`(z))), `-`(`*`(8761709, `*`(`^`(z, 2)))), `-`(`*`(223243686, `*`(`^`(z, 3)))), `-`(`*`(6022461300, `*`(`^`(z, 4)))), `-`(`*`(168302731402, `*`(`^`(z, 5)))), `...
series(`+`(`-`(18097), `-`(`*`(374883, `*`(z))), `-`(`*`(8761709, `*`(`^`(z, 2)))), `-`(`*`(223243686, `*`(`^`(z, 3)))), `-`(`*`(6022461300, `*`(`^`(z, 4)))), `-`(`*`(168302731402, `*`(`^`(z, 5)))), `...
series(`+`(`-`(18097), `-`(`*`(374883, `*`(z))), `-`(`*`(8761709, `*`(`^`(z, 2)))), `-`(`*`(223243686, `*`(`^`(z, 3)))), `-`(`*`(6022461300, `*`(`^`(z, 4)))), `-`(`*`(168302731402, `*`(`^`(z, 5)))), `...
 

Le calcul est très rapide avec des coefficients de type float : 

> time(getseries(A(60,float),z));
 

.119
 

mais ces flottants débordent rapidement : 

> coeff(getseries(A(70,float),z),z,140);
 

Float(undefined)
 

Pour corriger ce défaut, il suffit de changer une variable d'environnement : 

> UseHardwareFloats:=false;
 

false
 

> coeff(getseries(A(70,float),z),z,140);
 

0.1652052190e360
 

les temps de calcul s'en ressentent : 

> time(getseries(A(60,float),z));
 

.540
 

Q4. La complexité du calcul 

D'abord avec des coefficients flottants : 

> M:=A(500,float):time(getseries(M,z));
 

24.193
 

> M:=A(250,float):time(getseries(M,z));
 

6.883
 

Le temps est multiplié par environ 4 lorsque la taille double, la complexité est donc quadratique : chaque produit par A[n] est linéaire, et le calcul en effectue n. 

Ensuite avec des coefficients entiers : 

> M:=A(300,integer):time(getseries(M,z));
 

49.160
 

> M:=A(150,integer):time(getseries(M,z));
 

6.306
 

> %%%/%;
 

7.795750079
 

À nouveau, on perd 1 sur l'exposant. 

Q5. Le déterminant de A[10] 

> numapprox[pade](getseries(A(10,float),z),z,[9,10]);
 

`/`(`*`(`+`(4119.999999, `-`(`*`(208018.3355, `*`(z))), `*`(787643.2919, `*`(`^`(z, 2))), `*`(41202246.44, `*`(`^`(z, 3))), `*`(2906232975., `*`(`^`(z, 4))), `-`(`*`(0.1346109404e12, `*`(`^`(z, 5)))),...
 

> coeff(denom(%),z,10);
 

0.4412417655e11
 

> numapprox[pade](getseries(A(10,integer),z),z,[9,10]);
 

`/`(`*`(`+`(`-`(`*`(16820637, `*`(`^`(z, 2)))), `*`(246908354, `*`(`^`(z, 3))), `-`(`*`(354412604, `*`(`^`(z, 4)))), `-`(`*`(37479852544, `*`(`^`(z, 5)))), `*`(510883360907, `*`(`^`(z, 6))), `-`(`*`(2...
 

> coeff(denom(%),z,10);
 

3506022970
 

> LinearAlgebra[Determinant](A(10,integer));
 

3506022970
 

Numeriquement, ça ne se passe donc pas très bien, alors que le calcul exact sur des entiers n'a pas ce problème.
La situation est différente pour les temps de calcul :
 

> S:=getseries(A(250,float),z):time(numapprox[pade](S,z,[249,250]));
 

58.106
 

> S:=getseries(A(125,float),z):time(numapprox[pade](S,z,[124,125]));
 

6.953
 

> %%%/%;
 

8.356968215
 

Le temps de calcul de numapprox[pade] semble plus que cubique sur les flottants,
et la situation est pire sur les entiers :
 

> for i to 30 do S:=getseries(A(i,integer),z); tt[i]:=time(numapprox[pade](S,z,[i-1,i])) od:
 

> plots[listplot]([seq(tt[2*i]/tt[i],i=2..15)]);
 

>
 

Plot_2d
 

La complexité est exponentielle ! Ceci provient de l'explosion de la taille des entiers dans l'algorithme d'Euclide.
Ainsi, le domaine privilégié d'utilisation de l'algorithme de Wiedemann est l'arithmétique modulaire, où les calculs sont exacts et où les entiers ne croissent pas, ce qui permet une complexité quadratique via l'algorithme d'Euclide, voir la section IV.
 

III. Calculs exacts 

Q6. Un calcul d'approximants de Padé 

> denPade:=proc(S, deg)
local z, R, V, U, i, q;
   z:=op(0,S);
   # algorithme d'Euclide etendu : V[i]*F+U[i]*z^(2*deg)=R[i]
   R[0]:=z^(2*deg); V[0]:=0; U[0]:=1;
   R[1]:=rem(convert(S,polynom),R[0],z); V[1]:=U[0]; U[1]:=V[0];
   for i while R[i]<>0 and degree(R[i],z)>=deg do
       R[i+1]:=rem(R[i-1],R[i],z,'q'); # R[i-1]=q*R[i]+R[i+1]
       V[i+1]:=expand(V[i-1]-V[i]*q);
       U[i+1]:=expand(U[i-1]-U[i]*q)
   od;
   # on normalise avec 1 comme valeur du denominateur en 0
   V[i]/tcoeff(V[i],z)
end:
 

Q7. Comparaison avec numapprox[pade] 

> denPade(getseries(A(10,integer),z),10);
 

`+`(1, `-`(`*`(129, `*`(z))), `*`(7097, `*`(`^`(z, 2))), `-`(`*`(218073, `*`(`^`(z, 3)))), `*`(4115487, `*`(`^`(z, 4))), `-`(`*`(49392912, `*`(`^`(z, 5)))), `*`(377308449, `*`(`^`(z, 6))), `-`(`*`(178...
 

Comparaison avec numapprox[pade]: 

> %-denom(numapprox[pade](getseries(A(10,integer),z),z,[9,10]));
 

0
 

> S:=getseries(A(10,float),z):
 

> denom(numapprox[pade](S,z,[9,10]));
 

`+`(`-`(.9999999998), `*`(56.14950361, `*`(z)), `-`(`*`(312.3505825, `*`(`^`(z, 2)))), `-`(`*`(15245.73332, `*`(`^`(z, 3)))), `-`(`*`(588636.7620, `*`(`^`(z, 4)))), `*`(24452019.97, `*`(`^`(z, 5))), `...
 

> denPade(S,10);
 

`+`(1., `-`(`*`(0.1e2, `*`(z))), `-`(`*`(0.1e3, `*`(`^`(z, 2)))), `-`(`*`(0.3e2, `*`(`^`(z, 3)))), `*`(0.7361177121e12, `*`(`^`(z, 4))))
 

Ceci explique un peu la lenteur de numapprox[pade] sur des coefficients flottants : son résultat est de bien meilleure qualité numérique qu'une application naïve de l'algorithme d'Euclide étendu. 

Sur les entiers, notre implantation n'est pas meilleure que celle de numapprox[pade] : 

> S:=getseries(A(20,integer),z): time(denPade(S,20)),tt[20];
 

2.607, 1.781
 

mais elle nous donne une première version pour récrire le code en modulaire dans la partie suivante. 

IV. Calculs modulaires 

Q8. Approximants de Padé modulo p 

> denPademod:=proc(S, deg, p)
local z, R, V, U, i, q;
   z:=op(0,S);
   # algorithme d'Euclide etendu : V[i]*F+U[i]*z^(2*deg)=R[i]
   R[0]:=modp1(ConvertIn(z^(2*deg),z),p); V[0]:=modp1(Zero(z),p); U[0]:=modp1(One(z),p);
   R[1]:=modp1(Rem(ConvertIn(convert(S,polynom),z),R[0]),p); V[1]:=U[0]; U[1]:=V[0];
   for i while not modp1(IsZero(R[i]),p) and modp1(Degree(R[i]),p)>=deg do
       R[i+1]:=modp1(Rem(R[i-1],R[i],'q'),p); # R[i-1]=q*R[i]+R[i+1]
       V[i+1]:=modp1(Subtract(V[i-1],Multiply(V[i],q)),p);
       U[i+1]:=modp1(Subtract(U[i-1],Multiply(U[i],q)),p)
   od;
   # on normalise avec 1 comme valeur du denominateur en 0
   modp1(ConvertOut(Quo(V[i],Constant(Tcoeff(V[i]),z)),z),p)
end:
 

> pp:=prevprime(2^32);
 

4294967291
 

> denPademod(getseries(A(10,integer),z),10,pp);
 

`+`(`*`(3506022970, `*`(`^`(z, 10))), `*`(1785040775, `*`(`^`(z, 9))), `*`(591914175, `*`(`^`(z, 8))), `*`(2511611141, `*`(`^`(z, 7))), `*`(377308449, `*`(`^`(z, 6))), `*`(4245574379, `*`(`^`(z, 5))),...
 

Ici, le nombre premier était assez grand, et un seul suffit pour reconstituer le déterminant : 

> coeff(%,z,10)-LinearAlgebra[Determinant](A(10,integer));
 

0
 

Q9. Complexité empirique 

> N:=1500:S:=series(randpoly(z,degree=2*N,dense),z,2*N+1):time(denPademod(S,N,pp));
 

2.679
 

> N:=3000:S:=series(randpoly(z,degree=2*N,dense),z,2*N+1):time(denPademod(S,N,pp));
 

11.263
 

La complexité est bien quadratique comme attendu. 

Q10. Calcul du déterminant 

> detcreux:=proc(M)
local S, z, n, p, i, mulp, bound, det;
   S:=getseries(M,z);
   n:=LinearAlgebra[ColumnDimension](M);
   bound:=mul(M[i,i],i=1..n);
   mulp:=1;
   p[0]:=nextprime(2^32);
   for i while mulp<bound do
      p[i]:=prevprime(p[i-1]);
      mulp:=mulp*p[i];
      det[i]:=coeff(denPademod(S,n,p[i]),z,n)
   od;
   chrem([seq(det[j],j=1..i-1)],[seq(p[j],j=1..i-1)])
end:
 

> detcreux(A(50,integer));
 

9426493943014410457728941745209897841186563682837896125217516672806270909776687498783841868
 

> %-LinearAlgebra[Determinant](A(50,integer));
 

0
 

> time(detcreux(A(200,integer)));
 

17.174
 

> time(detcreux(A(100,integer)));
 

2.373
 

On a donc bien exploité le caractère creux de la matrice A[n]et obtenu une procédure de complexité totale à peu près cubique pour le calcul exact, là où la méthode généraliste employée par LinearAlgebra[Determinant] était quartique. Asymptotiquement, notre procédure doit donc être plus rapide. 

Complément : amélioration du temps de calcul 

Bien que de bonne complexité asymptotique, la procédure getseries est trop lente. Il faut donc tenter d'y améliorer l'opération qui y est coûteuse : le produit matrice x vecteur. 

Une première idée consiste à exploiter le caractère creux de A en précalculant une partie des opérations symboliquement : 

> getseries2:=proc(A,z)
  local n, multbyA, v, w, s, j;
  n:=LinearAlgebra[ColumnDimension](A);
  v:=LinearAlgebra[RandomVector][column](n);
  w:=LinearAlgebra[RandomVector][row](n);
  multbyA:=unapply( A . Vector([seq(V[i],i=1..n)]), V);
  s[0]:= w.v;
  for j to 2*n do
       v:=multbyA(v);
       s[j]:=w.v
  od;
  series(add(s[j]*z^j,j=0..2*n)+O(z^(2*n+1)),z,2*n+1)
end:
 

> denPade(getseries2(A(10,integer),z),10,z);
 

`+`(1, `-`(`*`(129, `*`(z))), `*`(7097, `*`(`^`(z, 2))), `-`(`*`(218073, `*`(`^`(z, 3)))), `*`(4115487, `*`(`^`(z, 4))), `-`(`*`(49392912, `*`(`^`(z, 5)))), `*`(377308449, `*`(`^`(z, 6))), `-`(`*`(178...
 

> %-denPade(getseries(A(10,integer),z),10,z);
 

0
 

> N:=250:time(getseries2(A(N,integer),z)),time(getseries(A(N,integer),z));
 

3.426, 27.209
 

> detcreux2:=subs(getseries=getseries2,eval(detcreux));
 

proc (M) local S, z, n, p, i, mulp, bound, det; `assign`(S, getseries2(M, z)); `assign`(n, LinearAlgebra[ColumnDimension](M)); `assign`(bound, mul(M[i, i], i = 1 .. n)); `assign`(mulp, 1); `assign`(p[...
proc (M) local S, z, n, p, i, mulp, bound, det; `assign`(S, getseries2(M, z)); `assign`(n, LinearAlgebra[ColumnDimension](M)); `assign`(bound, mul(M[i, i], i = 1 .. n)); `assign`(mulp, 1); `assign`(p[...
proc (M) local S, z, n, p, i, mulp, bound, det; `assign`(S, getseries2(M, z)); `assign`(n, LinearAlgebra[ColumnDimension](M)); `assign`(bound, mul(M[i, i], i = 1 .. n)); `assign`(mulp, 1); `assign`(p[...
proc (M) local S, z, n, p, i, mulp, bound, det; `assign`(S, getseries2(M, z)); `assign`(n, LinearAlgebra[ColumnDimension](M)); `assign`(bound, mul(M[i, i], i = 1 .. n)); `assign`(mulp, 1); `assign`(p[...
proc (M) local S, z, n, p, i, mulp, bound, det; `assign`(S, getseries2(M, z)); `assign`(n, LinearAlgebra[ColumnDimension](M)); `assign`(bound, mul(M[i, i], i = 1 .. n)); `assign`(mulp, 1); `assign`(p[...
proc (M) local S, z, n, p, i, mulp, bound, det; `assign`(S, getseries2(M, z)); `assign`(n, LinearAlgebra[ColumnDimension](M)); `assign`(bound, mul(M[i, i], i = 1 .. n)); `assign`(mulp, 1); `assign`(p[...
proc (M) local S, z, n, p, i, mulp, bound, det; `assign`(S, getseries2(M, z)); `assign`(n, LinearAlgebra[ColumnDimension](M)); `assign`(bound, mul(M[i, i], i = 1 .. n)); `assign`(mulp, 1); `assign`(p[...
proc (M) local S, z, n, p, i, mulp, bound, det; `assign`(S, getseries2(M, z)); `assign`(n, LinearAlgebra[ColumnDimension](M)); `assign`(bound, mul(M[i, i], i = 1 .. n)); `assign`(mulp, 1); `assign`(p[...
proc (M) local S, z, n, p, i, mulp, bound, det; `assign`(S, getseries2(M, z)); `assign`(n, LinearAlgebra[ColumnDimension](M)); `assign`(bound, mul(M[i, i], i = 1 .. n)); `assign`(mulp, 1); `assign`(p[...
proc (M) local S, z, n, p, i, mulp, bound, det; `assign`(S, getseries2(M, z)); `assign`(n, LinearAlgebra[ColumnDimension](M)); `assign`(bound, mul(M[i, i], i = 1 .. n)); `assign`(mulp, 1); `assign`(p[...
 

> time(detcreux2(A(200,integer)));
 

5.130
 

> time(detcreux2(A(400,integer)));
 

36.328
 

Pour aller plus loin, il est possible de produire automatiquement du code C pour le produit matrice x vecteur, et de l'exécuter depuis Maple. Voici les premières étapes sur un exemple : 

> N:=10:mA:=A(N,integer) . Vector([seq(V[i],i=1..N)]):
 

> CodeGeneration[C](mA);
 

cg[0] = 2 * V[0] + V[1] + V[2] + V[4] + V[8];
 

cg[1] = V[0] + 3 * V[1] + V[2] + V[3] + V[5] + V[9];
cg[2] = V[0] + V[1] + 5 * V[2] + V[3] + V[4] + V[6];
cg[3] = V[1] + V[2] + 7 * V[3] + V[4] + V[5] + V[7];
cg[4] = V[0] + V[2] + V[3] + 11 * V[4] + V[5] + V[6] + V[8];
cg[5] = V[1] + V[3] + V[4] + 13 * V[5] + V[6] + V[7] + V[9];
cg[6] = V[2] + V[4] + V[5] + 17 * V[6] + V[7] + V[8];
cg[7] = V[3] + V[5] + V[6] + 19 * V[7] + V[8] + V[9];
cg[8] = V[0] + V[4] + V[6] + V[7] + 23 * V[8] + V[9];
cg[9] = V[1] + V[5] + V[7] + V[8] + 29 * V[9];
 

Il faut ensuite pas mal de travail (je n'ai pas essayé) pour arriver au bout, le point de départ de la documentation à bien étudier est dans 

> ?define_external
 

>