Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sparse matrix support #1698

Merged
merged 6 commits into from
Dec 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ saverestore.cpp
semshm.cpp
sigfpehandler.cpp
sorting.cpp
sparse_matrix.cpp
str.cpp
terminfo.cpp
tiff.cxx
Expand Down
15 changes: 15 additions & 0 deletions src/libinit_ac.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

#include "math_fun_ac.hpp"
#include "gsl_matrix.hpp"
#include "sparse_matrix.hpp"
#include "gsl_fun.hpp"

#include "smooth.hpp"
Expand Down Expand Up @@ -74,6 +75,20 @@ void LibInit_ac()
new DLibFunRetNew(lib::fx_root_fun,string("FX_ROOT"),2,fx_rootKey);

#endif

//sparse matrices by GD
const string sprsinKey[]={"THRESHOLD", "COLUMN", KLISTEND};
const string sprsinIgnoreKey[]={"DOUBLE", KLISTEND};
new DLibFunRetNew(lib::sprsin_fun,string("SPRSIN"),4,sprsinKey,sprsinIgnoreKey);
const string sprsabKey[]={"DOUBLE", "THRESHOLD",KLISTEND};
new DLibFunRetNew(lib::sprsab_fun,string("SPRSAB"),2,sprsabKey);
const string sprsaxKey[]={"DOUBLE",KLISTEND};
new DLibFunRetNew(lib::sprsax_fun,string("SPRSAX"),2,sprsaxKey);
new DLibFunRetNew(lib::fulstr_fun,string("FULSTR"),1);
new DLibFunRetNew(lib::sprstp_fun,string("SPRSTP"),1);
const string linbcgKey[]={"ITER", "DOUBLE", KLISTEND};
const string linbcgIgnoreKey[]={"ITOL", "TOL", "ITMAX", KLISTEND};
new DLibFunRetNew(lib::linbcg_fun,string("LINBCG"),3,linbcgKey,linbcgIgnoreKey);

const string spl1Key[]={"YP0","YPN_1","YP1","DOUBLE","HELP",KLISTEND}; //YP1 is old value for YP0
new DLibFunRetNew(lib::spl_init_fun,string("SPL_INIT"),2,spl1Key);
Expand Down
246 changes: 246 additions & 0 deletions src/sparse_matrix.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
/***************************************************************************
sparse_matrix.cpp - GDL sparse matrix functions
-------------------
begin : Dec 9 2023
copyright : (C) 2023 by Gilles Duvert
email : surname dot name at free dot fr
***************************************************************************/

/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/

#include <Eigen/Core>
#include <Eigen/Sparse>
#include <vector>
#include "sparse_matrix.hpp"

typedef Eigen::SparseMatrix<double, Eigen::RowMajor> SPMATRowMajDbl;
typedef Eigen::SparseVector<double> SPVecDbl;
static const float f_thr=1E-7;
static const double d_thr=1E-14;

//To save time, the 'external' (i.e., as a GDL variable) representation of a Eigen::SPMatrix is just a DPtr to the Eigen::SPMatrix itself
//I have not checked if the reference counting of Ptrs is sufficient to destroy the SPMatrix when the Ptr is destroyed.
//Otherwise this code may lead to leaks.
typedef std::vector<SPMATRowMajDbl>::iterator MatVecIterator;
namespace lib {

SPMATRowMajDbl* getFromPtr(EnvT* e, DUInt i) {
BaseGDL* p0 = e->GetParDefined(i); // must be struct
DType varType = p0->Type();
if (varType != GDL_PTR) e->Throw("Expression " + e->GetString(i) + "must be a PTR in this context.");
DPtrGDL* s = e->GetParAs<DPtrGDL>(i);
return *((SPMATRowMajDbl**)(s->DataAddr()));
}

//Version where the variable exchanged is a special structure. useful for save/restore, but slows down process in all other cases
SPMATRowMajDbl getFromStruct(EnvT* e, DUInt i) {
BaseGDL* p0 = e->GetParDefined(i); // must be struct
DType varType = p0->Type();

Check warning on line 45 in src/sparse_matrix.cpp

View check run for this annotation

Codecov / codecov/patch

src/sparse_matrix.cpp#L43-L45

Added lines #L43 - L45 were not covered by tests
if (varType != GDL_STRUCT) e->Throw("Expression " + e->GetString(i) + "must be a structure in this context.");
DStructGDL* s = e->GetParAs<DStructGDL>(i);
DLongGDL* N = static_cast<DLongGDL*> (s->GetTag(0));
int nCols = (*N)[0];
int nRows = (*N)[1];
int nnz = (*static_cast<DLongGDL*> (s->GetTag(1)))[0];

Check warning on line 51 in src/sparse_matrix.cpp

View check run for this annotation

Codecov / codecov/patch

src/sparse_matrix.cpp#L47-L51

Added lines #L47 - L51 were not covered by tests
if (nnz > 0 ) { //protect against 0
double* values = static_cast<double*> (s->GetTag(2)->DataAddr());
int* innerIndicesPtr = static_cast<int*> (s->GetTag(3)->DataAddr());
int* outerIndexPtr = static_cast<int*> (s->GetTag(4)->DataAddr());
Eigen::Map<SPMATRowMajDbl> Mat(nRows, nCols, nnz, outerIndexPtr, innerIndicesPtr, values);
return Mat;

Check warning on line 57 in src/sparse_matrix.cpp

View check run for this annotation

Codecov / codecov/patch

src/sparse_matrix.cpp#L53-L57

Added lines #L53 - L57 were not covered by tests
} else {
SPMATRowMajDbl Mat(nRows, nCols);
return Mat;
}

Check warning on line 61 in src/sparse_matrix.cpp

View check run for this annotation

Codecov / codecov/patch

src/sparse_matrix.cpp#L59-L61

Added lines #L59 - L61 were not covered by tests
}
//takes a SparseMatrix and returns the full GDL variable.
BaseGDL* convertToGDL(SPMATRowMajDbl* Mat) {
int ncols = Mat->cols();
int nrows = Mat->rows();
DDoubleGDL* ret = new DDoubleGDL(dimension(ncols, nrows), BaseGDL::ZERO);
const int outs=Mat->outerSize();
const int* outerStartIndexPtr = Mat->outerIndexPtr();
const int* innerIndicesPtr = Mat->innerIndexPtr();
const double* values = Mat->valuePtr();
for (auto iRow = 0; iRow < outs; ++iRow) {
int jValmin = outerStartIndexPtr[iRow];
int jValmax = outerStartIndexPtr[iRow + 1];
for (int kk = jValmin; kk < jValmax; ++kk) { //outerstart
int iCol = innerIndicesPtr[kk]; //row
(*ret)[iRow * ncols + iCol] = values[kk];
}
}
return ret;
}

//Version where the variable exchanged is a special structure. useful for save/restore, but slows down process in all other cases
DStructGDL* convertToStruct(const SPMATRowMajDbl Mat) {
int nCols = Mat.cols();
int nRows = Mat.rows();
DStructDesc* sd = new DStructDesc("$truct");
DStructGDL* s = new DStructGDL(sd, dimension(1));
DLongGDL* Dim = new DLongGDL(dimension(2), BaseGDL::NOZERO);
(*Dim)[0] = nCols;
(*Dim)[1] = nRows;
s->NewTag("DIM", Dim);
DLong nnz = Mat.nonZeros();
s->NewTag("NNZ", new DLongGDL(nnz));

Check warning on line 94 in src/sparse_matrix.cpp

View check run for this annotation

Codecov / codecov/patch

src/sparse_matrix.cpp#L84-L94

Added lines #L84 - L94 were not covered by tests
//protect against 0
if (nnz > 0) {
DDoubleGDL* Values = new DDoubleGDL(dimension(nnz), BaseGDL::NOZERO);

Check warning on line 97 in src/sparse_matrix.cpp

View check run for this annotation

Codecov / codecov/patch

src/sparse_matrix.cpp#L97

Added line #L97 was not covered by tests
for (auto i = 0; i < nnz; ++i) (*Values)[i] = Mat.valuePtr()[i];
s->NewTag("VALUES", Values);
DLongGDL* InnerIndices = new DLongGDL(dimension(nnz), BaseGDL::NOZERO);

Check warning on line 100 in src/sparse_matrix.cpp

View check run for this annotation

Codecov / codecov/patch

src/sparse_matrix.cpp#L99-L100

Added lines #L99 - L100 were not covered by tests
for (auto i = 0; i < nnz; ++i) (*InnerIndices)[i] = Mat.innerIndexPtr()[i];
s->NewTag("INNER_INDICES", InnerIndices);
int outs=Mat.outerSize();
DLongGDL* OuterStarts = new DLongGDL(dimension(outs+1), BaseGDL::NOZERO);

Check warning on line 104 in src/sparse_matrix.cpp

View check run for this annotation

Codecov / codecov/patch

src/sparse_matrix.cpp#L102-L104

Added lines #L102 - L104 were not covered by tests
for (auto i = 0; i < outs+1; ++i) (*OuterStarts)[i] = Mat.outerIndexPtr()[i];
s->NewTag("OUTER_STARTS", OuterStarts);

Check warning on line 106 in src/sparse_matrix.cpp

View check run for this annotation

Codecov / codecov/patch

src/sparse_matrix.cpp#L106

Added line #L106 was not covered by tests
}
return s;

Check warning on line 108 in src/sparse_matrix.cpp

View check run for this annotation

Codecov / codecov/patch

src/sparse_matrix.cpp#L108

Added line #L108 was not covered by tests
}

DPtrGDL* convertToPtr(const SPMATRowMajDbl *Mat) {
DPtr pointer = (DPtr)Mat; //(DPtr)(MatVec.data());
return new DPtrGDL(pointer);
}

BaseGDL* sprsin_fun(EnvT* e) {
static bool warned=false;
SizeT nParam = e->NParam(); //1 or 4
if (nParam != 1 && nParam != 4) e->Throw("Incorrect number of arguments.");
double thresh=d_thr;
static int COLUMN = e->KeywordIx("COLUMN");
if (e->KeywordSet(COLUMN)) {
e->Throw("GDL's SPRSIN does not yet support the COLUMN keyword. Please report or use transposed arrays.");

Check warning on line 123 in src/sparse_matrix.cpp

View check run for this annotation

Codecov / codecov/patch

src/sparse_matrix.cpp#L123

Added line #L123 was not covered by tests
}
static int THRESHOLD=e->KeywordIx("THRESHOLD");
if (e->KeywordSet(THRESHOLD)){
e->AssureDoubleScalarKW(THRESHOLD,thresh);
}
if (nParam == 1) {
BaseGDL* p0 = e->GetParDefined(0); // matrix
DType varType = p0->Type();
if (p0->Dim().Rank() != 2) e->Throw("Argument " + e->GetString(0) + " must be a square matrix.");
if (varType == GDL_STRING) e->Throw("Argument " + e->GetString(0) + " must not be of STRING type.");
int nCols = p0->Dim(0);
int nRows = p0->Dim(1);
if (nCols != nRows && !warned) {
Message("NOTE: use of SPRSIN with non-square matrices is a GDL extension.");
warned=true;

Check warning on line 138 in src/sparse_matrix.cpp

View check run for this annotation

Codecov / codecov/patch

src/sparse_matrix.cpp#L137-L138

Added lines #L137 - L138 were not covered by tests
}
DDoubleGDL* var = e->GetParAs<DDoubleGDL>(0);
SPMATRowMajDbl *Mat= new SPMATRowMajDbl(nRows,nCols);
//import and prune wrt. threshold
for (auto i=0; i<nCols; ++i) for (auto j=0; j<nRows; ++j) if (fabs((*var)[j*nCols+i])>=thresh) Mat->insert(j,i)= (*var)[j*nCols+i];
Mat->makeCompressed();
return convertToPtr(Mat);
} else if (nParam == 4) {
DLongGDL* cols = e->GetParAs<DLongGDL>(0);
int nCols=cols->N_Elements();
DLongGDL* rows = e->GetParAs<DLongGDL>(1);
int nRows=rows->N_Elements();

Check warning on line 150 in src/sparse_matrix.cpp

View check run for this annotation

Codecov / codecov/patch

src/sparse_matrix.cpp#L147-L150

Added lines #L147 - L150 were not covered by tests
if (nCols != nRows) e->Throw("Vector "+e->GetString(1) + " must have "+ i2s(nCols) + " elements.");
DDoubleGDL* vals = e->GetParAs<DDoubleGDL>(2);
int nVals=vals->N_Elements();

Check warning on line 153 in src/sparse_matrix.cpp

View check run for this annotation

Codecov / codecov/patch

src/sparse_matrix.cpp#L152-L153

Added lines #L152 - L153 were not covered by tests
if (nVals != nRows) e->Throw("Vector "+e->GetString(2) + " must have "+ i2s(nCols) + " elements.");
DLongGDL* sizegdl = e->GetParAs<DLongGDL>(3);
DLong size=(*sizegdl)[0];
SPMATRowMajDbl *Mat= new SPMATRowMajDbl(size,size); //only square matrices this way.
Mat->reserve(nCols);

Check warning on line 158 in src/sparse_matrix.cpp

View check run for this annotation

Codecov / codecov/patch

src/sparse_matrix.cpp#L155-L158

Added lines #L155 - L158 were not covered by tests
for (auto i=0; i< nCols; ++i) {
DLong irow=(*rows)[i];

Check warning on line 160 in src/sparse_matrix.cpp

View check run for this annotation

Codecov / codecov/patch

src/sparse_matrix.cpp#L160

Added line #L160 was not covered by tests
if (irow >= size ) e->Throw(" Out of range subscript encountered: "+e->GetString(0)+" .");
DLong icol=(*cols)[i];

Check warning on line 162 in src/sparse_matrix.cpp

View check run for this annotation

Codecov / codecov/patch

src/sparse_matrix.cpp#L162

Added line #L162 was not covered by tests
if (icol >= size ) e->Throw(" Out of range subscript encountered: "+e->GetString(1)+" .");
Mat->coeffRef(irow,icol)+=(*vals)[i]; //protect against doublons

Check warning on line 164 in src/sparse_matrix.cpp

View check run for this annotation

Codecov / codecov/patch

src/sparse_matrix.cpp#L164

Added line #L164 was not covered by tests
if (Mat->coeffRef(irow,icol)!=(*vals)[i]) e->Throw("Duplicate subscript encountered in Columns and Rows arrays at element: "+i2s(i));
}
return convertToPtr(Mat);

Check warning on line 167 in src/sparse_matrix.cpp

View check run for this annotation

Codecov / codecov/patch

src/sparse_matrix.cpp#L167

Added line #L167 was not covered by tests
} else e->Throw("Incorrect number of arguments.");

return new DLongGDL(0);
}

BaseGDL* sprsab_fun(EnvT* e){
SizeT nParam = e->NParam(2);
double thresh=d_thr;
static int THRESHOLD=e->KeywordIx("THRESHOLD");
if (e->KeywordSet(THRESHOLD)){
e->AssureDoubleScalarKW(THRESHOLD,thresh);

Check warning on line 178 in src/sparse_matrix.cpp

View check run for this annotation

Codecov / codecov/patch

src/sparse_matrix.cpp#L178

Added line #L178 was not covered by tests
}
SPMATRowMajDbl *Mat1=getFromPtr(e, 0);
SPMATRowMajDbl *Mat2=getFromPtr(e, 1);
SPMATRowMajDbl *Mat3=new SPMATRowMajDbl((*Mat1)*(*Mat2));
Mat3->prune(thresh);
return convertToPtr(Mat3);
}

BaseGDL* sprstp_fun(EnvT* e){
SizeT nParam = e->NParam(1);
SPMATRowMajDbl *Mat=getFromPtr(e, 0);
SPMATRowMajDbl *res=new SPMATRowMajDbl((*Mat).transpose());
return convertToPtr(res);
}
BaseGDL* sprsax_fun(EnvT* e) {
SizeT nParam = e->NParam(2);
SPMATRowMajDbl* Mat = getFromPtr(e, 0);
BaseGDL* p1 = e->GetParDefined(1); // vector
DType varType = p1->Type();
if (p1->Dim().Rank() != 1) e->Throw("Argument " + e->GetString(1) + " must be a vector.");
if (varType == GDL_STRING) e->Throw("Argument " + e->GetString(1) + " must not be of STRING type.");
int m = p1->Dim(0);
if (m != Mat->cols()) e->Throw("Argument " + e->GetString(1) + " does not have correct size.");
DDoubleGDL* var = e->GetParAs<DDoubleGDL>(1);
SPVecDbl Vec(m);
//import
for (auto i=0; i<m; ++i) Vec.insert(i)= (*var)[i];
SPMATRowMajDbl* Mat3 = new SPMATRowMajDbl(((*Mat)*Vec).transpose());
return convertToGDL(Mat3);
}

BaseGDL* linbcg_fun(EnvT* e) {
SizeT nParam = e->NParam(3);
SPMATRowMajDbl* A = getFromPtr(e, 0);
BaseGDL* p1 = e->GetParDefined(1); // Right Hand B
DType varType = p1->Type();
if (p1->Dim().Rank() != 1) e->Throw("Argument " + e->GetString(1) + " must be a vector.");
if (varType == GDL_STRING) e->Throw("Argument " + e->GetString(1) + " must not be of STRING type.");
int m = p1->Dim(0);
if (m != A->cols()) e->Throw("Argument " + e->GetString(1) + " does not have correct size.");
DDoubleGDL* Bgdl = e->GetParAs<DDoubleGDL>(1);
Eigen::Map<Eigen::VectorXd> B(&(*Bgdl)[0], m);
BaseGDL* p2 = e->GetParDefined(2); // Initial Guess
varType = p2->Type();
if (p2->Dim().Rank() != 1) e->Throw("Argument " + e->GetString(2) + " must be a vector.");
if (varType == GDL_STRING) e->Throw("Argument " + e->GetString(2) + " must not be of STRING type.");
int n = p2->Dim(0);
if (n != m) e->Throw("Argument " + e->GetString(2) + " does not have correct size.");
DDoubleGDL* Xgdl = e->GetParAs<DDoubleGDL>(2);
Eigen::Map<Eigen::VectorXd> X(&(*Xgdl)[0], n);
//solve ax=b
Eigen::SparseLU<Eigen::SparseMatrix<double>> sparseLU;
sparseLU.compute(*A);
if(sparseLU.info()!=Eigen::Success) e->Throw("Matrix decomposition failed.");
X=sparseLU.solve(B);
if(sparseLU.info()!=Eigen::Success) e->Throw("No solution found.");
DDoubleGDL* resD =new DDoubleGDL(n, BaseGDL::NOZERO);
Eigen::Map<Eigen::VectorXd>(&(*resD)[0], n) = X;
return resD;
}

BaseGDL* fulstr_fun(EnvT* e){
SizeT nParam = e->NParam(1);
// SPMATRowMajDbl Mat=getFromIndexInMatVec(e, 0);
SPMATRowMajDbl *Mat=getFromPtr(e, 0);
return convertToGDL(Mat);
}
}
33 changes: 33 additions & 0 deletions src/sparse_matrix.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/***************************************************************************
sparse_matrix.hpp - GDL sparse matrix functions
-------------------
begin : Dec 9 2023
copyright : (C) 2023 by Gilles Duvert
email : surname dot name at free dot fr
***************************************************************************/

/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#ifndef SPARSE_MATRIX_HPP_
#define SPARSE_MATRIX_HPP_

#include "includefirst.hpp"
#include "datatypes.hpp"
#include "envt.hpp"

namespace lib {

BaseGDL* sprsin_fun(EnvT* e);
BaseGDL* sprsax_fun(EnvT* e);
BaseGDL* sprsab_fun(EnvT* e);
BaseGDL* sprstp_fun(EnvT* e);
BaseGDL* fulstr_fun(EnvT* e);
BaseGDL* linbcg_fun(EnvT* e);
}
#endif
1 change: 1 addition & 0 deletions testsuite/LIST
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ test_scope_varname.pro
test_simplex.pro
test_size.pro
test_sort.pro
test_sparse.pro
test_spher_harm.pro
test_spl.pro
test_standardize.pro
Expand Down
20 changes: 20 additions & 0 deletions testsuite/test_sparse.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
; no a test, really. Just a suggestion for test, plu sthe fact
; that it exercises the coverage.
pro test_sparse

a = [[ 2.0, 1.0, 1.0],[ 4.0, -6.0, 0.0],[-2.0, 7.0, 2.0]]
z=sprsin(a, thresh=0.5)
zz=sprstp(z)
q=fulstr(z)
if abs(total(a-q)) gt 1E-6 then exit,status=1
res=sprsab(z,zz)
result=fulstr(res)
if total(result) ne 29 then exit,status=1
if total(sprsax(z,[1,1,1])) ne 9 then exit,status=1
aludc=a
LUDC, aludc, index
b = [3,-8.0,10]
x = LUSOL(aludc, index, b)
r= LINBCG(SPRSIN(a), B, X)
if abs(total(r-x)) gt 1E-6 then exit,status=1
end