Skip to content

Commit

Permalink
Merge pull request #499 from edantec/topic/edantec_std_map
Browse files Browse the repository at this point in the history
Add bp::dist to std::map converter + unittest
  • Loading branch information
jcarpent authored Aug 30, 2024
2 parents 8571ed6 + ebfacfd commit f6a47cc
Show file tree
Hide file tree
Showing 5 changed files with 194 additions and 7 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Fixed
- Fix function signature on Windows ([#494](https://github.com/stack-of-tasks/eigenpy/pull/494))

### Added
- Add bp::dist to std::map converter ([#499](https://github.com/stack-of-tasks/eigenpy/pull/499))

## [3.8.1] - 2024-08-25

### Fixed
Expand Down
154 changes: 147 additions & 7 deletions include/eigenpy/std-map.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@
#ifndef __eigenpy_utils_map_hpp__
#define __eigenpy_utils_map_hpp__

#include "eigenpy/pickle-vector.hpp"

#include <boost/python/suite/indexing/map_indexing_suite.hpp>
#include <boost/python/stl_iterator.hpp>
#include <boost/python/suite/indexing/map_indexing_suite.hpp>
#include <boost/python/to_python_converter.hpp>
#include <map>

namespace eigenpy {
namespace details {
Expand All @@ -31,31 +37,165 @@ struct overload_base_get_item_for_std_map
typename Container::iterator i = container.get().find(idx);
if (i == container.get().end()) {
PyErr_SetString(PyExc_KeyError, "Invalid key");
bp::throw_error_already_set();
boost::python::throw_error_already_set();
}

typename bp::to_python_indirect<data_type&,
bp::detail::make_reference_holder>
typename boost::python::to_python_indirect<
data_type&, boost::python::detail::make_reference_holder>
convert;
return bp::object(bp::handle<>(convert(i->second)));
return boost::python::object(boost::python::handle<>(convert(i->second)));
}

static index_type convert_index(Container& /*container*/, PyObject* i_) {
bp::extract<key_type const&> i(i_);
boost::python::extract<key_type const&> i(i_);
if (i.check()) {
return i();
} else {
bp::extract<key_type> i(i_);
boost::python::extract<key_type> i(i_);
if (i.check()) return i();
}

PyErr_SetString(PyExc_TypeError, "Invalid index type");
bp::throw_error_already_set();
boost::python::throw_error_already_set();
return index_type();
}
};

} // namespace details

///////////////////////////////////////////////////////////////////////////////
// The following snippet of code has been taken from the header
// https://github.com/loco-3d/crocoddyl/blob/v2.1.0/bindings/python/crocoddyl/utils/map-converter.hpp
// The Crocoddyl library is written by Carlos Mastalli, Nicolas Mansard and
// Rohan Budhiraja.
///////////////////////////////////////////////////////////////////////////////

namespace python {

namespace bp = boost::python;

/**
* @brief Create a pickle interface for the std::map
*
* @param[in] Container Map type to be pickled
* \sa Pickle
*/
template <typename Container>
struct PickleMap : public PickleVector<Container> {
static void setstate(bp::object op, bp::tuple tup) {
Container& o = bp::extract<Container&>(op)();
bp::stl_input_iterator<typename Container::value_type> begin(tup[0]), end;
o.insert(begin, end);
}
};

/// Conversion from dict to map solution proposed in
/// https://stackoverflow.com/questions/6116345/boostpython-possible-to-automatically-convert-from-dict-stdmap
/// This template encapsulates the conversion machinery.
template <typename Container>
struct dict_to_map {
static void register_converter() {
bp::converter::registry::push_back(&dict_to_map::convertible,
&dict_to_map::construct,
bp::type_id<Container>());
}

/// Check if conversion is possible
static void* convertible(PyObject* object) {
// Check if it is a list
if (!PyObject_GetIter(object)) return 0;
return object;
}

/// Perform the conversion
static void construct(PyObject* object,
bp::converter::rvalue_from_python_stage1_data* data) {
// convert the PyObject pointed to by `object` to a bp::dict
bp::handle<> handle(bp::borrowed(object)); // "smart ptr"
bp::dict dict(handle);

// get a pointer to memory into which we construct the map
// this is provided by the Python runtime
typedef bp::converter::rvalue_from_python_storage<Container> storage_type;
void* storage = reinterpret_cast<storage_type*>(data)->storage.bytes;

// placement-new allocate the result
new (storage) Container();

// iterate over the dictionary `dict`, fill up the map `map`
Container& map(*(static_cast<Container*>(storage)));
bp::list keys(dict.keys());
int keycount(static_cast<int>(bp::len(keys)));
for (int i = 0; i < keycount; ++i) {
// get the key
bp::object keyobj(keys[i]);
bp::extract<typename Container::key_type> keyproxy(keyobj);
if (!keyproxy.check()) {
PyErr_SetString(PyExc_KeyError, "Bad key type");
bp::throw_error_already_set();
}
typename Container::key_type key = keyproxy();

// get the corresponding value
bp::object valobj(dict[keyobj]);
bp::extract<typename Container::mapped_type> valproxy(valobj);
if (!valproxy.check()) {
PyErr_SetString(PyExc_ValueError, "Bad value type");
bp::throw_error_already_set();
}
typename Container::mapped_type val = valproxy();
map[key] = val;
}

// remember the location for later
data->convertible = storage;
}

static bp::dict todict(Container& self) {
bp::dict dict;
typename Container::const_iterator it;
for (it = self.begin(); it != self.end(); ++it) {
dict.setdefault(it->first, it->second);
}
return dict;
}
};

/**
* @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
: 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;
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("todict", &FromPythonDictConverter::todict, bp::arg("self"),
"Returns the std::map as a Python dictionary.")
.def_pickle(PickleMap<Container>());
// Register conversion
FromPythonDictConverter::register_converter();
}
};

} // namespace python
} // namespace eigenpy

#endif // ifndef __eigenpy_utils_map_hpp__
3 changes: 3 additions & 0 deletions unittest/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ endif()
add_lib_unit_test(std_vector)
add_lib_unit_test(std_array)
add_lib_unit_test(std_pair)
add_lib_unit_test(std_map)
add_lib_unit_test(user_struct)

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

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

add_python_lib_unit_test("py-std-map" "unittest/python/test_std_map.py")

add_python_lib_unit_test("py-std-pair" "unittest/python/test_std_pair.py")

add_python_lib_unit_test("py-user-struct" "unittest/python/test_user_struct.py")
Expand Down
6 changes: 6 additions & 0 deletions unittest/python/test_std_map.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from std_map import copy, std_map_to_dict

t = {"one": 1.0, "two": 2.0}

assert std_map_to_dict(t) == t
assert std_map_to_dict(copy(t)) == t
35 changes: 35 additions & 0 deletions unittest/std_map.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/// @file
/// @copyright Copyright 2023 CNRS INRIA

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

namespace bp = boost::python;

template <typename T1>
bp::dict std_map_to_dict(const std::map<std::string, T1>& map) {
bp::dict dictionnary;
for (auto const& x : map) {
dictionnary[x.first] = x.second;
}
return dictionnary;
}

template <typename T1>
std::map<std::string, T1> copy(const std::map<std::string, T1>& map) {
std::map<std::string, T1> out = map;
return out;
}

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

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

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

0 comments on commit f6a47cc

Please sign in to comment.