├── .gitignore ├── .clang-format ├── .gitlab-ci.yml ├── unittest ├── python │ ├── test_multiple_registration.py │ ├── test_version.py │ ├── test_dimensions.py │ ├── test_std_pair.py │ ├── test_id.py │ ├── test_type_info.py │ ├── test_deprecation_policy.py │ ├── test_user_struct.py │ ├── test_SelfAdjointEigenSolver.py │ ├── test_EigenSolver.py │ ├── test_LLT.py │ ├── test_MINRES.py │ ├── test_std_map.py │ ├── test_LDLT.py │ ├── decompositions │ │ └── sparse │ │ │ ├── cholmod │ │ │ ├── test_CholmodSimplicialLDLT.py │ │ │ ├── test_CholmodSimplicialLLT.py │ │ │ └── test_CholmodSupernodalLLT.py │ │ │ ├── test_SimplicialLDLT.py │ │ │ ├── test_Accelerate.py │ │ │ ├── test_SimplicialLLT.py │ │ │ ├── test_SparseLU.py │ │ │ └── test_SparseQR.py │ ├── test_tensor.py │ ├── test_ComplexEigenSolver.py │ ├── test_complex.py │ ├── test_sparse_matrix.py │ ├── test_RealQZ.py │ ├── test_GeneralizedEigenSolver.py │ ├── test_RealSchur.py │ ├── test_std_unique_ptr.py │ ├── test_IncompleteLUT.py │ ├── test_return_by_ref.py │ ├── test_ComplexSchur.py │ ├── test_bind_optional.py.in │ ├── test_HessenbergDecomposition.py │ ├── test_IncompleteCholesky.py │ ├── test_PartialPivLU.py │ ├── test_bind_virtual.py │ ├── test_user_type.py │ ├── test_QR.py │ └── test_GeneralizedSelfAdjointEigenSolver.py ├── packaging │ └── cmake │ │ ├── extra_lib.cpp │ │ └── CMakeLists.txt ├── include.cpp ├── type_info.cpp ├── multiple_registration.cpp ├── user_struct.cpp ├── geometry.cpp ├── std_pair.cpp ├── deprecation_policy.cpp ├── std_array.cpp ├── return_by_ref.cpp ├── std_map.cpp ├── sparse_matrix.cpp ├── bind_optional.cpp.in ├── std_vector.cpp └── std_unique_ptr.cpp ├── .gitattributes ├── setup.cfg ├── .gitmodules ├── colcon.pkg ├── .github ├── dependabot.yml └── workflows │ ├── check-changelog.yml │ ├── update-flake-lock.yml │ ├── nix.yml │ ├── jrl-cmakemodules.yml │ ├── update_pixi_lockfile.yml │ ├── dockgen.yml │ ├── linux.yml │ ├── ros_ci.yml │ └── reloc.yml ├── dockgen.toml ├── pyproject.toml ├── src ├── matrix-bool.cpp ├── matrix-float.cpp ├── matrix-double.cpp ├── solvers │ ├── minres.cpp │ ├── incomplete-lut.cpp │ ├── incomplete-cholesky.cpp │ ├── bicgstab.cpp │ ├── solvers.cpp │ ├── conjugate-gradient.cpp │ ├── preconditioners.cpp │ └── least-squares-conjugate-gradient.cpp ├── angle-axis.cpp ├── matrix-char.cpp ├── matrix-long-double.cpp ├── decompositions │ ├── llt-solver.cpp │ ├── real-qz.cpp │ ├── ldlt-solver.cpp │ ├── bdcsvd-solver.cpp │ ├── real-schur.cpp │ ├── eigen-solver.cpp │ ├── complex-schur.cpp │ ├── fullpivlu-solver.cpp │ ├── partialpivlu-solver.cpp │ ├── permutation-matrix.cpp │ ├── tridiagonalization.cpp │ ├── complex-eigen-solver.cpp │ ├── generalized-eigen-solver.cpp │ ├── hessenberg-decomposition.cpp │ ├── self-adjoint-eigen-solver.cpp │ ├── simplicial-llt-solver.cpp │ ├── simplicial-ldlt-solver.cpp │ ├── generalized-self-adjoint-eigen-solver.cpp │ ├── qr-solvers.cpp │ ├── sparse-qr-solver.cpp │ ├── jacobisvd-solver.cpp │ ├── sparse-lu-solver.cpp │ ├── cholmod.cpp │ ├── accelerate.cpp │ └── decompositions.cpp ├── matrix-int8.cpp ├── matrix-int16.cpp ├── matrix-int32.cpp ├── matrix-int64.cpp ├── matrix-uint16.cpp ├── matrix-uint32.cpp ├── matrix-uint64.cpp ├── matrix-complex-double.cpp ├── matrix-complex-float.cpp ├── matrix-uint8.cpp ├── geometry-conversion.cpp ├── matrix-complex-long-double.cpp ├── quaternion.cpp ├── optional.cpp ├── matrix-mac-long.cpp ├── matrix-windows-long.cpp ├── matrix-mac-ulong.cpp ├── matrix-windows-ulong.cpp ├── matrix-linux-long-long.cpp ├── matrix-linux-ulong-long.cpp ├── std-vector.cpp ├── version.cpp ├── exception.cpp ├── numpy-type.cpp ├── scipy-type.cpp └── numpy.cpp ├── development ├── contributing.md ├── scripts │ └── pixi │ │ ├── activation.bat │ │ └── activation.sh ├── build.md └── release.md ├── include └── eigenpy │ ├── utils │ ├── is-aligned.hpp │ ├── empty-visitor.hpp │ ├── python-compat.hpp │ ├── scalar-name.hpp │ ├── is-approx.hpp │ └── traits.hpp │ ├── solvers │ ├── solvers.hpp │ ├── preconditioners.hpp │ ├── SparseSolverBase.hpp │ ├── BiCGSTAB.hpp │ ├── ConjugateGradient.hpp │ ├── LeastSquaresConjugateGradient.hpp │ └── BFGSPreconditioners.hpp │ ├── decompositions │ ├── minres.hpp │ ├── QR.hpp │ ├── sparse │ │ ├── LLT.hpp │ │ ├── LDLT.hpp │ │ ├── cholmod │ │ │ ├── CholmodDecomposition.hpp │ │ │ ├── CholmodSupernodalLLT.hpp │ │ │ ├── CholmodSimplicialLLT.hpp │ │ │ └── CholmodSimplicialLDLT.hpp │ │ ├── SparseSolverBase.hpp │ │ └── SimplicialLLT.hpp │ ├── decompositions.hpp │ └── HessenbergDecomposition.hpp │ ├── geometry.hpp │ ├── expose.hpp │ ├── computation-info.hpp │ ├── swig.hpp │ ├── memory.hpp │ ├── eigen │ └── EigenBase.hpp │ ├── id.hpp │ ├── copyable.hpp │ ├── exception.hpp │ ├── scalar-conversion.hpp │ ├── pickle-vector.hpp │ ├── std-map.hpp │ ├── version.hpp │ ├── geometry-conversion.hpp │ ├── eigen-typedef.hpp │ ├── scipy-type.hpp │ ├── std-pair.hpp │ ├── stride.hpp │ ├── eigenpy.hpp │ ├── registration.hpp │ ├── type_info.hpp │ └── details.hpp ├── .git-blame-ignore-revs ├── docker └── ubuntu │ └── Dockerfile ├── benchmarks └── bench-switch.py ├── .pre-commit-config.yaml ├── doc └── Doxyfile.extra.in ├── package.xml ├── python └── eigenpy │ ├── __init__.py │ └── windows_dll_manager.py ├── LICENSE ├── flake.lock └── flake.nix /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *build*/ 3 | # pixi environments 4 | .pixi 5 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Google 2 | SortIncludes: false 3 | Standard: Cpp11 4 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | include: https://rainboard.laas.fr/project/eigenpy/.gitlab-ci.yml 2 | -------------------------------------------------------------------------------- /unittest/python/test_multiple_registration.py: -------------------------------------------------------------------------------- 1 | import multiple_registration # noqa 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # GitHub syntax highlighting 2 | pixi.lock linguist-language=YAML 3 | 4 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [flake8] 2 | extend-ignore = E203 3 | max-line-length = 88 4 | exclude = cmake/.docs/cmake.py 5 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "cmake"] 2 | path = cmake 3 | url = https://github.com/jrl-umi3218/jrl-cmakemodules.git 4 | -------------------------------------------------------------------------------- /colcon.pkg: -------------------------------------------------------------------------------- 1 | { 2 | "hooks": [ 3 | "share/eigenpy/hook/ament_prefix_path.dsv", 4 | "share/eigenpy/hook/python_path.dsv" 5 | ] 6 | } -------------------------------------------------------------------------------- /unittest/python/test_version.py: -------------------------------------------------------------------------------- 1 | import eigenpy 2 | 3 | assert eigenpy.checkVersionAtLeast(0, 0, 0) 4 | assert eigenpy.__version__ != "" 5 | assert eigenpy.__raw_version__ != "" 6 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: / 5 | target-branch: devel 6 | schedule: 7 | interval: monthly 8 | -------------------------------------------------------------------------------- /unittest/packaging/cmake/extra_lib.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | int main(int /*argc*/, char** /*argv*/) { 5 | eigenpy::checkVersionAtLeast(0, 0, 0); 6 | return 0; 7 | } 8 | -------------------------------------------------------------------------------- /unittest/python/test_dimensions.py: -------------------------------------------------------------------------------- 1 | import eigenpy 2 | 3 | quat = eigenpy.Quaternion() 4 | 5 | # Switch to numpy.array 6 | coeffs_vector = quat.coeffs() 7 | assert len(coeffs_vector.shape) == 1 8 | -------------------------------------------------------------------------------- /unittest/python/test_std_pair.py: -------------------------------------------------------------------------------- 1 | from std_pair import copy, passthrough, std_pair_to_tuple 2 | 3 | t = (1, 2.0) 4 | assert std_pair_to_tuple(t) == t 5 | assert copy(t) == t 6 | assert passthrough(t) == t 7 | -------------------------------------------------------------------------------- /dockgen.toml: -------------------------------------------------------------------------------- 1 | [jrl-cmakemodules] 2 | url = "github:jrl-umi3218" 3 | 4 | [eigenpy] 5 | url = "." 6 | apt_deps = ["libboost-all-dev", "libeigen3-dev", "python3-numpy", "python3-scipy"] 7 | src_deps = ["jrl-cmakemodules"] 8 | -------------------------------------------------------------------------------- /unittest/include.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021, CNRS 3 | */ 4 | 5 | // Including this header should not raise a build error 6 | 7 | #include "eigenpy/registration.hpp" 8 | 9 | BOOST_PYTHON_MODULE(include) {} 10 | -------------------------------------------------------------------------------- /unittest/python/test_id.py: -------------------------------------------------------------------------------- 1 | import eigenpy 2 | 3 | ldlt1 = eigenpy.LDLT() 4 | ldlt2 = eigenpy.LDLT() 5 | 6 | id1 = ldlt1.id() 7 | id2 = ldlt2.id() 8 | 9 | assert id1 != id2 10 | assert id1 == ldlt1.id() 11 | assert id2 == ldlt2.id() 12 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.ruff] 2 | extend-exclude = ["cmake"] 3 | 4 | [tool.ruff.lint] 5 | extend-select = ["I", "NPY", "RUF", "UP", "W"] 6 | 7 | [tool.ruff.lint.isort] 8 | known-first-party = ["eigenpy"] 9 | 10 | [tool.tomlsort] 11 | all = true 12 | -------------------------------------------------------------------------------- /unittest/packaging/cmake/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.22) 2 | 3 | project(ExtraLib CXX) 4 | find_package(eigenpy REQUIRED) 5 | 6 | add_executable(extra_lib extra_lib.cpp) 7 | target_link_libraries(extra_lib PUBLIC eigenpy::eigenpy) 8 | -------------------------------------------------------------------------------- /unittest/python/test_type_info.py: -------------------------------------------------------------------------------- 1 | import type_info 2 | 3 | d = type_info.Dummy() 4 | assert "Dummy" in d.type_info().pretty_name() 5 | 6 | assert type_info.type_info(1).pretty_name() == "int" 7 | assert "basic_string" in type_info.type_info("toto").pretty_name() 8 | -------------------------------------------------------------------------------- /src/matrix-bool.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 INRIA 3 | */ 4 | 5 | #include "eigenpy/eigenpy.hpp" 6 | 7 | namespace eigenpy { 8 | void exposeMatrixBool() { 9 | exposeType(); 10 | exposeType(); 11 | } 12 | } // namespace eigenpy 13 | -------------------------------------------------------------------------------- /src/matrix-float.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 INRIA 3 | */ 4 | 5 | #include "eigenpy/eigenpy.hpp" 6 | 7 | namespace eigenpy { 8 | void exposeMatrixFloat() { 9 | exposeType(); 10 | exposeType(); 11 | } 12 | } // namespace eigenpy 13 | -------------------------------------------------------------------------------- /src/matrix-double.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 INRIA 3 | */ 4 | 5 | #include "eigenpy/eigenpy.hpp" 6 | 7 | namespace eigenpy { 8 | void exposeMatrixDouble() { 9 | exposeType(); 10 | exposeType(); 11 | } 12 | } // namespace eigenpy 13 | -------------------------------------------------------------------------------- /unittest/python/test_deprecation_policy.py: -------------------------------------------------------------------------------- 1 | from deprecation_policy import ( 2 | X, 3 | some_deprecated_function, 4 | some_future_deprecated_function, 5 | ) 6 | 7 | some_deprecated_function() 8 | some_future_deprecated_function() 9 | X().deprecated_member_function() 10 | -------------------------------------------------------------------------------- /src/solvers/minres.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 INRIA 3 | */ 4 | 5 | #include "eigenpy/solvers/MINRES.hpp" 6 | 7 | namespace eigenpy { 8 | void exposeMINRES() { 9 | using namespace Eigen; 10 | MINRESSolverVisitor::expose("MINRES"); 11 | } 12 | } // namespace eigenpy 13 | -------------------------------------------------------------------------------- /src/angle-axis.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2019, CNRS 3 | * Copyright 2018-2023, INRIA 4 | */ 5 | 6 | #include "eigenpy/angle-axis.hpp" 7 | #include "eigenpy/geometry.hpp" 8 | 9 | namespace eigenpy { 10 | void exposeAngleAxis() { expose(); } 11 | } // namespace eigenpy 12 | -------------------------------------------------------------------------------- /src/matrix-char.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 INRIA 3 | */ 4 | 5 | #include "eigenpy/eigenpy.hpp" 6 | 7 | #include 8 | 9 | namespace eigenpy { 10 | void exposeMatrixChar() { 11 | exposeType(); 12 | exposeType(); 13 | } 14 | } // namespace eigenpy 15 | -------------------------------------------------------------------------------- /src/matrix-long-double.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 INRIA 3 | */ 4 | 5 | #include "eigenpy/eigenpy.hpp" 6 | 7 | namespace eigenpy { 8 | void exposeMatrixLongDouble() { 9 | exposeType(); 10 | exposeType(); 11 | } 12 | } // namespace eigenpy 13 | -------------------------------------------------------------------------------- /src/decompositions/llt-solver.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 INRIA 3 | */ 4 | 5 | #include "eigenpy/decompositions/LLT.hpp" 6 | 7 | namespace eigenpy { 8 | void exposeLLTSolver() { 9 | using namespace Eigen; 10 | LLTSolverVisitor::expose("LLT"); 11 | } 12 | } // namespace eigenpy 13 | -------------------------------------------------------------------------------- /src/decompositions/real-qz.cpp: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright 2025 INRIA 4 | */ 5 | 6 | #include "eigenpy/decompositions/RealQZ.hpp" 7 | 8 | namespace eigenpy { 9 | void exposeRealQZ() { 10 | using namespace Eigen; 11 | RealQZVisitor::expose("RealQZ"); 12 | } 13 | } // namespace eigenpy 14 | -------------------------------------------------------------------------------- /src/matrix-int8.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 INRIA 3 | */ 4 | 5 | #include "eigenpy/eigenpy.hpp" 6 | 7 | #include 8 | 9 | namespace eigenpy { 10 | void exposeMatrixInt8() { 11 | exposeType(); 12 | exposeType(); 13 | } 14 | } // namespace eigenpy 15 | -------------------------------------------------------------------------------- /src/decompositions/ldlt-solver.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 INRIA 3 | */ 4 | 5 | #include "eigenpy/decompositions/LDLT.hpp" 6 | 7 | namespace eigenpy { 8 | void exposeLDLTSolver() { 9 | using namespace Eigen; 10 | LDLTSolverVisitor::expose("LDLT"); 11 | } 12 | } // namespace eigenpy 13 | -------------------------------------------------------------------------------- /src/matrix-int16.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 INRIA 3 | */ 4 | 5 | #include "eigenpy/eigenpy.hpp" 6 | 7 | #include 8 | 9 | namespace eigenpy { 10 | void exposeMatrixInt16() { 11 | exposeType(); 12 | exposeType(); 13 | } 14 | } // namespace eigenpy 15 | -------------------------------------------------------------------------------- /src/matrix-int32.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 INRIA 3 | */ 4 | 5 | #include "eigenpy/eigenpy.hpp" 6 | 7 | #include 8 | 9 | namespace eigenpy { 10 | void exposeMatrixInt32() { 11 | exposeType(); 12 | exposeType(); 13 | } 14 | } // namespace eigenpy 15 | -------------------------------------------------------------------------------- /src/matrix-int64.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 INRIA 3 | */ 4 | 5 | #include "eigenpy/eigenpy.hpp" 6 | 7 | #include 8 | 9 | namespace eigenpy { 10 | void exposeMatrixInt64() { 11 | exposeType(); 12 | exposeType(); 13 | } 14 | } // namespace eigenpy 15 | -------------------------------------------------------------------------------- /src/decompositions/bdcsvd-solver.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 INRIA 3 | */ 4 | 5 | #include "eigenpy/decompositions/BDCSVD.hpp" 6 | 7 | namespace eigenpy { 8 | void exposeBDCSVDSolver() { 9 | using namespace Eigen; 10 | BDCSVDVisitor::expose("BDCSVD"); 11 | } 12 | } // namespace eigenpy 13 | -------------------------------------------------------------------------------- /src/matrix-uint16.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 INRIA 3 | */ 4 | 5 | #include "eigenpy/eigenpy.hpp" 6 | 7 | #include 8 | 9 | namespace eigenpy { 10 | void exposeMatrixUInt16() { 11 | exposeType(); 12 | exposeType(); 13 | } 14 | } // namespace eigenpy 15 | -------------------------------------------------------------------------------- /src/matrix-uint32.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 INRIA 3 | */ 4 | 5 | #include "eigenpy/eigenpy.hpp" 6 | 7 | #include 8 | 9 | namespace eigenpy { 10 | void exposeMatrixUInt32() { 11 | exposeType(); 12 | exposeType(); 13 | } 14 | } // namespace eigenpy 15 | -------------------------------------------------------------------------------- /src/matrix-uint64.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 INRIA 3 | */ 4 | 5 | #include "eigenpy/eigenpy.hpp" 6 | 7 | #include 8 | 9 | namespace eigenpy { 10 | void exposeMatrixUInt64() { 11 | exposeType(); 12 | exposeType(); 13 | } 14 | } // namespace eigenpy 15 | -------------------------------------------------------------------------------- /src/decompositions/real-schur.cpp: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright 2025 INRIA 4 | */ 5 | 6 | #include "eigenpy/decompositions/RealSchur.hpp" 7 | 8 | namespace eigenpy { 9 | void exposeRealSchur() { 10 | using namespace Eigen; 11 | RealSchurVisitor::expose("RealSchur"); 12 | } 13 | } // namespace eigenpy 14 | -------------------------------------------------------------------------------- /src/matrix-complex-double.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 INRIA 3 | */ 4 | 5 | #include "eigenpy/eigenpy.hpp" 6 | 7 | namespace eigenpy { 8 | void exposeMatrixComplexDouble() { 9 | exposeType>(); 10 | exposeType, Eigen::RowMajor>(); 11 | } 12 | } // namespace eigenpy 13 | -------------------------------------------------------------------------------- /src/matrix-complex-float.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 INRIA 3 | */ 4 | 5 | #include "eigenpy/eigenpy.hpp" 6 | 7 | namespace eigenpy { 8 | void exposeMatrixComplexFloat() { 9 | exposeType>(); 10 | exposeType, Eigen::RowMajor>(); 11 | } 12 | } // namespace eigenpy 13 | -------------------------------------------------------------------------------- /src/matrix-uint8.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 INRIA 3 | */ 4 | 5 | #include "eigenpy/eigenpy.hpp" 6 | 7 | #include 8 | 9 | namespace eigenpy { 10 | void exposeMatrixUInt8() { 11 | exposeType(); 12 | exposeType(); 13 | } 14 | } // namespace eigenpy 15 | -------------------------------------------------------------------------------- /development/contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Standard matrix decomposion routines of Eigen such as the SVD and QR decompositions 4 | can be readily added to **EigenPy** following the example of the Cholesky decomposition 5 | that is already implemented. 6 | Feel free to open a PR if you wrap them for your use case. 7 | 8 | -------------------------------------------------------------------------------- /src/decompositions/eigen-solver.cpp: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright 2024 INRIA 4 | */ 5 | 6 | #include "eigenpy/decompositions/EigenSolver.hpp" 7 | 8 | namespace eigenpy { 9 | void exposeEigenSolver() { 10 | using namespace Eigen; 11 | EigenSolverVisitor::expose("EigenSolver"); 12 | } 13 | } // namespace eigenpy 14 | -------------------------------------------------------------------------------- /unittest/python/test_user_struct.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from user_struct import MyStruct 3 | 4 | x = np.ones(3) 5 | y = np.ones(4) 6 | ms = MyStruct(x, y) 7 | print(ms.x) 8 | print(ms.y) 9 | 10 | ms.x[0] = 0.0 11 | 12 | ms.x = x # ok 13 | assert np.allclose(ms.x, x) 14 | 15 | ms.y[:] = y 16 | ms.y = y # segfault 17 | -------------------------------------------------------------------------------- /src/decompositions/complex-schur.cpp: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright 2025 INRIA 4 | */ 5 | 6 | #include "eigenpy/decompositions/ComplexSchur.hpp" 7 | 8 | namespace eigenpy { 9 | void exposeComplexSchur() { 10 | using namespace Eigen; 11 | ComplexSchurVisitor::expose("ComplexSchur"); 12 | } 13 | } // namespace eigenpy 14 | -------------------------------------------------------------------------------- /src/decompositions/fullpivlu-solver.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 INRIA 3 | */ 4 | 5 | #include "eigenpy/decompositions/FullPivLU.hpp" 6 | 7 | namespace eigenpy { 8 | void exposeFullPivLUSolver() { 9 | using namespace Eigen; 10 | FullPivLUSolverVisitor::expose("FullPivLU"); 11 | } 12 | } // namespace eigenpy 13 | -------------------------------------------------------------------------------- /src/geometry-conversion.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2019, CNRS 3 | * Copyright 2018-2023, INRIA 4 | */ 5 | 6 | #include "eigenpy/geometry-conversion.hpp" 7 | #include "eigenpy/geometry.hpp" 8 | 9 | namespace eigenpy { 10 | void exposeGeometryConversion() { EulerAnglesConvertor::expose(); } 11 | } // namespace eigenpy 12 | -------------------------------------------------------------------------------- /src/matrix-complex-long-double.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 INRIA 3 | */ 4 | 5 | #include "eigenpy/eigenpy.hpp" 6 | 7 | namespace eigenpy { 8 | void exposeMatrixComplexLongDouble() { 9 | exposeType>(); 10 | exposeType, Eigen::RowMajor>(); 11 | } 12 | } // namespace eigenpy 13 | -------------------------------------------------------------------------------- /src/quaternion.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2019, CNRS 3 | * Copyright 2018-2023, INRIA 4 | */ 5 | 6 | #include 7 | 8 | #include "eigenpy/geometry.hpp" 9 | #include "eigenpy/quaternion.hpp" 10 | 11 | namespace eigenpy { 12 | void exposeQuaternion() { expose(); } 13 | } // namespace eigenpy 14 | -------------------------------------------------------------------------------- /src/decompositions/partialpivlu-solver.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 INRIA 3 | */ 4 | 5 | #include "eigenpy/decompositions/PartialPivLU.hpp" 6 | 7 | namespace eigenpy { 8 | void exposePartialPivLUSolver() { 9 | using namespace Eigen; 10 | PartialPivLUSolverVisitor::expose("PartialPivLU"); 11 | } 12 | } // namespace eigenpy 13 | -------------------------------------------------------------------------------- /src/decompositions/permutation-matrix.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 INRIA 3 | */ 4 | 5 | #include "eigenpy/decompositions/PermutationMatrix.hpp" 6 | 7 | namespace eigenpy { 8 | void exposePermutationMatrix() { 9 | using namespace Eigen; 10 | PermutationMatrixVisitor::expose("PermutationMatrix"); 11 | } 12 | } // namespace eigenpy 13 | -------------------------------------------------------------------------------- /src/decompositions/tridiagonalization.cpp: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright 2025 INRIA 4 | */ 5 | 6 | #include "eigenpy/decompositions/Tridiagonalization.hpp" 7 | 8 | namespace eigenpy { 9 | void exposeTridiagonalization() { 10 | using namespace Eigen; 11 | TridiagonalizationVisitor::expose("Tridiagonalization"); 12 | } 13 | } // namespace eigenpy 14 | -------------------------------------------------------------------------------- /src/decompositions/complex-eigen-solver.cpp: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright 2025 INRIA 4 | */ 5 | 6 | #include "eigenpy/decompositions/ComplexEigenSolver.hpp" 7 | 8 | namespace eigenpy { 9 | void exposeComplexEigenSolver() { 10 | using namespace Eigen; 11 | ComplexEigenSolverVisitor::expose("ComplexEigenSolver"); 12 | } 13 | } // namespace eigenpy 14 | -------------------------------------------------------------------------------- /src/optional.cpp: -------------------------------------------------------------------------------- 1 | /// 2 | /// Copyright 2023 CNRS, INRIA 3 | /// 4 | 5 | #include "eigenpy/optional.hpp" 6 | 7 | namespace eigenpy { 8 | void exposeNoneType() { 9 | detail::NoneToPython::registration(); 10 | #ifdef EIGENPY_WITH_CXX17_SUPPORT 11 | detail::NoneToPython::registration(); 12 | #endif 13 | } 14 | } // namespace eigenpy 15 | -------------------------------------------------------------------------------- /src/solvers/incomplete-lut.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 INRIA 3 | */ 4 | 5 | #include "eigenpy/solvers/IncompleteLUT.hpp" 6 | 7 | namespace eigenpy { 8 | void exposeIncompleteLUT() { 9 | typedef Eigen::SparseMatrix ColMajorSparseMatrix; 10 | IncompleteLUTVisitor::expose("IncompleteLUT"); 11 | } 12 | } // namespace eigenpy 13 | -------------------------------------------------------------------------------- /development/scripts/pixi/activation.bat: -------------------------------------------------------------------------------- 1 | :: Set default build value only if not previously set 2 | if not defined EIGENPY_BUILD_TYPE (set EIGENPY_BUILD_TYPE=Release) 3 | if not defined EIGENPY_PYTHON_STUBS (set EIGENPY_PYTHON_STUBS=ON) 4 | if not defined EIGENPY_CHOLMOD_SUPPORT (set EIGENPY_CHOLMOD_SUPPORT=OFF) 5 | if not defined EIGENPY_ACCELERATE_SUPPORT (set EIGENPY_ACCELERATE_SUPPORT=OFF) 6 | -------------------------------------------------------------------------------- /src/decompositions/generalized-eigen-solver.cpp: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright 2025 INRIA 4 | */ 5 | 6 | #include "eigenpy/decompositions/GeneralizedEigenSolver.hpp" 7 | 8 | namespace eigenpy { 9 | void exposeGeneralizedEigenSolver() { 10 | using namespace Eigen; 11 | GeneralizedEigenSolverVisitor::expose("GeneralizedEigenSolver"); 12 | } 13 | } // namespace eigenpy 14 | -------------------------------------------------------------------------------- /src/decompositions/hessenberg-decomposition.cpp: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright 2025 INRIA 4 | */ 5 | 6 | #include "eigenpy/decompositions/HessenbergDecomposition.hpp" 7 | 8 | namespace eigenpy { 9 | void exposeHessenbergDecomposition() { 10 | using namespace Eigen; 11 | HessenbergDecompositionVisitor::expose("HessenbergDecomposition"); 12 | } 13 | } // namespace eigenpy 14 | -------------------------------------------------------------------------------- /src/decompositions/self-adjoint-eigen-solver.cpp: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright 2024 INRIA 4 | */ 5 | 6 | #include "eigenpy/decompositions/SelfAdjointEigenSolver.hpp" 7 | 8 | namespace eigenpy { 9 | void exposeSelfAdjointEigenSolver() { 10 | using namespace Eigen; 11 | SelfAdjointEigenSolverVisitor::expose("SelfAdjointEigenSolver"); 12 | } 13 | } // namespace eigenpy 14 | -------------------------------------------------------------------------------- /unittest/python/test_SelfAdjointEigenSolver.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | import eigenpy 4 | 5 | dim = 100 6 | rng = np.random.default_rng() 7 | 8 | A = rng.random((dim, dim)) 9 | A = (A + A.T) * 0.5 10 | 11 | es = eigenpy.SelfAdjointEigenSolver(A) 12 | 13 | V = es.eigenvectors() 14 | D = es.eigenvalues() 15 | 16 | assert eigenpy.is_approx(A.dot(V), V.dot(np.diag(D)), 1e-6) 17 | -------------------------------------------------------------------------------- /src/matrix-mac-long.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 INRIA 3 | */ 4 | 5 | #include "eigenpy/eigenpy.hpp" 6 | 7 | namespace eigenpy { 8 | void exposeMatrixMacLong() { 9 | // On Mac, long is a 64 bytes type but it's a different type than int64_t 10 | #ifdef __APPLE__ 11 | exposeType(); 12 | exposeType(); 13 | #endif // Mac 14 | } 15 | } // namespace eigenpy 16 | -------------------------------------------------------------------------------- /src/matrix-windows-long.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 INRIA 3 | */ 4 | 5 | #include "eigenpy/eigenpy.hpp" 6 | 7 | namespace eigenpy { 8 | void exposeMatrixWindowsLong() { 9 | // On Windows, long is a 32 bytes type but it's a different type than int 10 | #ifdef WIN32 11 | exposeType(); 12 | exposeType(); 13 | #endif // WIN32 14 | } 15 | } // namespace eigenpy 16 | -------------------------------------------------------------------------------- /unittest/python/test_EigenSolver.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | import eigenpy 4 | 5 | dim = 100 6 | rng = np.random.default_rng() 7 | A = rng.random((dim, dim)) 8 | 9 | es = eigenpy.EigenSolver(A) 10 | 11 | V = es.eigenvectors() 12 | D = es.eigenvalues() 13 | 14 | assert eigenpy.is_approx(A.dot(V).real, V.dot(np.diag(D)).real) 15 | assert eigenpy.is_approx(A.dot(V).imag, V.dot(np.diag(D)).imag) 16 | -------------------------------------------------------------------------------- /include/eigenpy/utils/is-aligned.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2020-2023 INRIA 3 | // 4 | 5 | #ifndef __eigenpy_utils_is_aligned_hpp__ 6 | #define __eigenpy_utils_is_aligned_hpp__ 7 | 8 | namespace eigenpy { 9 | inline bool is_aligned(const void* ptr, std::size_t alignment) { 10 | return (reinterpret_cast(ptr) & (alignment - 1)) == 0; 11 | } 12 | } // namespace eigenpy 13 | 14 | #endif 15 | -------------------------------------------------------------------------------- /src/matrix-mac-ulong.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 INRIA 3 | */ 4 | 5 | #include "eigenpy/eigenpy.hpp" 6 | 7 | namespace eigenpy { 8 | void exposeMatrixMacULong() { 9 | // On Mac, long is a 64 bytes type but it's a different type than int64_t 10 | #ifdef __APPLE__ 11 | exposeType(); 12 | exposeType(); 13 | #endif // Mac 14 | } 15 | } // namespace eigenpy 16 | -------------------------------------------------------------------------------- /include/eigenpy/solvers/solvers.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2025 CNRS INRIA 3 | */ 4 | 5 | #ifndef __eigenpy_solvers_solvers_hpp__ 6 | #define __eigenpy_solvers_solvers_hpp__ 7 | 8 | #include "eigenpy/config.hpp" 9 | 10 | namespace eigenpy { 11 | struct SolversScope {}; 12 | 13 | void EIGENPY_DLLAPI exposeSolvers(); 14 | 15 | } // namespace eigenpy 16 | 17 | #endif // define __eigenpy_solvers_solvers_hpp__ 18 | -------------------------------------------------------------------------------- /src/matrix-windows-ulong.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 INRIA 3 | */ 4 | 5 | #include "eigenpy/eigenpy.hpp" 6 | 7 | namespace eigenpy { 8 | void exposeMatrixWindowsULong() { 9 | // On Windows, long is a 32 bytes type but it's a different type than int 10 | #ifdef WIN32 11 | exposeType(); 12 | exposeType(); 13 | #endif // WIN32 14 | } 15 | } // namespace eigenpy 16 | -------------------------------------------------------------------------------- /src/solvers/incomplete-cholesky.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 INRIA 3 | */ 4 | 5 | #include "eigenpy/solvers/IncompleteCholesky.hpp" 6 | 7 | namespace eigenpy { 8 | void exposeIncompleteCholesky() { 9 | using namespace Eigen; 10 | typedef SparseMatrix ColMajorSparseMatrix; 11 | IncompleteCholeskyVisitor::expose("IncompleteCholesky"); 12 | } 13 | } // namespace eigenpy 14 | -------------------------------------------------------------------------------- /.git-blame-ignore-revs: -------------------------------------------------------------------------------- 1 | # pre-commit run -a (Guilhem Saurel, 2022-07-27) 2 | 4af05ec6781f9da65b81af8e3af8d69213f99e85 3 | 4 | # pre-commit run -a (Guilhem Saurel, 2024-02-17) 5 | 48fb48c83f0456de2fb612ef55df8ad789824d87 6 | 7 | # pre-commit run -a (Guilhem Saurel, 2024-02-19) 8 | 0bae435330ee475f8dbb11bf5e672284d294d9b3 9 | 10 | # pre-commit run -a (ManifoldFR, 2025-04-25) 11 | 51b49061575d46e0668eba0da200217cbfd9e883 12 | -------------------------------------------------------------------------------- /src/matrix-linux-long-long.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 INRIA 3 | */ 4 | 5 | #include "eigenpy/eigenpy.hpp" 6 | 7 | namespace eigenpy { 8 | void exposeMatrixLinuxLongLong() { 9 | // On Linux, long long is a 64 bytes type but it's a different type than int64_t 10 | #ifdef __linux__ 11 | exposeType(); 12 | exposeType(); 13 | #endif // linux 14 | } 15 | } // namespace eigenpy 16 | -------------------------------------------------------------------------------- /src/decompositions/simplicial-llt-solver.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 INRIA 3 | */ 4 | 5 | #include "eigenpy/decompositions/sparse/SimplicialLLT.hpp" 6 | 7 | namespace eigenpy { 8 | void exposeSimplicialLLTSolver() { 9 | using namespace Eigen; 10 | typedef SparseMatrix ColMajorSparseMatrix; 11 | SimplicialLLTVisitor::expose("SimplicialLLT"); 12 | } 13 | } // namespace eigenpy 14 | -------------------------------------------------------------------------------- /src/decompositions/simplicial-ldlt-solver.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 INRIA 3 | */ 4 | 5 | #include "eigenpy/decompositions/sparse/SimplicialLDLT.hpp" 6 | 7 | namespace eigenpy { 8 | void exposeSimplicialLDLTSolver() { 9 | using namespace Eigen; 10 | typedef SparseMatrix ColMajorSparseMatrix; 11 | SimplicialLDLTVisitor::expose("SimplicialLDLT"); 12 | } 13 | } // namespace eigenpy 14 | -------------------------------------------------------------------------------- /src/matrix-linux-ulong-long.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 INRIA 3 | */ 4 | 5 | #include "eigenpy/eigenpy.hpp" 6 | 7 | namespace eigenpy { 8 | void exposeMatrixLinuxULongLong() { 9 | // On Linux, long long is a 64 bytes type but it's a different type than int64_t 10 | #ifdef __linux__ 11 | exposeType(); 12 | exposeType(); 13 | #endif // linux 14 | } 15 | } // namespace eigenpy 16 | -------------------------------------------------------------------------------- /.github/workflows/check-changelog.yml: -------------------------------------------------------------------------------- 1 | name: CI - Check-changelog 2 | 3 | on: 4 | pull_request: 5 | types: [assigned, opened, synchronize, reopened, labeled, unlabeled, edited] 6 | branches: 7 | - devel 8 | jobs: 9 | check-changelog: 10 | name: Check changelog action 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: tarides/changelog-check-action@v3 14 | with: 15 | changelog: CHANGELOG.md 16 | -------------------------------------------------------------------------------- /include/eigenpy/solvers/preconditioners.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 CNRS 3 | * Copyright 2025 INRIA 4 | */ 5 | 6 | #ifndef __eigenpy_solvers_preconditioners_hpp__ 7 | #define __eigenpy_solvers_preconditioners_hpp__ 8 | 9 | #include "eigenpy/config.hpp" 10 | 11 | namespace eigenpy { 12 | 13 | void EIGENPY_DLLAPI exposePreconditioners(); 14 | 15 | } // namespace eigenpy 16 | 17 | #endif // define __eigenpy_solvers_preconditioners_hpp__ 18 | -------------------------------------------------------------------------------- /src/decompositions/generalized-self-adjoint-eigen-solver.cpp: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright 2025 INRIA 4 | */ 5 | 6 | #include "eigenpy/decompositions/GeneralizedSelfAdjointEigenSolver.hpp" 7 | 8 | namespace eigenpy { 9 | void exposeGeneralizedSelfAdjointEigenSolver() { 10 | using namespace Eigen; 11 | GeneralizedSelfAdjointEigenSolverVisitor::expose( 12 | "GeneralizedSelfAdjointEigenSolver"); 13 | } 14 | } // namespace eigenpy 15 | -------------------------------------------------------------------------------- /include/eigenpy/utils/empty-visitor.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __eigenpy_utils_empty_visitor_hpp__ 2 | #define __eigenpy_utils_empty_visitor_hpp__ 3 | 4 | #include 5 | 6 | namespace eigenpy { 7 | 8 | struct EmptyPythonVisitor 9 | : public ::boost::python::def_visitor { 10 | template 11 | void visit(classT&) const {} 12 | }; 13 | 14 | } // namespace eigenpy 15 | 16 | #endif // ifndef __eigenpy_utils_empty_visitor_hpp__ 17 | -------------------------------------------------------------------------------- /include/eigenpy/decompositions/minres.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 INRIA 3 | */ 4 | 5 | #ifndef __eigenpy_decompositions_minres_hpp__ 6 | #define __eigenpy_decompositions_minres_hpp__ 7 | 8 | #include "eigenpy/fwd.hpp" 9 | 10 | // clang-format off 11 | EIGENPY_PRAGMA_DEPRECATED_HEADER(eigenpy/decompositions/minres.hpp, eigenpy/solvers/MINRES.hpp) 12 | // clang-format on 13 | 14 | #include "eigenpy/solvers/MINRES.hpp" 15 | 16 | #endif // ifndef __eigenpy_decompositions_minres_hpp__ 17 | -------------------------------------------------------------------------------- /include/eigenpy/decompositions/QR.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 INRIA 3 | */ 4 | 5 | #ifndef __eigenpy_decompositions_qr_hpp__ 6 | #define __eigenpy_decompositions_qr_hpp__ 7 | 8 | #include "eigenpy/decompositions/HouseholderQR.hpp" 9 | #include "eigenpy/decompositions/FullPivHouseholderQR.hpp" 10 | #include "eigenpy/decompositions/ColPivHouseholderQR.hpp" 11 | #include "eigenpy/decompositions/CompleteOrthogonalDecomposition.hpp" 12 | 13 | #endif // ifndef __eigenpy_decompositions_qr_hpp__ 14 | -------------------------------------------------------------------------------- /include/eigenpy/geometry.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2019, CNRS 3 | * Copyright 2018-2020, INRIA 4 | */ 5 | 6 | #ifndef __eigenpy_geometry_hpp__ 7 | #define __eigenpy_geometry_hpp__ 8 | 9 | #include "eigenpy/config.hpp" 10 | 11 | namespace eigenpy { 12 | 13 | void EIGENPY_DLLAPI exposeQuaternion(); 14 | void EIGENPY_DLLAPI exposeAngleAxis(); 15 | 16 | void EIGENPY_DLLAPI exposeGeometryConversion(); 17 | 18 | } // namespace eigenpy 19 | 20 | #endif // define __eigenpy_geometry_hpp__ 21 | -------------------------------------------------------------------------------- /unittest/python/test_LLT.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | import eigenpy 4 | 5 | dim = 100 6 | rng = np.random.default_rng() 7 | 8 | A = rng.random((dim, dim)) 9 | A = (A + A.T) * 0.5 + np.diag(10.0 + rng.random(dim)) 10 | 11 | llt = eigenpy.LLT(A) 12 | 13 | L = llt.matrixL() 14 | assert eigenpy.is_approx(L.dot(np.transpose(L)), A) 15 | 16 | X = rng.random((dim, 20)) 17 | B = A.dot(X) 18 | X_est = llt.solve(B) 19 | assert eigenpy.is_approx(X, X_est) 20 | assert eigenpy.is_approx(A.dot(X_est), B) 21 | -------------------------------------------------------------------------------- /unittest/python/test_MINRES.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | import eigenpy 4 | 5 | dim = 100 6 | rng = np.random.default_rng() 7 | A = np.eye(dim) 8 | 9 | minres = eigenpy.solvers.MINRES(A) 10 | 11 | X = rng.random((dim, 20)) 12 | B = A.dot(X) 13 | X_est = minres.solve(B) 14 | assert eigenpy.is_approx(A.dot(X_est), B, 1e-6) 15 | 16 | minres_back = eigenpy.MINRES(A) 17 | 18 | X = rng.random((dim, 20)) 19 | B = A.dot(X) 20 | X_est = minres_back.solve(B) 21 | assert eigenpy.is_approx(A.dot(X_est), B, 1e-6) 22 | -------------------------------------------------------------------------------- /src/solvers/bicgstab.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 INRIA 3 | */ 4 | 5 | #include "eigenpy/solvers/BiCGSTAB.hpp" 6 | 7 | namespace eigenpy { 8 | void exposeBiCGSTAB() { 9 | using namespace Eigen; 10 | using Eigen::BiCGSTAB; 11 | using Eigen::IdentityPreconditioner; 12 | using IdentityBiCGSTAB = BiCGSTAB; 13 | 14 | BiCGSTABVisitor>::expose("BiCGSTAB"); 15 | BiCGSTABVisitor::expose("IdentityBiCGSTAB"); 16 | } 17 | } // namespace eigenpy 18 | -------------------------------------------------------------------------------- /src/std-vector.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022, CNRS 3 | * Copyright 2022, INRIA 4 | */ 5 | 6 | #include "eigenpy/std-vector.hpp" 7 | 8 | namespace eigenpy { 9 | 10 | void exposeStdVector() { 11 | exposeStdVectorEigenSpecificType("MatrixXd"); 12 | exposeStdVectorEigenSpecificType("VectorXd"); 13 | 14 | exposeStdVectorEigenSpecificType("MatrixXi"); 15 | exposeStdVectorEigenSpecificType("VectorXi"); 16 | } 17 | 18 | } // namespace eigenpy 19 | -------------------------------------------------------------------------------- /unittest/type_info.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 INRIA 3 | */ 4 | 5 | #include 6 | 7 | #include "eigenpy/eigenpy.hpp" 8 | #include "eigenpy/type_info.hpp" 9 | 10 | struct Dummy {}; 11 | 12 | BOOST_PYTHON_MODULE(type_info) { 13 | using namespace Eigen; 14 | namespace bp = boost::python; 15 | eigenpy::enableEigenPy(); 16 | 17 | eigenpy::expose_boost_type_info(); 18 | eigenpy::expose_boost_type_info(); 19 | 20 | bp::class_("Dummy").def(eigenpy::TypeInfoVisitor()); 21 | } 22 | -------------------------------------------------------------------------------- /unittest/python/test_std_map.py: -------------------------------------------------------------------------------- 1 | from std_map import X, copy, copy_boost, copy_X, std_map_to_dict 2 | 3 | t = {"one": 1.0, "two": 2.0} 4 | t2 = {"one": 1, "two": 2, "three": 3} 5 | 6 | assert std_map_to_dict(t) == t 7 | assert std_map_to_dict(copy(t)) == t 8 | m = copy_boost(t2) 9 | assert m.todict() == t2 10 | 11 | xmap_cpp = copy_X({"one": X(1), "two": X(2)}) 12 | print(xmap_cpp.todict()) 13 | x1 = xmap_cpp["one"] 14 | x1.val = 11 15 | print(xmap_cpp.todict()) 16 | assert xmap_cpp["one"].val == 11 17 | assert xmap_cpp["two"].val == 2 18 | -------------------------------------------------------------------------------- /include/eigenpy/decompositions/sparse/LLT.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 INRIA 3 | */ 4 | 5 | #ifndef __eigenpy_decompositions_sparse_llt_hpp__ 6 | #define __eigenpy_decompositions_sparse_llt_hpp__ 7 | 8 | #include "eigenpy/fwd.hpp" 9 | 10 | // clang-format off 11 | EIGENPY_PRAGMA_DEPRECATED_HEADER(eigenpy/decompositions/sparse/LLT.hpp, eigenpy/decompositions/sparse/SimplicialLLT.hpp) 12 | // clang-format on 13 | 14 | #include "eigenpy/decompositions/sparse/SimplicialLLT.hpp" 15 | 16 | #endif // ifndef __eigenpy_decompositions_sparse_llt_hpp__ 17 | -------------------------------------------------------------------------------- /include/eigenpy/decompositions/sparse/LDLT.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 INRIA 3 | */ 4 | 5 | #ifndef __eigenpy_decompositions_sparse_ldlt_hpp__ 6 | #define __eigenpy_decompositions_sparse_ldlt_hpp__ 7 | 8 | #include "eigenpy/fwd.hpp" 9 | 10 | // clang-format off 11 | EIGENPY_PRAGMA_DEPRECATED_HEADER(eigenpy/decompositions/sparse/LDLT.hpp, eigenpy/decompositions/sparse/SimplicialLDLT.hpp) 12 | // clang-format on 13 | 14 | #include "eigenpy/decompositions/sparse/SimplicialLDLT.hpp" 15 | 16 | #endif // ifndef __eigenpy_decompositions_sparse_ldlt_hpp__ 17 | -------------------------------------------------------------------------------- /include/eigenpy/utils/python-compat.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2024 INRIA 3 | // 4 | // 5 | 6 | #ifndef __eigenpy_utils_python_compat_hpp__ 7 | #define __eigenpy_utils_python_compat_hpp__ 8 | 9 | #if PY_MAJOR_VERSION >= 3 10 | 11 | #define PyInt_Check PyLong_Check 12 | 13 | #define PyStr_Check PyUnicode_Check 14 | #define PyStr_FromString PyUnicode_FromString 15 | 16 | #else 17 | 18 | #define PyStr_Check PyString_Check 19 | #define PyStr_FromString PyString_FromString 20 | 21 | #endif 22 | 23 | #endif // ifndef __eigenpy_utils_python_compat_hpp__ 24 | -------------------------------------------------------------------------------- /src/decompositions/qr-solvers.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 INRIA 3 | */ 4 | 5 | #include "eigenpy/decompositions/QR.hpp" 6 | 7 | namespace eigenpy { 8 | void exposeQRSolvers() { 9 | using namespace Eigen; 10 | HouseholderQRSolverVisitor::expose("HouseholderQR"); 11 | FullPivHouseholderQRSolverVisitor::expose("FullPivHouseholderQR"); 12 | ColPivHouseholderQRSolverVisitor::expose("ColPivHouseholderQR"); 13 | CompleteOrthogonalDecompositionSolverVisitor::expose( 14 | "CompleteOrthogonalDecomposition"); 15 | } 16 | } // namespace eigenpy 17 | -------------------------------------------------------------------------------- /.github/workflows/update-flake-lock.yml: -------------------------------------------------------------------------------- 1 | name: update-flake-lock 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: '0 15 3 * *' 7 | 8 | jobs: 9 | lockfile: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout repository 13 | uses: actions/checkout@v6 14 | - name: Install Nix 15 | uses: DeterminateSystems/nix-installer-action@main 16 | - name: Update flake.lock 17 | uses: DeterminateSystems/update-flake-lock@main 18 | with: 19 | pr-labels: "no changelog" 20 | token: ${{ secrets.GH_TOKEN_FOR_UPDATES }} 21 | -------------------------------------------------------------------------------- /unittest/python/test_LDLT.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | import eigenpy 4 | 5 | dim = 100 6 | rng = np.random.default_rng() 7 | 8 | A = rng.random((dim, dim)) 9 | A = (A + A.T) * 0.5 + np.diag(10.0 + rng.random(dim)) 10 | 11 | ldlt = eigenpy.LDLT(A) 12 | 13 | L = ldlt.matrixL() 14 | D = ldlt.vectorD() 15 | P = ldlt.transpositionsP() 16 | 17 | assert eigenpy.is_approx( 18 | np.transpose(P).dot(L.dot(np.diag(D).dot(np.transpose(L).dot(P)))), A 19 | ) 20 | 21 | X = rng.random((dim, 20)) 22 | B = A.dot(X) 23 | X_est = ldlt.solve(B) 24 | assert eigenpy.is_approx(X, X_est) 25 | assert eigenpy.is_approx(A.dot(X_est), B) 26 | -------------------------------------------------------------------------------- /include/eigenpy/decompositions/decompositions.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 INRIA 3 | */ 4 | 5 | #ifndef __eigenpy_decompositions_decompositions_hpp__ 6 | #define __eigenpy_decompositions_decompositions_hpp__ 7 | 8 | #include "eigenpy/config.hpp" 9 | 10 | namespace eigenpy { 11 | void EIGENPY_DLLAPI exposeDecompositions(); 12 | 13 | #ifdef EIGENPY_WITH_CHOLMOD_SUPPORT 14 | void EIGENPY_DLLAPI exposeCholmod(); 15 | #endif 16 | 17 | #ifdef EIGENPY_WITH_ACCELERATE_SUPPORT 18 | void EIGENPY_DLLAPI exposeAccelerate(); 19 | #endif 20 | 21 | } // namespace eigenpy 22 | 23 | #endif // define __eigenpy_decompositions_decompositions_hpp__ 24 | -------------------------------------------------------------------------------- /src/solvers/solvers.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2025 CNRS INRIA 3 | */ 4 | 5 | #include "eigenpy/solvers/solvers.hpp" 6 | 7 | #include "eigenpy/fwd.hpp" 8 | 9 | namespace eigenpy { 10 | 11 | void exposeBiCGSTAB(); 12 | void exposeMINRES(); 13 | void exposeConjugateGradient(); 14 | void exposeLeastSquaresConjugateGradient(); 15 | void exposeIncompleteCholesky(); 16 | void exposeIncompleteLUT(); 17 | 18 | void exposeSolvers() { 19 | exposeBiCGSTAB(); 20 | exposeMINRES(); 21 | exposeConjugateGradient(); 22 | exposeLeastSquaresConjugateGradient(); 23 | exposeIncompleteCholesky(); 24 | exposeIncompleteLUT(); 25 | } 26 | } // namespace eigenpy 27 | -------------------------------------------------------------------------------- /unittest/python/decompositions/sparse/cholmod/test_CholmodSimplicialLDLT.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from scipy.sparse import csc_matrix 3 | 4 | import eigenpy 5 | 6 | dim = 100 7 | rng = np.random.default_rng() 8 | A = rng.random((dim, dim)) 9 | A = (A + A.T) * 0.5 + np.diag(10.0 + rng.random(dim)) 10 | 11 | A = csc_matrix(A) 12 | 13 | llt = eigenpy.CholmodSimplicialLDLT(A) 14 | 15 | assert llt.info() == eigenpy.ComputationInfo.Success 16 | 17 | X = rng.random((dim, 20)) 18 | B = A.dot(X) 19 | X_est = llt.solve(B) 20 | assert eigenpy.is_approx(X, X_est) 21 | assert eigenpy.is_approx(A.dot(X_est), B) 22 | 23 | llt.analyzePattern(A) 24 | llt.factorize(A) 25 | -------------------------------------------------------------------------------- /unittest/python/decompositions/sparse/cholmod/test_CholmodSimplicialLLT.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from scipy.sparse import csc_matrix 3 | 4 | import eigenpy 5 | 6 | dim = 100 7 | rng = np.random.default_rng() 8 | 9 | A = rng.random((dim, dim)) 10 | A = (A + A.T) * 0.5 + np.diag(10.0 + rng.random(dim)) 11 | 12 | A = csc_matrix(A) 13 | 14 | llt = eigenpy.CholmodSimplicialLLT(A) 15 | 16 | assert llt.info() == eigenpy.ComputationInfo.Success 17 | 18 | X = rng.random((dim, 20)) 19 | B = A.dot(X) 20 | X_est = llt.solve(B) 21 | assert eigenpy.is_approx(X, X_est) 22 | assert eigenpy.is_approx(A.dot(X_est), B) 23 | 24 | llt.analyzePattern(A) 25 | llt.factorize(A) 26 | -------------------------------------------------------------------------------- /unittest/python/decompositions/sparse/cholmod/test_CholmodSupernodalLLT.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from scipy.sparse import csc_matrix 3 | 4 | import eigenpy 5 | 6 | dim = 100 7 | rng = np.random.default_rng() 8 | 9 | A = rng.random((dim, dim)) 10 | A = (A + A.T) * 0.5 + np.diag(10.0 + rng.random(dim)) 11 | 12 | A = csc_matrix(A) 13 | 14 | llt = eigenpy.CholmodSupernodalLLT(A) 15 | 16 | assert llt.info() == eigenpy.ComputationInfo.Success 17 | 18 | X = rng.random((dim, 20)) 19 | B = A.dot(X) 20 | X_est = llt.solve(B) 21 | assert eigenpy.is_approx(X, X_est) 22 | assert eigenpy.is_approx(A.dot(X_est), B) 23 | 24 | llt.analyzePattern(A) 25 | llt.factorize(A) 26 | -------------------------------------------------------------------------------- /include/eigenpy/expose.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 INRIA 3 | */ 4 | 5 | #ifndef __eigenpy_expose_hpp__ 6 | #define __eigenpy_expose_hpp__ 7 | 8 | #include "eigenpy/registration.hpp" 9 | 10 | namespace eigenpy { 11 | /// 12 | /// \brief Allows a template specialization. 13 | /// 14 | template 15 | struct call { 16 | static inline void expose() { T::expose(); } 17 | }; 18 | 19 | /// 20 | /// \brief Call the expose function of a given type T. 21 | /// 22 | template 23 | inline void expose() { 24 | if (!register_symbolic_link_to_registered_type()) call::expose(); 25 | } 26 | } // namespace eigenpy 27 | 28 | #endif // ifndef __eigenpy_expose_hpp__ 29 | -------------------------------------------------------------------------------- /src/solvers/conjugate-gradient.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 INRIA 3 | */ 4 | 5 | #include "eigenpy/solvers/ConjugateGradient.hpp" 6 | 7 | namespace eigenpy { 8 | void exposeConjugateGradient() { 9 | using namespace Eigen; 10 | using Eigen::ConjugateGradient; 11 | using Eigen::IdentityPreconditioner; 12 | using Eigen::Lower; 13 | using IdentityConjugateGradient = 14 | ConjugateGradient; 15 | 16 | ConjugateGradientVisitor>::expose( 17 | "ConjugateGradient"); 18 | ConjugateGradientVisitor::expose( 19 | "IdentityConjugateGradient"); 20 | } 21 | } // namespace eigenpy 22 | -------------------------------------------------------------------------------- /include/eigenpy/computation-info.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 INRIA 3 | */ 4 | 5 | #ifndef __eigenpy_decompositions_computation_info_hpp__ 6 | #define __eigenpy_decompositions_computation_info_hpp__ 7 | 8 | #include "eigenpy/fwd.hpp" 9 | 10 | namespace eigenpy { 11 | inline void exposeComputationInfo() { 12 | boost::python::enum_("ComputationInfo") 13 | .value("Success", Eigen::Success) 14 | .value("NumericalIssue", Eigen::NumericalIssue) 15 | .value("NoConvergence", Eigen::NoConvergence) 16 | .value("InvalidInput", Eigen::InvalidInput); 17 | } 18 | } // namespace eigenpy 19 | 20 | #endif // define __eigenpy_decompositions_computation_info_hpp__ 21 | -------------------------------------------------------------------------------- /docker/ubuntu/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:22.04 2 | 3 | RUN apt-get update -y && DEBIAN_FRONTEND=noninteractive apt-get install -qqy \ 4 | build-essential \ 5 | cmake \ 6 | cmake-curses-gui \ 7 | vim \ 8 | gdb \ 9 | git \ 10 | libboost-all-dev \ 11 | libeigen3-dev \ 12 | liburdfdom-dev \ 13 | python3-numpy 14 | 15 | WORKDIR /src 16 | RUN git clone --recursive -j2 -b devel https://github.com/stack-of-tasks/eigenpy 17 | 18 | ENV CTEST_OUTPUT_ON_FAILURE=ON 19 | ENV CTEST_PROGRESS_OUTPUT=ON 20 | ENV CTEST_PARALLEL_LEVEL=2 21 | 22 | WORKDIR /src/eigenpy/build 23 | RUN cmake -DPYTHON_EXECUTABLE=$(which python3) -DCMAKE_BUILD_TYPE=Debug -DBUILD_TESTING=ON .. 24 | RUN make -sj2 25 | -------------------------------------------------------------------------------- /unittest/python/test_tensor.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import tensor 3 | 4 | dim = np.array([10, 20, 30], dtype=np.int64) 5 | t = tensor.TensorContainer3(dim) 6 | r = t.get_ref() 7 | r[:] = 0.0 8 | c = t.get_copy() 9 | r2 = tensor.ref(r) 10 | cr = tensor.const_ref(r) 11 | c2 = tensor.copy(cr) 12 | 13 | assert np.all(c == r) 14 | assert np.all(r2 == r) 15 | assert np.all(cr == r) 16 | assert np.all(c2 == r) 17 | 18 | tensor.print_base(cr) 19 | tensor.print_ref(cr) 20 | tensor.print(cr) 21 | 22 | r2[:] = 100.0 23 | assert not np.all(c == r) 24 | assert not np.all(c2 == r) 25 | assert np.all(r2 == r) 26 | assert np.all(cr == r) 27 | 28 | tensor.print_base(cr) 29 | tensor.print_ref(cr) 30 | tensor.print(cr) 31 | -------------------------------------------------------------------------------- /include/eigenpy/swig.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2020 INRIA 3 | // 4 | 5 | #ifndef __eigenpy_swig_hpp__ 6 | #define __eigenpy_swig_hpp__ 7 | 8 | #include "eigenpy/fwd.hpp" 9 | 10 | namespace eigenpy { 11 | struct PySwigObject { 12 | PyObject_HEAD void* ptr; 13 | const char* desc; 14 | }; 15 | 16 | inline PySwigObject* get_PySwigObject(PyObject* pyObj) { 17 | if (!PyObject_HasAttrString(pyObj, "this")) return NULL; 18 | 19 | PyObject* this_ptr = PyObject_GetAttrString(pyObj, "this"); 20 | if (this_ptr == NULL) return NULL; 21 | PySwigObject* swig_obj = reinterpret_cast(this_ptr); 22 | 23 | return swig_obj; 24 | } 25 | } // namespace eigenpy 26 | 27 | #endif // ifndef __eigenpy_swig_hpp__ 28 | -------------------------------------------------------------------------------- /development/build.md: -------------------------------------------------------------------------------- 1 | # Build and install from source with Pixi 2 | 3 | To build **EigenPy** from source the easiest way is to use [Pixi](https://pixi.sh/latest/#installation). 4 | 5 | [Pixi](https://pixi.sh/latest/) is a cross-platform package management tool for developers that 6 | will install all required dependencies in `.pixi` directory. 7 | It's used by our CI agent so you have the guarantee to get the right dependencies. 8 | 9 | Run the following command to install dependencies, configure, build and test the project: 10 | 11 | ```bash 12 | pixi run test 13 | ``` 14 | 15 | The project will be built in the `build` directory. 16 | You can run `pixi shell` and build the project with `cmake` and `ninja` manually. 17 | 18 | -------------------------------------------------------------------------------- /unittest/multiple_registration.cpp: -------------------------------------------------------------------------------- 1 | #include "eigenpy/registration.hpp" 2 | #include 3 | 4 | namespace bp = boost::python; 5 | 6 | class X { 7 | public: 8 | X() {} 9 | void operator()() { printf("DOOT\n"); } 10 | }; 11 | 12 | class X_wrapper : public X, bp::wrapper { 13 | public: 14 | static void expose() { 15 | if (!eigenpy::register_symbolic_link_to_registered_type()) { 16 | bp::class_("X", bp::init<>()).def("__call__", &X::operator()); 17 | } 18 | } 19 | }; 20 | 21 | BOOST_PYTHON_MODULE(multiple_registration) { 22 | X_wrapper::expose(); 23 | X_wrapper::expose(); 24 | X_wrapper::expose(); 25 | X_wrapper::expose(); 26 | X_wrapper::expose(); 27 | X_wrapper::expose(); 28 | } 29 | -------------------------------------------------------------------------------- /unittest/python/test_ComplexEigenSolver.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | import eigenpy 4 | 5 | dim = 100 6 | rng = np.random.default_rng() 7 | A = rng.random((dim, dim)) 8 | 9 | es = eigenpy.ComplexEigenSolver(A) 10 | assert es.info() == eigenpy.ComputationInfo.Success 11 | 12 | V = es.eigenvectors() 13 | D = es.eigenvalues() 14 | assert V.shape == (dim, dim) 15 | assert D.shape == (dim,) 16 | 17 | AV = A @ V 18 | VD = V @ np.diag(D) 19 | assert eigenpy.is_approx(AV.real, VD.real) 20 | assert eigenpy.is_approx(AV.imag, VD.imag) 21 | 22 | ces5 = eigenpy.ComplexEigenSolver(A) 23 | ces6 = eigenpy.ComplexEigenSolver(A) 24 | id5 = ces5.id() 25 | id6 = ces6.id() 26 | assert id5 != id6 27 | assert id5 == ces5.id() 28 | assert id6 == ces6.id() 29 | -------------------------------------------------------------------------------- /unittest/python/test_complex.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from complex import ascomplex, imag, real 3 | 4 | rows = 10 5 | cols = 20 6 | rng = np.random.default_rng() 7 | 8 | 9 | def test(dtype): 10 | Z = np.zeros((rows, cols), dtype=dtype) 11 | Z.real = rng.random((rows, cols)) 12 | Z.imag = rng.random((rows, cols)) 13 | 14 | Z_real = real(Z) 15 | assert (Z_real == Z.real).all() 16 | Z_imag = imag(Z) 17 | assert (Z_imag == Z.imag).all() 18 | 19 | Y = np.ones((rows, cols)) 20 | Y_complex = ascomplex(Y) 21 | assert (Y_complex.real == Y).all() 22 | assert (Y_complex.imag == np.zeros((rows, cols))).all() 23 | 24 | 25 | # Float 26 | test(np.csingle) 27 | # Double 28 | test(np.cdouble) 29 | # Long Double 30 | test(np.clongdouble) 31 | -------------------------------------------------------------------------------- /benchmarks/bench-switch.py: -------------------------------------------------------------------------------- 1 | import time # noqa 2 | import timeit # noqa 3 | 4 | import numpy as np 5 | from IPython import get_ipython 6 | 7 | import eigenpy 8 | 9 | ipython = get_ipython() 10 | 11 | quat = eigenpy.Quaternion() 12 | a = [0.0, 0.0, 0.0] 13 | 14 | cmd1 = "timeit np.array(a)" 15 | print("\n") 16 | print(cmd1) 17 | ipython.magic(cmd1) 18 | print("\n") 19 | 20 | cmd2 = "timeit np.matrix(a)" 21 | print(cmd2) 22 | ipython.magic(cmd2) 23 | print("\n") 24 | 25 | cmd4 = "timeit quat.coeffs()" 26 | print(cmd4) 27 | ipython.magic(cmd4) 28 | print("\n") 29 | 30 | cmd5 = "timeit np.asmatrix(quat.coeffs())" 31 | print(cmd5) 32 | ipython.magic(cmd5) 33 | print("\n") 34 | 35 | a_matrix = np.matrix(a) 36 | cmd6 = "timeit np.asarray(a_matrix)" 37 | print(cmd6) 38 | ipython.magic(cmd6) 39 | print("\n") 40 | -------------------------------------------------------------------------------- /unittest/user_struct.cpp: -------------------------------------------------------------------------------- 1 | #include "eigenpy/eigenpy.hpp" 2 | 3 | struct mystruct { 4 | Eigen::Vector3d x_; 5 | Eigen::Vector4d y_; 6 | 7 | mystruct(const Eigen::Vector3d& x, const Eigen::Vector4d& y) : x_(x), y_(y) {} 8 | }; 9 | 10 | BOOST_PYTHON_MODULE(user_struct) { 11 | using namespace Eigen; 12 | namespace bp = boost::python; 13 | eigenpy::enableEigenPy(); 14 | bp::class_("MyStruct", bp::init()) 15 | .add_property( 16 | "x", 17 | bp::make_getter(&mystruct::x_, bp::return_internal_reference<>()), 18 | bp::make_setter(&mystruct::x_)) 19 | .add_property( 20 | "y", 21 | bp::make_getter(&mystruct::y_, bp::return_internal_reference<>()), 22 | bp::make_setter(&mystruct::y_)); 23 | } 24 | -------------------------------------------------------------------------------- /src/decompositions/sparse-qr-solver.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 INRIA 3 | */ 4 | 5 | #include "eigenpy/decompositions/sparse/SparseQR.hpp" 6 | 7 | namespace eigenpy { 8 | void exposeSparseQRSolver() { 9 | using namespace Eigen; 10 | 11 | typedef SparseMatrix ColMajorSparseMatrix; 12 | typedef typename ColMajorSparseMatrix::StorageIndex StorageIndex; 13 | typedef COLAMDOrdering Ordering; 14 | typedef SparseQR SparseQRType; 15 | 16 | SparseQRMatrixQTransposeReturnTypeVisitor::expose( 17 | "SparseQRMatrixQTransposeReturnType"); 18 | SparseQRMatrixQReturnTypeVisitor::expose( 19 | "SparseQRMatrixQReturnType"); 20 | SparseQRVisitor::expose("SparseQR"); 21 | } 22 | } // namespace eigenpy 23 | -------------------------------------------------------------------------------- /include/eigenpy/memory.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2019, CNRS 3 | * Copyright 2018-2023, INRIA 4 | */ 5 | 6 | #include "eigenpy/fwd.hpp" 7 | 8 | EIGENPY_DEPRECATED_FILE( 9 | "This header file is now useless and should not be included anymore.") 10 | 11 | #ifndef __eigenpy_memory_hpp__ 12 | #define __eigenpy_memory_hpp__ 13 | 14 | /** 15 | * This section contains a convenience MACRO which allows an easy specialization 16 | * of Boost Python Object allocator for struct data types containing Eigen 17 | * objects and requiring strict alignment. 18 | */ 19 | #define EIGENPY_DEFINE_STRUCT_ALLOCATOR_SPECIALIZATION(...) \ 20 | EIGENPY_DEPRECATED_MACRO(EIGENPY_DEFINE_STRUCT_ALLOCATOR_SPECIALIZATION(), \ 21 | "it is no more needed.") 22 | 23 | #endif // __eigenpy_memory_hpp__ 24 | -------------------------------------------------------------------------------- /unittest/python/decompositions/sparse/test_SimplicialLDLT.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from scipy.sparse import csc_matrix 3 | 4 | import eigenpy 5 | 6 | dim = 100 7 | rng = np.random.default_rng() 8 | 9 | A = rng.random((dim, dim)) 10 | A = (A + A.T) * 0.5 + np.diag(10.0 + rng.random(dim)) 11 | 12 | A = csc_matrix(A) 13 | 14 | ldlt = eigenpy.SimplicialLDLT(A) 15 | 16 | assert ldlt.info() == eigenpy.ComputationInfo.Success 17 | 18 | L = ldlt.matrixL() 19 | U = ldlt.matrixU() 20 | D = csc_matrix(np.diag(ldlt.vectorD())) 21 | 22 | LDU = L @ D @ U 23 | assert eigenpy.is_approx(LDU.toarray(), A.toarray()) 24 | 25 | X = rng.random((dim, 20)) 26 | B = A.dot(X) 27 | X_est = ldlt.solve(B) 28 | assert eigenpy.is_approx(X, X_est) 29 | assert eigenpy.is_approx(A.dot(X_est), B) 30 | 31 | ldlt.analyzePattern(A) 32 | ldlt.factorize(A) 33 | permutation = ldlt.permutationP() 34 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | ci: 2 | autoupdate_branch: devel 3 | autofix_prs: false 4 | autoupdate_schedule: quarterly 5 | repos: 6 | - repo: https://github.com/astral-sh/ruff-pre-commit 7 | rev: v0.13.3 8 | hooks: 9 | - id: ruff 10 | args: 11 | - --fix 12 | - --exit-non-zero-on-fix 13 | - --ignore 14 | - UP036 15 | - id: ruff-format 16 | - repo: https://github.com/cheshirekow/cmake-format-precommit 17 | rev: v0.6.13 18 | hooks: 19 | - id: cmake-format 20 | - repo: https://github.com/pappasam/toml-sort 21 | rev: v0.24.3 22 | hooks: 23 | - id: toml-sort-fix 24 | exclude: pixi.toml|dockgen.toml 25 | - repo: https://github.com/pre-commit/mirrors-clang-format 26 | rev: v21.1.2 27 | hooks: 28 | - id: clang-format 29 | - repo: https://github.com/pre-commit/pre-commit-hooks 30 | rev: v6.0.0 31 | hooks: 32 | - id: trailing-whitespace 33 | -------------------------------------------------------------------------------- /src/solvers/preconditioners.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 CNRS 3 | */ 4 | 5 | #include 6 | 7 | #if EIGEN_VERSION_AT_LEAST(3, 2, 0) 8 | #include "eigenpy/solvers/BasicPreconditioners.hpp" 9 | #include "eigenpy/solvers/preconditioners.hpp" 10 | // #include "eigenpy/solvers/BFGSPreconditioners.hpp" 11 | 12 | namespace eigenpy { 13 | 14 | void exposePreconditioners() { 15 | using namespace Eigen; 16 | 17 | DiagonalPreconditionerVisitor::expose(); 18 | #if EIGEN_VERSION_AT_LEAST(3, 3, 5) 19 | LeastSquareDiagonalPreconditionerVisitor::expose(); 20 | #endif 21 | IdentityPreconditionerVisitor::expose(); 22 | // LimitedBFGSPreconditionerBaseVisitor< 23 | // LimitedBFGSPreconditioner 24 | // >::expose("LimitedBFGSPreconditioner"); 25 | } 26 | 27 | } // namespace eigenpy 28 | 29 | #endif 30 | -------------------------------------------------------------------------------- /include/eigenpy/eigen/EigenBase.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 INRIA 3 | */ 4 | 5 | #ifndef __eigenpy_eigen_eigen_base_hpp__ 6 | #define __eigenpy_eigen_eigen_base_hpp__ 7 | 8 | #include "eigenpy/eigenpy.hpp" 9 | 10 | namespace eigenpy { 11 | 12 | template 13 | struct EigenBaseVisitor 14 | : public boost::python::def_visitor> { 15 | template 16 | void visit(PyClass& cl) const { 17 | cl.def("cols", &Derived::cols, bp::arg("self"), 18 | "Returns the number of columns.") 19 | .def("rows", &Derived::rows, bp::arg("self"), 20 | "Returns the number of rows.") 21 | .def("size", &Derived::rows, bp::arg("self"), 22 | "Returns the number of coefficients, which is rows()*cols()."); 23 | } 24 | }; 25 | 26 | } // namespace eigenpy 27 | 28 | #endif // ifndef __eigenpy_eigen_eigen_base_hpp__ 29 | -------------------------------------------------------------------------------- /include/eigenpy/id.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2024 INRIA 3 | // 4 | 5 | #ifndef __eigenpy_id_hpp__ 6 | #define __eigenpy_id_hpp__ 7 | 8 | #include 9 | #include 10 | 11 | namespace eigenpy { 12 | 13 | /// 14 | /// \brief Add the Python method id to retrieving a unique id for a given object 15 | /// exposed with Boost.Python 16 | /// 17 | template 18 | struct IdVisitor : public bp::def_visitor> { 19 | template 20 | void visit(PyClass& cl) const { 21 | cl.def("id", &id, bp::arg("self"), 22 | "Returns the unique identity of an object.\n" 23 | "For object held in C++, it corresponds to its memory address."); 24 | } 25 | 26 | private: 27 | static boost::int64_t id(const C& self) { 28 | return boost::int64_t(reinterpret_cast(&self)); 29 | } 30 | }; 31 | } // namespace eigenpy 32 | 33 | #endif // ifndef __eigenpy_id_hpp__ 34 | -------------------------------------------------------------------------------- /include/eigenpy/solvers/SparseSolverBase.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 CNRS 3 | * Copyright 2025 INRIA 4 | */ 5 | 6 | #ifndef __eigenpy_solvers_sparse_solver_base_hpp__ 7 | #define __eigenpy_solvers_sparse_solver_base_hpp__ 8 | 9 | #include "eigenpy/fwd.hpp" 10 | 11 | namespace eigenpy { 12 | 13 | template 14 | struct SparseSolverVisitor 15 | : public bp::def_visitor> { 16 | typedef Eigen::VectorXd VectorType; 17 | 18 | template 19 | void visit(PyClass& cl) const { 20 | cl.def("solve", &solve, bp::arg("b"), 21 | "Returns the solution x of Ax = b using the current decomposition " 22 | "of A."); 23 | } 24 | 25 | private: 26 | static VectorType solve(SparseSolver& self, const VectorType& b) { 27 | return self.solve(b); 28 | } 29 | }; 30 | 31 | } // namespace eigenpy 32 | 33 | #endif // ifndef __eigenpy_solvers_sparse_solver_base_hpp__ 34 | -------------------------------------------------------------------------------- /include/eigenpy/utils/scalar-name.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 INRIA 3 | */ 4 | 5 | #ifndef __eigenpy_utils_scalar_name_hpp__ 6 | #define __eigenpy_utils_scalar_name_hpp__ 7 | 8 | #include 9 | #include 10 | 11 | namespace eigenpy { 12 | template 13 | struct scalar_name { 14 | static std::string shortname(); 15 | }; 16 | 17 | template <> 18 | struct scalar_name { 19 | static std::string shortname() { return "f"; }; 20 | }; 21 | 22 | template <> 23 | struct scalar_name { 24 | static std::string shortname() { return "d"; }; 25 | }; 26 | 27 | template <> 28 | struct scalar_name { 29 | static std::string shortname() { return "ld"; }; 30 | }; 31 | 32 | template 33 | struct scalar_name> { 34 | static std::string shortname() { return "c" + scalar_name(); }; 35 | }; 36 | } // namespace eigenpy 37 | 38 | #endif // ifndef __eigenpy_utils_scalar_name_hpp__ 39 | -------------------------------------------------------------------------------- /unittest/geometry.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2019, CNRS 3 | * Copyright 2018-2023, INRIA 4 | */ 5 | 6 | #include "eigenpy/eigenpy.hpp" 7 | #include "eigenpy/geometry.hpp" 8 | 9 | namespace bp = boost::python; 10 | 11 | Eigen::AngleAxisd testOutAngleAxis() { 12 | return Eigen::AngleAxisd(.1, Eigen::Vector3d::UnitZ()); 13 | } 14 | 15 | double testInAngleAxis(Eigen::AngleAxisd aa) { return aa.angle(); } 16 | 17 | Eigen::Quaterniond testOutQuaternion() { 18 | Eigen::Quaterniond res(1, 2, 3, 4); 19 | return res; 20 | } 21 | double testInQuaternion(Eigen::Quaterniond q) { return q.norm(); } 22 | 23 | BOOST_PYTHON_MODULE(geometry) { 24 | eigenpy::enableEigenPy(); 25 | 26 | eigenpy::exposeAngleAxis(); 27 | eigenpy::exposeQuaternion(); 28 | 29 | bp::def("testOutAngleAxis", &testOutAngleAxis); 30 | bp::def("testInAngleAxis", &testInAngleAxis); 31 | 32 | bp::def("testOutQuaternion", &testOutQuaternion); 33 | bp::def("testInQuaternion", &testInQuaternion); 34 | } 35 | -------------------------------------------------------------------------------- /doc/Doxyfile.extra.in: -------------------------------------------------------------------------------- 1 | INPUT = @PROJECT_SOURCE_DIR@/doc \ 2 | @PROJECT_SOURCE_DIR@/include \ 3 | @PROJECT_SOURCE_DIR@/README.md 4 | 5 | RECURSIVE = YES 6 | 7 | FILE_PATTERNS = *.cpp *.h *.hpp *.hxx 8 | 9 | EXTRACT_ALL = NO 10 | EXTRACT_ANON_NSPACES = NO 11 | 12 | ENABLE_PREPROCESSING = YES 13 | MACRO_EXPANSION = YES 14 | 15 | FULL_PATH_NAMES = YES 16 | EXCLUDE_SYMBOLS = std, *::internal, internal::*, *::internal::* 17 | 18 | INCLUDE_PATH = @PROJECT_SOURCE_DIR@/include 19 | 20 | GENERATE_TREEVIEW = YES 21 | 22 | VERBATIM_HEADERS = YES 23 | 24 | SHOW_FILES = YES 25 | SHOW_NAMESPACES = YES 26 | 27 | SOURCE_BROWSER = YES 28 | 29 | ALPHABETICAL_INDEX = YES 30 | 31 | USE_MDFILE_AS_MAINPAGE = README.md 32 | BUILTIN_STL_SUPPORT = YES 33 | HAVE_DOT = YES 34 | DOT_IMAGE_FORMAT = SVG 35 | -------------------------------------------------------------------------------- /src/version.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019-2023 INRIA 3 | // 4 | 5 | #include "eigenpy/version.hpp" 6 | #include "eigenpy/config.hpp" 7 | 8 | #include 9 | #include 10 | 11 | namespace eigenpy { 12 | 13 | std::string printVersion(const std::string& delimiter) { 14 | std::ostringstream oss; 15 | oss << EIGENPY_MAJOR_VERSION << delimiter << EIGENPY_MINOR_VERSION 16 | << delimiter << EIGENPY_PATCH_VERSION; 17 | return oss.str(); 18 | } 19 | 20 | std::string printEigenVersion(const std::string& delimiter) { 21 | std::ostringstream oss; 22 | oss << EIGEN_MAJOR_VERSION << delimiter << EIGEN_MINOR_VERSION << delimiter 23 | << EIGEN_MINOR_VERSION; 24 | return oss.str(); 25 | } 26 | 27 | bool checkVersionAtLeast(unsigned int major_version, unsigned int minor_version, 28 | unsigned int patch_version) { 29 | return EIGENPY_VERSION_AT_LEAST(major_version, minor_version, patch_version); 30 | } 31 | 32 | } // namespace eigenpy 33 | -------------------------------------------------------------------------------- /unittest/python/test_sparse_matrix.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import sparse_matrix 3 | from scipy.sparse import csr_matrix 4 | 5 | m = sparse_matrix.emptyMatrix() 6 | assert m.shape == (0, 0) 7 | 8 | v = sparse_matrix.emptyVector() 9 | assert v.shape == (0, 0) 10 | 11 | m = sparse_matrix.matrix1x1(2) 12 | assert m.toarray() == np.array([2]) 13 | 14 | v = sparse_matrix.vector1x1(2) 15 | assert v.toarray() == np.array([2]) 16 | 17 | rng = np.random.default_rng() 18 | diag_values = rng.random(10) 19 | diag_mat = sparse_matrix.diagonal(diag_values) 20 | assert (diag_mat.toarray() == np.diag(diag_values)).all() 21 | 22 | diag_mat_copy = sparse_matrix.copy(diag_mat) 23 | assert (diag_mat_copy != diag_mat).nnz == 0 24 | 25 | diag_mat_csr = csr_matrix(diag_mat) 26 | assert (sparse_matrix.copy(diag_mat_csr) != diag_mat_csr).nnz == 0 27 | 28 | # test zero matrix 29 | zero_mat = csr_matrix(np.zeros((10, 1))) 30 | zero_mat_copy = sparse_matrix.copy(zero_mat) 31 | assert (zero_mat_copy != zero_mat).nnz == 0 32 | -------------------------------------------------------------------------------- /unittest/python/test_RealQZ.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | import eigenpy 4 | 5 | dim = 100 6 | rng = np.random.default_rng() 7 | A = rng.random((dim, dim)) 8 | B = rng.random((dim, dim)) 9 | 10 | realqz = eigenpy.RealQZ(A, B) 11 | assert realqz.info() == eigenpy.ComputationInfo.Success 12 | 13 | Q = realqz.matrixQ() 14 | S = realqz.matrixS() 15 | Z = realqz.matrixZ() 16 | T = realqz.matrixT() 17 | 18 | assert eigenpy.is_approx(A, Q @ S @ Z) 19 | assert eigenpy.is_approx(B, Q @ T @ Z) 20 | 21 | assert eigenpy.is_approx(Q @ Q.T, np.eye(dim)) 22 | assert eigenpy.is_approx(Z @ Z.T, np.eye(dim)) 23 | 24 | for i in range(dim): 25 | for j in range(i): 26 | assert abs(T[i, j]) < 1e-12 27 | 28 | for i in range(dim): 29 | for j in range(i - 1): 30 | assert abs(S[i, j]) < 1e-12 31 | 32 | realqz3_id = eigenpy.RealQZ(A, B) 33 | realqz4_id = eigenpy.RealQZ(A, B) 34 | id3 = realqz3_id.id() 35 | id4 = realqz4_id.id() 36 | assert id3 != id4 37 | assert id3 == realqz3_id.id() 38 | assert id4 == realqz4_id.id() 39 | -------------------------------------------------------------------------------- /development/release.md: -------------------------------------------------------------------------------- 1 | # Release with Pixi 2 | 3 | To create a release with Pixi run the following commands on the **devel** branch: 4 | 5 | ```bash 6 | EIGENPY_VERSION=X.Y.Z pixi run release-new-version 7 | git push origin 8 | git push origin vX.Y.Z 9 | ``` 10 | 11 | Where `X.Y.Z` is the new version. 12 | Be careful to follow the [Semantic Versioning](https://semver.org/spec/v2.0.0.html) rules. 13 | 14 | You will find the following assets: 15 | - `./build_new_version/eigenpy-X.Y.Z.tar.gz` 16 | - `./build_new_version/eigenpy-X.Y.Z.tar.gz.sig` 17 | 18 | Then, create a new release on [GitHub](https://github.com/stack-of-tasks/eigenpy/releases/new) with: 19 | 20 | * Tag: vX.Y.Z 21 | * Title: EigenPy X.Y.Z 22 | * Body: 23 | ``` 24 | ## What's Changed 25 | 26 | CHANGELOG CONTENT 27 | 28 | **Full Changelog**: https://github.com/stack-of-tasks/eigenpy/compare/vXX.YY.ZZ...vX.Y.Z 29 | ``` 30 | 31 | Where `XX.YY.ZZ` is the last release version. 32 | 33 | Then upload `eigenpy-X.Y.Z.tar.gz` and `eigenpy-X.Y.Z.tar.gz.sig` and publish the release. 34 | -------------------------------------------------------------------------------- /src/exception.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2019, CNRS 3 | * Copyright 2018-2019, INRIA 4 | */ 5 | 6 | #include "eigenpy/exception.hpp" 7 | 8 | #include 9 | 10 | #include "eigenpy/registration.hpp" 11 | 12 | namespace eigenpy { 13 | PyObject* Exception::pyType; 14 | 15 | void Exception::translateException(Exception const& e) { 16 | assert(NULL != pyType); 17 | // Return an exception object of type pyType and value object(e). 18 | PyErr_SetString(PyExc_RuntimeError, e.what()); 19 | } 20 | 21 | void Exception::registerException() { 22 | if (check_registration()) return; 23 | 24 | pyType = boost::python::class_( 25 | "Exception", boost::python::init()) 26 | .add_property("message", &eigenpy::Exception::copyMessage) 27 | .ptr(); 28 | 29 | boost::python::register_exception_translator( 30 | &eigenpy::Exception::translateException); 31 | } 32 | 33 | } // namespace eigenpy 34 | -------------------------------------------------------------------------------- /unittest/std_pair.cpp: -------------------------------------------------------------------------------- 1 | /// @file 2 | /// @copyright Copyright 2023 CNRS INRIA 3 | 4 | #include 5 | #include 6 | 7 | namespace bp = boost::python; 8 | 9 | template 10 | bp::tuple std_pair_to_tuple(const std::pair& pair) { 11 | return bp::make_tuple(pair.first, pair.second); 12 | } 13 | 14 | template 15 | std::pair copy(const std::pair& pair) { 16 | return pair; 17 | } 18 | 19 | template 20 | const std::pair& passthrough(const std::pair& pair) { 21 | return pair; 22 | } 23 | 24 | BOOST_PYTHON_MODULE(std_pair) { 25 | eigenpy::enableEigenPy(); 26 | 27 | typedef std::pair PairType; 28 | eigenpy::StdPairConverter::registration(); 29 | 30 | bp::def("std_pair_to_tuple", std_pair_to_tuple); 31 | bp::def("copy", copy); 32 | bp::def("passthrough", passthrough, 33 | bp::return_value_policy()); 34 | } 35 | -------------------------------------------------------------------------------- /include/eigenpy/decompositions/sparse/cholmod/CholmodDecomposition.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 INRIA 3 | */ 4 | 5 | #ifndef __eigenpy_decomposition_sparse_cholmod_cholmod_decomposition_hpp__ 6 | #define __eigenpy_decomposition_sparse_cholmod_cholmod_decomposition_hpp__ 7 | 8 | #include "eigenpy/eigenpy.hpp" 9 | #include "eigenpy/decompositions/sparse/cholmod/CholmodBase.hpp" 10 | 11 | namespace eigenpy { 12 | 13 | template 14 | struct CholmodDecompositionVisitor 15 | : public boost::python::def_visitor< 16 | CholmodDecompositionVisitor> { 17 | typedef CholdmodDerived Solver; 18 | 19 | template 20 | void visit(PyClass& cl) const { 21 | cl 22 | 23 | .def(CholmodBaseVisitor()) 24 | .def("setMode", &Solver::setMode, bp::args("self", "mode"), 25 | "Set the mode for the Cholesky decomposition."); 26 | } 27 | }; 28 | 29 | } // namespace eigenpy 30 | 31 | #endif // ifndef 32 | // __eigenpy_decomposition_sparse_cholmod_cholmod_decomposition_hpp__ 33 | -------------------------------------------------------------------------------- /package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | eigenpy 4 | 3.12.0 5 | Bindings between Numpy and Eigen using Boost.Python 6 | Justin Carpentier 7 | Wolfgang Merkt 8 | Guilhem Saurel 9 | Justin Carpentier 10 | Nicolas Mansard 11 | BSD-2-Clause 12 | 13 | https://github.com/stack-of-tasks/eigenpy 14 | 15 | git 16 | doxygen 17 | jrl_cmakemodules 18 | 19 | python3 20 | python3-numpy 21 | python3-scipy 22 | eigen 23 | boost 24 | 25 | cmake 26 | 27 | cmake 28 | 29 | 30 | -------------------------------------------------------------------------------- /.github/workflows/nix.yml: -------------------------------------------------------------------------------- 1 | name: "CI - Nix" 2 | 3 | on: 4 | push: 5 | branches: 6 | - devel 7 | pull_request: 8 | concurrency: 9 | group: ${{ github.workflow }}-${{ github.ref }} 10 | cancel-in-progress: true 11 | 12 | jobs: 13 | nix: 14 | runs-on: "${{ matrix.os }}-latest" 15 | strategy: 16 | fail-fast: false 17 | matrix: 18 | os: [ubuntu, macos] 19 | eigen: [3, 5] 20 | steps: 21 | - uses: actions/checkout@v6 22 | - uses: cachix/install-nix-action@v31 23 | - uses: cachix/cachix-action@v16 24 | with: 25 | name: gepetto 26 | authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' 27 | - run: nix build -L ".#eigenpy-eigen${{ matrix.eigen }}" 28 | 29 | check: 30 | if: always() 31 | name: check-macos-linux-nix 32 | 33 | needs: 34 | - nix 35 | 36 | runs-on: Ubuntu-latest 37 | 38 | steps: 39 | - name: Decide whether the needed jobs succeeded or failed 40 | uses: re-actors/alls-green@release/v1 41 | with: 42 | jobs: ${{ toJSON(needs) }} 43 | -------------------------------------------------------------------------------- /include/eigenpy/copyable.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016-2023 CNRS INRIA 3 | // Copyright (c) 2023 Heriot-Watt University 4 | // 5 | 6 | #ifndef __eigenpy_utils_copyable_hpp__ 7 | #define __eigenpy_utils_copyable_hpp__ 8 | 9 | #include 10 | 11 | namespace eigenpy { 12 | 13 | /// 14 | /// \brief Add the Python method copy to allow a copy of this by calling the 15 | /// copy constructor. 16 | /// 17 | template 18 | struct CopyableVisitor : public bp::def_visitor> { 19 | template 20 | void visit(PyClass& cl) const { 21 | cl.def("copy", ©, bp::arg("self"), "Returns a copy of *this."); 22 | cl.def("__copy__", ©, bp::arg("self"), "Returns a copy of *this."); 23 | cl.def("__deepcopy__", &deepcopy, bp::args("self", "memo"), 24 | "Returns a deep copy of *this."); 25 | } 26 | 27 | private: 28 | static C copy(const C& self) { return C(self); } 29 | static C deepcopy(const C& self, bp::dict) { return C(self); } 30 | }; 31 | } // namespace eigenpy 32 | 33 | #endif // ifndef __eigenpy_utils_copyable_hpp__ 34 | -------------------------------------------------------------------------------- /unittest/python/decompositions/sparse/test_Accelerate.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from scipy.sparse import csc_matrix 3 | 4 | import eigenpy 5 | 6 | rng = np.random.default_rng() 7 | 8 | 9 | def test(SolverType: type): 10 | dim = 100 11 | A = rng.random((dim, dim)) 12 | A = (A + A.T) * 0.5 + np.diag(10.0 + rng.random(dim)) 13 | 14 | A = csc_matrix(A) 15 | 16 | llt = SolverType(A) 17 | 18 | assert llt.info() == eigenpy.ComputationInfo.Success 19 | 20 | X = rng.random((dim, 20)) 21 | B = A.dot(X) 22 | X_est = llt.solve(B) 23 | # import pdb; pdb.set_trace() 24 | assert eigenpy.is_approx(X, X_est) 25 | assert eigenpy.is_approx(A.dot(X_est), B) 26 | 27 | llt.analyzePattern(A) 28 | llt.factorize(A) 29 | 30 | 31 | test(eigenpy.AccelerateLLT) 32 | test(eigenpy.AccelerateLDLT) 33 | test(eigenpy.AccelerateLDLTUnpivoted) 34 | test(eigenpy.AccelerateLDLTSBK) 35 | test(eigenpy.AccelerateLDLTTPP) 36 | test(eigenpy.AccelerateQR) 37 | # test(eigenpy.AccelerateCholeskyAtA) # This test is not passing. Seems there is a bug in Eigen with the support of Accelerate. 38 | -------------------------------------------------------------------------------- /src/decompositions/jacobisvd-solver.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 INRIA 3 | */ 4 | 5 | #include "eigenpy/decompositions/JacobiSVD.hpp" 6 | 7 | namespace eigenpy { 8 | void exposeJacobiSVDSolver() { 9 | using namespace Eigen; 10 | using Eigen::JacobiSVD; 11 | 12 | using Eigen::ColPivHouseholderQRPreconditioner; 13 | using Eigen::FullPivHouseholderQRPreconditioner; 14 | using Eigen::HouseholderQRPreconditioner; 15 | using Eigen::NoQRPreconditioner; 16 | 17 | using ColPivHhJacobiSVD = 18 | JacobiSVD; 19 | using FullPivHhJacobiSVD = 20 | JacobiSVD; 21 | using HhJacobiSVD = JacobiSVD; 22 | using NoPrecondJacobiSVD = JacobiSVD; 23 | 24 | JacobiSVDVisitor::expose("ColPivHhJacobiSVD"); 25 | JacobiSVDVisitor::expose("FullPivHhJacobiSVD"); 26 | JacobiSVDVisitor::expose("HhJacobiSVD"); 27 | JacobiSVDVisitor::expose("NoPrecondJacobiSVD"); 28 | } 29 | } // namespace eigenpy 30 | -------------------------------------------------------------------------------- /unittest/deprecation_policy.cpp: -------------------------------------------------------------------------------- 1 | #include "eigenpy/eigenpy.hpp" 2 | #include "eigenpy/deprecation-policy.hpp" 3 | 4 | #include 5 | 6 | namespace bp = boost::python; 7 | using eigenpy::DeprecationType; 8 | 9 | void some_deprecated_function() { 10 | std::cout << "Calling this should produce a warning" << std::endl; 11 | } 12 | 13 | void some_future_deprecated_function() { 14 | std::cout 15 | << "Calling this should produce a warning about a future deprecation" 16 | << std::endl; 17 | } 18 | 19 | class X { 20 | public: 21 | void deprecated_member_function() {} 22 | }; 23 | 24 | BOOST_PYTHON_MODULE(deprecation_policy) { 25 | bp::def("some_deprecated_function", some_deprecated_function, 26 | eigenpy::deprecated_function()); 27 | bp::def("some_future_deprecated_function", some_future_deprecated_function, 28 | eigenpy::deprecated_function()); 29 | 30 | bp::class_("X", bp::init<>(bp::args("self"))) 31 | .def("deprecated_member_function", &X::deprecated_member_function, 32 | eigenpy::deprecated_member<>()); 33 | } 34 | -------------------------------------------------------------------------------- /unittest/python/decompositions/sparse/test_SimplicialLLT.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import scipy 3 | from scipy.sparse import csc_matrix 4 | 5 | import eigenpy 6 | 7 | dim = 100 8 | rng = np.random.default_rng() 9 | 10 | A = rng.random((dim, dim)) 11 | A = (A + A.T) * 0.5 + np.diag(10.0 + rng.random(dim)) 12 | 13 | A = csc_matrix(A) 14 | 15 | llt = eigenpy.SimplicialLLT(A) 16 | 17 | assert llt.info() == eigenpy.ComputationInfo.Success 18 | 19 | L = llt.matrixL() 20 | U = llt.matrixU() 21 | 22 | LU = L @ U 23 | assert eigenpy.is_approx(LU.toarray(), A.toarray()) 24 | 25 | X = rng.random((dim, 20)) 26 | B = A.dot(X) 27 | X_est = llt.solve(B) 28 | assert eigenpy.is_approx(X, X_est) 29 | assert eigenpy.is_approx(A.dot(X_est), B) 30 | 31 | llt.analyzePattern(A) 32 | llt.factorize(A) 33 | permutation = llt.permutationP() 34 | 35 | X_sparse = scipy.sparse.random(dim, 10) 36 | B_sparse = A.dot(X_sparse) 37 | B_sparse = B_sparse.tocsc(True) 38 | 39 | if not B_sparse.has_sorted_indices: 40 | B_sparse.sort_indices() 41 | 42 | X_est = llt.solve(B_sparse) 43 | assert eigenpy.is_approx(X_est.toarray(), X_sparse.toarray()) 44 | assert eigenpy.is_approx(A.dot(X_est.toarray()), B_sparse.toarray()) 45 | -------------------------------------------------------------------------------- /include/eigenpy/exception.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2019, CNRS 3 | * Copyright 2018-2019, INRIA 4 | */ 5 | 6 | #ifndef __eigenpy_exception_hpp__ 7 | #define __eigenpy_exception_hpp__ 8 | 9 | #include 10 | #include 11 | 12 | #include "eigenpy/fwd.hpp" 13 | 14 | namespace eigenpy { 15 | /* 16 | * Eigenpy exception. They can be catch with python (equivalent 17 | * eigenpy.exception class). 18 | */ 19 | class Exception : public std::exception { 20 | public: 21 | Exception() : message() {} 22 | Exception(const std::string& msg) : message(msg) {} 23 | const char* what() const throw() { return this->getMessage().c_str(); } 24 | ~Exception() throw() {} 25 | virtual const std::string& getMessage() const { return message; } 26 | std::string copyMessage() const { return getMessage(); } 27 | 28 | /* Call this static function to "enable" the translation of this C++ exception 29 | * in Python. */ 30 | static void registerException(); 31 | 32 | private: 33 | static void translateException(Exception const& e); 34 | static PyObject* pyType; 35 | 36 | protected: 37 | std::string message; 38 | }; 39 | 40 | } // namespace eigenpy 41 | 42 | #endif // ifndef __eigenpy_exception_hpp__ 43 | -------------------------------------------------------------------------------- /include/eigenpy/scalar-conversion.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2014-2024 CNRS INRIA 3 | // 4 | 5 | #ifndef __eigenpy_scalar_conversion_hpp__ 6 | #define __eigenpy_scalar_conversion_hpp__ 7 | 8 | #include "eigenpy/config.hpp" 9 | #include 10 | #include 11 | 12 | namespace eigenpy { 13 | 14 | template 15 | struct FromTypeToType 16 | : public boost::mpl::if_c::value, 17 | std::true_type, 18 | typename boost::numeric::conversion_traits< 19 | Source, Target>::subranged>::type {}; 20 | 21 | /// FromTypeToType specialization to manage std::complex 22 | template 23 | struct FromTypeToType, std::complex> 24 | : public boost::mpl::if_c< 25 | std::is_same::value, std::true_type, 26 | typename boost::numeric::conversion_traits< 27 | ScalarSource, ScalarTarget>::subranged>::type {}; 28 | 29 | } // namespace eigenpy 30 | 31 | #endif // __eigenpy_scalar_conversion_hpp__ 32 | -------------------------------------------------------------------------------- /src/decompositions/sparse-lu-solver.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 INRIA 3 | */ 4 | 5 | #include "eigenpy/decompositions/sparse/SparseLU.hpp" 6 | 7 | namespace eigenpy { 8 | void exposeSparseLUSolver() { 9 | using namespace Eigen; 10 | 11 | typedef SparseMatrix ColMajorSparseMatrix; 12 | typedef typename ColMajorSparseMatrix::StorageIndex StorageIndex; 13 | typedef COLAMDOrdering Ordering; 14 | typedef SparseLU SparseLUType; 15 | 16 | typedef typename SparseLUType::Scalar Scalar; 17 | typedef typename SparseLUType::SCMatrix SCMatrix; 18 | 19 | #if EIGEN_VERSION_AT_LEAST(3, 4, 90) 20 | typedef Eigen::Map> 21 | MappedSparseMatrix; 22 | #else 23 | typedef Eigen::MappedSparseMatrix 24 | MappedSparseMatrix; 25 | #endif 26 | 27 | SparseLUMatrixLReturnTypeVisitor::expose( 28 | ("SparseLUMatrixLReturnType")); 29 | SparseLUMatrixUReturnTypeVisitor::expose( 30 | ("SparseLUMatrixUReturnType")); 31 | SparseLUVisitor::expose("SparseLU"); 32 | } 33 | } // namespace eigenpy 34 | -------------------------------------------------------------------------------- /src/decompositions/cholmod.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 INRIA 3 | */ 4 | 5 | #include "eigenpy/fwd.hpp" 6 | #include "eigenpy/decompositions/decompositions.hpp" 7 | 8 | #include "eigenpy/decompositions/sparse/cholmod/CholmodSimplicialLLT.hpp" 9 | #include "eigenpy/decompositions/sparse/cholmod/CholmodSimplicialLDLT.hpp" 10 | #include "eigenpy/decompositions/sparse/cholmod/CholmodSupernodalLLT.hpp" 11 | 12 | namespace eigenpy { 13 | 14 | void exposeCholmod() { 15 | using namespace Eigen; 16 | 17 | typedef Eigen::SparseMatrix ColMajorSparseMatrix; 18 | // typedef Eigen::SparseMatrix RowMajorSparseMatrix; 19 | 20 | bp::enum_("CholmodMode") 21 | .value("CholmodAuto", CholmodAuto) 22 | .value("CholmodSimplicialLLt", CholmodSimplicialLLt) 23 | .value("CholmodSupernodalLLt", CholmodSupernodalLLt) 24 | .value("CholmodLDLt", CholmodLDLt); 25 | 26 | CholmodSimplicialLLTVisitor::expose( 27 | "CholmodSimplicialLLT"); 28 | CholmodSimplicialLDLTVisitor::expose( 29 | "CholmodSimplicialLDLT"); 30 | CholmodSupernodalLLTVisitor::expose( 31 | "CholmodSupernodalLLT"); 32 | } 33 | } // namespace eigenpy 34 | -------------------------------------------------------------------------------- /src/solvers/least-squares-conjugate-gradient.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 INRIA 3 | */ 4 | 5 | #include "eigenpy/solvers/LeastSquaresConjugateGradient.hpp" 6 | 7 | namespace eigenpy { 8 | void exposeLeastSquaresConjugateGradient() { 9 | using namespace Eigen; 10 | using Eigen::LeastSquaresConjugateGradient; 11 | 12 | using Eigen::DiagonalPreconditioner; 13 | using Eigen::IdentityPreconditioner; 14 | using Eigen::LeastSquareDiagonalPreconditioner; 15 | 16 | using IdentityLeastSquaresConjugateGradient = 17 | LeastSquaresConjugateGradient; 18 | using DiagonalLeastSquaresConjugateGradient = LeastSquaresConjugateGradient< 19 | MatrixXd, DiagonalPreconditioner>; 20 | 21 | LeastSquaresConjugateGradientVisitor>>:: 23 | expose("LeastSquaresConjugateGradient"); 24 | LeastSquaresConjugateGradientVisitor:: 25 | expose("IdentityLeastSquaresConjugateGradient"); 26 | LeastSquaresConjugateGradientVisitor:: 27 | expose("DiagonalLeastSquaresConjugateGradient"); 28 | } 29 | } // namespace eigenpy 30 | -------------------------------------------------------------------------------- /unittest/python/test_GeneralizedEigenSolver.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | import eigenpy 4 | 5 | dim = 100 6 | rng = np.random.default_rng() 7 | A = rng.random((dim, dim)) 8 | B = rng.random((dim, dim)) 9 | B = (B + B.T) * 0.5 + np.diag(10.0 + rng.random(dim)) 10 | 11 | ges_matrices = eigenpy.GeneralizedEigenSolver(A, B) 12 | assert ges_matrices.info() == eigenpy.ComputationInfo.Success 13 | 14 | alphas = ges_matrices.alphas() 15 | betas = ges_matrices.betas() 16 | eigenvectors = ges_matrices.eigenvectors() 17 | eigenvalues = ges_matrices.eigenvalues() 18 | 19 | for k in range(dim): 20 | v = eigenvectors[:, k] 21 | lambda_k = eigenvalues[k] 22 | 23 | Av = A @ v 24 | lambda_Bv = lambda_k * (B @ v) 25 | assert eigenpy.is_approx(Av.real, lambda_Bv.real, 1e-6) 26 | assert eigenpy.is_approx(Av.imag, lambda_Bv.imag, 1e-6) 27 | 28 | for k in range(dim): 29 | v = eigenvectors[:, k] 30 | alpha = alphas[k] 31 | beta = betas[k] 32 | 33 | alpha_Bv = alpha * (B @ v) 34 | beta_Av = beta * (A @ v) 35 | assert eigenpy.is_approx(alpha_Bv.real, beta_Av.real, 1e-6) 36 | assert eigenpy.is_approx(alpha_Bv.imag, beta_Av.imag, 1e-6) 37 | 38 | for k in range(dim): 39 | if abs(betas[k]) > 1e-12: 40 | expected_eigenvalue = alphas[k] / betas[k] 41 | assert abs(eigenvalues[k] - expected_eigenvalue) < 1e-12 42 | -------------------------------------------------------------------------------- /include/eigenpy/solvers/BiCGSTAB.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 INRIA 3 | */ 4 | 5 | #ifndef __eigenpy_solvers_bicgstab_hpp__ 6 | #define __eigenpy_solvers_bicgstab_hpp__ 7 | 8 | #include 9 | 10 | #include "eigenpy/fwd.hpp" 11 | #include "eigenpy/solvers/IterativeSolverBase.hpp" 12 | 13 | namespace eigenpy { 14 | 15 | template 16 | struct BiCGSTABVisitor 17 | : public boost::python::def_visitor> { 18 | typedef typename BiCGSTAB::MatrixType MatrixType; 19 | 20 | template 21 | void visit(PyClass& cl) const { 22 | cl.def(bp::init<>("Default constructor")) 23 | .def(bp::init( 24 | bp::arg("A"), 25 | "Initialize the solver with matrix A for further || Ax - b || " 26 | "solving.\n" 27 | "This constructor is a shortcut for the default constructor " 28 | "followed by a call to compute().")); 29 | } 30 | 31 | static void expose(const std::string& name = "BiCGSTAB") { 32 | bp::class_(name.c_str(), bp::no_init) 33 | .def(IterativeSolverVisitor()) 34 | .def(BiCGSTABVisitor()) 35 | .def(IdVisitor()); 36 | } 37 | }; 38 | 39 | } // namespace eigenpy 40 | 41 | #endif // ifndef __eigenpy_solvers_bicgstab_hpp__ 42 | -------------------------------------------------------------------------------- /python/eigenpy/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2017-2021 CNRS INRIA 3 | # 4 | 5 | # On Windows, if eigenpy.dll is not in the same directory than 6 | # the .pyd, it will not be loaded. 7 | # We first try to load eigenpy, then, if it fail and we are on Windows: 8 | # 1. We add all paths inside eigenpy_WINDOWS_DLL_PATH to DllDirectory 9 | # 2. If EIGENPY_WINDOWS_DLL_PATH we add the relative path from the 10 | # package directory to the bin directory to DllDirectory 11 | # This solution is inspired from: 12 | # - https://github.com/PixarAnimationStudios/OpenUSD/pull/1511/files 13 | # - https://stackoverflow.com/questions/65334494/python-c-extension-packaging-dll-along-with-pyd 14 | # More resources on https://github.com/diffpy/pyobjcryst/issues/33 15 | try: 16 | from .eigenpy_pywrap import * # noqa 17 | from .eigenpy_pywrap import __raw_version__, __version__ 18 | except ImportError: 19 | import platform 20 | 21 | if platform.system() == "Windows": 22 | from .windows_dll_manager import build_directory_manager, get_dll_paths 23 | 24 | with build_directory_manager() as dll_dir_manager: 25 | for p in get_dll_paths(): 26 | dll_dir_manager.add_dll_directory(p) 27 | from .eigenpy_pywrap import * # noqa 28 | from .eigenpy_pywrap import __raw_version__, __version__ # noqa 29 | else: 30 | raise 31 | -------------------------------------------------------------------------------- /src/numpy-type.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2023 INRIA 3 | */ 4 | 5 | #include "eigenpy/numpy-type.hpp" 6 | 7 | #include // For PY_MAJOR_VERSION 8 | 9 | namespace eigenpy { 10 | 11 | NumpyType& NumpyType::getInstance() { 12 | static NumpyType instance; 13 | return instance; 14 | } 15 | 16 | bp::object NumpyType::make(PyArrayObject* pyArray, bool copy) { 17 | return make((PyObject*)pyArray, copy); 18 | } 19 | 20 | bp::object NumpyType::make(PyObject* pyObj, bool /*copy*/) { 21 | bp::object m; 22 | m = bp::object(bp::handle<>(pyObj)); // nothing to do here 23 | 24 | Py_INCREF(m.ptr()); 25 | return m; 26 | } 27 | 28 | void NumpyType::sharedMemory(const bool value) { 29 | getInstance().shared_memory = value; 30 | } 31 | 32 | bool NumpyType::sharedMemory() { return getInstance().shared_memory; } 33 | 34 | const PyTypeObject* NumpyType::getNumpyArrayType() { 35 | return getInstance().NumpyArrayType; 36 | } 37 | 38 | NumpyType::NumpyType() { 39 | pyModule = bp::import("numpy"); 40 | 41 | #if PY_MAJOR_VERSION >= 3 42 | // TODO I don't know why this Py_INCREF is necessary. 43 | // Without it, the destructor of NumpyType SEGV sometimes. 44 | Py_INCREF(pyModule.ptr()); 45 | #endif 46 | 47 | NumpyArrayObject = pyModule.attr("ndarray"); 48 | NumpyArrayType = reinterpret_cast(NumpyArrayObject.ptr()); 49 | 50 | shared_memory = true; 51 | } 52 | } // namespace eigenpy 53 | -------------------------------------------------------------------------------- /unittest/std_array.cpp: -------------------------------------------------------------------------------- 1 | /// @file 2 | /// @copyright Copyright 2023 CNRS INRIA 3 | 4 | #include "eigenpy/std-array.hpp" 5 | 6 | using Eigen::VectorXd; 7 | 8 | std::array get_arr_3_ints() { return {1, 2, 3}; } 9 | 10 | std::array get_arr_3_vecs() { 11 | std::array out; 12 | out[0].setOnes(4); 13 | out[1].setZero(2); 14 | out[2].setRandom(10); 15 | return out; 16 | } 17 | 18 | struct test_struct { 19 | std::array integs; 20 | std::array vecs; 21 | test_struct() { 22 | integs = {42, 3, -1}; 23 | vecs[0].setRandom(4); // 4 randoms between [-1,1] 24 | vecs[1].setZero(11); // 11 zeroes 25 | } 26 | }; 27 | 28 | BOOST_PYTHON_MODULE(std_array) { 29 | using namespace eigenpy; 30 | 31 | enableEigenPy(); 32 | 33 | StdArrayPythonVisitor, true>::expose("StdArr3_int"); 34 | StdVectorPythonVisitor, true>::expose("StdVec_int"); 35 | 36 | exposeStdArrayEigenSpecificType("VectorXd"); 37 | exposeStdArrayEigenSpecificType("VectorXd"); 38 | exposeStdVectorEigenSpecificType("VectorXd"); 39 | 40 | bp::def("get_arr_3_ints", get_arr_3_ints); 41 | bp::def("get_arr_3_vecs", get_arr_3_vecs); 42 | 43 | bp::class_("test_struct", bp::init<>(bp::args("self"))) 44 | .def_readwrite("integs", &test_struct::integs) 45 | .def_readwrite("vecs", &test_struct::vecs); 46 | } 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2014-2020, CNRS 4 | Copyright (c) 2018-2025, INRIA 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are met: 9 | 10 | 1. Redistributions of source code must retain the above copyright notice, this 11 | list of conditions and the following disclaimer. 12 | 13 | 2. Redistributions in binary form must reproduce the above copyright notice, 14 | this list of conditions and the following disclaimer in the documentation 15 | and/or other materials provided with the distribution. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 21 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 23 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 24 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 25 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /.github/workflows/jrl-cmakemodules.yml: -------------------------------------------------------------------------------- 1 | name: CI - JRL-cmakemodules 2 | 3 | on: 4 | push: 5 | branches: 6 | - devel 7 | paths-ignore: 8 | - 'doc/**' 9 | - '.gitlab-ci.yml' 10 | - '.gitignore' 11 | - '*.md' 12 | - 'LICENSE' 13 | - 'colcon.pkg' 14 | - '.pre-commit-config.yaml' 15 | pull_request: 16 | paths-ignore: 17 | - 'doc/**' 18 | - '.gitlab-ci.yml' 19 | - '.gitignore' 20 | - '*.md' 21 | - 'LICENSE' 22 | - 'colcon.pkg' 23 | - '.pre-commit-config.yaml' 24 | concurrency: 25 | group: ${{ github.workflow }}-${{ github.ref }} 26 | cancel-in-progress: true 27 | 28 | jobs: 29 | with-submodules: 30 | name: Check configuration with git submodules 31 | runs-on: ubuntu-latest 32 | steps: 33 | - uses: actions/checkout@v6 34 | with: 35 | submodules: true 36 | - run: sudo apt install libboost-all-dev libeigen3-dev python3-numpy python3-scipy 37 | - run: cmake . 38 | 39 | 40 | without-submodules: 41 | name: Check configuration without git submodules 42 | runs-on: ubuntu-latest 43 | steps: 44 | - uses: actions/checkout@v6 45 | with: 46 | submodules: false 47 | path: eigenpy 48 | - run: sudo apt install libboost-all-dev libeigen3-dev python3-numpy python3-scipy 49 | - run: cmake -B build -S eigenpy 50 | - run: grep -qvz CMAKE_PROJECT_VERSION:STATIC=0.0 build/CMakeCache.txt 51 | -------------------------------------------------------------------------------- /include/eigenpy/pickle-vector.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019-2020 CNRS INRIA 3 | // 4 | 5 | #ifndef __eigenpy_utils_pickle_vector_hpp__ 6 | #define __eigenpy_utils_pickle_vector_hpp__ 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | namespace eigenpy { 13 | /// 14 | /// \brief Create a pickle interface for the std::vector 15 | /// 16 | /// \tparam VecType Vector Type to pickle 17 | /// 18 | template 19 | struct PickleVector : boost::python::pickle_suite { 20 | static boost::python::tuple getinitargs(const VecType&) { 21 | return boost::python::make_tuple(); 22 | } 23 | 24 | static boost::python::tuple getstate(boost::python::object op) { 25 | return boost::python::make_tuple( 26 | boost::python::list(boost::python::extract(op)())); 27 | } 28 | 29 | static void setstate(boost::python::object op, boost::python::tuple tup) { 30 | if (boost::python::len(tup) > 0) { 31 | VecType& o = boost::python::extract(op)(); 32 | boost::python::stl_input_iterator begin( 33 | tup[0]), 34 | end; 35 | while (begin != end) { 36 | o.push_back(*begin); 37 | ++begin; 38 | } 39 | } 40 | } 41 | 42 | static bool getstate_manages_dict() { return true; } 43 | }; 44 | } // namespace eigenpy 45 | 46 | #endif // ifndef __eigenpy_utils_pickle_vector_hpp__ 47 | -------------------------------------------------------------------------------- /unittest/python/test_RealSchur.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | import eigenpy 4 | 5 | 6 | def verify_is_quasi_triangular(T): 7 | size = T.shape[0] 8 | 9 | for row in range(2, size): 10 | for col in range(row - 1): 11 | assert abs(T[row, col]) < 1e-12 12 | 13 | for row in range(1, size): 14 | if abs(T[row, row - 1]) > 1e-12: 15 | if row < size - 1: 16 | assert abs(T[row + 1, row]) < 1e-12 17 | 18 | tr = T[row - 1, row - 1] + T[row, row] 19 | det = T[row - 1, row - 1] * T[row, row] - T[row - 1, row] * T[row, row - 1] 20 | assert 4 * det > tr * tr 21 | 22 | 23 | dim = 100 24 | rng = np.random.default_rng() 25 | A = rng.random((dim, dim)) 26 | 27 | rs = eigenpy.RealSchur(A) 28 | assert rs.info() == eigenpy.ComputationInfo.Success 29 | 30 | U = rs.matrixU() 31 | T = rs.matrixT() 32 | 33 | assert eigenpy.is_approx(A, U @ T @ U.T) 34 | assert eigenpy.is_approx(U @ U.T, np.eye(dim)) 35 | 36 | verify_is_quasi_triangular(T) 37 | 38 | hess = eigenpy.HessenbergDecomposition(A) 39 | H = hess.matrixH() 40 | Q_hess = hess.matrixQ() 41 | 42 | rs_from_hess = eigenpy.RealSchur(dim) 43 | result_from_hess = rs_from_hess.computeFromHessenberg(H, Q_hess, True) 44 | assert result_from_hess.info() == eigenpy.ComputationInfo.Success 45 | 46 | T_from_hess = rs_from_hess.matrixT() 47 | U_from_hess = rs_from_hess.matrixU() 48 | 49 | assert eigenpy.is_approx(A, U_from_hess @ T_from_hess @ U_from_hess.T) 50 | -------------------------------------------------------------------------------- /include/eigenpy/solvers/ConjugateGradient.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 CNRS 3 | * Copyright 2025 INRIA 4 | */ 5 | 6 | #ifndef __eigenpy_solvers_conjugate_gradient_hpp__ 7 | #define __eigenpy_solvers_conjugate_gradient_hpp__ 8 | 9 | #include 10 | 11 | #include "eigenpy/fwd.hpp" 12 | #include "eigenpy/solvers/IterativeSolverBase.hpp" 13 | 14 | namespace eigenpy { 15 | 16 | template 17 | struct ConjugateGradientVisitor 18 | : public boost::python::def_visitor< 19 | ConjugateGradientVisitor> { 20 | typedef typename ConjugateGradient::MatrixType MatrixType; 21 | 22 | template 23 | void visit(PyClass& cl) const { 24 | cl.def(IterativeSolverVisitor()) 25 | .def(bp::init<>("Default constructor")) 26 | .def(bp::init( 27 | bp::arg("A"), 28 | "Initialize the solver with matrix A for further Ax=b solving.\n" 29 | "This constructor is a shortcut for the default constructor " 30 | "followed by a call to compute().")); 31 | } 32 | 33 | static void expose(const std::string& name = "ConjugateGradient") { 34 | bp::class_(name.c_str(), bp::no_init) 35 | .def(ConjugateGradientVisitor()) 36 | .def(IdVisitor()); 37 | } 38 | }; 39 | 40 | } // namespace eigenpy 41 | 42 | #endif // ifndef __eigenpy_solvers_conjugate_gradient_hpp__ 43 | -------------------------------------------------------------------------------- /include/eigenpy/std-map.hpp: -------------------------------------------------------------------------------- 1 | /// Copyright (c) 2024, INRIA 2 | /// 3 | 4 | #ifndef __eigenpy_std_map_hpp__ 5 | #define __eigenpy_std_map_hpp__ 6 | 7 | #include "eigenpy/map.hpp" 8 | #include "eigenpy/deprecated.hpp" 9 | #include 10 | 11 | namespace eigenpy { 12 | 13 | template 14 | using overload_base_get_item_for_std_map EIGENPY_DEPRECATED_MESSAGE( 15 | "Use overload_base_get_item_for_map<> instead.") = 16 | overload_base_get_item_for_map; 17 | 18 | namespace details { 19 | using ::eigenpy::overload_base_get_item_for_std_map; 20 | } // namespace details 21 | 22 | /** 23 | * @brief Expose an std::map from a type given as template argument. 24 | * 25 | * @param[in] T Type to expose as std::map. 26 | * @param[in] Compare Type for the Compare in std::map. 27 | * @param[in] Allocator Type for the Allocator in 28 | * std::map. 29 | * @param[in] NoProxy When set to false, the elements will be copied when 30 | * returned to Python. 31 | */ 32 | template , 33 | class Allocator = std::allocator>, 34 | bool NoProxy = false> 35 | struct StdMapPythonVisitor 36 | : GenericMapVisitor, NoProxy> {}; 37 | 38 | namespace python { 39 | // fix previous mistake 40 | using ::eigenpy::StdMapPythonVisitor; 41 | } // namespace python 42 | } // namespace eigenpy 43 | 44 | #endif // ifndef __eigenpy_std_map_hpp__ 45 | -------------------------------------------------------------------------------- /src/scipy-type.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 INRIA 3 | */ 4 | 5 | #include "eigenpy/scipy-type.hpp" 6 | 7 | #include // For PY_MAJOR_VERSION 8 | 9 | namespace eigenpy { 10 | 11 | ScipyType& ScipyType::getInstance() { 12 | static ScipyType instance; 13 | return instance; 14 | } 15 | 16 | void ScipyType::sharedMemory(const bool value) { 17 | getInstance().shared_memory = value; 18 | } 19 | 20 | bool ScipyType::sharedMemory() { return getInstance().shared_memory; } 21 | 22 | const PyTypeObject* ScipyType::getScipyCSRMatrixType() { 23 | return getInstance().csr_matrix_type; 24 | } 25 | 26 | const PyTypeObject* ScipyType::getScipyCSCMatrixType() { 27 | return getInstance().csc_matrix_type; 28 | } 29 | 30 | ScipyType::ScipyType() { 31 | try { 32 | sparse_module = bp::import("scipy.sparse"); 33 | } catch (...) { 34 | throw std::runtime_error( 35 | "SciPy is not installed. " 36 | "You can install it using the command \'pip install scipy\'."); 37 | } 38 | 39 | #if PY_MAJOR_VERSION >= 3 40 | // TODO I don't know why this Py_INCREF is necessary. 41 | // Without it, the destructor of ScipyType SEGV sometimes. 42 | Py_INCREF(sparse_module.ptr()); 43 | #endif 44 | 45 | csr_matrix_obj = sparse_module.attr("csr_matrix"); 46 | csr_matrix_type = reinterpret_cast(csr_matrix_obj.ptr()); 47 | csc_matrix_obj = sparse_module.attr("csc_matrix"); 48 | csc_matrix_type = reinterpret_cast(csc_matrix_obj.ptr()); 49 | 50 | shared_memory = true; 51 | } 52 | } // namespace eigenpy 53 | -------------------------------------------------------------------------------- /unittest/python/test_std_unique_ptr.py: -------------------------------------------------------------------------------- 1 | from std_unique_ptr import ( 2 | V1, 3 | UniquePtrHolder, 4 | make_unique_complex, 5 | make_unique_int, 6 | make_unique_null, 7 | make_unique_str, 8 | make_unique_v1, 9 | ) 10 | 11 | v = make_unique_int() 12 | assert isinstance(v, int) 13 | assert v == 10 14 | 15 | v = make_unique_v1() 16 | assert isinstance(v, V1) 17 | assert v.v == 10 18 | 19 | v = make_unique_null() 20 | assert v is None 21 | 22 | v = make_unique_str() 23 | assert isinstance(v, str) 24 | assert v == "str" 25 | 26 | v = make_unique_complex() 27 | assert isinstance(v, complex) 28 | assert v == 1 + 0j 29 | 30 | unique_ptr_holder = UniquePtrHolder() 31 | 32 | v = unique_ptr_holder.int_ptr 33 | assert isinstance(v, int) 34 | assert v == 20 35 | # v is a copy, int_ptr will not be updated 36 | v = 10 37 | assert unique_ptr_holder.int_ptr == 20 38 | 39 | v = unique_ptr_holder.v1_ptr 40 | assert isinstance(v, V1) 41 | assert v.v == 200 42 | # v is a ref, v1_ptr will be updated 43 | v.v = 10 44 | assert unique_ptr_holder.v1_ptr.v == 10 45 | 46 | v = unique_ptr_holder.null_ptr 47 | assert v is None 48 | 49 | v = unique_ptr_holder.str_ptr 50 | assert isinstance(v, str) 51 | assert v == "str" 52 | # v is a copy, str_ptr will not be updated 53 | v = "str_updated" 54 | assert unique_ptr_holder.str_ptr == "str" 55 | 56 | v = unique_ptr_holder.complex_ptr 57 | assert isinstance(v, complex) 58 | assert v == 1 + 0j 59 | # v is a copy, complex_ptr will not be updated 60 | v = 1 + 2j 61 | assert unique_ptr_holder.complex_ptr == 1 + 0j 62 | -------------------------------------------------------------------------------- /development/scripts/pixi/activation.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | # Activation script 3 | 4 | # Remove flags setup from cxx-compiler 5 | unset CFLAGS 6 | unset CPPFLAGS 7 | unset CXXFLAGS 8 | unset DEBUG_CFLAGS 9 | unset DEBUG_CPPFLAGS 10 | unset DEBUG_CXXFLAGS 11 | unset LDFLAGS 12 | 13 | if [[ $host_alias == *"apple"* ]]; 14 | then 15 | # On OSX setting the rpath and -L it's important to use the conda libc++ instead of the system one. 16 | # If conda-forge use install_name_tool to package some libs, -headerpad_max_install_names is then mandatory 17 | export LDFLAGS="-Wl,-headerpad_max_install_names -Wl,-rpath,$CONDA_PREFIX/lib -L$CONDA_PREFIX/lib" 18 | elif [[ $host_alias == *"linux"* ]]; 19 | then 20 | # On GNU/Linux, I don't know if these flags are mandatory with g++ but 21 | # it allow to use clang++ as compiler 22 | export LDFLAGS="-Wl,-rpath,$CONDA_PREFIX/lib -Wl,-rpath-link,$CONDA_PREFIX/lib -L$CONDA_PREFIX/lib" 23 | # Conda compiler is named x86_64-conda-linux-gnu-c++, ccache can't resolve it 24 | # (https://ccache.dev/manual/latest.html#config_compiler_type) 25 | export CCACHE_COMPILERTYPE=gcc 26 | fi 27 | # Without -isystem, some LSP can't find headers 28 | export EIGENPY_CXX_FLAGS="$CXXFLAGS -isystem $CONDA_PREFIX/include" 29 | 30 | # Set default build value only if not previously set 31 | export EIGENPY_BUILD_TYPE=${EIGENPY_BUILD_TYPE:=Release} 32 | export EIGENPY_PYTHON_STUBS=${EIGENPY_PYTHON_STUBS:=ON} 33 | export EIGENPY_CHOLMOD_SUPPORT=${EIGENPY_CHOLMOD_SUPPORT:=OFF} 34 | export EIGENPY_ACCELERATE_SUPPORT=${EIGENPY_ACCELERATE_SUPPORT:=OFF} 35 | -------------------------------------------------------------------------------- /unittest/python/test_IncompleteLUT.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from scipy.sparse import csc_matrix 3 | 4 | import eigenpy 5 | 6 | dim = 100 7 | rng = np.random.default_rng() 8 | 9 | A = rng.random((dim, dim)) 10 | A = (A + A.T) * 0.5 + np.diag(5.0 + rng.random(dim)) 11 | A = csc_matrix(A) 12 | 13 | ilut = eigenpy.solvers.IncompleteLUT(A) 14 | assert ilut.info() == eigenpy.ComputationInfo.Success 15 | assert ilut.rows() == dim 16 | assert ilut.cols() == dim 17 | 18 | X = rng.random((dim, 100)) 19 | B = A.dot(X) 20 | X_est = ilut.solve(B) 21 | assert isinstance(X_est, np.ndarray) 22 | residual = np.linalg.norm(B - A.dot(X_est)) / np.linalg.norm(B) 23 | assert residual < 0.1 24 | 25 | x = rng.random(dim) 26 | b = A.dot(x) 27 | x_est = ilut.solve(b) 28 | assert isinstance(x_est, np.ndarray) 29 | residual = np.linalg.norm(b - A.dot(x_est)) / np.linalg.norm(b) 30 | assert residual < 0.1 31 | 32 | X_sparse = csc_matrix(rng.random((dim, 10))) 33 | B_sparse = A.dot(X_sparse).tocsc() 34 | if not B_sparse.has_sorted_indices: 35 | B_sparse.sort_indices() 36 | X_est_sparse = ilut.solve(B_sparse) 37 | assert isinstance(X_est_sparse, csc_matrix) 38 | 39 | ilut.analyzePattern(A) 40 | ilut.factorize(A) 41 | assert ilut.info() == eigenpy.ComputationInfo.Success 42 | 43 | ilut_params = eigenpy.solvers.IncompleteLUT(A, 1e-4, 15) 44 | assert ilut_params.info() == eigenpy.ComputationInfo.Success 45 | 46 | ilut_set = eigenpy.solvers.IncompleteLUT() 47 | ilut_set.setDroptol(1e-3) 48 | ilut_set.setFillfactor(20) 49 | ilut_set.compute(A) 50 | assert ilut_set.info() == eigenpy.ComputationInfo.Success 51 | -------------------------------------------------------------------------------- /unittest/return_by_ref.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 INRIA 3 | */ 4 | 5 | #include 6 | 7 | #include "eigenpy/eigenpy.hpp" 8 | 9 | template 10 | struct Base { 11 | Base(const Eigen::DenseIndex rows, const Eigen::DenseIndex cols) 12 | : mat(rows, cols) {} 13 | 14 | void show() { std::cout << mat << std::endl; } 15 | 16 | Matrix& ref() { return mat; } 17 | const Matrix& const_ref() { return mat; } 18 | Matrix copy() { return mat; } 19 | 20 | protected: 21 | Matrix mat; 22 | }; 23 | 24 | template 25 | void expose_matrix_class(const std::string& name) { 26 | using namespace Eigen; 27 | namespace bp = boost::python; 28 | 29 | bp::class_>(name.c_str(), bp::init()) 30 | .def("show", &Base::show) 31 | .def("ref", &Base::ref, bp::return_internal_reference<>()) 32 | .def("const_ref", &Base::const_ref, 33 | bp::return_internal_reference<>()) 34 | .def("copy", &Base::copy); 35 | } 36 | 37 | BOOST_PYTHON_MODULE(return_by_ref) { 38 | using namespace Eigen; 39 | eigenpy::enableEigenPy(); 40 | 41 | typedef Eigen::Matrix VectorType; 42 | typedef Eigen::Matrix MatrixType; 43 | typedef Eigen::Matrix 44 | RowMatrixType; 45 | 46 | expose_matrix_class("Vector"); 47 | expose_matrix_class("Matrix"); 48 | expose_matrix_class("RowMatrix"); 49 | } 50 | -------------------------------------------------------------------------------- /include/eigenpy/version.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019-2020 INRIA 3 | // 4 | 5 | #ifndef __eigenpy_version_hpp__ 6 | #define __eigenpy_version_hpp__ 7 | 8 | #include 9 | 10 | #include "eigenpy/config.hpp" 11 | 12 | namespace eigenpy { 13 | 14 | /// 15 | /// \brief Returns the current version of EigenPy as a string using 16 | /// the following standard: 17 | /// EIGENPY_MINOR_VERSION.EIGENPY_MINOR_VERSION.EIGENPY_PATCH_VERSION 18 | /// 19 | std::string EIGENPY_DLLAPI printVersion(const std::string& delimiter = "."); 20 | 21 | /// 22 | /// \brief Returns the current version of Eigen3 as a string using 23 | /// the following standard: 24 | /// EIGEN_MINOR_VERSION.EIGEN_MINOR_VERSION.EIGEN_PATCH_VERSION 25 | /// 26 | std::string EIGENPY_DLLAPI 27 | printEigenVersion(const std::string& delimiter = "."); 28 | 29 | /// 30 | /// \brief Checks if the current version of EigenPy is at least the version 31 | /// provided 32 | /// by the input arguments. 33 | /// 34 | /// \param[in] major_version Major version to check. 35 | /// \param[in] minor_version Minor version to check. 36 | /// \param[in] patch_version Patch version to check. 37 | /// 38 | /// \returns true if the current version of EigenPy is greater than the version 39 | /// provided 40 | /// by the input arguments. 41 | /// 42 | bool EIGENPY_DLLAPI checkVersionAtLeast(unsigned int major_version, 43 | unsigned int minor_version, 44 | unsigned int patch_version); 45 | } // namespace eigenpy 46 | 47 | #endif // __eigenpy_version_hpp__ 48 | -------------------------------------------------------------------------------- /unittest/python/test_return_by_ref.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import return_by_ref 3 | from return_by_ref import Matrix, RowMatrix, Vector 4 | 5 | 6 | def test_shared(mat): 7 | m_ref = mat.ref() 8 | m_ref.fill(0) 9 | m_copy = mat.copy() 10 | assert np.array_equal(m_ref, m_copy) 11 | 12 | m_const_ref = mat.const_ref() 13 | assert np.array_equal(m_const_ref, m_copy) 14 | assert np.array_equal(m_const_ref, m_ref) 15 | 16 | m_ref.fill(1) 17 | assert not np.array_equal(m_ref, m_copy) 18 | assert np.array_equal(m_const_ref, m_ref) 19 | 20 | try: 21 | m_const_ref.fill(2) 22 | assert False 23 | except Exception: 24 | assert True 25 | 26 | 27 | def test_not_shared(mat): 28 | m_ref = mat.ref() 29 | m_ref.fill(100.0) 30 | m_copy = mat.copy() 31 | assert not np.array_equal(m_ref, m_copy) 32 | 33 | m_const_ref = mat.const_ref() 34 | assert np.array_equal(m_const_ref, m_copy) 35 | assert not np.array_equal(m_const_ref, m_ref) 36 | 37 | m_ref.fill(10.0) 38 | assert not np.array_equal(m_ref, m_copy) 39 | assert not np.array_equal(m_const_ref, m_ref) 40 | 41 | try: 42 | m_const_ref.fill(2) 43 | assert True 44 | except Exception: 45 | assert False 46 | 47 | 48 | rows = 10 49 | cols = 30 50 | 51 | mat = Matrix(rows, cols) 52 | row_mat = RowMatrix(rows, cols) 53 | vec = Vector(rows, 1) 54 | 55 | test_shared(mat) 56 | test_shared(row_mat) 57 | test_shared(vec) 58 | 59 | return_by_ref.sharedMemory(False) 60 | test_not_shared(mat) 61 | test_not_shared(row_mat) 62 | test_not_shared(vec) 63 | -------------------------------------------------------------------------------- /.github/workflows/update_pixi_lockfile.yml: -------------------------------------------------------------------------------- 1 | name: CI - Update Pixi lockfile 2 | permissions: 3 | contents: write 4 | pull-requests: write 5 | 6 | on: 7 | workflow_dispatch: 8 | schedule: 9 | - cron: 0 5 1 * * 10 | 11 | jobs: 12 | pixi-update: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/create-github-app-token@v2 17 | id: generate-token 18 | with: 19 | app-id: ${{ secrets.APP_ID }} 20 | private-key: ${{ secrets.APP_PRIVATE_KEY }} 21 | 22 | - uses: actions/checkout@v6 23 | with: 24 | token: ${{ steps.generate-token.outputs.token }} 25 | ref: devel 26 | # Make sure the value of GITHUB_TOKEN will not be persisted in repo's config 27 | persist-credentials: false 28 | 29 | - name: Set up pixi 30 | uses: prefix-dev/setup-pixi@v0.9.3 31 | with: 32 | run-install: false 33 | 34 | - name: Update lockfile 35 | run: | 36 | set -o pipefail 37 | pixi update --json | pixi exec pixi-diff-to-markdown > diff.md 38 | 39 | - name: Create pull request 40 | uses: peter-evans/create-pull-request@v7 41 | with: 42 | token: ${{ steps.generate-token.outputs.token }} 43 | commit-message: 'pixi: Update pixi lockfile' 44 | title: Update pixi lockfile 45 | body-path: diff.md 46 | branch: topic/update-pixi 47 | base: devel 48 | labels: | 49 | pixi 50 | no changelog 51 | delete-branch: true 52 | add-paths: pixi.lock 53 | -------------------------------------------------------------------------------- /unittest/python/test_ComplexSchur.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | import eigenpy 4 | 5 | dim = 100 6 | rng = np.random.default_rng() 7 | A = rng.random((dim, dim)) 8 | 9 | cs = eigenpy.ComplexSchur(A) 10 | assert cs.info() == eigenpy.ComputationInfo.Success 11 | 12 | U = cs.matrixU() 13 | T = cs.matrixT() 14 | 15 | A_complex = A.astype(complex) 16 | assert eigenpy.is_approx(A_complex, U @ T @ U.conj().T) 17 | assert eigenpy.is_approx(U @ U.conj().T, np.eye(dim)) 18 | 19 | for row in range(1, dim): 20 | for col in range(row): 21 | assert abs(T[row, col]) < 1e-12 22 | 23 | A_triangular = np.triu(A) 24 | cs_triangular = eigenpy.ComplexSchur(dim) 25 | cs_triangular.setMaxIterations(1) 26 | result_triangular = cs_triangular.compute(A_triangular) 27 | assert result_triangular.info() == eigenpy.ComputationInfo.Success 28 | 29 | T_triangular = cs_triangular.matrixT() 30 | U_triangular = cs_triangular.matrixU() 31 | 32 | A_triangular_complex = A_triangular.astype(complex) 33 | assert eigenpy.is_approx(T_triangular, A_triangular_complex) 34 | assert eigenpy.is_approx(U_triangular, np.eye(dim, dtype=complex)) 35 | 36 | hess = eigenpy.HessenbergDecomposition(A) 37 | H = hess.matrixH() 38 | Q_hess = hess.matrixQ() 39 | 40 | cs_from_hess = eigenpy.ComplexSchur(dim) 41 | result_from_hess = cs_from_hess.computeFromHessenberg(H, Q_hess, True) 42 | assert result_from_hess.info() == eigenpy.ComputationInfo.Success 43 | 44 | T_from_hess = cs_from_hess.matrixT() 45 | U_from_hess = cs_from_hess.matrixU() 46 | 47 | A_complex = A.astype(complex) 48 | assert eigenpy.is_approx(A_complex, U_from_hess @ T_from_hess @ U_from_hess.conj().T) 49 | -------------------------------------------------------------------------------- /.github/workflows/dockgen.yml: -------------------------------------------------------------------------------- 1 | name: "Publish docker image" 2 | 3 | on: 4 | push: 5 | branches: 6 | - devel 7 | tags: 8 | - "v*" 9 | 10 | jobs: 11 | dockgen: 12 | runs-on: "ubuntu-latest" 13 | permissions: 14 | contents: read 15 | packages: write 16 | attestations: write 17 | id-token: write 18 | steps: 19 | - uses: actions/checkout@v6 20 | 21 | - uses: astral-sh/setup-uv@v7 22 | - run: uvx dockgen 23 | 24 | # https://docs.github.com/en/packages/managing-github-packages-using-github-actions-workflows/publishing-and-installing-a-package-with-github-actions 25 | 26 | - name: Log in to the Container registry 27 | uses: docker/login-action@v3 28 | with: 29 | registry: ghcr.io 30 | username: ${{ github.actor }} 31 | password: ${{ secrets.GITHUB_TOKEN }} 32 | 33 | - name: Extract metadata (tags, labels) for Docker 34 | id: meta 35 | uses: docker/metadata-action@v5 36 | with: 37 | images: ghcr.io/${{ github.repository }} 38 | 39 | - name: Build and push Docker image 40 | id: push 41 | uses: docker/build-push-action@v6 42 | with: 43 | context: . 44 | push: true 45 | tags: ${{ steps.meta.outputs.tags }} 46 | labels: ${{ steps.meta.outputs.labels }} 47 | 48 | - name: Generate artifact attestation 49 | uses: actions/attest-build-provenance@v3 50 | with: 51 | subject-name: ghcr.io/${{ github.repository }} 52 | subject-digest: ${{ steps.push.outputs.digest }} 53 | push-to-registry: true 54 | -------------------------------------------------------------------------------- /python/eigenpy/windows_dll_manager.py: -------------------------------------------------------------------------------- 1 | import contextlib 2 | import os 3 | 4 | 5 | def get_dll_paths(): 6 | eigenpy_paths = os.getenv("EIGENPY_WINDOWS_DLL_PATH") 7 | if eigenpy_paths is None: 8 | # From https://peps.python.org/pep-0250/#implementation 9 | # lib/python-version/site-packages/package 10 | RELATIVE_DLL_PATH1 = "..\\..\\..\\..\\bin" 11 | # lib/site-packages/package 12 | RELATIVE_DLL_PATH2 = "..\\..\\..\\bin" 13 | # For unit test 14 | RELATIVE_DLL_PATH3 = "..\\..\\bin" 15 | return [ 16 | os.path.join(os.path.dirname(__file__), RELATIVE_DLL_PATH1), 17 | os.path.join(os.path.dirname(__file__), RELATIVE_DLL_PATH2), 18 | os.path.join(os.path.dirname(__file__), RELATIVE_DLL_PATH3), 19 | ] 20 | else: 21 | return eigenpy_paths.split(os.pathsep) 22 | 23 | 24 | class DllDirectoryManager(contextlib.AbstractContextManager): 25 | """Restore DllDirectory state after importing Python module""" 26 | 27 | def add_dll_directory(self, dll_dir: str): 28 | # add_dll_directory can fail on relative path and non 29 | # existing path. 30 | # Since we don't know all the fail criterion we just ignore 31 | # thrown exception 32 | try: 33 | self.dll_dirs.append(os.add_dll_directory(dll_dir)) 34 | except OSError: 35 | pass 36 | 37 | def __enter__(self): 38 | self.dll_dirs = [] 39 | return self 40 | 41 | def __exit__(self, *exc_details): 42 | for d in self.dll_dirs: 43 | d.close() 44 | 45 | 46 | def build_directory_manager(): 47 | return DllDirectoryManager() 48 | -------------------------------------------------------------------------------- /include/eigenpy/utils/is-approx.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020-2024 INRIA 3 | */ 4 | 5 | #ifndef __eigenpy_utils_is_approx_hpp__ 6 | #define __eigenpy_utils_is_approx_hpp__ 7 | 8 | #include 9 | #include 10 | 11 | namespace eigenpy { 12 | template 13 | EIGEN_DONT_INLINE bool is_approx(const Eigen::MatrixBase& mat1, 14 | const Eigen::MatrixBase& mat2, 15 | const typename MatrixType1::RealScalar& prec) { 16 | return mat1.isApprox(mat2, prec); 17 | } 18 | 19 | template 20 | EIGEN_DONT_INLINE bool is_approx(const Eigen::MatrixBase& mat1, 21 | const Eigen::MatrixBase& mat2) { 22 | return is_approx( 23 | mat1, mat2, 24 | Eigen::NumTraits::dummy_precision()); 25 | } 26 | 27 | template 28 | EIGEN_DONT_INLINE bool is_approx( 29 | const Eigen::SparseMatrixBase& mat1, 30 | const Eigen::SparseMatrixBase& mat2, 31 | const typename MatrixType1::RealScalar& prec) { 32 | return mat1.isApprox(mat2, prec); 33 | } 34 | 35 | template 36 | EIGEN_DONT_INLINE bool is_approx( 37 | const Eigen::SparseMatrixBase& mat1, 38 | const Eigen::SparseMatrixBase& mat2) { 39 | return is_approx( 40 | mat1, mat2, 41 | Eigen::NumTraits::dummy_precision()); 42 | } 43 | } // namespace eigenpy 44 | 45 | #endif // ifndef __eigenpy_utils_is_approx_hpp__ 46 | -------------------------------------------------------------------------------- /.github/workflows/linux.yml: -------------------------------------------------------------------------------- 1 | name: CI - Linux via APT 2 | 3 | on: 4 | push: 5 | branches: 6 | - devel 7 | paths-ignore: 8 | - 'doc/**' 9 | - '.gitlab-ci.yml' 10 | - '.gitignore' 11 | - '*.md' 12 | - 'LICENSE' 13 | - 'colcon.pkg' 14 | - '.pre-commit-config.yaml' 15 | pull_request: 16 | paths-ignore: 17 | - 'doc/**' 18 | - '.gitlab-ci.yml' 19 | - '.gitignore' 20 | - '*.md' 21 | - 'LICENSE' 22 | - 'colcon.pkg' 23 | - '.pre-commit-config.yaml' 24 | concurrency: 25 | group: ${{ github.workflow }}-${{ github.ref }} 26 | cancel-in-progress: true 27 | 28 | jobs: 29 | test: 30 | name: "Test python ${{ matrix.python }} on ${{ matrix.ubuntu }}.04" 31 | runs-on: "ubuntu-${{ matrix.ubuntu }}.04" 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | python: [3] 36 | ubuntu: [22, 24] 37 | steps: 38 | - uses: actions/checkout@v6 39 | with: 40 | submodules: 'true' 41 | - run: | 42 | sudo apt-get update 43 | sudo apt-get install cmake libboost-all-dev libeigen3-dev python*-numpy python*-dev python*-scipy 44 | echo $(sudo apt list --installed) 45 | echo $(g++ --version) 46 | - run: cmake . -DPYTHON_EXECUTABLE=$(which python${{ matrix.python }}) -DBUILD_TESTING_SCIPY=ON 47 | - run: make -j2 48 | - run: ctest --output-on-failure 49 | 50 | check: 51 | if: always() 52 | name: check-linux 53 | 54 | needs: 55 | - test 56 | 57 | runs-on: Ubuntu-latest 58 | 59 | steps: 60 | - name: Decide whether the needed jobs succeeded or failed 61 | uses: re-actors/alls-green@release/v1 62 | with: 63 | jobs: ${{ toJSON(needs) }} 64 | -------------------------------------------------------------------------------- /unittest/python/test_bind_optional.py.in: -------------------------------------------------------------------------------- 1 | import importlib 2 | 3 | bind_optional = importlib.import_module("@MODNAME@") 4 | 5 | 6 | def test_none_if_zero(): 7 | x = bind_optional.none_if_zero(0) 8 | y = bind_optional.none_if_zero(-1) 9 | assert x is None 10 | assert y == -1 11 | 12 | 13 | def test_struct_ctors(): 14 | # test struct ctors 15 | 16 | struct = bind_optional.mystruct() 17 | assert struct.a is None 18 | assert struct.b is None 19 | assert struct.msg == "i am struct" 20 | 21 | ## no 2nd arg automatic overload using bp::optional 22 | struct = bind_optional.mystruct(2) 23 | assert struct.a == 2 24 | assert struct.b is None 25 | 26 | struct = bind_optional.mystruct(13, -1.0) 27 | assert struct.a == 13 28 | assert struct.b == -1.0 29 | 30 | 31 | def test_struct_setters(): 32 | struct = bind_optional.mystruct() 33 | struct.a = 1 34 | assert struct.a == 1 35 | 36 | struct.b = -3.14 37 | assert struct.b == -3.14 38 | 39 | # set to None 40 | struct.a = None 41 | struct.b = None 42 | struct.msg = None 43 | assert struct.a is None 44 | assert struct.b is None 45 | assert struct.msg is None 46 | 47 | 48 | def test_factory(): 49 | struct = bind_optional.create_if_true(False, None) 50 | assert struct is None 51 | struct = bind_optional.create_if_true(True, None) 52 | assert struct.a == 0 53 | assert struct.b is None 54 | 55 | 56 | def test_random_mat(): 57 | M = bind_optional.random_mat_if_true(False) 58 | assert M is None 59 | M = bind_optional.random_mat_if_true(True) 60 | assert M.shape == (4, 4) 61 | 62 | 63 | test_none_if_zero() 64 | test_struct_ctors() 65 | test_struct_setters() 66 | test_factory() 67 | test_random_mat() 68 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-parts": { 4 | "inputs": { 5 | "nixpkgs-lib": "nixpkgs-lib" 6 | }, 7 | "locked": { 8 | "lastModified": 1763759067, 9 | "narHash": "sha256-LlLt2Jo/gMNYAwOgdRQBrsRoOz7BPRkzvNaI/fzXi2Q=", 10 | "owner": "hercules-ci", 11 | "repo": "flake-parts", 12 | "rev": "2cccadc7357c0ba201788ae99c4dfa90728ef5e0", 13 | "type": "github" 14 | }, 15 | "original": { 16 | "owner": "hercules-ci", 17 | "repo": "flake-parts", 18 | "type": "github" 19 | } 20 | }, 21 | "nixpkgs": { 22 | "locked": { 23 | "lastModified": 1764517877, 24 | "narHash": "sha256-pp3uT4hHijIC8JUK5MEqeAWmParJrgBVzHLNfJDZxg4=", 25 | "owner": "NixOS", 26 | "repo": "nixpkgs", 27 | "rev": "2d293cbfa5a793b4c50d17c05ef9e385b90edf6c", 28 | "type": "github" 29 | }, 30 | "original": { 31 | "owner": "NixOS", 32 | "ref": "nixos-unstable", 33 | "repo": "nixpkgs", 34 | "type": "github" 35 | } 36 | }, 37 | "nixpkgs-lib": { 38 | "locked": { 39 | "lastModified": 1761765539, 40 | "narHash": "sha256-b0yj6kfvO8ApcSE+QmA6mUfu8IYG6/uU28OFn4PaC8M=", 41 | "owner": "nix-community", 42 | "repo": "nixpkgs.lib", 43 | "rev": "719359f4562934ae99f5443f20aa06c2ffff91fc", 44 | "type": "github" 45 | }, 46 | "original": { 47 | "owner": "nix-community", 48 | "repo": "nixpkgs.lib", 49 | "type": "github" 50 | } 51 | }, 52 | "root": { 53 | "inputs": { 54 | "flake-parts": "flake-parts", 55 | "nixpkgs": "nixpkgs" 56 | } 57 | } 58 | }, 59 | "root": "root", 60 | "version": 7 61 | } 62 | -------------------------------------------------------------------------------- /unittest/std_map.cpp: -------------------------------------------------------------------------------- 1 | /// @file 2 | /// @copyright Copyright 2023 CNRS INRIA 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | namespace bp = boost::python; 9 | 10 | template 11 | bp::dict std_map_to_dict(const std::map& map) { 12 | bp::dict dictionnary; 13 | for (auto const& x : map) { 14 | dictionnary[x.first] = x.second; 15 | } 16 | return dictionnary; 17 | } 18 | 19 | template 20 | std::map copy(const std::map& map) { 21 | std::map out = map; 22 | return out; 23 | } 24 | 25 | template 26 | boost::unordered_map copy_boost( 27 | const boost::unordered_map& obj) { 28 | return obj; 29 | } 30 | 31 | struct X { 32 | X() = delete; 33 | X(int x) : val(x) {} 34 | int val; 35 | }; 36 | 37 | BOOST_PYTHON_MODULE(std_map) { 38 | eigenpy::enableEigenPy(); 39 | 40 | eigenpy::StdMapPythonVisitor< 41 | std::string, double, std::less, 42 | std::allocator>, 43 | true>::expose("StdMap_Double"); 44 | 45 | eigenpy::GenericMapVisitor>::expose( 46 | "boost_map_int"); 47 | 48 | using StdMap_X = std::map; 49 | bp::class_("X", bp::init()).def_readwrite("val", &X::val); 50 | 51 | // this just needs to compile 52 | eigenpy::GenericMapVisitor::expose( 53 | "StdMap_X", eigenpy::overload_base_get_item_for_map()); 54 | 55 | bp::def("std_map_to_dict", std_map_to_dict); 56 | bp::def("copy", copy); 57 | bp::def("copy_boost", copy_boost); 58 | bp::def("copy_X", +[](const StdMap_X& m) { return m; }); 59 | } 60 | -------------------------------------------------------------------------------- /include/eigenpy/solvers/LeastSquaresConjugateGradient.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2018 CNRS 3 | * Copyright 2025 INRIA 4 | */ 5 | 6 | #ifndef __eigenpy_solvers_least_square_conjugate_gradient_hpp__ 7 | #define __eigenpy_solvers_least_square_conjugate_gradient_hpp__ 8 | 9 | #include 10 | 11 | #include "eigenpy/fwd.hpp" 12 | #include "eigenpy/solvers/IterativeSolverBase.hpp" 13 | 14 | namespace eigenpy { 15 | 16 | template 17 | struct LeastSquaresConjugateGradientVisitor 18 | : public boost::python::def_visitor< 19 | LeastSquaresConjugateGradientVisitor> { 20 | typedef typename LeastSquaresConjugateGradient::MatrixType MatrixType; 21 | 22 | template 23 | void visit(PyClass& cl) const { 24 | cl.def(bp::init<>("Default constructor")) 25 | .def(bp::init( 26 | bp::arg("A"), 27 | "Initialize the solver with matrix A for further || Ax - b || " 28 | "solving.\n" 29 | "This constructor is a shortcut for the default constructor " 30 | "followed by a call to compute().")); 31 | } 32 | 33 | static void expose( 34 | const std::string& name = "LeastSquaresConjugateGradient") { 35 | bp::class_(name.c_str(), 36 | bp::no_init) 37 | .def(IterativeSolverVisitor()) 38 | .def(LeastSquaresConjugateGradientVisitor< 39 | LeastSquaresConjugateGradient>()) 40 | .def(IdVisitor()); 41 | } 42 | }; 43 | 44 | } // namespace eigenpy 45 | 46 | #endif // ifndef __eigenpy_solvers_least_square_conjugate_gradient_hpp__ 47 | -------------------------------------------------------------------------------- /unittest/python/decompositions/sparse/test_SparseLU.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import scipy.sparse as spa 3 | 4 | import eigenpy 5 | 6 | dim = 100 7 | rng = np.random.default_rng() 8 | 9 | A_fac = spa.random(dim, dim, density=0.25, random_state=rng) 10 | A = A_fac.T @ A_fac 11 | A += spa.diags(10.0 * rng.standard_normal(dim) ** 2) 12 | A = A.tocsc(True) 13 | A.check_format() 14 | 15 | splu = eigenpy.SparseLU(A) 16 | 17 | assert splu.info() == eigenpy.ComputationInfo.Success 18 | 19 | X = rng.random((dim, 20)) 20 | B = A.dot(X) 21 | X_est = splu.solve(B) 22 | assert isinstance(X_est, np.ndarray) 23 | assert eigenpy.is_approx(X, X_est) 24 | assert eigenpy.is_approx(A.dot(X_est), B) 25 | 26 | splu.analyzePattern(A) 27 | splu.factorize(A) 28 | 29 | X_sparse = spa.random(dim, 10, random_state=rng) 30 | B_sparse = A.dot(X_sparse) 31 | B_sparse: spa.csc_matrix = B_sparse.tocsc(True) 32 | if not B_sparse.has_sorted_indices: 33 | B_sparse.sort_indices() 34 | 35 | X_est = splu.solve(B_sparse) 36 | assert isinstance(X_est, spa.csc_matrix) 37 | assert eigenpy.is_approx(X_est.toarray(), X_sparse.toarray()) 38 | assert eigenpy.is_approx(A.dot(X_est.toarray()), B_sparse.toarray()) 39 | 40 | assert splu.nnzL() > 0 41 | assert splu.nnzU() > 0 42 | 43 | L = splu.matrixL() 44 | U = splu.matrixU() 45 | 46 | assert L.rows() == dim 47 | assert L.cols() == dim 48 | assert U.rows() == dim 49 | assert U.cols() == dim 50 | 51 | x_true = rng.random(dim) 52 | b_true = A.dot(x_true) 53 | P_rows_indices = splu.rowsPermutation().indices() 54 | P_cols_indices = splu.colsPermutation().indices() 55 | 56 | b_permuted = b_true[P_rows_indices] 57 | z = b_permuted.copy() 58 | L.solveInPlace(z) 59 | y = z.copy() 60 | U.solveInPlace(y) 61 | x_reconstructed = np.zeros(dim) 62 | x_reconstructed[P_cols_indices] = y 63 | 64 | assert eigenpy.is_approx(x_reconstructed, x_true, prec=1e-6) 65 | -------------------------------------------------------------------------------- /include/eigenpy/geometry-conversion.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2019, CNRS 3 | * Copyright 2018-2023, INRIA 4 | */ 5 | 6 | #ifndef __eigenpy_geometry_conversion_hpp__ 7 | #define __eigenpy_geometry_conversion_hpp__ 8 | 9 | #include "eigenpy/fwd.hpp" 10 | 11 | namespace eigenpy { 12 | 13 | template 14 | struct EulerAnglesConvertor { 15 | typedef typename Eigen::Matrix Vector3; 16 | typedef typename Eigen::Matrix Matrix3; 17 | typedef typename Vector3::Index Index; 18 | 19 | typedef typename Eigen::AngleAxis AngleAxis; 20 | 21 | static void expose() { 22 | bp::def("toEulerAngles", &EulerAnglesConvertor::toEulerAngles, 23 | bp::args("rotation_matrix", "a0", "a1", "a2"), 24 | "It returns the Euler-angles of the rotation matrix mat using the " 25 | "convention defined by the triplet (a0,a1,a2)."); 26 | 27 | bp::def("fromEulerAngles", &EulerAnglesConvertor::fromEulerAngles, 28 | bp::args("euler_angles", "a0", "a1", "a2"), 29 | "It returns the rotation matrix associated to the Euler angles " 30 | "using the convention defined by the triplet (a0,a1,a2)."); 31 | } 32 | 33 | static Vector3 toEulerAngles(const Matrix3& mat, Index a0, Index a1, 34 | Index a2) { 35 | return mat.eulerAngles(a0, a1, a2); 36 | } 37 | 38 | static Matrix3 fromEulerAngles(const Vector3& ea, Index a0, Index a1, 39 | Index a2) { 40 | Matrix3 mat; 41 | mat = AngleAxis(ea[0], Vector3::Unit(a0)) * 42 | AngleAxis(ea[1], Vector3::Unit(a1)) * 43 | AngleAxis(ea[2], Vector3::Unit(a2)); 44 | return mat; 45 | } 46 | }; 47 | 48 | } // namespace eigenpy 49 | 50 | #endif // define __eigenpy_geometry_conversion_hpp__ 51 | -------------------------------------------------------------------------------- /include/eigenpy/utils/traits.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2024 INRIA 3 | // 4 | // 5 | 6 | #ifndef __eigenpy_utils_traits_hpp__ 7 | #define __eigenpy_utils_traits_hpp__ 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | namespace eigenpy { 14 | 15 | namespace details { 16 | 17 | /// Trait to remove const& 18 | template 19 | struct remove_cvref : std::remove_cv::type> { 20 | }; 21 | 22 | /// Trait to detect if T is a class or an union 23 | template 24 | struct is_class_or_union 25 | : std::integral_constant::value || 26 | std::is_union::value> {}; 27 | 28 | /// trait to detect if T is a std::complex managed by Boost Python 29 | template 30 | struct is_python_complex : std::false_type {}; 31 | 32 | /// From boost/python/converter/builtin_converters 33 | template <> 34 | struct is_python_complex> : std::true_type {}; 35 | template <> 36 | struct is_python_complex> : std::true_type {}; 37 | template <> 38 | struct is_python_complex> : std::true_type {}; 39 | 40 | template 41 | struct is_python_primitive_type_helper 42 | : std::integral_constant::value || 43 | std::is_same::value || 44 | std::is_same::value || 45 | is_python_complex::value> {}; 46 | 47 | /// Trait to detect if T is a Python primitive type 48 | template 49 | struct is_python_primitive_type 50 | : is_python_primitive_type_helper::type> {}; 51 | 52 | } // namespace details 53 | 54 | } // namespace eigenpy 55 | 56 | #endif // ifndef __eigenpy_utils_traits_hpp__ 57 | -------------------------------------------------------------------------------- /.github/workflows/ros_ci.yml: -------------------------------------------------------------------------------- 1 | # This config uses industrial_ci (https://github.com/ros-industrial/industrial_ci.git). 2 | # For troubleshooting, see readme (https://github.com/ros-industrial/industrial_ci/blob/master/README.rst) 3 | 4 | name: CI - Linux via ROS 5 | 6 | # This determines when this workflow is run 7 | on: 8 | push: 9 | branches: 10 | - devel 11 | paths-ignore: 12 | - 'doc/**' 13 | - '.gitlab-ci.yml' 14 | - '.gitignore' 15 | - '*.md' 16 | - 'LICENSE' 17 | - 'colcon.pkg' 18 | - '.pre-commit-config.yaml' 19 | pull_request: 20 | paths-ignore: 21 | - 'doc/**' 22 | - '.gitlab-ci.yml' 23 | - '.gitignore' 24 | - '*.md' 25 | - 'LICENSE' 26 | - 'colcon.pkg' 27 | - '.pre-commit-config.yaml' 28 | concurrency: 29 | group: ${{ github.workflow }}-${{ github.ref }} 30 | cancel-in-progress: true 31 | 32 | jobs: 33 | CI: 34 | strategy: 35 | fail-fast: false 36 | matrix: 37 | env: 38 | - {ROS_DISTRO: humble} 39 | - {ROS_DISTRO: jazzy} 40 | - {ROS_DISTRO: kilted} 41 | - {ROS_DISTRO: rolling} 42 | env: 43 | # PRERELEASE: true # Fails due to issues in the underlying Docker image 44 | BUILDER: colcon 45 | runs-on: ubuntu-latest 46 | steps: 47 | - uses: actions/checkout@v6 48 | with: 49 | submodules: recursive 50 | # Run industrial_ci 51 | - uses: 'ros-industrial/industrial_ci@eb3ea328ff056aa2435d5b3493e7e2929c310f8e' 52 | env: ${{ matrix.env }} 53 | 54 | check: 55 | if: always() 56 | name: check-ros-ci 57 | 58 | needs: 59 | - CI 60 | 61 | runs-on: Ubuntu-latest 62 | 63 | steps: 64 | - name: Decide whether the needed jobs succeeded or failed 65 | uses: re-actors/alls-green@release/v1 66 | with: 67 | jobs: ${{ toJSON(needs) }} 68 | -------------------------------------------------------------------------------- /unittest/python/test_HessenbergDecomposition.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | import eigenpy 4 | 5 | dim = 100 6 | rng = np.random.default_rng() 7 | A = rng.random((dim, dim)) 8 | 9 | hess = eigenpy.HessenbergDecomposition(A) 10 | 11 | Q = hess.matrixQ() 12 | H = hess.matrixH() 13 | 14 | if np.iscomplexobj(A): 15 | A_reconstructed = Q @ H @ Q.conj().T 16 | else: 17 | A_reconstructed = Q @ H @ Q.T 18 | assert eigenpy.is_approx(A, A_reconstructed) 19 | 20 | for row in range(2, dim): 21 | for col in range(row - 1): 22 | assert abs(H[row, col]) < 1e-12 23 | 24 | if np.iscomplexobj(Q): 25 | QQ_conj = Q @ Q.conj().T 26 | else: 27 | QQ_conj = Q @ Q.T 28 | assert eigenpy.is_approx(QQ_conj, np.eye(dim)) 29 | 30 | A_test = rng.random((dim, dim)) 31 | hess1 = eigenpy.HessenbergDecomposition(dim) 32 | hess1.compute(A_test) 33 | hess2 = eigenpy.HessenbergDecomposition(A_test) 34 | 35 | H1 = hess1.matrixH() 36 | H2 = hess2.matrixH() 37 | Q1 = hess1.matrixQ() 38 | Q2 = hess2.matrixQ() 39 | 40 | assert eigenpy.is_approx(H1, H2) 41 | assert eigenpy.is_approx(Q1, Q2) 42 | 43 | hCoeffs = hess.householderCoefficients() 44 | packed = hess.packedMatrix() 45 | 46 | assert hCoeffs.shape == (dim - 1,) 47 | assert packed.shape == (dim, dim) 48 | 49 | for i in range(dim): 50 | for j in range(i - 1, dim): 51 | if j >= 0: 52 | assert abs(H[i, j] - packed[i, j]) < 1e-12 53 | 54 | hess_default = eigenpy.HessenbergDecomposition(dim) 55 | hess_matrix = eigenpy.HessenbergDecomposition(A) 56 | 57 | hess1_id = eigenpy.HessenbergDecomposition(dim) 58 | hess2_id = eigenpy.HessenbergDecomposition(dim) 59 | id1 = hess1_id.id() 60 | id2 = hess2_id.id() 61 | assert id1 != id2 62 | assert id1 == hess1_id.id() 63 | assert id2 == hess2_id.id() 64 | 65 | hess3_id = eigenpy.HessenbergDecomposition(A) 66 | hess4_id = eigenpy.HessenbergDecomposition(A) 67 | id3 = hess3_id.id() 68 | id4 = hess4_id.id() 69 | assert id3 != id4 70 | assert id3 == hess3_id.id() 71 | assert id4 == hess4_id.id() 72 | -------------------------------------------------------------------------------- /unittest/python/test_IncompleteCholesky.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from scipy.sparse import csc_matrix 3 | 4 | import eigenpy 5 | 6 | dim = 100 7 | rng = np.random.default_rng() 8 | 9 | A = rng.random((dim, dim)) 10 | A = (A + A.T) * 0.5 + np.diag(5.0 + rng.random(dim)) 11 | A = csc_matrix(A) 12 | 13 | ichol = eigenpy.solvers.IncompleteCholesky(A) 14 | assert ichol.info() == eigenpy.ComputationInfo.Success 15 | assert ichol.rows() == dim 16 | assert ichol.cols() == dim 17 | 18 | X = rng.random((dim, 20)) 19 | B = A.dot(X) 20 | X_est = ichol.solve(B) 21 | assert isinstance(X_est, np.ndarray) 22 | residual = np.linalg.norm(B - A.dot(X_est)) / np.linalg.norm(B) 23 | assert residual < 0.1 24 | 25 | x = rng.random(dim) 26 | b = A.dot(x) 27 | x_est = ichol.solve(b) 28 | assert isinstance(x_est, np.ndarray) 29 | residual = np.linalg.norm(b - A.dot(x_est)) / np.linalg.norm(b) 30 | assert residual < 0.1 31 | 32 | X_sparse = csc_matrix(rng.random((dim, 10))) 33 | B_sparse = A.dot(X_sparse).tocsc() 34 | if not B_sparse.has_sorted_indices: 35 | B_sparse.sort_indices() 36 | X_est_sparse = ichol.solve(B_sparse) 37 | assert isinstance(X_est_sparse, csc_matrix) 38 | 39 | ichol.analyzePattern(A) 40 | ichol.factorize(A) 41 | ichol.compute(A) 42 | assert ichol.info() == eigenpy.ComputationInfo.Success 43 | 44 | L = ichol.matrixL() 45 | S_diag = ichol.scalingS() 46 | perm = ichol.permutationP() 47 | P = perm.toDenseMatrix() 48 | 49 | assert isinstance(L, csc_matrix) 50 | assert isinstance(S_diag, np.ndarray) 51 | assert L.shape == (dim, dim) 52 | assert S_diag.shape == (dim,) 53 | 54 | L_dense = L.toarray() 55 | upper_part = np.triu(L_dense, k=1) 56 | assert np.allclose(upper_part, 0, atol=1e-12) 57 | 58 | assert np.all(S_diag > 0) 59 | 60 | S = csc_matrix((S_diag, (range(dim), range(dim))), shape=(dim, dim)) 61 | 62 | PA = P @ A 63 | PAP = PA @ P.T 64 | SPAP = S @ PAP 65 | SPAPS = SPAP @ S 66 | 67 | LLT = L @ L.T 68 | 69 | diff = SPAPS - LLT 70 | relative_error = np.linalg.norm(diff.data) / np.linalg.norm(SPAPS.data) 71 | assert relative_error < 0.5 72 | -------------------------------------------------------------------------------- /include/eigenpy/decompositions/sparse/SparseSolverBase.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 INRIA 3 | */ 4 | 5 | #ifndef __eigenpy_decompositions_sparse_sparse_solver_base_hpp__ 6 | #define __eigenpy_decompositions_sparse_sparse_solver_base_hpp__ 7 | 8 | #include "eigenpy/eigenpy.hpp" 9 | #include "eigenpy/eigen/EigenBase.hpp" 10 | 11 | #include 12 | 13 | namespace eigenpy { 14 | 15 | template 16 | struct SparseSolverBaseVisitor 17 | : public boost::python::def_visitor< 18 | SparseSolverBaseVisitor> { 19 | typedef SimplicialDerived Solver; 20 | 21 | typedef typename SimplicialDerived::MatrixType MatrixType; 22 | typedef typename MatrixType::Scalar Scalar; 23 | typedef typename MatrixType::RealScalar RealScalar; 24 | 25 | typedef Eigen::Matrix 26 | DenseVectorXs; 27 | typedef Eigen::Matrix 29 | DenseMatrixXs; 30 | 31 | template 32 | void visit(PyClass& cl) const { 33 | cl.def("solve", &solve, bp::args("self", "b"), 34 | "Returns the solution x of A x = b using the current " 35 | "decomposition of A.") 36 | .def("solve", &solve, bp::args("self", "B"), 37 | "Returns the solution X of A X = B using the current " 38 | "decomposition of A where B is a right hand side matrix.") 39 | 40 | .def("solve", &solve, bp::args("self", "B"), 41 | "Returns the solution X of A X = B using the current " 42 | "decomposition of A where B is a right hand side matrix."); 43 | } 44 | 45 | private: 46 | template 47 | static MatrixOrVector solve(const Solver& self, const MatrixOrVector& vec) { 48 | return self.solve(vec); 49 | } 50 | }; 51 | 52 | } // namespace eigenpy 53 | 54 | #endif // ifndef __eigenpy_decompositions_sparse_sparse_solver_base_hpp__ 55 | -------------------------------------------------------------------------------- /include/eigenpy/eigen-typedef.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2020-2023 INRIA 3 | // 4 | 5 | #ifndef __eigenpy_eigen_typedef_hpp__ 6 | #define __eigenpy_eigen_typedef_hpp__ 7 | 8 | #include "eigenpy/fwd.hpp" 9 | 10 | #define EIGENPY_MAKE_TYPEDEFS(Type, Options, TypeSuffix, Size, SizeSuffix) \ 11 | /** \ingroup matrixtypedefs */ \ 12 | typedef Eigen::Matrix \ 13 | Matrix##SizeSuffix##TypeSuffix; \ 14 | /** \ingroup matrixtypedefs */ \ 15 | typedef Eigen::Matrix Vector##SizeSuffix##TypeSuffix; \ 16 | /** \ingroup matrixtypedefs */ \ 17 | typedef Eigen::Matrix RowVector##SizeSuffix##TypeSuffix; 18 | 19 | #define EIGENPY_MAKE_FIXED_TYPEDEFS(Type, Options, TypeSuffix, Size) \ 20 | /** \ingroup matrixtypedefs */ \ 21 | typedef Eigen::Matrix \ 22 | Matrix##Size##X##TypeSuffix; \ 23 | /** \ingroup matrixtypedefs */ \ 24 | typedef Eigen::Matrix \ 25 | Matrix##X##Size##TypeSuffix; 26 | 27 | #define EIGENPY_MAKE_TYPEDEFS_ALL_SIZES(Type, Options, TypeSuffix) \ 28 | EIGENPY_MAKE_TYPEDEFS(Type, Options, TypeSuffix, 2, 2) \ 29 | EIGENPY_MAKE_TYPEDEFS(Type, Options, TypeSuffix, 3, 3) \ 30 | EIGENPY_MAKE_TYPEDEFS(Type, Options, TypeSuffix, 4, 4) \ 31 | EIGENPY_MAKE_TYPEDEFS(Type, Options, TypeSuffix, Eigen::Dynamic, X) \ 32 | EIGENPY_MAKE_FIXED_TYPEDEFS(Type, Options, TypeSuffix, 2) \ 33 | EIGENPY_MAKE_FIXED_TYPEDEFS(Type, Options, TypeSuffix, 3) \ 34 | EIGENPY_MAKE_FIXED_TYPEDEFS(Type, Options, TypeSuffix, 4) \ 35 | EIGENPY_MAKE_TYPEDEFS(Type, Options, TypeSuffix, 1, 1) \ 36 | typedef Eigen::SparseMatrix SparseMatrixX##TypeSuffix 37 | 38 | #endif // ifndef __eigenpy_eigen_typedef_hpp__ 39 | -------------------------------------------------------------------------------- /include/eigenpy/scipy-type.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 INRIA 3 | */ 4 | 5 | #ifndef __eigenpy_scipy_type_hpp__ 6 | #define __eigenpy_scipy_type_hpp__ 7 | 8 | #include "eigenpy/fwd.hpp" 9 | #include "eigenpy/register.hpp" 10 | #include "eigenpy/scalar-conversion.hpp" 11 | #include "eigenpy/numpy-type.hpp" 12 | 13 | namespace eigenpy { 14 | 15 | struct EIGENPY_DLLAPI ScipyType { 16 | static ScipyType& getInstance(); 17 | 18 | static void sharedMemory(const bool value); 19 | 20 | static bool sharedMemory(); 21 | 22 | static bp::object getScipyType(); 23 | 24 | static const PyTypeObject* getScipyCSRMatrixType(); 25 | static const PyTypeObject* getScipyCSCMatrixType(); 26 | 27 | template 28 | static bp::object get_pytype_object( 29 | const Eigen::SparseMatrixBase* ptr = nullptr) { 30 | EIGENPY_UNUSED_VARIABLE(ptr); 31 | return SparseMatrix::IsRowMajor ? getInstance().csr_matrix_obj 32 | : getInstance().csc_matrix_obj; 33 | } 34 | 35 | template 36 | static PyTypeObject const* get_pytype( 37 | const Eigen::SparseMatrixBase* ptr = nullptr) { 38 | EIGENPY_UNUSED_VARIABLE(ptr); 39 | return SparseMatrix::IsRowMajor ? getInstance().csr_matrix_type 40 | : getInstance().csc_matrix_type; 41 | } 42 | 43 | static int get_numpy_type_num(const bp::object& obj) { 44 | const PyTypeObject* type = Py_TYPE(obj.ptr()); 45 | EIGENPY_USED_VARIABLE_ONLY_IN_DEBUG_MODE(type); 46 | assert(type == getInstance().csr_matrix_type || 47 | type == getInstance().csc_matrix_type); 48 | 49 | bp::object dtype = obj.attr("dtype"); 50 | 51 | const PyArray_Descr* npy_type = 52 | reinterpret_cast(dtype.ptr()); 53 | return npy_type->type_num; 54 | } 55 | 56 | protected: 57 | ScipyType(); 58 | 59 | bp::object sparse_module; 60 | 61 | // SciPy types 62 | bp::object csr_matrix_obj, csc_matrix_obj; 63 | PyTypeObject *csr_matrix_type, *csc_matrix_type; 64 | 65 | bool shared_memory; 66 | }; 67 | } // namespace eigenpy 68 | 69 | #endif // ifndef __eigenpy_scipy_type_hpp__ 70 | -------------------------------------------------------------------------------- /unittest/python/test_PartialPivLU.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | import eigenpy 4 | 5 | dim = 100 6 | rng = np.random.default_rng() 7 | A = rng.random((dim, dim)) 8 | A = (A + A.T) * 0.5 + np.diag(10.0 + rng.random(dim)) 9 | partialpivlu = eigenpy.PartialPivLU(A) 10 | 11 | X = rng.random((dim, 20)) 12 | B = A.dot(X) 13 | X_est = partialpivlu.solve(B) 14 | assert eigenpy.is_approx(X, X_est) 15 | assert eigenpy.is_approx(A.dot(X_est), B) 16 | 17 | x = rng.random(dim) 18 | b = A.dot(x) 19 | x_est = partialpivlu.solve(b) 20 | assert eigenpy.is_approx(x, x_est) 21 | assert eigenpy.is_approx(A.dot(x_est), b) 22 | 23 | rows = partialpivlu.rows() 24 | cols = partialpivlu.cols() 25 | assert cols == dim 26 | assert rows == dim 27 | 28 | partialpivlu_compute = partialpivlu.compute(A) 29 | A_reconstructed = partialpivlu.reconstructedMatrix() 30 | assert eigenpy.is_approx(A_reconstructed, A) 31 | 32 | LU = partialpivlu.matrixLU() 33 | P_perm = partialpivlu.permutationP() 34 | P = P_perm.toDenseMatrix() 35 | 36 | U = np.triu(LU) 37 | L = np.eye(dim) + np.tril(LU, -1) 38 | assert eigenpy.is_approx(P @ A, L @ U) 39 | 40 | inverse = partialpivlu.inverse() 41 | assert eigenpy.is_approx(A @ inverse, np.eye(dim)) 42 | assert eigenpy.is_approx(inverse @ A, np.eye(dim)) 43 | 44 | rcond = partialpivlu.rcond() 45 | determinant = partialpivlu.determinant() 46 | det_numpy = np.linalg.det(A) 47 | assert rcond > 0 48 | assert abs(determinant - det_numpy) / abs(det_numpy) < 1e-10 49 | 50 | P_inv = P_perm.inverse().toDenseMatrix() 51 | assert eigenpy.is_approx(P @ P_inv, np.eye(dim)) 52 | assert eigenpy.is_approx(P_inv @ P, np.eye(dim)) 53 | 54 | decomp1 = eigenpy.PartialPivLU() 55 | decomp2 = eigenpy.PartialPivLU() 56 | id1 = decomp1.id() 57 | id2 = decomp2.id() 58 | assert id1 != id2 59 | assert id1 == decomp1.id() 60 | assert id2 == decomp2.id() 61 | 62 | decomp3 = eigenpy.PartialPivLU(dim) 63 | decomp4 = eigenpy.PartialPivLU(dim) 64 | id3 = decomp3.id() 65 | id4 = decomp4.id() 66 | assert id3 != id4 67 | assert id3 == decomp3.id() 68 | assert id4 == decomp4.id() 69 | 70 | decomp5 = eigenpy.PartialPivLU(A) 71 | decomp6 = eigenpy.PartialPivLU(A) 72 | id5 = decomp5.id() 73 | id6 = decomp6.id() 74 | assert id5 != id6 75 | assert id5 == decomp5.id() 76 | assert id6 == decomp6.id() 77 | -------------------------------------------------------------------------------- /include/eigenpy/std-pair.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2023 INRIA 3 | // 4 | 5 | #ifndef __eigenpy_utils_std_pair_hpp__ 6 | #define __eigenpy_utils_std_pair_hpp__ 7 | 8 | #include 9 | #include 10 | 11 | namespace eigenpy { 12 | 13 | template 14 | struct StdPairConverter { 15 | typedef typename pair_type::first_type T1; 16 | typedef typename pair_type::second_type T2; 17 | 18 | static PyObject* convert(const pair_type& pair) { 19 | return boost::python::incref( 20 | boost::python::make_tuple(pair.first, pair.second).ptr()); 21 | } 22 | 23 | static void* convertible(PyObject* obj) { 24 | if (!PyTuple_CheckExact(obj)) return 0; 25 | if (PyTuple_Size(obj) != 2) return 0; 26 | { 27 | boost::python::tuple tuple(boost::python::borrowed(obj)); 28 | boost::python::extract elt1(tuple[0]); 29 | if (!elt1.check()) return 0; 30 | boost::python::extract elt2(tuple[1]); 31 | if (!elt2.check()) return 0; 32 | } 33 | return obj; 34 | } 35 | 36 | static void construct( 37 | PyObject* obj, 38 | boost::python::converter::rvalue_from_python_stage1_data* memory) { 39 | boost::python::tuple tuple(boost::python::borrowed(obj)); 40 | void* storage = 41 | reinterpret_cast< 42 | boost::python::converter::rvalue_from_python_storage*>( 43 | reinterpret_cast(memory)) 44 | ->storage.bytes; 45 | new (storage) pair_type(boost::python::extract(tuple[0]), 46 | boost::python::extract(tuple[1])); 47 | memory->convertible = storage; 48 | } 49 | 50 | static PyTypeObject const* get_pytype() { 51 | PyTypeObject const* py_type = &PyTuple_Type; 52 | return py_type; 53 | } 54 | 55 | static void registration() { 56 | boost::python::converter::registry::push_back( 57 | &convertible, &construct, boost::python::type_id() 58 | #ifndef BOOST_PYTHON_NO_PY_SIGNATURES 59 | , 60 | get_pytype 61 | #endif 62 | ); 63 | boost::python::to_python_converter(); 64 | } 65 | }; 66 | 67 | } // namespace eigenpy 68 | 69 | #endif // ifndef __eigenpy_utils_std_pair_hpp__ 70 | -------------------------------------------------------------------------------- /src/decompositions/accelerate.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 INRIA 3 | */ 4 | 5 | #include "eigenpy/fwd.hpp" 6 | #include "eigenpy/decompositions/decompositions.hpp" 7 | 8 | #include "eigenpy/decompositions/sparse/accelerate/Accelerate.hpp" 9 | 10 | namespace eigenpy { 11 | 12 | void exposeAccelerate() { 13 | using namespace Eigen; 14 | 15 | typedef Eigen::SparseMatrix ColMajorSparseMatrix; 16 | // typedef Eigen::SparseMatrix RowMajorSparseMatrix; 17 | 18 | bp::enum_("SparseOrder") 19 | .value("SparseOrderUser", SparseOrderUser) 20 | .value("SparseOrderAMD", SparseOrderAMD) 21 | .value("SparseOrderMetis", SparseOrderMetis) 22 | .value("SparseOrderCOLAMD", SparseOrderCOLAMD); 23 | 24 | #define EXPOSE_ACCELERATE_DECOMPOSITION(name, doc) \ 25 | AccelerateImplVisitor>::expose( \ 26 | EIGENPY_STRINGIZE(name), doc) 27 | 28 | EXPOSE_ACCELERATE_DECOMPOSITION( 29 | AccelerateLLT, 30 | "A direct Cholesky (LLT) factorization and solver based on Accelerate."); 31 | EXPOSE_ACCELERATE_DECOMPOSITION(AccelerateLDLT, 32 | "The default Cholesky (LDLT) factorization " 33 | "and solver based on Accelerate."); 34 | EXPOSE_ACCELERATE_DECOMPOSITION( 35 | AccelerateLDLTUnpivoted, 36 | "A direct Cholesky-like LDL^T factorization and solver based on " 37 | "Accelerate with only 1x1 pivots and no pivoting."); 38 | EXPOSE_ACCELERATE_DECOMPOSITION( 39 | AccelerateLDLTSBK, 40 | "A direct Cholesky (LDLT) factorization and solver based on Accelerate " 41 | "with Supernode Bunch-Kaufman and static pivoting."); 42 | EXPOSE_ACCELERATE_DECOMPOSITION( 43 | AccelerateLDLTTPP, 44 | "A direct Cholesky (LDLT) factorization and solver based on Accelerate " 45 | "with full threshold partial pivoting."); 46 | EXPOSE_ACCELERATE_DECOMPOSITION( 47 | AccelerateQR, "A QR factorization and solver based on Accelerate."); 48 | EXPOSE_ACCELERATE_DECOMPOSITION( 49 | AccelerateCholeskyAtA, 50 | "A QR factorization and solver based on Accelerate without storing Q " 51 | "(equivalent to A^TA = R^T R)."); 52 | } 53 | } // namespace eigenpy 54 | -------------------------------------------------------------------------------- /.github/workflows/reloc.yml: -------------------------------------------------------------------------------- 1 | name: CI - Ensure relocatable 2 | 3 | on: 4 | push: 5 | branches: 6 | - devel 7 | paths-ignore: 8 | - 'doc/**' 9 | - '.gitlab-ci.yml' 10 | - '.gitignore' 11 | - '*.md' 12 | - 'LICENSE' 13 | - 'colcon.pkg' 14 | - '.pre-commit-config.yaml' 15 | pull_request: 16 | paths-ignore: 17 | - 'doc/**' 18 | - '.gitlab-ci.yml' 19 | - '.gitignore' 20 | - '*.md' 21 | - 'LICENSE' 22 | - 'colcon.pkg' 23 | - '.pre-commit-config.yaml' 24 | concurrency: 25 | group: ${{ github.workflow }}-${{ github.ref }} 26 | cancel-in-progress: true 27 | 28 | jobs: 29 | relocatable: 30 | name: Ensure relocatable 31 | runs-on: ubuntu-latest 32 | env: 33 | CCACHE_DIR: /github/home/.ccache # Enable ccache 34 | CMAKE_CXX_COMPILER_LAUNCHER: ccache 35 | 36 | steps: 37 | - uses: actions/checkout@v6 38 | with: 39 | submodules: recursive 40 | 41 | - uses: actions/cache@v4 42 | with: 43 | path: ${{ env.CCACHE_DIR }} 44 | key: reloc 45 | 46 | - name: prepare cache dir 47 | run: sudo mkdir -p ${CCACHE_DIR}/tmp && sudo chown -R $(id -un) ${CCACHE_DIR} 48 | 49 | - name: prepare work prefix 50 | run: sudo mkdir -p /RELOC/SRC && sudo chown -R $(id -un) /RELOC 51 | 52 | - name: clone in /RELOC/SRC 53 | run: git -C /RELOC/SRC clone --recursive $(pwd) 54 | 55 | - name: install dependencies 56 | run: sudo apt install libboost-all-dev libeigen3-dev python-is-python3 python3-numpy python3-pip python3-scipy ccache 57 | 58 | - name: update CMake 59 | run: pip install -U pip && pip install -U cmake 60 | 61 | - name: configure in /RELOC/BLD 62 | run: cmake -S /RELOC/SRC/eigenpy -B /RELOC/BLD -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/RELOC/PFX 63 | 64 | - name: build in /RELOC/BLD 65 | run: cmake --build /RELOC/BLD -j2 66 | 67 | - name: install in /RELOC/PFX 68 | run: cmake --build /RELOC/BLD -t install 69 | 70 | - name: check installed package doesn't contain references to RELOC 71 | run: grep -r RELOC /RELOC/PFX | grep -v Binary | tee references 72 | 73 | - name: exit accordingly 74 | run: exit $(wc -l references | cut -d " " -f 1) 75 | -------------------------------------------------------------------------------- /unittest/python/test_bind_virtual.py: -------------------------------------------------------------------------------- 1 | import bind_virtual_factory as bvf 2 | 3 | 4 | class ImplClass(bvf.MyVirtualClass): 5 | def __init__(self): 6 | self.val = 42 7 | bvf.MyVirtualClass.__init__(self) 8 | 9 | def createData(self): 10 | return ImplData(self) 11 | 12 | # override MyVirtualClass::doSomethingPtr(shared_ptr data) 13 | def doSomethingPtr(self, data): 14 | print("Hello from doSomething!") 15 | assert isinstance(data, ImplData) 16 | print("Data value:", data.value) 17 | data.value += 1 18 | 19 | # override MyVirtualClass::doSomethingPtr(data&) 20 | def doSomethingRef(self, data): 21 | print("Hello from doSomethingRef!") 22 | print(type(data)) 23 | assert isinstance(data, ImplData) 24 | print("Data value:", data.value) 25 | data.value += 1 26 | 27 | 28 | class ImplData(bvf.MyVirtualData): 29 | def __init__(self, c): 30 | # parent virtual class requires arg 31 | bvf.MyVirtualData.__init__(self, c) 32 | self.value = c.val 33 | 34 | 35 | def test_instantiate_child(): 36 | obj = ImplClass() 37 | data = obj.createData() 38 | print(data) 39 | 40 | 41 | def test_call_do_something_ptr(): 42 | obj = ImplClass() 43 | print("Calling doSomething (by ptr)") 44 | d1 = bvf.callDoSomethingPtr(obj) 45 | print("Output data.value:", d1.value) 46 | 47 | 48 | def test_call_do_something_ref(): 49 | obj = ImplClass() 50 | print("Ref variant:") 51 | d2 = bvf.callDoSomethingRef(obj) 52 | print(d2.value) 53 | print("-----") 54 | 55 | 56 | def test_iden_fns(): 57 | obj = ImplClass() 58 | d = obj.createData() 59 | print(d, type(d)) 60 | 61 | # take and return const T& 62 | d1 = bvf.iden_ref(d) 63 | print(d1, type(d1)) 64 | assert isinstance(d1, ImplData) 65 | 66 | # take a shared_ptr, return const T& 67 | d2 = bvf.iden_shared(d) 68 | assert isinstance(d2, ImplData) 69 | print(d2, type(d2)) 70 | 71 | print("copy shared ptr -> py -> cpp") 72 | d3 = bvf.copy_shared(d) 73 | assert isinstance(d3, ImplData) 74 | print(d3, type(d3)) 75 | 76 | 77 | test_instantiate_child() 78 | test_call_do_something_ptr() 79 | test_call_do_something_ref() 80 | test_iden_fns() 81 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Bindings between Numpy and Eigen using Boost.Python"; 3 | 4 | inputs = { 5 | flake-parts.url = "github:hercules-ci/flake-parts"; 6 | nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; 7 | }; 8 | 9 | outputs = 10 | inputs: 11 | inputs.flake-parts.lib.mkFlake { inherit inputs; } { 12 | systems = inputs.nixpkgs.lib.systems.flakeExposed; 13 | perSystem = 14 | { pkgs, self', ... }: 15 | { 16 | apps.default = { 17 | type = "app"; 18 | program = pkgs.python3.withPackages (_: [ self'.packages.default ]); 19 | }; 20 | devShells.default = pkgs.mkShell { inputsFrom = [ self'.packages.default ]; }; 21 | packages = { 22 | default = self'.packages.eigenpy-eigen5; 23 | eigen3 = pkgs.eigen.overrideAttrs (super: rec { 24 | version = "3.4.1"; 25 | src = pkgs.fetchFromGitLab { 26 | inherit (super.src) owner repo; 27 | tag = version; 28 | hash = "sha256-NSq1tUfy2thz5gtsyASsKeYE4vMf71aSG4uXfrX86rk="; 29 | }; 30 | patches = [ ]; 31 | postPatch = ""; 32 | }); 33 | eigen5 = self'.packages.eigen3.overrideAttrs (super: rec { 34 | version = "5.0.0"; 35 | src = pkgs.fetchFromGitLab { 36 | inherit (super.src) owner repo; 37 | tag = version; 38 | hash = "sha256-L1KUFZsaibC/FD6abTXrT3pvaFhbYnw+GaWsxM2gaxM="; 39 | }; 40 | }); 41 | eigenpy-eigen3 = 42 | (pkgs.python3Packages.eigenpy.override { eigen = self'.packages.eigen3; }).overrideAttrs 43 | (_: { 44 | src = pkgs.lib.fileset.toSource { 45 | root = ./.; 46 | fileset = pkgs.lib.fileset.unions [ 47 | ./CMakeLists.txt 48 | ./doc 49 | ./include 50 | ./package.xml 51 | ./python 52 | ./src 53 | ./unittest 54 | ]; 55 | }; 56 | }); 57 | eigenpy-eigen5 = self'.packages.eigenpy-eigen3.override { eigen = self'.packages.eigen5; }; 58 | }; 59 | }; 60 | }; 61 | } 62 | -------------------------------------------------------------------------------- /include/eigenpy/stride.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2019, CNRS 3 | * Copyright 2018-2023, INRIA 4 | */ 5 | 6 | #ifndef __eigenpy_stride_hpp__ 7 | #define __eigenpy_stride_hpp__ 8 | 9 | #include 10 | 11 | namespace eigenpy { 12 | 13 | template 15 | struct stride_type_matrix { 16 | typedef Eigen::Stride type; 17 | }; 18 | 19 | template 20 | struct stride_type_matrix { 21 | typedef Eigen::InnerStride type; 22 | }; 23 | 24 | template ::type> 26 | struct stride_type; 27 | 28 | template 29 | struct stride_type> { 31 | typedef 32 | typename stride_type_matrix::type 33 | type; 34 | }; 35 | 36 | template 37 | struct stride_type> { 39 | typedef typename stride_type_matrix::type type; 41 | }; 42 | 43 | #ifdef EIGENPY_WITH_TENSOR_SUPPORT 44 | template 45 | struct stride_type> { 47 | typedef Eigen::Stride type; 48 | }; 49 | 50 | template 51 | struct stride_type> { 53 | typedef Eigen::Stride type; 54 | }; 55 | #endif 56 | 57 | template 59 | struct StrideType { 60 | typedef typename stride_type::type type; 61 | }; 62 | 63 | } // namespace eigenpy 64 | 65 | #endif // ifndef __eigenpy_stride_hpp__ 66 | -------------------------------------------------------------------------------- /include/eigenpy/decompositions/sparse/cholmod/CholmodSupernodalLLT.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 INRIA 3 | */ 4 | 5 | #ifndef __eigenpy_decomposition_sparse_cholmod_cholmod_supernodal_llt_hpp__ 6 | #define __eigenpy_decomposition_sparse_cholmod_cholmod_supernodal_llt_hpp__ 7 | 8 | #include "eigenpy/eigenpy.hpp" 9 | #include "eigenpy/decompositions/sparse/cholmod/CholmodDecomposition.hpp" 10 | #include "eigenpy/utils/scalar-name.hpp" 11 | 12 | namespace eigenpy { 13 | 14 | template 15 | struct CholmodSupernodalLLTVisitor 16 | : public boost::python::def_visitor< 17 | CholmodSupernodalLLTVisitor> { 18 | typedef MatrixType_ MatrixType; 19 | typedef typename MatrixType::Scalar Scalar; 20 | typedef typename MatrixType::RealScalar RealScalar; 21 | 22 | typedef Eigen::CholmodSupernodalLLT Solver; 23 | 24 | template 25 | void visit(PyClass& cl) const { 26 | cl 27 | 28 | .def(CholmodBaseVisitor()) 29 | .def(bp::init<>(bp::arg("self"), "Default constructor")) 30 | .def(bp::init(bp::args("self", "matrix"), 31 | "Constructs and performs the LLT " 32 | "factorization from a given matrix.")) 33 | 34 | ; 35 | } 36 | 37 | static void expose() { 38 | static const std::string classname = 39 | "CholmodSupernodalLLT_" + scalar_name::shortname(); 40 | expose(classname); 41 | } 42 | 43 | static void expose(const std::string& name) { 44 | bp::class_( 45 | name.c_str(), 46 | "A supernodal direct Cholesky (LLT) factorization and solver based on " 47 | "Cholmod.\n\n" 48 | "This class allows to solve for A.X = B sparse linear problems via a " 49 | "supernodal LL^T Cholesky factorization using the Cholmod library." 50 | "This supernodal variant performs best on dense enough problems, e.g., " 51 | "3D FEM, or very high order 2D FEM." 52 | "The sparse matrix A must be selfadjoint and positive definite. The " 53 | "vectors or matrices X and B can be either dense or sparse.", 54 | bp::no_init) 55 | .def(CholmodSupernodalLLTVisitor()); 56 | } 57 | }; 58 | 59 | } // namespace eigenpy 60 | 61 | #endif // ifndef 62 | // __eigenpy_decomposition_sparse_cholmod_cholmod_supernodal_llt_hpp__ 63 | -------------------------------------------------------------------------------- /include/eigenpy/decompositions/sparse/cholmod/CholmodSimplicialLLT.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 INRIA 3 | */ 4 | 5 | #ifndef __eigenpy_decomposition_sparse_cholmod_cholmod_simplicial_llt_hpp__ 6 | #define __eigenpy_decomposition_sparse_cholmod_cholmod_simplicial_llt_hpp__ 7 | 8 | #include "eigenpy/eigenpy.hpp" 9 | #include "eigenpy/decompositions/sparse/cholmod/CholmodDecomposition.hpp" 10 | #include "eigenpy/utils/scalar-name.hpp" 11 | 12 | namespace eigenpy { 13 | 14 | template 15 | struct CholmodSimplicialLLTVisitor 16 | : public boost::python::def_visitor< 17 | CholmodSimplicialLLTVisitor> { 18 | typedef MatrixType_ MatrixType; 19 | typedef typename MatrixType::Scalar Scalar; 20 | typedef typename MatrixType::RealScalar RealScalar; 21 | 22 | typedef Eigen::CholmodSimplicialLLT Solver; 23 | 24 | template 25 | void visit(PyClass& cl) const { 26 | cl 27 | 28 | .def(CholmodBaseVisitor()) 29 | .def(bp::init<>(bp::arg("self"), "Default constructor")) 30 | .def(bp::init(bp::args("self", "matrix"), 31 | "Constructs and performs the LLT " 32 | "factorization from a given matrix.")) 33 | 34 | ; 35 | } 36 | 37 | static void expose() { 38 | static const std::string classname = 39 | "CholmodSimplicialLLT_" + scalar_name::shortname(); 40 | expose(classname); 41 | } 42 | 43 | static void expose(const std::string& name) { 44 | bp::class_( 45 | name.c_str(), 46 | "A simplicial direct Cholesky (LLT) factorization and solver based on " 47 | "Cholmod.\n\n" 48 | "This class allows to solve for A.X = B sparse linear problems via a " 49 | "simplicial LL^T Cholesky factorization using the Cholmod library." 50 | "This simplicial variant is equivalent to Eigen's built-in " 51 | "SimplicialLLT class." 52 | "Therefore, it has little practical interest. The sparse matrix A must " 53 | "be selfadjoint and positive definite." 54 | "The vectors or matrices X and B can be either dense or sparse.", 55 | bp::no_init) 56 | .def(CholmodSimplicialLLTVisitor()); 57 | } 58 | }; 59 | 60 | } // namespace eigenpy 61 | 62 | #endif // ifndef 63 | // __eigenpy_decomposition_sparse_cholmod_cholmod_simplicial_llt_hpp__ 64 | -------------------------------------------------------------------------------- /include/eigenpy/eigenpy.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2019, CNRS 3 | * Copyright 2018-2024, INRIA 4 | */ 5 | 6 | #ifndef __eigenpy_eigenpy_hpp__ 7 | #define __eigenpy_eigenpy_hpp__ 8 | 9 | #include "eigenpy/fwd.hpp" 10 | #include "eigenpy/eigen-typedef.hpp" 11 | #include "eigenpy/expose.hpp" 12 | 13 | /// Custom CallPolicies 14 | #include "eigenpy/std-unique-ptr.hpp" 15 | 16 | #define ENABLE_SPECIFIC_MATRIX_TYPE(TYPE) \ 17 | ::eigenpy::enableEigenPySpecific(); 18 | 19 | namespace eigenpy { 20 | 21 | /* Enable Eigen-Numpy serialization for a set of standard MatrixBase instance. 22 | */ 23 | void EIGENPY_DLLAPI enableEigenPy(); 24 | 25 | bool EIGENPY_DLLAPI withTensorSupport(); 26 | 27 | /* Enable the Eigen--Numpy serialization for the templated MatType class.*/ 28 | template 29 | void enableEigenPySpecific(); 30 | 31 | template 32 | EIGEN_DONT_INLINE void exposeType() { 33 | EIGENPY_MAKE_TYPEDEFS_ALL_SIZES(Scalar, Options, s); 34 | 35 | EIGENPY_UNUSED_TYPE(Vector1s); 36 | EIGENPY_UNUSED_TYPE(RowVector1s); 37 | ENABLE_SPECIFIC_MATRIX_TYPE(Matrix1s); 38 | 39 | ENABLE_SPECIFIC_MATRIX_TYPE(Vector2s); 40 | ENABLE_SPECIFIC_MATRIX_TYPE(RowVector2s); 41 | ENABLE_SPECIFIC_MATRIX_TYPE(Matrix2s); 42 | ENABLE_SPECIFIC_MATRIX_TYPE(Matrix2Xs); 43 | ENABLE_SPECIFIC_MATRIX_TYPE(MatrixX2s); 44 | 45 | ENABLE_SPECIFIC_MATRIX_TYPE(Vector3s); 46 | ENABLE_SPECIFIC_MATRIX_TYPE(RowVector3s); 47 | ENABLE_SPECIFIC_MATRIX_TYPE(Matrix3s); 48 | ENABLE_SPECIFIC_MATRIX_TYPE(Matrix3Xs); 49 | ENABLE_SPECIFIC_MATRIX_TYPE(MatrixX3s); 50 | 51 | ENABLE_SPECIFIC_MATRIX_TYPE(Vector4s); 52 | ENABLE_SPECIFIC_MATRIX_TYPE(RowVector4s); 53 | ENABLE_SPECIFIC_MATRIX_TYPE(Matrix4s); 54 | ENABLE_SPECIFIC_MATRIX_TYPE(Matrix4Xs); 55 | ENABLE_SPECIFIC_MATRIX_TYPE(MatrixX4s); 56 | 57 | ENABLE_SPECIFIC_MATRIX_TYPE(VectorXs); 58 | ENABLE_SPECIFIC_MATRIX_TYPE(RowVectorXs); 59 | ENABLE_SPECIFIC_MATRIX_TYPE(MatrixXs); 60 | 61 | enableEigenPySpecific(); 62 | } 63 | 64 | template 65 | EIGEN_DONT_INLINE void exposeType() { 66 | exposeType(); 67 | 68 | #ifdef EIGENPY_WITH_TENSOR_SUPPORT 69 | enableEigenPySpecific>(); 70 | enableEigenPySpecific>(); 71 | enableEigenPySpecific>(); 72 | #endif 73 | } 74 | 75 | } // namespace eigenpy 76 | 77 | #include "eigenpy/details.hpp" 78 | 79 | #endif // ifndef __eigenpy_eigenpy_hpp__ 80 | -------------------------------------------------------------------------------- /include/eigenpy/decompositions/sparse/cholmod/CholmodSimplicialLDLT.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 INRIA 3 | */ 4 | 5 | #ifndef __eigenpy_decomposition_sparse_cholmod_cholmod_simplicial_ldlt_hpp__ 6 | #define __eigenpy_decomposition_sparse_cholmod_cholmod_simplicial_ldlt_hpp__ 7 | 8 | #include "eigenpy/eigenpy.hpp" 9 | #include "eigenpy/decompositions/sparse/cholmod/CholmodDecomposition.hpp" 10 | #include "eigenpy/utils/scalar-name.hpp" 11 | 12 | namespace eigenpy { 13 | 14 | template 15 | struct CholmodSimplicialLDLTVisitor 16 | : public boost::python::def_visitor< 17 | CholmodSimplicialLDLTVisitor> { 18 | typedef MatrixType_ MatrixType; 19 | typedef typename MatrixType::Scalar Scalar; 20 | typedef typename MatrixType::RealScalar RealScalar; 21 | 22 | typedef Eigen::CholmodSimplicialLDLT Solver; 23 | 24 | template 25 | void visit(PyClass& cl) const { 26 | cl 27 | 28 | .def(CholmodBaseVisitor()) 29 | .def(bp::init<>(bp::arg("self"), "Default constructor")) 30 | .def(bp::init(bp::args("self", "matrix"), 31 | "Constructs and performs the LDLT " 32 | "factorization from a given matrix.")) 33 | 34 | ; 35 | } 36 | 37 | static void expose() { 38 | static const std::string classname = 39 | "CholmodSimplicialLDLT_" + scalar_name::shortname(); 40 | expose(classname); 41 | } 42 | 43 | static void expose(const std::string& name) { 44 | bp::class_( 45 | name.c_str(), 46 | "A simplicial direct Cholesky (LDLT) factorization and solver based on " 47 | "Cholmod.\n\n" 48 | "This class allows to solve for A.X = B sparse linear problems via a " 49 | "simplicial LL^T Cholesky factorization using the Cholmod library." 50 | "This simplicial variant is equivalent to Eigen's built-in " 51 | "SimplicialLDLT class." 52 | "Therefore, it has little practical interest. The sparse matrix A must " 53 | "be selfadjoint and positive definite." 54 | "The vectors or matrices X and B can be either dense or sparse.", 55 | bp::no_init) 56 | .def(CholmodSimplicialLDLTVisitor()); 57 | } 58 | }; 59 | 60 | } // namespace eigenpy 61 | 62 | #endif // ifndef 63 | // __eigenpy_decomposition_sparse_cholmod_cholmod_simplicial_ldlt_hpp__ 64 | -------------------------------------------------------------------------------- /include/eigenpy/registration.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2019, CNRS 3 | * Copyright 2018-2019, INRIA 4 | */ 5 | 6 | #ifndef __eigenpy_registration_hpp__ 7 | #define __eigenpy_registration_hpp__ 8 | 9 | #include "eigenpy/fwd.hpp" 10 | #include "eigenpy/registration_class.hpp" 11 | 12 | namespace eigenpy { 13 | 14 | /// 15 | /// \brief Check at runtime the registration of the type T inside the boost 16 | /// python registry. 17 | /// 18 | /// \tparam T The type to check the registration. 19 | /// 20 | /// \returns true if the type T is already registered. 21 | /// 22 | template 23 | inline bool check_registration() { 24 | const bp::type_info info = bp::type_id(); 25 | const bp::converter::registration* reg = bp::converter::registry::query(info); 26 | if (reg == NULL) 27 | return false; 28 | else if ((*reg).m_to_python == NULL) 29 | return false; 30 | 31 | return true; 32 | } 33 | 34 | /// 35 | /// \brief Symlink to the current scope the already registered class T. 36 | /// 37 | ///  \returns true if the type T is effectively symlinked. 38 | /// 39 | /// \tparam T The type to symlink. 40 | /// 41 | template 42 | inline bool register_symbolic_link_to_registered_type() { 43 | if (eigenpy::check_registration()) { 44 | const bp::type_info info = bp::type_id(); 45 | const bp::converter::registration* reg = 46 | bp::converter::registry::query(info); 47 | bp::handle<> class_obj(reg->get_class_object()); 48 | bp::incref(class_obj.get()); 49 | bp::scope().attr(reg->get_class_object()->tp_name) = bp::object(class_obj); 50 | return true; 51 | } 52 | 53 | return false; 54 | } 55 | 56 | /// Same as \see register_symbolic_link_to_registered_type() but apply \p 57 | /// visitor on \tparam T if it already exists 58 | template 59 | inline bool register_symbolic_link_to_registered_type(const Visitor& visitor) { 60 | if (eigenpy::check_registration()) { 61 | const bp::type_info info = bp::type_id(); 62 | const bp::converter::registration* reg = 63 | bp::converter::registry::query(info); 64 | bp::handle<> class_obj(reg->get_class_object()); 65 | bp::incref(class_obj.get()); 66 | bp::object object(class_obj); 67 | bp::scope().attr(reg->get_class_object()->tp_name) = object; 68 | registration_class cl(object); 69 | cl.def(visitor); 70 | return true; 71 | } 72 | 73 | return false; 74 | } 75 | } // namespace eigenpy 76 | 77 | #endif // ifndef __eigenpy_registration_hpp__ 78 | -------------------------------------------------------------------------------- /src/numpy.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020-2024 INRIA 3 | */ 4 | 5 | #include "eigenpy/numpy.hpp" 6 | 7 | namespace eigenpy { 8 | void import_numpy() { 9 | if (_import_array() < 0) { 10 | PyErr_Print(); 11 | PyErr_SetString(PyExc_ImportError, 12 | "numpy.core.multiarray failed to import"); 13 | } 14 | } 15 | 16 | int PyArray_TypeNum(PyTypeObject* type) { 17 | PyArray_Descr* descr = 18 | PyArray_DescrFromTypeObject(reinterpret_cast(type)); 19 | if (descr == NULL) { 20 | return NPY_NOTYPE; 21 | } 22 | return descr->type_num; 23 | } 24 | 25 | #if defined _WIN32 || defined __CYGWIN__ 26 | 27 | bool call_PyArray_Check(PyObject* py_obj) { return PyArray_Check(py_obj); } 28 | 29 | PyObject* call_PyArray_SimpleNew(int nd, npy_intp* shape, int np_type) { 30 | return PyArray_SimpleNew(nd, shape, np_type); 31 | } 32 | 33 | PyObject* call_PyArray_New(PyTypeObject* py_type_ptr, int nd, npy_intp* shape, 34 | int np_type, void* data_ptr, int options) { 35 | return PyArray_New(py_type_ptr, nd, shape, np_type, NULL, data_ptr, 0, 36 | options, NULL); 37 | } 38 | 39 | PyObject* call_PyArray_New(PyTypeObject* py_type_ptr, int nd, npy_intp* shape, 40 | int np_type, npy_intp* strides, void* data_ptr, 41 | int options) { 42 | return PyArray_New(py_type_ptr, nd, shape, np_type, strides, data_ptr, 0, 43 | options, NULL); 44 | } 45 | 46 | int call_PyArray_ObjectType(PyObject* obj, int val) { 47 | return PyArray_ObjectType(obj, val); 48 | } 49 | 50 | PyTypeObject* getPyArrayType() { return &PyArray_Type; } 51 | 52 | PyArray_Descr* call_PyArray_DescrFromType(int typenum) { 53 | return PyArray_DescrFromType(typenum); 54 | } 55 | 56 | void call_PyArray_InitArrFuncs(PyArray_ArrFuncs* funcs) { 57 | PyArray_InitArrFuncs(funcs); 58 | } 59 | 60 | int call_PyArray_RegisterDataType(PyArray_DescrProto* dtype) { 61 | return PyArray_RegisterDataType(dtype); 62 | } 63 | 64 | PyArray_Descr* call_PyArray_MinScalarType(PyArrayObject* arr) { 65 | return PyArray_MinScalarType(arr); 66 | } 67 | 68 | int call_PyArray_RegisterCanCast(PyArray_Descr* descr, int totype, 69 | NPY_SCALARKIND scalar) { 70 | return PyArray_RegisterCanCast(descr, totype, scalar); 71 | } 72 | 73 | int call_PyArray_RegisterCastFunc(PyArray_Descr* descr, int totype, 74 | PyArray_VectorUnaryFunc* castfunc) { 75 | return PyArray_RegisterCastFunc(descr, totype, castfunc); 76 | } 77 | 78 | #endif 79 | } // namespace eigenpy 80 | -------------------------------------------------------------------------------- /unittest/python/test_user_type.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import user_type 3 | 4 | # from packaging import version 5 | 6 | rows = 10 7 | cols = 20 8 | 9 | 10 | def test(dtype): 11 | rng = np.random.default_rng() 12 | mat = np.ones((rows, cols), dtype=dtype) 13 | mat = rng.random((rows, cols)).astype(dtype) 14 | mat_copy = mat.copy() 15 | assert (mat == mat_copy).all() 16 | assert not (mat != mat_copy).all() 17 | 18 | # if version.parse(np.__version__) >= version.parse("1.21.0"): 19 | # # check if it fixes for new version of NumPy 20 | # mat.fill(mat.dtype.type(20.0)) 21 | # mat_copy = mat.copy() 22 | # assert (mat == mat_copy).all() 23 | # assert not (mat != mat_copy).all() 24 | 25 | mat_op = mat + mat 26 | mat_op = mat.copy(order="F") + mat.copy(order="C") 27 | 28 | mat_op = mat - mat 29 | mat_op = mat * mat 30 | mat_op = mat.dot(mat.T) 31 | mat_op = mat / mat 32 | 33 | mat_op = -mat # noqa 34 | 35 | assert (mat >= mat).all() 36 | assert (mat <= mat).all() 37 | assert not (mat > mat).all() 38 | assert not (mat < mat).all() 39 | 40 | mat2 = mat.dot(mat.T) 41 | mat2_ref = mat.astype(np.double).dot(mat.T.astype(np.double)) 42 | assert np.isclose(mat2.astype(np.double), mat2_ref).all() 43 | if np.__version__ >= "1.17.0": 44 | mat2 = np.matmul(mat, mat.T) 45 | assert np.isclose(mat2.astype(np.double), mat2_ref).all() 46 | 47 | vec = np.ones((rows,), dtype=dtype) 48 | norm = np.linalg.norm(vec) 49 | norm_ref = np.linalg.norm(vec.astype(np.double)) 50 | assert norm == norm_ref 51 | 52 | 53 | def test_cast(from_dtype, to_dtype): 54 | np.can_cast(from_dtype, to_dtype) 55 | 56 | from_mat = np.zeros((rows, cols), dtype=from_dtype) 57 | to_mat = from_mat.astype(dtype=to_dtype) # noqa 58 | 59 | 60 | test(user_type.CustomDouble) 61 | 62 | test_cast(user_type.CustomDouble, np.double) 63 | test_cast(np.double, user_type.CustomDouble) 64 | 65 | test_cast(user_type.CustomDouble, np.int64) 66 | test_cast(np.int64, user_type.CustomDouble) 67 | 68 | test_cast(user_type.CustomDouble, np.int32) 69 | test_cast(np.int32, user_type.CustomDouble) 70 | 71 | v = user_type.CustomDouble(1) 72 | a = np.array(v) 73 | assert type(v) is a.dtype.type 74 | 75 | test(user_type.CustomFloat) 76 | 77 | test_cast(user_type.CustomFloat, np.float32) 78 | test_cast(np.double, user_type.CustomFloat) 79 | 80 | test_cast(user_type.CustomFloat, np.int64) 81 | test_cast(np.int64, user_type.CustomFloat) 82 | 83 | test_cast(user_type.CustomFloat, np.int32) 84 | test_cast(np.int32, user_type.CustomFloat) 85 | -------------------------------------------------------------------------------- /unittest/sparse_matrix.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 CNRS INRIA 3 | */ 4 | 5 | #include 6 | 7 | #include "eigenpy/eigenpy.hpp" 8 | 9 | template 10 | Eigen::SparseMatrix vector1x1(const Scalar& value) { 11 | typedef Eigen::SparseMatrix ReturnType; 12 | ReturnType mat(1, 1); 13 | mat.coeffRef(0, 0) = value; 14 | mat.makeCompressed(); 15 | return mat; 16 | } 17 | 18 | template 19 | Eigen::SparseMatrix matrix1x1(const Scalar& value) { 20 | typedef Eigen::SparseMatrix ReturnType; 21 | ReturnType mat(1, 1); 22 | mat.coeffRef(0, 0) = value; 23 | mat.makeCompressed(); 24 | return mat; 25 | } 26 | 27 | template 28 | Eigen::SparseMatrix diagonal( 29 | const Eigen::Ref>& 30 | diag_values) { 31 | typedef Eigen::SparseMatrix ReturnType; 32 | ReturnType mat(diag_values.size(), diag_values.size()); 33 | for (Eigen::Index k = 0; k < diag_values.size(); ++k) 34 | mat.coeffRef(k, k) = diag_values[k]; 35 | mat.makeCompressed(); 36 | return mat; 37 | } 38 | 39 | template 40 | Eigen::SparseMatrix emptyVector() { 41 | return Eigen::SparseMatrix(); 42 | } 43 | 44 | template 45 | Eigen::SparseMatrix emptyMatrix() { 46 | return Eigen::SparseMatrix(); 47 | } 48 | 49 | template 50 | void print(const Eigen::SparseMatrix& mat) { 51 | std::cout << mat << std::endl; 52 | } 53 | 54 | template 55 | Eigen::SparseMatrix copy( 56 | const Eigen::SparseMatrix& mat) { 57 | return mat; 58 | } 59 | 60 | template 61 | void expose_functions() { 62 | namespace bp = boost::python; 63 | bp::def("vector1x1", vector1x1); 64 | bp::def("matrix1x1", matrix1x1); 65 | 66 | bp::def("print", print); 67 | bp::def("copy", copy); 68 | bp::def("diagonal", diagonal); 69 | 70 | bp::def("emptyVector", emptyVector); 71 | bp::def("emptyMatrix", emptyMatrix); 72 | } 73 | 74 | BOOST_PYTHON_MODULE(sparse_matrix) { 75 | namespace bp = boost::python; 76 | eigenpy::enableEigenPy(); 77 | 78 | expose_functions(); 79 | expose_functions(); 80 | } 81 | -------------------------------------------------------------------------------- /include/eigenpy/type_info.hpp: -------------------------------------------------------------------------------- 1 | /// 2 | /// Copyright (c) 2024 INRIA 3 | /// 4 | 5 | #ifndef __eigenpy_type_info_hpp__ 6 | #define __eigenpy_type_info_hpp__ 7 | 8 | #include "eigenpy/fwd.hpp" 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | namespace eigenpy { 15 | 16 | template 17 | boost::typeindex::type_index type_info(const T& value) { 18 | return boost::typeindex::type_id_runtime(value); 19 | } 20 | 21 | template 22 | void expose_boost_type_info() { 23 | boost::python::def( 24 | "type_info", 25 | +[](const T& value) -> boost::typeindex::type_index { 26 | return boost::typeindex::type_id_runtime(value); 27 | }, 28 | bp::arg("value"), 29 | "Returns information of the type of value as a " 30 | "boost::typeindex::type_index (can work without RTTI)."); 31 | boost::python::def( 32 | "boost_type_info", 33 | +[](const T& value) -> boost::typeindex::type_index { 34 | return boost::typeindex::type_id_runtime(value); 35 | }, 36 | bp::arg("value"), 37 | "Returns information of the type of value as a " 38 | "boost::typeindex::type_index (can work without RTTI)."); 39 | } 40 | 41 | template 42 | void expose_std_type_info() { 43 | boost::python::def( 44 | "std_type_info", 45 | +[](const T& value) -> std::type_index { return typeid(value); }, 46 | bp::arg("value"), 47 | "Returns information of the type of value as a std::type_index."); 48 | } 49 | 50 | /// 51 | /// \brief Add the Python method type_info to query information of a type. 52 | /// 53 | template 54 | struct TypeInfoVisitor : public bp::def_visitor> { 55 | template 56 | void visit(PyClass& cl) const { 57 | cl.def("type_info", &boost_type_info, bp::arg("self"), 58 | "Queries information of the type of *this as a " 59 | "boost::typeindex::type_index (can work without RTTI)."); 60 | cl.def("boost_type_info", &boost_type_info, bp::arg("self"), 61 | "Queries information of the type of *this as a " 62 | "boost::typeindex::type_index (can work without RTTI)."); 63 | cl.def("std_type_info", &std_type_info, bp::arg("self"), 64 | "Queries information of the type of *this as a std::type_index."); 65 | } 66 | 67 | private: 68 | static boost::typeindex::type_index boost_type_info(const C& self) { 69 | return boost::typeindex::type_id_runtime(self); 70 | } 71 | 72 | static std::type_index std_type_info(const C& self) { return typeid(self); } 73 | }; 74 | 75 | } // namespace eigenpy 76 | 77 | #endif // __eigenpy_type_info_hpp__ 78 | -------------------------------------------------------------------------------- /unittest/bind_optional.cpp.in: -------------------------------------------------------------------------------- 1 | /// 2 | /// Copyright (c) 2023 CNRS INRIA 3 | /// 4 | 5 | #include "eigenpy/eigenpy.hpp" 6 | #include "eigenpy/optional.hpp" 7 | #ifdef EIGENPY_WITH_CXX17_SUPPORT 8 | #include 9 | #endif 10 | 11 | #cmakedefine TEST_TYPE @TEST_TYPE@ 12 | #define OPTIONAL TEST_TYPE 13 | 14 | typedef eigenpy::detail::nullopt_helper none_helper; 15 | static auto OPT_NONE = none_helper::value(); 16 | typedef OPTIONAL opt_dbl; 17 | 18 | struct mystruct { 19 | OPTIONAL a; 20 | opt_dbl b; 21 | OPTIONAL msg{"i am struct"}; 22 | mystruct() : a(OPT_NONE), b(OPT_NONE) {} 23 | mystruct(int a, const opt_dbl &b = OPT_NONE) : a(a), b(b) {} 24 | }; 25 | 26 | OPTIONAL none_if_zero(int i) { 27 | if (i == 0) 28 | return OPT_NONE; 29 | else 30 | return i; 31 | } 32 | 33 | OPTIONAL create_if_true(bool flag, opt_dbl b = OPT_NONE) { 34 | if (flag) { 35 | return mystruct(0, b); 36 | } else { 37 | return OPT_NONE; 38 | } 39 | } 40 | 41 | OPTIONAL random_mat_if_true(bool flag) { 42 | if (flag) 43 | return Eigen::MatrixXd(Eigen::MatrixXd::Random(4, 4)); 44 | else 45 | return OPT_NONE; 46 | } 47 | 48 | BOOST_PYTHON_MODULE(@MODNAME@) { 49 | using namespace eigenpy; 50 | OptionalConverter::registration(); 51 | OptionalConverter::registration(); 52 | OptionalConverter::registration(); 53 | OptionalConverter::registration(); 54 | OptionalConverter::registration(); 55 | enableEigenPy(); 56 | 57 | bp::class_("mystruct", bp::no_init) 58 | .def(bp::init<>(bp::args("self"))) 59 | .def(bp::init >( 60 | bp::args("self", "a", "b"))) 61 | .add_property( 62 | "a", 63 | bp::make_getter(&mystruct::a, 64 | bp::return_value_policy()), 65 | bp::make_setter(&mystruct::a)) 66 | .add_property( 67 | "b", 68 | bp::make_getter(&mystruct::b, 69 | bp::return_value_policy()), 70 | bp::make_setter(&mystruct::b)) 71 | .add_property( 72 | "msg", 73 | bp::make_getter(&mystruct::msg, 74 | bp::return_value_policy()), 75 | bp::make_setter(&mystruct::msg)); 76 | 77 | bp::def("none_if_zero", none_if_zero, bp::args("i")); 78 | bp::def("create_if_true", create_if_true, 79 | (bp::arg("flag"), bp::arg("b") = OPT_NONE)); 80 | bp::def("random_mat_if_true", random_mat_if_true, bp::args("flag")); 81 | } 82 | -------------------------------------------------------------------------------- /include/eigenpy/decompositions/sparse/SimplicialLLT.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 INRIA 3 | */ 4 | 5 | #ifndef __eigenpy_decompositions_sparse_simplicial_llt_hpp__ 6 | #define __eigenpy_decompositions_sparse_simplicial_llt_hpp__ 7 | 8 | #include "eigenpy/eigenpy.hpp" 9 | #include "eigenpy/decompositions/sparse/SimplicialCholesky.hpp" 10 | #include "eigenpy/utils/scalar-name.hpp" 11 | 12 | namespace eigenpy { 13 | 14 | template > 17 | struct SimplicialLLTVisitor 18 | : public boost::python::def_visitor< 19 | SimplicialLLTVisitor<_MatrixType, _UpLo, _Ordering>> { 20 | typedef SimplicialLLTVisitor<_MatrixType, _UpLo, _Ordering> Visitor; 21 | typedef _MatrixType MatrixType; 22 | 23 | typedef Eigen::SimplicialLLT Solver; 24 | typedef typename MatrixType::Scalar Scalar; 25 | typedef typename MatrixType::RealScalar RealScalar; 26 | typedef Eigen::Matrix 27 | DenseVectorXs; 28 | typedef Eigen::Matrix 30 | DenseMatrixXs; 31 | 32 | template 33 | void visit(PyClass& cl) const { 34 | cl.def(bp::init<>(bp::arg("self"), "Default constructor")) 35 | .def(bp::init(bp::args("self", "matrix"), 36 | "Constructs and performs the LLT " 37 | "factorization from a given matrix.")) 38 | 39 | .def(SimplicialCholeskyVisitor()); 40 | } 41 | 42 | static void expose() { 43 | static const std::string classname = 44 | "SimplicialLLT_" + scalar_name::shortname(); 45 | expose(classname); 46 | } 47 | 48 | static void expose(const std::string& name) { 49 | bp::class_( 50 | name.c_str(), 51 | "A direct sparse LLT Cholesky factorizations.\n\n" 52 | "This class provides a LL^T Cholesky factorizations of sparse matrices " 53 | "that are selfadjoint and positive definite." 54 | "The factorization allows for solving A.X = B where X and B can be " 55 | "either dense or sparse.\n\n" 56 | "In order to reduce the fill-in, a symmetric permutation P is applied " 57 | "prior to the factorization such that the factorized matrix is P A " 58 | "P^-1.", 59 | bp::no_init) 60 | .def(SimplicialLLTVisitor()) 61 | .def(IdVisitor()); 62 | } 63 | }; 64 | 65 | } // namespace eigenpy 66 | 67 | #endif // ifndef __eigenpy_decompositions_sparse_simplicial_llt_hpp__ 68 | -------------------------------------------------------------------------------- /unittest/std_vector.cpp: -------------------------------------------------------------------------------- 1 | /// @file 2 | /// @copyright Copyright 2022, CNRS 3 | /// @copyright Copyright 2022, INRIA 4 | #include 5 | 6 | #include "eigenpy/eigenpy.hpp" 7 | #include "eigenpy/eigen-from-python.hpp" 8 | #include "eigenpy/std-vector.hpp" 9 | 10 | template 11 | void printVectorOfMatrix( 12 | const std::vector>& Ms) { 13 | const std::size_t n = Ms.size(); 14 | for (std::size_t i = 0; i < n; i++) { 15 | std::cout << "el[" << i << "] =\n" << Ms[i] << '\n'; 16 | } 17 | } 18 | 19 | template 20 | std::vector> copy( 21 | const std::vector>& Ms) { 22 | std::vector> out = Ms; 23 | return out; 24 | } 25 | 26 | template 27 | void setZero(std::vector>& Ms) { 28 | for (std::size_t i = 0; i < Ms.size(); i++) { 29 | Ms[i].setZero(); 30 | } 31 | } 32 | 33 | struct CustomTestStruct { 34 | bool operator==(const CustomTestStruct&) const { return true; } 35 | }; 36 | 37 | BOOST_PYTHON_MODULE(std_vector) { 38 | namespace bp = boost::python; 39 | using namespace eigenpy; 40 | 41 | enableEigenPy(); 42 | 43 | bp::def("printVectorOfMatrix", printVectorOfMatrix); 44 | bp::def("printVectorOfMatrix", printVectorOfMatrix); 45 | 46 | bp::def("copyStdVector", copy); 47 | bp::def("copyStdVector", copy); 48 | 49 | exposeStdVectorEigenSpecificType("Mat3d"); 50 | bp::def("printVectorOf3x3", printVectorOfMatrix); 51 | bp::def("copyStdVec_3x3", copy, bp::args("mats")); 52 | 53 | typedef Eigen::Ref RefXd; 54 | StdVectorPythonVisitor, true>::expose("StdVec_MatRef"); 55 | bp::def("setZero", setZero, "Sets the coeffs to 0."); 56 | 57 | // Test matrix modification 58 | // Mat2d don't have tolist, reserve, mutable __getitem__ and from list 59 | // conversion 60 | // exposeStdVectorEigenSpecificType must add those methods to StdVec_Mat2d 61 | bp::class_>("StdVec_Mat2d") 62 | .def( 63 | boost::python::vector_indexing_suite>()); 64 | exposeStdVectorEigenSpecificType("Mat2d"); 65 | 66 | // Test API regression: 67 | // Exposing a `std::vector` with documentation doesn't clash with 68 | // exposing a `std::vector` with a visitor 69 | StdVectorPythonVisitor>::expose( 70 | "StdVec_CustomTestStruct", "some documentation"); 71 | } 72 | -------------------------------------------------------------------------------- /src/decompositions/decompositions.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020-2024 INRIA 3 | */ 4 | 5 | #include "eigenpy/decompositions/decompositions.hpp" 6 | #include "eigenpy/solvers/MINRES.hpp" 7 | 8 | #include "eigenpy/fwd.hpp" 9 | 10 | namespace eigenpy { 11 | 12 | void exposeEigenSolver(); 13 | void exposeGeneralizedEigenSolver(); 14 | void exposeSelfAdjointEigenSolver(); 15 | void exposeGeneralizedSelfAdjointEigenSolver(); 16 | void exposeHessenbergDecomposition(); 17 | void exposeRealQZ(); 18 | void exposeRealSchur(); 19 | void exposeTridiagonalization(); 20 | void exposeComplexEigenSolver(); 21 | void exposeComplexSchur(); 22 | void exposeLLTSolver(); 23 | void exposeLDLTSolver(); 24 | void exposeFullPivLUSolver(); 25 | void exposePartialPivLUSolver(); 26 | void exposeQRSolvers(); 27 | void exposeSimplicialLLTSolver(); 28 | void exposeSimplicialLDLTSolver(); 29 | void exposeSparseLUSolver(); 30 | void exposeSparseQRSolver(); 31 | void exposePermutationMatrix(); 32 | void exposeBDCSVDSolver(); 33 | void exposeJacobiSVDSolver(); 34 | 35 | void exposeBackwardCompatibilityAliases() { 36 | typedef Eigen::MatrixXd MatrixXd; 37 | MINRESSolverVisitor::expose("MINRES"); 38 | } 39 | 40 | void exposeDecompositions() { 41 | using namespace Eigen; 42 | 43 | exposeEigenSolver(); 44 | exposeGeneralizedEigenSolver(); 45 | exposeSelfAdjointEigenSolver(); 46 | exposeGeneralizedSelfAdjointEigenSolver(); 47 | exposeHessenbergDecomposition(); 48 | exposeRealQZ(); 49 | exposeRealSchur(); 50 | exposeTridiagonalization(); 51 | exposeComplexEigenSolver(); 52 | exposeComplexSchur(); 53 | exposeLLTSolver(); 54 | exposeLDLTSolver(); 55 | exposeFullPivLUSolver(); 56 | exposePartialPivLUSolver(); 57 | exposeQRSolvers(); 58 | exposeBDCSVDSolver(); 59 | exposeJacobiSVDSolver(); 60 | 61 | { 62 | bp::enum_("DecompositionOptions") 63 | .value("ComputeFullU", ComputeFullU) 64 | .value("ComputeThinU", ComputeThinU) 65 | .value("ComputeFullV", ComputeFullV) 66 | .value("ComputeThinV", ComputeThinV) 67 | .value("EigenvaluesOnly", EigenvaluesOnly) 68 | .value("ComputeEigenvectors", ComputeEigenvectors) 69 | .value("Ax_lBx", Ax_lBx) 70 | .value("ABx_lx", ABx_lx) 71 | .value("BAx_lx", BAx_lx); 72 | } 73 | 74 | // Expose sparse decompositions 75 | exposeSimplicialLLTSolver(); 76 | exposeSimplicialLDLTSolver(); 77 | exposeSparseLUSolver(); 78 | exposeSparseQRSolver(); 79 | 80 | exposePermutationMatrix(); 81 | 82 | #ifdef EIGENPY_WITH_CHOLMOD_SUPPORT 83 | exposeCholmod(); 84 | #endif 85 | 86 | #ifdef EIGENPY_WITH_ACCELERATE_SUPPORT 87 | exposeAccelerate(); 88 | #endif 89 | 90 | exposeBackwardCompatibilityAliases(); 91 | } 92 | } // namespace eigenpy 93 | -------------------------------------------------------------------------------- /include/eigenpy/solvers/BFGSPreconditioners.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 CNRS 3 | * Copyright 2024-2025 INRIA 4 | */ 5 | 6 | #ifndef __eigenpy_solvers_bfgs_preconditioners_hpp__ 7 | #define __eigenpy_solvers_bfgs_preconditioners_hpp__ 8 | 9 | #include 10 | 11 | #include "eigenpy/fwd.hpp" 12 | #include "eigenpy/solvers/BasicPreconditioners.hpp" 13 | 14 | namespace eigenpy { 15 | 16 | template 17 | struct BFGSPreconditionerBaseVisitor 18 | : public bp::def_visitor> { 19 | typedef Eigen::VectorXd VectorType; 20 | template 21 | void visit(PyClass& cl) const { 22 | cl.def(PreconditionerBaseVisitor()) 23 | .def("rows", &Preconditioner::rows, 24 | "Returns the number of rows in the preconditioner.") 25 | .def("cols", &Preconditioner::cols, 26 | "Returns the number of cols in the preconditioner.") 27 | .def("dim", &Preconditioner::dim, 28 | "Returns the dimension of the BFGS preconditioner") 29 | .def("update", 30 | (const Preconditioner& (Preconditioner::*)(const VectorType&, 31 | const VectorType&) 32 | const) & 33 | Preconditioner::update, 34 | bp::args("s", "y"), "Update the BFGS estimate of the matrix A.", 35 | bp::return_value_policy()) 36 | .def("reset", &Preconditioner::reset, "Reset the BFGS estimate."); 37 | } 38 | 39 | static void expose(const std::string& name) { 40 | bp::class_(name, bp::no_init) 41 | .def(IdVisitor()) 42 | .def(BFGSPreconditionerBaseVisitor()); 43 | } 44 | }; 45 | 46 | template 47 | struct LimitedBFGSPreconditionerBaseVisitor 48 | : public bp::def_visitor< 49 | LimitedBFGSPreconditionerBaseVisitor> { 50 | template 51 | void visit(PyClass& cl) const { 52 | cl.def(PreconditionerBaseVisitor()) 53 | .def(BFGSPreconditionerBaseVisitor()) 54 | .def("resize", &Preconditioner::resize, bp::arg("dim"), 55 | "Resizes the preconditionner with size dim.", 56 | bp::return_value_policy()); 57 | } 58 | 59 | static void expose(const std::string& name) { 60 | bp::class_(name.c_str(), bp::no_init) 61 | .def(IdVisitor()) 62 | .def(LimitedBFGSPreconditionerBaseVisitor()); 63 | } 64 | }; 65 | 66 | } // namespace eigenpy 67 | 68 | #endif // ifndef __eigenpy_solvers_bfgs_preconditioners_hpp__ 69 | -------------------------------------------------------------------------------- /unittest/python/test_QR.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | import eigenpy 4 | 5 | rows = 20 6 | cols = 100 7 | rng = np.random.default_rng() 8 | 9 | A = rng.random((rows, cols)) 10 | 11 | # Test HouseholderQR decomposition 12 | householder_qr = eigenpy.HouseholderQR() 13 | householder_qr = eigenpy.HouseholderQR(rows, cols) 14 | householder_qr = eigenpy.HouseholderQR(A) 15 | 16 | householder_qr_eye = eigenpy.HouseholderQR(np.eye(rows, rows)) 17 | X = rng.random((rows, 20)) 18 | assert householder_qr_eye.absDeterminant() == 1.0 19 | assert householder_qr_eye.logAbsDeterminant() == 0.0 20 | 21 | Y = householder_qr_eye.solve(X) 22 | assert (X == Y).all() 23 | 24 | # Test FullPivHouseholderQR decomposition 25 | fullpiv_householder_qr = eigenpy.FullPivHouseholderQR() 26 | fullpiv_householder_qr = eigenpy.FullPivHouseholderQR(rows, cols) 27 | fullpiv_householder_qr = eigenpy.FullPivHouseholderQR(A) 28 | 29 | fullpiv_householder_qr = eigenpy.FullPivHouseholderQR(np.eye(rows, rows)) 30 | X = rng.random((rows, 20)) 31 | assert fullpiv_householder_qr.absDeterminant() == 1.0 32 | assert fullpiv_householder_qr.logAbsDeterminant() == 0.0 33 | 34 | Y = fullpiv_householder_qr.solve(X) 35 | assert (X == Y).all() 36 | assert fullpiv_householder_qr.rank() == rows 37 | 38 | fullpiv_householder_qr.setThreshold(1e-8) 39 | assert fullpiv_householder_qr.threshold() == 1e-8 40 | assert eigenpy.is_approx(np.eye(rows, rows), fullpiv_householder_qr.inverse()) 41 | 42 | # Test ColPivHouseholderQR decomposition 43 | colpiv_householder_qr = eigenpy.ColPivHouseholderQR() 44 | colpiv_householder_qr = eigenpy.ColPivHouseholderQR(rows, cols) 45 | colpiv_householder_qr = eigenpy.ColPivHouseholderQR(A) 46 | 47 | colpiv_householder_qr = eigenpy.ColPivHouseholderQR(np.eye(rows, rows)) 48 | X = rng.random((rows, 20)) 49 | assert colpiv_householder_qr.absDeterminant() == 1.0 50 | assert colpiv_householder_qr.logAbsDeterminant() == 0.0 51 | 52 | Y = colpiv_householder_qr.solve(X) 53 | assert (X == Y).all() 54 | assert colpiv_householder_qr.rank() == rows 55 | 56 | colpiv_householder_qr.setThreshold(1e-8) 57 | assert colpiv_householder_qr.threshold() == 1e-8 58 | assert eigenpy.is_approx(np.eye(rows, rows), colpiv_householder_qr.inverse()) 59 | 60 | # Test CompleteOrthogonalDecomposition 61 | cod = eigenpy.CompleteOrthogonalDecomposition() 62 | cod = eigenpy.CompleteOrthogonalDecomposition(rows, cols) 63 | cod = eigenpy.CompleteOrthogonalDecomposition(A) 64 | 65 | cod = eigenpy.CompleteOrthogonalDecomposition(np.eye(rows, rows)) 66 | X = rng.random((rows, 20)) 67 | assert cod.absDeterminant() == 1.0 68 | assert cod.logAbsDeterminant() == 0.0 69 | 70 | Y = cod.solve(X) 71 | assert (X == Y).all() 72 | assert cod.rank() == rows 73 | 74 | cod.setThreshold(1e-8) 75 | assert cod.threshold() == 1e-8 76 | assert eigenpy.is_approx(np.eye(rows, rows), cod.pseudoInverse()) 77 | -------------------------------------------------------------------------------- /unittest/python/decompositions/sparse/test_SparseQR.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import scipy.sparse as spa 3 | 4 | import eigenpy 5 | 6 | dim = 100 7 | rng = np.random.default_rng() 8 | 9 | A_fac = spa.random(dim, dim, density=0.25, random_state=rng) 10 | A = A_fac.T @ A_fac 11 | A += spa.diags(10.0 * rng.standard_normal(dim) ** 2) 12 | A = A.tocsc(True) 13 | A.check_format() 14 | 15 | spqr = eigenpy.SparseQR(A) 16 | 17 | assert spqr.info() == eigenpy.ComputationInfo.Success 18 | 19 | X = rng.random((dim, 20)) 20 | B = A.dot(X) 21 | X_est = spqr.solve(B) 22 | assert isinstance(X_est, np.ndarray) 23 | assert eigenpy.is_approx(X, X_est) 24 | assert eigenpy.is_approx(A.dot(X_est), B) 25 | 26 | spqr.analyzePattern(A) 27 | spqr.factorize(A) 28 | 29 | X_sparse = spa.random(dim, 10, random_state=rng) 30 | B_sparse = A.dot(X_sparse) 31 | B_sparse: spa.csc_matrix = B_sparse.tocsc(True) 32 | if not B_sparse.has_sorted_indices: 33 | B_sparse.sort_indices() 34 | 35 | X_est = spqr.solve(B_sparse) 36 | assert isinstance(X_est, spa.csc_matrix) 37 | assert eigenpy.is_approx(X_est.toarray(), X_sparse.toarray()) 38 | assert eigenpy.is_approx(A.dot(X_est.toarray()), B_sparse.toarray()) 39 | 40 | Q = spqr.matrixQ() 41 | R = spqr.matrixR() 42 | P = spqr.colsPermutation() 43 | 44 | assert spqr.matrixQ().rows() == dim 45 | assert spqr.matrixQ().cols() == dim 46 | assert R.shape[0] == dim 47 | assert R.shape[1] == dim 48 | assert P.indices().size == dim 49 | 50 | test_vec = rng.random(dim) 51 | test_matrix = rng.random((dim, 20)) 52 | 53 | Qv = Q @ test_vec 54 | QM = Q @ test_matrix 55 | Qt = Q.transpose() 56 | QtV = Qt @ test_vec 57 | QtM = Qt @ test_matrix 58 | 59 | assert Qv.shape == (dim,) 60 | assert QM.shape == (dim, 20) 61 | assert QtV.shape == (dim,) 62 | assert QtM.shape == (dim, 20) 63 | 64 | Qa_real_mat = Q.adjoint() 65 | QaV = Qa_real_mat @ test_vec 66 | assert eigenpy.is_approx(QtV, QaV) 67 | 68 | A_dense = A.toarray() 69 | P_indices = np.array([P.indices()[i] for i in range(dim)]) 70 | A_permuted = A_dense[:, P_indices] 71 | 72 | QtAP = Qt @ A_permuted 73 | R_dense = spqr.matrixR().toarray() 74 | assert eigenpy.is_approx(QtAP, R_dense) 75 | 76 | Q_sparse = Q.toSparse() 77 | R_sparse = R 78 | 79 | assert Q_sparse.shape == (dim, dim) 80 | 81 | QtQ_sparse = Q_sparse.T @ Q_sparse 82 | QQt_sparse = Q_sparse @ Q_sparse.T 83 | I_sparse = spa.identity(dim, format="csc") 84 | 85 | assert eigenpy.is_approx(QtQ_sparse.toarray(), I_sparse.toarray()) 86 | assert eigenpy.is_approx(QQt_sparse.toarray(), I_sparse.toarray()) 87 | 88 | Q_sparse_test_vec = Q_sparse @ test_vec 89 | assert eigenpy.is_approx(Qv, Q_sparse_test_vec) 90 | 91 | Q_sparse_test_matrix = Q_sparse @ test_matrix 92 | assert eigenpy.is_approx(QM, Q_sparse_test_matrix) 93 | 94 | QR_sparse = Q_sparse @ R_sparse.toarray() 95 | assert eigenpy.is_approx(QR_sparse, A_permuted) 96 | -------------------------------------------------------------------------------- /include/eigenpy/details.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2019, CNRS 3 | * Copyright 2018-2024, INRIA 4 | */ 5 | 6 | #ifndef __eigenpy_details_hpp__ 7 | #define __eigenpy_details_hpp__ 8 | 9 | #include "eigenpy/fwd.hpp" 10 | #include "eigenpy/eigen-allocator.hpp" 11 | #include "eigenpy/eigen-from-python.hpp" 12 | #include "eigenpy/eigen-to-python.hpp" 13 | #include "eigenpy/eigenpy.hpp" 14 | #include "eigenpy/exception.hpp" 15 | #include "eigenpy/numpy-type.hpp" 16 | #include "eigenpy/registration.hpp" 17 | #include "eigenpy/scalar-conversion.hpp" 18 | 19 | namespace eigenpy { 20 | 21 | template ::type, 23 | typename Scalar = typename EigenType::Scalar> 24 | struct expose_eigen_type_impl; 25 | 26 | template 27 | struct expose_eigen_type_impl, Scalar> { 28 | static void run() { 29 | if (check_registration()) return; 30 | 31 | // to-python 32 | EigenToPyConverter::registration(); 33 | #if EIGEN_VERSION_AT_LEAST(3, 2, 0) 34 | EigenToPyConverter>::registration(); 35 | EigenToPyConverter>::registration(); 36 | #endif 37 | 38 | // from-python 39 | EigenFromPyConverter::registration(); 40 | } 41 | }; 42 | 43 | template 44 | struct expose_eigen_type_impl, 45 | Scalar> { 46 | static void run() { 47 | if (check_registration()) return; 48 | 49 | // to-python 50 | EigenToPyConverter::registration(); 51 | // #if EIGEN_VERSION_AT_LEAST(3, 2, 0) 52 | // EigenToPyConverter >::registration(); 53 | // EigenToPyConverter >::registration(); 54 | // #endif 55 | 56 | // from-python 57 | EigenFromPyConverter::registration(); 58 | } 59 | }; 60 | 61 | #ifdef EIGENPY_WITH_TENSOR_SUPPORT 62 | template 63 | struct expose_eigen_type_impl, 64 | Scalar> { 65 | static void run() { 66 | if (check_registration()) return; 67 | 68 | // to-python 69 | EigenToPyConverter::registration(); 70 | EigenToPyConverter>::registration(); 71 | EigenToPyConverter< 72 | const Eigen::TensorRef>::registration(); 73 | 74 | // from-python 75 | EigenFromPyConverter::registration(); 76 | } 77 | }; 78 | #endif 79 | 80 | template 81 | void enableEigenPySpecific() { 82 | expose_eigen_type_impl::run(); 83 | } 84 | 85 | } // namespace eigenpy 86 | 87 | #endif // ifndef __eigenpy_details_hpp__ 88 | -------------------------------------------------------------------------------- /include/eigenpy/decompositions/HessenbergDecomposition.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 INRIA 3 | */ 4 | 5 | #ifndef __eigenpy_decompositions_hessenberg_decomposition_hpp__ 6 | #define __eigenpy_decompositions_hessenberg_decomposition_hpp__ 7 | 8 | #include 9 | #include 10 | 11 | #include "eigenpy/eigen-to-python.hpp" 12 | #include "eigenpy/eigenpy.hpp" 13 | #include "eigenpy/utils/scalar-name.hpp" 14 | 15 | namespace eigenpy { 16 | 17 | template 18 | struct HessenbergDecompositionVisitor 19 | : public boost::python::def_visitor< 20 | HessenbergDecompositionVisitor<_MatrixType>> { 21 | typedef _MatrixType MatrixType; 22 | typedef typename MatrixType::Scalar Scalar; 23 | typedef Eigen::HessenbergDecomposition Solver; 24 | 25 | template 26 | void visit(PyClass& cl) const { 27 | cl 28 | .def(bp::init( 29 | bp::arg("size"), 30 | "Default constructor; the decomposition will be computed later. ")) 31 | .def(bp::init( 32 | bp::arg("matrix"), 33 | "Constructor; computes Hessenberg decomposition of given matrix. ")) 34 | 35 | .def( 36 | "compute", 37 | (Solver & (Solver::*)(const Eigen::EigenBase& matrix)) & 38 | Solver::compute, 39 | bp::args("self", "A"), 40 | "Computes Hessenberg decomposition of given matrix. ", 41 | bp::return_self<>()) 42 | 43 | .def("householderCoefficients", &Solver::householderCoefficients, 44 | bp::arg("self"), "Returns the Householder coefficients. ", 45 | bp::return_value_policy()) 46 | 47 | .def( 48 | "matrixQ", 49 | +[](const Solver& c) -> MatrixType { return c.matrixQ(); }, 50 | "Reconstructs the orthogonal matrix Q in the decomposition.") 51 | .def( 52 | "matrixH", 53 | +[](const Solver& c) -> MatrixType { return c.matrixH(); }, 54 | "Constructs the Hessenberg matrix H in the decomposition.") 55 | 56 | .def("packedMatrix", &Solver::packedMatrix, bp::arg("self"), 57 | "Returns the internal representation of the decomposition. ", 58 | bp::return_value_policy()); 59 | } 60 | 61 | static void expose() { 62 | static const std::string classname = 63 | "HessenbergDecomposition" + scalar_name::shortname(); 64 | expose(classname); 65 | } 66 | 67 | static void expose(const std::string& name) { 68 | bp::class_(name.c_str(), bp::no_init) 69 | .def(HessenbergDecompositionVisitor()) 70 | .def(IdVisitor()); 71 | } 72 | }; 73 | 74 | } // namespace eigenpy 75 | 76 | #endif // ifndef __eigenpy_decompositions_hessenberg_decomposition_hpp__ 77 | -------------------------------------------------------------------------------- /unittest/python/test_GeneralizedSelfAdjointEigenSolver.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | import eigenpy 4 | 5 | _options = [ 6 | eigenpy.DecompositionOptions.ComputeEigenvectors 7 | | eigenpy.DecompositionOptions.Ax_lBx, 8 | eigenpy.DecompositionOptions.EigenvaluesOnly | eigenpy.DecompositionOptions.Ax_lBx, 9 | eigenpy.DecompositionOptions.ComputeEigenvectors 10 | | eigenpy.DecompositionOptions.ABx_lx, 11 | eigenpy.DecompositionOptions.EigenvaluesOnly | eigenpy.DecompositionOptions.ABx_lx, 12 | eigenpy.DecompositionOptions.ComputeEigenvectors 13 | | eigenpy.DecompositionOptions.BAx_lx, 14 | eigenpy.DecompositionOptions.EigenvaluesOnly | eigenpy.DecompositionOptions.BAx_lx, 15 | ] 16 | 17 | 18 | def test_generalized_selfadjoint_eigensolver(options): 19 | dim = 100 20 | rng = np.random.default_rng() 21 | A = rng.random((dim, dim)) 22 | A = (A + A.T) * 0.5 23 | B = rng.random((dim, dim)) 24 | B = B @ B.T + 0.1 * np.eye(dim) 25 | 26 | gsaes = eigenpy.GeneralizedSelfAdjointEigenSolver(A, B, options) 27 | assert gsaes.info() == eigenpy.ComputationInfo.Success 28 | 29 | D = gsaes.eigenvalues() 30 | assert D.shape == (dim,) 31 | assert all(abs(D[i].imag) < 1e-12 for i in range(dim)) 32 | assert all(D[i] <= D[i + 1] + 1e-12 for i in range(dim - 1)) 33 | 34 | compute_eigenvectors = bool( 35 | options & eigenpy.DecompositionOptions.ComputeEigenvectors 36 | ) 37 | 38 | if compute_eigenvectors: 39 | V = gsaes.eigenvectors() 40 | assert V.shape == (dim, dim) 41 | 42 | if options & eigenpy.DecompositionOptions.Ax_lBx: 43 | for i in range(dim): 44 | v = V[:, i] 45 | lam = D[i] 46 | Av = A @ v 47 | lam_Bv = lam * (B @ v) 48 | assert eigenpy.is_approx(Av, lam_Bv, 1e-6) 49 | 50 | VT_B_V = V.T @ B @ V 51 | assert eigenpy.is_approx(VT_B_V, np.eye(dim), 1e-6) 52 | 53 | elif options & eigenpy.DecompositionOptions.ABx_lx: 54 | AB = A @ B 55 | for i in range(dim): 56 | v = V[:, i] 57 | lam = D[i] 58 | ABv = AB @ v 59 | lam_v = lam * v 60 | assert eigenpy.is_approx(ABv, lam_v, 1e-6) 61 | 62 | elif options & eigenpy.DecompositionOptions.BAx_lx: 63 | BA = B @ A 64 | for i in range(dim): 65 | v = V[:, i] 66 | lam = D[i] 67 | BAv = BA @ v 68 | lam_v = lam * v 69 | assert eigenpy.is_approx(BAv, lam_v, 1e-6) 70 | 71 | _gsaes_compute = gsaes.compute(A, B) 72 | _gsaes_compute_options = gsaes.compute(A, B, options) 73 | 74 | rank = len([d for d in D if abs(d) > 1e-12]) 75 | assert rank <= dim 76 | 77 | 78 | for opt in _options: 79 | test_generalized_selfadjoint_eigensolver(opt) 80 | -------------------------------------------------------------------------------- /unittest/std_unique_ptr.cpp: -------------------------------------------------------------------------------- 1 | /// @file 2 | /// @copyright Copyright 2023 CNRS INRIA 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | namespace bp = boost::python; 12 | 13 | struct V1 { 14 | V1() = default; 15 | V1(double p_v) : v(p_v) {} 16 | 17 | double v = 100; 18 | }; 19 | 20 | std::unique_ptr make_unique_int() { return std::make_unique(10); } 21 | 22 | std::unique_ptr make_unique_v1() { return std::make_unique(10); } 23 | 24 | std::unique_ptr make_unique_null() { return nullptr; } 25 | 26 | std::unique_ptr make_unique_str() { 27 | return std::make_unique("str"); 28 | } 29 | 30 | std::unique_ptr> make_unique_complex() { 31 | return std::make_unique>(1., 0.); 32 | } 33 | 34 | struct UniquePtrHolder { 35 | UniquePtrHolder() 36 | : int_ptr(std::make_unique(20)), 37 | v1_ptr(std::make_unique(200)), 38 | str_ptr(std::make_unique("str")), 39 | complex_ptr(std::make_unique>(1., 0.)) {} 40 | 41 | std::unique_ptr int_ptr; 42 | std::unique_ptr v1_ptr; 43 | std::unique_ptr null_ptr; 44 | std::unique_ptr str_ptr; 45 | std::unique_ptr> complex_ptr; 46 | }; 47 | 48 | BOOST_PYTHON_MODULE(std_unique_ptr) { 49 | eigenpy::enableEigenPy(); 50 | 51 | bp::class_("V1", bp::init<>()).def_readwrite("v", &V1::v); 52 | 53 | bp::def("make_unique_int", make_unique_int); 54 | bp::def("make_unique_v1", make_unique_v1); 55 | bp::def("make_unique_null", make_unique_null, 56 | eigenpy::StdUniquePtrCallPolicies()); 57 | bp::def("make_unique_str", make_unique_str); 58 | bp::def("make_unique_complex", make_unique_complex); 59 | 60 | boost::python::class_("UniquePtrHolder", 61 | bp::init<>()) 62 | .add_property("int_ptr", 63 | bp::make_getter(&UniquePtrHolder::int_ptr, 64 | eigenpy::ReturnInternalStdUniquePtr())) 65 | .add_property("v1_ptr", 66 | bp::make_getter(&UniquePtrHolder::v1_ptr, 67 | eigenpy::ReturnInternalStdUniquePtr())) 68 | .add_property("null_ptr", 69 | bp::make_getter(&UniquePtrHolder::null_ptr, 70 | eigenpy::ReturnInternalStdUniquePtr())) 71 | .add_property("str_ptr", 72 | bp::make_getter(&UniquePtrHolder::str_ptr, 73 | eigenpy::ReturnInternalStdUniquePtr())) 74 | .add_property("complex_ptr", 75 | bp::make_getter(&UniquePtrHolder::complex_ptr, 76 | eigenpy::ReturnInternalStdUniquePtr())); 77 | } 78 | --------------------------------------------------------------------------------