diff --git a/lincs/liblincs/io/model.cpp b/lincs/liblincs/io/model.cpp index e347a7c0..f4b54024 100644 --- a/lincs/liblincs/io/model.cpp +++ b/lincs/liblincs/io/model.cpp @@ -101,6 +101,22 @@ JsonValidator validator(schema); } // namespace +std::vector> SufficientCoalitions::get_upset_roots() const { + std::vector> roots; + + roots.reserve(upset_roots.size()); + for (const auto& upset_root : upset_roots) { + std::vector& root = roots.emplace_back(); + for (unsigned criterion_index = 0; criterion_index != upset_root.size(); ++criterion_index) { + if (upset_root[criterion_index]) { + root.emplace_back(criterion_index); + } + } + } + + return roots; +} + void Model::dump(const Problem& problem, std::ostream& os) const { CHRONE(); @@ -134,19 +150,13 @@ void Model::dump(const Problem& problem, std::ostream& os) const { break; case SufficientCoalitions::Kind::roots: out << YAML::Key << "upset_roots" << YAML::Value; - const auto upset_roots = boundary.sufficient_coalitions.upset_roots; + const std::vector> upset_roots = boundary.sufficient_coalitions.get_upset_roots(); if (upset_roots.empty()) { out << YAML::Flow; } out << YAML::BeginSeq; - for (const auto& root : upset_roots) { - out << YAML::Flow << YAML::BeginSeq; - for (unsigned criterion_index = 0; criterion_index != problem.criteria.size(); ++criterion_index) { - if (root[criterion_index]) { - out << criterion_index; - } - } - out << YAML::EndSeq; + for (const std::vector& upset_root : upset_roots) { + out << YAML::Flow << upset_root; } out << YAML::EndSeq; break; diff --git a/lincs/liblincs/io/model.hpp b/lincs/liblincs/io/model.hpp index fe49b09c..4f827bf4 100644 --- a/lincs/liblincs/io/model.hpp +++ b/lincs/liblincs/io/model.hpp @@ -36,9 +36,9 @@ struct SufficientCoalitions { SufficientCoalitions(Roots, const unsigned criteria_count, const std::vector>& upset_roots_) : kind(Kind::roots), upset_roots() { upset_roots.reserve(upset_roots_.size()); for (const auto& root: upset_roots_) { - upset_roots.emplace_back(criteria_count); + boost::dynamic_bitset<>& upset_root = upset_roots.emplace_back(criteria_count); for (unsigned criterion_index: root) { - upset_roots.back()[criterion_index] = true; + upset_root[criterion_index] = true; } } } @@ -48,6 +48,8 @@ struct SufficientCoalitions { upset_roots(upset_roots_) {} + std::vector> get_upset_roots() const; + bool operator==(const SufficientCoalitions& other) const { return kind == other.kind && criterion_weights == other.criterion_weights && upset_roots == other.upset_roots; } diff --git a/lincs/liblincs/liblincs_module.cpp b/lincs/liblincs/liblincs_module.cpp index cc7caab3..570e27ff 100644 --- a/lincs/liblincs/liblincs_module.cpp +++ b/lincs/liblincs/liblincs_module.cpp @@ -88,42 +88,80 @@ lincs::Alternatives load_alternatives(const lincs::Problem& problem, bp::object& return lincs::Alternatives::load(problem, in_stream); } -// @todo(Project management, soon) Thoroughly review all conversions between Python and C++ types. -// - read Boost.Python doc in details and understand the contract -// - homogenize converters (some were copy-pasted from SO answers and even ChatGPT) -// - double-check if/when we need to increment reference counts on Python objects -// https://stackoverflow.com/a/15940413/905845 -// https://misspent.wordpress.com/2009/09/27/how-to-write-boost-python-converters/ -struct iterable_converter { - template - iterable_converter& from_python() { +template +struct std_vector_converter { + static void* convertible(PyObject* obj) { + if (PyObject_GetIter(obj)) { + return obj; + } else { + return nullptr; + } + } + + static void construct(PyObject* obj, bp::converter::rvalue_from_python_stage1_data* data) { + bp::handle<> handle(bp::borrowed(obj)); + + typedef bp::converter::rvalue_from_python_storage> storage_type; + void* storage = reinterpret_cast(data)->storage.bytes; + + typedef bp::stl_input_iterator::value_type> iterator; + + new (storage) std::vector(iterator(bp::object(handle)), iterator()); + data->convertible = storage; + } + + static void enroll() { + // No need for 'bp::to_python_converter': already implemented by Boost.Python bp::converter::registry::push_back( - &iterable_converter::convertible, - &iterable_converter::construct, - bp::type_id()); + &std_vector_converter::convertible, + &std_vector_converter::construct, + bp::type_id>() + ); + } +}; - return *this; +template +struct std_vector_converter> { + static PyObject* convert(const std::vector>& vvv) { + bp::list result; + for (const std::vector& vv : vvv) { + bp::list sublist; + for (const T& v : vv) { + sublist.append(v); + } + result.append(sublist); + } + return bp::incref(result.ptr()); } - static void* convertible(PyObject* object) { - return PyObject_GetIter(object) ? object : NULL; + static void* convertible(PyObject* obj) { + if (PyObject_GetIter(obj)) { + return obj; + } else { + return nullptr; + } } - template - static void construct( - PyObject* object, - bp::converter::rvalue_from_python_stage1_data* data - ) { - bp::handle<> handle(bp::borrowed(object)); + static void construct(PyObject* obj, bp::converter::rvalue_from_python_stage1_data* data) { + bp::handle<> handle(bp::borrowed(obj)); - typedef bp::converter::rvalue_from_python_storage storage_type; + typedef bp::converter::rvalue_from_python_storage>> storage_type; void* storage = reinterpret_cast(data)->storage.bytes; - typedef bp::stl_input_iterator iterator; + typedef bp::stl_input_iterator>::value_type> iterator; - new (storage) Container(iterator(bp::object(handle)), iterator()); + new (storage) std::vector>(iterator(bp::object(handle)), iterator()); data->convertible = storage; } + + static void enroll() { + bp::to_python_converter>, std_vector_converter>>(); + bp::converter::registry::push_back( + &std_vector_converter>::convertible, + &std_vector_converter>::construct, + bp::type_id>>() + ); + } }; template @@ -196,18 +234,16 @@ auto auto_enum(const std::string& name) { } BOOST_PYTHON_MODULE(liblincs) { - iterable_converter() - .from_python>() - .from_python>() - .from_python>>() - .from_python>() - .from_python>() - .from_python>() - .from_python>() - .from_python>() - .from_python>() - .from_python>() - ; + std_vector_converter::enroll(); + std_vector_converter::enroll(); + std_vector_converter>::enroll(); + std_vector_converter::enroll(); + std_vector_converter::enroll(); + std_vector_converter::enroll(); + std_vector_converter::enroll(); + std_vector_converter::enroll(); + std_vector_converter::enroll(); + std_vector_converter::enroll(); std_optional_converter::enroll(); std_optional_converter::enroll(); @@ -275,6 +311,7 @@ BOOST_PYTHON_MODULE(liblincs) { .def(bp::init>>()) .def_readwrite("kind", &lincs::SufficientCoalitions::kind) .def_readwrite("criterion_weights", &lincs::SufficientCoalitions::criterion_weights) + .add_property("upset_roots", &lincs::SufficientCoalitions::get_upset_roots) ; sufficient_coalitions_class.attr("Kind") = auto_enum("Kind"); sufficient_coalitions_class.attr("weights") = lincs::SufficientCoalitions::weights; diff --git a/lincs/liblincs_module_tests.py b/lincs/liblincs_module_tests.py index 71b8d23b..d747e906 100644 --- a/lincs/liblincs_module_tests.py +++ b/lincs/liblincs_module_tests.py @@ -130,7 +130,7 @@ def test_init_three_criteria_two_categories_weights_boundary(self): [ Model.Boundary( [5., 5., 5], - SufficientCoalitions(SufficientCoalitions.weights, [0.7, 0.7, 1]) + SufficientCoalitions(SufficientCoalitions.weights, [0.5, 0.25, 1]) ), ], ) @@ -138,7 +138,10 @@ def test_init_three_criteria_two_categories_weights_boundary(self): self.assertEqual(len(model.boundaries[0].profile), 3) self.assertEqual(model.boundaries[0].sufficient_coalitions.kind, SufficientCoalitions.Kind.weights) self.assertEqual(len(model.boundaries[0].sufficient_coalitions.criterion_weights), 3) - # @todo(Feature, when upset_roots are exposed, v1) self.assertEqual(len(model.boundaries[0].sufficient_coalitions.upset_roots), 0) + self.assertEqual(model.boundaries[0].sufficient_coalitions.criterion_weights[0], 0.5) + self.assertEqual(model.boundaries[0].sufficient_coalitions.criterion_weights[1], 0.25) + self.assertEqual(model.boundaries[0].sufficient_coalitions.criterion_weights[2], 1) + self.assertEqual(len(model.boundaries[0].sufficient_coalitions.upset_roots), 0) def test_init_three_criteria_two_categories_roots_boundary(self): problem = Problem( @@ -164,7 +167,12 @@ def test_init_three_criteria_two_categories_roots_boundary(self): self.assertEqual(len(model.boundaries[0].profile), 3) self.assertEqual(model.boundaries[0].sufficient_coalitions.kind, SufficientCoalitions.Kind.roots) self.assertEqual(len(model.boundaries[0].sufficient_coalitions.criterion_weights), 0) - # @todo(Feature, when upset_roots are exposed, v1) self.assertEqual(len(model.boundaries[0].sufficient_coalitions.upset_roots), 2) + self.assertEqual(len(model.boundaries[0].sufficient_coalitions.upset_roots), 2) + self.assertEqual(len(model.boundaries[0].sufficient_coalitions.upset_roots[0]), 2) + self.assertEqual(model.boundaries[0].sufficient_coalitions.upset_roots[0][0], 0) + self.assertEqual(model.boundaries[0].sufficient_coalitions.upset_roots[0][1], 1) + self.assertEqual(model.boundaries[0].sufficient_coalitions.upset_roots[1][0], 0) + self.assertEqual(model.boundaries[0].sufficient_coalitions.upset_roots[1][1], 2) def test_assign_model_attributes(self): problem = Problem([], [])