Skip to content

Commit

Permalink
Merge pull request #504 from ManifoldFR/topic/generic-maps
Browse files Browse the repository at this point in the history
[std-map] add more general visitor for map types
  • Loading branch information
ManifoldFR authored Sep 18, 2024
2 parents 9537d8f + 00bbef8 commit 694d996
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 30 deletions.
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>);
}

0 comments on commit 694d996

Please sign in to comment.