begin process at 2008 08 29 23:16:09
1 233 931 membres
450 nouveaux aujourd'hui
14 294 membres club

Vous ne trouvez pas de réponse à votre problème ? Alors posez la question dans le forum.
Souvenez-vous qu'il n'y a jamais de question bête, mais rester dans l'ignorance parce que l'on n'ose pas poser une question, ça c'est une erreur !

CLASS DE CALCUL MATRICIEL, DIAGONALISATION DE MATRICES SYMÈTRIQUES RÉELLES.


Information sur la source

Description

Bon, voila, j'avais ecrit pour mon TIPE des bouts de programmes de calcul matriciel, et récemment j'ai du écrire des programmes en Matlab pour l'école. Je me suis dit qu'il était dommage de ne pas pouvoir utiliser ces fonctions dans mon langage favoris ;-) J'ai trouvé aussi que le sujet était intéressant d'un point de vu mathématique. Après quelques tests d'algorithmes sous Matlab j'ai vu que cela était faisable.

Cette unité définit donc une Class: TMatrix gérant des matrices réelles de dimensions variables.
Pour éviter autant que possible de remplir une matrice élément par élément, on définit une dizaine de fonctions de création de vecteurs et de matrices particulières (Equivalent des fonctions matlab Rand, LinSpace, SpDiags...).

Les opérations gérées sont:
Les fonctions de base addition, soustraction, multiplication, transposition, calcul de produits scalaires, de normes pour tous type de matrices.
Le reste des opérations est principalement destiné aux matrices carrés:

-Inversion, calcul de déterminants
-Divers types de décompositions: LU, QR, QL, Hessenberg pour toutes matrices carrés.
-Le calcul des valeurs propres d'une matrice symétrique  (en O(n^2))
-Le calcul des valeurs propres et des vecteurs propres d'une matrice symétrique (en O(n^3))

Une procédure permet de tracer des courbes 1D et 2D de plusieurs types représentant les matrices.

Conclusion

Les 2 fonctions de diagonalisation utilisent la méthode itérative de factorisation QR avec décalage de Wilkinson.
Cette méthode à l'avantage d'être rapide ( 0,2 secondes pour une matrice 100X100 ), de plus elle est numériquement très stable, ce qui permet d'obtenir des valeurs et des vecteurs propres d'une précision de 10^-17.
Pour plus de précisions sur l'algorithme lire: http://www-math.mit.edu/~persson/18.335/lec16.pdf

Je vais étendre le travail aux matrices complexes, en particuliers pour diagonaliser tout types de matrices réelles et tout types de matrices complexes.
Cependant j'hésite encore entre 2 solutions: soit développer une class à part de matrices complexes, soit intégrer les matrices complexes dans une unique class de manière à ce que chaque fonction gére indifféremment des matrices réelles et complexes ce qui nécessite plus de travail.
Tout vos commentaires sont les bienvenus, notamment si vous savez comment autoriser des calculs du type M1:=add(M1,mul(M2,M3)) sans que cela ne génère des zones de mémoire non libérables, voir même surcharger les opérateurs.
Pour les "Membres Club", vous pouvez télécharger directement un fichier contenu dans le zip sans télécharger le zip en entier !

Télécharger le zip

  • signaler à un administrateur
    Commentaire de f0xi le 17/05/2006 04:35:49 administrateur CS

    pour les fonctions qui renvois des objets :

    function Methode(obj1, obj2 : TClasse) : TClasse;

    fait plutot cela :

    procedure Methode(const Obj1, Obj2 : TClasse;var ObjResultat : TClasse);

    et ne fait pas la creation dans la procedure.

    il faudrat ensuite faire comme cela :

    procedure Methode(const Obj1, Obj2 : TClasse;var ObjResultat : TClasse);
    begin
      if not assigned(ObjResultat) then exit;
      ObjResultat.Prop := Obj1.prop (calcul) Obj2.Prop;
    end;

    le "not assigned" permet de controller si l'objet est instancié ou non ... auquel cas il ne faut pas provoquer d'erreur de violation donc on sort de la procedure.
    on pourrait egalement generer une exception (classe exception) avec cette methode :

    type
      EEObjectNotAssigned = class(Exception);

    procedure Methode(const Obj1, Obj2 : TClasse;var ObjResultat : TClasse);
    begin
      if not assigned(ObjResultat) then begin
         raise EEObjectNotAssigned.Create('Erreur procedure [Methode] objet resultat non créé.');
         exit;
      end;
      ObjResultat.Prop := Obj1.prop (calcul) Obj2.Prop;
    end;

    et dans le code qui appel la procedure :

    begin
      try
        // on essaye d'appliquer la methode
        Methode(M1,M2,MR);
      except
        // MR ne semble pas etre instancié
        on EEObjectNotAssigned do begin
           // alors on le crée
           MR := TClasse.Create;
           // et on retente d'appliquer la methode
           Methode(M1,M2,MR);
        end;
      end;
    end;

    si on ne gere pas l'exception, alors on aurat un message durant l'execution ... avec la gestion de l'exception en interne, on aurat pas de message durant l'execution.
    bien sur, si on est en pleine session Delphi, comme l'execution est en mode de deboggage l'exception s'afficheras.
    il faut lancer l'executable independament de Delphi pour ne pas la voir.

    les fonctions ne doivent jamais renvoyer une classe ... ceci empeche la liberation propre de ces derniere.
    on peu cependant renvoyer des tableaux ou des enregistrements (record) ou encore des ensembles (set of) mais aucunement des classes.

    de meme que pour ta fonction "copy" qui devrait s'appelée "Assign" ou "AssignTo" tout depend le sens d'assignation qu'on desire et devrais appartenir a la classe TMatrix :

    procedure TMatrix.AssignFrom(const Matrix : Tmatrix);
    var i,j:integer;
    begin
      if Not Assigned(Matrix) then exit;
      with Self do begin
        ColCount := Matrix.ColCount;
        RowCount := Matrix.RowCount;
        FSquare  := Matrix.Square;
        FSym     := Matrix.Sym;
        FLo      := Matrix.Lo;
        FUp      := Matrix.Up;
        FDiag    := Matrix.Diag;
        FHess    := Matrix.Hess;
        FTriDiag := Matrix.TriDiag;
        // on peut faire ça avec les tableaux a partir du moment qu'ils sont du meme type!
        Cells    := Matrix.Cells;
      end;
    end;

    ensuite on regle les petits problemes :

    function TMatrix.GetRowCount : integer;
    begin
      // on renvois la longueur et non l'indice maximum
      // un tableau dynamique commence a 0
      result := length(Cells);
    end;

    procedure TMatrix.SetRowCount(const RC : integer);
    begin
      // si je veux 10 lignes elle seront numerotée de 0 a 9
      SetLength(Cells, RC);
    end;

    function TMatrix.GetColCount : integer;
    begin
      // idem on commence a 0 et on renvois la longueur
      result := length(Cells[0]);
    end;

    procedure TMatrix.SetColCount(const CC : integer);
    begin
      // tu le savais pas ça ? ben maintenant si ...
      SetLength(Cells,length(Cells),CC);
    end;

    encore d'autre petit probleme :

    procedure TMatrix.Define;
    var i,j,n : integer;
        a,b   : extended;
    begin
       n := RowCount;
       if n = ColCount then begin
          FSquare  := True;  Fsym  := True;  FLo   := True;
          FUp      := True;  FDiag := True;  FHess := True;
          FTridiag := True;
          for i := 0 to n-1 do begin
          for j := 0 to n-1 do begin
              a := Cells[i,j];
              b := Cells[j,i];
              FSym  := not (a <> b);
              FDiag := not (a <> 0);
              if i<j then begin
                 FLo := FDiag;
                 FTriDiag := not ((j > i+1) and FDiag);
              end;
              if i>j then begin
                 FUp := FDiag;
                 FTriDiag := not ((i > j+1) and FDiag);
                 FHess    := FTriDiag;
              end;
          end;
          end;
       end else begin
          FSquare  := False;  Fsym  := False;  FLo   := False;
          FUp      := False;  FDiag := False;  FHess := False;
          FTridiag := False;
       end;
    end;

    procedure TMatrix.Transpose;
    var i,j,RC,CC:integer;
        M1 : TMatrix;
    begin
      RC := RowCount;
      CC := ColCount;
      M1 := TMatrix.Create(RC,CC);
      M1.AssignFrom(Self);
      RowCount := CC;
      ColCount := RC;
      for i := 0 to RowCount-1 do
          for j := 0 to ColCount-1 do
              Cells[i,j] := M1.Cells[j,i];
      M1.Free;
    end;

    il faut bien rédadapter avec les nouveautées et les corrections d'erreurs.


    procedure TMatrix.Add(M:TMatrix);
    var i,j,RC,CC:integer;
    begin
      RC := M.RowCount;
      CC := M.ColCount;
      if (RC<>RowCount) and (CC<>ColCount) then exit;
      for i := 0 to RC-1 do
          for j := 0 to CC-1 do
              Cells[i,j] := Cells[i,j] + M.Cells[i,j];
    end;

    procedure TMatrix.Sub(M:TMatrix);
    var i,j,RC,CC:integer;
    begin
      RC := M.RowCount;
      CC := M.ColCount;
      if (RC<>RowCount) and (CC<>ColCount) then exit;
      for i := 0 to RC-1 do
          for j := 0 to CC-1 do
              Cells[i,j] := Cells[i,j] - M.Cells[i,j];
    end;


    et enfin, pour liberer un objet on appel pas directement Destroy mais Free!
    Free fait des verifications avant d'appeler Destroy.

    et si on veux pas se prendre la tete a savoir ou commence le tableau et ou il finit :

    for i := low(Cells) to high(Cells) do
        for j := low(Cells[i]) to high(Cells[i]) do
            ...

    voila je pense t'avoir donner assé d'elements pour que tu puisse t'en sortir.

    a plus

  • signaler à un administrateur
    Commentaire de JnBiz le 17/05/2006 17:38:46

    Tout d'abord, merci foxi de toutes ces suggestions.
    Donc, si j'ai bien compris, on ne pourra pas traiter les matrices comme des nombres avec des fonctions.
    Pour ce qui est de la numérotation des indices, je tiens à conserver le fait que le premier élément soit le numéro 1, car c'est quand même beaucoup plus logique pour une matrice.

  • signaler à un administrateur
    Commentaire de f0xi le 17/05/2006 19:44:44 administrateur CS

    alors, pour un humain oui, la logique veux qu'on appel le premier tiroir d'un meuble le tirroir numero 1.
    mais pour un ordinateur cette logique n'est pas applicable le premier tiroir serat le tirroir numero 0.
    car si il n'y a pas de tirroir il n'y a pas de valeur donc "null" et null n'est ni 0, ni 1, ni -1 ...
    c'est une valeur flou qui determine un element vide, sans valeur.

    et quand tu fait un SetLength sur une matrice dynamique, le premier indice serat 0 et non 1.
    il faut donc prendre en compte cela car si tu fait SetLength(matrice,10)
    low(matrice) renvois 0
    high(matrice) renvois 9
    et
    length(matrice) renvois 10

    ce serait comme dire, je compte les minutes a partir de 1 car c'est moins chiant ...
    et donc on se retrouve avec des heures comprise dans un interval :
    1h 1m 1s 1ms .. 24h 60m 60s 1000ms
    ce qui serait une aberation.

    si cela te parait plus logique, et je le concois bien, c'est parce que depuis qu'on est tout petit on nous dis que 0 n'est pas une valeur et quand on apprend a compter on compte de 1 a N ... ce qui est par definition mathematiquement inexacte.
    on devrait apprendre a compter a partir de Zero qui est un nombre comme les autres et qui surtout est le premier nombre de toute base.
    base 2 : 0..1
    base 6 : 0..6
    base 10: 0..9
    base 16: 0..F

    donc il est parfaitement logique que le premier indice d'un tableau soit 0 car si le tableau n'a pas de longeur l'indice 0 n'existe pas et on provoqueras une erreur en voulant y acceder.

    car par exemple, si nous commencions tout nos tableaux a 1 ...
    que ce passerais t'il quand dans un interval -N...+N nous arriverions a zero ?
    simple ... il y'aurait un trou qui provoquerais une reaction en chaine ce qui pourrais dechirer le continium espace temps de l'univers entier et brisans ainsi l'equilibre Matiere/Anti-matiere et provoquant le plus grand cataclysme que l'univers est connus, une explosion de plusieurs yotta de yotta de yotta de yotta-tonnes qui detruirait toutes les structures dimensionnelles et qui auraient donc pour effet de boucher les toilettes de notre bon vieux Dieu.

    bref ... ça craint du boudin.

    donc fait comme tu le sens, mais d'experience je puis t'affirmer qu'il est preferable de compter a partir de 0 ... au moins pour eviter l'apocalypse.

    ^^


    "Donc, si j'ai bien compris, on ne pourra pas traiter les matrices comme des nombres avec des fonctions."

    mmm .. si pourquoi ne pourrait-on pas ?
    aucune difference entre :

    methode(A,B,C)
    et
    C := Methode(A,B)

    c'est meme mieux et plus performant d'utiliser une procedure.
    il faut penser "informatique" et pas "mathematique"

    si tu connais un peu l'electricité, vois un peu cela comme la difference entre un branchement en serie et un branchement en parallele.

    C := methode(A,B) (en serie)
    methode(A,B,C) (en parallele)

    c'est juste pour faire une analogie.

    si A B et C sont des ampoules ... et qu'on les branches en serie C aurat moins de courant et donc brilleras moins.
    par contre si on les mets en parrallele ... la elle brillerons toute a la meme intensitée...

    ben la c'est pareil.

    en passant j'ai fait une erreur : procedure (const M1,M2 : tmatrix; M3: TMatrix)
    il ne faut pas mettre VAR.

    dans une procedure on fournis 3 variable qui sont des references d'objet.
    on vas donc travailler avec ces references ce qui serat plus rapide pour assigner les resultats.

    avec une fonction, on doit instancier dynamiquement le resultat pour au final l'assigner a un objet existant. ce qui demande donc des traitements plus (creation > assignation)
    sans parler de la gestion de la liberation, au plus propre il faudrait ecrire :

    with methode(M1,M2) do
         M3.Cells := Cells;
         Free;
    end;

    et encore ... pas sur que ça marche et c'est pas trés propre je trouve.
    car c'est l'utilisateur qui doit veiller a la liberation ... (on est pas en C++)

    le mieux donc est de travailler avec des procedures et des buffers si besoin.

    procedure methode(M1,M2,MR);
    var buffer;
    begin
      Buffer := Create;
      Buffer.Cells := M1.Cells (calcul) M2.Cells;
      MR.Cells := Buffer.Cells;
      Buffer.Free;
    end;

    ce qui ne nous empeche pas de faire :

    methode(M1,M2,M1) ou methode(M1,M2,M2)

    ou encore :

    add(M1,M2) (additionne M1 et M2 et place le resultat dans M1)
    sub(M1,M2) (soustrait M1 et M2 et place le resultat dans M2)

  • signaler à un administrateur
    Commentaire de JnBiz le 17/05/2006 20:23:18

    Je me suis fait mal comprendre:
    J'ai très bien compris que les tableaux dynamiques commencent par 0, seulement dans l'utilisation pratique d'une matrice (donc par un humain) l'élément (1,1) est par convention mathématique le premier élément, d'où ce décalage.
    De même pour l'utilisation des fonctions, methode(A,B,C) C := Methode(A,B) ont beau être équivalent pour le calcul, mais pour l'utilisateur (là encore humain) la 2° solution est plus pratique.
    Pour résumer je cherche à faire quelque chose de pratique et de naturel pour l'humain qui ne le soit pas forcément pour l'ordinateur (avoue qu'il est plus facile de réecrire un programme que de réecrire l'algèbre des matrices ;-)).
    Par contre la dernière solution add(M1,M2) (additionne M1 et M2 et place le resultat dans M1)) me parait un bon compromis, même si cela n'est pas forcément optimisé pour tout types de calculs (genre M3:=M1+M2). Il ne faut pas oublier que le principal facteur limitant dans le calcul matriciel est l'accès en mémoire qu'il faut limiter au max.
    C'est pour ca que j'ai prévu 2 types de calcul:
    M1.add(M2) (M1=M1+M2)
    et M3:=add(M1,M2) (M3=M1+M2)

    Maintenant je me demande si le plus pratique ne serait pas de faire un parser qui gérerait les expression du type "M3=(M1+M2)*M2" cela éviterait d'avoir à gérer des matrices temporaires ce qui est pénible.

  • signaler à un administrateur
    Commentaire de BruNews le 17/05/2006 20:35:48 administrateur CS

    Salut f0xi,

    à propos du NULL : à moins d'un cpu comprenant la philo, par force une comparaison à null (comme à quoi que ce soit d'autre) ne se fait que par arythmétique, aucune valeur 'floue' représentable en binaire ni autre base. Il est d'usage de définir NULL à zéro.

    JnBiz > 1 comme base d'indexation n'est en RIEN logique, ça indique simplement l'incompréhension de ce qu'est un tableau. L'index indique l'offset pour accéder à l'élément partant de l'adresse de base du tableau:
    exemple d'un tableau de INT32:
    tab[n] se trouve à: adresse(tab) + n * sizeof(INT32)
    Le 1er élém est donc bien à: adresse(tab) + 0 * sizeof(INT32)
    Ceci ne te parait pas nettement plus logique ???

  • signaler à un administrateur
    Commentaire de JnBiz le 17/05/2006 20:59:59

    Brunews, il s'agit d'une convention mathématique, je n'y peux rien, de même le dernier numéro est le numéro n qui correspond également à la taille de la matrice (ce qui n'est pas le cas si on numérote à partir de 0). Une matrice n'est pas qu'un tableau, il y a des règles, des conventions, on ne peut pas faire ce qu'on veut et decréter que le premier élément est placé en (0,0).
    C'est juste que les logiques mathématique et informatique différent.
    Mais quand il ne s'agit pas de matrices, je rejoins évidemment votre point de vue.

  • signaler à un administrateur
    Commentaire de cantador le 25/05/2006 11:32:07

    Zéro n'existait pas à l'origine..
    c'est pour çà qu'il n'existe pas d'année zéro.
    il a donc fallut inventer ce chiffre.
    Pour ma part, je concerve cet élément tant que je peux dans les tableaux sinon à chaque fois je tombe sur des cas non prévus déclenchant des exceptions.

  • signaler à un administrateur
    Commentaire de tybman le 05/02/2007 22:31:27

    Moi je suis un mathématicien et je trouve cela tout simplement fabuleux

Ajouter un commentaire

Discussions en rapport avec ce code source

Pub



Appels d'offres

Recherche developpeur ...
Budget : 700€
SITE MARCHAND LOCATION...
Budget : 3 000€
SITE MARCHAND POUR HOTEL
Budget : 4 000€

CalendriCode

Août 2008
LMMJVSD
    123
45678910
11121314151617
18192021222324
25262728293031

Boutique

Boutique de goodies CodeS-SourceS