Skip to content
Snippets Groups Projects
Unverified Commit 5e2e7b78 authored by Justin Carpentier's avatar Justin Carpentier Committed by GitHub
Browse files

Merge pull request #357 from ManifoldFR/wjallet/bind-boost-optional

Add util to expose optional types
parents 8c252389 f4952d59
No related branches found
No related tags found
No related merge requests found
Pipeline #27115 passed with warnings
...@@ -163,6 +163,7 @@ set(${PROJECT_NAME}_HEADERS ...@@ -163,6 +163,7 @@ set(${PROJECT_NAME}_HEADERS
include/eigenpy/register.hpp include/eigenpy/register.hpp
include/eigenpy/std-map.hpp include/eigenpy/std-map.hpp
include/eigenpy/std-vector.hpp include/eigenpy/std-vector.hpp
include/eigenpy/optional.hpp
include/eigenpy/pickle-vector.hpp include/eigenpy/pickle-vector.hpp
include/eigenpy/stride.hpp include/eigenpy/stride.hpp
include/eigenpy/tensor/eigen-from-python.hpp include/eigenpy/tensor/eigen-from-python.hpp
......
/// Copyright (c) 2023 CNRS INRIA
/// Definitions for exposing boost::optional<T> types.
/// Also works with std::optional.
#ifndef __eigenpy_optional_hpp__
#define __eigenpy_optional_hpp__
#include "eigenpy/fwd.hpp"
#include "eigenpy/eigen-from-python.hpp"
#include <boost/optional.hpp>
#ifdef EIGENPY_WITH_CXX17_SUPPORT
#include <optional>
#endif
#ifndef EIGENPY_DEFAULT_OPTIONAL
#define EIGENPY_DEFAULT_OPTIONAL boost::optional
#endif
namespace boost {
namespace python {
namespace converter {
template <typename T>
struct expected_pytype_for_arg<boost::optional<T> >
: expected_pytype_for_arg<T> {};
#ifdef EIGENPY_WITH_CXX17_SUPPORT
template <typename T>
struct expected_pytype_for_arg<std::optional<T> > : expected_pytype_for_arg<T> {
};
#endif
} // namespace converter
} // namespace python
} // namespace boost
namespace eigenpy {
namespace detail {
/// Helper struct to decide which type is the "none" type for a specific
/// optional<T> implementation.
template <template <typename> class OptionalTpl>
struct nullopt_helper {};
template <>
struct nullopt_helper<boost::optional> {
typedef boost::none_t type;
static type value() { return boost::none; }
};
#ifdef EIGENPY_WITH_CXX17_SUPPORT
template <>
struct nullopt_helper<std::optional> {
typedef std::nullopt_t type;
static type value() { return std::nullopt; }
};
#endif
template <typename T,
template <typename> class OptionalTpl = EIGENPY_DEFAULT_OPTIONAL>
struct OptionalToPython {
static PyObject *convert(const OptionalTpl<T> &obj) {
if (obj)
return bp::incref(bp::object(*obj).ptr());
else {
return bp::incref(bp::object().ptr()); // None
}
}
static PyTypeObject const *get_pytype() {
return bp::converter::registered_pytype<T>::get_pytype();
}
static void registration() {
bp::to_python_converter<OptionalTpl<T>, OptionalToPython, true>();
}
};
template <typename T,
template <typename> class OptionalTpl = EIGENPY_DEFAULT_OPTIONAL>
struct OptionalFromPython {
static void *convertible(PyObject *obj_ptr);
static void construct(PyObject *obj_ptr,
bp::converter::rvalue_from_python_stage1_data *memory);
static void registration();
};
template <typename T, template <typename> class OptionalTpl>
void *OptionalFromPython<T, OptionalTpl>::convertible(PyObject *obj_ptr) {
if (obj_ptr == Py_None) {
return obj_ptr;
}
bp::extract<T> bp_obj(obj_ptr);
if (!bp_obj.check())
return 0;
else
return obj_ptr;
}
template <typename T, template <typename> class OptionalTpl>
void OptionalFromPython<T, OptionalTpl>::construct(
PyObject *obj_ptr, bp::converter::rvalue_from_python_stage1_data *memory) {
// create storage
using rvalue_storage_t =
bp::converter::rvalue_from_python_storage<OptionalTpl<T> >;
void *storage =
reinterpret_cast<rvalue_storage_t *>(reinterpret_cast<void *>(memory))
->storage.bytes;
if (obj_ptr == Py_None) {
new (storage) OptionalTpl<T>(nullopt_helper<OptionalTpl>::value());
} else {
const T value = bp::extract<T>(obj_ptr);
new (storage) OptionalTpl<T>(value);
}
memory->convertible = storage;
}
template <typename T, template <typename> class OptionalTpl>
void OptionalFromPython<T, OptionalTpl>::registration() {
bp::converter::registry::push_back(
&convertible, &construct, bp::type_id<OptionalTpl<T> >(),
bp::converter::expected_pytype_for_arg<OptionalTpl<T> >::get_pytype);
}
} // namespace detail
/// Register converters for the type `optional<T>` to Python.
/// By default \tparam optional is `EIGENPY_DEFAULT_OPTIONAL`.
template <typename T,
template <typename> class OptionalTpl = EIGENPY_DEFAULT_OPTIONAL>
struct OptionalConverter {
static void registration() {
detail::OptionalToPython<T, OptionalTpl>::registration();
detail::OptionalFromPython<T, OptionalTpl>::registration();
}
};
} // namespace eigenpy
#endif // __eigenpy_optional_hpp__
...@@ -40,6 +40,28 @@ endif() ...@@ -40,6 +40,28 @@ endif()
add_lib_unit_test(std_vector) add_lib_unit_test(std_vector)
add_lib_unit_test(user_struct) add_lib_unit_test(user_struct)
function(config_bind_optional tagname opttype)
set(MODNAME bind_optional_${tagname})
set(OPTIONAL ${opttype})
configure_file(bind_optional.cpp.in ${MODNAME}.cpp)
set(py_file test_optional_${tagname}.py)
configure_file(python/test_optional.py.in
${CMAKE_CURRENT_SOURCE_DIR}/python/${py_file})
add_lib_unit_test(${MODNAME})
message(
STATUS
"Adding unit test py-optional-${tagname} with file ${py_file} and module ${MODNAME}"
)
add_python_unit_test("py-optional-${tagname}" "unittest/python/${py_file}"
"unittest")
endfunction()
config_bind_optional(boost "boost::optional")
if(CMAKE_CXX_STANDARD GREATER 14 AND CMAKE_CXX_STANDARD LESS 98)
config_bind_optional(std "std::optional")
endif()
add_python_unit_test("py-matrix" "unittest/python/test_matrix.py" "unittest") add_python_unit_test("py-matrix" "unittest/python/test_matrix.py" "unittest")
add_python_unit_test("py-tensor" "unittest/python/test_tensor.py" "unittest") add_python_unit_test("py-tensor" "unittest/python/test_tensor.py" "unittest")
......
///
/// Copyright (c) 2023 CNRS INRIA
///
#include "eigenpy/eigenpy.hpp"
#include "eigenpy/optional.hpp"
#ifdef EIGENPY_WITH_CXX17_SUPPORT
#include <optional>
#endif
#cmakedefine OPTIONAL @OPTIONAL@
typedef eigenpy::detail::nullopt_helper<OPTIONAL> none_helper;
static auto OPT_NONE = none_helper::value();
typedef OPTIONAL<double> opt_dbl;
struct mystruct {
OPTIONAL<int> a;
opt_dbl b;
OPTIONAL<std::string> msg{"i am struct"};
mystruct() : a(OPT_NONE), b(OPT_NONE) {}
mystruct(int a, const opt_dbl &b = OPT_NONE) : a(a), b(b) {}
};
OPTIONAL<int> none_if_zero(int i) {
if (i == 0)
return OPT_NONE;
else
return i;
}
OPTIONAL<mystruct> create_if_true(bool flag, opt_dbl b = OPT_NONE) {
if (flag) {
return mystruct(0, b);
} else {
return OPT_NONE;
}
}
OPTIONAL<Eigen::MatrixXd> random_mat_if_true(bool flag) {
if (flag)
return Eigen::MatrixXd(Eigen::MatrixXd::Random(4, 4));
else
return OPT_NONE;
}
BOOST_PYTHON_MODULE(@MODNAME@) {
using namespace eigenpy;
OptionalConverter<int, OPTIONAL>::registration();
OptionalConverter<double, OPTIONAL>::registration();
OptionalConverter<std::string, OPTIONAL>::registration();
OptionalConverter<mystruct, OPTIONAL>::registration();
OptionalConverter<Eigen::MatrixXd, OPTIONAL>::registration();
enableEigenPy();
bp::class_<mystruct>("mystruct", bp::no_init)
.def(bp::init<>(bp::args("self")))
.def(bp::init<int, bp::optional<const opt_dbl &> >(
bp::args("self", "a", "b")))
.add_property(
"a",
bp::make_getter(&mystruct::a,
bp::return_value_policy<bp::return_by_value>()),
bp::make_setter(&mystruct::a))
.add_property(
"b",
bp::make_getter(&mystruct::b,
bp::return_value_policy<bp::return_by_value>()),
bp::make_setter(&mystruct::b))
.add_property(
"msg",
bp::make_getter(&mystruct::msg,
bp::return_value_policy<bp::return_by_value>()),
bp::make_setter(&mystruct::msg));
bp::def("none_if_zero", none_if_zero, bp::args("i"));
bp::def("create_if_true", create_if_true, bp::args("flag", "b"));
bp::def("random_mat_if_true", random_mat_if_true, bp::args("flag"));
}
import importlib
bind_optional = importlib.import_module("@MODNAME@")
def test_none_if_zero():
x = bind_optional.none_if_zero(0)
y = bind_optional.none_if_zero(-1)
assert x is None
assert y == -1
def test_struct_ctors():
# test struct ctors
struct = bind_optional.mystruct()
assert struct.a is None
assert struct.b is None
assert struct.msg == "i am struct"
## no 2nd arg automatic overload using bp::optional
struct = bind_optional.mystruct(2)
assert struct.a == 2
assert struct.b is None
struct = bind_optional.mystruct(13, -1.0)
assert struct.a == 13
assert struct.b == -1.0
def test_struct_setters():
struct = bind_optional.mystruct()
struct.a = 1
assert struct.a == 1
struct.b = -3.14
assert struct.b == -3.14
# set to None
struct.a = None
struct.b = None
struct.msg = None
assert struct.a is None
assert struct.b is None
assert struct.msg is None
def test_factory():
struct = bind_optional.create_if_true(False, None)
assert struct is None
struct = bind_optional.create_if_true(True, None)
assert struct.a == 0
assert struct.b is None
def test_random_mat():
M = bind_optional.random_mat_if_true(False)
assert M is None
M = bind_optional.random_mat_if_true(True)
assert M.shape == (4, 4)
test_none_if_zero()
test_struct_ctors()
test_struct_setters()
test_factory()
test_random_mat()
import importlib
bind_optional = importlib.import_module("bind_optional_boost")
def test_none_if_zero():
x = bind_optional.none_if_zero(0)
y = bind_optional.none_if_zero(-1)
assert x is None
assert y == -1
def test_struct_ctors():
# test struct ctors
struct = bind_optional.mystruct()
assert struct.a is None
assert struct.b is None
assert struct.msg == "i am struct"
## no 2nd arg automatic overload using bp::optional
struct = bind_optional.mystruct(2)
assert struct.a == 2
assert struct.b is None
struct = bind_optional.mystruct(13, -1.0)
assert struct.a == 13
assert struct.b == -1.0
def test_struct_setters():
struct = bind_optional.mystruct()
struct.a = 1
assert struct.a == 1
struct.b = -3.14
assert struct.b == -3.14
# set to None
struct.a = None
struct.b = None
struct.msg = None
assert struct.a is None
assert struct.b is None
assert struct.msg is None
def test_factory():
struct = bind_optional.create_if_true(False, None)
assert struct is None
struct = bind_optional.create_if_true(True, None)
assert struct.a == 0
assert struct.b is None
def test_random_mat():
M = bind_optional.random_mat_if_true(False)
assert M is None
M = bind_optional.random_mat_if_true(True)
assert M.shape == (4, 4)
test_none_if_zero()
test_struct_ctors()
test_struct_setters()
test_factory()
test_random_mat()
import importlib
bind_optional = importlib.import_module("bind_optional_std")
def test_none_if_zero():
x = bind_optional.none_if_zero(0)
y = bind_optional.none_if_zero(-1)
assert x is None
assert y == -1
def test_struct_ctors():
# test struct ctors
struct = bind_optional.mystruct()
assert struct.a is None
assert struct.b is None
assert struct.msg == "i am struct"
## no 2nd arg automatic overload using bp::optional
struct = bind_optional.mystruct(2)
assert struct.a == 2
assert struct.b is None
struct = bind_optional.mystruct(13, -1.0)
assert struct.a == 13
assert struct.b == -1.0
def test_struct_setters():
struct = bind_optional.mystruct()
struct.a = 1
assert struct.a == 1
struct.b = -3.14
assert struct.b == -3.14
# set to None
struct.a = None
struct.b = None
struct.msg = None
assert struct.a is None
assert struct.b is None
assert struct.msg is None
def test_factory():
struct = bind_optional.create_if_true(False, None)
assert struct is None
struct = bind_optional.create_if_true(True, None)
assert struct.a == 0
assert struct.b is None
def test_random_mat():
M = bind_optional.random_mat_if_true(False)
assert M is None
M = bind_optional.random_mat_if_true(True)
assert M.shape == (4, 4)
test_none_if_zero()
test_struct_ctors()
test_struct_setters()
test_factory()
test_random_mat()
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment