vendredi 21 décembre 2007

Matrices en Ruby : 1 - Attention !

On ne se refait pas... Étant (paraît-il) un peu matheux, il fallait bien que je m'intéresse au traitement des matrices en Ruby dans la librairie standard. Pour résumer, les matrices sont des objets mathématiques qu'on peut décrire comme des tableaux 2D, comprenant un grand nombre d'opérations, et de propriétés et pouvant représenter à peu près n'importe quelle donnée (système d'équations, graphe, statistique, raideur physique en méca, viscosité en CFD...). Elles sont utilisées assez systématiquement en maths appliquées, et leur enseignement se fait, en France, en Bac+1 et plus dans les cursus scientifiques. Voyons leur gestion en Ruby. On va faire ça par étape, gentiment, en commençant par un ou deux petits soucis avec cette classe.

Avant de raller, un rapide aperçu :

  • Pour la création, pas mal de méthodes à disposition (mais pas new !) : par ligne, par colonne, nulle, diagonale, identité... il manquerait peut-être les triangulaires mais ne chipotons pas trop...
  • Les méthodes d'accès aux cellules, lignes et colonnes, tailles, méthode collect...
  • Les opérateurs classiques (+, -, *, **...) et l'opérateur "/" qui ferait hurler à peu près n'importe quel prof de maths non-muet (la division d'une matrice A par une matrice B n'existant pas au sens strict : il s'agit de la multiplication de A par l'inverse de B SI CELLE-CI EST INVERSIBLE !).
  • Les méthodes sur les matrices : déterminant, rang, trace et transposition.
  • Les outils de conversion divers (to_s, vecteurs colonnes, etc...)
Donc c'est pas trop mal. Mais il y a quelques hiatus assez surprenants :

WARNING : déterminant faux !!!

C'est vrai, c'est noté dans la doc, mais ça reste savonneux un truc pareil : il est nécessaire d'inclure la lib mathn sous peine d'avoir des déterminants erronés (dans l'exemple du code on obtenait -20 au lieu de -19 pour a et 0 pour b, ce qui est par définition impossible pour une matrice inversible, et donc pour l'inverse d'une matrice...). J'espère que la correction sera faite dans la version 1.9 parce qu'un truc pareil, ça craint sec !

Affichage : "Yé souis pas oun' machine !"

La méthode to_s est bien gentille, mais elle est pas franchement compréhensive pour nos pauvres yeux : une matrice étant le plus souvent un tableau 2D (ici en tout cas...), c'est plus sympa de l'avoir sous forme de tableau à l'écran. Je propose une méthode print dans le code du jour pour avoir une sortie console plus lisible.

Matrice non-rectangulaire ?

Également notifié dans la documentation, il n'est pas imposé par la classe qu'une matrice soit rectangulaire : par les mathématiques si en revanche. Cela pourrait éventuellement poser des soucis assez significatifs de se trimballer des coefficients nil... Mais d'un autre côté, économiser de la place sur les matrices creuses sans introduire de structure de stockage dédiée (typiquement un tableau 3*n pour une matrice tri-diagonale n*n) est assez tentant. A tester, on en reparlera.

Le code :

Juste des petits tests sur une matrice d'entiers 3*3 (tester sans le 'mathn', vous verrez !) avec l'introduction de ma petite méthode d'affichage.



require 'Matrix'
# WARNING !!! Mandatory to get real determinant value !!!
require 'mathn'

a = Matrix.columns([[1,2,5],[0,5,1],[1,1,1]])

# "Classic" to_string
puts "a = " + a.to_s

class Matrix
# Print a "human readable" matrix
# Why doesn't it already exist ?
def print
for i in (0...self.row_size)
strg = ""
for j in (0...self.column_size)
strg = strg + " " + (self.[](i,j)).to_s
end
puts strg
end
end
end

puts "\na = "
a.print
puts "\nDet(a) = " + (a.determinant).to_s

b = a.inverse
puts "\nb = a^-1 = "
b.print
# b determinant should be a determinant inverse
puts "\nDet(b) = " + (b.determinant).to_s + "\n"

c = a*b # Should be equal to identity matrix I
puts "\nc = a*b = "
c.print

3 commentaires:

Benjamin Francisoud a dit…

Pour info, il faut mettre:
require 'matrix'
avec un M minuscule sous linux.

Ca fonctionne sous windows avec un M majuscule parce que windows est pas regardant sur les minuscules/majuscules ;)

Sobe a dit…

Tiens donc !

Merci pour l'info.
Je suis un peu étonné que sur des petites choses de ce genre on ait des différences entre Windows et les Unix...

A partir de maintenant, je vais tâcher de préciser sous quelle plate-forme a été testé tel code (en général, Windows mais pas systématiquement).

Par ailleurs, la gestion standard des matrices en Ruby étant, à mon avis, un peu bancale, je devrais parler bientôt d'une lib plus riche et "rigoureuse" : linalg, basée sur Lapack.

Benjamin Francisoud a dit…

Oui j'ai été moi même assez surpris! C'est la première fois que ça m'arrivait...