Even if Eigen is probably not the solution for you problem at hand, it contains a couple of other interesting features that one might want to use from within Mathematica.
So here a minimal example with LibraryLink that compiles and load a function that computes the positive-definite part of a symmetric matrix A with respect to given positive-definite matrix B.
First you have to download Eigen and place it somewhere on your harddrive. You can do it with a package manager (which might make it automatically visible to you C++ compiler). But you can also simply store anywhere you want it to be and write a string with the path to the eigen3 directory into the variable $EigenIncludeDirectory; that's what I assume in the example below:
srcpath = "~";
outpath = "~";
Needs["CCompilerDriver`"];
Module[{opts, path, file, lib},
If[! FileExistsQ[srcpath], CreateDirectory[srcpath]];
If[! FileExistsQ[outpath], CreateDirectory[outpath]];
file = Export[FileNameJoin[{srcpath, "cClipGeneralizedEigenvalues.cpp"}],
"
#include"WolframLibrary.h"
#include<Eigen/Eigenvalues>
EXTERN_C DLLEXPORT int cClipGeneralizedEigenvalues(WolframLibraryData libData, mint Argc, MArgument *Args, MArgument Res)
{
MTensor Ain = MArgument_getMTensor(Args[0]);
MTensor Bin = MArgument_getMTensor(Args[1]);
MTensor Aout;
mint n = libData->MTensor_getDimensions(Ain)[0];
int err = libData->MTensor_new(MType_Real, 2, libData->MTensor_getDimensions(Ain), &Aout);
Eigen::GeneralizedSelfAdjointEigenSolver<Eigen::MatrixXd>eigs(n);
Eigen::Map<Eigen::MatrixXd>A(libData->MTensor_getRealData(Ain),n,n);
Eigen::Map<Eigen::MatrixXd>B(libData->MTensor_getRealData(Bin),n,n);
Eigen::Map<Eigen::MatrixXd>C(libData->MTensor_getRealData(Aout),n,n);
eigs.compute(A,B);
C=B*eigs.eigenvectors()*eigs.eigenvalues().cwiseMax(0).asDiagonal()*eigs.eigenvectors().inverse();
MArgument_setMTensor(Res,Aout);
return LIBRARY_NO_ERROR;
}"
,
"Text"
];
lib = CreateLibrary[{file}, "cClipGeneralizedEigenvalues",
"TargetDirectory" -> outpath,
(*"ShellCommandFunction"[Rule]Print,*)
"ShellOutputFunction" -> Print,
"IncludeDirectories" -> {$EigenIncludeDirectory}
];
With[{libfile = lib},
cClipGeneralizedEigenvalues::usage = "";
cClipGeneralizedEigenvalues := cClipGeneralizedEigenvalues =
LibraryFunctionLoad[libfile, "cClipGeneralizedEigenvalues", {{Real, 2}, {Real, 2}}, {Real, 2}];
]
]
Here is a simple usage example with timing comparison:
n = 1200;
A = Transpose[#].# &@RandomReal[{-1, 1}, {n, n}];
B = Transpose[#].# &@RandomReal[{-1, 1}, {n, n}];
resultEigen = cClipGeneralizedEigenvalues[A, B]; // AbsoluteTiming // First
resultEigen = cClipGeneralizedEigenvalues[A, B]; // AbsoluteTiming // First
First@AbsoluteTiming[
{[CapitalLambda], U} = Eigensystem[{A, B}];
result = LinearSolve[U, Ramp[[CapitalLambda]] (U . B)];
]
Max[Abs[resultEigen - result]]/Max[Abs[resultEigen]]
2.8983
2.75963
0.242256
1.60978*10^-9
When you run this the first time after compilation, you see that the first timing (2.8983) is a bit greater than the second (2.75963). This is due to the call to LibraryFunctionLoad. You also see that Eigen is very sluggish when not linked to any good linear algebra libraries. Eigen and Armadillo merely provide interfaces to dedicated linear algebra libraries -- something that Mathematica does, too. So when working with the latter, using the former should have little value in general.
mkl_sparse_d_mmin the MKL docs (https://software.intel.com/content/www/us/en/develop/documentation/onemkl-developer-reference-c/top/blas-and-sparse-blas-routines/inspector-executor-sparse-blas-routines/inspector-executor-sparse-blas-execution-routines/mkl-sparse-mm.html); grab the function's prototype from there and just use it in your"LTemplate`"workflow. The MKL should already be linked by every library created by"LTemplate`". The interface is not as fancy as in Armadillo or Eigen, but it gets the job done. – Henrik Schumacher Jul 20 '21 at 11:37