Skip to content
Snippets Groups Projects
Commit 3666ff02 authored by Wilson Jallet's avatar Wilson Jallet :clapper:
Browse files

Add util to expose optional types

+ default is boost::optional<T>
+ add test for boost::optional
parent 8c252389
No related branches found
No related tags found
No related merge requests found
......@@ -163,6 +163,7 @@ set(${PROJECT_NAME}_HEADERS
include/eigenpy/register.hpp
include/eigenpy/std-map.hpp
include/eigenpy/std-vector.hpp
include/eigenpy/optional.hpp
include/eigenpy/pickle-vector.hpp
include/eigenpy/stride.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>
#define EIGENPY_DEFAULT_OPTIONAL boost::optional
namespace boost {
namespace python {
namespace converter {
template <typename T>
struct expected_pytype_for_arg<EIGENPY_DEFAULT_OPTIONAL<T> >
: expected_pytype_for_arg<T> {};
} // namespace converter
} // namespace python
} // namespace boost
namespace eigenpy {
namespace detail {
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>(boost::none);
} 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__
......@@ -39,6 +39,7 @@ if(NOT NUMPY_WITH_BROKEN_UFUNC_SUPPORT)
endif()
add_lib_unit_test(std_vector)
add_lib_unit_test(user_struct)
add_lib_unit_test(bind_optional)
add_python_unit_test("py-matrix" "unittest/python/test_matrix.py" "unittest")
......@@ -97,3 +98,6 @@ set_tests_properties("py-std-vector" PROPERTIES DEPENDS ${PYWRAP})
add_python_unit_test("py-user-struct" "unittest/python/test_user_struct.py"
"python;unittest")
set_tests_properties("py-std-vector" PROPERTIES DEPENDS ${PYWRAP})
add_python_unit_test("py-optional" "unittest/python/test_optional.py"
"unittest")
#include "eigenpy/eigenpy.hpp"
#include "eigenpy/optional.hpp"
#define OPTIONAL boost::optional
#define OPT_NONE boost::none
using opt_dbl = OPTIONAL<double>;
struct mystruct {
OPTIONAL<int> a;
opt_dbl b;
OPTIONAL<std::string> msg{"i am struct"};
mystruct() : a(OPT_NONE), b(boost::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(bind_optional) {
using namespace eigenpy;
OptionalConverter<int>::registration();
OptionalConverter<double>::registration();
OptionalConverter<std::string>::registration();
OptionalConverter<mystruct>::registration();
OptionalConverter<Eigen::MatrixXd>::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 bind_optional
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)
if __name__ == "__main__":
import pytest
import sys
sys.exit(pytest.main(sys.argv))
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