Skip to content

Commit 7c9c10f

Browse files
edantecjcarpent
authored andcommitted
Add bp::dist to std::map converter + unittest
1 parent 8571ed6 commit 7c9c10f

File tree

4 files changed

+190
-7
lines changed

4 files changed

+190
-7
lines changed

include/eigenpy/std-map.hpp

Lines changed: 146 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@
77
#define __eigenpy_utils_map_hpp__
88

99
#include <boost/python/suite/indexing/map_indexing_suite.hpp>
10+
#include "eigenpy/pickle-vector.hpp"
11+
#include <boost/python/stl_iterator.hpp>
12+
#include <boost/python/suite/indexing/map_indexing_suite.hpp>
13+
#include <boost/python/to_python_converter.hpp>
14+
#include <map>
1015

1116
namespace eigenpy {
1217
namespace details {
@@ -31,31 +36,165 @@ struct overload_base_get_item_for_std_map
3136
typename Container::iterator i = container.get().find(idx);
3237
if (i == container.get().end()) {
3338
PyErr_SetString(PyExc_KeyError, "Invalid key");
34-
bp::throw_error_already_set();
39+
boost::python::throw_error_already_set();
3540
}
3641

37-
typename bp::to_python_indirect<data_type&,
38-
bp::detail::make_reference_holder>
42+
typename boost::python::to_python_indirect<
43+
data_type&, boost::python::detail::make_reference_holder>
3944
convert;
40-
return bp::object(bp::handle<>(convert(i->second)));
45+
return boost::python::object(boost::python::handle<>(convert(i->second)));
4146
}
4247

4348
static index_type convert_index(Container& /*container*/, PyObject* i_) {
44-
bp::extract<key_type const&> i(i_);
49+
boost::python::extract<key_type const&> i(i_);
4550
if (i.check()) {
4651
return i();
4752
} else {
48-
bp::extract<key_type> i(i_);
53+
boost::python::extract<key_type> i(i_);
4954
if (i.check()) return i();
5055
}
5156

5257
PyErr_SetString(PyExc_TypeError, "Invalid index type");
53-
bp::throw_error_already_set();
58+
boost::python::throw_error_already_set();
5459
return index_type();
5560
}
5661
};
5762

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

61200
#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)