diff --git a/ext/gudhi-devel b/ext/gudhi-devel index 14262df8..86bf12b2 160000 --- a/ext/gudhi-devel +++ b/ext/gudhi-devel @@ -1 +1 @@ -Subproject commit 14262df87081a89afb32399623691f7703a42ee9 +Subproject commit 86bf12b2260264e7065da2f78502b1c8914e0b99 diff --git a/multipers/_mma_nanobind.cpp b/multipers/_mma_nanobind.cpp index 06bd00e3..ee4fad6c 100644 --- a/multipers/_mma_nanobind.cpp +++ b/multipers/_mma_nanobind.cpp @@ -199,18 +199,11 @@ void bind_module_class(nb::module_& m) { .def_prop_ro("num_parameters", &Module::get_number_of_parameters) .def_prop_rw("box", &Module::get_box_view, - [](Module& self, nb::object box) { - try { - if (nb::ndarray_check(box.ptr())) { - self.set_box(nb::cast(box)); - return; - } - self.set_box(nb::cast>>(box)); - return; - } catch (const nb::cast_error&) { - throw nb::type_error( - "Box has to be set with an array or a nested sequence of shape (2, p)."); - } + [](Module& self, nb::iterable box) { + auto boxCpp = + Gudhi::python::_convert_iterable_to_cpp_type>>( + box, "Box has to be set with an array or a nested sequence of shape (2, p)."); + std::visit([&](auto&& val) { self.set_box(val); }, boxCpp); }) .def("set_box", [](Module& self, NDArray2 box) { diff --git a/multipers/_slicer_nanobind.cpp b/multipers/_slicer_nanobind.cpp index 3718bd93..9dc10404 100644 --- a/multipers/_slicer_nanobind.cpp +++ b/multipers/_slicer_nanobind.cpp @@ -6,12 +6,10 @@ #include #include -#include #include #include #include #include -#include #include #include #include @@ -28,7 +26,6 @@ #include "Persistence_slices_interface.h" #include "ext_interface/backend_log_policy.hpp" #include "ext_interface/nanobind_generator_basis.hpp" -#include "ext_interface/packed_multi_critical_bridge.hpp" #include "ext_interface/nanobind_registry_helpers.hpp" #include "ext_interface/nanobind_registry_runtime.hpp" #include "gudhi/Multi_parameter_filtered_complex.h" @@ -39,10 +36,9 @@ #include #include "multi_parameter_rank_invariant/hilbert_function.h" #include "multi_parameter_rank_invariant/rank_invariant.h" -#include "multiparameter_module_approximation/approximation.h" +#include #include "nanobind_array_utils.hpp" #include "nanobind_dense_array_utils.hpp" -#include "nanobind_mma_registry_helpers.hpp" #include "nanobind_object_utils.hpp" #include "nanobind_slicer_serialization.hpp" @@ -208,9 +204,9 @@ Wrapper& make_filtration_non_decreasing_inplace(Wrapper& self, bool safe) { bool modified = true; while (modified) { modified = false; - for (size_t i = boundaries.size(); i-- > 0;) { + for (size_t i = 1; i < boundaries.size(); ++i) { for (auto b : boundaries[i]) { - modified |= intersect_lifetimes(filtrations[b], filtrations[i]); + modified |= intersect_lifetimes(filtrations[i], filtrations[b]); } } if (ordered) { @@ -1676,7 +1672,7 @@ void bind_slicer_class(nb::module_& m, nb::list& available_slicers) { nb::make_tuple(serialized_state(self), self.filtration_grid, self.generator_basis, - self.minpres_degree)); + self.minpres_degree)); }) .def("__reduce_ex__", [](Wrapper& self, int) -> nb::tuple { @@ -1686,7 +1682,7 @@ void bind_slicer_class(nb::module_& m, nb::list& available_slicers) { nb::make_tuple(serialized_state(self), self.filtration_grid, self.generator_basis, - self.minpres_degree)); + self.minpres_degree)); }) .def("_serialize_state", [](Wrapper& self) -> nb::ndarray { @@ -1842,17 +1838,17 @@ void bind_slicer_class(nb::module_& m, nb::list& available_slicers) { "basepoint"_a, "direction"_a = nb::none(), nb::rv_policy::reference_internal) - .def( - "set_slice", - [](Wrapper& self, nb::object values) -> Wrapper& { - auto c_values = cast_vector(values); - { - nb::gil_scoped_release release; - self.truc.set_slice(c_values); - } - return self; - }, - nb::rv_policy::reference_internal) + // .def( + // "set_slice", + // [](Wrapper& self, nb::object values) -> Wrapper& { + // auto c_values = cast_vector(values); + // { + // nb::gil_scoped_release release; + // self.truc.set_slice(c_values); + // } + // return self; + // }, + // nb::rv_policy::reference_internal) .def( "initialize_persistence_computation", [](Wrapper& self, bool ignore_infinite_filtration_values) -> Wrapper& { @@ -1904,15 +1900,15 @@ void bind_slicer_class(nb::module_& m, nb::list& available_slicers) { } return dim_barcode_to_tuple(barcode); }) - .def("get_current_filtration", - [](Wrapper& self) -> nb::ndarray { - std::vector current; - { - nb::gil_scoped_release release; - current = self.truc.get_slice(); - } - return owned_array(std::move(current), {current.size()}); - }) + // .def("get_current_filtration", + // [](Wrapper& self) -> nb::ndarray { + // std::vector current; + // { + // nb::gil_scoped_release release; + // current = self.truc.get_slice(); + // } + // return owned_array(std::move(current), {current.size()}); + // }) .def( "prune_above_dimension", [](Wrapper& self, int max_dimension) -> Wrapper& { @@ -2093,8 +2089,15 @@ Gudhi::multi_persistence::Module_interface module_approximation_from_des Gudhi::multi_persistence::Module mod; { nb::gil_scoped_release release; - mod = Gudhi::multiparameter::mma::multiparameter_module_approximation( - wrapper.truc, direction, max_error, box, threshold, complete, verbose, n_jobs); + mod = Gudhi::multi_persistence::multiparameter_module_approximation(wrapper.truc, + max_error, + box.get_lower_corner(), + box.get_upper_corner(), + direction, + threshold, + complete, + verbose, + n_jobs); } return {std::move(mod), box}; } diff --git a/multipers/gudhi/Module_interface.h b/multipers/gudhi/Module_interface.h index 85a1d02e..cf49b050 100644 --- a/multipers/gudhi/Module_interface.h +++ b/multipers/gudhi/Module_interface.h @@ -26,6 +26,7 @@ #include #include +#include // missing in boost/range/any_range.hpp #include #include @@ -43,6 +44,7 @@ #include #include #include +#include namespace Gudhi { namespace multi_persistence { @@ -61,7 +63,7 @@ class Module_interface { using Summand_of_dimension_range = boost::any_range; using Tensor1D = nanobind::ndarray, nanobind::any_contig>; - using Tensor2D = nanobind::ndarray>; + using Tensor2D = nanobind::ndarray >; template using IntTensor1D = nanobind::ndarray, nanobind::any_contig>; using Box_t = std::vector; @@ -160,7 +162,7 @@ class Module_interface { return *this; } - [[nodiscard]] nanobind::list get_flat_filtration_values(bool unique) const { + [[nodiscard]] nanobind::tuple get_flat_filtration_values(bool unique) const { int numParam = module_.get_number_of_parameters(); if (numParam == get_null_value()) return {}; // empty module @@ -193,15 +195,14 @@ class Module_interface { } } - nanobind::list out; - for (auto &vals : values) { + return Gudhi::python::_build_tuple(numParam, [&](std::size_t p) { + auto &vals = values[p]; if (unique) { std::sort(vals.begin(), vals.end()); vals.erase(std::unique(vals.begin(), vals.end()), vals.end()); } - out.append(_wrap_as_numpy_array(std::move(vals), vals.size())); - } - return out; + return _wrap_as_numpy_array(std::move(vals), vals.size()); + }); } auto get_all_dimension() const { @@ -260,9 +261,8 @@ class Module_interface { return _wrap_as_numpy_array(std::move(corners), 2, dim); } - nanobind::list get_barcode_from_line(Tensor1D basepoint, std::optional direction, int degree) const { + nanobind::tuple get_barcode_from_line(Tensor1D basepoint, std::optional direction, int degree) const { std::vector>> barcode; - nanobind::list out; { nanobind::gil_scoped_release release; Dimension dim = degree < 0 ? get_null_value() : static_cast(degree); @@ -276,16 +276,15 @@ class Module_interface { } barcode = module_.get_barcode_from_line(line, dim); } - for (auto &d : barcode) { - out.append(_wrap_as_numpy_array(std::move(d))); - } - return out; + + return Gudhi::python::_build_tuple(barcode.size(), + [&](std::size_t d) { return _wrap_as_numpy_array(std::move(barcode[d])); }); } - nanobind::list get_barcode_from_lines(Tensor2D basepoints, - std::optional directions, - int degree, - bool keep_inf) const { + nanobind::tuple get_barcode_from_lines(Tensor2D basepoints, + std::optional directions, + int degree, + bool keep_inf) const { std::vector>> barcode; std::size_t numberOfLines; { @@ -633,55 +632,49 @@ class Module_interface { return fb; } - static nanobind::list get_numpy_barcode_from_lines_with_inf(std::vector>> &barcode, - std::size_t numberOfLines) { - nanobind::list out; + static nanobind::tuple get_numpy_barcode_from_lines_with_inf(std::vector>> &barcode, + std::size_t numberOfLines) { std::vector splits; if (numberOfLines == 0) { - for (auto &d : barcode) { - if (d.size() != 0) throw std::logic_error("No lines but the barcode is not empty... ?"); - out.append(nanobind::make_tuple(_wrap_as_numpy_array(std::move(d), 0, 0, 2), - _wrap_as_numpy_array(std::move(splits), 0))); - } - return out; + return Gudhi::python::_build_tuple(barcode.size(), [&](std::size_t d) { + if (barcode[d].size() != 0) throw std::logic_error("No lines but the barcode is not empty... ?"); + return nanobind::make_tuple(_wrap_as_numpy_array(std::move(barcode[d]), 0, 0, 2), + _wrap_as_numpy_array(std::move(splits), 0)); + }); } - for (auto &d : barcode) { - if (d.size() % numberOfLines != 0) + return Gudhi::python::_build_tuple(barcode.size(), [&](std::size_t d) { + if (barcode[d].size() % numberOfLines != 0) throw std::logic_error("Barcodes do not have consistent sizes from a line to another."); - std::size_t numberOfBars = d.size() / numberOfLines; - out.append(nanobind::make_tuple(_wrap_as_numpy_array(std::move(d), numberOfLines, numberOfBars, 2), - _wrap_as_numpy_array(std::move(splits), 0))); - } - return out; + std::size_t numberOfBars = barcode[d].size() / numberOfLines; + return nanobind::make_tuple(_wrap_as_numpy_array(std::move(barcode[d]), numberOfLines, numberOfBars, 2), + _wrap_as_numpy_array(std::move(splits), 0)); + }); } - static nanobind::list get_numpy_barcode_from_lines_without_inf( + static nanobind::tuple get_numpy_barcode_from_lines_without_inf( std::vector>> &barcode, std::size_t numberOfLines) { - nanobind::list out; - if (numberOfLines == 0) { - for (auto &d : barcode) { - if (d.size() != 0) throw std::logic_error("No lines but the barcode is not empty... ?"); + return Gudhi::python::_build_tuple(barcode.size(), [&](std::size_t d) { + if (barcode[d].size() != 0) throw std::logic_error("No lines but the barcode is not empty... ?"); std::vector splits; - out.append( - nanobind::make_tuple(_wrap_as_numpy_array(std::move(d), 0, 2), _wrap_as_numpy_array(std::move(splits), 0))); - } - return out; + return nanobind::make_tuple(_wrap_as_numpy_array(std::move(barcode[d]), 0, 2), + _wrap_as_numpy_array(std::move(splits), 0)); + }); } - for (auto &d : barcode) { - if (d.size() % numberOfLines != 0) + return Gudhi::python::_build_tuple(barcode.size(), [&](std::size_t d) { + if (barcode[d].size() % numberOfLines != 0) throw std::logic_error("Barcodes do not have consistent sizes from a line to another."); std::vector splits(numberOfLines - 1); std::size_t barCount = 0; - std::size_t numberOfBars = d.size() / numberOfLines; + std::size_t numberOfBars = barcode[d].size() / numberOfLines; std::vector> bars; for (std::size_t l = 0; l < numberOfLines; ++l) { for (std::size_t i = 0; i < numberOfBars; ++i) { - auto &b = d[i + (l * numberOfBars)]; + auto &b = barcode[d][i + (l * numberOfBars)]; if (b[0] != Module::T_inf) { bars.push_back(b); ++barCount; @@ -689,15 +682,14 @@ class Module_interface { } if (l + 1 < numberOfLines) splits[l] = barCount; } - out.append(nanobind::make_tuple(_wrap_as_numpy_array(std::move(bars), barCount, 2), - _wrap_as_numpy_array(std::move(splits), numberOfLines - 1))); - } - return out; + return nanobind::make_tuple(_wrap_as_numpy_array(std::move(bars), barCount, 2), + _wrap_as_numpy_array(std::move(splits), numberOfLines - 1)); + }); } - static nanobind::list get_numpy_barcode_from_lines(std::vector>> &barcode, - std::size_t numberOfLines, - bool keepInf) { + static nanobind::tuple get_numpy_barcode_from_lines(std::vector>> &barcode, + std::size_t numberOfLines, + bool keepInf) { if (keepInf) return get_numpy_barcode_from_lines_with_inf(barcode, numberOfLines); return get_numpy_barcode_from_lines_without_inf(barcode, numberOfLines); } diff --git a/pyproject.toml b/pyproject.toml index 86953ba5..6bf21ea2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -310,6 +310,7 @@ include = [ "ext/gudhi-devel/src/Multi_persistence/include/gudhi/Thread_safe_slicer.h", "ext/gudhi-devel/src/Multi_persistence/include/gudhi/multi_simplex_tree_helpers.h", "ext/gudhi-devel/src/Multi_persistence/include/gudhi/slicer_helpers.h", + "ext/gudhi-devel/src/Multi_persistence/include/gudhi/multiparameter_module_approximation.h", "ext/gudhi-devel/src/Persistence_matrix/include/gudhi/Fields/Z2_field_operators.h", "ext/gudhi-devel/src/Persistence_matrix/include/gudhi/Fields/Zp_field_operators.h", "ext/gudhi-devel/src/Persistence_matrix/include/gudhi/Matrix.h", @@ -370,6 +371,7 @@ include = [ "ext/gudhi-devel/src/common/include/gudhi/reader_utils.h", "ext/gudhi-devel/src/common/include/gudhi/simple_mdspan.h", "ext/gudhi-devel/src/python/include/python_interfaces/numpy_utils.h", + "ext/gudhi-devel/src/python/include/python_interfaces/construction_utils.h", "ext/hera/extern/phat/algorithms/standard_reduction.h", "ext/hera/extern/phat/algorithms/twist_reduction.h", "ext/hera/extern/phat/boundary_matrix.h", diff --git a/tests/test_slicer.py b/tests/test_slicer.py index c67c577f..40c2154a 100644 --- a/tests/test_slicer.py +++ b/tests/test_slicer.py @@ -96,16 +96,24 @@ def test_3(): def test_make_filtration_non_decreasing_propagates_transitively(): slicer_type = mp.Slicer(return_type_only=True, dtype=np.float64) slicer = slicer_type( - [[], [], [0, 1], [2]], - np.asarray([0, 0, 1, 2], dtype=np.int32), - np.asarray([[0, 0], [0, 0], [1, 1], [5, 5]], dtype=np.float64), + [[], [0, 2], [], [], [5], [1, 6], [2, 3]], + np.asarray([0, 1, 0, 0, 3, 2, 2], dtype=np.int32), + np.asarray( + [[0, 2, 1], [0, 2, 0], [0, 1, 2], [3, 4, 5], [6, 4, 2], [3, 4, 3], [1, 2, 4]], + dtype=np.float64, + ), ) slicer.make_filtration_non_decreasing() got = np.asarray(slicer.get_filtrations(), dtype=np.float64) - expected = np.asarray([[5, 5], [5, 5], [5, 5], [5, 5]], dtype=np.float64) + expected = np.asarray( + [[0, 2, 1], [0, 2, 2], [0, 1, 2], [3, 4, 5], [6, 4, 5], [3, 4, 5], [3, 4, 5]], + dtype=np.float64, + ) assert np.array_equal(got, expected) + + def test_rank_custom(): B = [[], [0], [0], [0], [0]] F = [[0, 0], [2, 1], [1, 2], [3, 0], [0, 3]]