Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[std-map] add more general visitor for map types, fix non-default-constructible types #504

Merged
merged 6 commits into from
Sep 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [Unreleased]

### Added

- Add more general visitor `GenericMapPythonVisitor` for map types test `boost::unordered_map<std::string, int>` ([#504](https://github.com/stack-of-tasks/eigenpy/pull/504))
- Support for non-[default-contructible](https://en.cppreference.com/w/cpp/named_req/DefaultConstructible) types in map types ([#504](https://github.com/stack-of-tasks/eigenpy/pull/504))
- Add type_info helpers ([#502](https://github.com/stack-of-tasks/eigenpy/pull/502))

### Changed

- Move `StdMapPythonVisitor` out of `eigenpy::python` namespace, which was a mistake ([#504](https://github.com/stack-of-tasks/eigenpy/pull/504))

## [3.9.0] - 2024-08-31

### Changed
Expand Down
19 changes: 10 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,16 @@ EigenPy — Versatile and efficient Python bindings between Numpy and Eigen
**EigenPy** is an open-source framework that allows the binding of the famous [Eigen](http://eigen.tuxfamily.org) C++ library in Python via Boost.Python.

**EigenPy** provides:
- full memory sharing between Numpy and Eigen, avoiding memory allocation
- full support Eigen::Ref avoiding memory allocation
- full support of the Eigen::Tensor module
- exposition of the Geometry module of Eigen for easy code prototyping
- standard matrix decomposion routines of Eigen such as the Cholesky decomposition (SVD and QR decompositions [can be added](#contributing))
- full support of SWIG objects
- full support of runtime declaration of Numpy scalar types
- extended API to expose std::vector types
- full support of vectorization between C++ and Python (all the hold objects are properly aligned in memory)

- full memory sharing between Numpy and Eigen, avoiding memory allocation
- full support Eigen::Ref avoiding memory allocation
- full support of the Eigen::Tensor module
- exposition of the Geometry module of Eigen for easy code prototyping
- standard matrix decomposion routines of Eigen such as the Cholesky decomposition (SVD and QR decompositions [can be added](#contributing))
- full support of SWIG objects
- full support of runtime declaration of Numpy scalar types
- extended API to expose several STL types and some of their Boost equivalents: `optional` types, `std::pair`, maps, variants...
- full support of vectorization between C++ and Python (all the hold objects are properly aligned in memory)

## Setup

Expand Down
85 changes: 67 additions & 18 deletions include/eigenpy/std-map.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,6 @@ struct overload_base_get_item_for_std_map
// Rohan Budhiraja.
///////////////////////////////////////////////////////////////////////////////

namespace python {

namespace bp = boost::python;

/**
Expand Down Expand Up @@ -144,7 +142,7 @@ struct dict_to_map {
bp::throw_error_already_set();
}
typename Container::mapped_type val = valproxy();
map[key] = val;
map.emplace(key, val);
}

// remember the location for later
Expand All @@ -161,40 +159,91 @@ struct dict_to_map {
}
};

/// Policies which handle the non-default constructible case
/// and set_item() using emplace().
template <class Container, bool NoProxy>
struct emplace_set_derived_policies
: bp::map_indexing_suite<
Container, NoProxy,
emplace_set_derived_policies<Container, NoProxy> > {
typedef typename Container::key_type index_type;
typedef typename Container::value_type::second_type data_type;
typedef typename Container::value_type value_type;
using DerivedPolicies =
bp::detail::final_map_derived_policies<Container, NoProxy>;

template <class Class>
static void extension_def(Class& cl) {
// Wrap the map's element (value_type)
std::string elem_name = "map_indexing_suite_";
bp::object class_name(cl.attr("__name__"));
bp::extract<std::string> class_name_extractor(class_name);
elem_name += class_name_extractor();
elem_name += "_entry";
namespace mpl = boost::mpl;

typedef typename mpl::if_<
mpl::and_<boost::is_class<data_type>, mpl::bool_<!NoProxy> >,
bp::return_internal_reference<>, bp::default_call_policies>::type
get_data_return_policy;

bp::class_<value_type>(elem_name.c_str(), bp::no_init)
.def("__repr__", &DerivedPolicies::print_elem)
.def("data", &DerivedPolicies::get_data, get_data_return_policy())
.def("key", &DerivedPolicies::get_key);
}

static void set_item(Container& container, index_type i, data_type const& v) {
container.emplace(i, v);
}
};

/**
* @brief Expose an std::map from a type given as template argument.
* @brief Expose the map-like container, e.g. (std::map).
*
* @param[in] T Type to expose as std::map<T>.
* @param[in] Compare Type for the Compare in std::map<T,Compare,Allocator>.
* @param[in] Allocator Type for the Allocator in
* std::map<T,Compare,Allocator>.
* @param[in] Container Container to expose.
* @param[in] NoProxy When set to false, the elements will be copied when
* returned to Python.
*/
template <class Key, class T, class Compare = std::less<Key>,
class Allocator = std::allocator<std::pair<const Key, T> >,
bool NoProxy = false>
struct StdMapPythonVisitor
: public bp::map_indexing_suite<
typename std::map<Key, T, Compare, Allocator>, NoProxy>,
public dict_to_map<std::map<Key, T, Compare, Allocator> > {
typedef std::map<Key, T, Compare, Allocator> Container;
template <class Container, bool NoProxy = false>
struct GenericMapVisitor
: public emplace_set_derived_policies<Container, NoProxy>,
public dict_to_map<Container> {
typedef dict_to_map<Container> FromPythonDictConverter;

static void expose(const std::string& class_name,
const std::string& doc_string = "") {
namespace bp = bp;

bp::class_<Container>(class_name.c_str(), doc_string.c_str())
.def(StdMapPythonVisitor())
.def(GenericMapVisitor())
.def("todict", &FromPythonDictConverter::todict, bp::arg("self"),
"Returns the std::map as a Python dictionary.")
"Returns the map type as a Python dictionary.")
.def_pickle(PickleMap<Container>());
// Register conversion
FromPythonDictConverter::register_converter();
}
};

/**
* @brief Expose an std::map from a type given as template argument.
*
* @param[in] T Type to expose as std::map<T>.
* @param[in] Compare Type for the Compare in std::map<T,Compare,Allocator>.
* @param[in] Allocator Type for the Allocator in
* std::map<T,Compare,Allocator>.
* @param[in] NoProxy When set to false, the elements will be copied when
* returned to Python.
*/
template <class Key, class T, class Compare = std::less<Key>,
class Allocator = std::allocator<std::pair<const Key, T> >,
bool NoProxy = false>
struct StdMapPythonVisitor
: GenericMapVisitor<std::map<Key, T, Compare, Allocator>, NoProxy> {};

namespace python {
// fix previous mistake
using ::eigenpy::StdMapPythonVisitor;
} // namespace python
} // namespace eigenpy

Expand Down
5 changes: 4 additions & 1 deletion unittest/python/test_std_map.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
from std_map import copy, std_map_to_dict
from std_map import copy, copy_boost, std_map_to_dict

t = {"one": 1.0, "two": 2.0}
t2 = {"one": 1, "two": 2, "three": 3}

assert std_map_to_dict(t) == t
assert std_map_to_dict(copy(t)) == t
m = copy_boost(t2)
assert m.todict() == t2
22 changes: 20 additions & 2 deletions unittest/std_map.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

#include <eigenpy/eigenpy.hpp>
#include <eigenpy/std-map.hpp>
#include <iostream>
#include <boost/unordered_map.hpp>

namespace bp = boost::python;

Expand All @@ -22,14 +22,32 @@ std::map<std::string, T1> copy(const std::map<std::string, T1>& map) {
return out;
}

template <typename T1>
boost::unordered_map<std::string, T1> copy_boost(
const boost::unordered_map<std::string, T1>& obj) {
return obj;
}

struct X {
X() = delete;
X(int x) : val(x) {}
int val;
};

BOOST_PYTHON_MODULE(std_map) {
eigenpy::enableEigenPy();

eigenpy::python::StdMapPythonVisitor<
eigenpy::StdMapPythonVisitor<
std::string, double, std::less<std::string>,
std::allocator<std::pair<const std::string, double> >,
true>::expose("StdMap_Double");

eigenpy::GenericMapVisitor<boost::unordered_map<std::string, int> >::expose(
"boost_map_int");

eigenpy::GenericMapVisitor<std::map<std::string, X> >::expose("StdMap_X");

bp::def("std_map_to_dict", std_map_to_dict<double>);
bp::def("copy", copy<double>);
bp::def("copy_boost", copy_boost<int>);
}
Loading