diff --git a/.github/workflows/cmake.yml b/.github/workflows/ci-ubuntu.yml similarity index 97% rename from .github/workflows/cmake.yml rename to .github/workflows/ci-ubuntu.yml index 53685ab..9149985 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/ci-ubuntu.yml @@ -38,7 +38,7 @@ jobs: export PYTHONPATH=$PYTHONPATH:/opt/openrobots/lib/python3/site-packages:/opt/openrobots/lib/python3/dist-packages export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/opt/openrobots/lib64:/opt/openrobots/lib/x86_64-linux-gnu:/opt/openrobots/lib/plugin:/opt/openrobots/lib export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/opt/openrobots/lib/pkgconfig:/opt/openrobots/share/pkgconfig:/opt/openrobots/lib/x86_64-linux-gnu/pkgconfig:/opt/openrobots/lib64/pkgconfig - cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DPYTHON_EXECUTABLE=$(which python3) -DCMAKE_PREFIX_PATH=/opt/openrobots -DBUILD_CODEGEN_BINDINGS=ON + cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DPYTHON_EXECUTABLE=$(which python3) -DCMAKE_PREFIX_PATH=/opt/openrobots -DBUILD_WITH_CPPAD_CODEGEN_BINDINGS=ON - name: Build # Build your program with the given configuration diff --git a/CMakeLists.txt b/CMakeLists.txt index cd677a0..73e506b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,9 +7,9 @@ CMAKE_MINIMUM_REQUIRED(VERSION 3.1) SET(PROJECT_NAME pycppad) SET(PROJECT_DESCRIPTION "Python bindings for CppAD and CppADCodeGen using Boost.Python") SET(PROJECT_URL "https://github.com/simple-robotics/pycppad") +SET(PROJECT_CUSTOM_HEADER_EXTENSION "hpp") +SET(PROJECT_USE_CMAKE_EXPORT TRUE) -# Project options -OPTION(SUFFIX_SO_VERSION "Suffix library name with its version" ON) # Project configuration SET(PROJECT_USE_CMAKE_EXPORT TRUE) @@ -33,6 +33,10 @@ COMPUTE_PROJECT_ARGS(PROJECT_ARGS LANGUAGES CXX) PROJECT(${PROJECT_NAME} ${PROJECT_ARGS}) CHECK_MINIMAL_CXX_STANDARD(11 ENFORCE) +# Project options +OPTION(SUFFIX_SO_VERSION "Suffix library name with its version" ON) +OPTION(BUILD_WITH_CPPAD_CODEGEN_BINDINGS "Build the python bindings for code generation (via CppADCodeGen)" OFF) + FINDPYTHON() SET_BOOST_DEFAULT_OPTIONS() @@ -40,37 +44,41 @@ ADD_PROJECT_DEPENDENCY(Boost REQUIRED) SEARCH_FOR_BOOST_PYTHON(REQUIRED) EXPORT_BOOST_DEFAULT_OPTIONS() +IF(BUILD_WITH_CPPAD_CODEGEN_BINDINGS) + ADD_PROJECT_DEPENDENCY(cppadcg 2.4.1 REQUIRED PKG_CONFIG_REQUIRES "cppadcg >= 2.4.1") # CppADCodeGen 2.4.1 is the first version to check the minimal version of CppAD + ADD_DEFINITIONS(-DPYCPPAD_WITH_CPPAD_CODEGEN_BINDINGS) +ENDIF(BUILD_WITH_CPPAD_CODEGEN_BINDINGS) + ADD_PROJECT_DEPENDENCY(cppad 20180000.0 REQUIRED PKG_CONFIG_REQUIRES "cppad >= 20180000.0") ADD_PROJECT_DEPENDENCY(Eigen3 REQUIRED PKG_CONFIG_REQUIRES "eigen3 >= 3.0.5") ADD_PROJECT_DEPENDENCY(eigenpy 2.6.6 REQUIRED) -OPTION(BUILD_CODEGEN_BINDINGS "Build the python bindings for code generation (via CppADCodeGen)" OFF) - -IF(BUILD_CODEGEN_BINDINGS) - ADD_PROJECT_DEPENDENCY(cppadcg 2.4.1 REQUIRED PKG_CONFIG_REQUIRES "cppadcg >= 2.4.1") # CppADCodeGen 2.4.1 is the first version to check the minimal version of CppAD - ADD_DEFINITIONS(-DPYCPPAD_BUILD_CPPAD_CODEGEN_BINDINGS) -ENDIF(BUILD_CODEGEN_BINDINGS) - SET(${PROJECT_NAME}_HEADERS include/${PROJECT_NAME}/fwd.hpp include/${PROJECT_NAME}/ad.hpp include/${PROJECT_NAME}/independent.hpp include/${PROJECT_NAME}/ad_fun.hpp + include/${PROJECT_NAME}/cast.hpp include/${PROJECT_NAME}/cppad.hpp include/${PROJECT_NAME}/cppad-scalar.hpp + include/${PROJECT_NAME}/utils/scope.hpp + + # Generated headers + ${${PROJECT_NAME}_BINARY_DIR}/include/${PROJECT_NAME}/config.hpp + ${${PROJECT_NAME}_BINARY_DIR}/include/${PROJECT_NAME}/deprecated.hpp + ${${PROJECT_NAME}_BINARY_DIR}/include/${PROJECT_NAME}/warning.hpp ) SET(${PROJECT_NAME}_SOURCES src/cppad.cpp ) -IF(BUILD_CODEGEN_BINDINGS) +IF(BUILD_WITH_CPPAD_CODEGEN_BINDINGS) LIST(APPEND ${PROJECT_NAME}_HEADERS include/${PROJECT_NAME}/codegen/cg.hpp - include/${PROJECT_NAME}/codegen/ad.hpp include/${PROJECT_NAME}/codegen/cppadcg-scalar.hpp ) -ENDIF(BUILD_CODEGEN_BINDINGS) +ENDIF(BUILD_WITH_CPPAD_CODEGEN_BINDINGS) ADD_LIBRARY(${PROJECT_NAME} SHARED ${${PROJECT_NAME}_SOURCES} ${${PROJECT_NAME}_HEADERS}) @@ -79,10 +87,17 @@ IF(SUFFIX_SO_VERSION) ENDIF(SUFFIX_SO_VERSION) TARGET_LINK_BOOST_PYTHON(${PROJECT_NAME} PUBLIC) +TARGET_LINK_LIBRARIES(${PROJECT_NAME} PUBLIC ${cppad_LIBRARY} eigenpy::eigenpy) +IF(BUILD_WITH_CPPAD_CODEGEN_BINDINGS) + TARGET_INCLUDE_DIRECTORIES(${PROJECT_NAME} + SYSTEM PUBLIC + ${cppadcg_INCLUDE_DIR} + ) + TARGET_LINK_LIBRARIES(${PROJECT_NAME} PUBLIC ${CMAKE_DL_LIBS}) +ENDIF(BUILD_WITH_CPPAD_CODEGEN_BINDINGS) TARGET_INCLUDE_DIRECTORIES(${PROJECT_NAME} SYSTEM PUBLIC - ${cppad_INCLUDE_DIR} ${Boost_INCLUDE_DIRS} ${EIGEN3_INCLUDE_DIR} ${PYTHON_INCLUDE_DIRS} diff --git a/include/pycppad/ad.hpp b/include/pycppad/ad.hpp index b89510f..e2a5680 100644 --- a/include/pycppad/ad.hpp +++ b/include/pycppad/ad.hpp @@ -6,8 +6,10 @@ #define __pycppad_ad_hpp__ #include "pycppad/fwd.hpp" -#include "eigenpy/user-type.hpp" -#include "eigenpy/ufunc.hpp" +#include "pycppad/cast.hpp" + +#include +#include namespace pycppad { @@ -27,7 +29,7 @@ namespace pycppad cl .def(bp::init<>(bp::arg("self"),"Default constructor")) .def(bp::init(bp::args("self","value"), - std::string("Constructor from a ").append(typeid(Scalar).name()).c_str())) + std::string("Constructor from a ").append(bp::type_id().name()).c_str())) .def(bp::init(bp::args("self","other"),"Copy constructor")) .def(bp::self + bp::self) .def(bp::self - bp::self) @@ -71,7 +73,7 @@ namespace pycppad .def("__repr__",&print) .def("__float__",&::CppAD::Value) - .def("__int__",&__int__) + .def("__int__",&internal::Cast::run) ; } @@ -83,11 +85,6 @@ namespace pycppad ss << get_class_name() << "(" << self <<")"; return ss.str(); } - - static int64_t __int__(const AD & self) - { - return static_cast(::CppAD::Value(self)); - } protected: @@ -109,7 +106,7 @@ namespace pycppad { set_class_name(class_name); bp::class_(class_name.c_str(), - std::string("AD type corresponding to the scalar type ").append(typeid(Scalar).name()).c_str(), + std::string("AD type corresponding to the scalar type ").append(bp::type_id().name()).c_str(), bp::no_init) .def(ADVisitor()); diff --git a/include/pycppad/cast.hpp b/include/pycppad/cast.hpp new file mode 100644 index 0000000..7cd2b19 --- /dev/null +++ b/include/pycppad/cast.hpp @@ -0,0 +1,45 @@ +/* + * Copyright 2021 INRIA + */ + +#ifndef __pycppad_cast_hpp__ +#define __pycppad_cast_hpp__ + +#include "pycppad/fwd.hpp" + +namespace pycppad +{ + namespace internal + { + + template + struct Cast + { + static To run(const From & from) + { + return static_cast(from); + } + }; + + template + struct CppADValue + { + static Scalar get(const ::CppAD::AD & v) + { + return ::CppAD::Value(v); + } + }; + + template + struct Cast<::CppAD::AD,To> + { + typedef ::CppAD::AD From; + static To run(const From & from) + { + return static_cast(CppADValue::get(from)); + } + }; + } // namespace internal +} // namespace pycppad + +#endif //#ifndef __pycppad_cast_hpp__ diff --git a/include/pycppad/codegen/ad.hpp b/include/pycppad/codegen/ad.hpp deleted file mode 100644 index 008d90c..0000000 --- a/include/pycppad/codegen/ad.hpp +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright 2021 INRIA - */ - -#ifndef __pycppad_codegen_ad_hpp__ -#define __pycppad_codegen_ad_hpp__ - -#include "pycppad/ad.hpp" - -namespace pycppad -{ - - template<> - int64_t ADVisitor<::CppAD::cg::CG >::__int__(const AD & self) - { - return static_cast(::CppAD::Value(self).getValue()); - } -} - -#endif //#ifndef __pycppad_ad_hpp__ diff --git a/include/pycppad/codegen/cg.hpp b/include/pycppad/codegen/cg.hpp index c219b6c..37700ff 100644 --- a/include/pycppad/codegen/cg.hpp +++ b/include/pycppad/codegen/cg.hpp @@ -7,19 +7,44 @@ #include -#include "eigenpy/user-type.hpp" -#include "eigenpy/ufunc.hpp" +#include +#include +#include "pycppad/cast.hpp" namespace pycppad { - namespace codegen { + namespace internal + { + + template + struct CppADValue<::CppAD::cg::CG> + { + static const Scalar & get(const ::CppAD::AD<::CppAD::cg::CG> & v) + { + return ::CppAD::Value<::CppAD::cg::CG>(v).getValue(); + } + }; + + template + struct Cast<::CppAD::cg::CG,To> + { + typedef ::CppAD::cg::CG From; + static To run(const From & from) + { + return static_cast(::CppAD::Value(from).getValue()); + } + }; + } + + namespace codegen + { namespace bp = boost::python; template class CGVisitor - : public bp::def_visitor< CGVisitor > + : public bp::def_visitor< CGVisitor > { public: typedef ::CppAD::cg::CG CG; @@ -27,85 +52,80 @@ namespace pycppad template void visit(PyClass& cl) const { - cl - .def(bp::init<>(bp::arg("self"),"Default constructor")) - .def(bp::init(bp::args("self","value"), - std::string("Constructor from a ").append(typeid(Scalar).name()).c_str())) - .def(bp::init(bp::args("self","other"),"Copy constructor")) - .def("isIdenticalZero", &CG::isIdenticalZero, bp::arg("self")) - .def("isIdenticalOne", &CG::isIdenticalOne, bp::arg("self")) - .def("isValueDefined", &CG::isValueDefined, bp::arg("self")) - .def("isParameter", &CG::isParameter, bp::arg("self")) - .def("isVariable", &CG::isVariable, bp::arg("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) + cl + .def(bp::init<>(bp::arg("self"),"Default constructor")) + .def(bp::init(bp::args("self","value"), + std::string("Constructor from a ").append(bp::type_id().name()).c_str())) + .def(bp::init(bp::args("self","other"),"Copy constructor")) + .def("isIdenticalZero", &CG::isIdenticalZero, bp::arg("self")) + .def("isIdenticalOne", &CG::isIdenticalOne, bp::arg("self")) + .def("isValueDefined", &CG::isValueDefined, bp::arg("self")) + .def("isParameter", &CG::isParameter, bp::arg("self")) + .def("isVariable", &CG::isVariable, bp::arg("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) #ifdef __clang__ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wself-assign-overloaded" #endif - .def(bp::self /= bp::self) - .def(bp::self -= bp::self) // See https://bugs.llvm.org/show_bug.cgi?id=43124 for the bug + .def(bp::self /= bp::self) + .def(bp::self -= bp::self) // See https://bugs.llvm.org/show_bug.cgi?id=43124 for the bug #ifdef __clang__ #pragma GCC diagnostic pop #endif - .def(bp::self *= bp::self) - .add_property("value", - bp::make_function(&CG::getValue, - bp::return_value_policy()), - &CG::setValue) - .def("__str__",&print) - .def("__repr__",&print) - .add_property("__float__", - bp::make_function(&CG::getValue, - bp::return_value_policy()), - &CG::setValue) - .def("__int__",&__int__) - ; + .def(bp::self *= bp::self) + .add_property("value", + bp::make_function(&CG::getValue, + bp::return_value_policy()), + &CG::setValue) + .def("__str__",&print) + .def("__repr__",&print) + .add_property("__float__", + bp::make_function(&CG::getValue, + bp::return_value_policy()), + &CG::setValue) + .def("__int__",&internal::Cast::run) + ; } private: static std::string print(const CG & self) { - std::stringstream ss; - ss << get_class_name() << "(" << self <<")"; - return ss.str(); + std::stringstream ss; + ss << get_class_name() << "(" << self <<")"; + return ss.str(); } - - static int64_t __int__(const CG & self) - { - return static_cast(self.getValue()); - } - + protected: static std::string & get_class_name() { - static std::string class_name; - return class_name; + static std::string class_name; + return class_name; } static void set_class_name(const std::string & class_name) { - get_class_name() = class_name; + get_class_name() = class_name; } public: - + static void expose(const std::string & class_name = "CG") { - set_class_name(class_name); - bp::class_(class_name.c_str(), - std::string("CG type corresponding to the scalar type ").append(typeid(Scalar).name()).c_str(), - bp::no_init) - .def(CGVisitor()); - - eigenpy::registerNewType(); - eigenpy::registerCommonUfunc(); + set_class_name(class_name); + bp::class_(class_name.c_str(), + std::string("CG type corresponding to the scalar type ").append(bp::type_id().name()).c_str(), + bp::no_init) + .def(CGVisitor()); + + eigenpy::registerNewType(); + eigenpy::registerCommonUfunc(); } }; diff --git a/include/pycppad/codegen/cppadcg-scalar.hpp b/include/pycppad/codegen/cppadcg-scalar.hpp index 5ccef8e..5ad81d9 100644 --- a/include/pycppad/codegen/cppadcg-scalar.hpp +++ b/include/pycppad/codegen/cppadcg-scalar.hpp @@ -2,26 +2,25 @@ * Copyright 2021 INRIA */ -#ifndef __pycppad_cppad_codegen_scalar_hpp__ -#define __pycppad_cppad_codegen_scalar_hpp__ +#ifndef __pycppad_cppad_codegen_cppadcg_scalar_hpp__ +#define __pycppad_cppad_codegen_cppadcg_scalar_hpp__ #include #include #include "pycppad/codegen/cg.hpp" -#include "pycppad/codegen/ad.hpp" #include "pycppad/ad.hpp" #include "pycppad/independent.hpp" #include "pycppad/ad_fun.hpp" - namespace pycppad { - namespace codegen { - + namespace codegen + { template void exposeCppADCGScalar() { + namespace bp = boost::python; typedef ::CppAD::cg::CG CGScalar; typedef ::CppAD::AD ADCGScalar; typedef Eigen::Matrix VectorADCG; @@ -35,13 +34,12 @@ namespace pycppad CGVisitor::expose(); - pycppad::ADVisitor::expose("ADCG"); pycppad::ADFunVisitor::expose("ADCGFun"); - pycppad::IndependentVisitor::expose("CGIndependent"); - pycppad::IndependentVisitor::expose("CGIndependent"); + pycppad::IndependentVisitor::expose("Independent"); + pycppad::IndependentVisitor::expose("Independent"); } } } -#endif // ifndef __pycppad_cppad_scalar_hpp__ +#endif // ifndef __pycppad_cppad_codegen_cppadcg_scalar_hpp__ diff --git a/include/pycppad/fwd.hpp b/include/pycppad/fwd.hpp index 9ebd9c7..0c7d80c 100644 --- a/include/pycppad/fwd.hpp +++ b/include/pycppad/fwd.hpp @@ -5,6 +5,10 @@ #ifndef __pycppad_fwd_hpp__ #define __pycppad_fwd_hpp__ +#include "pycppad/config.hpp" +#include "pycppad/deprecated.hpp" +#include "pycppad/warning.hpp" + #define BOOST_BIND_GLOBAL_PLACEHOLDERS #include #include diff --git a/include/pycppad/independent.hpp b/include/pycppad/independent.hpp index 749f04d..5e92e33 100644 --- a/include/pycppad/independent.hpp +++ b/include/pycppad/independent.hpp @@ -19,26 +19,7 @@ namespace pycppad : public bp::def_visitor< IndependentVisitor > { typedef Eigen::Ref RefADVector; - - public: - template - void visit(PyClass&) const - { } - - protected: - - static std::string & get_class_name() - { - static std::string class_name; - return class_name; - } - - static void set_class_name(const std::string & class_name) - { - get_class_name() = class_name; - } - public: static void Independent(RefADVector x, @@ -53,12 +34,11 @@ namespace pycppad return; } - static void expose(const std::string & class_name = "Independent") + static void expose(const std::string & func_name = "Independent") { - set_class_name(class_name); - bp::def(class_name.c_str(),&Independent, + bp::def(func_name.c_str(),&Independent, (bp::arg("x"), bp::arg("abort_op_index") = 0, bp::arg("record_compare") = true), - "define a variable as Independent." + "Define a variable as Independent." "Parameters:\n" "\tx: variable\n" "\tabort_op_index: operator index at which execution will be aborted (during the recording of operations). The value zero corresponds to not aborting (will not match).\n" diff --git a/include/pycppad/utils/scope.hpp b/include/pycppad/utils/scope.hpp new file mode 100644 index 0000000..1eed6db --- /dev/null +++ b/include/pycppad/utils/scope.hpp @@ -0,0 +1,36 @@ +// +// Copyright (c) 2019-2021 INRIA +// + +#ifndef __pycppad_utils_scope_hpp__ +#define __pycppad_utils_scope_hpp__ + +#include + +namespace pycppad +{ + + /// + /// \brief Helper to create or simply return an existing namespace in Python + /// + /// \param[in] submodule_name name of the submodule + /// + /// \returns The submodule related to the namespace name. + /// + inline boost::python::scope scope(const std::string & submodule_name) + { + namespace bp = boost::python; + + bp::scope current_scope; + std::string current_scope_name(bp::extract(current_scope.attr("__name__"))); + std::string complete_submodule_name = current_scope_name + "." + submodule_name; + + bp::object submodule(bp::borrowed(PyImport_AddModule(complete_submodule_name.c_str()))); + current_scope.attr(submodule_name.c_str()) = submodule; + + return submodule; + } + +} // namespace pycppad + +#endif // ifndef __pycppad_utils_scope_hpp__ diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index 3ff4061..6adb789 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -13,8 +13,7 @@ SET_TARGET_PROPERTIES(python PROPERTIES EXCLUDE_FROM_DEFAULT_BUILD True) ADD_LIBRARY(${PYWRAP} SHARED main.cpp) ADD_DEPENDENCIES(python ${PYWRAP}) -TARGET_INCLUDE_DIRECTORIES(${PYWRAP} SYSTEM PUBLIC ${cppad_INCLUDE_DIR}) -TARGET_LINK_LIBRARIES(${PYWRAP} PUBLIC ${PROJECT_NAME} ${cppad_LIBRARY} eigenpy::eigenpy) +TARGET_LINK_LIBRARIES(${PYWRAP} PUBLIC ${PROJECT_NAME}) TARGET_LINK_BOOST_PYTHON(${PYWRAP} PUBLIC) # BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS spews conversion warnings from int to long unsigned int. # Unfortunately, using literals does not work in a macro. As such, this turns them off for the entire wrapper: @@ -43,6 +42,7 @@ SET(PYTHON_FILES ) FOREACH(python ${PYTHON_FILES}) + PYTHON_BUILD(${PROJECT_NAME} ${python}) INSTALL(FILES "${CMAKE_CURRENT_SOURCE_DIR}/pycppad/${python}" DESTINATION ${${PYWRAP}_INSTALL_DIR}) diff --git a/python/main.cpp b/python/main.cpp index e0b561b..6cd3801 100644 --- a/python/main.cpp +++ b/python/main.cpp @@ -9,9 +9,26 @@ #include "pycppad/ad.hpp" #include "pycppad/independent.hpp" +#include + +inline std::string printVersion(const std::string & delimiter = ".") +{ + std::ostringstream oss; + oss + << PYCPPAD_MAJOR_VERSION << delimiter + << PYCPPAD_MINOR_VERSION << delimiter + << PYCPPAD_PATCH_VERSION; + return oss.str(); +} + +namespace bp = boost::python; BOOST_PYTHON_MODULE(pycppad) { + bp::docstring_options module_docstring_options(true,true,false); + + bp::scope().attr("__version__") = printVersion(); + bp::scope().attr("__raw_version__") = bp::str(PYCPPAD_VERSION); eigenpy::enableEigenPy(); pycppad::enablePyCppAD(); diff --git a/src/cppad.cpp b/src/cppad.cpp index cbdf7ce..13e85bd 100644 --- a/src/cppad.cpp +++ b/src/cppad.cpp @@ -2,8 +2,8 @@ * Copyright 2021 INRIA */ -#ifdef PYCPPAD_BUILD_CPPAD_CODEGEN_BINDINGS -#include "pycppad/codegen/cppadcg-scalar.hpp" +#ifdef PYCPPAD_WITH_CPPAD_CODEGEN_BINDINGS + #include "pycppad/codegen/cppadcg-scalar.hpp" #endif #include "pycppad/cppad.hpp" @@ -17,7 +17,7 @@ namespace pycppad { exposeCppADScalar(); -#ifdef PYCPPAD_BUILD_CPPAD_CODEGEN_BINDINGS +#ifdef PYCPPAD_WITH_CPPAD_CODEGEN_BINDINGS codegen::exposeCppADCGScalar(); #endif