Programmation fonctionnelle comme exemple de travail avec des matrices de la théorie de l'algÚbre linéaire

introduction


La base du travail avec les matrices (dans cet article, nous ne considĂ©rerons que les matrices bidimensionnelles) est une puissante thĂ©orie mathĂ©matique du domaine de l'algĂšbre linĂ©aire. Une dĂ©finition ou une action en dĂ©coule, une fonction en appelle une autre. Par consĂ©quent, pour l'implĂ©mentation fonctionnelle de la fonctionnelle des opĂ©rations mathĂ©matiques sur les matrices, les langages fonctionnels s'adaptent trĂšs bien. Dans cet article, nous allons examiner des exemples spĂ©cifiques en F # et donner des commentaires dĂ©taillĂ©s sur la façon dont cela fonctionne. Étant donnĂ© que F # fait partie de la famille .NET, la fonctionnalitĂ© rĂ©sultante peut ĂȘtre utilisĂ©e sans aucun problĂšme dans d'autres langages impĂ©ratifs, par exemple C #.

Définition et implémentation d'une matrice en F #


Les matrices sont la partie fondamentale et la plus importante de l'algĂšbre linĂ©aire. Les matrices sont souvent utilisĂ©es dans la programmation, par exemple, dans la modĂ©lisation 3D ou le dĂ©veloppement de jeux. Bien sĂ»r, le vĂ©lo a longtemps Ă©tĂ© inventĂ© et les cadres nĂ©cessaires pour travailler avec des matrices sont dĂ©jĂ  prĂȘts, et ils peuvent et doivent ĂȘtre utilisĂ©s. Cet article ne vise pas Ă  inventer un nouveau framework, mais montre l'implĂ©mentation d'opĂ©rations mathĂ©matiques de base pour travailler avec des matrices dans un style fonctionnel en utilisant le langage de programmation F #. En examinant le matĂ©riau, nous nous tournerons vers la thĂ©orie mathĂ©matique des matrices et verrons comment il peut ĂȘtre implĂ©mentĂ© dans le code.

Rappelons d'abord ce qu'est une matrice? La théorie nous dit ce qui suit
Un tableau rectangulaire de nombres contenant m lignes et n colonnes est appelé matrice de taille mxn

Les matrices sont généralement désignées par des majuscules de l'alphabet latin et s'écrivent comme suit:

A=(a11a12...a1na21a22...a2n............am1am2...amn)



Ou peu

A=(aij)



Pour travailler avec des matrices en F #, nous allons créer un enregistrement basé sur un tableau à deux dimensions, que nous développerons avec des méthodes utiles afin d'effectuer les opérations mathématiques nécessaires sur celui-ci.

type Matrix = { values: int[,] }
    with
        //    
    end

Ajouter une méthode d'assistance pour initialiser l'enregistrement avec un tableau à deux dimensions

static member ofArray2D (values: int [,]) = 
    { values = values }

L'argument d'entrée de la fonction sera un tableau à deux dimensions et, à sa sortie, un enregistrement de type Matrix . Voici un exemple d'initialisation d'enregistrement.

let a = array2D [[1;0;2]
                 [3;1;0]]
let A = Matrix.ofArray2D a

Deux matrices A = (aij) et B = (bij) sont appelées égales si elles coïncident par élément, c'est-à-dire aij = bij pour tout i = 1,2 ..., m et j = 1,2 ... n

Pour implémenter cette rÚgle, nous utiliserons l'opérateur substitué == et ajouterons quelques fonctions utiles, dont nous aurons également besoin à l'avenir.

static member sizes matrix =
    let rows = matrix.values.[*,0].Length
    let cols = matrix.values.[0,*].Length
    (rows, cols)

static member isEquallySized matrix1 matrix2 =
    let dim1 = Matrix.sizes matrix1
    let dim2 = Matrix.sizes matrix2
    (dim1 = dim2)

static member (==) (matrix1, matrix2) =
    if not (Matrix.isEquallySized matrix1 matrix2) then false
    else
        not (matrix1.values
               |> Array2D.mapi (fun x y v -> if matrix2.values.[x, y] <> v then false else true)
               |> Seq.cast<bool>
               |> Seq.contains false)

Examinons de plus prĂšs le code ci-dessus. Comme vous pouvez le voir, il existe trois fonctions. La premiĂšre fonction de tailles renvoie la dimension de la matrice sous forme de tuple. Puisque nous travaillons uniquement avec des matrices rectangulaires, pour obtenir le nombre de lignes, nous prenons une tranche complĂšte de la premiĂšre colonne et retournons sa longueur.

let rows = matrix.values.[*,0].Length

La dĂ©termination du nombre de colonnes fonctionne de la mĂȘme maniĂšre - nous obtenons une tranche complĂšte de la premiĂšre ligne et retournons sa longueur.

La fonction isEquallySized suivante compare la dimension de deux matrices et renvoie vrai si elles sont Ă©gales. Pour ce faire, il utilise la fonction de tailles prĂȘtes Ă  l'emploi et compare simplement les rĂ©sultats.

L'opérateur == pour comparer élément par élément deux matrices semble plus compliqué, mais maintenant vous verrez qu'il est également simple.

Avant de comparer deux matrices, nous comparons leur dimension. S'ils ne sont pas égaux, il n'y a pas d'autre point à vérifier, car il est déjà clair que les matrices ne seront pas égales.

if not (Matrix.isEquallySized matrix1 matrix2) then false

Ensuite, sur la base des matrices originales matrice1 et matrice2, nous formons une nouvelle matrice remplie de vrai ou de faux , selon que les cellules correspondantes des deux matrices coĂŻncident.

matrix1.values
|> Array2D.mapi (fun x y v -> if matrix2.values.[x, y] <> v then false else true

La fonction Array2D.mapi itÚre sur tous les éléments de matrix1 et transmet trois paramÚtres
x au gestionnaire - index de ligne
y - index de colonne
v -
contenu de la cellule Nous comparons le contenu de la cellule v avec la cellule de matrice correspondante2 et s'ils sont Ă©gaux, alors Ă©crire vrai , sinon faux .

S'il y a au moins une cellule avec le faux élément , cela signifie que les matrices ne sont pas égales les unes aux autres.

Comme Array2D ne contient pas de mĂ©thodes de filtrage ou de recherche, nous allons l'implĂ©menter nous-mĂȘmes. Pour ce faire, nous dĂ©composons la matrice en une liste linĂ©aire

|> Seq.cast<bool>

Et trouver au moins un décalage

|> Seq.contains false

La fonction Seq.contains renverra true si au moins une fausse valeur est trouvée dans la liste développée . Par conséquent, nous devons inverser le résultat afin que l'opérateur == fonctionne comme nous le voulons

else
    not (matrix1.values
           |> Array2D.mapi (fun x y v -> if matrix2.values.[x, y] <> v then false else true)
           |> Seq.cast<bool>
           |> Seq.contains false)

Une matrice O est appelée matrice nulle ou nulle si tous ses éléments sont égaux à zéro.


static member O rows cols =
    let array2d = Array2D.zeroCreate rows cols
    { values = array2d }

Un exemple d'utilisation de cette fonction

let AO = Matrix.O 5 5

Je crois qu'il n'y a rien de compliqué qui nécessite une explication, alors nous continuons.
Une matrice dont le nombre de lignes est égal au nombre de colonnes et égal à n est appelée matrice carrée d'ordre n

Ainsi, la matrice carrée a la forme.

A=[a11a12...a1na21a22...a2n............an1an2...ann]


Dans le cadre de cette rÚgle, nous allons créer une fonction qui transforme une matrice rectangulaire en matrice carrée en coupant tous les éléments qui ne tombent pas dans le carré.

static member toSquare matrix =

    //    
    let dim = Matrix.sizes matrix

    //   
    let colCount: int = snd dim
    //   
    let rowCount: int = fst dim

    //    
    let length = System.Math.Min (colCount, rowCount)

    //      
    //     
    let zero = Array2D.zeroCreate length length

    //     
    let square = zero |> Array2D.mapi (fun x y _ -> matrix.values.[x, y])

    //   
    { values = square }

Les commentaires dans le code source expliquent comment fonctionne la fonction, alors continuons.
Une matrice carrée est appelée triangulaire si tous ses éléments en dessous de la diagonale principale sont égaux à zéro, c'est-à-dire la matrice triangulaire a la forme

T=(a11a12...a1n0a22...a2n............00...ann)


Ci-dessous se trouve le code de fonction qui convertit la matrice d'origine en triangulaire. Mais dans notre fonction, nous travaillerons avec une matrice rectangulaire, c'est-Ă -dire qu'elle peut ne pas ĂȘtre carrĂ©e. Le lecteur peut facilement modifier le code de fonction afin qu'il renvoie une matrice triangulaire carrĂ©e en utilisant la fonction que nous avons examinĂ©e prĂ©cĂ©demment.

static member T matrix =
    let values = matrix.values |> Array2D.mapi (fun x y v -> if y < x then 0 else v)
    { values = values }

La fonction Array2D.mapi convertit le tableau bidimensionnel d'origine en un nouveau Ă  l'aide d'un gestionnaire qui prend trois paramĂštres

x - numéro de ligne
y - numéro de colonne
v - contenu de la cellule

if y < x then 0 else v

Ici, nous vérifions si l'élément est en dessous de la diagonale principale et si oui, remplissons la cellule 0. Sinon, la valeur initiale de la matrice d'entrée.

Voici un exemple d'utilisation de cette fonction.

let a = array2D [[1;2;3]
                 [4;5;6]
                 [7;8;9]
                 [10;11;12]]
let A = Matrix.ofArray2D a
let R = Matrix.triangular A
printfn "origin = \n %A" A.values
printfn "triangular = \n %A" R.values

Nous obtenons le résultat suivant

origin = 
 [[1; 2; 3]
 [4; 5; 6]
 [7; 8; 9]
 [10; 11; 12]]
triangular = 
 [[1; 2; 3]
 [0; 5; 6]
 [0; 0; 9]
 [0; 0; 0]]

Une matrice carrée est appelée diagonale si tous ses éléments situés à l'extérieur de la diagonale principale sont égaux à zéro


static member D matrix =
    let diagonal = matrix.values |> Array2D.mapi (fun x y v -> if x <> y then 0 else v)
    { values = diagonal }

Cette fonction est trÚs similaire à la précédente, seule la condition de vérification est différente. Voici un exemple d'utilisation

let a = array2D [[1;2;3]
                 [4;5;6]
                 [7;8;9]
                 [10;11;12]]
let A = Matrix.ofArray2D a
let R = Matrix.D A
printfn "origin = \n %A" A.values
printfn "diagonal = \n %A" R.values

origin = 
 [[1; 2; 3]
 [4; 5; 6]
 [7; 8; 9]
 [10; 11; 12]]
diagonal = 
 [[1; 0; 0]
 [0; 5; 0]
 [0; 0; 9]
 [0; 0; 0]]

La matrice diagonale est unitaire et est notée E si tous ses éléments situés sur la diagonale principale sont égaux à l'unité

E=(10...001...0............00...1)


L'implémentation d'une telle matrice sur F # ressemble à ceci

static member E rows cols =
    let array2d = Array2D.init rows cols (fun x y -> if x = y then 1 else 0)
    { values = array2d }

Opérations matricielles avec F #


Un certain nombre d'actions peuvent ĂȘtre effectuĂ©es sur des matrices, ainsi que sur des nombres, dont certaines sont similaires aux opĂ©rations sur les nombres, et certaines sont spĂ©cifiques.
La somme de deux matrices Amn = (aij) et Bmn = (bij) de mĂȘme taille est la matrice de mĂȘme taille A + B = Cmn = (cij) , dont les Ă©lĂ©ments sont Ă©gaux Ă  la somme des Ă©lĂ©ments des matrices A et B situĂ©es aux endroits correspondants

cij=aij+bij,i=1,2,...,m,j=1,2,...,n


Exemple, pour des matrices données A et B, on trouve la somme A + B

A=(231−506),B=(−331720),A+B=(−162226)


Considérez le code pour ajouter deux matrices

static member (+) (matrix1, matrix2) =
    if Matrix.isEquallySized matrix1 matrix2 then
        let array2d = matrix1.values |> Array2D.mapi (fun x y v -> matrix2.values.[x, y] + v)
        { values = array2d }
    else failwith "matrix1 is not equal to matrix2"

Avant d'ajouter des matrices, vous devez vous assurer que leur dimension coĂŻncide, sinon la fonction lĂšve une exception. Voici un exemple d'utilisation de cette fonction

let a = array2D [[2;3]
                 [1;-5]
                 [0;6]]
let A = Matrix.ofArray2D a

let b = array2D [[-3;3]
                 [1;7]
                 [2;0]]
let B = Matrix.ofArray2D b

let R = A+B
printfn "A+B =\n %A" R.values

A+B =
 [[-1; 6]
 [2; 2]
 [2; 6]]

Le produit de la matrice A = (aij) par le nombre k est la matrice kA = (kaij) de mĂȘme taille que la matrice A obtenue en multipliant tous les Ă©lĂ©ments de la matrice A par le nombre k

Exemple, pour une matrice A donnée on trouve la matrice 3A

A=(230154),3A=(69031512)



static member (*) (value, matrix) = 
    let array2d = matrix.values |> Array2D.mapi (fun _ _ v -> v * value)
    { values = array2d }

Matrice -A = (- 1) * A sera appelée la matrice opposée A . A partir de cette définition, nous procédons en douceur à la suivante
La différence des matrices A et B de tailles égales est la somme de la matrice A et de la matrice opposée à B

static member (-) (matrix1: Matrix, matrix2: Matrix) = 
    if Matrix.isEquallySized matrix1 matrix2 then
        matrix1 + (-1)*matrix2
    else failwith "matrix1 is not equal to matrix2"

Deux matrices sont dites cohérentes si le nombre de colonnes de la premiÚre est égal au nombre de lignes de la seconde

A=(2103),B=(05211437)



static member isMatched matrix1 matrix2 = 
    let row1Count = matrix1.values.[0,*].Length
    let col2Count = matrix2.values.[*,0].Length

    row1Count = col2Count

La vérification de la cohérence de la matrice est nécessaire pour les multiplier.
Le produit AB matrice cohérente Amn = (aij) et Bnp = (bjk) est la matrice Cmn = (cik) , l'élément cik est calculé comme la somme des éléments i- Úme ligne de la matrice A et des éléments correspondants de la k Úme colonne de la matrice B

Calculer le produit des matrices

A=(102310),B=(−1051−20)


Décision de déterminer le produit des matrices

AB=(1(−1)+0∗5+2(−2)1∗0+0∗1+2∗03(−1)+1∗5+0(−2)3∗0+1∗1+0∗0)=(−5021)


Considérez le code pour multiplier deux matrices

static member (*) (matrix1, (matrix2: Matrix)) =
    if Matrix.isMatched matrix1 matrix2 then
        let row1Count = matrix1.values.[*,0].Length
        let col2Count = matrix2.values.[0,*].Length

        let values = Array2D.zeroCreate row1Count col2Count

        for r in 0..row1Count-1 do
            for c in 0..col2Count-1 do
                let row = Array.toList matrix1.values.[r,*]
                let col = Array.toList matrix2.values.[*,c]

                let cell = List.fold2 (fun acc val1 val2 -> acc + (val1 * val2)) 0 row col
                values.[r,c] <- cell

        { values = values }

    else failwith "matrix1 is not matched to matrix2"

Examinons le code plus en détail.
Avant la multiplication, vous devez vous assurer que les matrices sont cohérentes

if Matrix.isMatched matrix1 matrix2 then

La matrice rĂ©sultante aura une dimension dans laquelle le nombre de lignes est le mĂȘme que la premiĂšre matrice et le nombre de colonnes est le mĂȘme que la deuxiĂšme matrice

let row1Count = matrix1.values.[*,0].Length
let col2Count = matrix2.values.[0,*].Length

//        
let values = Array2D.zeroCreate row1Count col2Count

AprĂšs cela, nous parcourons toutes les lignes et toutes les colonnes des matrices originales

for r in 0..row1Count-1 do
    for c in 0..col2Count-1 do
        let row = Array.toList matrix1.values.[r,*]
        let col = Array.toList matrix2.values.[*,c]

Nous calculons la valeur totale de chaque cellule

let cell = List.fold2 (fun acc val1 val2 -> acc + (val1 * val2)) 0 row col

La fonction List.fold2 reçoit deux listes en entrée (ligne et colonne) et transmet les paramÚtres suivants au gestionnaire

acc - l'accumulateur contenant le résultat du calcul précédent
val1 - le contenu de la cellule du premier tableau. Dans notre cas, il s'agit de la ligne de la premiĂšre matrice
val2 - le contenu de la cellule du deuxiĂšme tableau, c'est-Ă -dire les colonnes de la deuxiĂšme matrice.

Puisque les matrices sont cohérentes, nous sommes sûrs de ne pas aller au-delà des tableaux.

Le gestionnaire ajoute à l'accumulateur un produit de cellules provenant de lignes et d'une colonne, et la valeur résultante sera passée à l'itération suivante. Ainsi, le résultat final de la fonction List.fold2sera la valeur finale des produits de deux matrices. Il ne reste plus qu'à les remplir avec la matrice vide précédemment créée

values.[r,c] <- cell

Qui reviendra en conséquence

{ values = values }

Voici un exemple d'utilisation de cette fonction

let a = array2D [[1;0;2]
                 [3;1;0]]
let A = Matrix.ofArray2D a

let b = array2D [[-1;0]
                 [5;1]
                 [-2;0]]
let B = Matrix.ofArray2D b

let R = A*B

printfn "A*B =\n %A" R.values

A1*B1 =
 [[-5; 0]
 [2; 1]]

Si k ∈ N, alors la kiÚme puissance d'une matrice carrée A est le produit de k matrices A

Ak=A∗A∗...A(k−)


Considérez le code sur F # pour le produit d'une matrice à une puissance. La récursivité de la queue sera utilisée ici afin de ne pas faire déborder la pile avec de grandes puissances. La récursivité de queue est une récursivité que le compilateur convertit finalement en boucle. Si possible, il est recommandé que vous utilisiez toujours la récursivité de queue au lieu de la normale, mais pour cela, vous avez besoin de chaque trame de récursivité pour retourner la valeur calculée finale. Cette valeur est généralement appelée accumulateur et est transmise à la trame de récursivité suivante. Autrement dit, contrairement à la récursivité réguliÚre, qui renvoie la valeur calculée vers le haut de la pile, la récursivité de queue transmet la valeur calculée vers le bas de la pile. Chaque nouvelle trame de récursivité effectue ses propres calculs et les ajoute à la valeur précédemment calculée, qui est stockée dans la batterie. AprÚs ça,Comme la derniÚre image de la récursivité a fonctionné, l'accumulateur a déjà une valeur calculée, qui revient simplement en conséquence.

Ainsi, le compilateur peut optimiser le code et le convertir en boucle réguliÚre.


//    
// matrix -  
// value -  
static member (^^) (matrix, value) =

    //  ,    
    // m - 
    // p =  
    let inRecPow m p =

        //  
        // acc -  .   Matrix
        // p -     
        //         
        let rec recPow acc p =
            //   
            match p with
            | x when x > 0 ->
                //    
                //      ,      
                let nextAcc = acc*m
                //           
                recPow nextAcc (x-1)
            //    ,    
            | _ -> acc

        //   ,         
        let dim = Matrix.sizes matrix
        let colCount = snd dim
        let rowCount = fst dim

        let u = Matrix.E rowCount colCount
        //           
        recPow u p

    //  ,      
    let powMatrix = inRecPow matrix value
    //   
    { values = powMatrix.values }

Le code contient des commentaires détaillés sur son fonctionnement. Nécessite une petite explication, pourquoi la matrice unitaire est-elle utilisée? Il est nécessaire pour la premiÚre trame de récursivité et sert de valeur de base à l'accumulateur, dans lequel le résultat final sera accumulé.
Voici un exemple d'utilisation de notre fonction

A=(102−1)


A2=AA=(102−1)(102−1)=(1001)


A3=AA2=(102−1)(1001)=(102−1)


Nous calculons le produit suivant

f(A)=2A3−4A2+3E


OĂč E est la matrice d'identitĂ©. Puisque nous ne pouvons pas ajouter un nombre Ă  la matrice, nous devons ajouter 3E .

2(102−1)−4(1001)+3(1001)=(104−3)



//     
static member (+) (matrix, (value: int)) =
    let dim = Matrix.sizes matrix
    let r = fst dim
    let c = snd dim

    //   
    let unit = Matrix.E r c
    //          
    value*unit + matrix

let a = array2D [[1;0]
                 [2;-1]]
let A = Matrix.ofArray2D a

let R = 2*(A^^3) - 4*(A^^2) + 3
printfn "2*A^3 - 4*A^2 + 3 =\n %A" R.values

2*A5^3 - 4*A5^2 + 3 =
 [[1; 0]
 [4; -3]]

Une matrice A T dont les colonnes sont composĂ©es de rangĂ©es de matrice A avec les mĂȘmes nombres et la mĂȘme sĂ©quence d'Ă©lĂ©ments est appelĂ©e transposĂ©e en matrice A

static member transpose matrix =
    let dim = Matrix.sizes matrix
    let rows = fst dim
    let cols = snd dim

    //      
    let tMatrix = Matrix.O cols rows
    //       
    matrix.values |> Array2D.iteri(fun x y v -> tMatrix.values.[y, x] <- v)

    //  
    tMatrix

Exemple d'utilisation

let a = array2D [[1;2;3]
                 [4;5;6]
                 [7;8;9]
                 [10;11;12]]
let A = Matrix.ofArray2D a
let R6 = Matrix.T A
printfn "origin = \n %A" A.values
printfn "transpose = \n %A" R.values

origin = 
 [[1; 2; 3]
 [4; 5; 6]
 [7; 8; 9]
 [10; 11; 12]]
transpose = 
 [[1; 4; 7; 10]
 [2; 5; 8; 11]
 [3; 6; 9; 12]]

Sommaire


Dans cet article, nous avons examinĂ© des exemples de mise en Ɠuvre et d'utilisation de matrices issues de la thĂ©orie de l'algĂšbre linĂ©aire. Ainsi que les opĂ©rations mathĂ©matiques de base sur celles-ci, en utilisant une approche fonctionnelle en F #. J'espĂšre que le lecteur pourra apprĂ©cier la flexibilitĂ© offerte par les langages fonctionnels.

Le code source complet du module de matrice, dont des fragments ont été examinés dans l'article, vous pouvez le trouver sur le github.

Github Matrix.fs

All Articles