J'aimerais pouvoir calculer l'inverse d'une matrice générale NxN
en C/C++ en utilisant lapack.
Je crois comprendre que la façon de faire une inversion dans lapack est d'utiliser la fonction dgetri
, cependant, je ne peux pas comprendre ce que tous ses arguments sont censés être.
Voici le code que j'ai:
void dgetri_(int* N, double* A, int* lda, int* IPIV, double* WORK, int* lwork, int* INFO);
int main(){
double M [9] = {
1,2,3,
4,5,6,
7,8,9
};
return 0;
}
Comment le compléteriez-vous pour obtenir l'inverse de 3x3
matrice M utilisant dgetri_?
Premièrement, M doit être un tableau à deux dimensions, comme double M[3][3]
. Votre tableau est, mathématiquement parlant, un vecteur 1x9, qui n'est pas inversible.
N est un pointeur vers un int pour l'ordre de la matrice - dans ce cas, N = 3.
A est un pointeur vers la factorisation LU de la matrice, que vous pouvez obtenir en exécutant la routine LAPACK dgetrf
.
LDA est un entier pour "l'élément principal" de la matrice, qui vous permet de choisir un sous-ensemble d'une matrice plus grande si vous voulez simplement inverser un petit morceau. Si vous souhaitez inverser la matrice entière, LDA doit être juste égal à N.
IPIV est les indices pivots de la matrice, en d'autres termes, c'est une liste d'instructions des lignes à permuter pour inverser la matrice. IPIV doit être généré par la routine LAPACK dgetrf
.
LWORK et WORK sont les "espaces de travail" utilisés par LAPACK. Si vous inversez la matrice entière, LWORK doit être un entier égal à N ^ 2 et WORK doit être un double tableau avec des éléments LWORK.
INFO est simplement une variable d'état pour vous dire si l'opération s'est terminée avec succès. Étant donné que toutes les matrices ne sont pas inversibles, je vous recommande de l'envoyer à une sorte de système de vérification des erreurs. INFO = 0 pour une opération réussie, INFO = -i si le i'ième argument a une valeur d'entrée incorrecte et INFO> 0 si la matrice n'est pas inversible.
Donc, pour votre code, je ferais quelque chose comme ceci:
int main(){
double M[3][3] = { {1 , 2 , 3},
{4 , 5 , 6},
{7 , 8 , 9}}
double pivotArray[3]; //since our matrix has three rows
int errorHandler;
double lapackWorkspace[9];
// dgetrf(M,N,A,LDA,IPIV,INFO) means invert LDA columns of an M by N matrix
// called A, sending the pivot indices to IPIV, and spitting error
// information to INFO.
// also don't forget (like I did) that when you pass a two-dimensional array
// to a function you need to specify the number of "rows"
dgetrf_(3,3,M[3][],3,pivotArray[3],&errorHandler);
//some sort of error check
dgetri_(3,M[3][],3,pivotArray[3],9,lapackWorkspace,&errorHandler);
//another error check
}
Voici le code de travail pour calculer l'inverse d'une matrice en utilisant lapack en C/C++:
#include <cstdio>
extern "C" {
// LU decomoposition of a general matrix
void dgetrf_(int* M, int *N, double* A, int* lda, int* IPIV, int* INFO);
// generate inverse of a matrix given its LU decomposition
void dgetri_(int* N, double* A, int* lda, int* IPIV, double* WORK, int* lwork, int* INFO);
}
void inverse(double* A, int N)
{
int *IPIV = new int[N+1];
int LWORK = N*N;
double *WORK = new double[LWORK];
int INFO;
dgetrf_(&N,&N,A,&N,IPIV,&INFO);
dgetri_(&N,A,&N,IPIV,WORK,&LWORK,&INFO);
delete IPIV;
delete WORK;
}
int main(){
double A [2*2] = {
1,2,
3,4
};
inverse(A, 2);
printf("%f %f\n", A[0], A[1]);
printf("%f %f\n", A[2], A[3]);
return 0;
}
Voici une version fonctionnelle de ce qui précède utilisant l'interface OpenBlas vers LAPACKE. Lien avec la bibliothèque openblas (LAPACKE est déjà contenu)
#include <stdio.h>
#include "cblas.h"
#include "lapacke.h"
// inplace inverse n x n matrix A.
// matrix A is Column Major (i.e. firts line, second line ... *not* C[][] order)
// returns:
// ret = 0 on success
// ret < 0 illegal argument value
// ret > 0 singular matrix
lapack_int matInv(double *A, unsigned n)
{
int ipiv[n+1];
lapack_int ret;
ret = LAPACKE_dgetrf(LAPACK_COL_MAJOR,
n,
n,
A,
n,
ipiv);
if (ret !=0)
return ret;
ret = LAPACKE_dgetri(LAPACK_COL_MAJOR,
n,
A,
n,
ipiv);
return ret;
}
int main()
{
double A[] = {
0.378589, 0.971711, 0.016087, 0.037668, 0.312398,
0.756377, 0.345708, 0.922947, 0.846671, 0.856103,
0.732510, 0.108942, 0.476969, 0.398254, 0.507045,
0.162608, 0.227770, 0.533074, 0.807075, 0.180335,
0.517006, 0.315992, 0.914848, 0.460825, 0.731980
};
for (int i=0; i<25; i++) {
if ((i%5) == 0) putchar('\n');
printf("%+12.8f ",A[i]);
}
putchar('\n');
matInv(A,5);
for (int i=0; i<25; i++) {
if ((i%5) == 0) putchar('\n');
printf("%+12.8f ",A[i]);
}
putchar('\n');
}
Exemple:
% g++ -I [OpenBlas path]/include/ example.cpp [OpenBlas path]/lib/libopenblas.a
% a.out
+0.37858900 +0.97171100 +0.01608700 +0.03766800 +0.31239800
+0.75637700 +0.34570800 +0.92294700 +0.84667100 +0.85610300
+0.73251000 +0.10894200 +0.47696900 +0.39825400 +0.50704500
+0.16260800 +0.22777000 +0.53307400 +0.80707500 +0.18033500
+0.51700600 +0.31599200 +0.91484800 +0.46082500 +0.73198000
+0.24335255 -2.67946180 +3.57538817 +0.83711880 +0.34704217
+1.02790497 -1.05086895 -0.07468137 +0.71041070 +0.66708313
-0.21087237 -4.47765165 +1.73958308 +1.73999641 +3.69324020
-0.14100897 +2.34977565 -0.93725915 +0.47383541 -2.15554470
-0.26329660 +6.46315378 -4.07721533 -3.37094863 -2.42580445
Voici une version fonctionnelle de l'exemple de Spencer Nelson ci-dessus. Un mystère à ce sujet est que la matrice d'entrée est dans l'ordre des lignes principales, même si elle semble appeler la routine fortran sous-jacente dgetri
. Je suis amené à croire que toutes les routines fortran sous-jacentes nécessitent un ordre majeur de colonne, mais je ne suis pas un expert de LAPACK, en fait, j'utilise cet exemple pour m'aider à l'apprendre. Mais, ce mystère mis à part:
La matrice d'entrée dans l'exemple est singulière. LAPACK essaie de vous dire qu'en renvoyant un 3
dans le errorHandler
. J'ai changé le 9
dans cette matrice en un 19
, obtenant un errorHandler
sur 0
signalant le succès, et comparé le résultat à celui de Mathematica
. La comparaison a également réussi et a confirmé que la matrice de l'exemple devait être dans l'ordre des lignes principales, comme présenté.
Voici le code de travail:
#include <stdio.h>
#include <stddef.h>
#include <lapacke.h>
int main() {
int N = 3;
int NN = 9;
double M[3][3] = { {1 , 2 , 3},
{4 , 5 , 6},
{7 , 8 , 9} };
int pivotArray[3]; //since our matrix has three rows
int errorHandler;
double lapackWorkspace[9];
// dgetrf(M,N,A,LDA,IPIV,INFO) means invert LDA columns of an M by N matrix
// called A, sending the pivot indices to IPIV, and spitting error information
// to INFO. also don't forget (like I did) that when you pass a two-dimensional
// array to a function you need to specify the number of "rows"
dgetrf_(&N, &N, M[0], &N, pivotArray, &errorHandler);
printf ("dgetrf eh, %d, should be zero\n", errorHandler);
dgetri_(&N, M[0], &N, pivotArray, lapackWorkspace, &NN, &errorHandler);
printf ("dgetri eh, %d, should be zero\n", errorHandler);
for (size_t row = 0; row < N; ++row)
{ for (size_t col = 0; col < N; ++col)
{ printf ("%g", M[row][col]);
if (N-1 != col)
{ printf (", "); } }
if (N-1 != row)
{ printf ("\n"); } }
return 0; }
Je l'ai construit et exécuté comme suit sur un Mac:
gcc main.c -llapacke -llapack
./a.out
J'ai fait un nm
sur la bibliothèque LAPACKE et j'ai trouvé ce qui suit:
liblapacke.a(lapacke_dgetri.o):
U _LAPACKE_dge_nancheck
0000000000000000 T _LAPACKE_dgetri
U _LAPACKE_dgetri_work
U _LAPACKE_xerbla
U _free
U _malloc
liblapacke.a(lapacke_dgetri_work.o):
U _LAPACKE_dge_trans
0000000000000000 T _LAPACKE_dgetri_work
U _LAPACKE_xerbla
U _dgetri_
U _free
U _malloc
et il semble qu'il y ait un wrapper LAPACKE [sic] qui nous éviterait vraisemblablement d'avoir à emporter des adresses partout pour la commodité de fortran, mais je ne vais probablement pas essayer parce que j'ai un moyen d'avancer.
MODIFIER
Voici une version de travail qui contourne LAPACKE [sic], en utilisant directement les routines LAPACK fortran. Je ne comprends pas pourquoi une entrée de ligne principale produit des résultats corrects, mais je l'ai confirmé à nouveau dans Mathematica.
#include <stdio.h>
#include <stddef.h>
int main() {
int N = 3;
int NN = 9;
double M[3][3] = { {1 , 2 , 3},
{4 , 5 , 6},
{7 , 8 , 19} };
int pivotArray[3]; //since our matrix has three rows
int errorHandler;
double lapackWorkspace[9];
/* from http://www.netlib.no/netlib/lapack/double/dgetrf.f
SUBROUTINE DGETRF( M, N, A, LDA, IPIV, INFO )
*
* -- LAPACK routine (version 3.1) --
* Univ. of Tennessee, Univ. of California Berkeley and NAG Ltd..
* November 2006
*
* .. Scalar Arguments ..
INTEGER INFO, LDA, M, N
* ..
* .. Array Arguments ..
INTEGER IPIV( * )
DOUBLE PRECISION A( LDA, * )
*/
extern void dgetrf_ (int * m, int * n, double * A, int * LDA, int * IPIV,
int * INFO);
/* from http://www.netlib.no/netlib/lapack/double/dgetri.f
SUBROUTINE DGETRI( N, A, LDA, IPIV, WORK, LWORK, INFO )
*
* -- LAPACK routine (version 3.1) --
* Univ. of Tennessee, Univ. of California Berkeley and NAG Ltd..
* November 2006
*
* .. Scalar Arguments ..
INTEGER INFO, LDA, LWORK, N
* ..
* .. Array Arguments ..
INTEGER IPIV( * )
DOUBLE PRECISION A( LDA, * ), WORK( * )
*/
extern void dgetri_ (int * n, double * A, int * LDA, int * IPIV,
double * WORK, int * LWORK, int * INFO);
// dgetrf(M,N,A,LDA,IPIV,INFO) means invert LDA columns of an M by N matrix
// called A, sending the pivot indices to IPIV, and spitting error information
// to INFO. also don't forget (like I did) that when you pass a two-dimensional
// array to a function you need to specify the number of "rows"
dgetrf_(&N, &N, M[0], &N, pivotArray, &errorHandler);
printf ("dgetrf eh, %d, should be zero\n", errorHandler);
dgetri_(&N, M[0], &N, pivotArray, lapackWorkspace, &NN, &errorHandler);
printf ("dgetri eh, %d, should be zero\n", errorHandler);
for (size_t row = 0; row < N; ++row)
{ for (size_t col = 0; col < N; ++col)
{ printf ("%g", M[row][col]);
if (N-1 != col)
{ printf (", "); } }
if (N-1 != row)
{ printf ("\n"); } }
return 0; }
construit et exécuté comme ceci:
$ gcc foo.c -llapack
$ ./a.out
dgetrf eh, 0, should be zero
dgetri eh, 0, should be zero
-1.56667, 0.466667, 0.1
1.13333, 0.0666667, -0.2
0.1, -0.2, 0.1
MODIFIER
Le mystère ne semble plus être un mystère. Je pense que les calculs sont effectués dans l'ordre principal des colonnes, comme ils le doivent, mais je saisis et j'imprime les matrices comme si elles étaient dans l'ordre des lignes principales. J'ai deux bogues qui s'annulent l'un l'autre, donc les choses semblent rangées même si elles sont en colonnes.