From b66155d535c621bb44adac293e43e9241f9e99c8 Mon Sep 17 00:00:00 2001 From: silviana amethyst <1388063+ofloveandhate@users.noreply.github.com> Date: Thu, 18 May 2023 15:29:02 -0500 Subject: [PATCH 01/16] source and header for example project --- .../custom_numeric_type/include/header.hpp | 229 ++++++++++++++++++ examples/custom_numeric_type/src/src.cpp | 62 +++++ 2 files changed, 291 insertions(+) create mode 100644 examples/custom_numeric_type/include/header.hpp create mode 100644 examples/custom_numeric_type/src/src.cpp diff --git a/examples/custom_numeric_type/include/header.hpp b/examples/custom_numeric_type/include/header.hpp new file mode 100644 index 000000000..97536d972 --- /dev/null +++ b/examples/custom_numeric_type/include/header.hpp @@ -0,0 +1,229 @@ + + +#pragma once + +#ifndef EXAMPLE_CUSTOM_NUMERIC_TYPE +#define EXAMPLE_CUSTOM_NUMERIC_TYPE + +#include +#include +#include + +#include + + +namespace bmp = boost::multiprecision; +using bmp::backends::mpc_complex_backend; +using mpfr_complex = bmp::number, bmp::et_on >; // T is a variable-precision complex number with expression templates turned on. + + + +void Expose(); + + + + + + +// this code derived from +// https://github.com/stack-of-tasks/eigenpy/issues/365 +// where I asked about using custom types, and @jcarpent responded with a discussion +// of an application of this in Pinnochio, a library for rigid body dynamics. +namespace eigenpy +{ + namespace internal + { + + + +// a template specialization for complex numbers + // derived directly from the example for Pinnochio +template <> +struct getitem +{ + static PyObject* run(void* data, void* /* arr */) { + mpfr_complex & mpfr_scalar = *static_cast(data); + auto & backend = mpfr_scalar.backend(); + + if(backend.data()[0].re->_mpfr_d == 0) // If the mpfr_scalar is not initialized, we have to init it. + { + mpfr_scalar = mpfr_complex(0); + } + boost::python::object m(boost::ref(mpfr_scalar)); + Py_INCREF(m.ptr()); + return m.ptr(); + } +}; + + +} // namespace internal + + + + + + +// i lifted this from EigenPy and adapted it, basically removing the calls for the comparitors. +template +void registerUfunct_without_comparitors(){ + const int type_code = Register::getTypeCode(); + + PyObject *numpy_str; + #if PY_MAJOR_VERSION >= 3 + numpy_str = PyUnicode_FromString("numpy"); + #else + numpy_str = PyString_FromString("numpy"); + #endif + PyObject *numpy; + numpy = PyImport_Import(numpy_str); + Py_DECREF(numpy_str); + + import_ufunc(); + + // Matrix multiply + { + int types[3] = {type_code, type_code, type_code}; + + std::stringstream ss; + ss << "return result of multiplying two matrices of "; + ss << bp::type_info(typeid(Scalar)).name(); + PyUFuncObject *ufunc = + (PyUFuncObject *)PyObject_GetAttrString(numpy, "matmul"); + if (!ufunc) { + std::stringstream ss; + ss << "Impossible to define matrix_multiply for given type " + << bp::type_info(typeid(Scalar)).name() << std::endl; + eigenpy::Exception(ss.str()); + } + if (PyUFunc_RegisterLoopForType((PyUFuncObject *)ufunc, type_code, + &internal::gufunc_matrix_multiply, + types, 0) < 0) { + std::stringstream ss; + ss << "Impossible to register matrix_multiply for given type " + << bp::type_info(typeid(Scalar)).name() << std::endl; + eigenpy::Exception(ss.str()); + } + + Py_DECREF(ufunc); + } + + // Binary operators + EIGENPY_REGISTER_BINARY_UFUNC(add, type_code, Scalar, Scalar, Scalar); + EIGENPY_REGISTER_BINARY_UFUNC(subtract, type_code, Scalar, Scalar, Scalar); + EIGENPY_REGISTER_BINARY_UFUNC(multiply, type_code, Scalar, Scalar, Scalar); + EIGENPY_REGISTER_BINARY_UFUNC(divide, type_code, Scalar, Scalar, Scalar); + + // Comparison operators + EIGENPY_REGISTER_BINARY_UFUNC(equal, type_code, Scalar, Scalar, bool); + EIGENPY_REGISTER_BINARY_UFUNC(not_equal, type_code, Scalar, Scalar, bool); + + //these are commented out because the comparisons are NOT defined for complex types!! + // EIGENPY_REGISTER_BINARY_UFUNC(greater, type_code, Scalar, Scalar, bool); + // EIGENPY_REGISTER_BINARY_UFUNC(less, type_code, Scalar, Scalar, bool); + // EIGENPY_REGISTER_BINARY_UFUNC(greater_equal, type_code, Scalar, Scalar, bool); + // EIGENPY_REGISTER_BINARY_UFUNC(less_equal, type_code, Scalar, Scalar, bool); + + // Unary operators + EIGENPY_REGISTER_UNARY_UFUNC(negative, type_code, Scalar, Scalar); + + Py_DECREF(numpy); +} + +} // namespace eigenpy + + + +namespace bp = boost::python; + +// this derived directly from the code at https://github.com/stack-of-tasks/eigenpy/issues/365, in which this example was requested +template +struct BoostNumberPythonVisitor +: public boost::python::def_visitor< BoostNumberPythonVisitor > +{ + +public: + + template + void visit(PyClass& cl) const + { + cl + .def(bp::init<>("Default constructor.",bp::arg("self"))) + .def(bp::init("Copy constructor.",bp::args("self","value"))) + .def(bp::init("Constructor from a string.",bp::args("self","str_value"))) + .def(bp::init("Constructor from a pair of strings.",bp::args("self","real","imag"))) + + + .def(bp::self + bp::self) + .def(bp::self += bp::self) + .def(bp::self - bp::self) + .def(bp::self -= bp::self) + .def(bp::self * bp::self) + .def(bp::self *= bp::self) + .def(bp::self / bp::self) + .def(bp::self /= bp::self) + + .def(bp::self == bp::self) + .def(bp::self != bp::self) + .def(bp::self_ns::pow(bp::self_ns::self,long())) + + + + .def("str",&BoostNumber::str,bp::args("self","precision","scientific")) + + .def("default_precision", + static_cast(BoostNumber::default_precision), + "Get the default precision of the class.") + .def("default_precision", + static_cast(BoostNumber::default_precision),bp::arg("digits10"), + "Set the default precision of the class.") + .staticmethod("default_precision") + + .def("precision", + static_cast(&BoostNumber::precision), + bp::arg("self"), + "Get the precision of this.") + .def("precision", + static_cast(&BoostNumber::precision), + bp::args("self","digits10"), + "Set the precision of this.") + + + .def("__str__",&print,bp::arg("self")) + .def("__repr__",&print,bp::arg("self")) + + .def("set_display_precision",&set_display_precision,bp::arg("digit"), + "Set the precision when printing values.") + .staticmethod("set_display_precision") + + .def("get_display_precision",&get_display_precision, + "Get the precision when printing values.", + bp::return_value_policy()) + .staticmethod("get_display_precision") + + ; + + } + + static std::string print(const BoostNumber & self) + { + return self.str(get_display_precision(), std::ios_base::dec); + } + + static void set_display_precision(const int digit) + { + get_display_precision() = digit; + } + + static int & get_display_precision() + { + static int precision = BoostNumber::default_precision(); + return precision; + } +}; + + + + +#endif + + diff --git a/examples/custom_numeric_type/src/src.cpp b/examples/custom_numeric_type/src/src.cpp new file mode 100644 index 000000000..a47aaf810 --- /dev/null +++ b/examples/custom_numeric_type/src/src.cpp @@ -0,0 +1,62 @@ +#include "header.hpp" + + + +BOOST_PYTHON_MODULE(eigenpy_example_custom_numeric_type) // this name must match the name of the generated .so file. +{ + // see https://stackoverflow.com/questions/6114462/how-to-override-the-automatically-created-docstring-data-for-boostpython + // docstring_options d(true, true, false); // local_ + boost::python::docstring_options docopt; + docopt.enable_all(); + docopt.disable_cpp_signatures(); + + boost::python::object package = boost::python::scope(); + package.attr("__path__") = "eigenpy_example_custom_numeric_type"; + + + Expose(); +} + + + +#define IMPLICITLY_CONVERTIBLE(T1,T2) \ + boost::python::implicitly_convertible(); + +void Expose(){ + + eigenpy::enableEigenPy(); + + boost::python::class_("MpfrComplex", + "",bp::no_init) + .def(BoostNumberPythonVisitor()) + ; + + + + eigenpy::registerNewType(); + eigenpy::registerUfunct_without_comparitors(); + + + eigenpy::registerCast(true); + eigenpy::registerCast(true); + eigenpy::registerCast(true); + + + + IMPLICITLY_CONVERTIBLE(int,mpfr_complex); + IMPLICITLY_CONVERTIBLE(long,mpfr_complex); + IMPLICITLY_CONVERTIBLE(int64_t,mpfr_complex); + + using VecX = Eigen::Matrix; + using MatXX = Eigen::Matrix; + + + + + eigenpy::enableEigenPySpecific(); + eigenpy::enableEigenPySpecific(); +} + + + +#undef IMPLICITLY_CONVERTIBLE From 26151126944b3d279008588154c51fdb8d9583df Mon Sep 17 00:00:00 2001 From: silviana amethyst <1388063+ofloveandhate@users.noreply.github.com> Date: Thu, 18 May 2023 15:30:33 -0500 Subject: [PATCH 02/16] build system directly derived from other files inside eigenpy --- examples/custom_numeric_type/CMakeLists.txt | 226 ++++++++++++++++++++ examples/custom_numeric_type/package.xml | 32 +++ 2 files changed, 258 insertions(+) create mode 100644 examples/custom_numeric_type/CMakeLists.txt create mode 100644 examples/custom_numeric_type/package.xml diff --git a/examples/custom_numeric_type/CMakeLists.txt b/examples/custom_numeric_type/CMakeLists.txt new file mode 100644 index 000000000..6fade50b0 --- /dev/null +++ b/examples/custom_numeric_type/CMakeLists.txt @@ -0,0 +1,226 @@ +# derived from eigenpy/CMakeLists.txt for this example + +cmake_minimum_required(VERSION 3.1) + +set(PROJECT_NAME eigenpy_example_custom_numeric_type) +set(PROJECT_DESCRIPTION "An example of using eigenpy with a custom numeric type: Boost.Multiprecision complex numbers") +set(PROJECT_URL "http://github.com/stack-of-tasks/eigenpy/examples/custom_numeric_type") +set(PROJECT_USE_CMAKE_EXPORT TRUE) +set(PROJECT_USE_KEYWORD_LINK_LIBRARIES TRUE) +set(PROJECT_CUSTOM_HEADER_EXTENSION "hpp") +set(PROJECT_COMPATIBILITY_VERSION AnyNewerVersion) + + + +# Check if the submodule cmake have been initialized +set(JRL_CMAKE_MODULES "${CMAKE_CURRENT_LIST_DIR}/cmake") +if(NOT EXISTS "${CMAKE_SOURCE_DIR}/cmake/base.cmake") + if(${CMAKE_VERSION} VERSION_LESS "3.14.0") + message( + FATAL_ERROR + "\nPlease run the following command first:\ngit submodule update --init\n" + ) + else() + message(STATUS "JRL cmakemodules not found. Let's fetch it.") + include(FetchContent) + FetchContent_Declare( + "jrl-cmakemodules" + GIT_REPOSITORY "https://github.com/jrl-umi3218/jrl-cmakemodules.git") + FetchContent_MakeAvailable("jrl-cmakemodules") + FetchContent_GetProperties("jrl-cmakemodules" SOURCE_DIR JRL_CMAKE_MODULES) + endif() +endif() + + +# Disable -Werror on Unix for now. +set(CXX_DISABLE_WERROR True) +set(CMAKE_VERBOSE_MAKEFILE True) + +option(INSTALL_DOCUMENTATION "Generate and install the documentation" OFF) +option(SUFFIX_SO_VERSION "Suffix library name with its version" OFF) + +if(DEFINED BUILD_UNIT_TESTS) + message( + AUTHOR_WARNING + "BUILD_UNIT_TESTS is deprecated. Use BUILD_TESTING instead.") + set(BUILD_TESTING ${BUILD_UNIT_TESTS}) +endif(DEFINED BUILD_UNIT_TESTS) + + +include("${JRL_CMAKE_MODULES}/base.cmake") +compute_project_args(PROJECT_ARGS LANGUAGES CXX) +project(${PROJECT_NAME} ${PROJECT_ARGS}) + + +include("${JRL_CMAKE_MODULES}/boost.cmake") +include("${JRL_CMAKE_MODULES}/python.cmake") +include("${JRL_CMAKE_MODULES}/ide.cmake") +include("${JRL_CMAKE_MODULES}/apple.cmake") + + +option(GENERATE_PYTHON_STUBS + "Generate the Python stubs associated to the Python library" OFF) + +string(REPLACE "-pedantic" "" CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS}) + + +# If needed, fix CMake policy for APPLE systems +apply_default_apple_configuration() +check_minimal_cxx_standard(11 ENFORCE) + +if(WIN32) + set(LINK copy_if_different) +else(WIN32) + set(LINK create_symlink) +endif(WIN32) + +if(CMAKE_CROSSCOMPILING) + set(PYTHON_COMPONENTS Interpreter NumPy) +else() + set(PYTHON_COMPONENTS Interpreter Development.Module NumPy) +endif() +set(PYTHON_EXPORT_DEPENDENCY ON) +findpython(REQUIRED) + + +if(${NUMPY_VERSION} VERSION_LESS "1.16.0") + set(NUMPY_WITH_BROKEN_UFUNC_SUPPORT TRUE) +endif() + + +if(WIN32) + link_directories(${PYTHON_LIBRARY_DIRS}) + # # Set default Windows build paths SET(CMAKE_LIBRARY_OUTPUT_DIRECTORY + # ${PROJECT_BINARY_DIR}/Bin CACHE PATH "Single directory for all libraries") + # SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/Bin CACHE PATH + # "Single directory for all executables") SET(CMAKE_ARCHIVE_OUTPUT_DIRECTORY + # ${PROJECT_BINARY_DIR}/Bin CACHE PATH "Sing$le directory for all archives") +endif(WIN32) + + + +# ---------------------------------------------------- +# --- DEPENDENCIES ----------------------------------- +# ---------------------------------------------------- +add_project_dependency(Eigen3 REQUIRED PKG_CONFIG_REQUIRES "eigen3 >= 3.0.5") +add_project_dependency(eigenpy REQUIRED PKG_CONFIG_REQUIRES "eigenpy >= 3.0.0") + +set_boost_default_options() +export_boost_default_options() +find_package(Boost REQUIRED) +search_for_boost_python(REQUIRED) + + +# ---------------------------------------------------- +# --- INCLUDE ---------------------------------------- +# ---------------------------------------------------- +set(${PROJECT_NAME}_HEADERS + include/header.hpp) + + +set(${PROJECT_NAME}_SOURCES src/src.cpp) + + + +add_library(${PROJECT_NAME} SHARED ${${PROJECT_NAME}_SOURCES} + ${${PROJECT_NAME}_HEADERS}) + + +target_include_directories( + ${PROJECT_NAME} SYSTEM + PUBLIC $ + $) + + +modernize_target_link_libraries( + ${PROJECT_NAME} + SCOPE + PUBLIC + TARGETS + Eigen3::Eigen + INCLUDE_DIRS + ${EIGEN3_INCLUDE_DIR}) + + +modernize_target_link_libraries( + ${PROJECT_NAME} + SCOPE + PUBLIC + TARGETS + Python${PYTHON_VERSION_MAJOR}::NumPy + INCLUDE_DIRS + ${NUMPY_INCLUDE_DIRS} + ${PYTHON_INCLUDE_DIR}) + + + +if(SUFFIX_SO_VERSION) + set_target_properties(${PROJECT_NAME} PROPERTIES SOVERSION ${PROJECT_VERSION}) +endif(SUFFIX_SO_VERSION) + + + + +if(NOT WIN32) + target_compile_options( + ${PROJECT_NAME} PRIVATE $<$:-bigobj> + "-Wno-conversion") +else() + target_compile_options(${PROJECT_NAME} + PRIVATE $<$:-bigobj>) + target_compile_definitions(${PROJECT_NAME} PUBLIC "HAVE_SNPRINTF") +endif() + +target_link_boost_python(${PROJECT_NAME} PUBLIC) +install( + TARGETS ${PROJECT_NAME} + EXPORT ${TARGETS_EXPORT_NAME} + PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} + INCLUDES + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + +add_header_group(${PROJECT_NAME}_HEADERS) +add_source_group(${PROJECT_NAME}_SOURCES) + +# Install package for ROS +install(FILES package.xml DESTINATION share/example_) +# Allows Colcon to find non-Ament packages when using workspace underlays +file( + WRITE + ${CMAKE_CURRENT_BINARY_DIR}/share/ament_index/resource_index/packages/${PROJECT_NAME} + "") +install( + FILES + ${CMAKE_CURRENT_BINARY_DIR}/share/ament_index/resource_index/packages/${PROJECT_NAME} + DESTINATION share/ament_index/resource_index/packages) +file( + WRITE + ${CMAKE_CURRENT_BINARY_DIR}/share/${PROJECT_NAME}/hook/ament_prefix_path.dsv + "prepend-non-duplicate;AMENT_PREFIX_PATH;") +install( + FILES + ${CMAKE_CURRENT_BINARY_DIR}/share/${PROJECT_NAME}/hook/ament_prefix_path.dsv + DESTINATION share/${PROJECT_NAME}/hook) +file(WRITE + ${CMAKE_CURRENT_BINARY_DIR}/share/${PROJECT_NAME}/hook/python_path.dsv + "prepend-non-duplicate;PYTHONPATH;${PYTHON_SITELIB}") +install( + FILES ${CMAKE_CURRENT_BINARY_DIR}/share/${PROJECT_NAME}/hook/python_path.dsv + DESTINATION share/${PROJECT_NAME}/hook) + +# ---------------------------------------------------- +# --- PYTHON LIBRARY --------------------------------- +# ---------------------------------------------------- +#add_subdirectory(python) + +pkg_config_append_libs(${PROJECT_NAME}) +pkg_config_append_cflags("-I${PYTHON_INCLUDE_DIRS}") +pkg_config_append_cflags("-I${NUMPY_INCLUDE_DIRS}") +pkg_config_append_boost_libs(${BOOST_COMPONENTS}) + + + + diff --git a/examples/custom_numeric_type/package.xml b/examples/custom_numeric_type/package.xml new file mode 100644 index 000000000..ea4f32fb4 --- /dev/null +++ b/examples/custom_numeric_type/package.xml @@ -0,0 +1,32 @@ + + + eigenpy_example_custom_numeric_type + 3.0.0 + Example of using EigenPy to enable custom numeric types with Eigen + Justin Carpentier + Wolfgang Merkt + Justin Carpentier + Nicolas Mansard + BSD + + https://github.com/stack-of-tasks/eigenpy/examples/custom_numeric_type + + git + doxygen + + + catkin + + python + python3 + python-numpy + python3-numpy + eigen + boost + eigenpy + + cmake + + cmake + + From 7d9048b6134e387affe576908ebec91977c1288f Mon Sep 17 00:00:00 2001 From: silviana amethyst <1388063+ofloveandhate@users.noreply.github.com> Date: Thu, 18 May 2023 15:30:43 -0500 Subject: [PATCH 03/16] ignoring generated files --- examples/custom_numeric_type/.gitignore | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 examples/custom_numeric_type/.gitignore diff --git a/examples/custom_numeric_type/.gitignore b/examples/custom_numeric_type/.gitignore new file mode 100644 index 000000000..68e319c8b --- /dev/null +++ b/examples/custom_numeric_type/.gitignore @@ -0,0 +1,3 @@ +include/eigenpy/example/custom/numeric/type/config.hpp +include/eigenpy/example/custom/numeric/type/deprecated.hpp +include/eigenpy/example/custom/numeric/type/warning.hpp \ No newline at end of file From c98aafbcebf3d49aab3351170c8e222e39497a2a Mon Sep 17 00:00:00 2001 From: silviana amethyst <1388063+ofloveandhate@users.noreply.github.com> Date: Thu, 18 May 2023 15:30:59 -0500 Subject: [PATCH 04/16] made a readme --- examples/custom_numeric_type/readme.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 examples/custom_numeric_type/readme.md diff --git a/examples/custom_numeric_type/readme.md b/examples/custom_numeric_type/readme.md new file mode 100644 index 000000000..dd933cb61 --- /dev/null +++ b/examples/custom_numeric_type/readme.md @@ -0,0 +1,10 @@ +# An example of using eigenpy to extend Python using Eigen linear algebra package with custom numeric types + +## Building + +1. Make a build directory. Move into it +2. `cmake ../` +3. `make` +4. `make install` +5. Move back up a directory +6. `python3 examplescript.py` \ No newline at end of file From d13f4fabc3b400177d3974e24ddeec2d7193e02a Mon Sep 17 00:00:00 2001 From: silviana amethyst <1388063+ofloveandhate@users.noreply.github.com> Date: Thu, 18 May 2023 15:31:06 -0500 Subject: [PATCH 05/16] an example script --- examples/custom_numeric_type/examplescript.py | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 examples/custom_numeric_type/examplescript.py diff --git a/examples/custom_numeric_type/examplescript.py b/examples/custom_numeric_type/examplescript.py new file mode 100644 index 000000000..d3c947d63 --- /dev/null +++ b/examples/custom_numeric_type/examplescript.py @@ -0,0 +1,3 @@ +import eigenpy_example_custom_numeric_type as example + +example.MpfrComplex(2) # the number 2, in variable precision as a complex number \ No newline at end of file From 6566e878370da14d437b171234bbc879d32bc09b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 18 May 2023 20:33:45 +0000 Subject: [PATCH 06/16] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- examples/custom_numeric_type/CMakeLists.txt | 42 +-- examples/custom_numeric_type/examplescript.py | 2 +- .../custom_numeric_type/include/header.hpp | 316 ++++++++---------- examples/custom_numeric_type/src/src.cpp | 81 ++--- 4 files changed, 188 insertions(+), 253 deletions(-) diff --git a/examples/custom_numeric_type/CMakeLists.txt b/examples/custom_numeric_type/CMakeLists.txt index 6fade50b0..fe4300a74 100644 --- a/examples/custom_numeric_type/CMakeLists.txt +++ b/examples/custom_numeric_type/CMakeLists.txt @@ -3,15 +3,16 @@ cmake_minimum_required(VERSION 3.1) set(PROJECT_NAME eigenpy_example_custom_numeric_type) -set(PROJECT_DESCRIPTION "An example of using eigenpy with a custom numeric type: Boost.Multiprecision complex numbers") -set(PROJECT_URL "http://github.com/stack-of-tasks/eigenpy/examples/custom_numeric_type") +set(PROJECT_DESCRIPTION + "An example of using eigenpy with a custom numeric type: Boost.Multiprecision complex numbers" +) +set(PROJECT_URL + "http://github.com/stack-of-tasks/eigenpy/examples/custom_numeric_type") set(PROJECT_USE_CMAKE_EXPORT TRUE) set(PROJECT_USE_KEYWORD_LINK_LIBRARIES TRUE) set(PROJECT_CUSTOM_HEADER_EXTENSION "hpp") set(PROJECT_COMPATIBILITY_VERSION AnyNewerVersion) - - # Check if the submodule cmake have been initialized set(JRL_CMAKE_MODULES "${CMAKE_CURRENT_LIST_DIR}/cmake") if(NOT EXISTS "${CMAKE_SOURCE_DIR}/cmake/base.cmake") @@ -31,7 +32,6 @@ if(NOT EXISTS "${CMAKE_SOURCE_DIR}/cmake/base.cmake") endif() endif() - # Disable -Werror on Unix for now. set(CXX_DISABLE_WERROR True) set(CMAKE_VERBOSE_MAKEFILE True) @@ -41,29 +41,24 @@ option(SUFFIX_SO_VERSION "Suffix library name with its version" OFF) if(DEFINED BUILD_UNIT_TESTS) message( - AUTHOR_WARNING - "BUILD_UNIT_TESTS is deprecated. Use BUILD_TESTING instead.") + AUTHOR_WARNING "BUILD_UNIT_TESTS is deprecated. Use BUILD_TESTING instead.") set(BUILD_TESTING ${BUILD_UNIT_TESTS}) endif(DEFINED BUILD_UNIT_TESTS) - include("${JRL_CMAKE_MODULES}/base.cmake") compute_project_args(PROJECT_ARGS LANGUAGES CXX) project(${PROJECT_NAME} ${PROJECT_ARGS}) - include("${JRL_CMAKE_MODULES}/boost.cmake") include("${JRL_CMAKE_MODULES}/python.cmake") include("${JRL_CMAKE_MODULES}/ide.cmake") include("${JRL_CMAKE_MODULES}/apple.cmake") - option(GENERATE_PYTHON_STUBS "Generate the Python stubs associated to the Python library" OFF) string(REPLACE "-pedantic" "" CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS}) - # If needed, fix CMake policy for APPLE systems apply_default_apple_configuration() check_minimal_cxx_standard(11 ENFORCE) @@ -82,12 +77,10 @@ endif() set(PYTHON_EXPORT_DEPENDENCY ON) findpython(REQUIRED) - if(${NUMPY_VERSION} VERSION_LESS "1.16.0") set(NUMPY_WITH_BROKEN_UFUNC_SUPPORT TRUE) endif() - if(WIN32) link_directories(${PYTHON_LIBRARY_DIRS}) # # Set default Windows build paths SET(CMAKE_LIBRARY_OUTPUT_DIRECTORY @@ -97,8 +90,6 @@ if(WIN32) # ${PROJECT_BINARY_DIR}/Bin CACHE PATH "Sing$le directory for all archives") endif(WIN32) - - # ---------------------------------------------------- # --- DEPENDENCIES ----------------------------------- # ---------------------------------------------------- @@ -110,28 +101,21 @@ export_boost_default_options() find_package(Boost REQUIRED) search_for_boost_python(REQUIRED) - # ---------------------------------------------------- # --- INCLUDE ---------------------------------------- # ---------------------------------------------------- -set(${PROJECT_NAME}_HEADERS - include/header.hpp) - +set(${PROJECT_NAME}_HEADERS include/header.hpp) set(${PROJECT_NAME}_SOURCES src/src.cpp) - - add_library(${PROJECT_NAME} SHARED ${${PROJECT_NAME}_SOURCES} ${${PROJECT_NAME}_HEADERS}) - target_include_directories( ${PROJECT_NAME} SYSTEM PUBLIC $ $) - modernize_target_link_libraries( ${PROJECT_NAME} SCOPE @@ -141,7 +125,6 @@ modernize_target_link_libraries( INCLUDE_DIRS ${EIGEN3_INCLUDE_DIR}) - modernize_target_link_libraries( ${PROJECT_NAME} SCOPE @@ -152,15 +135,10 @@ modernize_target_link_libraries( ${NUMPY_INCLUDE_DIRS} ${PYTHON_INCLUDE_DIR}) - - if(SUFFIX_SO_VERSION) set_target_properties(${PROJECT_NAME} PROPERTIES SOVERSION ${PROJECT_VERSION}) endif(SUFFIX_SO_VERSION) - - - if(NOT WIN32) target_compile_options( ${PROJECT_NAME} PRIVATE $<$:-bigobj> @@ -214,13 +192,9 @@ install( # ---------------------------------------------------- # --- PYTHON LIBRARY --------------------------------- # ---------------------------------------------------- -#add_subdirectory(python) +# add_subdirectory(python) pkg_config_append_libs(${PROJECT_NAME}) pkg_config_append_cflags("-I${PYTHON_INCLUDE_DIRS}") pkg_config_append_cflags("-I${NUMPY_INCLUDE_DIRS}") pkg_config_append_boost_libs(${BOOST_COMPONENTS}) - - - - diff --git a/examples/custom_numeric_type/examplescript.py b/examples/custom_numeric_type/examplescript.py index d3c947d63..e566d36be 100644 --- a/examples/custom_numeric_type/examplescript.py +++ b/examples/custom_numeric_type/examplescript.py @@ -1,3 +1,3 @@ import eigenpy_example_custom_numeric_type as example -example.MpfrComplex(2) # the number 2, in variable precision as a complex number \ No newline at end of file +example.MpfrComplex(2) # the number 2, in variable precision as a complex number diff --git a/examples/custom_numeric_type/include/header.hpp b/examples/custom_numeric_type/include/header.hpp index 97536d972..659cfdae6 100644 --- a/examples/custom_numeric_type/include/header.hpp +++ b/examples/custom_numeric_type/include/header.hpp @@ -11,41 +11,33 @@ #include - namespace bmp = boost::multiprecision; using bmp::backends::mpc_complex_backend; -using mpfr_complex = bmp::number, bmp::et_on >; // T is a variable-precision complex number with expression templates turned on. - - +using mpfr_complex = + bmp::number, + bmp::et_on>; // T is a variable-precision complex number with + // expression templates turned on. void Expose(); - - - - - -// this code derived from +// this code derived from // https://github.com/stack-of-tasks/eigenpy/issues/365 -// where I asked about using custom types, and @jcarpent responded with a discussion -// of an application of this in Pinnochio, a library for rigid body dynamics. -namespace eigenpy -{ - namespace internal - { - - +// where I asked about using custom types, and @jcarpent responded with a +// discussion of an application of this in Pinnochio, a library for rigid body +// dynamics. +namespace eigenpy { +namespace internal { // a template specialization for complex numbers - // derived directly from the example for Pinnochio +// derived directly from the example for Pinnochio template <> -struct getitem -{ - static PyObject* run(void* data, void* /* arr */) { - mpfr_complex & mpfr_scalar = *static_cast(data); - auto & backend = mpfr_scalar.backend(); - - if(backend.data()[0].re->_mpfr_d == 0) // If the mpfr_scalar is not initialized, we have to init it. +struct getitem { + static PyObject *run(void *data, void * /* arr */) { + mpfr_complex &mpfr_scalar = *static_cast(data); + auto &backend = mpfr_scalar.backend(); + + if (backend.data()[0].re->_mpfr_d == + 0) // If the mpfr_scalar is not initialized, we have to init it. { mpfr_scalar = mpfr_complex(0); } @@ -55,175 +47,159 @@ struct getitem } }; +} // namespace internal -} // namespace internal - - - - - - -// i lifted this from EigenPy and adapted it, basically removing the calls for the comparitors. +// i lifted this from EigenPy and adapted it, basically removing the calls for +// the comparitors. template -void registerUfunct_without_comparitors(){ - const int type_code = Register::getTypeCode(); - - PyObject *numpy_str; - #if PY_MAJOR_VERSION >= 3 - numpy_str = PyUnicode_FromString("numpy"); - #else - numpy_str = PyString_FromString("numpy"); - #endif - PyObject *numpy; - numpy = PyImport_Import(numpy_str); - Py_DECREF(numpy_str); - - import_ufunc(); - - // Matrix multiply - { - int types[3] = {type_code, type_code, type_code}; +void registerUfunct_without_comparitors() { + const int type_code = Register::getTypeCode(); + + PyObject *numpy_str; +#if PY_MAJOR_VERSION >= 3 + numpy_str = PyUnicode_FromString("numpy"); +#else + numpy_str = PyString_FromString("numpy"); +#endif + PyObject *numpy; + numpy = PyImport_Import(numpy_str); + Py_DECREF(numpy_str); + import_ufunc(); + + // Matrix multiply + { + int types[3] = {type_code, type_code, type_code}; + + std::stringstream ss; + ss << "return result of multiplying two matrices of "; + ss << bp::type_info(typeid(Scalar)).name(); + PyUFuncObject *ufunc = + (PyUFuncObject *)PyObject_GetAttrString(numpy, "matmul"); + if (!ufunc) { std::stringstream ss; - ss << "return result of multiplying two matrices of "; - ss << bp::type_info(typeid(Scalar)).name(); - PyUFuncObject *ufunc = - (PyUFuncObject *)PyObject_GetAttrString(numpy, "matmul"); - if (!ufunc) { - std::stringstream ss; - ss << "Impossible to define matrix_multiply for given type " - << bp::type_info(typeid(Scalar)).name() << std::endl; - eigenpy::Exception(ss.str()); - } - if (PyUFunc_RegisterLoopForType((PyUFuncObject *)ufunc, type_code, - &internal::gufunc_matrix_multiply, - types, 0) < 0) { - std::stringstream ss; - ss << "Impossible to register matrix_multiply for given type " - << bp::type_info(typeid(Scalar)).name() << std::endl; - eigenpy::Exception(ss.str()); - } - - Py_DECREF(ufunc); + ss << "Impossible to define matrix_multiply for given type " + << bp::type_info(typeid(Scalar)).name() << std::endl; + eigenpy::Exception(ss.str()); + } + if (PyUFunc_RegisterLoopForType((PyUFuncObject *)ufunc, type_code, + &internal::gufunc_matrix_multiply, + types, 0) < 0) { + std::stringstream ss; + ss << "Impossible to register matrix_multiply for given type " + << bp::type_info(typeid(Scalar)).name() << std::endl; + eigenpy::Exception(ss.str()); } - // Binary operators - EIGENPY_REGISTER_BINARY_UFUNC(add, type_code, Scalar, Scalar, Scalar); - EIGENPY_REGISTER_BINARY_UFUNC(subtract, type_code, Scalar, Scalar, Scalar); - EIGENPY_REGISTER_BINARY_UFUNC(multiply, type_code, Scalar, Scalar, Scalar); - EIGENPY_REGISTER_BINARY_UFUNC(divide, type_code, Scalar, Scalar, Scalar); - - // Comparison operators - EIGENPY_REGISTER_BINARY_UFUNC(equal, type_code, Scalar, Scalar, bool); - EIGENPY_REGISTER_BINARY_UFUNC(not_equal, type_code, Scalar, Scalar, bool); + Py_DECREF(ufunc); + } - //these are commented out because the comparisons are NOT defined for complex types!! - // EIGENPY_REGISTER_BINARY_UFUNC(greater, type_code, Scalar, Scalar, bool); - // EIGENPY_REGISTER_BINARY_UFUNC(less, type_code, Scalar, Scalar, bool); - // EIGENPY_REGISTER_BINARY_UFUNC(greater_equal, type_code, Scalar, Scalar, bool); - // EIGENPY_REGISTER_BINARY_UFUNC(less_equal, type_code, Scalar, Scalar, bool); + // Binary operators + EIGENPY_REGISTER_BINARY_UFUNC(add, type_code, Scalar, Scalar, Scalar); + EIGENPY_REGISTER_BINARY_UFUNC(subtract, type_code, Scalar, Scalar, Scalar); + EIGENPY_REGISTER_BINARY_UFUNC(multiply, type_code, Scalar, Scalar, Scalar); + EIGENPY_REGISTER_BINARY_UFUNC(divide, type_code, Scalar, Scalar, Scalar); - // Unary operators - EIGENPY_REGISTER_UNARY_UFUNC(negative, type_code, Scalar, Scalar); + // Comparison operators + EIGENPY_REGISTER_BINARY_UFUNC(equal, type_code, Scalar, Scalar, bool); + EIGENPY_REGISTER_BINARY_UFUNC(not_equal, type_code, Scalar, Scalar, bool); - Py_DECREF(numpy); -} + // these are commented out because the comparisons are NOT defined for complex + // types!! + // EIGENPY_REGISTER_BINARY_UFUNC(greater, type_code, Scalar, Scalar, bool); + // EIGENPY_REGISTER_BINARY_UFUNC(less, type_code, Scalar, Scalar, bool); + // EIGENPY_REGISTER_BINARY_UFUNC(greater_equal, type_code, Scalar, Scalar, + // bool); EIGENPY_REGISTER_BINARY_UFUNC(less_equal, type_code, Scalar, + // Scalar, bool); -} // namespace eigenpy + // Unary operators + EIGENPY_REGISTER_UNARY_UFUNC(negative, type_code, Scalar, Scalar); + Py_DECREF(numpy); +} +} // namespace eigenpy namespace bp = boost::python; -// this derived directly from the code at https://github.com/stack-of-tasks/eigenpy/issues/365, in which this example was requested -template -struct BoostNumberPythonVisitor -: public boost::python::def_visitor< BoostNumberPythonVisitor > -{ - -public: - - template - void visit(PyClass& cl) const - { - cl - .def(bp::init<>("Default constructor.",bp::arg("self"))) - .def(bp::init("Copy constructor.",bp::args("self","value"))) - .def(bp::init("Constructor from a string.",bp::args("self","str_value"))) - .def(bp::init("Constructor from a pair of strings.",bp::args("self","real","imag"))) - - - .def(bp::self + bp::self) - .def(bp::self += bp::self) - .def(bp::self - bp::self) - .def(bp::self -= bp::self) - .def(bp::self * bp::self) - .def(bp::self *= bp::self) - .def(bp::self / bp::self) - .def(bp::self /= bp::self) - - .def(bp::self == bp::self) - .def(bp::self != bp::self) - .def(bp::self_ns::pow(bp::self_ns::self,long())) - - - - .def("str",&BoostNumber::str,bp::args("self","precision","scientific")) - - .def("default_precision", - static_cast(BoostNumber::default_precision), - "Get the default precision of the class.") - .def("default_precision", - static_cast(BoostNumber::default_precision),bp::arg("digits10"), - "Set the default precision of the class.") - .staticmethod("default_precision") - - .def("precision", - static_cast(&BoostNumber::precision), - bp::arg("self"), - "Get the precision of this.") - .def("precision", - static_cast(&BoostNumber::precision), - bp::args("self","digits10"), - "Set the precision of this.") - - - .def("__str__",&print,bp::arg("self")) - .def("__repr__",&print,bp::arg("self")) - - .def("set_display_precision",&set_display_precision,bp::arg("digit"), - "Set the precision when printing values.") - .staticmethod("set_display_precision") - - .def("get_display_precision",&get_display_precision, - "Get the precision when printing values.", - bp::return_value_policy()) - .staticmethod("get_display_precision") - - ; - +// this derived directly from the code at +// https://github.com/stack-of-tasks/eigenpy/issues/365, in which this example +// was requested +template +struct BoostNumberPythonVisitor : public boost::python::def_visitor< + BoostNumberPythonVisitor > { + public: + template + void visit(PyClass &cl) const { + cl.def(bp::init<>("Default constructor.", bp::arg("self"))) + .def(bp::init("Copy constructor.", + bp::args("self", "value"))) + .def(bp::init("Constructor from a string.", + bp::args("self", "str_value"))) + .def(bp::init( + "Constructor from a pair of strings.", + bp::args("self", "real", "imag"))) + + .def(bp::self + bp::self) + .def(bp::self += bp::self) + .def(bp::self - bp::self) + .def(bp::self -= bp::self) + .def(bp::self * bp::self) + .def(bp::self *= bp::self) + .def(bp::self / bp::self) + .def(bp::self /= bp::self) + + .def(bp::self == bp::self) + .def(bp::self != bp::self) + .def(bp::self_ns::pow(bp::self_ns::self, long())) + + .def("str", &BoostNumber::str, + bp::args("self", "precision", "scientific")) + + .def("default_precision", + static_cast(BoostNumber::default_precision), + "Get the default precision of the class.") + .def("default_precision", + static_cast(BoostNumber::default_precision), + bp::arg("digits10"), "Set the default precision of the class.") + .staticmethod("default_precision") + + .def("precision", + static_cast( + &BoostNumber::precision), + bp::arg("self"), "Get the precision of this.") + .def("precision", + static_cast( + &BoostNumber::precision), + bp::args("self", "digits10"), "Set the precision of this.") + + .def("__str__", &print, bp::arg("self")) + .def("__repr__", &print, bp::arg("self")) + + .def("set_display_precision", &set_display_precision, bp::arg("digit"), + "Set the precision when printing values.") + .staticmethod("set_display_precision") + + .def("get_display_precision", &get_display_precision, + "Get the precision when printing values.", + bp::return_value_policy()) + .staticmethod("get_display_precision") + + ; } - static std::string print(const BoostNumber & self) - { + static std::string print(const BoostNumber &self) { return self.str(get_display_precision(), std::ios_base::dec); } - static void set_display_precision(const int digit) - { + static void set_display_precision(const int digit) { get_display_precision() = digit; } - - static int & get_display_precision() - { + + static int &get_display_precision() { static int precision = BoostNumber::default_precision(); return precision; } }; - - - #endif - - diff --git a/examples/custom_numeric_type/src/src.cpp b/examples/custom_numeric_type/src/src.cpp index a47aaf810..79b88b85c 100644 --- a/examples/custom_numeric_type/src/src.cpp +++ b/examples/custom_numeric_type/src/src.cpp @@ -1,62 +1,47 @@ #include "header.hpp" - - -BOOST_PYTHON_MODULE(eigenpy_example_custom_numeric_type) // this name must match the name of the generated .so file. -{ - // see https://stackoverflow.com/questions/6114462/how-to-override-the-automatically-created-docstring-data-for-boostpython - // docstring_options d(true, true, false); // local_ - boost::python::docstring_options docopt; - docopt.enable_all(); - docopt.disable_cpp_signatures(); - - boost::python::object package = boost::python::scope(); - package.attr("__path__") = "eigenpy_example_custom_numeric_type"; - - - Expose(); +BOOST_PYTHON_MODULE( + eigenpy_example_custom_numeric_type) // this name must match the name of + // the generated .so file. +{ + // see + // https://stackoverflow.com/questions/6114462/how-to-override-the-automatically-created-docstring-data-for-boostpython + // docstring_options d(true, true, false); // local_ + boost::python::docstring_options docopt; + docopt.enable_all(); + docopt.disable_cpp_signatures(); + + boost::python::object package = boost::python::scope(); + package.attr("__path__") = "eigenpy_example_custom_numeric_type"; + + Expose(); } +#define IMPLICITLY_CONVERTIBLE(T1, T2) \ + boost::python::implicitly_convertible(); +void Expose() { + eigenpy::enableEigenPy(); -#define IMPLICITLY_CONVERTIBLE(T1,T2) \ - boost::python::implicitly_convertible(); - -void Expose(){ - - eigenpy::enableEigenPy(); - - boost::python::class_("MpfrComplex", - "",bp::no_init) - .def(BoostNumberPythonVisitor()) - ; - + boost::python::class_("MpfrComplex", "", bp::no_init) + .def(BoostNumberPythonVisitor()); + eigenpy::registerNewType(); + eigenpy::registerUfunct_without_comparitors(); - eigenpy::registerNewType(); - eigenpy::registerUfunct_without_comparitors(); + eigenpy::registerCast(true); + eigenpy::registerCast(true); + eigenpy::registerCast(true); + IMPLICITLY_CONVERTIBLE(int, mpfr_complex); + IMPLICITLY_CONVERTIBLE(long, mpfr_complex); + IMPLICITLY_CONVERTIBLE(int64_t, mpfr_complex); - eigenpy::registerCast(true); - eigenpy::registerCast(true); - eigenpy::registerCast(true); + using VecX = Eigen::Matrix; + using MatXX = Eigen::Matrix; - - - IMPLICITLY_CONVERTIBLE(int,mpfr_complex); - IMPLICITLY_CONVERTIBLE(long,mpfr_complex); - IMPLICITLY_CONVERTIBLE(int64_t,mpfr_complex); - - using VecX = Eigen::Matrix; - using MatXX = Eigen::Matrix; - - - - - eigenpy::enableEigenPySpecific(); - eigenpy::enableEigenPySpecific(); + eigenpy::enableEigenPySpecific(); + eigenpy::enableEigenPySpecific(); } - - #undef IMPLICITLY_CONVERTIBLE From 5645b0a7b160d056cebf56d1d3c9bf7fbd757444 Mon Sep 17 00:00:00 2001 From: silviana amethyst <1388063+ofloveandhate@users.noreply.github.com> Date: Tue, 6 Feb 2024 17:29:54 -0600 Subject: [PATCH 07/16] finding libraries, making .so without lib now makes a library without `lib` at front, and with `.so` at the end. I was able to get this to compile and import using libraries installed via `conda`. --- examples/custom_numeric_type/CMakeLists.txt | 28 ++++++++++++++------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/examples/custom_numeric_type/CMakeLists.txt b/examples/custom_numeric_type/CMakeLists.txt index fe4300a74..d332efbed 100644 --- a/examples/custom_numeric_type/CMakeLists.txt +++ b/examples/custom_numeric_type/CMakeLists.txt @@ -1,6 +1,6 @@ # derived from eigenpy/CMakeLists.txt for this example -cmake_minimum_required(VERSION 3.1) +cmake_minimum_required(VERSION 3.10) set(PROJECT_NAME eigenpy_example_custom_numeric_type) set(PROJECT_DESCRIPTION @@ -93,6 +93,7 @@ endif(WIN32) # ---------------------------------------------------- # --- DEPENDENCIES ----------------------------------- # ---------------------------------------------------- + add_project_dependency(Eigen3 REQUIRED PKG_CONFIG_REQUIRES "eigen3 >= 3.0.5") add_project_dependency(eigenpy REQUIRED PKG_CONFIG_REQUIRES "eigenpy >= 3.0.0") @@ -116,14 +117,8 @@ target_include_directories( PUBLIC $ $) -modernize_target_link_libraries( - ${PROJECT_NAME} - SCOPE - PUBLIC - TARGETS - Eigen3::Eigen - INCLUDE_DIRS - ${EIGEN3_INCLUDE_DIR}) +target_link_directories(${PROJECT_NAME} + PUBLIC ${EIGENPY_LIB_DIR}) modernize_target_link_libraries( ${PROJECT_NAME} @@ -131,14 +126,26 @@ modernize_target_link_libraries( PUBLIC TARGETS Python${PYTHON_VERSION_MAJOR}::NumPy + Eigen3::Eigen + eigenpy INCLUDE_DIRS + ${EIGEN3_INCLUDE_DIR} + ${EIGENPY_INCLUDE_DIR} ${NUMPY_INCLUDE_DIRS} ${PYTHON_INCLUDE_DIR}) + +target_link_libraries(${PROJECT_NAME} PUBLIC eigenpy::eigenpy mpc mpfr) + if(SUFFIX_SO_VERSION) set_target_properties(${PROJECT_NAME} PROPERTIES SOVERSION ${PROJECT_VERSION}) endif(SUFFIX_SO_VERSION) + +#silviana did this +set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "") +set_target_properties(${PROJECT_NAME} PROPERTIES SUFFIX ".so") + if(NOT WIN32) target_compile_options( ${PROJECT_NAME} PRIVATE $<$:-bigobj> @@ -150,6 +157,9 @@ else() endif() target_link_boost_python(${PROJECT_NAME} PUBLIC) + + + install( TARGETS ${PROJECT_NAME} EXPORT ${TARGETS_EXPORT_NAME} From bc20416be95dcfc29de2c351826e683b26f313ed Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 6 Feb 2024 23:30:17 +0000 Subject: [PATCH 08/16] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- examples/custom_numeric_type/CMakeLists.txt | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/examples/custom_numeric_type/CMakeLists.txt b/examples/custom_numeric_type/CMakeLists.txt index d332efbed..be6dba62e 100644 --- a/examples/custom_numeric_type/CMakeLists.txt +++ b/examples/custom_numeric_type/CMakeLists.txt @@ -117,8 +117,7 @@ target_include_directories( PUBLIC $ $) -target_link_directories(${PROJECT_NAME} - PUBLIC ${EIGENPY_LIB_DIR}) +target_link_directories(${PROJECT_NAME} PUBLIC ${EIGENPY_LIB_DIR}) modernize_target_link_libraries( ${PROJECT_NAME} @@ -134,15 +133,13 @@ modernize_target_link_libraries( ${NUMPY_INCLUDE_DIRS} ${PYTHON_INCLUDE_DIR}) - target_link_libraries(${PROJECT_NAME} PUBLIC eigenpy::eigenpy mpc mpfr) if(SUFFIX_SO_VERSION) set_target_properties(${PROJECT_NAME} PROPERTIES SOVERSION ${PROJECT_VERSION}) endif(SUFFIX_SO_VERSION) - -#silviana did this +# silviana did this set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "") set_target_properties(${PROJECT_NAME} PROPERTIES SUFFIX ".so") @@ -158,8 +155,6 @@ endif() target_link_boost_python(${PROJECT_NAME} PUBLIC) - - install( TARGETS ${PROJECT_NAME} EXPORT ${TARGETS_EXPORT_NAME} From de32d977608e25cd08fe3917c944a304aeed63b4 Mon Sep 17 00:00:00 2001 From: silviana amethyst <1388063+ofloveandhate@users.noreply.github.com> Date: Wed, 7 Feb 2024 16:43:38 -0600 Subject: [PATCH 09/16] now can actually make complexes and interact without crashes * brought in the real visitor from https://github.com/stack-of-tasks/eigenpy/issues/365 (from which I had previously derived the complex visitor). * provided the `real` and `imag` properties the code could be cleaner, but now I can actually import the built library, make a complex, get its real/imag parts, do arithmetic, and use dot-tab completion in ipython without crashes. (motivation: i'm working towards a MWE for the alignment issues I get in Bertini 2. either I can replicate it using this code, in which case we can find the bug / whatever in eigenpy, or I fail, and I reveal the bug in MY code.) --- .../custom_numeric_type/include/header.hpp | 209 +++++++++++++++++- examples/custom_numeric_type/src/src.cpp | 27 ++- 2 files changed, 227 insertions(+), 9 deletions(-) diff --git a/examples/custom_numeric_type/include/header.hpp b/examples/custom_numeric_type/include/header.hpp index 659cfdae6..658718b6b 100644 --- a/examples/custom_numeric_type/include/header.hpp +++ b/examples/custom_numeric_type/include/header.hpp @@ -11,14 +11,24 @@ #include +#include + namespace bmp = boost::multiprecision; + + + +using mpfr_float = boost::multiprecision::number, boost::multiprecision::et_off>; + + using bmp::backends::mpc_complex_backend; using mpfr_complex = bmp::number, - bmp::et_on>; // T is a variable-precision complex number with + bmp::et_off>; // T is a variable-precision complex number with // expression templates turned on. -void Expose(); +void ExposeAll(); +void ExposeReal(); +void ExposeComplex(); // this code derived from // https://github.com/stack-of-tasks/eigenpy/issues/365 @@ -28,6 +38,24 @@ void Expose(); namespace eigenpy { namespace internal { +// a template specialization for complex numbers +// derived directly from the example for Pinnochio +template <> +struct getitem { + static PyObject *run(void *data, void * /* arr */) { + mpfr_float &mpfr_scalar = *static_cast(data); + auto &backend = mpfr_scalar.backend(); + + if (backend.data()[0]._mpfr_d == 0) // If the mpfr_scalar is not initialized, we have to init it. + { + mpfr_scalar = mpfr_float(0); + } + boost::python::object m(boost::ref(mpfr_scalar)); + Py_INCREF(m.ptr()); + return m.ptr(); + } +}; + // a template specialization for complex numbers // derived directly from the example for Pinnochio template <> @@ -36,8 +64,7 @@ struct getitem { mpfr_complex &mpfr_scalar = *static_cast(data); auto &backend = mpfr_scalar.backend(); - if (backend.data()[0].re->_mpfr_d == - 0) // If the mpfr_scalar is not initialized, we have to init it. + if (backend.data()[0].re->_mpfr_d == 0 || backend.data()[0].im->_mpfr_d == 0) // If the mpfr_scalar is not initialized, we have to init it. { mpfr_scalar = mpfr_complex(0); } @@ -120,14 +147,169 @@ void registerUfunct_without_comparitors() { } // namespace eigenpy + namespace bp = boost::python; + + + + +template +struct BoostNumberPythonVisitor +: public boost::python::def_visitor< BoostNumberPythonVisitor > +{ + +public: + + template + void visit(PyClass& cl) const + { + cl + .def(bp::init<>("Default constructor.",bp::arg("self"))) + .def(bp::init("Copy constructor.",bp::args("self","value"))) +// .def(bp::init("Copy constructor.",bp::args("self","value"))) +// .def(bp::init("Copy constructor.",bp::args("self","value"))) +// .def(bp::init("Copy constructor.",bp::args("self","value"))) +// .def(bp::init("Copy constructor.",bp::args("self","value"))) +// .def(bp::init("Copy constructor.",bp::args("self","value"))) +// .def(bp::init("Copy constructor.",bp::args("self","value"))) +// .def(bp::init("Copy constructor.",bp::args("self","value"))) + .def(bp::init("Constructor from a string.",bp::args("self","str_value"))) + + + .def(bp::self + bp::self) + .def(bp::self += bp::self) + .def(bp::self - bp::self) + .def(bp::self -= bp::self) + .def(bp::self * bp::self) + .def(bp::self *= bp::self) + .def(bp::self / bp::self) + .def(bp::self /= bp::self) + + .def(bp::self < bp::self) + .def(bp::self <= bp::self) + .def(bp::self > bp::self) + .def(bp::self >= bp::self) + .def(bp::self == bp::self) + .def(bp::self != bp::self) + .def(bp::self_ns::pow(bp::self_ns::self,long())) + + + .def("str",&BoostNumber::str,bp::args("self","precision","scientific")) + + .def("default_precision", + static_cast(BoostNumber::default_precision), + "Get the default precision of the class.") + .def("default_precision", + static_cast(BoostNumber::default_precision),bp::arg("digits10"), + "Set the default precision of the class.") + .staticmethod("default_precision") + + .def("precision", + static_cast(&BoostNumber::precision), + bp::arg("self"), + "Get the precision of this.") + .def("precision", + static_cast(&BoostNumber::precision), + bp::args("self","digits10"), + "Set the precision of this.") + + .def("__float__",&cast,bp::arg("self"),"Cast to float.") + .def("__int__",&cast,bp::arg("self"),"Cast to int.") + + .def("__str__",&print,bp::arg("self")) + .def("__repr__",&print,bp::arg("self")) + + .def("set_display_precision",&set_display_precision,bp::arg("digit"), + "Set the precision when printing values.") + .staticmethod("set_display_precision") + + .def("get_display_precision",&get_display_precision, + "Get the precision when printing values.", + bp::return_value_policy()) + .staticmethod("get_display_precision") + +//#ifndef PINOCCHIO_PYTHON_NO_SERIALIZATION +// .def_pickle(Pickle()) +//#endif + ; + + } + + + static void expose(const std::string & type_name) + { + bp::class_(type_name.c_str(), + "",bp::no_init) + .def(BoostNumberPythonVisitor()) + ; + + eigenpy::registerNewType(); + eigenpy::registerCommonUfunc(); + +#define IMPLICITLY_CONVERTIBLE(T1,T2) \ + bp::implicitly_convertible(); +// bp::implicitly_convertible(); + + IMPLICITLY_CONVERTIBLE(double,BoostNumber); + IMPLICITLY_CONVERTIBLE(float,BoostNumber); + IMPLICITLY_CONVERTIBLE(long int,BoostNumber); + IMPLICITLY_CONVERTIBLE(int,BoostNumber); + IMPLICITLY_CONVERTIBLE(long,BoostNumber); + IMPLICITLY_CONVERTIBLE(unsigned int,BoostNumber); + IMPLICITLY_CONVERTIBLE(unsigned long int,BoostNumber); + IMPLICITLY_CONVERTIBLE(bool,BoostNumber); + +#undef IMPLICITLY_CONVERTIBLE + + eigenpy::registerCast(false); + eigenpy::registerCast(true); + eigenpy::registerCast(false); + eigenpy::registerCast(true); + eigenpy::registerCast(false); + eigenpy::registerCast(true); + eigenpy::registerCast(false); + eigenpy::registerCast(true);; + eigenpy::registerCast(false); + eigenpy::registerCast(true); + } + + private: + + template + static T cast(const BoostNumber & self) + { + return static_cast(self); + } + + static std::string print(const BoostNumber & self) + { + return self.str(get_display_precision(), std::ios_base::dec); + } + + static void set_display_precision(const int digit) + { + get_display_precision() = digit; + } + + static int & get_display_precision() + { + static int precision = BoostNumber::default_precision(); + return precision; + } + + }; + + + + // this derived directly from the code at // https://github.com/stack-of-tasks/eigenpy/issues/365, in which this example // was requested + template -struct BoostNumberPythonVisitor : public boost::python::def_visitor< - BoostNumberPythonVisitor > { +struct BoostComplexPythonVisitor : public boost::python::def_visitor< + BoostComplexPythonVisitor > { public: template void visit(PyClass &cl) const { @@ -140,6 +322,10 @@ struct BoostNumberPythonVisitor : public boost::python::def_visitor< "Constructor from a pair of strings.", bp::args("self", "real", "imag"))) + .def(bp::init( + "Constructor from a pair of integers.", + bp::args("self", "real", "imag"))) + .def(bp::self + bp::self) .def(bp::self += bp::self) .def(bp::self - bp::self) @@ -164,6 +350,10 @@ struct BoostNumberPythonVisitor : public boost::python::def_visitor< bp::arg("digits10"), "Set the default precision of the class.") .staticmethod("default_precision") + + .add_property("real", &get_real, &set_real) + .add_property("imag", &get_imag, &set_imag) + .def("precision", static_cast( &BoostNumber::precision), @@ -188,6 +378,13 @@ struct BoostNumberPythonVisitor : public boost::python::def_visitor< ; } + static void set_real(BoostNumber &c, mpfr_float const& r) { c.real(r);} + static mpfr_float get_real(BoostNumber const&c) { return c.real();} + + static void set_imag(BoostNumber &c, mpfr_float const& r) { c.imag(r);} + static mpfr_float get_imag(BoostNumber const&c) { return c.imag();} + + static std::string print(const BoostNumber &self) { return self.str(get_display_precision(), std::ios_base::dec); } diff --git a/examples/custom_numeric_type/src/src.cpp b/examples/custom_numeric_type/src/src.cpp index 79b88b85c..423103972 100644 --- a/examples/custom_numeric_type/src/src.cpp +++ b/examples/custom_numeric_type/src/src.cpp @@ -14,17 +14,38 @@ BOOST_PYTHON_MODULE( boost::python::object package = boost::python::scope(); package.attr("__path__") = "eigenpy_example_custom_numeric_type"; - Expose(); + ExposeAll(); } #define IMPLICITLY_CONVERTIBLE(T1, T2) \ boost::python::implicitly_convertible(); -void Expose() { + + +void ExposeAll(){ eigenpy::enableEigenPy(); + ExposeReal(); + ExposeComplex(); +} + +void ExposeReal() { + + BoostNumberPythonVisitor::expose("MpfrFloat"); + + + using VecX = Eigen::Matrix; + using MatXX = Eigen::Matrix; + + eigenpy::enableEigenPySpecific(); + eigenpy::enableEigenPySpecific(); +} + + +void ExposeComplex() { + boost::python::class_("MpfrComplex", "", bp::no_init) - .def(BoostNumberPythonVisitor()); + .def(BoostComplexPythonVisitor()); eigenpy::registerNewType(); eigenpy::registerUfunct_without_comparitors(); From 4c40a1940bb3fe28733f12c4a38d5bdfa1d4f7b5 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 7 Feb 2024 22:44:16 +0000 Subject: [PATCH 10/16] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .../custom_numeric_type/include/header.hpp | 297 +++++++++--------- examples/custom_numeric_type/src/src.cpp | 8 +- 2 files changed, 141 insertions(+), 164 deletions(-) diff --git a/examples/custom_numeric_type/include/header.hpp b/examples/custom_numeric_type/include/header.hpp index 658718b6b..d8289aaef 100644 --- a/examples/custom_numeric_type/include/header.hpp +++ b/examples/custom_numeric_type/include/header.hpp @@ -15,16 +15,15 @@ namespace bmp = boost::multiprecision; - - -using mpfr_float = boost::multiprecision::number, boost::multiprecision::et_off>; - +using mpfr_float = + boost::multiprecision::number, + boost::multiprecision::et_off>; using bmp::backends::mpc_complex_backend; using mpfr_complex = bmp::number, bmp::et_off>; // T is a variable-precision complex number with - // expression templates turned on. + // expression templates turned on. void ExposeAll(); void ExposeReal(); @@ -46,7 +45,8 @@ struct getitem { mpfr_float &mpfr_scalar = *static_cast(data); auto &backend = mpfr_scalar.backend(); - if (backend.data()[0]._mpfr_d == 0) // If the mpfr_scalar is not initialized, we have to init it. + if (backend.data()[0]._mpfr_d == + 0) // If the mpfr_scalar is not initialized, we have to init it. { mpfr_scalar = mpfr_float(0); } @@ -64,7 +64,9 @@ struct getitem { mpfr_complex &mpfr_scalar = *static_cast(data); auto &backend = mpfr_scalar.backend(); - if (backend.data()[0].re->_mpfr_d == 0 || backend.data()[0].im->_mpfr_d == 0) // If the mpfr_scalar is not initialized, we have to init it. + if (backend.data()[0].re->_mpfr_d == 0 || + backend.data()[0].im->_mpfr_d == + 0) // If the mpfr_scalar is not initialized, we have to init it. { mpfr_scalar = mpfr_complex(0); } @@ -147,169 +149,153 @@ void registerUfunct_without_comparitors() { } // namespace eigenpy - namespace bp = boost::python; +template +struct BoostNumberPythonVisitor : public boost::python::def_visitor< + BoostNumberPythonVisitor > { + public: + template + void visit(PyClass &cl) const { + cl.def(bp::init<>("Default constructor.", bp::arg("self"))) + .def(bp::init("Copy constructor.", + bp::args("self", "value"))) + // .def(bp::init("Copy + // constructor.",bp::args("self","value"))) + // .def(bp::init("Copy + // constructor.",bp::args("self","value"))) + // .def(bp::init("Copy + // constructor.",bp::args("self","value"))) + // .def(bp::init("Copy + // constructor.",bp::args("self","value"))) .def(bp::init("Copy constructor.",bp::args("self","value"))) + // .def(bp::init("Copy + // constructor.",bp::args("self","value"))) + // .def(bp::init("Copy + // constructor.",bp::args("self","value"))) + .def(bp::init("Constructor from a string.", + bp::args("self", "str_value"))) + .def(bp::self + bp::self) + .def(bp::self += bp::self) + .def(bp::self - bp::self) + .def(bp::self -= bp::self) + .def(bp::self * bp::self) + .def(bp::self *= bp::self) + .def(bp::self / bp::self) + .def(bp::self /= bp::self) + + .def(bp::self < bp::self) + .def(bp::self <= bp::self) + .def(bp::self > bp::self) + .def(bp::self >= bp::self) + .def(bp::self == bp::self) + .def(bp::self != bp::self) + .def(bp::self_ns::pow(bp::self_ns::self, long())) + + .def("str", &BoostNumber::str, + bp::args("self", "precision", "scientific")) + + .def("default_precision", + static_cast(BoostNumber::default_precision), + "Get the default precision of the class.") + .def("default_precision", + static_cast(BoostNumber::default_precision), + bp::arg("digits10"), "Set the default precision of the class.") + .staticmethod("default_precision") + + .def("precision", + static_cast( + &BoostNumber::precision), + bp::arg("self"), "Get the precision of this.") + .def("precision", + static_cast( + &BoostNumber::precision), + bp::args("self", "digits10"), "Set the precision of this.") + .def("__float__", &cast, bp::arg("self"), "Cast to float.") + .def("__int__", &cast, bp::arg("self"), "Cast to int.") + .def("__str__", &print, bp::arg("self")) + .def("__repr__", &print, bp::arg("self")) -template -struct BoostNumberPythonVisitor -: public boost::python::def_visitor< BoostNumberPythonVisitor > -{ + .def("set_display_precision", &set_display_precision, bp::arg("digit"), + "Set the precision when printing values.") + .staticmethod("set_display_precision") -public: + .def("get_display_precision", &get_display_precision, + "Get the precision when printing values.", + bp::return_value_policy()) + .staticmethod("get_display_precision") - template - void visit(PyClass& cl) const - { - cl - .def(bp::init<>("Default constructor.",bp::arg("self"))) - .def(bp::init("Copy constructor.",bp::args("self","value"))) -// .def(bp::init("Copy constructor.",bp::args("self","value"))) -// .def(bp::init("Copy constructor.",bp::args("self","value"))) -// .def(bp::init("Copy constructor.",bp::args("self","value"))) -// .def(bp::init("Copy constructor.",bp::args("self","value"))) -// .def(bp::init("Copy constructor.",bp::args("self","value"))) -// .def(bp::init("Copy constructor.",bp::args("self","value"))) -// .def(bp::init("Copy constructor.",bp::args("self","value"))) - .def(bp::init("Constructor from a string.",bp::args("self","str_value"))) - - - .def(bp::self + bp::self) - .def(bp::self += bp::self) - .def(bp::self - bp::self) - .def(bp::self -= bp::self) - .def(bp::self * bp::self) - .def(bp::self *= bp::self) - .def(bp::self / bp::self) - .def(bp::self /= bp::self) - - .def(bp::self < bp::self) - .def(bp::self <= bp::self) - .def(bp::self > bp::self) - .def(bp::self >= bp::self) - .def(bp::self == bp::self) - .def(bp::self != bp::self) - .def(bp::self_ns::pow(bp::self_ns::self,long())) - - - .def("str",&BoostNumber::str,bp::args("self","precision","scientific")) - - .def("default_precision", - static_cast(BoostNumber::default_precision), - "Get the default precision of the class.") - .def("default_precision", - static_cast(BoostNumber::default_precision),bp::arg("digits10"), - "Set the default precision of the class.") - .staticmethod("default_precision") - - .def("precision", - static_cast(&BoostNumber::precision), - bp::arg("self"), - "Get the precision of this.") - .def("precision", - static_cast(&BoostNumber::precision), - bp::args("self","digits10"), - "Set the precision of this.") - - .def("__float__",&cast,bp::arg("self"),"Cast to float.") - .def("__int__",&cast,bp::arg("self"),"Cast to int.") - - .def("__str__",&print,bp::arg("self")) - .def("__repr__",&print,bp::arg("self")) - - .def("set_display_precision",&set_display_precision,bp::arg("digit"), - "Set the precision when printing values.") - .staticmethod("set_display_precision") - - .def("get_display_precision",&get_display_precision, - "Get the precision when printing values.", - bp::return_value_policy()) - .staticmethod("get_display_precision") - -//#ifndef PINOCCHIO_PYTHON_NO_SERIALIZATION -// .def_pickle(Pickle()) -//#endif - ; - + // #ifndef PINOCCHIO_PYTHON_NO_SERIALIZATION + // .def_pickle(Pickle()) + // #endif + ; } + static void expose(const std::string &type_name) { + bp::class_(type_name.c_str(), "", bp::no_init) + .def(BoostNumberPythonVisitor()); + + eigenpy::registerNewType(); + eigenpy::registerCommonUfunc(); + +#define IMPLICITLY_CONVERTIBLE(T1, T2) bp::implicitly_convertible(); + // bp::implicitly_convertible(); + + IMPLICITLY_CONVERTIBLE(double, BoostNumber); + IMPLICITLY_CONVERTIBLE(float, BoostNumber); + IMPLICITLY_CONVERTIBLE(long int, BoostNumber); + IMPLICITLY_CONVERTIBLE(int, BoostNumber); + IMPLICITLY_CONVERTIBLE(long, BoostNumber); + IMPLICITLY_CONVERTIBLE(unsigned int, BoostNumber); + IMPLICITLY_CONVERTIBLE(unsigned long int, BoostNumber); + IMPLICITLY_CONVERTIBLE(bool, BoostNumber); - static void expose(const std::string & type_name) - { - bp::class_(type_name.c_str(), - "",bp::no_init) - .def(BoostNumberPythonVisitor()) - ; - - eigenpy::registerNewType(); - eigenpy::registerCommonUfunc(); - -#define IMPLICITLY_CONVERTIBLE(T1,T2) \ - bp::implicitly_convertible(); -// bp::implicitly_convertible(); - - IMPLICITLY_CONVERTIBLE(double,BoostNumber); - IMPLICITLY_CONVERTIBLE(float,BoostNumber); - IMPLICITLY_CONVERTIBLE(long int,BoostNumber); - IMPLICITLY_CONVERTIBLE(int,BoostNumber); - IMPLICITLY_CONVERTIBLE(long,BoostNumber); - IMPLICITLY_CONVERTIBLE(unsigned int,BoostNumber); - IMPLICITLY_CONVERTIBLE(unsigned long int,BoostNumber); - IMPLICITLY_CONVERTIBLE(bool,BoostNumber); - #undef IMPLICITLY_CONVERTIBLE - eigenpy::registerCast(false); - eigenpy::registerCast(true); - eigenpy::registerCast(false); - eigenpy::registerCast(true); - eigenpy::registerCast(false); - eigenpy::registerCast(true); - eigenpy::registerCast(false); - eigenpy::registerCast(true);; - eigenpy::registerCast(false); - eigenpy::registerCast(true); - } - - private: - - template - static T cast(const BoostNumber & self) - { - return static_cast(self); - } - - static std::string print(const BoostNumber & self) - { - return self.str(get_display_precision(), std::ios_base::dec); - } - - static void set_display_precision(const int digit) - { - get_display_precision() = digit; - } - - static int & get_display_precision() - { - static int precision = BoostNumber::default_precision(); - return precision; - } - - }; + eigenpy::registerCast(false); + eigenpy::registerCast(true); + eigenpy::registerCast(false); + eigenpy::registerCast(true); + eigenpy::registerCast(false); + eigenpy::registerCast(true); + eigenpy::registerCast(false); + eigenpy::registerCast(true); + ; + eigenpy::registerCast(false); + eigenpy::registerCast(true); + } + private: + template + static T cast(const BoostNumber &self) { + return static_cast(self); + } + static std::string print(const BoostNumber &self) { + return self.str(get_display_precision(), std::ios_base::dec); + } + static void set_display_precision(const int digit) { + get_display_precision() = digit; + } + + static int &get_display_precision() { + static int precision = BoostNumber::default_precision(); + return precision; + } +}; // this derived directly from the code at // https://github.com/stack-of-tasks/eigenpy/issues/365, in which this example // was requested template -struct BoostComplexPythonVisitor : public boost::python::def_visitor< - BoostComplexPythonVisitor > { +struct BoostComplexPythonVisitor + : public boost::python::def_visitor< + BoostComplexPythonVisitor > { public: template void visit(PyClass &cl) const { @@ -322,9 +308,8 @@ struct BoostComplexPythonVisitor : public boost::python::def_visitor< "Constructor from a pair of strings.", bp::args("self", "real", "imag"))) - .def(bp::init( - "Constructor from a pair of integers.", - bp::args("self", "real", "imag"))) + .def(bp::init("Constructor from a pair of integers.", + bp::args("self", "real", "imag"))) .def(bp::self + bp::self) .def(bp::self += bp::self) @@ -350,7 +335,6 @@ struct BoostComplexPythonVisitor : public boost::python::def_visitor< bp::arg("digits10"), "Set the default precision of the class.") .staticmethod("default_precision") - .add_property("real", &get_real, &set_real) .add_property("imag", &get_imag, &set_imag) @@ -378,12 +362,11 @@ struct BoostComplexPythonVisitor : public boost::python::def_visitor< ; } - static void set_real(BoostNumber &c, mpfr_float const& r) { c.real(r);} - static mpfr_float get_real(BoostNumber const&c) { return c.real();} - - static void set_imag(BoostNumber &c, mpfr_float const& r) { c.imag(r);} - static mpfr_float get_imag(BoostNumber const&c) { return c.imag();} + static void set_real(BoostNumber &c, mpfr_float const &r) { c.real(r); } + static mpfr_float get_real(BoostNumber const &c) { return c.real(); } + static void set_imag(BoostNumber &c, mpfr_float const &r) { c.imag(r); } + static mpfr_float get_imag(BoostNumber const &c) { return c.imag(); } static std::string print(const BoostNumber &self) { return self.str(get_display_precision(), std::ios_base::dec); diff --git a/examples/custom_numeric_type/src/src.cpp b/examples/custom_numeric_type/src/src.cpp index 423103972..14b055cb2 100644 --- a/examples/custom_numeric_type/src/src.cpp +++ b/examples/custom_numeric_type/src/src.cpp @@ -20,9 +20,7 @@ BOOST_PYTHON_MODULE( #define IMPLICITLY_CONVERTIBLE(T1, T2) \ boost::python::implicitly_convertible(); - - -void ExposeAll(){ +void ExposeAll() { eigenpy::enableEigenPy(); ExposeReal(); @@ -30,10 +28,8 @@ void ExposeAll(){ } void ExposeReal() { - BoostNumberPythonVisitor::expose("MpfrFloat"); - using VecX = Eigen::Matrix; using MatXX = Eigen::Matrix; @@ -41,9 +37,7 @@ void ExposeReal() { eigenpy::enableEigenPySpecific(); } - void ExposeComplex() { - boost::python::class_("MpfrComplex", "", bp::no_init) .def(BoostComplexPythonVisitor()); From f6d612e7f4b491995312580d3346af97a43bfb13 Mon Sep 17 00:00:00 2001 From: silviana amethyst <1388063+ofloveandhate@users.noreply.github.com> Date: Thu, 14 Nov 2024 17:32:03 +0100 Subject: [PATCH 11/16] updates to help find mpc and mpfr for building --- examples/custom_numeric_type/CMakeLists.txt | 10 ++- .../custom_numeric_type/cmake/FindMPC.cmake | 21 +++++ .../custom_numeric_type/cmake/FindMPFR.cmake | 83 +++++++++++++++++++ 3 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 examples/custom_numeric_type/cmake/FindMPC.cmake create mode 100644 examples/custom_numeric_type/cmake/FindMPFR.cmake diff --git a/examples/custom_numeric_type/CMakeLists.txt b/examples/custom_numeric_type/CMakeLists.txt index be6dba62e..afb7e86d0 100644 --- a/examples/custom_numeric_type/CMakeLists.txt +++ b/examples/custom_numeric_type/CMakeLists.txt @@ -102,6 +102,14 @@ export_boost_default_options() find_package(Boost REQUIRED) search_for_boost_python(REQUIRED) +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") + +find_package(MPFR REQUIRED) +include_directories(${MPFR_INCLUDES}) + +find_package(MPC REQUIRED) +include_directories(${MPC_INCLUDES}) + # ---------------------------------------------------- # --- INCLUDE ---------------------------------------- # ---------------------------------------------------- @@ -133,7 +141,7 @@ modernize_target_link_libraries( ${NUMPY_INCLUDE_DIRS} ${PYTHON_INCLUDE_DIR}) -target_link_libraries(${PROJECT_NAME} PUBLIC eigenpy::eigenpy mpc mpfr) +target_link_libraries(${PROJECT_NAME} PUBLIC eigenpy::eigenpy ${MPC_LIBRARIES} ${MPFR_LIBRARIES}) if(SUFFIX_SO_VERSION) set_target_properties(${PROJECT_NAME} PROPERTIES SOVERSION ${PROJECT_VERSION}) diff --git a/examples/custom_numeric_type/cmake/FindMPC.cmake b/examples/custom_numeric_type/cmake/FindMPC.cmake new file mode 100644 index 000000000..e10c4772a --- /dev/null +++ b/examples/custom_numeric_type/cmake/FindMPC.cmake @@ -0,0 +1,21 @@ +if (MPC_INCLUDES AND MPC_LIBRARIES) + set(MPC_FIND_QUIETLY TRUE) +endif (MPC_INCLUDES AND MPC_LIBRARIES) + +find_path(MPC_INCLUDES + NAMES + mpc.h + PATHS + $ENV{MPC_INC} + ${INCLUDE_INSTALL_DIR} +) + +find_library(MPC_LIBRARIES mpc PATHS $ENV{MPC_LIB} ${LIB_INSTALL_DIR}) + +include(FindPackageHandleStandardArgs) + +# Makes sure that mpc_include and mpc_libraries are valid +# https://cmake.org/cmake/help/latest/module/FindPackageHandleStandardArgs.html +find_package_handle_standard_args(MPC DEFAULT_MSG + MPC_INCLUDES MPC_LIBRARIES) +mark_as_advanced(MPC_INCLUDES MPC_LIBRARIES) \ No newline at end of file diff --git a/examples/custom_numeric_type/cmake/FindMPFR.cmake b/examples/custom_numeric_type/cmake/FindMPFR.cmake new file mode 100644 index 000000000..606a5913e --- /dev/null +++ b/examples/custom_numeric_type/cmake/FindMPFR.cmake @@ -0,0 +1,83 @@ +# Try to find the MPFR library +# See http://www.mpfr.org/ +# +# This module supports requiring a minimum version, e.g. you can do +# find_package(MPFR 2.3.0) +# to require version 2.3.0 to newer of MPFR. +# +# Once done this will define +# +# MPFR_FOUND - system has MPFR lib with correct version +# MPFR_INCLUDES - the MPFR include directory +# MPFR_LIBRARIES - the MPFR library +# MPFR_VERSION - MPFR version + +# Copyright (c) 2006, 2007 Montel Laurent, +# Copyright (c) 2008, 2009 Gael Guennebaud, +# Copyright (c) 2010 Jitse Niesen, +# Redistribution and use is allowed according to the terms of the BSD license. + +# Set MPFR_INCLUDES + +find_path(MPFR_INCLUDES + NAMES + mpfr.h + PATHS + $ENV{GMPDIR} + ${INCLUDE_INSTALL_DIR} +) + +# Set MPFR_FIND_VERSION to 1.0.0 if no minimum version is specified + +if(NOT MPFR_FIND_VERSION) + if(NOT MPFR_FIND_VERSION_MAJOR) + set(MPFR_FIND_VERSION_MAJOR 1) + endif(NOT MPFR_FIND_VERSION_MAJOR) + if(NOT MPFR_FIND_VERSION_MINOR) + set(MPFR_FIND_VERSION_MINOR 0) + endif(NOT MPFR_FIND_VERSION_MINOR) + if(NOT MPFR_FIND_VERSION_PATCH) + set(MPFR_FIND_VERSION_PATCH 0) + endif(NOT MPFR_FIND_VERSION_PATCH) + + set(MPFR_FIND_VERSION "${MPFR_FIND_VERSION_MAJOR}.${MPFR_FIND_VERSION_MINOR}.${MPFR_FIND_VERSION_PATCH}") +endif(NOT MPFR_FIND_VERSION) + + +if(MPFR_INCLUDES) + + # Set MPFR_VERSION + + file(READ "${MPFR_INCLUDES}/mpfr.h" _mpfr_version_header) + + string(REGEX MATCH "define[ \t]+MPFR_VERSION_MAJOR[ \t]+([0-9]+)" _mpfr_major_version_match "${_mpfr_version_header}") + set(MPFR_MAJOR_VERSION "${CMAKE_MATCH_1}") + string(REGEX MATCH "define[ \t]+MPFR_VERSION_MINOR[ \t]+([0-9]+)" _mpfr_minor_version_match "${_mpfr_version_header}") + set(MPFR_MINOR_VERSION "${CMAKE_MATCH_1}") + string(REGEX MATCH "define[ \t]+MPFR_VERSION_PATCHLEVEL[ \t]+([0-9]+)" _mpfr_patchlevel_version_match "${_mpfr_version_header}") + set(MPFR_PATCHLEVEL_VERSION "${CMAKE_MATCH_1}") + + set(MPFR_VERSION ${MPFR_MAJOR_VERSION}.${MPFR_MINOR_VERSION}.${MPFR_PATCHLEVEL_VERSION}) + + # Check whether found version exceeds minimum version + + if(${MPFR_VERSION} VERSION_LESS ${MPFR_FIND_VERSION}) + set(MPFR_VERSION_OK FALSE) + message(STATUS "MPFR version ${MPFR_VERSION} found in ${MPFR_INCLUDES}, " + "but at least version ${MPFR_FIND_VERSION} is required") + else(${MPFR_VERSION} VERSION_LESS ${MPFR_FIND_VERSION}) + set(MPFR_VERSION_OK TRUE) + endif(${MPFR_VERSION} VERSION_LESS ${MPFR_FIND_VERSION}) + +endif(MPFR_INCLUDES) + +# Set MPFR_LIBRARIES + +find_library(MPFR_LIBRARIES mpfr PATHS $ENV{GMPDIR} ${LIB_INSTALL_DIR}) + +# Epilogue + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(MPFR DEFAULT_MSG + MPFR_INCLUDES MPFR_LIBRARIES MPFR_VERSION_OK) +mark_as_advanced(MPFR_INCLUDES MPFR_LIBRARIES) \ No newline at end of file From b221c8f18fd9bf7cfbb16c3c633599b3fd4a317c Mon Sep 17 00:00:00 2001 From: silviana amethyst <1388063+ofloveandhate@users.noreply.github.com> Date: Thu, 14 Nov 2024 17:33:53 +0100 Subject: [PATCH 12/16] added code that crashes `np.zeros()` crashes with my custom data type. I also added a function that tries to modify an existing eigen array, through the eigenpy interface, but my python script doesn't get that far. --- examples/custom_numeric_type/examplescript.py | 13 ++++++++++++- examples/custom_numeric_type/src/src.cpp | 17 +++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/examples/custom_numeric_type/examplescript.py b/examples/custom_numeric_type/examplescript.py index e566d36be..af1d8ac37 100644 --- a/examples/custom_numeric_type/examplescript.py +++ b/examples/custom_numeric_type/examplescript.py @@ -1,3 +1,14 @@ import eigenpy_example_custom_numeric_type as example -example.MpfrComplex(2) # the number 2, in variable precision as a complex number +x = example.MpfrComplex(2) # the number 2, in variable precision as a complex number + +import numpy as np + +M = np.zeros((3,4),dtype=example.MpfrComplex) # make an array of the custom numeric type + +print(M) + + +example.set_to_ones(M) + +print(M) \ No newline at end of file diff --git a/examples/custom_numeric_type/src/src.cpp b/examples/custom_numeric_type/src/src.cpp index 14b055cb2..5804d52f6 100644 --- a/examples/custom_numeric_type/src/src.cpp +++ b/examples/custom_numeric_type/src/src.cpp @@ -1,5 +1,19 @@ #include "header.hpp" + +// a placeholder for a library function that writes data into an existing matrix +void set_to_ones(Eigen::Ref> M){ + for (size_t ii=0; ii(); } + #undef IMPLICITLY_CONVERTIBLE From d0393849ef7deed6718c46e6a3f6238f778b06f2 Mon Sep 17 00:00:00 2001 From: silviana amethyst <1388063+ofloveandhate@users.noreply.github.com> Date: Mon, 25 Nov 2024 15:27:48 +0100 Subject: [PATCH 13/16] templatized `set_to_ones` so works for either reals or complexes --- examples/custom_numeric_type/src/src.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/examples/custom_numeric_type/src/src.cpp b/examples/custom_numeric_type/src/src.cpp index 5804d52f6..263875df9 100644 --- a/examples/custom_numeric_type/src/src.cpp +++ b/examples/custom_numeric_type/src/src.cpp @@ -2,10 +2,11 @@ // a placeholder for a library function that writes data into an existing matrix -void set_to_ones(Eigen::Ref> M){ +template +void set_to_ones(Eigen::Ref> M){ for (size_t ii=0; ii, "set an array to all ones"); + boost::python::def("set_to_ones", &set_to_ones, "set an array to all ones"); } void ExposeReal() { From c6048d3ec4c79ffb1a0038347f49bb6be64e11ed Mon Sep 17 00:00:00 2001 From: silviana amethyst <1388063+ofloveandhate@users.noreply.github.com> Date: Mon, 25 Nov 2024 15:29:28 +0100 Subject: [PATCH 14/16] more things exercised in example script including * making a numpy array of custom type from an existing array of built-in type * calling c++ function that takes eigen matrix which modifies its argument * making an array of zeros without conversion from existing numpy array --- examples/custom_numeric_type/examplescript.py | 42 ++++++++++++++++--- 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/examples/custom_numeric_type/examplescript.py b/examples/custom_numeric_type/examplescript.py index af1d8ac37..e11bb62c8 100644 --- a/examples/custom_numeric_type/examplescript.py +++ b/examples/custom_numeric_type/examplescript.py @@ -1,14 +1,44 @@ +import sys + +sys.path.append('./') + import eigenpy_example_custom_numeric_type as example -x = example.MpfrComplex(2) # the number 2, in variable precision as a complex number -import numpy as np -M = np.zeros((3,4),dtype=example.MpfrComplex) # make an array of the custom numeric type +def try_things(num_type): + + print(f'testing {num_type}') + + x = num_type(2) # the number 2, in variable precision as a complex number + + import numpy as np + + print('making array from empty WITH conversion') + A = np.array( np.empty( (3,4)).astype(np.int64),dtype=num_type) + + print(A) + + print('making array from zeros WITH conversion') + M = np.array( np.zeros( (3,4)).astype(np.int64),dtype=num_type) # make an array of the custom numeric type + + print(M) + + assert(M[0,0] == num_type(0)) + + + example.set_to_ones(M) + + + assert(M[0,0] == num_type(1)) + -print(M) + print(M) + print('making zeros without conversion') + B = np.zeros( (4,5), dtype=num_type) + print(B) -example.set_to_ones(M) -print(M) \ No newline at end of file +try_things(example.MpfrFloat) +try_things(example.MpfrComplex) \ No newline at end of file From cb58069e105f09e5c83853f39a3cfa604678da46 Mon Sep 17 00:00:00 2001 From: silviana amethyst <1388063+ofloveandhate@users.noreply.github.com> Date: Wed, 18 Dec 2024 16:05:07 +0100 Subject: [PATCH 15/16] added a bunch more exercises to the custom type, + pytest much of the functions I added were me trying to get Python to crash in a specific way. I failed, which is a good thing, because it means that EigenPy doesn't have the bug I thought it does. BUT. EigenPy *does* have two issues exercised in the unit tests for the custom type, issues #519 and #520 . Additionally, this code exercises issue #521 , where I try to compute vector norms in two different ways and fail. Additionally, I bumped the C++ standard to C++14, since Boost 1.87 didn't work correctly with only C++11, and 1.87 is now distributed by homebrew (I develop on a Mac) --- CMakeLists.txt | 2 +- examples/custom_numeric_type/CMakeLists.txt | 6 +- examples/custom_numeric_type/examplescript.py | 167 ++++++++++++++++-- .../custom_numeric_type/include/a_class.hpp | 54 ++++++ .../custom_numeric_type/include/header.hpp | 60 +++++-- examples/custom_numeric_type/src/a_class.cpp | 15 ++ examples/custom_numeric_type/src/src.cpp | 36 +++- .../test/test_custom_numeric_type.py | 157 ++++++++++++++++ 8 files changed, 457 insertions(+), 40 deletions(-) create mode 100644 examples/custom_numeric_type/include/a_class.hpp create mode 100644 examples/custom_numeric_type/src/a_class.cpp create mode 100644 examples/custom_numeric_type/test/test_custom_numeric_type.py diff --git a/CMakeLists.txt b/CMakeLists.txt index 030a659cd..51cb1be0b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -98,7 +98,7 @@ string(REPLACE "-pedantic" "" CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS}) # If needed, fix CMake policy for APPLE systems apply_default_apple_configuration() -check_minimal_cxx_standard(11 ENFORCE) +check_minimal_cxx_standard(17 ENFORCE) if(WIN32) set(LINK copy_if_different) diff --git a/examples/custom_numeric_type/CMakeLists.txt b/examples/custom_numeric_type/CMakeLists.txt index afb7e86d0..56264688f 100644 --- a/examples/custom_numeric_type/CMakeLists.txt +++ b/examples/custom_numeric_type/CMakeLists.txt @@ -61,7 +61,7 @@ string(REPLACE "-pedantic" "" CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS}) # If needed, fix CMake policy for APPLE systems apply_default_apple_configuration() -check_minimal_cxx_standard(11 ENFORCE) +check_minimal_cxx_standard(14 ENFORCE) if(WIN32) set(LINK copy_if_different) @@ -113,9 +113,9 @@ include_directories(${MPC_INCLUDES}) # ---------------------------------------------------- # --- INCLUDE ---------------------------------------- # ---------------------------------------------------- -set(${PROJECT_NAME}_HEADERS include/header.hpp) +set(${PROJECT_NAME}_HEADERS include/header.hpp include/a_class.hpp) -set(${PROJECT_NAME}_SOURCES src/src.cpp) +set(${PROJECT_NAME}_SOURCES src/src.cpp src/a_class.cpp) add_library(${PROJECT_NAME} SHARED ${${PROJECT_NAME}_SOURCES} ${${PROJECT_NAME}_HEADERS}) diff --git a/examples/custom_numeric_type/examplescript.py b/examples/custom_numeric_type/examplescript.py index e11bb62c8..0db75406c 100644 --- a/examples/custom_numeric_type/examplescript.py +++ b/examples/custom_numeric_type/examplescript.py @@ -1,44 +1,173 @@ +# some code that exercises C++-exposed code using custom numeric type via EigenPy. + import sys sys.path.append('./') +import numpy as np import eigenpy_example_custom_numeric_type as example +def make_empty_with_conversion(num_type): + return np.array( np.empty( (3)).astype(np.int64),dtype=num_type) + + +def make_zeros_with_conversion(num_type): + return np.array( np.zeros( (3)).astype(np.int32),dtype=num_type) # make an array of the custom numeric type + + + + + + + + +def make_in_numpy_then_modify_in_cpp(num_type): + A = make_zeros_with_conversion(num_type) + example.set_to_ones(A) + + assert(A[0] == num_type(1)) + +def make_in_cpp_then_modify_in_cpp_once(num_type): + + A = example.make_a_vector_in_cpp(4,num_type(1)) # the second argument is used only for type dispatch + example.set_to_ones(A) + + for a in A: + assert(a == num_type(1)) + + +def make_in_cpp_then_modify_in_cpp_list(num_type): + + my_list = [] + + for ii in range(10): + A = example.make_a_vector_in_cpp(4,num_type(1)) # the second argument is used only for type dispatch + my_list.append( A ) + + for A in my_list: + example.set_to_ones(A) + for a in A: + assert(a == num_type(1)) + + example.set_to_ones(A) + + +def make_then_call_function_taking_scalar_and_vector(num_type): + A = make_zeros_with_conversion(num_type) + s = num_type(3) + + result = example.a_function_taking_both_a_scalar_and_a_vector(s, A) + + + +def set_entire_array_to_one_value(num_type): + A = example.make_a_vector_in_cpp(10, num_type(0)) # again, type dispatch on the second + + cst = num_type("13") / num_type("7") # 13/7 seems like a good number. why not. + + example.set_all_entries_to_constant(A,cst) # all entries should become the constant, in this case 13 + + + + +def class_function_with_both_arguments(): + num_type = example.MpfrFloat + + c = example.JustSomeClass(); + + A = example.make_a_vector_in_cpp(10, num_type(0)) # again, type dispatch on the second + + cst = num_type("13") / num_type("7") # 13/7 seems like a good number. why not. + + c.foo(cst,A) # all entries should become the constant, in this case 13 + example.qwfp(cst,A) + + + +def numpy_norm(num_type): + A = make_zeros_with_conversion(num_type) + example.set_to_ones(A) + + # assert np.abs(np.linalg.norm(A) - np.sqrt(3)) < 1e-10 + + +def numpy_manual_norm(num_type): + A = make_zeros_with_conversion(num_type) + example.set_to_ones(A) + assert np.sqrt(np.sum((A)**2)) < 1e-10 + print('arst') + + + +def expected_to_succeed(num_type): + + print(f'testing {num_type} at precision {num_type.default_precision()}') + + make_empty_with_conversion(num_type) + make_zeros_with_conversion(num_type) + + make_in_numpy_then_modify_in_cpp(num_type) + make_in_cpp_then_modify_in_cpp_once(num_type) + make_in_cpp_then_modify_in_cpp_list(num_type) + + set_entire_array_to_one_value(num_type) + + make_then_call_function_taking_scalar_and_vector(num_type) + + class_function_with_both_arguments() + + numpy_norm(num_type) + numpy_manual_norm(num_type) + + + + + + + + + + + + + +def make_empty_without_conversion(num_type): + return np.empty( (3),dtype=num_type) + +def make_zeros_without_conversion(num_type): + + A = np.zeros( (3),dtype=num_type) # make an array of the custom numeric type + assert(A[0] == num_type(0)) + + return A -def try_things(num_type): - print(f'testing {num_type}') +def expected_to_crash(num_type): + print("the following calls are expected to crash, not because they should, but because for whatever reason, eigenpy does not let us directly make numpy arrays WITHOUT converting") + make_empty_without_conversion(num_type) + make_zeros_without_conversion(num_type) - x = num_type(2) # the number 2, in variable precision as a complex number - import numpy as np - print('making array from empty WITH conversion') - A = np.array( np.empty( (3,4)).astype(np.int64),dtype=num_type) - print(A) - print('making array from zeros WITH conversion') - M = np.array( np.zeros( (3,4)).astype(np.int64),dtype=num_type) # make an array of the custom numeric type - print(M) - assert(M[0,0] == num_type(0)) - example.set_to_ones(M) - assert(M[0,0] == num_type(1)) +for prec in [20, 50, 100]: + example.MpfrFloat.default_precision(prec) + expected_to_succeed(example.MpfrFloat) + example.MpfrComplex.default_precision(prec) + expected_to_succeed(example.MpfrComplex) - print(M) - print('making zeros without conversion') - B = np.zeros( (4,5), dtype=num_type) - print(B) -try_things(example.MpfrFloat) -try_things(example.MpfrComplex) \ No newline at end of file +# these really shouldn't crash!!!! but they do, and it's a problem. 2024.12.18 +expected_to_crash(example.MpfrFloat) +expected_to_crash(example.MpfrComplex) \ No newline at end of file diff --git a/examples/custom_numeric_type/include/a_class.hpp b/examples/custom_numeric_type/include/a_class.hpp new file mode 100644 index 000000000..203474c3c --- /dev/null +++ b/examples/custom_numeric_type/include/a_class.hpp @@ -0,0 +1,54 @@ +#pragma once + +#ifndef EXAMPLE_A_CLASS +#define EXAMPLE_A_CLASS + +#include +#include +#include + +#include + +#include + + + +namespace bmp = boost::multiprecision; + +using mpfr_float = + boost::multiprecision::number, + boost::multiprecision::et_off>; + +using bmp::backends::mpc_complex_backend; +using mpfr_complex = + bmp::number, + bmp::et_off>; // T is a variable-precision complex number with + // expression templates turned on. + + +class Whatevs : public boost::python::def_visitor{ + +public: + static + void qwfp(mpfr_float const& c, Eigen::Matrix const& M){} +}; + +class JustSomeClass +{ +public: + JustSomeClass(){}; + ~JustSomeClass() = default; + + void foo(mpfr_float const& the_constant, Eigen::Matrix const& M){}; + + static int bar(JustSomeClass const& self, mpfr_float const& c, Eigen::Matrix const& M){return 42;} +}; + + + + + +void ExposeAClass(); + + +#endif diff --git a/examples/custom_numeric_type/include/header.hpp b/examples/custom_numeric_type/include/header.hpp index d8289aaef..e8f2cdbf0 100644 --- a/examples/custom_numeric_type/include/header.hpp +++ b/examples/custom_numeric_type/include/header.hpp @@ -25,6 +25,10 @@ using mpfr_complex = bmp::et_off>; // T is a variable-precision complex number with // expression templates turned on. + + + + void ExposeAll(); void ExposeReal(); void ExposeComplex(); @@ -39,16 +43,19 @@ namespace internal { // a template specialization for complex numbers // derived directly from the example for Pinnochio -template <> -struct getitem { +template +struct getitem> { + + typedef bmp::number Scalar; + static PyObject *run(void *data, void * /* arr */) { - mpfr_float &mpfr_scalar = *static_cast(data); + + Scalar &mpfr_scalar = *static_cast(data); auto &backend = mpfr_scalar.backend(); - if (backend.data()[0]._mpfr_d == - 0) // If the mpfr_scalar is not initialized, we have to init it. + if (backend.data()[0]._mpfr_d == 0) // If the mpfr_scalar is not initialized, we have to init it. { - mpfr_scalar = mpfr_float(0); + mpfr_scalar = Scalar(0); } boost::python::object m(boost::ref(mpfr_scalar)); Py_INCREF(m.ptr()); @@ -61,12 +68,13 @@ struct getitem { template <> struct getitem { static PyObject *run(void *data, void * /* arr */) { + // std::cout << "getitem mpfr_complex" << std::endl; mpfr_complex &mpfr_scalar = *static_cast(data); auto &backend = mpfr_scalar.backend(); - if (backend.data()[0].re->_mpfr_d == 0 || - backend.data()[0].im->_mpfr_d == - 0) // If the mpfr_scalar is not initialized, we have to init it. + if (backend.data()[0].re[0]._mpfr_d == 0 || + backend.data()[0].im[0]._mpfr_d == 0) + // If the mpfr_scalar is not initialized, we have to init it. { mpfr_scalar = mpfr_complex(0); } @@ -158,10 +166,8 @@ struct BoostNumberPythonVisitor : public boost::python::def_visitor< template void visit(PyClass &cl) const { cl.def(bp::init<>("Default constructor.", bp::arg("self"))) - .def(bp::init("Copy constructor.", - bp::args("self", "value"))) - // .def(bp::init("Copy - // constructor.",bp::args("self","value"))) + .def(bp::init("Copy constructor.", bp::args("self", "value"))) + .def(bp::init("Copy constructor.",bp::args("self","value"))) // .def(bp::init("Copy // constructor.",bp::args("self","value"))) // .def(bp::init("Copy @@ -235,10 +241,11 @@ struct BoostNumberPythonVisitor : public boost::python::def_visitor< } static void expose(const std::string &type_name) { - bp::class_(type_name.c_str(), "", bp::no_init) + bp::class_(type_name.c_str(), "docstring here?", bp::no_init) .def(BoostNumberPythonVisitor()); - eigenpy::registerNewType(); + auto code = eigenpy::registerNewType(); + eigenpy::registerCommonUfunc(); #define IMPLICITLY_CONVERTIBLE(T1, T2) bp::implicitly_convertible(); @@ -382,4 +389,27 @@ struct BoostComplexPythonVisitor } }; + + + + + +// showing we can write a function that returns an eigen vector, and then use it in Python via Eigenpy. +template +Eigen::Matrix make_a_vector_in_cpp(size_t length, T /* for type dispatch*/ ) +{ + Eigen::Matrix a; + a.resize(length,1); + for (size_t ii(0); ii(ii); + + } + + return a; +} + + +#include "a_class.hpp" + #endif diff --git a/examples/custom_numeric_type/src/a_class.cpp b/examples/custom_numeric_type/src/a_class.cpp new file mode 100644 index 000000000..5573b83fc --- /dev/null +++ b/examples/custom_numeric_type/src/a_class.cpp @@ -0,0 +1,15 @@ +#include "a_class.hpp" + + +namespace bp = boost::python; + +void ExposeAClass(){ + boost::python::class_("JustSomeClass", "") + + .def("foo", &JustSomeClass::foo) + .def("bar", &JustSomeClass::bar) + ; + + bp::def("qwfp", &Whatevs::qwfp); +} + diff --git a/examples/custom_numeric_type/src/src.cpp b/examples/custom_numeric_type/src/src.cpp index 263875df9..294b64f03 100644 --- a/examples/custom_numeric_type/src/src.cpp +++ b/examples/custom_numeric_type/src/src.cpp @@ -4,8 +4,8 @@ // a placeholder for a library function that writes data into an existing matrix template void set_to_ones(Eigen::Ref> M){ - for (size_t ii=0; ii> M } +template +void set_all_entries_to_constant(Eigen::Ref> M, T const& the_constant){ + for (Eigen::Index ii=0; ii +Eigen::Matrix a_function_taking_both_a_scalar_and_a_vector(T const& scalar, Eigen::Matrix const& M) +{ + Eigen::Matrix result; + set_all_entries_to_constant(Eigen::Ref>{result}, scalar); + return result; +} BOOST_PYTHON_MODULE( eigenpy_example_custom_numeric_type) // this name must match the name of @@ -41,8 +58,23 @@ void ExposeAll() { ExposeReal(); ExposeComplex(); + // some free C++ functions that do stuff to Eigen::Matrix types. useful to prove they work. boost::python::def("set_to_ones", &set_to_ones, "set an array to all ones"); boost::python::def("set_to_ones", &set_to_ones, "set an array to all ones"); + + boost::python::def("set_all_entries_to_constant", &set_all_entries_to_constant, "set an array to all one value, from a given number of the same type"); + boost::python::def("set_all_entries_to_constant", &set_all_entries_to_constant, "set an array to all one value, from a given number of the same type"); + + + boost::python::def("make_a_vector_in_cpp", &make_a_vector_in_cpp, "make a vector from c++"); + boost::python::def("make_a_vector_in_cpp", &make_a_vector_in_cpp, "make a vector from c++"); + + boost::python::def("a_function_taking_both_a_scalar_and_a_vector", &a_function_taking_both_a_scalar_and_a_vector, "do nothing, but accept both a scalar and a vector"); + boost::python::def("a_function_taking_both_a_scalar_and_a_vector", &a_function_taking_both_a_scalar_and_a_vector, "do nothing, but accept both a scalar and a vector"); + + + // showing we can expose classes that do stuff with exposed types + ExposeAClass(); } void ExposeReal() { diff --git a/examples/custom_numeric_type/test/test_custom_numeric_type.py b/examples/custom_numeric_type/test/test_custom_numeric_type.py new file mode 100644 index 000000000..12059c2c1 --- /dev/null +++ b/examples/custom_numeric_type/test/test_custom_numeric_type.py @@ -0,0 +1,157 @@ +# pytest unit tests for the custom numeric type example. +# the custom numeric types are multi precision variable-precision floats and complex +# numbers from Boost.Multiprecision + +# silviana amethyst +# Max Planck Institute of Molecular Cell Biology and Genetics +# fall 2024 + + +import pytest + +import sys + +sys.path.append("../build") + +import numpy as np +import eigenpy_example_custom_numeric_type as example + + +@pytest.fixture(params=[example.MpfrFloat, example.MpfrComplex]) +def dtype(request): + return request.param + + +@pytest.fixture() +def empty_with_conversion(dtype): + yield np.array( np.empty( (3)).astype(np.int64),dtype=dtype) + + +@pytest.fixture() +def zeros_with_conversion(dtype): + yield np.array( np.zeros( (3)).astype(np.int64),dtype=dtype) + +@pytest.fixture() +def ones_with_conversion(dtype): + yield np.array( np.ones( (3)).astype(np.int64),dtype=dtype) + + + + + + + + + +@pytest.fixture() +def empty_without_conversion(dtype): + yield np.empty( (3,),dtype=dtype) + + +@pytest.fixture() +def zeros_without_conversion(dtype): + yield np.zeros( (3,),dtype=dtype) + +@pytest.fixture() +def ones_without_conversion(dtype): + yield np.ones( (3,),dtype=dtype) + + + + + + + +class TestAllTypes: + + + def test_make_empty_with_conversion(self, dtype, empty_with_conversion): + pass + + + def test_make_zeros_with_conversion(self,dtype, zeros_with_conversion): + # A = np.array( np.zeros( (3)).astype(np.int32),dtype=dtype) # make an array of the custom numeric type + for x in zeros_with_conversion: + assert x == 0 + + + def test_make_in_numpy_then_modify_in_cpp(self,dtype, zeros_with_conversion): + A = zeros_with_conversion + example.set_to_ones(A) + + assert(A[0] == dtype(1)) + + + def test_make_in_cpp_then_modify_in_cpp_once(self,dtype): + + A = example.make_a_vector_in_cpp(4,dtype(1)) # the second argument is used only for type dispatch + example.set_to_ones(A) + + for a in A: + assert(a == dtype(1)) + + + def test_make_in_cpp_then_modify_in_cpp_list(self,dtype): + + my_list = [] + + for ii in range(10): + A = example.make_a_vector_in_cpp(4,dtype(1)) # the second argument is used only for type dispatch + my_list.append( A ) + + for A in my_list: + example.set_to_ones(A) + for a in A: + assert(a == dtype(1)) + + example.set_to_ones(A) + + + def test_make_then_call_function_taking_scalar_and_vector(self,dtype, zeros_with_conversion): + A = zeros_with_conversion + s = dtype(3) + + result = example.a_function_taking_both_a_scalar_and_a_vector(s, A) + + + + def test_set_entire_array_to_one_value(self,dtype): + A = example.make_a_vector_in_cpp(10, dtype(0)) # again, type dispatch on the second + + cst = dtype("13") / dtype("7") # 13/7 seems like a good number. why not. + + example.set_all_entries_to_constant(A,cst) # all entries should become the constant, in this case 13 + + + + + def test_class_function_with_both_arguments(self,dtype): + dtype = example.MpfrFloat + + c = example.JustSomeClass(); + + A = example.make_a_vector_in_cpp(10, dtype(0)) # again, type dispatch on the second + + cst = dtype("13") / dtype("7") # 13/7 seems like a good number. why not. + + c.foo(cst,A) # all entries should become the constant, in this case 13 + example.qwfp(cst,A) + + + + def test_numpy_norm(self,dtype, ones_with_conversion): + A = ones_with_conversion + # this fails for complexes because the linear algebra norm function doesn't know how to deal with the custom type + assert np.abs(np.linalg.norm(A) - np.sqrt(3)) < 1e-10 + + + def test_numpy_manual_norm(self, ones_with_conversion): + A = ones_with_conversion + # this fails because the ufunc 'square' is not defined by Eigenpy. + assert np.sqrt(np.sum((A)**2)) < 1e-10 + + + + + + From 93434b67623b08d488ba9b4bfc19603b628bb00f Mon Sep 17 00:00:00 2001 From: silviana amethyst <1388063+ofloveandhate@users.noreply.github.com> Date: Wed, 18 Dec 2024 16:07:01 +0100 Subject: [PATCH 16/16] ignore .pyc file in the example --- examples/custom_numeric_type/.gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/custom_numeric_type/.gitignore b/examples/custom_numeric_type/.gitignore index 68e319c8b..6c914adc0 100644 --- a/examples/custom_numeric_type/.gitignore +++ b/examples/custom_numeric_type/.gitignore @@ -1,3 +1,5 @@ include/eigenpy/example/custom/numeric/type/config.hpp include/eigenpy/example/custom/numeric/type/deprecated.hpp -include/eigenpy/example/custom/numeric/type/warning.hpp \ No newline at end of file +include/eigenpy/example/custom/numeric/type/warning.hpp + +*.pyc \ No newline at end of file