diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a38a1c3137b5c4f7627f8bebe3aa91fc77ead37..920fd3918a95dad11f3534271515c1f72f4998e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,11 +6,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [Unreleased] +### Added +- Added id() helper to retrieve unique object identifier in Python ([#477](https://github.com/stack-of-tasks/eigenpy/pull/477)) +- Expose QR solvers ([#478](https://github.com/stack-of-tasks/eigenpy/pull/478)) + ## [3.6.0] - 2024-06-05 ### Added - Added a deprecation call policy shortcut ([#466](https://github.com/stack-of-tasks/eigenpy/pull/466)) -- Added id() helper to retrieve unique object identifier in Python ([#477](https://github.com/stack-of-tasks/eigenpy/pull/477)) ### Fixed - Fix register_symbolic_link_to_registered_type() for multiple successive registrations ([#471](https://github.com/stack-of-tasks/eigenpy/pull/471)) diff --git a/CMakeLists.txt b/CMakeLists.txt index 94bf5d85acf9716dc37d2e407748fc7de0aa9998..fa9e1e7b1e1e8bdf2fa169aa4276f5865fe69589 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -210,6 +210,11 @@ set(${PROJECT_NAME}_DECOMPOSITIONS_HEADERS include/eigenpy/decompositions/PermutationMatrix.hpp include/eigenpy/decompositions/LDLT.hpp include/eigenpy/decompositions/LLT.hpp + include/eigenpy/decompositions/QR.hpp + include/eigenpy/decompositions/HouseholderQR.hpp + include/eigenpy/decompositions/ColPivHouseholderQR.hpp + include/eigenpy/decompositions/CompleteOrthogonalDecomposition.hpp + include/eigenpy/decompositions/FullPivHouseholderQR.hpp include/eigenpy/decompositions/SelfAdjointEigenSolver.hpp include/eigenpy/decompositions/minres.hpp) @@ -282,6 +287,7 @@ set(${PROJECT_NAME}_DECOMPOSITIONS_SOURCES src/decompositions/llt-solver.cpp src/decompositions/ldlt-solver.cpp src/decompositions/minres-solver.cpp + src/decompositions/qr-solvers.cpp src/decompositions/eigen-solver.cpp src/decompositions/self-adjoint-eigen-solver.cpp src/decompositions/permutation-matrix.cpp diff --git a/include/eigenpy/decompositions/ColPivHouseholderQR.hpp b/include/eigenpy/decompositions/ColPivHouseholderQR.hpp new file mode 100644 index 0000000000000000000000000000000000000000..95e1bd794e7f7122a6ffe6754d90ba0151c65ae6 --- /dev/null +++ b/include/eigenpy/decompositions/ColPivHouseholderQR.hpp @@ -0,0 +1,189 @@ +/* + * Copyright 2024 INRIA + */ + +#ifndef __eigenpy_decompositions_col_piv_houselholder_qr_hpp__ +#define __eigenpy_decompositions_col_piv_houselholder_qr_hpp__ + +#include "eigenpy/eigenpy.hpp" +#include "eigenpy/utils/scalar-name.hpp" + +#include <Eigen/QR> + +namespace eigenpy { + +template <typename _MatrixType> +struct ColPivHouseholderQRSolverVisitor + : public boost::python::def_visitor< + ColPivHouseholderQRSolverVisitor<_MatrixType> > { + typedef _MatrixType MatrixType; + typedef typename MatrixType::Scalar Scalar; + typedef typename MatrixType::RealScalar RealScalar; + typedef Eigen::Matrix<Scalar, Eigen::Dynamic, 1, MatrixType::Options> + VectorXs; + typedef Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic, + MatrixType::Options> + MatrixXs; + typedef Eigen::ColPivHouseholderQR<MatrixType> Solver; + typedef Solver Self; + + template <class PyClass> + void visit(PyClass &cl) const { + cl.def(bp::init<>(bp::arg("self"), + "Default constructor.\n" + "The default constructor is useful in cases in which the " + "user intends to perform decompositions via " + "HouseholderQR.compute(matrix)")) + .def(bp::init<Eigen::DenseIndex, Eigen::DenseIndex>( + bp::args("self", "rows", "cols"), + "Default constructor with memory preallocation.\n" + "Like the default constructor but with preallocation of the " + "internal data according to the specified problem size. ")) + .def(bp::init<MatrixType>( + bp::args("self", "matrix"), + "Constructs a QR factorization from a given matrix.\n" + "This constructor computes the QR factorization of the matrix " + "matrix by calling the method compute().")) + + .def("absDeterminant", &Self::absDeterminant, bp::arg("self"), + "Returns the absolute value of the determinant of the matrix of " + "which *this is the QR decomposition.\n" + "It has only linear complexity (that is, O(n) where n is the " + "dimension of the square matrix) as the QR decomposition has " + "already been computed.\n" + "Note: This is only for square matrices.") + .def("logAbsDeterminant", &Self::logAbsDeterminant, bp::arg("self"), + "Returns the natural log of the absolute value of the determinant " + "of the matrix of which *this is the QR decomposition.\n" + "It has only linear complexity (that is, O(n) where n is the " + "dimension of the square matrix) as the QR decomposition has " + "already been computed.\n" + "Note: This is only for square matrices. This method is useful to " + "work around the risk of overflow/underflow that's inherent to " + "determinant computation.") + .def("dimensionOfKernel", &Self::dimensionOfKernel, bp::arg("self"), + "Returns the dimension of the kernel of the matrix of which *this " + "is the QR decomposition.") + .def("info", &Self::info, bp::arg("self"), + "Reports whether the QR factorization was successful.\n" + "Note: This function always returns Success. It is provided for " + "compatibility with other factorization routines.") + .def("isInjective", &Self::isInjective, bp::arg("self"), + "Returns true if the matrix associated with this QR decomposition " + "represents an injective linear map, i.e. has trivial kernel; " + "false otherwise.\n" + "\n" + "Note: This method has to determine which pivots should be " + "considered nonzero. For that, it uses the threshold value that " + "you can control by calling setThreshold(threshold).") + .def("isInvertible", &Self::isInvertible, bp::arg("self"), + "Returns true if the matrix associated with the QR decomposition " + "is invertible.\n" + "\n" + "Note: This method has to determine which pivots should be " + "considered nonzero. For that, it uses the threshold value that " + "you can control by calling setThreshold(threshold).") + .def("isSurjective", &Self::isSurjective, bp::arg("self"), + "Returns true if the matrix associated with this QR decomposition " + "represents a surjective linear map; false otherwise.\n" + "\n" + "Note: This method has to determine which pivots should be " + "considered nonzero. For that, it uses the threshold value that " + "you can control by calling setThreshold(threshold).") + .def("maxPivot", &Self::maxPivot, bp::arg("self"), + "Returns the absolute value of the biggest pivot, i.e. the " + "biggest diagonal coefficient of U.") + .def("nonzeroPivots", &Self::nonzeroPivots, bp::arg("self"), + "Returns the number of nonzero pivots in the QR decomposition. " + "Here nonzero is meant in the exact sense, not in a fuzzy sense. " + "So that notion isn't really intrinsically interesting, but it is " + "still useful when implementing algorithms.") + .def("rank", &Self::rank, bp::arg("self"), + "Returns the rank of the matrix associated with the QR " + "decomposition.\n" + "\n" + "Note: This method has to determine which pivots should be " + "considered nonzero. For that, it uses the threshold value that " + "you can control by calling setThreshold(threshold).") + + .def("setThreshold", + (Self & (Self::*)(const RealScalar &)) & Self::setThreshold, + bp::args("self", "threshold"), + "Allows to prescribe a threshold to be used by certain methods, " + "such as rank(), who need to determine when pivots are to be " + "considered nonzero. This is not used for the QR decomposition " + "itself.\n" + "\n" + "When it needs to get the threshold value, Eigen calls " + "threshold(). By default, this uses a formula to automatically " + "determine a reasonable threshold. Once you have called the " + "present method setThreshold(const RealScalar&), your value is " + "used instead.\n" + "\n" + "Note: A pivot will be considered nonzero if its absolute value " + "is strictly greater than |pivot| ⩽ threshold×|maxpivot| where " + "maxpivot is the biggest pivot.", + bp::return_self<>()) + .def("threshold", &Self::threshold, bp::arg("self"), + "Returns the threshold that will be used by certain methods such " + "as rank().") + + .def("matrixQR", &Self::matrixQR, bp::arg("self"), + "Returns the matrix where the Householder QR decomposition is " + "stored in a LAPACK-compatible way.", + bp::return_value_policy<bp::copy_const_reference>()) + .def("matrixR", &Self::matrixR, bp::arg("self"), + "Returns the matrix where the result Householder QR is stored.", + bp::return_value_policy<bp::copy_const_reference>()) + + .def( + "compute", + (Solver & (Solver::*)(const Eigen::EigenBase<MatrixType> &matrix)) & + Solver::compute, + bp::args("self", "matrix"), + "Computes the QR factorization of given matrix.", + bp::return_self<>()) + + .def("inverse", inverse, bp::arg("self"), + "Returns the inverse of the matrix associated with the QR " + "decomposition..") + + .def("solve", &solve<MatrixXs>, bp::args("self", "B"), + "Returns the solution X of A X = B using the current " + "decomposition of A where B is a right hand side matrix."); + } + + static void expose() { + static const std::string classname = + "ColPivHouseholderQR" + scalar_name<Scalar>::shortname(); + expose(classname); + } + + static void expose(const std::string &name) { + bp::class_<Solver>( + name.c_str(), + "This class performs a rank-revealing QR decomposition of a matrix A " + "into matrices P, Q and R such that:\n" + "AP=QR\n" + "by using Householder transformations. Here, P is a permutation " + "matrix, Q a unitary matrix and R an upper triangular matrix.\n" + "\n" + "This decomposition performs column pivoting in order to be " + "rank-revealing and improve numerical stability. It is slower than " + "HouseholderQR, and faster than FullPivHouseholderQR.", + bp::no_init) + .def(ColPivHouseholderQRSolverVisitor()) + .def(IdVisitor<Solver>()); + } + + private: + template <typename MatrixOrVector> + static MatrixOrVector solve(const Solver &self, const MatrixOrVector &vec) { + return self.solve(vec); + } + static MatrixXs inverse(const Self &self) { return self.inverse(); } +}; + +} // namespace eigenpy + +#endif // ifndef __eigenpy_decompositions_col_piv_houselholder_qr_hpp__ diff --git a/include/eigenpy/decompositions/CompleteOrthogonalDecomposition.hpp b/include/eigenpy/decompositions/CompleteOrthogonalDecomposition.hpp new file mode 100644 index 0000000000000000000000000000000000000000..5cfc4a1c9a0399563128aa5c9f3c375d39be08a5 --- /dev/null +++ b/include/eigenpy/decompositions/CompleteOrthogonalDecomposition.hpp @@ -0,0 +1,204 @@ +/* + * Copyright 2024 INRIA + */ + +#ifndef __eigenpy_decompositions_complete_orthogonal_decomposition_hpp__ +#define __eigenpy_decompositions_complete_orthogonal_decomposition_hpp__ + +#include "eigenpy/eigenpy.hpp" +#include "eigenpy/utils/scalar-name.hpp" + +#include <Eigen/QR> + +namespace eigenpy { + +template <typename _MatrixType> +struct CompleteOrthogonalDecompositionSolverVisitor + : public boost::python::def_visitor< + CompleteOrthogonalDecompositionSolverVisitor<_MatrixType> > { + typedef _MatrixType MatrixType; + typedef typename MatrixType::Scalar Scalar; + typedef typename MatrixType::RealScalar RealScalar; + typedef Eigen::Matrix<Scalar, Eigen::Dynamic, 1, MatrixType::Options> + VectorXs; + typedef Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic, + MatrixType::Options> + MatrixXs; + typedef Eigen::CompleteOrthogonalDecomposition<MatrixType> Solver; + typedef Solver Self; + + template <class PyClass> + void visit(PyClass &cl) const { + cl.def(bp::init<>(bp::arg("self"), + "Default constructor.\n" + "The default constructor is useful in cases in which the " + "user intends to perform decompositions via " + "CompleteOrthogonalDecomposition.compute(matrix)")) + .def(bp::init<Eigen::DenseIndex, Eigen::DenseIndex>( + bp::args("self", "rows", "cols"), + "Default constructor with memory preallocation.\n" + "Like the default constructor but with preallocation of the " + "internal data according to the specified problem size. ")) + .def(bp::init<MatrixType>(bp::args("self", "matrix"), + "Constructs a complete orthogonal " + "factorization from a given matrix.\n" + "This constructor computes the complete " + "orthogonal factorization of the matrix " + "matrix by calling the method compute().")) + + .def("absDeterminant", &Self::absDeterminant, bp::arg("self"), + "Returns the absolute value of the determinant of the matrix " + "associated with the complete orthogonal decomposition.\n" + "It has only linear complexity (that is, O(n) where n is the " + "dimension of the square matrix) as the complete orthogonal " + "decomposition has " + "already been computed.\n" + "Note: This is only for square matrices.") + .def("logAbsDeterminant", &Self::logAbsDeterminant, bp::arg("self"), + "Returns the natural log of the absolute value of the determinant " + "of the matrix of which *this is the complete orthogonal " + "decomposition.\n" + "It has only linear complexity (that is, O(n) where n is the " + "dimension of the square matrix) as the complete orthogonal " + "decomposition has " + "already been computed.\n" + "Note: This is only for square matrices. This method is useful to " + "work around the risk of overflow/underflow that's inherent to " + "determinant computation.") + .def("dimensionOfKernel", &Self::dimensionOfKernel, bp::arg("self"), + "Returns the dimension of the kernel of the matrix of which *this " + "is the complete orthogonal decomposition.") + .def("info", &Self::info, bp::arg("self"), + "Reports whether the complete orthogonal factorization was " + "successful.\n" + "Note: This function always returns Success. It is provided for " + "compatibility with other factorization routines.") + .def("isInjective", &Self::isInjective, bp::arg("self"), + "Returns true if the matrix associated with this complete " + "orthogonal decomposition " + "represents an injective linear map, i.e. has trivial kernel; " + "false otherwise.\n" + "\n" + "Note: This method has to determine which pivots should be " + "considered nonzero. For that, it uses the threshold value that " + "you can control by calling setThreshold(threshold).") + .def("isInvertible", &Self::isInvertible, bp::arg("self"), + "Returns true if the matrix associated with the complete " + "orthogonal decomposition " + "is invertible.\n" + "\n" + "Note: This method has to determine which pivots should be " + "considered nonzero. For that, it uses the threshold value that " + "you can control by calling setThreshold(threshold).") + .def("isSurjective", &Self::isSurjective, bp::arg("self"), + "Returns true if the matrix associated with this complete " + "orthogonal decomposition " + "represents a surjective linear map; false otherwise.\n" + "\n" + "Note: This method has to determine which pivots should be " + "considered nonzero. For that, it uses the threshold value that " + "you can control by calling setThreshold(threshold).") + .def("maxPivot", &Self::maxPivot, bp::arg("self"), + "Returns the absolute value of the biggest pivot, i.e. the " + "biggest diagonal coefficient of U.") + .def("nonzeroPivots", &Self::nonzeroPivots, bp::arg("self"), + "Returns the number of nonzero pivots in the complete orthogonal " + "decomposition. " + "Here nonzero is meant in the exact sense, not in a fuzzy sense. " + "So that notion isn't really intrinsically interesting, but it is " + "still useful when implementing algorithms.") + .def("rank", &Self::rank, bp::arg("self"), + "Returns the rank of the matrix associated with the complete " + "orthogonal " + "decomposition.\n" + "\n" + "Note: This method has to determine which pivots should be " + "considered nonzero. For that, it uses the threshold value that " + "you can control by calling setThreshold(threshold).") + + .def("setThreshold", + (Self & (Self::*)(const RealScalar &)) & Self::setThreshold, + bp::args("self", "threshold"), + "Allows to prescribe a threshold to be used by certain methods, " + "such as rank(), who need to determine when pivots are to be " + "considered nonzero. This is not used for the complete orthogonal " + "decomposition " + "itself.\n" + "\n" + "When it needs to get the threshold value, Eigen calls " + "threshold(). By default, this uses a formula to automatically " + "determine a reasonable threshold. Once you have called the " + "present method setThreshold(const RealScalar&), your value is " + "used instead.\n" + "\n" + "Note: A pivot will be considered nonzero if its absolute value " + "is strictly greater than |pivot| ⩽ threshold×|maxpivot| where " + "maxpivot is the biggest pivot.", + bp::return_self<>()) + .def("threshold", &Self::threshold, bp::arg("self"), + "Returns the threshold that will be used by certain methods such " + "as rank().") + + .def("matrixQTZ", &Self::matrixQTZ, bp::arg("self"), + "Returns the matrix where the complete orthogonal decomposition " + "is stored.", + bp::return_value_policy<bp::copy_const_reference>()) + .def("matrixT", &Self::matrixT, bp::arg("self"), + "Returns the matrix where the complete orthogonal decomposition " + "is stored.", + bp::return_value_policy<bp::copy_const_reference>()) + .def("matrixZ", &Self::matrixZ, bp::arg("self"), + "Returns the matrix Z.") + + .def( + "compute", + (Solver & (Solver::*)(const Eigen::EigenBase<MatrixType> &matrix)) & + Solver::compute, + bp::args("self", "matrix"), + "Computes the complete orthogonal factorization of given matrix.", + bp::return_self<>()) + + .def("pseudoInverse", pseudoInverse, bp::arg("self"), + "Returns the pseudo-inverse of the matrix associated with the " + "complete orthogonal " + "decomposition.") + + .def("solve", &solve<MatrixXs>, bp::args("self", "B"), + "Returns the solution X of A X = B using the current " + "decomposition of A where B is a right hand side matrix."); + } + + static void expose() { + static const std::string classname = + "CompleteOrthogonalDecomposition" + scalar_name<Scalar>::shortname(); + expose(classname); + } + + static void expose(const std::string &name) { + bp::class_<Solver>( + name.c_str(), + "This class performs a rank-revealing complete orthogonal " + "decomposition of a matrix A into matrices P, Q, T, and Z such that:\n" + "AP=Q[T000]Z" + "by using Householder transformations. Here, P is a permutation " + "matrix, Q and Z are unitary matrices and T an upper triangular matrix " + "of size rank-by-rank. A may be rank deficient.", + bp::no_init) + .def(CompleteOrthogonalDecompositionSolverVisitor()) + .def(IdVisitor<Solver>()); + } + + private: + template <typename MatrixOrVector> + static MatrixOrVector solve(const Solver &self, const MatrixOrVector &vec) { + return self.solve(vec); + } + static MatrixXs pseudoInverse(const Self &self) { + return self.pseudoInverse(); + } +}; + +} // namespace eigenpy + +#endif // ifndef + // __eigenpy_decompositions_complete_orthogonal_decomposition_hpp__ diff --git a/include/eigenpy/decompositions/EigenSolver.hpp b/include/eigenpy/decompositions/EigenSolver.hpp index 14583b2e3e9cade3d0bd2d42f7a9b8a162eaa824..5aa91cb8d2c05ebb10c1e483ceae7b3c40df6334 100644 --- a/include/eigenpy/decompositions/EigenSolver.hpp +++ b/include/eigenpy/decompositions/EigenSolver.hpp @@ -2,8 +2,8 @@ * Copyright 2020 INRIA */ -#ifndef __eigenpy_decomposition_eigen_solver_hpp__ -#define __eigenpy_decomposition_eigen_solver_hpp__ +#ifndef __eigenpy_decompositions_eigen_solver_hpp__ +#define __eigenpy_decompositions_eigen_solver_hpp__ #include <Eigen/Core> #include <Eigen/Eigenvalues> @@ -90,4 +90,4 @@ struct EigenSolverVisitor } // namespace eigenpy -#endif // ifndef __eigenpy_decomposition_eigen_solver_hpp__ +#endif // ifndef __eigenpy_decompositions_eigen_solver_hpp__ diff --git a/include/eigenpy/decompositions/FullPivHouseholderQR.hpp b/include/eigenpy/decompositions/FullPivHouseholderQR.hpp new file mode 100644 index 0000000000000000000000000000000000000000..70dad1e454798167dc7b58bb5c75362294bd7d50 --- /dev/null +++ b/include/eigenpy/decompositions/FullPivHouseholderQR.hpp @@ -0,0 +1,183 @@ +/* + * Copyright 2024 INRIA + */ + +#ifndef __eigenpy_decompositions_full_piv_houselholder_qr_hpp__ +#define __eigenpy_decompositions_full_piv_houselholder_qr_hpp__ + +#include "eigenpy/eigenpy.hpp" +#include "eigenpy/utils/scalar-name.hpp" + +#include <Eigen/QR> + +namespace eigenpy { + +template <typename _MatrixType> +struct FullPivHouseholderQRSolverVisitor + : public boost::python::def_visitor< + FullPivHouseholderQRSolverVisitor<_MatrixType> > { + typedef _MatrixType MatrixType; + typedef typename MatrixType::Scalar Scalar; + typedef typename MatrixType::RealScalar RealScalar; + typedef Eigen::Matrix<Scalar, Eigen::Dynamic, 1, MatrixType::Options> + VectorXs; + typedef Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic, + MatrixType::Options> + MatrixXs; + typedef Eigen::FullPivHouseholderQR<MatrixType> Solver; + typedef Solver Self; + + template <class PyClass> + void visit(PyClass &cl) const { + cl.def(bp::init<>(bp::arg("self"), + "Default constructor.\n" + "The default constructor is useful in cases in which the " + "user intends to perform decompositions via " + "HouseholderQR.compute(matrix)")) + .def(bp::init<Eigen::DenseIndex, Eigen::DenseIndex>( + bp::args("self", "rows", "cols"), + "Default constructor with memory preallocation.\n" + "Like the default constructor but with preallocation of the " + "internal data according to the specified problem size. ")) + .def(bp::init<MatrixType>( + bp::args("self", "matrix"), + "Constructs a QR factorization from a given matrix.\n" + "This constructor computes the QR factorization of the matrix " + "matrix by calling the method compute().")) + + .def("absDeterminant", &Self::absDeterminant, bp::arg("self"), + "Returns the absolute value of the determinant of the matrix of " + "which *this is the QR decomposition.\n" + "It has only linear complexity (that is, O(n) where n is the " + "dimension of the square matrix) as the QR decomposition has " + "already been computed.\n" + "Note: This is only for square matrices.") + .def("logAbsDeterminant", &Self::logAbsDeterminant, bp::arg("self"), + "Returns the natural log of the absolute value of the determinant " + "of the matrix of which *this is the QR decomposition.\n" + "It has only linear complexity (that is, O(n) where n is the " + "dimension of the square matrix) as the QR decomposition has " + "already been computed.\n" + "Note: This is only for square matrices. This method is useful to " + "work around the risk of overflow/underflow that's inherent to " + "determinant computation.") + .def("dimensionOfKernel", &Self::dimensionOfKernel, bp::arg("self"), + "Returns the dimension of the kernel of the matrix of which *this " + "is the QR decomposition.") + .def("isInjective", &Self::isInjective, bp::arg("self"), + "Returns true if the matrix associated with this QR decomposition " + "represents an injective linear map, i.e. has trivial kernel; " + "false otherwise.\n" + "\n" + "Note: This method has to determine which pivots should be " + "considered nonzero. For that, it uses the threshold value that " + "you can control by calling setThreshold(threshold).") + .def("isInvertible", &Self::isInvertible, bp::arg("self"), + "Returns true if the matrix associated with the QR decomposition " + "is invertible.\n" + "\n" + "Note: This method has to determine which pivots should be " + "considered nonzero. For that, it uses the threshold value that " + "you can control by calling setThreshold(threshold).") + .def("isSurjective", &Self::isSurjective, bp::arg("self"), + "Returns true if the matrix associated with this QR decomposition " + "represents a surjective linear map; false otherwise.\n" + "\n" + "Note: This method has to determine which pivots should be " + "considered nonzero. For that, it uses the threshold value that " + "you can control by calling setThreshold(threshold).") + .def("maxPivot", &Self::maxPivot, bp::arg("self"), + "Returns the absolute value of the biggest pivot, i.e. the " + "biggest diagonal coefficient of U.") + .def("nonzeroPivots", &Self::nonzeroPivots, bp::arg("self"), + "Returns the number of nonzero pivots in the QR decomposition. " + "Here nonzero is meant in the exact sense, not in a fuzzy sense. " + "So that notion isn't really intrinsically interesting, but it is " + "still useful when implementing algorithms.") + .def("rank", &Self::rank, bp::arg("self"), + "Returns the rank of the matrix associated with the QR " + "decomposition.\n" + "\n" + "Note: This method has to determine which pivots should be " + "considered nonzero. For that, it uses the threshold value that " + "you can control by calling setThreshold(threshold).") + + .def("setThreshold", + (Self & (Self::*)(const RealScalar &)) & Self::setThreshold, + bp::args("self", "threshold"), + "Allows to prescribe a threshold to be used by certain methods, " + "such as rank(), who need to determine when pivots are to be " + "considered nonzero. This is not used for the QR decomposition " + "itself.\n" + "\n" + "When it needs to get the threshold value, Eigen calls " + "threshold(). By default, this uses a formula to automatically " + "determine a reasonable threshold. Once you have called the " + "present method setThreshold(const RealScalar&), your value is " + "used instead.\n" + "\n" + "Note: A pivot will be considered nonzero if its absolute value " + "is strictly greater than |pivot| ⩽ threshold×|maxpivot| where " + "maxpivot is the biggest pivot.", + bp::return_self<>()) + .def("threshold", &Self::threshold, bp::arg("self"), + "Returns the threshold that will be used by certain methods such " + "as rank().") + + .def("matrixQR", &Self::matrixQR, bp::arg("self"), + "Returns the matrix where the Householder QR decomposition is " + "stored in a LAPACK-compatible way.", + bp::return_value_policy<bp::copy_const_reference>()) + + .def( + "compute", + (Solver & (Solver::*)(const Eigen::EigenBase<MatrixType> &matrix)) & + Solver::compute, + bp::args("self", "matrix"), + "Computes the QR factorization of given matrix.", + bp::return_self<>()) + + .def("inverse", inverse, bp::arg("self"), + "Returns the inverse of the matrix associated with the QR " + "decomposition..") + + .def("solve", &solve<MatrixXs>, bp::args("self", "B"), + "Returns the solution X of A X = B using the current " + "decomposition of A where B is a right hand side matrix."); + } + + static void expose() { + static const std::string classname = + "FullPivHouseholderQR" + scalar_name<Scalar>::shortname(); + expose(classname); + } + + static void expose(const std::string &name) { + bp::class_<Solver>( + name.c_str(), + "This class performs a rank-revealing QR decomposition of a matrix A " + "into matrices P, P', Q and R such that:\n" + "PAP′=QR\n" + "by using Householder transformations. Here, P and P' are permutation " + "matrices, Q a unitary matrix and R an upper triangular matrix.\n" + "\n" + "This decomposition performs a very prudent full pivoting in order to " + "be rank-revealing and achieve optimal numerical stability. The " + "trade-off is that it is slower than HouseholderQR and " + "ColPivHouseholderQR.", + bp::no_init) + .def(FullPivHouseholderQRSolverVisitor()) + .def(IdVisitor<Solver>()); + } + + private: + template <typename MatrixOrVector> + static MatrixOrVector solve(const Solver &self, const MatrixOrVector &vec) { + return self.solve(vec); + } + static MatrixXs inverse(const Self &self) { return self.inverse(); } +}; + +} // namespace eigenpy + +#endif // ifndef __eigenpy_decompositions_full_piv_houselholder_qr_hpp__ diff --git a/include/eigenpy/decompositions/HouseholderQR.hpp b/include/eigenpy/decompositions/HouseholderQR.hpp new file mode 100644 index 0000000000000000000000000000000000000000..895000f4a0ac7131762f5ec0e42f8dbd23845ee5 --- /dev/null +++ b/include/eigenpy/decompositions/HouseholderQR.hpp @@ -0,0 +1,118 @@ +/* + * Copyright 2024 INRIA + */ + +#ifndef __eigenpy_decompositions_houselholder_qr_hpp__ +#define __eigenpy_decompositions_houselholder_qr_hpp__ + +#include "eigenpy/eigenpy.hpp" +#include "eigenpy/utils/scalar-name.hpp" + +#include <Eigen/QR> + +namespace eigenpy { + +template <typename _MatrixType> +struct HouseholderQRSolverVisitor + : public boost::python::def_visitor< + HouseholderQRSolverVisitor<_MatrixType> > { + typedef _MatrixType MatrixType; + typedef typename MatrixType::Scalar Scalar; + typedef typename MatrixType::RealScalar RealScalar; + typedef Eigen::Matrix<Scalar, Eigen::Dynamic, 1, MatrixType::Options> + VectorXs; + typedef Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic, + MatrixType::Options> + MatrixXs; + typedef Eigen::HouseholderQR<MatrixType> Solver; + typedef Solver Self; + + template <class PyClass> + void visit(PyClass &cl) const { + cl.def(bp::init<>(bp::arg("self"), + "Default constructor.\n" + "The default constructor is useful in cases in which the " + "user intends to perform decompositions via " + "HouseholderQR.compute(matrix)")) + .def(bp::init<Eigen::DenseIndex, Eigen::DenseIndex>( + bp::args("self", "rows", "cols"), + "Default constructor with memory preallocation.\n" + "Like the default constructor but with preallocation of the " + "internal data according to the specified problem size. ")) + .def(bp::init<MatrixType>( + bp::args("self", "matrix"), + "Constructs a QR factorization from a given matrix.\n" + "This constructor computes the QR factorization of the matrix " + "matrix by calling the method compute().")) + + .def("absDeterminant", &Self::absDeterminant, bp::arg("self"), + "Returns the absolute value of the determinant of the matrix of " + "which *this is the QR decomposition.\n" + "It has only linear complexity (that is, O(n) where n is the " + "dimension of the square matrix) as the QR decomposition has " + "already been computed.\n" + "Note: This is only for square matrices.") + .def("logAbsDeterminant", &Self::logAbsDeterminant, bp::arg("self"), + "Returns the natural log of the absolute value of the determinant " + "of the matrix of which *this is the QR decomposition.\n" + "It has only linear complexity (that is, O(n) where n is the " + "dimension of the square matrix) as the QR decomposition has " + "already been computed.\n" + "Note: This is only for square matrices. This method is useful to " + "work around the risk of overflow/underflow that's inherent to " + "determinant computation.") + + .def("matrixQR", &Self::matrixQR, bp::arg("self"), + "Returns the matrix where the Householder QR decomposition is " + "stored in a LAPACK-compatible way.", + bp::return_value_policy<bp::copy_const_reference>()) + + .def( + "compute", + (Solver & (Solver::*)(const Eigen::EigenBase<MatrixType> &matrix)) & + Solver::compute, + bp::args("self", "matrix"), + "Computes the QR factorization of given matrix.", + bp::return_self<>()) + + .def("solve", &solve<MatrixXs>, bp::args("self", "B"), + "Returns the solution X of A X = B using the current " + "decomposition of A where B is a right hand side matrix."); + } + + static void expose() { + static const std::string classname = + "HouseholderQR" + scalar_name<Scalar>::shortname(); + expose(classname); + } + + static void expose(const std::string &name) { + bp::class_<Solver>( + name.c_str(), + "This class performs a QR decomposition of a matrix A into matrices Q " + "and R such that A=QR by using Householder transformations.\n" + "Here, Q a unitary matrix and R an upper triangular matrix. The result " + "is stored in a compact way compatible with LAPACK.\n" + "\n" + "Note that no pivoting is performed. This is not a rank-revealing " + "decomposition. If you want that feature, use FullPivHouseholderQR or " + "ColPivHouseholderQR instead.\n" + "\n" + "This Householder QR decomposition is faster, but less numerically " + "stable and less feature-full than FullPivHouseholderQR or " + "ColPivHouseholderQR.", + bp::no_init) + .def(HouseholderQRSolverVisitor()) + .def(IdVisitor<Solver>()); + } + + private: + template <typename MatrixOrVector> + static MatrixOrVector solve(const Solver &self, const MatrixOrVector &vec) { + return self.solve(vec); + } +}; + +} // namespace eigenpy + +#endif // ifndef __eigenpy_decompositions_houselholder_qr_hpp__ diff --git a/include/eigenpy/decompositions/LDLT.hpp b/include/eigenpy/decompositions/LDLT.hpp index b148a23a2ddbc652cdb7a75e743c632b33a4931c..4f67d2713269760358a7088afca3a1708703a017 100644 --- a/include/eigenpy/decompositions/LDLT.hpp +++ b/include/eigenpy/decompositions/LDLT.hpp @@ -2,8 +2,8 @@ * Copyright 2020-2024 INRIA */ -#ifndef __eigenpy_decomposition_ldlt_hpp__ -#define __eigenpy_decomposition_ldlt_hpp__ +#ifndef __eigenpy_decompositions_ldlt_hpp__ +#define __eigenpy_decompositions_ldlt_hpp__ #include <Eigen/Cholesky> #include <Eigen/Core> @@ -141,4 +141,4 @@ struct LDLTSolverVisitor } // namespace eigenpy -#endif // ifndef __eigenpy_decomposition_ldlt_hpp__ +#endif // ifndef __eigenpy_decompositions_ldlt_hpp__ diff --git a/include/eigenpy/decompositions/LLT.hpp b/include/eigenpy/decompositions/LLT.hpp index 695c37300cc2072fe2ca7f96a26c9ce8aecbf85a..abf898ec3f672e90e033698bce19ee27ea0be072 100644 --- a/include/eigenpy/decompositions/LLT.hpp +++ b/include/eigenpy/decompositions/LLT.hpp @@ -2,8 +2,8 @@ * Copyright 2020-2024 INRIA */ -#ifndef __eigenpy_decomposition_llt_hpp__ -#define __eigenpy_decomposition_llt_hpp__ +#ifndef __eigenpy_decompositions_llt_hpp__ +#define __eigenpy_decompositions_llt_hpp__ #include <Eigen/Cholesky> #include <Eigen/Core> @@ -131,4 +131,4 @@ struct LLTSolverVisitor } // namespace eigenpy -#endif // ifndef __eigenpy_decomposition_llt_hpp__ +#endif // ifndef __eigenpy_decompositions_llt_hpp__ diff --git a/include/eigenpy/decompositions/PermutationMatrix.hpp b/include/eigenpy/decompositions/PermutationMatrix.hpp index 094bdbaf1144c0258c9e2ba36aa4910550f4ab72..d66396c85f8a0f1d796fa0320ae35887666bd207 100644 --- a/include/eigenpy/decompositions/PermutationMatrix.hpp +++ b/include/eigenpy/decompositions/PermutationMatrix.hpp @@ -2,8 +2,8 @@ * Copyright 2024 INRIA */ -#ifndef __eigenpy_decomposition_permutation_matrix_hpp__ -#define __eigenpy_decomposition_permutation_matrix_hpp__ +#ifndef __eigenpy_decompositions_permutation_matrix_hpp__ +#define __eigenpy_decompositions_permutation_matrix_hpp__ #include "eigenpy/eigenpy.hpp" #include "eigenpy/eigen/EigenBase.hpp" @@ -104,4 +104,4 @@ struct PermutationMatrixVisitor } // namespace eigenpy -#endif // ifndef __eigenpy_decomposition_permutation_matrix_hpp__ +#endif // ifndef __eigenpy_decompositions_permutation_matrix_hpp__ diff --git a/include/eigenpy/decompositions/QR.hpp b/include/eigenpy/decompositions/QR.hpp new file mode 100644 index 0000000000000000000000000000000000000000..14868a1fc262b86bb80cddd5de14ea8b35d3b066 --- /dev/null +++ b/include/eigenpy/decompositions/QR.hpp @@ -0,0 +1,13 @@ +/* + * Copyright 2024 INRIA + */ + +#ifndef __eigenpy_decompositions_qr_hpp__ +#define __eigenpy_decompositions_qr_hpp__ + +#include "eigenpy/decompositions/HouseholderQR.hpp" +#include "eigenpy/decompositions/FullPivHouseholderQR.hpp" +#include "eigenpy/decompositions/ColPivHouseholderQR.hpp" +#include "eigenpy/decompositions/CompleteOrthogonalDecomposition.hpp" + +#endif // ifndef __eigenpy_decompositions_qr_hpp__ diff --git a/include/eigenpy/decompositions/SelfAdjointEigenSolver.hpp b/include/eigenpy/decompositions/SelfAdjointEigenSolver.hpp index b2587a802459c192b6358a6abf3d4daf75292828..8a378fa3c88592847e1ccae377fe171baf28c858 100644 --- a/include/eigenpy/decompositions/SelfAdjointEigenSolver.hpp +++ b/include/eigenpy/decompositions/SelfAdjointEigenSolver.hpp @@ -2,8 +2,8 @@ * Copyright 2020-2024 INRIA */ -#ifndef __eigenpy_decomposition_self_adjoint_eigen_solver_hpp__ -#define __eigenpy_decomposition_self_adjoint_eigen_solver_hpp__ +#ifndef __eigenpy_decompositions_self_adjoint_eigen_solver_hpp__ +#define __eigenpy_decompositions_self_adjoint_eigen_solver_hpp__ #include <Eigen/Core> #include <Eigen/Eigenvalues> @@ -102,4 +102,4 @@ struct SelfAdjointEigenSolverVisitor } // namespace eigenpy -#endif // ifndef __eigenpy_decomposition_self_adjoint_eigen_solver_hpp__ +#endif // ifndef __eigenpy_decompositions_self_adjoint_eigen_solver_hpp__ diff --git a/include/eigenpy/decompositions/minres.hpp b/include/eigenpy/decompositions/minres.hpp index 7f5019c4b61f708f546c6cdbfc0638305fa6e65c..03ee7d19b260bba67608ca8f59c79dc403d58545 100644 --- a/include/eigenpy/decompositions/minres.hpp +++ b/include/eigenpy/decompositions/minres.hpp @@ -2,8 +2,8 @@ * Copyright 2021 INRIA */ -#ifndef __eigenpy_decomposition_minres_hpp__ -#define __eigenpy_decomposition_minres_hpp__ +#ifndef __eigenpy_decompositions_minres_hpp__ +#define __eigenpy_decompositions_minres_hpp__ #include <Eigen/Core> #include <iostream> @@ -178,4 +178,4 @@ struct MINRESSolverVisitor } // namespace eigenpy -#endif // ifndef __eigenpy_decomposition_minres_hpp__ +#endif // ifndef __eigenpy_decompositions_minres_hpp__ diff --git a/include/eigenpy/decompositions/sparse/LDLT.hpp b/include/eigenpy/decompositions/sparse/LDLT.hpp index 0d552e4e86cfb2f7c2b342d6e0ddbd34a826dac6..53772aede9ed9b292ff235d0d3371957524c4f5f 100644 --- a/include/eigenpy/decompositions/sparse/LDLT.hpp +++ b/include/eigenpy/decompositions/sparse/LDLT.hpp @@ -2,8 +2,8 @@ * Copyright 2024 INRIA */ -#ifndef __eigenpy_decomposition_sparse_ldlt_hpp__ -#define __eigenpy_decomposition_sparse_ldlt_hpp__ +#ifndef __eigenpy_decompositions_sparse_ldlt_hpp__ +#define __eigenpy_decompositions_sparse_ldlt_hpp__ #include "eigenpy/eigenpy.hpp" #include "eigenpy/decompositions/sparse/SimplicialCholesky.hpp" @@ -69,4 +69,4 @@ struct SimplicialLDLTVisitor } // namespace eigenpy -#endif // ifndef __eigenpy_decomposition_sparse_ldlt_hpp__ +#endif // ifndef __eigenpy_decompositions_sparse_ldlt_hpp__ diff --git a/include/eigenpy/decompositions/sparse/LLT.hpp b/include/eigenpy/decompositions/sparse/LLT.hpp index 3d3fa0d562e99fc468bfb976cd19dfe0e4fd8d4e..4e521636547f7a51d42bc40a5ba33fdee7ddc4fd 100644 --- a/include/eigenpy/decompositions/sparse/LLT.hpp +++ b/include/eigenpy/decompositions/sparse/LLT.hpp @@ -2,8 +2,8 @@ * Copyright 2024 INRIA */ -#ifndef __eigenpy_decomposition_sparse_llt_hpp__ -#define __eigenpy_decomposition_sparse_llt_hpp__ +#ifndef __eigenpy_decompositions_sparse_llt_hpp__ +#define __eigenpy_decompositions_sparse_llt_hpp__ #include "eigenpy/eigenpy.hpp" #include "eigenpy/decompositions/sparse/SimplicialCholesky.hpp" @@ -64,4 +64,4 @@ struct SimplicialLLTVisitor } // namespace eigenpy -#endif // ifndef __eigenpy_decomposition_sparse_llt_hpp__ +#endif // ifndef __eigenpy_decompositions_sparse_llt_hpp__ diff --git a/include/eigenpy/decompositions/sparse/SimplicialCholesky.hpp b/include/eigenpy/decompositions/sparse/SimplicialCholesky.hpp index ad109140b08eeb79e8705ce419086203bd6a6721..912674b5f02a0a8d57f44ce601f8996a3469aae1 100644 --- a/include/eigenpy/decompositions/sparse/SimplicialCholesky.hpp +++ b/include/eigenpy/decompositions/sparse/SimplicialCholesky.hpp @@ -2,8 +2,8 @@ * Copyright 2024 INRIA */ -#ifndef __eigenpy_decomposition_sparse_simplicial_cholesky_hpp__ -#define __eigenpy_decomposition_sparse_simplicial_cholesky_hpp__ +#ifndef __eigenpy_decompositions_sparse_simplicial_cholesky_hpp__ +#define __eigenpy_decompositions_sparse_simplicial_cholesky_hpp__ #include "eigenpy/eigenpy.hpp" #include "eigenpy/eigen/EigenBase.hpp" @@ -91,4 +91,4 @@ struct SimplicialCholeskyVisitor } // namespace eigenpy -#endif // ifndef __eigenpy_decomposition_sparse_simplicial_cholesky_hpp__ +#endif // ifndef __eigenpy_decompositions_sparse_simplicial_cholesky_hpp__ diff --git a/include/eigenpy/decompositions/sparse/SparseSolverBase.hpp b/include/eigenpy/decompositions/sparse/SparseSolverBase.hpp index 2afc1b917d1060ef037f5186f3b885f340cfc87d..a1b3c8522edc2fdefb318eb309d557f39488f4ba 100644 --- a/include/eigenpy/decompositions/sparse/SparseSolverBase.hpp +++ b/include/eigenpy/decompositions/sparse/SparseSolverBase.hpp @@ -2,8 +2,8 @@ * Copyright 2024 INRIA */ -#ifndef __eigenpy_decomposition_sparse_sparse_solver_base_hpp__ -#define __eigenpy_decomposition_sparse_sparse_solver_base_hpp__ +#ifndef __eigenpy_decompositions_sparse_sparse_solver_base_hpp__ +#define __eigenpy_decompositions_sparse_sparse_solver_base_hpp__ #include "eigenpy/eigenpy.hpp" #include "eigenpy/eigen/EigenBase.hpp" @@ -51,4 +51,4 @@ struct SparseSolverBaseVisitor } // namespace eigenpy -#endif // ifndef __eigenpy_decomposition_sparse_sparse_solver_base_hpp__ +#endif // ifndef __eigenpy_decompositions_sparse_sparse_solver_base_hpp__ diff --git a/src/decompositions/decompositions.cpp b/src/decompositions/decompositions.cpp index a497db1e8012c5ba0efd6058a77c01942405289b..77ae22f295b6097420454c60f91de861ad7e2707 100644 --- a/src/decompositions/decompositions.cpp +++ b/src/decompositions/decompositions.cpp @@ -12,6 +12,7 @@ void exposeEigenSolver(); void exposeSelfAdjointEigenSolver(); void exposeLLTSolver(); void exposeLDLTSolver(); +void exposeQRSolvers(); void exposeMINRESSolver(); void exposeSimplicialLLTSolver(); void exposeSimplicialLDLTSolver(); @@ -24,6 +25,7 @@ void exposeDecompositions() { exposeSelfAdjointEigenSolver(); exposeLLTSolver(); exposeLDLTSolver(); + exposeQRSolvers(); exposeMINRESSolver(); { diff --git a/src/decompositions/qr-solvers.cpp b/src/decompositions/qr-solvers.cpp new file mode 100644 index 0000000000000000000000000000000000000000..75ab771fa4c87f6a518b916346532fac42cee571 --- /dev/null +++ b/src/decompositions/qr-solvers.cpp @@ -0,0 +1,16 @@ +/* + * Copyright 2024 INRIA + */ + +#include "eigenpy/decompositions/QR.hpp" + +namespace eigenpy { +void exposeQRSolvers() { + using namespace Eigen; + HouseholderQRSolverVisitor<MatrixXd>::expose("HouseholderQR"); + FullPivHouseholderQRSolverVisitor<MatrixXd>::expose("FullPivHouseholderQR"); + ColPivHouseholderQRSolverVisitor<MatrixXd>::expose("ColPivHouseholderQR"); + CompleteOrthogonalDecompositionSolverVisitor<MatrixXd>::expose( + "CompleteOrthogonalDecomposition"); +} +} // namespace eigenpy diff --git a/unittest/CMakeLists.txt b/unittest/CMakeLists.txt index 38f75745f9f7672bf953bb8bba2ccd36190c6059..8d6571d1bade3aec7eced4e65c7feaeb02185771 100644 --- a/unittest/CMakeLists.txt +++ b/unittest/CMakeLists.txt @@ -136,6 +136,8 @@ add_python_eigenpy_lib_unit_test("py-LDLT" "unittest/python/test_LDLT.py") add_python_eigenpy_lib_unit_test("py-id" "unittest/python/test_id.py") +add_python_eigenpy_lib_unit_test("py-QR" "unittest/python/test_QR.py") + if(NOT WIN32) add_python_eigenpy_lib_unit_test("py-MINRES" "unittest/python/test_MINRES.py") endif(NOT WIN32) diff --git a/unittest/python/test_QR.py b/unittest/python/test_QR.py new file mode 100644 index 0000000000000000000000000000000000000000..aaf13cf09747f564fcc8d82c5b496de4e163d22e --- /dev/null +++ b/unittest/python/test_QR.py @@ -0,0 +1,76 @@ +import numpy as np + +import eigenpy + +rows = 20 +cols = 100 +rng = np.random.default_rng() + +A = rng.random((rows, cols)) + +# Test HouseholderQR decomposition +householder_qr = eigenpy.HouseholderQR() +householder_qr = eigenpy.HouseholderQR(rows, cols) +householder_qr = eigenpy.HouseholderQR(A) + +householder_qr_eye = eigenpy.HouseholderQR(np.eye(rows, rows)) +X = rng.random((rows, 20)) +assert householder_qr_eye.absDeterminant() == 1.0 +assert householder_qr_eye.logAbsDeterminant() == 0.0 + +Y = householder_qr_eye.solve(X) +assert (X == Y).all() + +# Test FullPivHouseholderQR decomposition +fullpiv_householder_qr = eigenpy.FullPivHouseholderQR() +fullpiv_householder_qr = eigenpy.FullPivHouseholderQR(rows, cols) +fullpiv_householder_qr = eigenpy.FullPivHouseholderQR(A) + +fullpiv_householder_qr = eigenpy.FullPivHouseholderQR(np.eye(rows, rows)) +X = rng.random((rows, 20)) +assert fullpiv_householder_qr.absDeterminant() == 1.0 +assert fullpiv_householder_qr.logAbsDeterminant() == 0.0 + +Y = fullpiv_householder_qr.solve(X) +assert (X == Y).all() +assert fullpiv_householder_qr.rank() == rows + +fullpiv_householder_qr.setThreshold(1e-8) +assert fullpiv_householder_qr.threshold() == 1e-8 +assert eigenpy.is_approx(np.eye(rows, rows), fullpiv_householder_qr.inverse()) + +# Test ColPivHouseholderQR decomposition +colpiv_householder_qr = eigenpy.ColPivHouseholderQR() +colpiv_householder_qr = eigenpy.ColPivHouseholderQR(rows, cols) +colpiv_householder_qr = eigenpy.ColPivHouseholderQR(A) + +colpiv_householder_qr = eigenpy.ColPivHouseholderQR(np.eye(rows, rows)) +X = rng.random((rows, 20)) +assert colpiv_householder_qr.absDeterminant() == 1.0 +assert colpiv_householder_qr.logAbsDeterminant() == 0.0 + +Y = colpiv_householder_qr.solve(X) +assert (X == Y).all() +assert colpiv_householder_qr.rank() == rows + +colpiv_householder_qr.setThreshold(1e-8) +assert colpiv_householder_qr.threshold() == 1e-8 +assert eigenpy.is_approx(np.eye(rows, rows), colpiv_householder_qr.inverse()) + +# Test CompleteOrthogonalDecomposition +cod = eigenpy.CompleteOrthogonalDecomposition() +cod = eigenpy.CompleteOrthogonalDecomposition(rows, cols) +cod = eigenpy.CompleteOrthogonalDecomposition(A) + +cod = eigenpy.CompleteOrthogonalDecomposition(np.eye(rows, rows)) +X = rng.random((rows, 20)) +assert cod.absDeterminant() == 1.0 +assert cod.logAbsDeterminant() == 0.0 + +Y = cod.solve(X) +assert (X == Y).all() +assert cod.rank() == rows + +cod.setThreshold(1e-8) +assert cod.threshold() == 1e-8 +assert eigenpy.is_approx(np.eye(rows, rows), cod.pseudoInverse())