Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • jcarpent/eigenpy
  • gsaurel/eigenpy
  • stack-of-tasks/eigenpy
3 results
Show changes
Showing
with 9714 additions and 78 deletions
///
/// Copyright (c) 2024 INRIA
///
#ifndef __eigenpy_type_info_hpp__
#define __eigenpy_type_info_hpp__
#include "eigenpy/fwd.hpp"
#include <boost/type_index.hpp>
#include <typeinfo>
#include <typeindex>
namespace eigenpy {
template <typename T>
boost::typeindex::type_index type_info(const T& value) {
return boost::typeindex::type_id_runtime(value);
}
template <typename T>
void expose_boost_type_info() {
boost::python::def(
"type_info",
+[](const T& value) -> boost::typeindex::type_index {
return boost::typeindex::type_id_runtime(value);
},
bp::arg("value"),
"Returns information of the type of value as a "
"boost::typeindex::type_index (can work without RTTI).");
boost::python::def(
"boost_type_info",
+[](const T& value) -> boost::typeindex::type_index {
return boost::typeindex::type_id_runtime(value);
},
bp::arg("value"),
"Returns information of the type of value as a "
"boost::typeindex::type_index (can work without RTTI).");
}
template <typename T>
void expose_std_type_info() {
boost::python::def(
"std_type_info",
+[](const T& value) -> std::type_index { return typeid(value); },
bp::arg("value"),
"Returns information of the type of value as a std::type_index.");
}
///
/// \brief Add the Python method type_info to query information of a type.
///
template <class C>
struct TypeInfoVisitor : public bp::def_visitor<TypeInfoVisitor<C> > {
template <class PyClass>
void visit(PyClass& cl) const {
cl.def("type_info", &boost_type_info, bp::arg("self"),
"Queries information of the type of *this as a "
"boost::typeindex::type_index (can work without RTTI).");
cl.def("boost_type_info", &boost_type_info, bp::arg("self"),
"Queries information of the type of *this as a "
"boost::typeindex::type_index (can work without RTTI).");
cl.def("std_type_info", &std_type_info, bp::arg("self"),
"Queries information of the type of *this as a std::type_index.");
}
private:
static boost::typeindex::type_index boost_type_info(const C& self) {
return boost::typeindex::type_id_runtime(self);
}
static std::type_index std_type_info(const C& self) { return typeid(self); }
};
} // namespace eigenpy
#endif // __eigenpy_type_info_hpp__
//
// Copyright (c) 2020-2021 INRIA
// Copyright (c) 2020-2025 INRIA
// code aptapted from
// https://github.com/numpy/numpy/blob/41977b24ae011a51f64faa75cb524c7350fdedd9/numpy/core/src/umath/_rational_tests.c.src
//
......@@ -9,6 +9,7 @@
#include "eigenpy/register.hpp"
#include "eigenpy/user-type.hpp"
#include "eigenpy/utils/python-compat.hpp"
namespace eigenpy {
namespace internal {
......@@ -150,6 +151,7 @@ EIGENPY_REGISTER_BINARY_OPERATOR(greater_equal, >=)
}
EIGENPY_REGISTER_UNARY_OPERATOR(negative, -)
EIGENPY_REGISTER_UNARY_OPERATOR(square, x *)
} // namespace internal
......@@ -207,11 +209,7 @@ void registerCommonUfunc() {
const int type_code = Register::getTypeCode<Scalar>();
PyObject *numpy_str;
#if PY_MAJOR_VERSION >= 3
numpy_str = PyUnicode_FromString("numpy");
#else
numpy_str = PyString_FromString("numpy");
#endif
numpy_str = PyStr_FromString("numpy");
PyObject *numpy;
numpy = PyImport_Import(numpy_str);
Py_DECREF(numpy_str);
......@@ -261,6 +259,7 @@ void registerCommonUfunc() {
// Unary operators
EIGENPY_REGISTER_UNARY_UFUNC(negative, type_code, Scalar, Scalar);
EIGENPY_REGISTER_UNARY_UFUNC(square, type_code, Scalar, Scalar);
Py_DECREF(numpy);
}
......
......@@ -132,34 +132,43 @@ struct SpecialMethods<T, NPY_USERDEF> {
eigenpy::Exception("Cannot retrieve the type stored in the array.");
return -1;
}
PyArrayObject* py_array = static_cast<PyArrayObject*>(array);
PyArray_Descr* descr = PyArray_DTYPE(py_array);
PyTypeObject* array_scalar_type = descr->typeobj;
PyTypeObject* src_obj_type = Py_TYPE(src_obj);
T& dest = *static_cast<T*>(dest_ptr);
if (array_scalar_type != src_obj_type) {
std::stringstream ss;
ss << "The input type is of wrong type. ";
ss << "The expected type is " << bp::type_info(typeid(T)).name()
<< std::endl;
eigenpy::Exception(ss.str());
return -1;
}
long long src_value = PyLong_AsLongLong(src_obj);
if (src_value == -1 && PyErr_Occurred()) {
std::stringstream ss;
ss << "The input type is of wrong type. ";
ss << "The expected type is " << bp::type_info(typeid(T)).name()
<< std::endl;
eigenpy::Exception(ss.str());
return -1;
}
dest = T(src_value);
bp::extract<T&> extract_src_obj(src_obj);
if (!extract_src_obj.check()) {
std::stringstream ss;
ss << "The input type is of wrong type. ";
ss << "The expected type is " << bp::type_info(typeid(T)).name()
<< std::endl;
eigenpy::Exception(ss.str());
return -1;
} else {
bp::extract<T&> extract_src_obj(src_obj);
if (!extract_src_obj.check()) {
std::cout << "if (!extract_src_obj.check())" << std::endl;
std::stringstream ss;
ss << "The input type is of wrong type. ";
ss << "The expected type is " << bp::type_info(typeid(T)).name()
<< std::endl;
eigenpy::Exception(ss.str());
return -1;
}
const T& src = extract_src_obj();
T& dest = *static_cast<T*>(dest_ptr);
dest = src;
}
const T& src = extract_src_obj();
T& dest = *static_cast<T*>(dest_ptr);
dest = src;
return 0;
}
......@@ -171,7 +180,8 @@ struct SpecialMethods<T, NPY_USERDEF> {
char* srcptr = static_cast<char*>(src);
PyArrayObject* py_array = static_cast<PyArrayObject*>(array);
PyArray_CopySwapFunc* copyswap = PyArray_DESCR(py_array)->f->copyswap;
PyArray_CopySwapFunc* copyswap =
PyDataType_GetArrFuncs(PyArray_DESCR(py_array))->copyswap;
for (npy_intp i = 0; i < n; i++) {
copyswap(dstptr, srcptr, swap, array);
......@@ -189,8 +199,8 @@ struct SpecialMethods<T, NPY_USERDEF> {
return (npy_bool)(value != ZeroValue);
} else {
T tmp_value;
PyArray_DESCR(py_array)->f->copyswap(
&tmp_value, ip, PyArray_ISBYTESWAPPED(py_array), array);
PyDataType_GetArrFuncs(PyArray_DESCR(py_array))
->copyswap(&tmp_value, ip, PyArray_ISBYTESWAPPED(py_array), array);
return (npy_bool)(tmp_value != ZeroValue);
}
}
......
#ifndef __eigenpy_utils_empty_visitor_hpp__
#define __eigenpy_utils_empty_visitor_hpp__
#include <boost/python.hpp>
namespace eigenpy {
struct EmptyPythonVisitor
: public ::boost::python::def_visitor<EmptyPythonVisitor> {
template <class classT>
void visit(classT &) const {}
};
} // namespace eigenpy
#endif // ifndef __eigenpy_utils_empty_visitor_hpp__
/*
* Copyright 2020 INRIA
* Copyright 2020-2024 INRIA
*/
#ifndef __eigenpy_utils_scalar_is_approx_hpp__
#define __eigenpy_utils_scalar_is_approx_hpp__
#ifndef __eigenpy_utils_is_approx_hpp__
#define __eigenpy_utils_is_approx_hpp__
#include <Eigen/Core>
#include <Eigen/SparseCore>
namespace eigenpy {
template <typename MatrixType1, typename MatrixType2>
inline EIGEN_DONT_INLINE bool is_approx(
const Eigen::MatrixBase<MatrixType1>& mat1,
const Eigen::MatrixBase<MatrixType2>& mat2,
const typename MatrixType1::Scalar& prec) {
EIGEN_DONT_INLINE bool is_approx(const Eigen::MatrixBase<MatrixType1>& mat1,
const Eigen::MatrixBase<MatrixType2>& mat2,
const typename MatrixType1::RealScalar& prec) {
return mat1.isApprox(mat2, prec);
}
template <typename MatrixType1, typename MatrixType2>
inline EIGEN_DONT_INLINE bool is_approx(
const Eigen::MatrixBase<MatrixType1>& mat1,
const Eigen::MatrixBase<MatrixType2>& mat2) {
EIGEN_DONT_INLINE bool is_approx(const Eigen::MatrixBase<MatrixType1>& mat1,
const Eigen::MatrixBase<MatrixType2>& mat2) {
return is_approx(
mat1, mat2,
Eigen::NumTraits<typename MatrixType1::Scalar>::dummy_precision());
Eigen::NumTraits<typename MatrixType1::RealScalar>::dummy_precision());
}
template <typename MatrixType1, typename MatrixType2>
EIGEN_DONT_INLINE bool is_approx(
const Eigen::SparseMatrixBase<MatrixType1>& mat1,
const Eigen::SparseMatrixBase<MatrixType2>& mat2,
const typename MatrixType1::RealScalar& prec) {
return mat1.isApprox(mat2, prec);
}
template <typename MatrixType1, typename MatrixType2>
EIGEN_DONT_INLINE bool is_approx(
const Eigen::SparseMatrixBase<MatrixType1>& mat1,
const Eigen::SparseMatrixBase<MatrixType2>& mat2) {
return is_approx(
mat1, mat2,
Eigen::NumTraits<typename MatrixType1::RealScalar>::dummy_precision());
}
} // namespace eigenpy
#endif // ifndef __eigenpy_utils_scalar_is_approx_hpp__
#endif // ifndef __eigenpy_utils_is_approx_hpp__
//
// Copyright (c) 2024 INRIA
//
//
#ifndef __eigenpy_utils_python_compat_hpp__
#define __eigenpy_utils_python_compat_hpp__
#if PY_MAJOR_VERSION >= 3
#define PyInt_Check PyLong_Check
#define PyStr_Check PyUnicode_Check
#define PyStr_FromString PyUnicode_FromString
#else
#define PyStr_Check PyString_Check
#define PyStr_FromString PyString_FromString
#endif
#endif // ifndef __eigenpy_utils_python_compat_hpp__
//
// Copyright (c) 2024 INRIA
//
//
#ifndef __eigenpy_utils_traits_hpp__
#define __eigenpy_utils_traits_hpp__
#include <type_traits>
#include <string>
#include <complex>
namespace eigenpy {
namespace details {
/// Trait to remove const&
template <typename T>
struct remove_cvref : std::remove_cv<typename std::remove_reference<T>::type> {
};
/// Trait to detect if T is a class or an union
template <typename T>
struct is_class_or_union
: std::integral_constant<bool, std::is_class<T>::value ||
std::is_union<T>::value> {};
/// trait to detect if T is a std::complex managed by Boost Python
template <typename T>
struct is_python_complex : std::false_type {};
/// From boost/python/converter/builtin_converters
template <>
struct is_python_complex<std::complex<float> > : std::true_type {};
template <>
struct is_python_complex<std::complex<double> > : std::true_type {};
template <>
struct is_python_complex<std::complex<long double> > : std::true_type {};
template <typename T>
struct is_python_primitive_type_helper
: std::integral_constant<bool, !is_class_or_union<T>::value ||
std::is_same<T, std::string>::value ||
std::is_same<T, std::wstring>::value ||
is_python_complex<T>::value> {};
/// Trait to detect if T is a Python primitive type
template <typename T>
struct is_python_primitive_type
: is_python_primitive_type_helper<typename remove_cvref<T>::type> {};
} // namespace details
} // namespace eigenpy
#endif // ifndef __eigenpy_utils_traits_hpp__
//
// Copyright (c) 2024 INRIA
//
#ifndef __eigenpy_utils_variant_hpp__
#define __eigenpy_utils_variant_hpp__
#include "eigenpy/fwd.hpp"
#include "eigenpy/utils/traits.hpp"
#include "eigenpy/utils/python-compat.hpp"
#include <boost/python.hpp>
#include <boost/variant.hpp>
#include <boost/mpl/for_each.hpp>
#include <boost/mpl/vector.hpp>
#include <type_traits>
#ifdef EIGENPY_WITH_CXX17_SUPPORT
#include <variant>
#endif
namespace eigenpy {
namespace details {
/// Allow to use std::variant and boost::variant with the same API
template <typename ResultType, typename Variant>
struct VariantVisitorType {};
/// Allow to get all alternatives in a boost::mpl vector
template <typename Variant>
struct VariantAlternatives {};
template <typename Variant>
struct empty_variant {};
template <typename T>
struct is_empty_variant : std::false_type {};
#ifdef EIGENPY_WITH_CXX17_SUPPORT
/// std::variant implementation
template <typename ResultType, typename... Alternatives>
struct VariantVisitorType<ResultType, std::variant<Alternatives...> > {
typedef std::variant<Alternatives...> variant_type;
typedef ResultType result_type;
template <typename Visitor, typename Visitable>
static result_type visit(Visitor&& visitor, Visitable&& v) {
return std::visit(std::forward<Visitor>(visitor),
std::forward<Visitable>(v));
}
result_type operator()(std::monostate) const {
return bp::incref(bp::object().ptr()); // None
}
};
template <typename... Alternatives>
struct VariantAlternatives<std::variant<Alternatives...> > {
typedef boost::mpl::vector<Alternatives...> types;
};
template <typename... Alternatives>
struct empty_variant<std::variant<Alternatives...> > {
typedef std::monostate type;
};
template <>
struct is_empty_variant<std::monostate> : std::true_type {};
#endif
/// boost::variant implementation
template <typename ResultType, typename... Alternatives>
struct VariantVisitorType<ResultType, boost::variant<Alternatives...> >
: boost::static_visitor<ResultType> {
typedef boost::variant<Alternatives...> variant_type;
typedef ResultType result_type;
template <typename Visitor, typename Visitable>
static result_type visit(Visitor&& visitor, Visitable&& visitable) {
return std::forward<Visitable>(visitable).apply_visitor(visitor);
}
result_type operator()(boost::blank) const {
return bp::incref(bp::object().ptr()); // None
}
};
template <typename... Alternatives>
struct VariantAlternatives<boost::variant<Alternatives...> > {
typedef typename boost::variant<Alternatives...>::types types;
};
template <typename... Alternatives>
struct empty_variant<boost::variant<Alternatives...> > {
typedef boost::blank type;
};
template <>
struct is_empty_variant<boost::blank> : std::true_type {};
/// Convert None to a {boost,std}::variant with boost::blank or std::monostate
/// value
template <typename Variant>
struct EmptyConvertible {
static void registration() {
bp::converter::registry::push_back(convertible, construct,
bp::type_id<Variant>());
}
// convertible only for None
static void* convertible(PyObject* obj) {
return (obj == Py_None) ? obj : nullptr;
};
// construct in place
static void construct(PyObject*,
bp::converter::rvalue_from_python_stage1_data* data) {
void* storage =
reinterpret_cast<bp::converter::rvalue_from_python_storage<Variant>*>(
data)
->storage.bytes;
new (storage) Variant(typename empty_variant<Variant>::type());
data->convertible = storage;
};
};
/// Implement convertible and expected_pytype for bool, integer and float
template <typename T, class Enable = void>
struct NumericConvertibleImpl {};
template <typename T>
struct NumericConvertibleImpl<
T, typename std::enable_if<std::is_same<T, bool>::value>::type> {
static void* convertible(PyObject* obj) {
return PyBool_Check(obj) ? obj : nullptr;
}
static PyTypeObject const* expected_pytype() { return &PyBool_Type; }
};
template <typename T>
struct NumericConvertibleImpl<
T, typename std::enable_if<!std::is_same<T, bool>::value &&
std::is_integral<T>::value>::type> {
static void* convertible(PyObject* obj) {
// PyLong return true for bool type
return (PyInt_Check(obj) && !PyBool_Check(obj)) ? obj : nullptr;
}
static PyTypeObject const* expected_pytype() { return &PyLong_Type; }
};
template <typename T>
struct NumericConvertibleImpl<
T, typename std::enable_if<std::is_floating_point<T>::value>::type> {
static void* convertible(PyObject* obj) {
return PyFloat_Check(obj) ? obj : nullptr;
}
static PyTypeObject const* expected_pytype() { return &PyFloat_Type; }
};
/// Convert numeric type to Variant without ambiguity
template <typename T, typename Variant>
struct NumericConvertible {
static void registration() {
bp::converter::registry::push_back(
&convertible, &bp::converter::implicit<T, Variant>::construct,
bp::type_id<Variant>()
#ifndef BOOST_PYTHON_NO_PY_SIGNATURES
,
&expected_pytype
#endif
);
}
static void* convertible(PyObject* obj) {
return NumericConvertibleImpl<T>::convertible(obj);
}
static PyTypeObject const* expected_pytype() {
return NumericConvertibleImpl<T>::expected_pytype();
}
};
/// Convert {boost,std}::variant<class...> alternative to a Python object.
/// This converter copy the alternative.
template <typename Variant>
struct VariantValueToObject : VariantVisitorType<PyObject*, Variant> {
typedef VariantVisitorType<PyObject*, Variant> Base;
typedef typename Base::result_type result_type;
typedef typename Base::variant_type variant_type;
static result_type convert(const variant_type& v) {
return Base::visit(VariantValueToObject(), v);
}
template <typename T>
result_type operator()(T& t) const {
return bp::incref(bp::object(t).ptr());
}
using Base::operator();
};
/// Convert {boost,std}::variant<class...> alternative reference to a Python
/// object. This converter return the alternative reference. The code that
/// create the reference holder is taken from \see
/// bp::to_python_indirect.
template <typename Variant>
struct VariantRefToObject : VariantVisitorType<PyObject*, Variant> {
typedef VariantVisitorType<PyObject*, Variant> Base;
typedef typename Base::result_type result_type;
typedef typename Base::variant_type variant_type;
static result_type convert(const variant_type& v) {
return Base::visit(VariantRefToObject(), v);
}
template <typename T,
typename std::enable_if<is_python_primitive_type<T>::value,
bool>::type = true>
result_type operator()(T t) const {
return bp::incref(bp::object(t).ptr());
}
template <typename T,
typename std::enable_if<!is_python_primitive_type<T>::value,
bool>::type = true>
result_type operator()(T& t) const {
return bp::detail::make_reference_holder::execute(&t);
}
/// Copy the object when it's None
using Base::operator();
};
/// Converter used in \see ReturnInternalVariant.
/// This is inspired by \see bp::reference_existing_object.
/// It will call \see VariantRefToObject to extract the alternative
/// reference.
template <typename Variant>
struct VariantConverter {
typedef Variant variant_type;
template <class T>
struct apply {
struct type {
PyObject* operator()(const variant_type& v) const {
return VariantRefToObject<variant_type>::convert(v);
}
#ifndef BOOST_PYTHON_NO_PY_SIGNATURES
PyTypeObject const* get_pytype() const {
return bp::converter::registered_pytype<variant_type>::get_pytype();
}
#endif
};
};
};
/// Convert an Alternative type to a Variant
template <typename Variant>
struct VariantConvertible {
typedef Variant variant_type;
template <class T, typename std::enable_if<is_empty_variant<T>::value,
bool>::type = true>
void operator()(T) {
EmptyConvertible<variant_type>::registration();
}
template <class T, typename std::enable_if<!is_empty_variant<T>::value &&
std::is_arithmetic<T>::value,
bool>::type = true>
void operator()(T) {
NumericConvertible<T, variant_type>::registration();
}
template <class T, typename std::enable_if<!is_empty_variant<T>::value &&
!std::is_arithmetic<T>::value,
bool>::type = true>
void operator()(T) {
bp::implicitly_convertible<T, variant_type>();
}
};
} // namespace details
/// Variant of \see bp::return_internal_reference that
/// extract {boost,std}::variant<class...> alternative reference before
/// converting it into a PyObject
template <typename Variant>
struct ReturnInternalVariant : bp::return_internal_reference<> {
typedef Variant variant_type;
typedef details::VariantConverter<variant_type> result_converter;
template <class ArgumentPackage>
static PyObject* postcall(ArgumentPackage const& args_, PyObject* result) {
// Don't run return_internal_reference postcall on primitive type
if (PyInt_Check(result) || PyBool_Check(result) || PyFloat_Check(result) ||
PyStr_Check(result) || PyComplex_Check(result)) {
return result;
}
return bp::return_internal_reference<>::postcall(args_, result);
}
};
/// Define a defaults converter to convert a {boost,std}::variant alternative to
/// a Python object by copy and to convert implicitly an alternative to a
/// {boost,std}::variant.
///
/// Example:
///
/// typedef boost::variant<Struct1, Struct2> MyVariant;
/// struct VariantHolder {
/// MyVariant variant;
/// };
/// ...
/// void expose() {
/// bp::class_<Struct1>("Struct1", bp::init<>());
/// bp::class_<Struct2>("Struct1", bp::init<>())
/// typedef eigenpy::VariantConverter<MyVariant> Converter;
/// Converter::registration();
///
/// bp::class_<VariantHolder>("VariantHolder", bp::init<>())
/// .add_property("variant",
/// bp::make_getter(&VariantHolder::variant,
/// Converter::return_internal_reference()),
/// bp::make_setter(&VariantHolder::variant));
/// }
template <typename Variant>
struct VariantConverter {
typedef Variant variant_type;
typedef ReturnInternalVariant<variant_type> return_internal_reference;
static void registration() {
typedef details::VariantValueToObject<variant_type> variant_to_value;
typedef typename details::VariantAlternatives<variant_type>::types types;
bp::to_python_converter<variant_type, variant_to_value>();
boost::mpl::for_each<types>(details::VariantConvertible<variant_type>());
}
};
} // namespace eigenpy
#endif // ifndef __eigenpy_utils_variant_hpp__
<?xml version="1.0"?>
<package format="3">
<name>eigenpy</name>
<version>2.9.2</version>
<version>3.10.3</version>
<description>Bindings between Numpy and Eigen using Boost.Python</description>
<maintainer email="justin.carpentier@inria.fr">Justin Carpentier</maintainer>
<maintainer email="opensource@wolfgangmerkt.com">Wolfgang Merkt</maintainer>
......@@ -20,7 +20,9 @@
<depend condition="$ROS_PYTHON_VERSION == 2">python</depend>
<depend condition="$ROS_PYTHON_VERSION == 3">python3</depend>
<depend condition="$ROS_PYTHON_VERSION == 2">python-numpy</depend>
<depend condition="$ROS_PYTHON_VERSION == 2">python-scipy</depend>
<depend condition="$ROS_PYTHON_VERSION == 3">python3-numpy</depend>
<depend condition="$ROS_PYTHON_VERSION == 3">python3-scipy</depend>
<depend>eigen</depend>
<depend>boost</depend>
......
Source diff could not be displayed: it is too large. Options to address this: view the blob.
[project]
name = "eigenpy"
version = "3.10.3"
description = "Bindings between Numpy and Eigen using Boost.Python"
platforms = ["linux-64", "osx-64", "osx-arm64", "win-64"]
channels = ["conda-forge"]
license = "BSD-2-Clause"
license-file = "LICENSE"
[build-dependencies]
ccache = ">=4.9.1"
cmake = ">=3.10"
cxx-compiler = ">=1.7.0"
ninja = ">=1.11"
pkg-config = ">=0.29.2"
git = ">=2.47.0"
[dependencies]
libboost-devel = ">=1.80.0"
libboost-python-devel = ">=1.80.0"
eigen = ">=3.4.0"
numpy = ">=1.22.0"
python = ">=3.9.0"
scipy = ">=1.10.0"
[activation]
scripts = ["development/scripts//pixi/activation.sh"]
[target.win-64.activation]
scripts = ["development/scripts//pixi/activation.bat"]
[tasks]
# We must avoid to set CMAKE_CXX_FLAGS because of WIN32
# https://discourse.cmake.org/t/strictly-appending-to-cmake-lang-flags/6478
configure = { cmd = [
"CXXFLAGS=$EIGENPY_CXX_FLAGS",
"cmake",
"-G",
"Ninja",
"-B",
"build",
"-S",
".",
"-DCMAKE_INSTALL_PREFIX=$CONDA_PREFIX",
"-DCMAKE_BUILD_TYPE=$EIGENPY_BUILD_TYPE",
"-DGENERATE_PYTHON_STUBS=$EIGENPY_PYTHON_STUBS",
"-DBUILD_WITH_CHOLMOD_SUPPORT=$EIGENPY_CHOLMOD_SUPPORT",
"-DBUILD_WITH_ACCELERATE_SUPPORT=$EIGENPY_ACCELERATE_SUPPORT",
] }
build = { cmd = "cmake --build build --target all", depends-on = ["configure"] }
clean = { cmd = "rm -rf build" }
test = { cmd = "ctest --test-dir build --output-on-failure", depends-on = [
"build",
] }
[feature.lint]
dependencies = { pre-commit = ">=3.6.2" }
tasks = { lint = { cmd = "pre-commit run --all" } }
# Increment the version number with EIGENPY_VERSION variable
[feature.new-version.dependencies]
tomlkit = ">=0.13.2"
[feature.new-version.tasks]
configure_new_version = { cmd = [
"CXXFLAGS=$EIGENPY_CXX_FLAGS",
"cmake",
"-G",
"Ninja",
"-B",
"build_new_version",
"-S",
".",
"-DCMAKE_INSTALL_PREFIX=$CONDA_PREFIX",
"-DCMAKE_BUILD_TYPE=$EIGENPY_BUILD_TYPE",
"-DGENERATE_PYTHON_STUBS=ON",
"-DBUILD_WITH_CHOLMOD_SUPPORT=ON",
"-DBUILD_WITH_ACCELERATE_SUPPORT=OFF",
] }
release_new_version = { cmd = "VERSION=$EIGENPY_VERSION cmake --build build_new_version --target release", depends-on = [
"configure_new_version",
] }
# Cholmod support
[feature.cholmod]
dependencies = { suitesparse = ">=5" }
activation = { env = { EIGENPY_CHOLMOD_SUPPORT = "ON" } }
# Accelerate only work on Apple ARM platform
[feature.accelerate]
[feature.accelerate.target.osx-arm64]
activation = { env = { EIGENPY_ACCELERATE_SUPPORT = "ON" } }
[feature.py312.dependencies]
python = "3.12.*"
[feature.py39.dependencies]
python = "3.9.*"
# Use clang-cl on Windows.
# We must use scripts instead of env to setup CC and CXX
# to avoid cxx-compiler to overwrite them.
[feature.clang-cl]
platforms = ["win-64"]
activation = { scripts = ["development/scripts//pixi/activation_clang_cl.bat"] }
# Use clang on GNU/Linux.
# We must use scripts instead of env to setup CC and CXX
# to avoid cxx-compiler to overwrite them.
[feature.clang]
platforms = ["linux-64"]
activation = { scripts = ["development/scripts//pixi/activation_clang.sh"] }
dependencies = { clangxx = "*" }
[environments]
default = { features = ["py312"], solve-group = "py312" }
clang = { features = ["clang", "py312"] }
lint = { features = ["lint"], solve-group = "py312" }
cholmod = { features = ["cholmod", "py312"], solve-group = "py312" }
accelerate = { features = ["accelerate", "py312"], solve-group = "py312" }
py39 = { features = ["py39"], solve-group = "py39" }
# Accelerate will only work in Eigen next release
all = { features = ["cholmod", "py312"], solve-group = "py312" }
all-py39 = { features = ["cholmod", "py39"], solve-group = "py39" }
all-clang-cl = { features = [
"cholmod",
"clang-cl",
"py312",
], solve-group = "py312" }
# Release a new software version
new-version = { features = [
"new-version",
"cholmod",
"py312",
], solve-group = "py312" }
[tool.ruff]
extend-exclude = ["cmake"]
[tool.ruff.lint]
extend-select = ["I", "NPY", "RUF", "UP", "W"]
[tool.ruff.lint.isort]
known-first-party = ["eigenpy"]
[tool.tomlsort]
all = true
#
# Copyright (c) 2014-2021 CNRS INRIA
# Copyright (c) 2014-2023 CNRS INRIA
#
# --- LIBRARY --- #
......@@ -9,14 +9,20 @@ set(PYWRAP
PARENT_SCOPE)
make_directory("${${PROJECT_NAME}_BINARY_DIR}/python/${PROJECT_NAME}")
include(${JRL_CMAKE_MODULES}/python-helpers.cmake)
include("${JRL_CMAKE_MODULES}/stubs.cmake")
add_custom_target(python)
set_target_properties(python PROPERTIES EXCLUDE_FROM_DEFAULT_BUILD True)
add_custom_target(${PROJECT_NAME}_python)
set_target_properties(${PROJECT_NAME}_python
PROPERTIES EXCLUDE_FROM_DEFAULT_BUILD True)
add_library(${PYWRAP} SHARED main.cpp)
add_dependencies(python ${PYWRAP})
add_library(${PYWRAP} MODULE main.cpp)
add_dependencies(${PROJECT_NAME}_python ${PYWRAP})
target_link_libraries(${PYWRAP} PUBLIC ${PROJECT_NAME})
python_build_get_target(python_build_target)
add_dependencies(${PYWRAP} ${python_build_target})
# BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS spews conversion warnings from int to
# long unsigned int. Unfortunately, using literals does not work in a macro. As
# such, this turns them off for the entire wrapper:
......@@ -35,13 +41,13 @@ set_target_properties(
PROPERTIES PREFIX ""
SUFFIX ${PYTHON_EXT_SUFFIX}
LIBRARY_OUTPUT_DIRECTORY
"${CMAKE_BINARY_DIR}/python/${PROJECT_NAME}"
"${PROJECT_BINARY_DIR}/python/${PROJECT_NAME}"
LIBRARY_OUTPUT_DIRECTORY_<CONFIG>
"${CMAKE_BINARY_DIR}/python/${PROJECT_NAME}"
"${PROJECT_BINARY_DIR}/python/${PROJECT_NAME}"
RUNTIME_OUTPUT_DIRECTORY
"${CMAKE_BINARY_DIR}/python/${PROJECT_NAME}"
"${PROJECT_BINARY_DIR}/python/${PROJECT_NAME}"
RUNTIME_OUTPUT_DIRECTORY_<CONFIG>
"${CMAKE_BINARY_DIR}/python/${PROJECT_NAME}")
"${PROJECT_BINARY_DIR}/python/${PROJECT_NAME}")
if(UNIX)
get_relative_rpath(${${PYWRAP}_INSTALL_DIR} ${PYWRAP}_INSTALL_RPATH)
......@@ -55,12 +61,14 @@ install(TARGETS ${PYWRAP} DESTINATION ${${PYWRAP}_INSTALL_DIR})
if(GENERATE_PYTHON_STUBS)
load_stubgen()
# Set PYWRAP and PROJECT_NAME as stubs dependencies PROJECT_NAME is mandatory
# (even if it's a PYWRAP dependency) to find PROJECT_NAME name DLL on windows
generate_stubs(${CMAKE_CURRENT_BINARY_DIR} ${PROJECT_NAME}
${ABSOLUTE_PYTHON_SITELIB} ${PYWRAP})
${ABSOLUTE_PYTHON_SITELIB} ${PYWRAP} ${PROJECT_NAME})
endif(GENERATE_PYTHON_STUBS)
# --- INSTALL SCRIPTS
set(PYTHON_FILES __init__.py)
set(PYTHON_FILES __init__.py windows_dll_manager.py)
foreach(python ${PYTHON_FILES})
python_build(${PROJECT_NAME} ${python})
......
......@@ -2,5 +2,29 @@
# Copyright (c) 2017-2021 CNRS INRIA
#
from .eigenpy_pywrap import * # noqa
from .eigenpy_pywrap import __version__, __raw_version__ # noqa
# On Windows, if eigenpy.dll is not in the same directory than
# the .pyd, it will not be loaded.
# We first try to load eigenpy, then, if it fail and we are on Windows:
# 1. We add all paths inside eigenpy_WINDOWS_DLL_PATH to DllDirectory
# 2. If EIGENPY_WINDOWS_DLL_PATH we add the relative path from the
# package directory to the bin directory to DllDirectory
# This solution is inspired from:
# - https://github.com/PixarAnimationStudios/OpenUSD/pull/1511/files
# - https://stackoverflow.com/questions/65334494/python-c-extension-packaging-dll-along-with-pyd
# More resources on https://github.com/diffpy/pyobjcryst/issues/33
try:
from .eigenpy_pywrap import * # noqa
from .eigenpy_pywrap import __raw_version__, __version__
except ImportError:
import platform
if platform.system() == "Windows":
from .windows_dll_manager import build_directory_manager, get_dll_paths
with build_directory_manager() as dll_dir_manager:
for p in get_dll_paths():
dll_dir_manager.add_dll_directory(p)
from .eigenpy_pywrap import * # noqa
from .eigenpy_pywrap import __raw_version__, __version__ # noqa
else:
raise
import contextlib
import os
def get_dll_paths():
eigenpy_paths = os.getenv("EIGENPY_WINDOWS_DLL_PATH")
if eigenpy_paths is None:
# From https://peps.python.org/pep-0250/#implementation
# lib/python-version/site-packages/package
RELATIVE_DLL_PATH1 = "..\\..\\..\\..\\bin"
# lib/site-packages/package
RELATIVE_DLL_PATH2 = "..\\..\\..\\bin"
# For unit test
RELATIVE_DLL_PATH3 = "..\\..\\bin"
return [
os.path.join(os.path.dirname(__file__), RELATIVE_DLL_PATH1),
os.path.join(os.path.dirname(__file__), RELATIVE_DLL_PATH2),
os.path.join(os.path.dirname(__file__), RELATIVE_DLL_PATH3),
]
else:
return eigenpy_paths.split(os.pathsep)
class DllDirectoryManager(contextlib.AbstractContextManager):
"""Restore DllDirectory state after importing Python module"""
def add_dll_directory(self, dll_dir: str):
# add_dll_directory can fail on relative path and non
# existing path.
# Since we don't know all the fail criterion we just ignore
# thrown exception
try:
self.dll_dirs.append(os.add_dll_directory(dll_dir))
except OSError:
pass
def __enter__(self):
self.dll_dirs = []
return self
def __exit__(self, *exc_details):
for d in self.dll_dirs:
d.close()
def build_directory_manager():
return DllDirectoryManager()
/*
* Copyright 2014-2019, CNRS
* Copyright 2018-2023, INRIA
* Copyright 2018-2024, INRIA
*/
#include <boost/python/scope.hpp>
......@@ -17,6 +17,37 @@
using namespace eigenpy;
template <typename Scalar>
void exposeIsApprox() {
enum { Options = 0 };
EIGENPY_MAKE_TYPEDEFS(Scalar, Options, s, Eigen::Dynamic, X);
EIGENPY_UNUSED_TYPE(VectorXs);
EIGENPY_UNUSED_TYPE(RowVectorXs);
// typedef Eigen::SparseMatrix<Scalar, Options> SparseMatrixXs;
typedef typename MatrixXs::RealScalar RealScalar;
using namespace Eigen;
const RealScalar dummy_precision =
Eigen::NumTraits<RealScalar>::dummy_precision();
bp::def("is_approx",
(bool (*)(const Eigen::MatrixBase<MatrixXs> &,
const Eigen::MatrixBase<MatrixXs> &,
const RealScalar &))&is_approx,
(bp::arg("A"), bp::arg("B"), bp::arg("prec") = dummy_precision),
"Returns True if A is approximately equal to B, within the "
"precision determined by prec.");
// bp::def("is_approx",
// (bool (*)(const Eigen::SparseMatrixBase<SparseMatrixXs> &,
// const Eigen::SparseMatrixBase<SparseMatrixXs> &,
// const RealScalar &)) &
// is_approx,
// (bp::arg("A"), bp::arg("B"), bp::arg("prec") = dummy_precision),
// "Returns True if A is approximately equal to B, within the "
// "precision determined by prec.");
}
BOOST_PYTHON_MODULE(eigenpy_pywrap) {
enableEigenPy();
......@@ -46,17 +77,8 @@ BOOST_PYTHON_MODULE(eigenpy_pywrap) {
register_symbolic_link_to_registered_type<Eigen::ComputationInfo>();
}
{
using namespace Eigen;
bp::def("is_approx",
(bool (*)(const Eigen::MatrixBase<MatrixXd> &,
const Eigen::MatrixBase<MatrixXd> &, const double &)) &
is_approx<MatrixXd, MatrixXd>,
(bp::arg("A"), bp::arg("B"), bp::arg("prec") = 1e-12),
"Returns True if A is approximately equal to B, within the "
"precision determined by prec.");
}
exposeIsApprox<double>();
exposeIsApprox<std::complex<double> >();
exposeDecompositions();
}
/*
* Copyright 2024 INRIA
*/
#include "eigenpy/fwd.hpp"
#include "eigenpy/decompositions/decompositions.hpp"
#include "eigenpy/decompositions/sparse/accelerate/accelerate.hpp"
namespace eigenpy {
void exposeAccelerate() {
using namespace Eigen;
typedef Eigen::SparseMatrix<double, Eigen::ColMajor> ColMajorSparseMatrix;
// typedef Eigen::SparseMatrix<double,Eigen::RowMajor> RowMajorSparseMatrix;
bp::enum_<SparseOrder_t>("SparseOrder")
.value("SparseOrderUser", SparseOrderUser)
.value("SparseOrderAMD", SparseOrderAMD)
.value("SparseOrderMetis", SparseOrderMetis)
.value("SparseOrderCOLAMD", SparseOrderCOLAMD);
#define EXPOSE_ACCELERATE_DECOMPOSITION(name, doc) \
AccelerateImplVisitor<name<ColMajorSparseMatrix> >::expose( \
EIGENPY_STRINGIZE(name), doc)
EXPOSE_ACCELERATE_DECOMPOSITION(
AccelerateLLT,
"A direct Cholesky (LLT) factorization and solver based on Accelerate.");
EXPOSE_ACCELERATE_DECOMPOSITION(AccelerateLDLT,
"The default Cholesky (LDLT) factorization "
"and solver based on Accelerate.");
EXPOSE_ACCELERATE_DECOMPOSITION(
AccelerateLDLTUnpivoted,
"A direct Cholesky-like LDL^T factorization and solver based on "
"Accelerate with only 1x1 pivots and no pivoting.");
EXPOSE_ACCELERATE_DECOMPOSITION(
AccelerateLDLTSBK,
"A direct Cholesky (LDLT) factorization and solver based on Accelerate "
"with Supernode Bunch-Kaufman and static pivoting.");
EXPOSE_ACCELERATE_DECOMPOSITION(
AccelerateLDLTTPP,
"A direct Cholesky (LDLT) factorization and solver based on Accelerate "
"with full threshold partial pivoting.");
EXPOSE_ACCELERATE_DECOMPOSITION(
AccelerateQR, "A QR factorization and solver based on Accelerate.");
EXPOSE_ACCELERATE_DECOMPOSITION(
AccelerateCholeskyAtA,
"A QR factorization and solver based on Accelerate without storing Q "
"(equivalent to A^TA = R^T R).");
}
} // namespace eigenpy
/*
* Copyright 2024 INRIA
*/
#include "eigenpy/fwd.hpp"
#include "eigenpy/decompositions/decompositions.hpp"
#include "eigenpy/decompositions/sparse/cholmod/CholmodSimplicialLLT.hpp"
#include "eigenpy/decompositions/sparse/cholmod/CholmodSimplicialLDLT.hpp"
#include "eigenpy/decompositions/sparse/cholmod/CholmodSupernodalLLT.hpp"
namespace eigenpy {
void exposeCholmod() {
using namespace Eigen;
typedef Eigen::SparseMatrix<double, Eigen::ColMajor> ColMajorSparseMatrix;
// typedef Eigen::SparseMatrix<double,Eigen::RowMajor> RowMajorSparseMatrix;
bp::enum_<CholmodMode>("CholmodMode")
.value("CholmodAuto", CholmodAuto)
.value("CholmodSimplicialLLt", CholmodSimplicialLLt)
.value("CholmodSupernodalLLt", CholmodSupernodalLLt)
.value("CholmodLDLt", CholmodLDLt);
CholmodSimplicialLLTVisitor<ColMajorSparseMatrix>::expose(
"CholmodSimplicialLLT");
CholmodSimplicialLDLTVisitor<ColMajorSparseMatrix>::expose(
"CholmodSimplicialLDLT");
CholmodSupernodalLLTVisitor<ColMajorSparseMatrix>::expose(
"CholmodSupernodalLLT");
}
} // namespace eigenpy
/*
* Copyright 2020-2021 INRIA
* Copyright 2020-2024 INRIA
*/
#include "eigenpy/decompositions/decompositions.hpp"
#include "eigenpy/decompositions/EigenSolver.hpp"
#include "eigenpy/decompositions/LDLT.hpp"
#include "eigenpy/decompositions/LLT.hpp"
#include "eigenpy/decompositions/SelfAdjointEigenSolver.hpp"
#include "eigenpy/decompositions/minres.hpp"
#include "eigenpy/fwd.hpp"
namespace eigenpy {
void exposeEigenSolver();
void exposeSelfAdjointEigenSolver();
void exposeLLTSolver();
void exposeLDLTSolver();
void exposeQRSolvers();
void exposeMINRESSolver();
void exposeSimplicialLLTSolver();
void exposeSimplicialLDLTSolver();
void exposePermutationMatrix();
void exposeDecompositions() {
using namespace Eigen;
EigenSolverVisitor<MatrixXd>::expose("EigenSolver");
SelfAdjointEigenSolverVisitor<MatrixXd>::expose("SelfAdjointEigenSolver");
LLTSolverVisitor<MatrixXd>::expose("LLT");
LDLTSolverVisitor<MatrixXd>::expose("LDLT");
MINRESSolverVisitor<MatrixXd>::expose("MINRES");
exposeEigenSolver();
exposeSelfAdjointEigenSolver();
exposeLLTSolver();
exposeLDLTSolver();
exposeQRSolvers();
exposeMINRESSolver();
{
bp::enum_<DecompositionOptions>("DecompositionOptions")
......@@ -34,5 +40,19 @@ void exposeDecompositions() {
.value("ABx_lx", ABx_lx)
.value("BAx_lx", BAx_lx);
}
// Expose sparse decompositions
exposeSimplicialLLTSolver();
exposeSimplicialLDLTSolver();
exposePermutationMatrix();
#ifdef EIGENPY_WITH_CHOLMOD_SUPPORT
exposeCholmod();
#endif
#ifdef EIGENPY_WITH_ACCELERATE_SUPPORT
exposeAccelerate();
#endif
}
} // namespace eigenpy
/*
* Copyright 2024 INRIA
*/
#include "eigenpy/decompositions/EigenSolver.hpp"
namespace eigenpy {
void exposeEigenSolver() {
using namespace Eigen;
EigenSolverVisitor<MatrixXd>::expose("EigenSolver");
}
} // namespace eigenpy