Repository: equinor/GraphSPME (language: python)

Issue:
Create bindings to Py using e.g. PyBind
Se an example [here](https://github.com/pybind/python_example) for how this can be achieved.


Discussion / hints:

Fix patch:
diff --git a/.gitignore b/.gitignore
index 257a44a..1323f24 100644
--- a/.gitignore
+++ b/.gitignore
@@ -52,6 +52,11 @@
 # RStudio files
 .Rproj.user/
 
+# renv
+.Rprofile
+renv.lock
+/renv/
+
 # produced vignettes
 vignettes/*.html
 vignettes/*.pdf
@@ -86,3 +91,145 @@ rsconnect/
 *.zip
 
 .DS_Store
+
+# Using https://github.com/github/gitignore/blob/master/Python.gitignore
+
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+#  Usually these files are written by a python script from a template
+#  before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+*.py,cover
+.hypothesis/
+.pytest_cache/
+cover/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+db.sqlite3-journal
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+.pybuilder/
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+#   For a library or package, you might want to ignore these files since the code is
+#   intended to run in multiple environments; otherwise, check them in:
+# .python-version
+
+# pipenv
+#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+#   However, in case of collaboration, if having platform-specific dependencies or dependencies
+#   having no cross-platform support, pipenv may install dependencies that don't work, or not
+#   install all needed dependencies.
+#Pipfile.lock
+
+# PEP 582; used by e.g. github.com/David-OConnor/pyflow
+__pypackages__/
+
+# Celery stuff
+celerybeat-schedule
+celerybeat.pid
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+# pytype static type analyzer
+.pytype/
+
+# Cython debug symbols
+cython_debug/
+
diff --git a/GraphSPME/inst/include/graph_spme.hpp b/GraphSPME/inst/include/graph_spme.hpp
deleted file mode 100644
index 0ca2f41..0000000
--- a/GraphSPME/inst/include/graph_spme.hpp
+++ /dev/null
@@ -1,34 +0,0 @@
-// Includes all header files for GraphSPME
-// License: GPL-3
-
-#ifndef __GRAPHSPME_HPP_INCLUDED__
-#define __GRAPHSPME_HPP_INCLUDED__
-
-#include <RcppEigen.h>
-
-// Enable C++11 via this plugin (Rcpp 0.10.3 or later)
-// [[Rcpp::plugins("cpp11")]]
-
-// Enables Eigen
-// // [[Rcpp::depends(RcppEigen)]]
-
-template <class T>
-using Tvec = Eigen::Matrix<T,Eigen::Dynamic,1>;
-
-template <class T>
-using Tmat = Eigen::Matrix<T,Eigen::Dynamic,Eigen::Dynamic>;
-
-template<class T>
-using Tavec = Eigen::Array<T,Eigen::Dynamic,1>; 
-
-template<class T>
-using SpTMat = Eigen::SparseMatrix<T>;
-template<class T>
-using SpTMatMap = Eigen::MappedSparseMatrix<T>;
-
-using SpdMat = Eigen::SparseMatrix<double>;
-using SpdMatMap = Eigen::MappedSparseMatrix<double>;
-using SpiMat = Eigen::SparseMatrix<int>;
-using SpiMatMap = Eigen::MappedSparseMatrix<double>;
-
-#endif // __GRAPHSPME_HPP_INCLUDED__
\ No newline at end of file
diff --git a/GraphSPME/src/RcppExports.cpp b/GraphSPME/src/RcppExports.cpp
deleted file mode 100644
index 4656b94..0000000
--- a/GraphSPME/src/RcppExports.cpp
+++ /dev/null
@@ -1,48 +0,0 @@
-// Generated by using Rcpp::compileAttributes() -> do not edit by hand
-// Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393
-
-#include <RcppEigen.h>
-#include <Rcpp.h>
-
-using namespace Rcpp;
-
-#ifdef RCPP_USE_GLOBAL_ROSTREAM
-Rcpp::Rostream<true>&  Rcpp::Rcout = Rcpp::Rcpp_cout_get();
-Rcpp::Rostream<false>& Rcpp::Rcerr = Rcpp::Rcpp_cerr_get();
-#endif
-
-// cov_shrink_spd
-Eigen::Matrix<double,Eigen::Dynamic,Eigen::Dynamic> cov_shrink_spd(Eigen::Matrix<double,Eigen::Dynamic,Eigen::Dynamic>& x);
-RcppExport SEXP _GraphSPME_cov_shrink_spd(SEXP xSEXP) {
-BEGIN_RCPP
-    Rcpp::RObject rcpp_result_gen;
-    Rcpp::RNGScope rcpp_rngScope_gen;
-    Rcpp::traits::input_parameter< Eigen::Matrix<double,Eigen::Dynamic,Eigen::Dynamic>& >::type x(xSEXP);
-    rcpp_result_gen = Rcpp::wrap(cov_shrink_spd(x));
-    return rcpp_result_gen;
-END_RCPP
-}
-// prec_sparse
-Eigen::SparseMatrix<double> prec_sparse(Eigen::Matrix<double,Eigen::Dynamic,Eigen::Dynamic>& x, Eigen::SparseMatrix<double>& Z, bool cov_shrinkage);
-RcppExport SEXP _GraphSPME_prec_sparse(SEXP xSEXP, SEXP ZSEXP, SEXP cov_shrinkageSEXP) {
-BEGIN_RCPP
-    Rcpp::RObject rcpp_result_gen;
-    Rcpp::RNGScope rcpp_rngScope_gen;
-    Rcpp::traits::input_parameter< Eigen::Matrix<double,Eigen::Dynamic,Eigen::Dynamic>& >::type x(xSEXP);
-    Rcpp::traits::input_parameter< Eigen::SparseMatrix<double>& >::type Z(ZSEXP);
-    Rcpp::traits::input_parameter< bool >::type cov_shrinkage(cov_shrinkageSEXP);
-    rcpp_result_gen = Rcpp::wrap(prec_sparse(x, Z, cov_shrinkage));
-    return rcpp_result_gen;
-END_RCPP
-}
-
-static const R_CallMethodDef CallEntries[] = {
-    {"_GraphSPME_cov_shrink_spd", (DL_FUNC) &_GraphSPME_cov_shrink_spd, 1},
-    {"_GraphSPME_prec_sparse", (DL_FUNC) &_GraphSPME_prec_sparse, 3},
-    {NULL, NULL, 0}
-};
-
-RcppExport void R_init_GraphSPME(DllInfo *dll) {
-    R_registerRoutines(dll, NULL, CallEntries, NULL, NULL);
-    R_useDynamicSymbols(dll, FALSE);
-}
diff --git a/GraphSPME/.Rbuildignore b/R-package/.Rbuildignore
similarity index 100%
rename from GraphSPME/.Rbuildignore
rename to R-package/.Rbuildignore
diff --git a/GraphSPME/DESCRIPTION b/R-package/DESCRIPTION
similarity index 100%
rename from GraphSPME/DESCRIPTION
rename to R-package/DESCRIPTION
diff --git a/GraphSPME/NAMESPACE b/R-package/NAMESPACE
similarity index 100%
rename from GraphSPME/NAMESPACE
rename to R-package/NAMESPACE
diff --git a/GraphSPME/NEWS.md b/R-package/NEWS.md
similarity index 100%
rename from GraphSPME/NEWS.md
rename to R-package/NEWS.md
diff --git a/R-package/R-package.Rproj b/R-package/R-package.Rproj
new file mode 100644
index 0000000..a4dce49
--- /dev/null
+++ b/R-package/R-package.Rproj
@@ -0,0 +1,17 @@
+Version: 1.0
+
+RestoreWorkspace: Default
+SaveWorkspace: Default
+AlwaysSaveHistory: Default
+
+EnableCodeIndexing: Yes
+UseSpacesForTab: Yes
+NumSpacesForTab: 4
+Encoding: UTF-8
+
+RnwWeave: Sweave
+LaTeX: pdfLaTeX
+
+BuildType: Package
+PackageUseDevtools: Yes
+PackageInstallArgs: --no-multiarch --with-keep.source
diff --git a/GraphSPME/R/RcppExports.R b/R-package/R/RcppExports.R
similarity index 53%
rename from GraphSPME/R/RcppExports.R
rename to R-package/R/RcppExports.R
index 7306972..1f84c58 100644
--- a/GraphSPME/R/RcppExports.R
+++ b/R-package/R/RcppExports.R
@@ -2,10 +2,14 @@
 # Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393
 
 .cov_shrink_spd <- function(x) {
-    .Call(`_GraphSPME_cov_shrink_spd`, x)
+    .Call(`_GraphSPME__cov_shrink_spd`, x)
+}
+
+.sparse_matrix_inverse <- function(A) {
+    .Call(`_GraphSPME__sparse_matrix_inverse`, A)
 }
 
 .prec_sparse <- function(x, Z, cov_shrinkage = TRUE) {
-    .Call(`_GraphSPME_prec_sparse`, x, Z, cov_shrinkage)
+    .Call(`_GraphSPME__prec_sparse`, x, Z, cov_shrinkage)
 }
 
diff --git a/GraphSPME/R/cov_shrink_spd.R b/R-package/R/cov_shrink_spd.R
similarity index 100%
rename from GraphSPME/R/cov_shrink_spd.R
rename to R-package/R/cov_shrink_spd.R
diff --git a/GraphSPME/R/graphspme-package.R b/R-package/R/graphspme-package.R
similarity index 100%
rename from GraphSPME/R/graphspme-package.R
rename to R-package/R/graphspme-package.R
diff --git a/GraphSPME/R/prec_sparse.R b/R-package/R/prec_sparse.R
similarity index 100%
rename from GraphSPME/R/prec_sparse.R
rename to R-package/R/prec_sparse.R
diff --git a/GraphSPME/README.md b/R-package/README.md
similarity index 100%
rename from GraphSPME/README.md
rename to R-package/README.md
diff --git a/GraphSPME/man/GraphSPME.Rd b/R-package/man/GraphSPME.Rd
similarity index 100%
rename from GraphSPME/man/GraphSPME.Rd
rename to R-package/man/GraphSPME.Rd
diff --git a/GraphSPME/man/cov_shrink_spd.Rd b/R-package/man/cov_shrink_spd.Rd
similarity index 100%
rename from GraphSPME/man/cov_shrink_spd.Rd
rename to R-package/man/cov_shrink_spd.Rd
diff --git a/GraphSPME/man/prec_sparse.Rd b/R-package/man/prec_sparse.Rd
similarity index 100%
rename from GraphSPME/man/prec_sparse.Rd
rename to R-package/man/prec_sparse.Rd
diff --git a/GraphSPME/src/Makevars b/R-package/src/Makevars
similarity index 90%
rename from GraphSPME/src/Makevars
rename to R-package/src/Makevars
index d0a9db4..cb19f75 100644
--- a/GraphSPME/src/Makevars
+++ b/R-package/src/Makevars
@@ -6,7 +6,7 @@
 ## enable compilation with C++11 (or even C++14) where available
 #CXX_STD = CXX11
 
-PKG_CPPFLAGS = -I../inst/include
+PKG_CPPFLAGS = -I../../include/graph_spme
 PKG_LIBS = $(LAPACK_LIBS) $(BLAS_LIBS) $(FLIBS)
 #PKG_CXXFLAGS = -std=c++11
 CXX_STD = CXX11
\ No newline at end of file
diff --git a/GraphSPME/src/Makevars.win b/R-package/src/Makevars.win
similarity index 90%
rename from GraphSPME/src/Makevars.win
rename to R-package/src/Makevars.win
index d0a9db4..cb19f75 100644
--- a/GraphSPME/src/Makevars.win
+++ b/R-package/src/Makevars.win
@@ -6,7 +6,7 @@
 ## enable compilation with C++11 (or even C++14) where available
 #CXX_STD = CXX11
 
-PKG_CPPFLAGS = -I../inst/include
+PKG_CPPFLAGS = -I../../include/graph_spme
 PKG_LIBS = $(LAPACK_LIBS) $(BLAS_LIBS) $(FLIBS)
 #PKG_CXXFLAGS = -std=c++11
 CXX_STD = CXX11
\ No newline at end of file
diff --git a/R-package/src/RcppExports.cpp b/R-package/src/RcppExports.cpp
new file mode 100644
index 0000000..3287c3c
--- /dev/null
+++ b/R-package/src/RcppExports.cpp
@@ -0,0 +1,60 @@
+// Generated by using Rcpp::compileAttributes() -> do not edit by hand
+// Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393
+
+#include <RcppEigen.h>
+#include <Rcpp.h>
+
+using namespace Rcpp;
+
+#ifdef RCPP_USE_GLOBAL_ROSTREAM
+Rcpp::Rostream<true>&  Rcpp::Rcout = Rcpp::Rcpp_cout_get();
+Rcpp::Rostream<false>& Rcpp::Rcerr = Rcpp::Rcpp_cerr_get();
+#endif
+
+// _cov_shrink_spd
+Eigen::Matrix<double,Eigen::Dynamic,Eigen::Dynamic> _cov_shrink_spd(Eigen::Matrix<double,Eigen::Dynamic,Eigen::Dynamic>& x);
+RcppExport SEXP _GraphSPME__cov_shrink_spd(SEXP xSEXP) {
+BEGIN_RCPP
+    Rcpp::RObject rcpp_result_gen;
+    Rcpp::RNGScope rcpp_rngScope_gen;
+    Rcpp::traits::input_parameter< Eigen::Matrix<double,Eigen::Dynamic,Eigen::Dynamic>& >::type x(xSEXP);
+    rcpp_result_gen = Rcpp::wrap(_cov_shrink_spd(x));
+    return rcpp_result_gen;
+END_RCPP
+}
+// _sparse_matrix_inverse
+Eigen::Matrix<double,Eigen::Dynamic,Eigen::Dynamic> _sparse_matrix_inverse(Eigen::SparseMatrix<double>& A);
+RcppExport SEXP _GraphSPME__sparse_matrix_inverse(SEXP ASEXP) {
+BEGIN_RCPP
+    Rcpp::RObject rcpp_result_gen;
+    Rcpp::RNGScope rcpp_rngScope_gen;
+    Rcpp::traits::input_parameter< Eigen::SparseMatrix<double>& >::type A(ASEXP);
+    rcpp_result_gen = Rcpp::wrap(_sparse_matrix_inverse(A));
+    return rcpp_result_gen;
+END_RCPP
+}
+// _prec_sparse
+Eigen::SparseMatrix<double> _prec_sparse(Eigen::Matrix<double,Eigen::Dynamic,Eigen::Dynamic>& x, Eigen::SparseMatrix<double>& Z, bool cov_shrinkage);
+RcppExport SEXP _GraphSPME__prec_sparse(SEXP xSEXP, SEXP ZSEXP, SEXP cov_shrinkageSEXP) {
+BEGIN_RCPP
+    Rcpp::RObject rcpp_result_gen;
+    Rcpp::RNGScope rcpp_rngScope_gen;
+    Rcpp::traits::input_parameter< Eigen::Matrix<double,Eigen::Dynamic,Eigen::Dynamic>& >::type x(xSEXP);
+    Rcpp::traits::input_parameter< Eigen::SparseMatrix<double>& >::type Z(ZSEXP);
+    Rcpp::traits::input_parameter< bool >::type cov_shrinkage(cov_shrinkageSEXP);
+    rcpp_result_gen = Rcpp::wrap(_prec_sparse(x, Z, cov_shrinkage));
+    return rcpp_result_gen;
+END_RCPP
+}
+
+static const R_CallMethodDef CallEntries[] = {
+    {"_GraphSPME__cov_shrink_spd", (DL_FUNC) &_GraphSPME__cov_shrink_spd, 1},
+    {"_GraphSPME__sparse_matrix_inverse", (DL_FUNC) &_GraphSPME__sparse_matrix_inverse, 1},
+    {"_GraphSPME__prec_sparse", (DL_FUNC) &_GraphSPME__prec_sparse, 3},
+    {NULL, NULL, 0}
+};
+
+RcppExport void R_init_GraphSPME(DllInfo *dll) {
+    R_registerRoutines(dll, NULL, CallEntries, NULL, NULL);
+    R_useDynamicSymbols(dll, FALSE);
+}
diff --git a/R-package/src/graph_spme.cpp b/R-package/src/graph_spme.cpp
new file mode 100644
index 0000000..3568247
--- /dev/null
+++ b/R-package/src/graph_spme.cpp
@@ -0,0 +1,42 @@
+// Main functions for GraphSPME
+// License: GPL-3
+
+#include <Rcpp.h>
+#include "graph_spme.hpp"
+
+// Enable C++11 via this plugin (Rcpp 0.10.3 or later)
+// [[Rcpp::plugins("cpp11")]]
+
+/*
+ * Covariance shrinkage estimate as specified in Touloumis (2015)
+ */
+// [[Rcpp::export(.cov_shrink_spd)]]
+Eigen::Matrix<double,Eigen::Dynamic,Eigen::Dynamic> _cov_shrink_spd(
+        Eigen::Matrix<double,Eigen::Dynamic,Eigen::Dynamic>& x
+){
+    return cov_shrink_spd(x);
+}
+
+/*
+ *  Sparse precision matrix inverse
+ *  Employs cojugate gradient
+ *  Recomended by http://eigen.tuxfamily.org/dox-devel/group__TopicSparseSystems.html
+ */
+// [[Rcpp::export(.sparse_matrix_inverse)]]
+Eigen::Matrix<double,Eigen::Dynamic,Eigen::Dynamic> _sparse_matrix_inverse(Eigen::SparseMatrix<double>& A){
+    return sparse_matrix_inverse(A);
+}
+
+/*
+ * Graphical sparse precision matrix estimation
+ * as defined in Le (2021)
+ * and possibilities of using covariance shrinkage from Lunde etal (2022?)
+ */
+// [[Rcpp::export(.prec_sparse)]]
+Eigen::SparseMatrix<double> _prec_sparse(
+        Eigen::Matrix<double,Eigen::Dynamic,Eigen::Dynamic>& x,
+        Eigen::SparseMatrix<double>& Z,
+        bool cov_shrinkage=true
+){
+    return prec_sparse(x, Z, cov_shrinkage);
+}
diff --git a/GraphSPME/src/graph_spme.cpp b/include/graph_spme/graph_spme.hpp
similarity index 94%
rename from GraphSPME/src/graph_spme.cpp
rename to include/graph_spme/graph_spme.hpp
index 082b3e9..f32a051 100644
--- a/GraphSPME/src/graph_spme.cpp
+++ b/include/graph_spme/graph_spme.hpp
@@ -1,21 +1,13 @@
 // Main functions for GraphSPME
 // License: GPL-3
+#include <Eigen/Dense>
+#include <Eigen/Sparse>
 
-//#include "graph_spme.hpp"
-
-
-#include <RcppEigen.h>
-
-// Enable C++11 via this plugin (Rcpp 0.10.3 or later)
-// [[Rcpp::plugins("cpp11")]]
-
-// Enables Eigen
-// // [[Rcpp::depends(RcppEigen)]]
 
 
 using Dmat = Eigen::Matrix<double,Eigen::Dynamic,Eigen::Dynamic>;
 using SpdMat = Eigen::SparseMatrix<double>;
-using SpdMatMap = Eigen::MappedSparseMatrix<double>;
+// using SpdMatMap = Eigen::MappedSparseMatrix<double>;
 using dTriplet = Eigen::Triplet<double>;
 
 
@@ -61,7 +53,6 @@ Dmat cov_ml(Dmat& x){
 /*
  * Covariance shrinkage estimate as specified in Touloumis (2015)
  */
-// [[Rcpp::export(.cov_shrink_spd)]]
 Eigen::Matrix<double,Eigen::Dynamic,Eigen::Dynamic> cov_shrink_spd(
         Eigen::Matrix<double,Eigen::Dynamic,Eigen::Dynamic>& x
     ){
@@ -142,7 +133,6 @@ Dmat sparse_matrix_inverse(SpdMat& A){
  * as defined in Le (2021)
  * and possibilities of using covariance shrinkage from Lunde etal (2022?)
  */
-// [[Rcpp::export(.prec_sparse)]]
 Eigen::SparseMatrix<double> prec_sparse(
     Eigen::Matrix<double,Eigen::Dynamic,Eigen::Dynamic>& x,
     Eigen::SparseMatrix<double>& Z,
diff --git a/python-package/pyproject.toml b/python-package/pyproject.toml
new file mode 100644
index 0000000..6179a03
--- /dev/null
+++ b/python-package/pyproject.toml
@@ -0,0 +1,12 @@
+[build-system]
+requires = [
+    "setuptools>=42",
+    "wheel",
+    "pybind11>=2.8.0",
+]
+
+build-backend = "setuptools.build_meta"
+
+[tool.cibuildwheel]
+test-command = "python {project}/tests/test.py"
+test-skip = "*universal2:arm64"
diff --git a/python-package/setup.py b/python-package/setup.py
new file mode 100644
index 0000000..ef67e95
--- /dev/null
+++ b/python-package/setup.py
@@ -0,0 +1,32 @@
+import os
+from glob import glob
+from setuptools import setup
+
+from pybind11.setup_helpers import Pybind11Extension
+
+__version__ = "0.0.1"
+
+ext_modules = [
+    Pybind11Extension(
+        "graph_spme",
+        sorted(glob("src/*.cpp")),
+        cxx_std=14,
+        include_dirs=[os.path.join(os.path.dirname(__file__),"../include/graph_spme")],
+        # Example: passing in the version to the compiled code
+        define_macros=[("VERSION_INFO", __version__)],
+    ),
+]
+
+setup(
+    name="GraphSPME",
+    version=__version__,
+    author="Berent Ånund Strømnes Lunde",
+    author_email="lundeberent@gmail.com",
+    url="https://github.com/Blunde1/GraphSPME",
+    description="High dimensional precision matrix estimation with a known graphical structure",
+    long_description="",
+    ext_modules=ext_modules,
+    extras_require={"test": "pytest"},
+    zip_safe=False,
+    python_requires=">=3.6",
+)
diff --git a/python-package/src/pybind.cpp b/python-package/src/pybind.cpp
new file mode 100644
index 0000000..513d8d3
--- /dev/null
+++ b/python-package/src/pybind.cpp
@@ -0,0 +1,33 @@
+#include <pybind11/pybind11.h>
+#include <pybind11/eigen.h>
+
+#include "graph_spme.hpp"
+
+#define STRINGIFY(x) #x
+#define MACRO_STRINGIFY(x) STRINGIFY(x)
+
+namespace py = pybind11;
+
+PYBIND11_MODULE(graph_spme, m) {
+    m.doc() = R"pbdoc(
+        GraphSPME
+        ---------
+
+        .. currentmodule:: graph_spme
+
+        .. autosummary::
+           :toctree: _generate
+
+           prec_sparse
+    )pbdoc";
+
+    m.def("prec_sparse", &prec_sparse, R"pbdoc(
+        prec_sparse
+    )pbdoc");
+
+#ifdef VERSION_INFO
+    m.attr("__version__") = MACRO_STRINGIFY(VERSION_INFO);
+#else
+    m.attr("__version__") = "dev";
+#endif
+}


Test patch:
diff --git a/test.R b/test.R
new file mode 100644
index 0000000..425963a
--- /dev/null
+++ b/test.R
@@ -0,0 +1,68 @@
+renv::load("~/repos/GraphSPME")
+library(GraphSPME)
+library(Matrix)
+
+# Do a super-simple 1-dim example first!
+# zero mean AR1
+rar1 <- function(n, psi){
+    x <- numeric(n)
+    x[1] <- rnorm(1, 0, 1/sqrt(1-psi^2))
+    for(i in 2:n){
+        x[i] <- psi*x[i-1] + rnorm(1)
+    }
+    return(x)
+}
+
+set.seed(123)
+psi_values <- c(0,0.2,0.8)
+par(mfrow=c(1,3))
+for(psi in psi_values){
+    # Some random forward model
+    G <- function(mu_t0, psi){
+        u <- rar1(length(mu_t0), psi)
+        mu_t1 <- mu_t0 + u
+        return(mu_t1)
+    }
+    p <- 100
+    n <- 200
+    # Sample the prior at t0
+    mu_t0_t0_sample <- t(replicate(n, rar1(p,psi)))
+    # Bring forward to t1: Gives sample at t1
+    mu_t1_t0_sample <- t(sapply(1:n, function(i) G(mu_t0_t0_sample[i,], psi=psi)))
+
+    # Calculate prior 1|0 precision
+    # Defining non-zero elements
+    Z <- bandSparse(p, p,
+                    (-1):1,
+                    list(rep(1,p-1),
+                         rep(1,p),
+                         rep(1,p-1))
+    )
+    mu_t1_t0 <- colMeans(mu_t1_t0_sample)
+    prec_t1_t0 <- prec_sparse(mu_t1_t0_sample, Z, shrinkage = TRUE)
+    prec_t1_t0
+    nu_t1_t0 <- prec_t1_t0 %*% mu_t1_t0
+
+    # Calculate posterior 1|1 nu and precision
+    # Using information filter equations
+    # d is a sensor point at the middle p/2
+    ind <- ceiling(p/2)
+    d_middle_t1 <- 3
+    sd_d <- 1.3
+    prec_d <- matrix(1/sd_d^2, nrow=1, ncol=1)
+
+    # direct observation matrix of ind
+    M <- matrix(0, nrow=1, ncol=p)
+    M[1,ind] <- 1
+
+    nu_t1_t1 <- nu_t1_t0 + t(M) %*% prec_d %*% d_middle_t1
+    prec_t1_t1 <- prec_t1_t0 + t(M) %*% prec_d %*% M
+    mu_t1_t1 <- solve(prec_t1_t1) %*% nu_t1_t1
+
+    # Compare posterior estimate with prior
+    plot(mu_t1_t1 - mu_t1_t0,
+         xlab="state-element",
+         ylab="posterior - prior",
+         main = paste0("True dependence, psi: ", psi)
+    )
+}
diff --git a/test.py b/test.py
new file mode 100644
index 0000000..2237c1c
--- /dev/null
+++ b/test.py
@@ -0,0 +1,66 @@
+import functools
+import graph_spme
+import numpy as np
+import math
+import matplotlib.pyplot as plt
+import matplotlib
+from scipy import sparse
+
+matplotlib.use('Qt5Agg')
+
+# zero mean AR1
+def rar1(n, psi):
+    x = np.empty([n])
+    x[0] = np.random.normal(0,1/math.sqrt(1-psi**2),1)
+    for i in range(1,n):
+        x[i] = psi*x[i-1] + np.random.normal(0,1,1)
+    return x
+
+# Some random forward model
+def G(mu_t0, psi):
+    u = rar1(len(mu_t0), psi=psi)
+    mu_t1 = mu_t0 + u
+    return mu_t1
+
+if __name__ == "__main__":
+
+    np.random.seed(1)
+    n = 200
+    p = 100
+    fig, axes = plt.subplots(1, 3)
+    for psi, ax in zip([0.0, 0.2, 0.8],axes):
+        # Sample the prior at t0
+        mu_t0_t0_sample = np.tile(rar1(p,psi), (n,1))
+
+        # Bring forward to t1: Gives sample at t1
+        g_bound = functools.partial(G, psi=psi)
+        mu_t1_t0_sample = np.apply_along_axis(g_bound, 1, mu_t0_t0_sample)
+
+        # Calculate prior 1|0 precision
+        # Defining non-zero elements
+        diagonals = [[1]*p, [1]*(p-1), [1]*(p-1)]
+        Z = sparse.diags(diagonals, [0, -1, 1],format="csr")
+
+        prec_t1_t0 = graph_spme.prec_sparse(mu_t1_t0_sample, Z, True)
+
+        mu_t1_t0 = mu_t1_t0_sample.mean(axis=0).reshape((p,1))
+        nu_t1_t0 = prec_t1_t0 @ mu_t1_t0
+
+        # Calculate posterior 1|1 nu and precision
+        # Using information filter equations
+        # d is a sensor point at the middle p/2
+        ind = math.ceil(p/2)
+        d_middle_t1 = np.array([3])
+        sd_d = 1.3
+        prec_d = np.array([1/sd_d**2], ndmin=2)
+
+        M = np.array([0]*p, ndmin=2)
+        M[0,ind] = 1
+
+        nu_t1_t1 = nu_t1_t0 + (np.transpose(M) @ prec_d @ d_middle_t1).reshape((p,1))
+        prec_t1_t1 = prec_t1_t0 + np.transpose(M) @ prec_d @ M
+        mu_t1_t1 = np.linalg.inv(prec_t1_t1) * nu_t1_t1.reshape((p,1))
+
+        ax.plot(mu_t1_t1 - mu_t1_t0, "o", mfc='none')
+        ax.set(title=f"True dependence, psi: {psi}",xlabel="state-element",ylabel="posterior - prior")
+    plt.show()