Skip to content

Commit f6a47cc

Browse files
authored
Merge pull request #499 from edantec/topic/edantec_std_map
Add bp::dist to std::map converter + unittest
2 parents 8571ed6 + ebfacfd commit f6a47cc

File tree

5 files changed

+194
-7
lines changed

5 files changed

+194
-7
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
1515
### Fixed
1616
- Fix function signature on Windows ([#494](https://github.com/stack-of-tasks/eigenpy/pull/494))
1717

18+
### Added
19+
- Add bp::dist to std::map converter ([#499](https://github.com/stack-of-tasks/eigenpy/pull/499))
20+
1821
## [3.8.1] - 2024-08-25
1922

2023
### Fixed

include/eigenpy/std-map.hpp

Lines changed: 147 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,13 @@
66
#ifndef __eigenpy_utils_map_hpp__
77
#define __eigenpy_utils_map_hpp__
88

9+
#include "eigenpy/pickle-vector.hpp"
10+
11+
#include <boost/python/suite/indexing/map_indexing_suite.hpp>
12+
#include <boost/python/stl_iterator.hpp>
913
#include <boost/python/suite/indexing/map_indexing_suite.hpp>
14+
#include <boost/python/to_python_converter.hpp>
15+
#include <map>
1016

1117
namespace eigenpy {
1218
namespace details {
@@ -31,31 +37,165 @@ struct overload_base_get_item_for_std_map
3137
typename Container::iterator i = container.get().find(idx);
3238
if (i == container.get().end()) {
3339
PyErr_SetString(PyExc_KeyError, "Invalid key");
34-
bp::throw_error_already_set();
40+
boost::python::throw_error_already_set();
3541
}
3642

37-
typename bp::to_python_indirect<data_type&,
38-
bp::detail::make_reference_holder>
43+
typename boost::python::to_python_indirect<
44+
data_type&, boost::python::detail::make_reference_holder>
3945
convert;
40-
return bp::object(bp::handle<>(convert(i->second)));
46+
return boost::python::object(boost::python::handle<>(convert(i->second)));
4147
}
4248

4349
static index_type convert_index(Container& /*container*/, PyObject* i_) {
44-
bp::extract<key_type const&> i(i_);
50+
boost::python::extract<key_type const&> i(i_);
4551
if (i.check()) {
4652
return i();
4753
} else {
48-
bp::extract<key_type> i(i_);
54+
boost::python::extract<key_type> i(i_);
4955
if (i.check()) return i();
5056
}
5157

5258
PyErr_SetString(PyExc_TypeError, "Invalid index type");
53-
bp::throw_error_already_set();
59+
boost::python::throw_error_already_set();
5460
return index_type();
5561
}
5662
};
5763

5864
} // namespace details
65+
66+
///////////////////////////////////////////////////////////////////////////////
67+
// The following snippet of code has been taken from the header
68+
// https://github.com/loco-3d/crocoddyl/blob/v2.1.0/bindings/python/crocoddyl/utils/map-converter.hpp
69+
// The Crocoddyl library is written by Carlos Mastalli, Nicolas Mansard and
70+
// Rohan Budhiraja.
71+
///////////////////////////////////////////////////////////////////////////////
72+
73+
namespace python {
74+
75+
namespace bp = boost::python;
76+
77+
/**
78+
* @brief Create a pickle interface for the std::map
79+
*
80+
* @param[in] Container Map type to be pickled
81+
* \sa Pickle
82+
*/
83+
template <typename Container>
84+
struct PickleMap : public PickleVector<Container> {
85+
static void setstate(bp::object op, bp::tuple tup) {
86+
Container& o = bp::extract<Container&>(op)();
87+
bp::stl_input_iterator<typename Container::value_type> begin(tup[0]), end;
88+
o.insert(begin, end);
89+
}
90+
};
91+
92+
/// Conversion from dict to map solution proposed in
93+
/// https://stackoverflow.com/questions/6116345/boostpython-possible-to-automatically-convert-from-dict-stdmap
94+
/// This template encapsulates the conversion machinery.
95+
template <typename Container>
96+
struct dict_to_map {
97+
static void register_converter() {
98+
bp::converter::registry::push_back(&dict_to_map::convertible,
99+
&dict_to_map::construct,
100+
bp::type_id<Container>());
101+
}
102+
103+
/// Check if conversion is possible
104+
static void* convertible(PyObject* object) {
105+
// Check if it is a list
106+
if (!PyObject_GetIter(object)) return 0;
107+
return object;
108+
}
109+
110+
/// Perform the conversion
111+
static void construct(PyObject* object,
112+
bp::converter::rvalue_from_python_stage1_data* data) {
113+
// convert the PyObject pointed to by `object` to a bp::dict
114+
bp::handle<> handle(bp::borrowed(object)); // "smart ptr"
115+
bp::dict dict(handle);
116+
117+
// get a pointer to memory into which we construct the map
118+
// this is provided by the Python runtime
119+
typedef bp::converter::rvalue_from_python_storage<Container> storage_type;
120+
void* storage = reinterpret_cast<storage_type*>(data)->storage.bytes;
121+
122+
// placement-new allocate the result
123+
new (storage) Container();
124+
125+
// iterate over the dictionary `dict`, fill up the map `map`
126+
Container& map(*(static_cast<Container*>(storage)));
127+
bp::list keys(dict.keys());
128+
int keycount(static_cast<int>(bp::len(keys)));
129+
for (int i = 0; i < keycount; ++i) {
130+
// get the key
131+
bp::object keyobj(keys[i]);
132+
bp::extract<typename Container::key_type> keyproxy(keyobj);
133+
if (!keyproxy.check()) {
134+
PyErr_SetString(PyExc_KeyError, "Bad key type");
135+
bp::throw_error_already_set();
136+
}
137+
typename Container::key_type key = keyproxy();
138+
139+
// get the corresponding value
140+
bp::object valobj(dict[keyobj]);
141+
bp::extract<typename Container::mapped_type> valproxy(valobj);
142+
if (!valproxy.check()) {
143+
PyErr_SetString(PyExc_ValueError, "Bad value type");
144+
bp::throw_error_already_set();
145+
}
146+
typename Container::mapped_type val = valproxy();
147+
map[key] = val;
148+
}
149+
150+
// remember the location for later
151+
data->convertible = storage;
152+
}
153+
154+
static bp::dict todict(Container& self) {
155+
bp::dict dict;
156+
typename Container::const_iterator it;
157+
for (it = self.begin(); it != self.end(); ++it) {
158+
dict.setdefault(it->first, it->second);
159+
}
160+
return dict;
161+
}
162+
};
163+
164+
/**
165+
* @brief Expose an std::map from a type given as template argument.
166+
*
167+
* @param[in] T Type to expose as std::map<T>.
168+
* @param[in] Compare Type for the Compare in std::map<T,Compare,Allocator>.
169+
* @param[in] Allocator Type for the Allocator in
170+
* std::map<T,Compare,Allocator>.
171+
* @param[in] NoProxy When set to false, the elements will be copied when
172+
* returned to Python.
173+
*/
174+
template <class Key, class T, class Compare = std::less<Key>,
175+
class Allocator = std::allocator<std::pair<const Key, T> >,
176+
bool NoProxy = false>
177+
struct StdMapPythonVisitor
178+
: public bp::map_indexing_suite<
179+
typename std::map<Key, T, Compare, Allocator>, NoProxy>,
180+
public dict_to_map<std::map<Key, T, Compare, Allocator> > {
181+
typedef std::map<Key, T, Compare, Allocator> Container;
182+
typedef dict_to_map<Container> FromPythonDictConverter;
183+
184+
static void expose(const std::string& class_name,
185+
const std::string& doc_string = "") {
186+
namespace bp = bp;
187+
188+
bp::class_<Container>(class_name.c_str(), doc_string.c_str())
189+
.def(StdMapPythonVisitor())
190+
.def("todict", &FromPythonDictConverter::todict, bp::arg("self"),
191+
"Returns the std::map as a Python dictionary.")
192+
.def_pickle(PickleMap<Container>());
193+
// Register conversion
194+
FromPythonDictConverter::register_converter();
195+
}
196+
};
197+
198+
} // namespace python
59199
} // namespace eigenpy
60200

61201
#endif // ifndef __eigenpy_utils_map_hpp__

unittest/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ endif()
5656
add_lib_unit_test(std_vector)
5757
add_lib_unit_test(std_array)
5858
add_lib_unit_test(std_pair)
59+
add_lib_unit_test(std_map)
5960
add_lib_unit_test(user_struct)
6061

6162
if(CMAKE_CXX_STANDARD GREATER 14 AND CMAKE_CXX_STANDARD LESS 98)
@@ -155,6 +156,8 @@ add_python_eigenpy_lib_unit_test("py-std-vector"
155156

156157
add_python_lib_unit_test("py-std-array" "unittest/python/test_std_array.py")
157158

159+
add_python_lib_unit_test("py-std-map" "unittest/python/test_std_map.py")
160+
158161
add_python_lib_unit_test("py-std-pair" "unittest/python/test_std_pair.py")
159162

160163
add_python_lib_unit_test("py-user-struct" "unittest/python/test_user_struct.py")

unittest/python/test_std_map.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from std_map import copy, std_map_to_dict
2+
3+
t = {"one": 1.0, "two": 2.0}
4+
5+
assert std_map_to_dict(t) == t
6+
assert std_map_to_dict(copy(t)) == t

unittest/std_map.cpp

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/// @file
2+
/// @copyright Copyright 2023 CNRS INRIA
3+
4+
#include <eigenpy/eigenpy.hpp>
5+
#include <eigenpy/std-map.hpp>
6+
#include <iostream>
7+
8+
namespace bp = boost::python;
9+
10+
template <typename T1>
11+
bp::dict std_map_to_dict(const std::map<std::string, T1>& map) {
12+
bp::dict dictionnary;
13+
for (auto const& x : map) {
14+
dictionnary[x.first] = x.second;
15+
}
16+
return dictionnary;
17+
}
18+
19+
template <typename T1>
20+
std::map<std::string, T1> copy(const std::map<std::string, T1>& map) {
21+
std::map<std::string, T1> out = map;
22+
return out;
23+
}
24+
25+
BOOST_PYTHON_MODULE(std_map) {
26+
eigenpy::enableEigenPy();
27+
28+
eigenpy::python::StdMapPythonVisitor<
29+
std::string, double, std::less<std::string>,
30+
std::allocator<std::pair<const std::string, double> >,
31+
true>::expose("StdMap_Double");
32+
33+
bp::def("std_map_to_dict", std_map_to_dict<double>);
34+
bp::def("copy", copy<double>);
35+
}

0 commit comments

Comments
 (0)