multipers 2.4.0b1__cp312-cp312-macosx_11_0_arm64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (184) hide show
  1. multipers/.dylibs/libboost_timer.dylib +0 -0
  2. multipers/.dylibs/libc++.1.0.dylib +0 -0
  3. multipers/.dylibs/libtbb.12.17.dylib +0 -0
  4. multipers/__init__.py +33 -0
  5. multipers/_signed_measure_meta.py +426 -0
  6. multipers/_slicer_meta.py +231 -0
  7. multipers/array_api/__init__.py +62 -0
  8. multipers/array_api/numpy.py +124 -0
  9. multipers/array_api/torch.py +133 -0
  10. multipers/data/MOL2.py +458 -0
  11. multipers/data/UCR.py +18 -0
  12. multipers/data/__init__.py +1 -0
  13. multipers/data/graphs.py +466 -0
  14. multipers/data/immuno_regions.py +27 -0
  15. multipers/data/minimal_presentation_to_st_bf.py +0 -0
  16. multipers/data/pytorch2simplextree.py +91 -0
  17. multipers/data/shape3d.py +101 -0
  18. multipers/data/synthetic.py +113 -0
  19. multipers/distances.py +202 -0
  20. multipers/filtration_conversions.pxd +736 -0
  21. multipers/filtration_conversions.pxd.tp +226 -0
  22. multipers/filtrations/__init__.py +21 -0
  23. multipers/filtrations/density.py +529 -0
  24. multipers/filtrations/filtrations.py +480 -0
  25. multipers/filtrations.pxd +534 -0
  26. multipers/filtrations.pxd.tp +332 -0
  27. multipers/function_rips.cpython-312-darwin.so +0 -0
  28. multipers/function_rips.pyx +104 -0
  29. multipers/grids.cpython-312-darwin.so +0 -0
  30. multipers/grids.pyx +538 -0
  31. multipers/gudhi/Persistence_slices_interface.h +213 -0
  32. multipers/gudhi/Simplex_tree_interface.h +274 -0
  33. multipers/gudhi/Simplex_tree_multi_interface.h +648 -0
  34. multipers/gudhi/gudhi/Bitmap_cubical_complex.h +450 -0
  35. multipers/gudhi/gudhi/Bitmap_cubical_complex_base.h +1070 -0
  36. multipers/gudhi/gudhi/Bitmap_cubical_complex_periodic_boundary_conditions_base.h +579 -0
  37. multipers/gudhi/gudhi/Debug_utils.h +52 -0
  38. multipers/gudhi/gudhi/Degree_rips_bifiltration.h +2307 -0
  39. multipers/gudhi/gudhi/Dynamic_multi_parameter_filtration.h +2524 -0
  40. multipers/gudhi/gudhi/Fields/Multi_field.h +453 -0
  41. multipers/gudhi/gudhi/Fields/Multi_field_operators.h +460 -0
  42. multipers/gudhi/gudhi/Fields/Multi_field_shared.h +444 -0
  43. multipers/gudhi/gudhi/Fields/Multi_field_small.h +584 -0
  44. multipers/gudhi/gudhi/Fields/Multi_field_small_operators.h +490 -0
  45. multipers/gudhi/gudhi/Fields/Multi_field_small_shared.h +580 -0
  46. multipers/gudhi/gudhi/Fields/Z2_field.h +391 -0
  47. multipers/gudhi/gudhi/Fields/Z2_field_operators.h +389 -0
  48. multipers/gudhi/gudhi/Fields/Zp_field.h +493 -0
  49. multipers/gudhi/gudhi/Fields/Zp_field_operators.h +384 -0
  50. multipers/gudhi/gudhi/Fields/Zp_field_shared.h +492 -0
  51. multipers/gudhi/gudhi/Flag_complex_edge_collapser.h +337 -0
  52. multipers/gudhi/gudhi/Matrix.h +2200 -0
  53. multipers/gudhi/gudhi/Multi_filtration/Multi_parameter_generator.h +1712 -0
  54. multipers/gudhi/gudhi/Multi_filtration/multi_filtration_conversions.h +237 -0
  55. multipers/gudhi/gudhi/Multi_filtration/multi_filtration_utils.h +225 -0
  56. multipers/gudhi/gudhi/Multi_parameter_filtered_complex.h +485 -0
  57. multipers/gudhi/gudhi/Multi_parameter_filtration.h +2643 -0
  58. multipers/gudhi/gudhi/Multi_persistence/Box.h +233 -0
  59. multipers/gudhi/gudhi/Multi_persistence/Line.h +309 -0
  60. multipers/gudhi/gudhi/Multi_persistence/Multi_parameter_filtered_complex_pcoh_interface.h +268 -0
  61. multipers/gudhi/gudhi/Multi_persistence/Persistence_interface_cohomology.h +159 -0
  62. multipers/gudhi/gudhi/Multi_persistence/Persistence_interface_matrix.h +463 -0
  63. multipers/gudhi/gudhi/Multi_persistence/Point.h +853 -0
  64. multipers/gudhi/gudhi/Off_reader.h +173 -0
  65. multipers/gudhi/gudhi/Persistence_matrix/Base_matrix.h +834 -0
  66. multipers/gudhi/gudhi/Persistence_matrix/Base_matrix_with_column_compression.h +838 -0
  67. multipers/gudhi/gudhi/Persistence_matrix/Boundary_matrix.h +833 -0
  68. multipers/gudhi/gudhi/Persistence_matrix/Chain_matrix.h +1367 -0
  69. multipers/gudhi/gudhi/Persistence_matrix/Id_to_index_overlay.h +1157 -0
  70. multipers/gudhi/gudhi/Persistence_matrix/Position_to_index_overlay.h +869 -0
  71. multipers/gudhi/gudhi/Persistence_matrix/RU_matrix.h +905 -0
  72. multipers/gudhi/gudhi/Persistence_matrix/allocators/entry_constructors.h +122 -0
  73. multipers/gudhi/gudhi/Persistence_matrix/base_pairing.h +260 -0
  74. multipers/gudhi/gudhi/Persistence_matrix/base_swap.h +288 -0
  75. multipers/gudhi/gudhi/Persistence_matrix/chain_pairing.h +170 -0
  76. multipers/gudhi/gudhi/Persistence_matrix/chain_rep_cycles.h +247 -0
  77. multipers/gudhi/gudhi/Persistence_matrix/chain_vine_swap.h +571 -0
  78. multipers/gudhi/gudhi/Persistence_matrix/columns/chain_column_extra_properties.h +182 -0
  79. multipers/gudhi/gudhi/Persistence_matrix/columns/column_dimension_holder.h +130 -0
  80. multipers/gudhi/gudhi/Persistence_matrix/columns/column_utilities.h +235 -0
  81. multipers/gudhi/gudhi/Persistence_matrix/columns/entry_types.h +312 -0
  82. multipers/gudhi/gudhi/Persistence_matrix/columns/heap_column.h +1092 -0
  83. multipers/gudhi/gudhi/Persistence_matrix/columns/intrusive_list_column.h +923 -0
  84. multipers/gudhi/gudhi/Persistence_matrix/columns/intrusive_set_column.h +914 -0
  85. multipers/gudhi/gudhi/Persistence_matrix/columns/list_column.h +930 -0
  86. multipers/gudhi/gudhi/Persistence_matrix/columns/naive_vector_column.h +1071 -0
  87. multipers/gudhi/gudhi/Persistence_matrix/columns/row_access.h +203 -0
  88. multipers/gudhi/gudhi/Persistence_matrix/columns/set_column.h +886 -0
  89. multipers/gudhi/gudhi/Persistence_matrix/columns/unordered_set_column.h +984 -0
  90. multipers/gudhi/gudhi/Persistence_matrix/columns/vector_column.h +1213 -0
  91. multipers/gudhi/gudhi/Persistence_matrix/index_mapper.h +58 -0
  92. multipers/gudhi/gudhi/Persistence_matrix/matrix_dimension_holders.h +227 -0
  93. multipers/gudhi/gudhi/Persistence_matrix/matrix_row_access.h +200 -0
  94. multipers/gudhi/gudhi/Persistence_matrix/ru_pairing.h +166 -0
  95. multipers/gudhi/gudhi/Persistence_matrix/ru_rep_cycles.h +319 -0
  96. multipers/gudhi/gudhi/Persistence_matrix/ru_vine_swap.h +562 -0
  97. multipers/gudhi/gudhi/Persistence_on_a_line.h +152 -0
  98. multipers/gudhi/gudhi/Persistence_on_rectangle.h +617 -0
  99. multipers/gudhi/gudhi/Persistent_cohomology/Field_Zp.h +118 -0
  100. multipers/gudhi/gudhi/Persistent_cohomology/Multi_field.h +173 -0
  101. multipers/gudhi/gudhi/Persistent_cohomology/Persistent_cohomology_column.h +128 -0
  102. multipers/gudhi/gudhi/Persistent_cohomology.h +769 -0
  103. multipers/gudhi/gudhi/Points_off_io.h +171 -0
  104. multipers/gudhi/gudhi/Projective_cover_kernel.h +379 -0
  105. multipers/gudhi/gudhi/Simple_object_pool.h +69 -0
  106. multipers/gudhi/gudhi/Simplex_tree/Simplex_tree_iterators.h +559 -0
  107. multipers/gudhi/gudhi/Simplex_tree/Simplex_tree_node_explicit_storage.h +83 -0
  108. multipers/gudhi/gudhi/Simplex_tree/Simplex_tree_siblings.h +121 -0
  109. multipers/gudhi/gudhi/Simplex_tree/Simplex_tree_star_simplex_iterators.h +277 -0
  110. multipers/gudhi/gudhi/Simplex_tree/filtration_value_utils.h +155 -0
  111. multipers/gudhi/gudhi/Simplex_tree/hooks_simplex_base.h +62 -0
  112. multipers/gudhi/gudhi/Simplex_tree/indexing_tag.h +27 -0
  113. multipers/gudhi/gudhi/Simplex_tree/serialization_utils.h +60 -0
  114. multipers/gudhi/gudhi/Simplex_tree/simplex_tree_options.h +105 -0
  115. multipers/gudhi/gudhi/Simplex_tree.h +3170 -0
  116. multipers/gudhi/gudhi/Slicer.h +848 -0
  117. multipers/gudhi/gudhi/Thread_safe_slicer.h +393 -0
  118. multipers/gudhi/gudhi/distance_functions.h +62 -0
  119. multipers/gudhi/gudhi/graph_simplicial_complex.h +104 -0
  120. multipers/gudhi/gudhi/multi_simplex_tree_helpers.h +147 -0
  121. multipers/gudhi/gudhi/persistence_interval.h +263 -0
  122. multipers/gudhi/gudhi/persistence_matrix_options.h +188 -0
  123. multipers/gudhi/gudhi/reader_utils.h +367 -0
  124. multipers/gudhi/gudhi/simple_mdspan.h +484 -0
  125. multipers/gudhi/gudhi/slicer_helpers.h +779 -0
  126. multipers/gudhi/tmp_h0_pers/mma_interface_h0.h +223 -0
  127. multipers/gudhi/tmp_h0_pers/naive_merge_tree.h +536 -0
  128. multipers/io.cpython-312-darwin.so +0 -0
  129. multipers/io.pyx +472 -0
  130. multipers/ml/__init__.py +0 -0
  131. multipers/ml/accuracies.py +90 -0
  132. multipers/ml/invariants_with_persistable.py +79 -0
  133. multipers/ml/kernels.py +176 -0
  134. multipers/ml/mma.py +713 -0
  135. multipers/ml/one.py +472 -0
  136. multipers/ml/point_clouds.py +352 -0
  137. multipers/ml/signed_measures.py +1667 -0
  138. multipers/ml/sliced_wasserstein.py +461 -0
  139. multipers/ml/tools.py +113 -0
  140. multipers/mma_structures.cpython-312-darwin.so +0 -0
  141. multipers/mma_structures.pxd +134 -0
  142. multipers/mma_structures.pyx +1483 -0
  143. multipers/mma_structures.pyx.tp +1126 -0
  144. multipers/multi_parameter_rank_invariant/diff_helpers.h +85 -0
  145. multipers/multi_parameter_rank_invariant/euler_characteristic.h +95 -0
  146. multipers/multi_parameter_rank_invariant/function_rips.h +317 -0
  147. multipers/multi_parameter_rank_invariant/hilbert_function.h +761 -0
  148. multipers/multi_parameter_rank_invariant/persistence_slices.h +149 -0
  149. multipers/multi_parameter_rank_invariant/rank_invariant.h +350 -0
  150. multipers/multiparameter_edge_collapse.py +41 -0
  151. multipers/multiparameter_module_approximation/approximation.h +2541 -0
  152. multipers/multiparameter_module_approximation/debug.h +107 -0
  153. multipers/multiparameter_module_approximation/format_python-cpp.h +292 -0
  154. multipers/multiparameter_module_approximation/utilities.h +428 -0
  155. multipers/multiparameter_module_approximation.cpython-312-darwin.so +0 -0
  156. multipers/multiparameter_module_approximation.pyx +286 -0
  157. multipers/ops.cpython-312-darwin.so +0 -0
  158. multipers/ops.pyx +231 -0
  159. multipers/pickle.py +89 -0
  160. multipers/plots.py +550 -0
  161. multipers/point_measure.cpython-312-darwin.so +0 -0
  162. multipers/point_measure.pyx +409 -0
  163. multipers/simplex_tree_multi.cpython-312-darwin.so +0 -0
  164. multipers/simplex_tree_multi.pxd +136 -0
  165. multipers/simplex_tree_multi.pyx +11719 -0
  166. multipers/simplex_tree_multi.pyx.tp +2102 -0
  167. multipers/slicer.cpython-312-darwin.so +0 -0
  168. multipers/slicer.pxd +2097 -0
  169. multipers/slicer.pxd.tp +263 -0
  170. multipers/slicer.pyx +13042 -0
  171. multipers/slicer.pyx.tp +1259 -0
  172. multipers/tensor/tensor.h +672 -0
  173. multipers/tensor.pxd +13 -0
  174. multipers/test.pyx +44 -0
  175. multipers/tests/__init__.py +70 -0
  176. multipers/torch/__init__.py +1 -0
  177. multipers/torch/diff_grids.py +240 -0
  178. multipers/torch/rips_density.py +310 -0
  179. multipers/vector_interface.pxd +46 -0
  180. multipers-2.4.0b1.dist-info/METADATA +131 -0
  181. multipers-2.4.0b1.dist-info/RECORD +184 -0
  182. multipers-2.4.0b1.dist-info/WHEEL +6 -0
  183. multipers-2.4.0b1.dist-info/licenses/LICENSE +21 -0
  184. multipers-2.4.0b1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,2643 @@
1
+ /* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT.
2
+ * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details.
3
+ * Author(s): Hannah Schreiber, David Loiseaux
4
+ *
5
+ * Copyright (C) 2024-25 Inria
6
+ *
7
+ * Modification(s):
8
+ * - YYYY/MM Author: Description of the modification
9
+ */
10
+
11
+ /**
12
+ * @file Multi_parameter_filtration.h
13
+ * @author Hannah Schreiber, David Loiseaux
14
+ * @brief Contains the @ref Gudhi::multi_filtration::Multi_parameter_filtration class.
15
+ */
16
+
17
+ #ifndef MF_MULTI_PARAMETER_FILTRATION_H_
18
+ #define MF_MULTI_PARAMETER_FILTRATION_H_
19
+
20
+ #include <algorithm> //std::lower_bound
21
+ #include <cmath> //std::isnan, std::min, std::abs
22
+ #include <cstddef> //std::size_t
23
+ #include <cstdint> //std::int32_t, std::uint8_t
24
+ #include <cstring> //memcpy
25
+ #include <iterator> //std::distance
26
+ #include <ostream> //std::ostream
27
+ #include <limits> //std::numerical_limits
28
+ #include <stdexcept> //std::logic_error
29
+ #include <type_traits> //std::is_arithmetic
30
+ #include <utility> //std::swap, std::move
31
+ #include <numeric> //std::iota
32
+ #include <vector>
33
+ #include <initializer_list>
34
+
35
+ #include <gudhi/Debug_utils.h>
36
+ #include <gudhi/simple_mdspan.h>
37
+ #include <gudhi/Multi_filtration/multi_filtration_utils.h>
38
+ #include <oneapi/tbb/parallel_for.h>
39
+
40
+ namespace Gudhi::multi_filtration {
41
+
42
+ // declaration needed pre C++20 for friends with templates defined inside a class
43
+ template <typename U>
44
+ U compute_linear_projection();
45
+ template <typename U>
46
+ U compute_euclidean_distance_to();
47
+ template <typename U>
48
+ U compute_norm();
49
+ template <typename OutValue, typename U>
50
+ void compute_coordinates_in_grid();
51
+ template <typename U>
52
+ void evaluate_coordinates_in_grid();
53
+ template <bool inverse>
54
+ bool is_strict_less_than_lexicographically();
55
+ template <bool inverse>
56
+ bool is_less_or_equal_than_lexicographically();
57
+
58
+ /**
59
+ * @class Multi_parameter_filtration Multi_parameter_filtration.h gudhi/Multi_parameter_filtration.h
60
+ * @ingroup multi_filtration
61
+ *
62
+ * @brief Class encoding the different generators, i.e., apparition times, of a \f$ k \f$-critical
63
+ * \f$\mathbb R^n\f$-filtration value. E.g., the filtration value of a simplex, or, of the algebraic generator of a
64
+ * module presentation. The encoding is compacted into a single vector, so if a lot of non trivial modifications are
65
+ * done (that not only consists of simply adding new generators at the end of the vector), it is probably preferable
66
+ * to use @ref Dynamic_multi_parameter_filtration instead. Implements the concept @ref FiltrationValue of the
67
+ * @ref Gudhi::Simplex_tree and the concept @ref Gudhi::multi_persistence::MultiFiltrationValue.
68
+ *
69
+ * @details Overloads `std::numeric_limits` such that:
70
+ * - `std::numeric_limits<Multi_parameter_filtration>::has_infinity` returns `true`,
71
+ * - `std::numeric_limits<Multi_parameter_filtration>::has_quiet_NaN` returns `std::numeric_limits<T>::has_quiet_NaN`,
72
+ * - `std::numeric_limits<Multi_parameter_filtration>::infinity(int)` returns
73
+ * @ref Multi_parameter_filtration::inf(int) "",
74
+ * - `std::numeric_limits<Multi_parameter_filtration>::minus_infinity(int)` returns
75
+ * @ref Multi_parameter_filtration::minus_inf(int) "",
76
+ * - `std::numeric_limits<Multi_parameter_filtration>::max(int num_param)` returns a @ref Multi_parameter_filtration
77
+ * with one generator of `num_param` parameters evaluated at value `std::numeric_limits<T>::max()`,
78
+ * - `std::numeric_limits<Multi_parameter_filtration>::quiet_NaN(int)` returns
79
+ * @ref Multi_parameter_filtration::nan(int) if `std::numeric_limits<Multi_parameter_filtration>::has_quiet_NaN`
80
+ * and throws otherwise.
81
+ *
82
+ * Multi-critical filtrations are filtrations such that the lifetime of each object is union of positive cones in
83
+ * \f$\mathbb R^n\f$, e.g.,
84
+ * - \f$ \{ x \in \mathbb R^2 : x \ge (1,2)\} \cap \{ x \in \mathbb R^2 : x \ge (2,1)\} \f$ is finitely critical,
85
+ * and more particularly 2-critical, while
86
+ * - \f$ \{ x \in \mathbb R^2 : x \ge \mathrm{epigraph}(y \mapsto e^{-y})\} \f$ is not.
87
+ *
88
+ * @tparam T Arithmetic type of an entry for one parameter of a filtration value. Has to be **signed** and
89
+ * to implement `std::isnan(T)`, `std::numeric_limits<T>::has_quiet_NaN`, `std::numeric_limits<T>::quiet_NaN()`,
90
+ * `std::numeric_limits<T>::has_infinity`, `std::numeric_limits<T>::infinity()` and `std::numeric_limits<T>::max()`.
91
+ * If `std::numeric_limits<T>::has_infinity` returns `false`, a call to `std::numeric_limits<T>::infinity()`
92
+ * can simply throw. Examples are the native types `double`, `float` and `int`.
93
+ * @tparam Co If `true`, reverses the poset order, i.e., the order \f$ \le \f$ in \f$ \mathbb R^n \f$ becomes
94
+ * \f$ \ge \f$. That is, the positive cones representing a lifetime become all negative instead.
95
+ * @tparam Ensure1Criticality If `true`, the methods ensure that the filtration value is always 1-critical by throwing
96
+ * or refusing to compile if a modification increases the number of generators.
97
+ */
98
+ template <typename T, bool Co = false, bool Ensure1Criticality = false>
99
+ class Multi_parameter_filtration
100
+ {
101
+ private:
102
+ using view_extents = extents<std::size_t, Gudhi::dynamic_extent, Gudhi::dynamic_extent>;
103
+ using Viewer = Gudhi::Simple_mdspan<T, view_extents>;
104
+
105
+ public:
106
+ using Underlying_container = std::vector<T>; /**< Underlying container for values. */
107
+
108
+ // CONSTRUCTORS
109
+
110
+ /**
111
+ * @brief Default constructor. Builds filtration value with one generator and given number of parameters.
112
+ * If Co is false, all values are at -inf, if Co is true, all values are at +inf.
113
+ *
114
+ * @param number_of_parameters If negative, takes the default value instead. Default value: 2.
115
+ */
116
+ Multi_parameter_filtration(int number_of_parameters = 2)
117
+ : generators_(number_of_parameters < 0 ? 2 : number_of_parameters, _get_default_value()),
118
+ generator_view_(generators_.data(), generators_.empty() ? 0 : 1, generators_.size())
119
+ {}
120
+
121
+ /**
122
+ * @brief Builds filtration value with one generator and given number of parameters.
123
+ * All values are initialized at the given value.
124
+ *
125
+ * @param number_of_parameters If negative, is set to 2 instead.
126
+ * @param value Initialization value for every value in the generator.
127
+ */
128
+ Multi_parameter_filtration(int number_of_parameters, T value)
129
+ : generators_(number_of_parameters < 0 ? 2 : number_of_parameters, value),
130
+ generator_view_(generators_.data(), generators_.empty() ? 0 : 1, generators_.size())
131
+ {}
132
+
133
+ /**
134
+ * @brief Builds filtration value with one generator that is initialized with the given range. The number of
135
+ * parameters are therefore deduced from the length of the range.
136
+ *
137
+ * @tparam ValueRange Range of types convertible to `T`. Should have a begin() and end() method.
138
+ * @param range Values of the generator.
139
+ */
140
+ template <class ValueRange = std::initializer_list<T>, class = std::enable_if_t<RangeTraits<ValueRange>::has_begin> >
141
+ Multi_parameter_filtration(const ValueRange &range)
142
+ : generators_(range.begin(), range.end()),
143
+ generator_view_(generators_.data(), generators_.empty() ? 0 : 1, generators_.size())
144
+ {}
145
+
146
+ /**
147
+ * @brief Builds filtration value with one generator that is initialized with the given range. The range is
148
+ * determined from the two given iterators. The number of parameters are therefore deduced from the distance
149
+ * between the two.
150
+ *
151
+ * @tparam Iterator Iterator type that has to satisfy the requirements of standard LegacyInputIterator and
152
+ * dereferenced elements have to be convertible to `T`.
153
+ * @param it_begin Iterator pointing to the start of the range.
154
+ * @param it_end Iterator pointing to the end of the range.
155
+ */
156
+ template <class Iterator>
157
+ Multi_parameter_filtration(Iterator it_begin, Iterator it_end)
158
+ : generators_(it_begin, it_end),
159
+ generator_view_(generators_.data(), generators_.empty() ? 0 : 1, generators_.size())
160
+ {}
161
+
162
+ /**
163
+ * @brief Builds filtration value with given number of parameters and values from the given range. Lets \f$ p \f$
164
+ * be the number of parameters. The \f$ p \f$ first elements of the range have to correspond to the first generator,
165
+ * the \f$ p \f$ next elements to the second generator and so on... So the length of the range has to be a multiple
166
+ * of \f$ p \f$ and the number of generators will be \f$ length / p \f$. The range is represented by two iterators.
167
+ *
168
+ * @tparam Iterator Iterator type that has to satisfy the requirements of standard LegacyInputIterator and
169
+ * dereferenced elements have to be convertible to `T`.
170
+ * @param it_begin Iterator pointing to the start of the range.
171
+ * @param it_end Iterator pointing to the end of the range.
172
+ * @param number_of_parameters Negative values are associated to 0.
173
+ */
174
+ template <class Iterator, class = std::enable_if_t<!std::is_arithmetic_v<Iterator> > >
175
+ Multi_parameter_filtration(Iterator it_begin, Iterator it_end, int number_of_parameters)
176
+ : generators_(it_begin, it_end),
177
+ generator_view_(generators_.data(),
178
+ number_of_parameters <= 0 ? 0 : generators_.size() / number_of_parameters,
179
+ number_of_parameters)
180
+ {
181
+ if constexpr (Ensure1Criticality) {
182
+ if (generator_view_.extent(0) != 1) throw std::logic_error("Multiparameter filtration value is not 1-critical.");
183
+ }
184
+ }
185
+
186
+ /**
187
+ * @brief Builds filtration value with given number of parameters and values from the given range. Lets \f$ p \f$
188
+ * be the number of parameters. The \f$ p \f$ first elements of the range have to correspond to the first generator,
189
+ * the \f$ p \f$ next elements to the second generator and so on... So the length of the range has to be a multiple
190
+ * of \f$ p \f$ and the number of generators will be \f$ length / p \f$. The range is represented by
191
+ * @ref Multi_parameter_filtration::Underlying_container "" and copied into the underlying container of the class.
192
+ *
193
+ * @param generators Values.
194
+ * @param number_of_parameters Negative values are associated to 0.
195
+ */
196
+ Multi_parameter_filtration(const Underlying_container &generators, int number_of_parameters)
197
+ : generators_(generators),
198
+ generator_view_(generators_.data(),
199
+ number_of_parameters <= 0 ? 0 : generators_.size() / number_of_parameters,
200
+ number_of_parameters)
201
+ {
202
+ GUDHI_CHECK(number_of_parameters > 0 || generators_.empty(),
203
+ std::invalid_argument("Number of parameters cannot be 0 if the container is not empty."));
204
+
205
+ if constexpr (Ensure1Criticality) {
206
+ if (generator_view_.extent(0) != 1) throw std::logic_error("Multiparameter filtration value is not 1-critical.");
207
+ }
208
+ }
209
+
210
+ /**
211
+ * @brief Builds filtration value with given number of parameters and values from the given range. Lets \f$ p \f$
212
+ * be the number of parameters. The \f$ p \f$ first elements of the range have to correspond to the first generator,
213
+ * the \f$ p \f$ next elements to the second generator and so on... So the length of the range has to be a multiple
214
+ * of \f$ p \f$ and the number of generators will be \f$ length / p \f$. The range is represented by
215
+ * @ref Multi_parameter_filtration::Underlying_container "" and **moved** into the underlying container of the class.
216
+ *
217
+ * @param generators Values to move.
218
+ * @param number_of_parameters Negative values are associated to 0.
219
+ */
220
+ Multi_parameter_filtration(Underlying_container &&generators, int number_of_parameters)
221
+ : generators_(std::move(generators)),
222
+ generator_view_(generators_.data(),
223
+ number_of_parameters <= 0 ? 0 : generators_.size() / number_of_parameters,
224
+ number_of_parameters)
225
+ {
226
+ GUDHI_CHECK(number_of_parameters > 0 || generators_.empty(),
227
+ std::invalid_argument("Number of parameters cannot be 0 if the container is not empty."));
228
+
229
+ if constexpr (Ensure1Criticality) {
230
+ if (generator_view_.extent(0) != 1) throw std::logic_error("Multiparameter filtration value is not 1-critical.");
231
+ }
232
+ }
233
+
234
+ /**
235
+ * @brief Copy constructor.
236
+ */
237
+ Multi_parameter_filtration(const Multi_parameter_filtration &other)
238
+ : generators_(other.generators_),
239
+ generator_view_(generators_.data(), other.num_generators(), other.num_parameters())
240
+ {}
241
+
242
+ /**
243
+ * @brief Copy constructor.
244
+ *
245
+ * @tparam U Type convertible into `T`.
246
+ */
247
+ template <typename U, bool OtherCo, bool OtherEnsure1Criticality>
248
+ Multi_parameter_filtration(const Multi_parameter_filtration<U, OtherCo, OtherEnsure1Criticality> &other)
249
+ : generators_(other.begin(), other.end()),
250
+ generator_view_(generators_.data(), other.num_generators(), other.num_parameters())
251
+ {
252
+ if constexpr (Ensure1Criticality && !OtherEnsure1Criticality) {
253
+ if (generator_view_.extent(0) != 1) throw std::logic_error("Multiparameter filtration value is not 1-critical.");
254
+ }
255
+ }
256
+
257
+ /**
258
+ * @brief Move constructor.
259
+ */
260
+ Multi_parameter_filtration(Multi_parameter_filtration &&other) noexcept
261
+ : generators_(std::move(other.generators_)),
262
+ generator_view_(generators_.data(), other.num_generators(), other.num_parameters())
263
+ {}
264
+
265
+ ~Multi_parameter_filtration() = default;
266
+
267
+ /**
268
+ * @brief Assign operator.
269
+ */
270
+ Multi_parameter_filtration &operator=(const Multi_parameter_filtration &other)
271
+ {
272
+ generators_ = other.generators_;
273
+ generator_view_ = Viewer(generators_.data(), other.num_generators(), other.num_parameters());
274
+ return *this;
275
+ }
276
+
277
+ /**
278
+ * @brief Assign operator.
279
+ *
280
+ * @tparam U Type convertible into `T`.
281
+ */
282
+ template <typename U, bool OtherCo, bool OtherEnsure1Criticality>
283
+ Multi_parameter_filtration &operator=(const Multi_parameter_filtration<U, OtherCo, OtherEnsure1Criticality> &other)
284
+ {
285
+ if constexpr (Ensure1Criticality && !OtherEnsure1Criticality) {
286
+ if (other.num_generators() != 1) throw std::logic_error("Multiparameter filtration value is not 1-critical.");
287
+ }
288
+ generators_ = Underlying_container(other.begin(), other.end());
289
+ generator_view_ = Viewer(generators_.data(), other.num_generators(), other.num_parameters());
290
+ return *this;
291
+ }
292
+
293
+ /**
294
+ * @brief Move assign operator.
295
+ */
296
+ Multi_parameter_filtration &operator=(Multi_parameter_filtration &&other) noexcept
297
+ {
298
+ generators_ = std::move(other.generators_);
299
+ generator_view_ = Viewer(generators_.data(), other.num_generators(), other.num_parameters());
300
+ return *this;
301
+ }
302
+
303
+ /**
304
+ * @brief Swap operator.
305
+ */
306
+ friend void swap(Multi_parameter_filtration &f1, Multi_parameter_filtration &f2) noexcept
307
+ {
308
+ f1.generators_.swap(f2.generators_);
309
+ swap(f1.generator_view_, f2.generator_view_);
310
+ }
311
+
312
+ // VECTOR-LIKE
313
+
314
+ using value_type = T; /**< Value type. */
315
+ using size_type = typename Underlying_container::size_type; /**< Size type. */
316
+ using difference_type = typename Underlying_container::difference_type; /**< Difference type. */
317
+ using reference = value_type &; /**< Reference type. */
318
+ using const_reference = const value_type &; /**< Const reference type. */
319
+ using pointer = typename Underlying_container::pointer; /**< Pointer type. */
320
+ using const_pointer = typename Underlying_container::const_pointer; /**< Const pointer type. */
321
+ using iterator = typename Underlying_container::iterator; /**< Iterator type. */
322
+ using const_iterator = typename Underlying_container::const_iterator; /**< Const iterator type. */
323
+ using reverse_iterator = typename Underlying_container::reverse_iterator; /**< Reverse iterator type. */
324
+ using const_reverse_iterator = typename Underlying_container::const_reverse_iterator; /**< Const reverse iterator. */
325
+
326
+ /**
327
+ * @brief Returns reference to value of parameter `p` of generator `g`.
328
+ */
329
+ reference operator()(size_type g, size_type p) { return generator_view_(g, p); }
330
+
331
+ /**
332
+ * @brief Returns const reference to value of parameter `p` of generator `g`.
333
+ */
334
+ const_reference operator()(size_type g, size_type p) const { return generator_view_(g, p); }
335
+
336
+ /**
337
+ * @brief Let \f$ g \f$ be the first value in `indices` and \f$ p \f$ the second value.
338
+ * Returns reference to value of parameter \f$ p \f$ of generator \f$ g \f$.
339
+ *
340
+ * @tparam IndexRange Range with a begin() and size() method.
341
+ * @param indices Range with at least two elements. The first element should correspond to the generator number and
342
+ * the second element to the parameter number.
343
+ */
344
+ template <class IndexRange = std::initializer_list<size_type>,
345
+ class = std::enable_if_t<RangeTraits<IndexRange>::has_begin> >
346
+ reference operator[](const IndexRange &indices)
347
+ {
348
+ GUDHI_CHECK(indices.size() == 2,
349
+ std::invalid_argument(
350
+ "Exactly 2 indices allowed only: first the generator number, second the parameter number."));
351
+ auto it = indices.begin();
352
+ size_type g = *it;
353
+ return generator_view_(g, *(++it));
354
+ }
355
+
356
+ /**
357
+ * @brief Let \f$ g \f$ be the first value in `indices` and \f$ p \f$ the second value.
358
+ * Returns reference to value of parameter \f$ p \f$ of generator \f$ g \f$.
359
+ *
360
+ * @tparam IndexRange Range with a begin() and size() method.
361
+ * @param indices Range with at least two elements. The first element should correspond to the generator number and
362
+ * the second element to the parameter number.
363
+ */
364
+ template <class IndexRange = std::initializer_list<size_type>,
365
+ class = std::enable_if_t<RangeTraits<IndexRange>::has_begin> >
366
+ const_reference operator[](const IndexRange &indices) const
367
+ {
368
+ GUDHI_CHECK(indices.size() == 2,
369
+ std::invalid_argument(
370
+ "Exactly 2 indices allowed only: first the generator number, second the parameter number."));
371
+ auto it = indices.begin();
372
+ size_type g = *it;
373
+ return generator_view_(g, *(++it));
374
+ }
375
+
376
+ /**
377
+ * @brief Returns an iterator pointing the begining of the underlying container. The @ref num_parameters() first
378
+ * elements corresponds to the first generator, the @ref num_parameters() next to the second and so on.
379
+ *
380
+ * @warning If a generator is modified and the new set of generators is not minimal or not sorted, the behaviour
381
+ * of most methods is undefined. It is possible to call @ref simplify() after construction if there is a doubt to
382
+ * ensure this property.
383
+ */
384
+ iterator begin() noexcept { return generators_.begin(); }
385
+
386
+ /**
387
+ * @brief Returns an iterator pointing the begining of the underlying container. The @ref num_parameters() first
388
+ * elements corresponds to the first generator, the @ref num_parameters() next to the second and so on.
389
+ */
390
+ const_iterator begin() const noexcept { return generators_.begin(); }
391
+
392
+ /**
393
+ * @brief Returns an iterator pointing the begining of the underlying container. The @ref num_parameters() first
394
+ * elements corresponds to the first generator, the @ref num_parameters() next to the second and so on.
395
+ */
396
+ const_iterator cbegin() const noexcept { return generators_.cbegin(); }
397
+
398
+ /**
399
+ * @brief Returns an iterator pointing the end of the underlying container.
400
+ */
401
+ iterator end() noexcept { return generators_.end(); }
402
+
403
+ /**
404
+ * @brief Returns an iterator pointing the end of the underlying container.
405
+ */
406
+ const_iterator end() const noexcept { return generators_.end(); }
407
+
408
+ /**
409
+ * @brief Returns an iterator pointing the end of the underlying container.
410
+ */
411
+ const_iterator cend() const noexcept { return generators_.cend(); }
412
+
413
+ /**
414
+ * @brief Returns a reverse iterator pointing to the first element from the back of the underlying container.
415
+ * The @ref num_parameters() first elements corresponds to the last generator (in parameter reverse order), the
416
+ * @ref num_parameters() next to the second to last and so on.
417
+ *
418
+ * @warning If a generator is modified and the new set of generators is not minimal or not sorted, the behaviour
419
+ * of most methods is undefined. It is possible to call @ref simplify() after construction if there is a doubt to
420
+ * ensure this property.
421
+ */
422
+ reverse_iterator rbegin() noexcept { return generators_.rbegin(); }
423
+
424
+ /**
425
+ * @brief Returns a reverse iterator pointing to the first element from the back of the underlying container.
426
+ * The @ref num_parameters() first elements corresponds to the last generator (in parameter reverse order), the
427
+ * @ref num_parameters() next to the second to last and so on.
428
+ */
429
+ const_reverse_iterator rbegin() const noexcept { return generators_.rbegin(); }
430
+
431
+ /**
432
+ * @brief Returns a reverse iterator pointing to the first element from the back of the underlying container.
433
+ * The @ref num_parameters() first elements corresponds to the last generator (in parameter reverse order), the
434
+ * @ref num_parameters() next to the second to last and so on.
435
+ */
436
+ const_reverse_iterator crbegin() const noexcept { return generators_.crbegin(); }
437
+
438
+ /**
439
+ * @brief Returns a reverse iterator pointing to the end of the reversed underlying container.
440
+ */
441
+ reverse_iterator rend() noexcept { return generators_.rend(); }
442
+
443
+ /**
444
+ * @brief Returns a reverse iterator pointing to the end of the reversed underlying container.
445
+ */
446
+ const_reverse_iterator rend() const noexcept { return generators_.rend(); }
447
+
448
+ /**
449
+ * @brief Returns a reverse iterator pointing to the end of the reversed underlying container.
450
+ */
451
+ const_reverse_iterator crend() const noexcept { return generators_.crend(); }
452
+
453
+ /**
454
+ * @brief Returns the size of the underlying container. Corresponds exactly to @ref num_entries(), but enables to use
455
+ * the class as a classic range with a `begin`, `end` and `size` method.
456
+ */
457
+ size_type size() const noexcept { return generators_.size(); }
458
+
459
+ /**
460
+ * @brief Reserves space for the given number of generators in the underlying container. Does nothing if
461
+ * `Ensure1Criticality` is true.
462
+ */
463
+ void reserve([[maybe_unused]] size_type number_of_generators)
464
+ {
465
+ if constexpr (Ensure1Criticality) {
466
+ return;
467
+ } else {
468
+ generators_.reserve(num_parameters() * number_of_generators);
469
+ }
470
+ }
471
+
472
+ // CONVERTERS
473
+
474
+ // like numpy
475
+ /**
476
+ * @brief Returns a copy with entries casted into the type given as template parameter.
477
+ *
478
+ * @tparam U New type for the entries.
479
+ * @tparam OCo New value for `Co`. Default value: `Co`.
480
+ * @tparam OEns New value for `Ensure1Criticality`. Note that if `OEns` is set to true and the value is not
481
+ * 1-critical, the method will throw. Default value: `Ensure1Criticality`.
482
+ * @return Copy with new entry type.
483
+ */
484
+ template <typename U, bool OCo = Co, bool OEns = Ensure1Criticality>
485
+ Multi_parameter_filtration<U, OCo, OEns> as_type() const
486
+ {
487
+ std::vector<U> out(generators_.begin(), generators_.end());
488
+ return Multi_parameter_filtration<U, OCo, OEns>(std::move(out), num_parameters());
489
+ }
490
+
491
+ // ACCESS
492
+
493
+ /**
494
+ * @brief Returns the number of parameters in the filtration value.
495
+ */
496
+ size_type num_parameters() const { return generator_view_.extent(1); }
497
+
498
+ /**
499
+ * @brief Returns the number of generators in the filtration value, i.e. the criticality of the element.
500
+ */
501
+ size_type num_generators() const
502
+ {
503
+ if constexpr (Ensure1Criticality) {
504
+ return 1; // for possible optimizations? If there is none, we can just keep the other version
505
+ } else {
506
+ return generator_view_.extent(0);
507
+ }
508
+ }
509
+
510
+ /**
511
+ * @brief Returns the total number of values in the filtration value, that is,
512
+ * @ref num_parameters() * @ref num_generators().
513
+ */
514
+ size_type num_entries() const { return generators_.size(); }
515
+
516
+ /**
517
+ * @brief Returns a filtration value with given number of parameters for which @ref is_plus_inf() returns `true`.
518
+ */
519
+ static Multi_parameter_filtration inf(int number_of_parameters)
520
+ {
521
+ return Multi_parameter_filtration(number_of_parameters, T_inf);
522
+ }
523
+
524
+ /**
525
+ * @brief Returns a filtration value with given number of parameters for which @ref is_minus_inf() returns `true`.
526
+ */
527
+ static Multi_parameter_filtration minus_inf(int number_of_parameters)
528
+ {
529
+ return Multi_parameter_filtration(number_of_parameters, T_m_inf);
530
+ }
531
+
532
+ /**
533
+ * @brief If `std::numeric_limits<T>::has_quiet_NaN` is true, returns a filtration value with given number of
534
+ * parameters for which @ref is_nan() returns `true`. Otherwise, throws.
535
+ */
536
+ static Multi_parameter_filtration nan(int number_of_parameters)
537
+ {
538
+ if constexpr (std::numeric_limits<T>::has_quiet_NaN) {
539
+ return Multi_parameter_filtration(number_of_parameters, std::numeric_limits<T>::quiet_NaN());
540
+ } else {
541
+ throw std::logic_error("No NaN value exists.");
542
+ }
543
+ }
544
+
545
+ // DESCRIPTORS
546
+
547
+ /**
548
+ * @brief Returns value of `Ensure1Criticality`.
549
+ */
550
+ static constexpr bool ensures_1_criticality() { return Ensure1Criticality; }
551
+
552
+ /**
553
+ * @brief Returns value of `Co`.
554
+ */
555
+ static constexpr bool has_negative_cones() { return Co; }
556
+
557
+ /**
558
+ * @brief Returns `true` if and only if the filtration value is considered as plus infinity.
559
+ */
560
+ [[nodiscard]] bool is_plus_inf() const
561
+ {
562
+ for (const T &v : generators_) {
563
+ if (v != T_inf) return false;
564
+ }
565
+ return true;
566
+ }
567
+
568
+ /**
569
+ * @brief Returns `true` if and only if the filtration value is considered as minus infinity.
570
+ */
571
+ [[nodiscard]] bool is_minus_inf() const
572
+ {
573
+ for (const T &v : generators_) {
574
+ if (v != T_m_inf) return false;
575
+ }
576
+ return true;
577
+ }
578
+
579
+ /**
580
+ * @brief Returns `true` if and only if the filtration value is considered as NaN.
581
+ */
582
+ [[nodiscard]] bool is_nan() const
583
+ {
584
+ if constexpr (std::numeric_limits<T>::has_quiet_NaN) {
585
+ for (const auto &v : generators_) {
586
+ if (!std::isnan(v)) return false;
587
+ }
588
+ return true;
589
+ } else {
590
+ return false;
591
+ }
592
+ }
593
+
594
+ /**
595
+ * @brief Returns `true` if and only if the filtration value is non-empty and is not considered as plus infinity,
596
+ * minus infinity or NaN.
597
+ */
598
+ [[nodiscard]] bool is_finite() const
599
+ {
600
+ bool isInf = true, isMinusInf = true, isNan = true;
601
+ for (const auto &v : generators_) {
602
+ if (v != T_inf) isInf = false;
603
+ if (v != T_m_inf) isMinusInf = false;
604
+ if (!_is_nan(v)) isNan = false;
605
+ if (!isInf && !isMinusInf && !isNan) return true;
606
+ }
607
+ return false;
608
+ }
609
+
610
+ // COMPARAISON OPERATORS
611
+
612
+ /**
613
+ * @brief Returns `true` if and only if the first argument is lexicographically strictly less than the second
614
+ * argument. The "words" considered for the lexicographical order are all the generators concatenated together
615
+ * in order of generator index and then in order of parameter index. Different from @ref operator< "", this order
616
+ * is total.
617
+ *
618
+ * @tparam inverse If true, the parameter index and generator index order is inverted.
619
+ */
620
+ template <bool inverse = false>
621
+ friend bool is_strict_less_than_lexicographically(const Multi_parameter_filtration &a,
622
+ const Multi_parameter_filtration &b)
623
+ {
624
+ if (&a == &b) return false;
625
+
626
+ GUDHI_CHECK(a.num_parameters() == b.num_parameters(),
627
+ std::invalid_argument("Only filtration values with same number of parameters can be compared."));
628
+
629
+ for (std::size_t i = 0U; i < a.num_parameters() * std::min(a.num_generators(), b.num_generators()); ++i) {
630
+ std::size_t iA = i;
631
+ std::size_t iB = i;
632
+ if constexpr (inverse) {
633
+ iA = a.generators_.size() - 1 - i;
634
+ iB = b.generators_.size() - 1 - i;
635
+ }
636
+ if (_is_nan(a.generators_[iA]) && !_is_nan(b.generators_[iB])) return false;
637
+ if (_is_nan(b.generators_[iB])) return true;
638
+ if (a.generators_[iA] < b.generators_[iB]) return true;
639
+ if (b.generators_[iB] < a.generators_[iA]) return false;
640
+ }
641
+ return a.num_generators() < b.num_generators();
642
+ }
643
+
644
+ /**
645
+ * @brief Returns `true` if and only if the first argument is lexicographically less than or equal to the second
646
+ * argument. The "words" considered for the lexicographical order are all the generators concatenated together
647
+ * in order of generator index and then in order of parameter index. Different from @ref operator<= "", this order
648
+ * is total.
649
+ *
650
+ * @tparam inverse If true, the parameter index and generator index order is inverted.
651
+ */
652
+ template <bool inverse = false>
653
+ friend bool is_less_or_equal_than_lexicographically(const Multi_parameter_filtration &a,
654
+ const Multi_parameter_filtration &b)
655
+ {
656
+ if (&a == &b) return true;
657
+
658
+ GUDHI_CHECK(a.num_parameters() == b.num_parameters(),
659
+ std::invalid_argument("Only filtration values with same number of parameters can be compared."));
660
+
661
+ for (std::size_t i = 0U; i < a.num_parameters() * std::min(a.num_generators(), b.num_generators()); ++i) {
662
+ std::size_t iA = i;
663
+ std::size_t iB = i;
664
+ if constexpr (inverse) {
665
+ iA = a.generators_.size() - 1 - i;
666
+ iB = b.generators_.size() - 1 - i;
667
+ }
668
+ if (_is_nan(a.generators_[iA]) && !_is_nan(b.generators_[iB])) return false;
669
+ if (_is_nan(b.generators_[iB])) return true;
670
+ if (a.generators_[iA] < b.generators_[iB]) return true;
671
+ if (b.generators_[iB] < a.generators_[iA]) return false;
672
+ }
673
+ return a.num_generators() <= b.num_generators();
674
+ }
675
+
676
+ /**
677
+ * @brief Returns `true` if and only if the cones generated by @p b are strictly contained in the
678
+ * cones generated by @p a (recall that the cones are positive if `Co` is false and negative if `Co` is true).
679
+ * Both @p a and @p b have to have the same number of parameters.
680
+ *
681
+ * Note that not all filtration values are comparable. That is, \f$ a < b \f$ and \f$ b < a \f$ returning both false
682
+ * does **not** imply \f$ a == b \f$. If a total order is needed, use @ref is_strict_less_than_lexicographically
683
+ * instead.
684
+ */
685
+ friend bool operator<(const Multi_parameter_filtration &a, const Multi_parameter_filtration &b)
686
+ {
687
+ if (&a == &b) return false;
688
+
689
+ GUDHI_CHECK(a.num_parameters() == b.num_parameters(),
690
+ std::invalid_argument("Only filtration values with same number of parameters can be compared."));
691
+
692
+ if (a.num_generators() == 0 || b.num_generators() == 0) return false;
693
+
694
+ const auto &view_a = a.generator_view_;
695
+ const auto &view_b = b.generator_view_;
696
+ // TODO: verify if this really makes a differences in the 1-critical case, otherwise just keep the general case
697
+ if constexpr (Ensure1Criticality) {
698
+ if (_first_dominates(view_a, 0, view_b, 0)) return false;
699
+ return _strictly_contains(view_a, 0, view_b, 0);
700
+ } else {
701
+ for (std::size_t i = 0U; i < b.num_generators(); ++i) {
702
+ // for each generator in b, verify if it is strictly in the cone of at least one generator of a
703
+ bool isContained = false;
704
+ for (std::size_t j = 0U; j < a.num_generators() && !isContained; ++j) {
705
+ // lexicographical order, so if a[j][0] dom b[j][0], than a[j'] can never strictly contain b[i] for all
706
+ // j' > j.
707
+ if (_first_dominates(view_a, j, view_b, i)) return false;
708
+ isContained = _strictly_contains(view_a, j, view_b, i);
709
+ }
710
+ if (!isContained) return false;
711
+ }
712
+ return true;
713
+ }
714
+ }
715
+
716
+ /**
717
+ * @brief Returns `true` if and only if the cones generated by @p a are strictly contained in the
718
+ * cones generated by @p b (recall that the cones are positive if `Co` is false and negative if `Co` is true).
719
+ * Both @p a and @p b have to have the same number of parameters.
720
+ *
721
+ * Note that not all filtration values are comparable. That is, \f$ a \le b \f$ and \f$ b \le a \f$ can both return
722
+ * `false`. If a total order is needed, use @ref is_less_or_equal_than_lexicographically instead.
723
+ */
724
+ friend bool operator<=(const Multi_parameter_filtration &a, const Multi_parameter_filtration &b)
725
+ {
726
+ GUDHI_CHECK(a.num_parameters() == b.num_parameters(),
727
+ std::invalid_argument("Only filtration values with same number of parameters can be compared."));
728
+
729
+ if (a.num_generators() == 0 || b.num_generators() == 0) return false;
730
+ if (a.is_nan() || b.is_nan()) return false;
731
+ if (&a == &b) return true;
732
+
733
+ const auto &view_a = a.generator_view_;
734
+ const auto &view_b = b.generator_view_;
735
+ // TODO: verify if this really makes a differences in the 1-critical case, otherwise just keep the general case
736
+ if constexpr (Ensure1Criticality) {
737
+ if (_first_strictly_dominates(view_a, 0, view_b, 0)) return false;
738
+ return _contains(view_a, 0, view_b, 0);
739
+ } else {
740
+ // check if this curves is below other's curve
741
+ // ie for each guy in this, check if there is a guy in other that dominates him
742
+ for (std::size_t i = 0U; i < b.num_generators(); ++i) {
743
+ // for each generator in b, verify if it is in the cone of at least one generator of a
744
+ bool isContained = false;
745
+ for (std::size_t j = 0U; j < a.num_generators() && !isContained; ++j) {
746
+ // lexicographical order, so if a[j][0] strictly dom b[j][0], than a[j'] can never contain b[i] for all
747
+ // j' > j.
748
+ if (_first_strictly_dominates(view_a, j, view_b, i)) return false;
749
+ isContained = _contains(view_a, j, view_b, i);
750
+ }
751
+ if (!isContained) return false;
752
+ }
753
+ return true;
754
+ }
755
+ }
756
+
757
+ /**
758
+ * @brief Returns `true` if and only if the cones generated by @p b are contained in or are (partially)
759
+ * equal to the cones generated by @p a (recall that the cones are positive if `Co` is false and negative if `Co` is
760
+ * true).
761
+ * Both @p a and @p b have to have the same number of parameters.
762
+ *
763
+ * Note that not all filtration values are comparable. That is, \f$ a > b \f$ and \f$ b > a \f$ returning both false
764
+ * does **not** imply \f$ a == b \f$. If a total order is needed, use @ref is_strict_less_than_lexicographically
765
+ * instead.
766
+ */
767
+ friend bool operator>(const Multi_parameter_filtration &a, const Multi_parameter_filtration &b) { return b < a; }
768
+
769
+ /**
770
+ * @brief Returns `true` if and only if the cones generated by @p a are contained in or are (partially)
771
+ * equal to the cones generated by @p b (recall that the cones are positive if `Co` is false and negative if `Co` is
772
+ * true).
773
+ * Both @p a and @p b have to have the same number of parameters.
774
+ *
775
+ * Note that not all filtration values are comparable. That is, \f$ a \ge b \f$ and \f$ b \ge a \f$ can both return
776
+ * `false`. If a total order is needed, use @ref is_less_or_equal_than_lexicographically instead.
777
+ */
778
+ friend bool operator>=(const Multi_parameter_filtration &a, const Multi_parameter_filtration &b) { return b <= a; }
779
+
780
+ /**
781
+ * @brief Returns `true` if and only if for each \f$ i,j \f$, \f$ a(i,j) \f$ is equal to \f$ b(i,j) \f$.
782
+ */
783
+ friend bool operator==(const Multi_parameter_filtration &a, const Multi_parameter_filtration &b)
784
+ {
785
+ if (&a == &b) return true;
786
+ // assumes lexicographical order for both
787
+ return a.generators_ == b.generators_;
788
+ }
789
+
790
+ /**
791
+ * @brief Returns `true` if and only if \f$ a == b \f$ returns `false`.
792
+ */
793
+ friend bool operator!=(const Multi_parameter_filtration &a, const Multi_parameter_filtration &b) { return !(a == b); }
794
+
795
+ // ARITHMETIC OPERATORS
796
+
797
+ // opposite
798
+ /**
799
+ * @brief Returns a filtration value such that an entry at index \f$ i,j \f$ is equal to \f$ -f(i,j) \f$.
800
+ *
801
+ * Used conventions:
802
+ * - \f$ -NaN = NaN \f$.
803
+ *
804
+ * @param f Value to opposite.
805
+ * @return The opposite of @p f.
806
+ */
807
+ friend Multi_parameter_filtration operator-(const Multi_parameter_filtration &f)
808
+ {
809
+ using F = Multi_parameter_filtration;
810
+
811
+ Underlying_container result(f.generators_);
812
+ std::for_each(result.begin(), result.end(), [](T &v) {
813
+ if (v == F::T_inf)
814
+ v = F::T_m_inf;
815
+ else if (v == F::T_m_inf)
816
+ v = F::T_inf;
817
+ else
818
+ v = -v;
819
+ });
820
+ return Multi_parameter_filtration(std::move(result), f.num_parameters());
821
+ }
822
+
823
+ // subtraction
824
+ /**
825
+ * @brief Returns a filtration value such that an entry at index \f$ (g,p) \f$, with
826
+ * \f$ 0 \leq g \leq num_generators \f$ and \f$ 0 \leq p \leq num_parameters \f$ is equal to \f$ f(g,p) - r(p) \f$
827
+ * if \f$ p < length_r \f$ and to \f$ f(g,p) \f$ otherwise.
828
+ *
829
+ * Used conventions:
830
+ * - \f$ inf - inf = NaN \f$,
831
+ * - \f$ -inf - (-inf) = NaN \f$,
832
+ * - \f$ NaN - b = NaN \f$,
833
+ * - \f$ a - NaN = NaN \f$.
834
+ *
835
+ * All NaN values are represented by `std::numeric_limits<T>::quiet_NaN()` independently if
836
+ * `std::numeric_limits<T>::has_quiet_NaN` is true or not.
837
+ *
838
+ * @tparam ValueRange Range with a begin() and end() method.
839
+ * @param f First element of the subtraction.
840
+ * @param r Second element of the subtraction.
841
+ */
842
+ template <class ValueRange, class = std::enable_if_t<RangeTraits<ValueRange>::has_begin> >
843
+ friend Multi_parameter_filtration operator-(Multi_parameter_filtration f, const ValueRange &r)
844
+ {
845
+ f -= r;
846
+ return f;
847
+ }
848
+
849
+ /**
850
+ * @brief Returns a filtration value such that an entry at index \f$ (g,p) \f$, with
851
+ * \f$ 0 \leq g \leq num_generators \f$ and \f$ 0 \leq p \leq num_parameters \f$ is equal to \f$ r(p) - f(g,p) \f$
852
+ * if \f$ p < length_r \f$ and to \f$ -f(g,p) \f$ otherwise.
853
+ *
854
+ * Used conventions:
855
+ * - \f$ inf - inf = NaN \f$,
856
+ * - \f$ -inf - (-inf) = NaN \f$,
857
+ * - \f$ NaN - b = NaN \f$,
858
+ * - \f$ a - NaN = NaN \f$.
859
+ *
860
+ * All NaN values are represented by `std::numeric_limits<T>::quiet_NaN()` independently if
861
+ * `std::numeric_limits<T>::has_quiet_NaN` is true or not.
862
+ *
863
+ * @tparam ValueRange Range with a begin() and end() method.
864
+ * @param r First element of the subtraction.
865
+ * @param f Second element of the subtraction.
866
+ */
867
+ template <class ValueRange,
868
+ class = std::enable_if_t<RangeTraits<ValueRange>::has_begin &&
869
+ !std::is_same_v<ValueRange, Multi_parameter_filtration> > >
870
+ friend Multi_parameter_filtration operator-(const ValueRange &r, Multi_parameter_filtration f)
871
+ {
872
+ f._apply_operation(r, [](T &valF, const T &valR) {
873
+ valF = -valF;
874
+ _add(valF, valR);
875
+ });
876
+ return f;
877
+ }
878
+
879
+ /**
880
+ * @brief Returns a filtration value such that an entry at index \f$ (g,p) \f$ is equal to \f$ f(g,p) - val \f$.
881
+ *
882
+ * Used conventions:
883
+ * - \f$ inf - inf = NaN \f$,
884
+ * - \f$ -inf - (-inf) = NaN \f$,
885
+ * - \f$ NaN - b = NaN \f$,
886
+ * - \f$ a - NaN = NaN \f$.
887
+ *
888
+ * All NaN values are represented by `std::numeric_limits<T>::quiet_NaN()` independently if
889
+ * `std::numeric_limits<T>::has_quiet_NaN` is true or not.
890
+ *
891
+ * @param f First element of the subtraction.
892
+ * @param val Second element of the subtraction.
893
+ */
894
+ friend Multi_parameter_filtration operator-(Multi_parameter_filtration f, const T &val)
895
+ {
896
+ f -= val;
897
+ return f;
898
+ }
899
+
900
+ /**
901
+ * @brief Returns a filtration value such that an entry at index \f$ (g,p) \f$ is equal to \f$ val - f(g,p) \f$.
902
+ *
903
+ * Used conventions:
904
+ * - \f$ inf - inf = NaN \f$,
905
+ * - \f$ -inf - (-inf) = NaN \f$,
906
+ * - \f$ NaN - b = NaN \f$,
907
+ * - \f$ a - NaN = NaN \f$.
908
+ *
909
+ * All NaN values are represented by `std::numeric_limits<T>::quiet_NaN()` independently if
910
+ * `std::numeric_limits<T>::has_quiet_NaN` is true or not.
911
+ *
912
+ * @param val First element of the subtraction.
913
+ * @param f Second element of the subtraction.
914
+ */
915
+ friend Multi_parameter_filtration operator-(const T &val, Multi_parameter_filtration f)
916
+ {
917
+ f._apply_operation(val, [](T &valF, const T &valR) {
918
+ valF = -valF;
919
+ _add(valF, valR);
920
+ });
921
+ return f;
922
+ }
923
+
924
+ /**
925
+ * @brief Modifies the first parameter such that an entry at index \f$ (g,p) \f$, with
926
+ * \f$ 0 \leq g \leq num_generators \f$ and \f$ 0 \leq p \leq num_parameters \f$ is equal to \f$ f(g,p) - r(p) \f$
927
+ * if \f$ p < length_r \f$ and to \f$ f(g,p) \f$ otherwise.
928
+ *
929
+ * Used conventions:
930
+ * - \f$ inf - inf = NaN \f$,
931
+ * - \f$ -inf - (-inf) = NaN \f$,
932
+ * - \f$ NaN - b = NaN \f$,
933
+ * - \f$ a - NaN = NaN \f$.
934
+ *
935
+ * All NaN values are represented by `std::numeric_limits<T>::quiet_NaN()` independently if
936
+ * `std::numeric_limits<T>::has_quiet_NaN` is true or not.
937
+ *
938
+ * @tparam ValueRange Range with a begin() and end() method.
939
+ * @param f First element of the subtraction.
940
+ * @param r Second element of the subtraction.
941
+ */
942
+ template <class ValueRange, class = std::enable_if_t<RangeTraits<ValueRange>::has_begin> >
943
+ friend Multi_parameter_filtration &operator-=(Multi_parameter_filtration &f, const ValueRange &r)
944
+ {
945
+ f._apply_operation(r, [](T &valF, const T &valR) { _subtract(valF, valR); });
946
+ return f;
947
+ }
948
+
949
+ /**
950
+ * @brief Modifies the first parameter such that an entry at index \f$ (g,p) \f$ is equal to \f$ f(g,p) - val \f$.
951
+ *
952
+ * Used conventions:
953
+ * - \f$ inf - inf = NaN \f$,
954
+ * - \f$ -inf - (-inf) = NaN \f$,
955
+ * - \f$ NaN - b = NaN \f$,
956
+ * - \f$ a - NaN = NaN \f$.
957
+ *
958
+ * All NaN values are represented by `std::numeric_limits<T>::quiet_NaN()` independently if
959
+ * `std::numeric_limits<T>::has_quiet_NaN` is true or not.
960
+ *
961
+ * @param f First element of the subtraction.
962
+ * @param val Second element of the subtraction.
963
+ */
964
+ friend Multi_parameter_filtration &operator-=(Multi_parameter_filtration &f, const T &val)
965
+ {
966
+ f._apply_operation(val, [](T &valF, const T &valR) { _subtract(valF, valR); });
967
+ return f;
968
+ }
969
+
970
+ // addition
971
+ /**
972
+ * @brief Returns a filtration value such that an entry at index \f$ (g,p) \f$, with
973
+ * \f$ 0 \leq g \leq num_generators \f$ and \f$ 0 \leq p \leq num_parameters \f$ is equal to \f$ f(g,p) + r(p) \f$
974
+ * if \f$ p < length_r \f$ and to \f$ f(g,p) \f$ otherwise.
975
+ *
976
+ * Used conventions:
977
+ * - \f$ inf + (-inf) = NaN \f$,
978
+ * - \f$ -inf + inf = NaN \f$,
979
+ * - \f$ NaN + b = NaN \f$,
980
+ * - \f$ a + NaN = NaN \f$.
981
+ *
982
+ * All NaN values are represented by `std::numeric_limits<T>::quiet_NaN()` independently if
983
+ * `std::numeric_limits<T>::has_quiet_NaN` is true or not.
984
+ *
985
+ * @tparam ValueRange Range with a begin() and end() method.
986
+ * @param f First element of the addition.
987
+ * @param r Second element of the addition.
988
+ */
989
+ template <class ValueRange, class = std::enable_if_t<RangeTraits<ValueRange>::has_begin> >
990
+ friend Multi_parameter_filtration operator+(Multi_parameter_filtration f, const ValueRange &r)
991
+ {
992
+ f += r;
993
+ return f;
994
+ }
995
+
996
+ /**
997
+ * @brief Returns a filtration value such that an entry at index \f$ (g,p) \f$, with
998
+ * \f$ 0 \leq g \leq num_generators \f$ and \f$ 0 \leq p \leq num_parameters \f$ is equal to \f$ r(p) + f(g,p) \f$
999
+ * if \f$ p < length_r \f$ and to \f$ f(g,p) \f$ otherwise.
1000
+ *
1001
+ * Used conventions:
1002
+ * - \f$ inf + (-inf) = NaN \f$,
1003
+ * - \f$ -inf + inf = NaN \f$,
1004
+ * - \f$ NaN + b = NaN \f$,
1005
+ * - \f$ a + NaN = NaN \f$.
1006
+ *
1007
+ * All NaN values are represented by `std::numeric_limits<T>::quiet_NaN()` independently if
1008
+ * `std::numeric_limits<T>::has_quiet_NaN` is true or not.
1009
+ *
1010
+ * @tparam ValueRange Range with a begin() and end() method.
1011
+ * @param r First element of the addition.
1012
+ * @param f Second element of the addition.
1013
+ */
1014
+ template <class ValueRange,
1015
+ class = std::enable_if_t<RangeTraits<ValueRange>::has_begin &&
1016
+ !std::is_same_v<ValueRange, Multi_parameter_filtration> > >
1017
+ friend Multi_parameter_filtration operator+(const ValueRange &r, Multi_parameter_filtration f)
1018
+ {
1019
+ f += r;
1020
+ return f;
1021
+ }
1022
+
1023
+ /**
1024
+ * @brief Returns a filtration value such that an entry at index \f$ (g,p) \f$ is equal to \f$ f(g,p) + val \f$.
1025
+ *
1026
+ * Used conventions:
1027
+ * - \f$ inf + (-inf) = NaN \f$,
1028
+ * - \f$ -inf + inf = NaN \f$,
1029
+ * - \f$ NaN + b = NaN \f$,
1030
+ * - \f$ a + NaN = NaN \f$.
1031
+ *
1032
+ * All NaN values are represented by `std::numeric_limits<T>::quiet_NaN()` independently if
1033
+ * `std::numeric_limits<T>::has_quiet_NaN` is true or not.
1034
+ *
1035
+ * @param f First element of the addition.
1036
+ * @param val Second element of the addition.
1037
+ */
1038
+ friend Multi_parameter_filtration operator+(Multi_parameter_filtration f, const T &val)
1039
+ {
1040
+ f += val;
1041
+ return f;
1042
+ }
1043
+
1044
+ /**
1045
+ * @brief Returns a filtration value such that an entry at index \f$ (g,p) \f$ is equal to \f$ val + f(g,p) \f$.
1046
+ *
1047
+ * Used conventions:
1048
+ * - \f$ inf + (-inf) = NaN \f$,
1049
+ * - \f$ -inf + inf = NaN \f$,
1050
+ * - \f$ NaN + b = NaN \f$,
1051
+ * - \f$ a + NaN = NaN \f$.
1052
+ *
1053
+ * All NaN values are represented by `std::numeric_limits<T>::quiet_NaN()` independently if
1054
+ * `std::numeric_limits<T>::has_quiet_NaN` is true or not.
1055
+ *
1056
+ * @param val First element of the addition.
1057
+ * @param f Second element of the addition.
1058
+ */
1059
+ friend Multi_parameter_filtration operator+(const T &val, Multi_parameter_filtration f)
1060
+ {
1061
+ f += val;
1062
+ return f;
1063
+ }
1064
+
1065
+ /**
1066
+ * @brief Modifies the first parameter such that an entry at index \f$ (g,p) \f$, with
1067
+ * \f$ 0 \leq g \leq num_generators \f$ and \f$ 0 \leq p \leq num_parameters \f$ is equal to \f$ f(g,p) + r(p) \f$
1068
+ * if \f$ p < length_r \f$ and to \f$ f(g,p) \f$ otherwise.
1069
+ *
1070
+ * Used conventions:
1071
+ * - \f$ inf + (-inf) = NaN \f$,
1072
+ * - \f$ -inf + inf = NaN \f$,
1073
+ * - \f$ NaN + b = NaN \f$,
1074
+ * - \f$ a + NaN = NaN \f$.
1075
+ *
1076
+ * All NaN values are represented by `std::numeric_limits<T>::quiet_NaN()` independently if
1077
+ * `std::numeric_limits<T>::has_quiet_NaN` is true or not.
1078
+ *
1079
+ * @tparam ValueRange Range with a begin() and end() method.
1080
+ * @param f First element of the addition.
1081
+ * @param r Second element of the addition.
1082
+ */
1083
+ template <class ValueRange, class = std::enable_if_t<RangeTraits<ValueRange>::has_begin> >
1084
+ friend Multi_parameter_filtration &operator+=(Multi_parameter_filtration &f, const ValueRange &r)
1085
+ {
1086
+ f._apply_operation(r, [](T &valF, const T &valR) { _add(valF, valR); });
1087
+ return f;
1088
+ }
1089
+
1090
+ /**
1091
+ * @brief Modifies the first parameter such that an entry at index \f$ (g,p) \f$ is equal to \f$ f(g,p) + val \f$.
1092
+ *
1093
+ * Used conventions:
1094
+ * - \f$ inf + (-inf) = NaN \f$,
1095
+ * - \f$ -inf + inf = NaN \f$,
1096
+ * - \f$ NaN + b = NaN \f$,
1097
+ * - \f$ a + NaN = NaN \f$.
1098
+ *
1099
+ * All NaN values are represented by `std::numeric_limits<T>::quiet_NaN()` independently if
1100
+ * `std::numeric_limits<T>::has_quiet_NaN` is true or not.
1101
+ *
1102
+ * @param f First element of the addition.
1103
+ * @param val Second element of the addition.
1104
+ */
1105
+ friend Multi_parameter_filtration &operator+=(Multi_parameter_filtration &f, const T &val)
1106
+ {
1107
+ f._apply_operation(val, [](T &valF, const T &valR) { _add(valF, valR); });
1108
+ return f;
1109
+ }
1110
+
1111
+ // multiplication
1112
+ /**
1113
+ * @brief Returns a filtration value such that an entry at index \f$ (g,p) \f$, with
1114
+ * \f$ 0 \leq g \leq num_generators \f$ and \f$ 0 \leq p \leq num_parameters \f$ is equal to \f$ f(g,p) * r(p) \f$
1115
+ * if \f$ p < length_r \f$ and to \f$ f(g,p) \f$ otherwise.
1116
+ *
1117
+ * Used conventions:
1118
+ * - \f$ inf * 0 = NaN \f$,
1119
+ * - \f$ 0 * inf = NaN \f$,
1120
+ * - \f$ -inf * 0 = NaN \f$,
1121
+ * - \f$ 0 * (-inf) = NaN \f$,
1122
+ * - \f$ NaN * b = NaN \f$,
1123
+ * - \f$ a * NaN = NaN \f$.
1124
+ *
1125
+ * All NaN values are represented by `std::numeric_limits<T>::quiet_NaN()` independently if
1126
+ * `std::numeric_limits<T>::has_quiet_NaN` is true or not.
1127
+ *
1128
+ * @tparam ValueRange Range with a begin() and end() method.
1129
+ * @param f First element of the multiplication.
1130
+ * @param r Second element of the multiplication.
1131
+ */
1132
+ template <class ValueRange, class = std::enable_if_t<RangeTraits<ValueRange>::has_begin> >
1133
+ friend Multi_parameter_filtration operator*(Multi_parameter_filtration f, const ValueRange &r)
1134
+ {
1135
+ f *= r;
1136
+ return f;
1137
+ }
1138
+
1139
+ /**
1140
+ * @brief Returns a filtration value such that an entry at index \f$ (g,p) \f$, with
1141
+ * \f$ 0 \leq g \leq num_generators \f$ and \f$ 0 \leq p \leq num_parameters \f$ is equal to \f$ r(p) * f(g,p) \f$
1142
+ * if \f$ p < length_r \f$ and to \f$ f(g,p) \f$ otherwise.
1143
+ *
1144
+ * Used conventions:
1145
+ * - \f$ inf * 0 = NaN \f$,
1146
+ * - \f$ 0 * inf = NaN \f$,
1147
+ * - \f$ -inf * 0 = NaN \f$,
1148
+ * - \f$ 0 * (-inf) = NaN \f$,
1149
+ * - \f$ NaN * b = NaN \f$,
1150
+ * - \f$ a * NaN = NaN \f$.
1151
+ *
1152
+ * All NaN values are represented by `std::numeric_limits<T>::quiet_NaN()` independently if
1153
+ * `std::numeric_limits<T>::has_quiet_NaN` is true or not.
1154
+ *
1155
+ * @tparam ValueRange Range with a begin() and end() method.
1156
+ * @param r First element of the multiplication.
1157
+ * @param f Second element of the multiplication.
1158
+ */
1159
+ template <class ValueRange,
1160
+ class = std::enable_if_t<RangeTraits<ValueRange>::has_begin &&
1161
+ !std::is_same_v<ValueRange, Multi_parameter_filtration> > >
1162
+ friend Multi_parameter_filtration operator*(const ValueRange &r, Multi_parameter_filtration f)
1163
+ {
1164
+ f *= r;
1165
+ return f;
1166
+ }
1167
+
1168
+ /**
1169
+ * @brief Returns a filtration value such that an entry at index \f$ (g,p) \f$ is equal to \f$ f(g,p) * val \f$.
1170
+ *
1171
+ * Used conventions:
1172
+ * - \f$ inf * 0 = NaN \f$,
1173
+ * - \f$ 0 * inf = NaN \f$,
1174
+ * - \f$ -inf * 0 = NaN \f$,
1175
+ * - \f$ 0 * (-inf) = NaN \f$,
1176
+ * - \f$ NaN * b = NaN \f$,
1177
+ * - \f$ a * NaN = NaN \f$.
1178
+ *
1179
+ * All NaN values are represented by `std::numeric_limits<T>::quiet_NaN()` independently if
1180
+ * `std::numeric_limits<T>::has_quiet_NaN` is true or not.
1181
+ *
1182
+ * @param f First element of the multiplication.
1183
+ * @param val Second element of the multiplication.
1184
+ */
1185
+ friend Multi_parameter_filtration operator*(Multi_parameter_filtration f, const T &val)
1186
+ {
1187
+ f *= val;
1188
+ return f;
1189
+ }
1190
+
1191
+ /**
1192
+ * @brief Returns a filtration value such that an entry at index \f$ (g,p) \f$ is equal to \f$ val * f(g,p) \f$.
1193
+ *
1194
+ * Used conventions:
1195
+ * - \f$ inf * 0 = NaN \f$,
1196
+ * - \f$ 0 * inf = NaN \f$,
1197
+ * - \f$ -inf * 0 = NaN \f$,
1198
+ * - \f$ 0 * (-inf) = NaN \f$,
1199
+ * - \f$ NaN * b = NaN \f$,
1200
+ * - \f$ a * NaN = NaN \f$.
1201
+ *
1202
+ * All NaN values are represented by `std::numeric_limits<T>::quiet_NaN()` independently if
1203
+ * `std::numeric_limits<T>::has_quiet_NaN` is true or not.
1204
+ *
1205
+ * @param val First element of the multiplication.
1206
+ * @param f Second element of the multiplication.
1207
+ */
1208
+ friend Multi_parameter_filtration operator*(const T &val, Multi_parameter_filtration f)
1209
+ {
1210
+ f *= val;
1211
+ return f;
1212
+ }
1213
+
1214
+ /**
1215
+ * @brief Modifies the first parameter such that an entry at index \f$ (g,p) \f$, with
1216
+ * \f$ 0 \leq g \leq num_generators \f$ and \f$ 0 \leq p \leq num_parameters \f$ is equal to \f$ f(g,p) * r(p) \f$
1217
+ * if \f$ p < length_r \f$ and to \f$ f(g,p) \f$ otherwise.
1218
+ *
1219
+ * Used conventions:
1220
+ * - \f$ inf * 0 = NaN \f$,
1221
+ * - \f$ 0 * inf = NaN \f$,
1222
+ * - \f$ -inf * 0 = NaN \f$,
1223
+ * - \f$ 0 * (-inf) = NaN \f$,
1224
+ * - \f$ NaN * b = NaN \f$,
1225
+ * - \f$ a * NaN = NaN \f$.
1226
+ *
1227
+ * All NaN values are represented by `std::numeric_limits<T>::quiet_NaN()` independently if
1228
+ * `std::numeric_limits<T>::has_quiet_NaN` is true or not.
1229
+ *
1230
+ * @tparam ValueRange Range with a begin() and end() method.
1231
+ * @param f First element of the multiplication.
1232
+ * @param r Second element of the multiplication.
1233
+ */
1234
+ template <class ValueRange, class = std::enable_if_t<RangeTraits<ValueRange>::has_begin> >
1235
+ friend Multi_parameter_filtration &operator*=(Multi_parameter_filtration &f, const ValueRange &r)
1236
+ {
1237
+ f._apply_operation(r, [](T &valF, const T &valR) { _multiply(valF, valR); });
1238
+ return f;
1239
+ }
1240
+
1241
+ /**
1242
+ * @brief Modifies the first parameter such that an entry at index \f$ (g,p) \f$ is equal to \f$ f(g,p) * val \f$.
1243
+ *
1244
+ * Used conventions:
1245
+ * - \f$ inf * 0 = NaN \f$,
1246
+ * - \f$ 0 * inf = NaN \f$,
1247
+ * - \f$ -inf * 0 = NaN \f$,
1248
+ * - \f$ 0 * (-inf) = NaN \f$,
1249
+ * - \f$ NaN * b = NaN \f$,
1250
+ * - \f$ a * NaN = NaN \f$.
1251
+ *
1252
+ * All NaN values are represented by `std::numeric_limits<T>::quiet_NaN()` independently if
1253
+ * `std::numeric_limits<T>::has_quiet_NaN` is true or not.
1254
+ *
1255
+ * @param f First element of the multiplication.
1256
+ * @param val Second element of the multiplication.
1257
+ */
1258
+ friend Multi_parameter_filtration &operator*=(Multi_parameter_filtration &f, const T &val)
1259
+ {
1260
+ f._apply_operation(val, [](T &valF, const T &valR) { _multiply(valF, valR); });
1261
+ return f;
1262
+ }
1263
+
1264
+ // division
1265
+ /**
1266
+ * @brief Returns a filtration value such that an entry at index \f$ (g,p) \f$, with
1267
+ * \f$ 0 \leq g \leq num_generators \f$ and \f$ 0 \leq p \leq num_parameters \f$ is equal to \f$ f(g,p) / r(p) \f$
1268
+ * if \f$ p < length_r \f$ and to \f$ f(g,p) \f$ otherwise.
1269
+ *
1270
+ * Used conventions:
1271
+ * - \f$ a / 0 = NaN \f$,
1272
+ * - \f$ inf / inf = NaN \f$,
1273
+ * - \f$ -inf / inf = NaN \f$,
1274
+ * - \f$ inf / -inf = NaN \f$,
1275
+ * - \f$ -inf / -inf = NaN \f$,
1276
+ * - \f$ NaN / b = NaN \f$,
1277
+ * - \f$ a / NaN = NaN \f$,
1278
+ * - \f$ a / inf = 0 \f$,
1279
+ * - \f$ a / -inf = 0 \f$.
1280
+ *
1281
+ * All NaN values are represented by `std::numeric_limits<T>::quiet_NaN()` independently if
1282
+ * `std::numeric_limits<T>::has_quiet_NaN` is true or not.
1283
+ *
1284
+ * @tparam ValueRange Range with a begin() and end() method.
1285
+ * @param f First element of the division.
1286
+ * @param r Second element of the division.
1287
+ */
1288
+ template <class ValueRange, class = std::enable_if_t<RangeTraits<ValueRange>::has_begin> >
1289
+ friend Multi_parameter_filtration operator/(Multi_parameter_filtration f, const ValueRange &r)
1290
+ {
1291
+ f /= r;
1292
+ return f;
1293
+ }
1294
+
1295
+ /**
1296
+ * @brief Returns a filtration value such that an entry at index \f$ (g,p) \f$, with
1297
+ * \f$ 0 \leq g \leq num_generators \f$ and \f$ 0 \leq p \leq num_parameters \f$ is equal to \f$ r(p) / f(g,p) \f$
1298
+ * if \f$ p < length_r \f$ and to \f$ f(g,p) \f$ otherwise.
1299
+ *
1300
+ * Used conventions:
1301
+ * - \f$ a / 0 = NaN \f$,
1302
+ * - \f$ inf / inf = NaN \f$,
1303
+ * - \f$ -inf / inf = NaN \f$,
1304
+ * - \f$ inf / -inf = NaN \f$,
1305
+ * - \f$ -inf / -inf = NaN \f$,
1306
+ * - \f$ NaN / b = NaN \f$,
1307
+ * - \f$ a / NaN = NaN \f$,
1308
+ * - \f$ a / inf = 0 \f$,
1309
+ * - \f$ a / -inf = 0 \f$.
1310
+ *
1311
+ * All NaN values are represented by `std::numeric_limits<T>::quiet_NaN()` independently if
1312
+ * `std::numeric_limits<T>::has_quiet_NaN` is true or not.
1313
+ *
1314
+ * @tparam ValueRange Range with a begin() and end() method.
1315
+ * @param r First element of the division.
1316
+ * @param f Second element of the division.
1317
+ */
1318
+ template <class ValueRange,
1319
+ class = std::enable_if_t<RangeTraits<ValueRange>::has_begin &&
1320
+ !std::is_same_v<ValueRange, Multi_parameter_filtration> > >
1321
+ friend Multi_parameter_filtration operator/(const ValueRange &r, Multi_parameter_filtration f)
1322
+ {
1323
+ f._apply_operation(r, [](T &valF, const T &valR) {
1324
+ T tmp = valF;
1325
+ valF = valR;
1326
+ _divide(valF, tmp);
1327
+ });
1328
+ return f;
1329
+ }
1330
+
1331
+ /**
1332
+ * @brief Returns a filtration value such that an entry at index \f$ (g,p) \f$ is equal to \f$ f(g,p) / val \f$.
1333
+ *
1334
+ * Used conventions:
1335
+ * - \f$ a / 0 = NaN \f$,
1336
+ * - \f$ inf / inf = NaN \f$,
1337
+ * - \f$ -inf / inf = NaN \f$,
1338
+ * - \f$ inf / -inf = NaN \f$,
1339
+ * - \f$ -inf / -inf = NaN \f$,
1340
+ * - \f$ NaN / b = NaN \f$,
1341
+ * - \f$ a / NaN = NaN \f$,
1342
+ * - \f$ a / inf = 0 \f$,
1343
+ * - \f$ a / -inf = 0 \f$.
1344
+ *
1345
+ * All NaN values are represented by `std::numeric_limits<T>::quiet_NaN()` independently if
1346
+ * `std::numeric_limits<T>::has_quiet_NaN` is true or not.
1347
+ *
1348
+ * @param f First element of the division.
1349
+ * @param val Second element of the division.
1350
+ */
1351
+ friend Multi_parameter_filtration operator/(Multi_parameter_filtration f, const T &val)
1352
+ {
1353
+ f /= val;
1354
+ return f;
1355
+ }
1356
+
1357
+ /**
1358
+ * @brief Returns a filtration value such that an entry at index \f$ (g,p) \f$ is equal to \f$ val / f(g,p) \f$.
1359
+ *
1360
+ * Used conventions:
1361
+ * - \f$ a / 0 = NaN \f$,
1362
+ * - \f$ inf / inf = NaN \f$,
1363
+ * - \f$ -inf / inf = NaN \f$,
1364
+ * - \f$ inf / -inf = NaN \f$,
1365
+ * - \f$ -inf / -inf = NaN \f$,
1366
+ * - \f$ NaN / b = NaN \f$,
1367
+ * - \f$ a / NaN = NaN \f$,
1368
+ * - \f$ a / inf = 0 \f$,
1369
+ * - \f$ a / -inf = 0 \f$.
1370
+ *
1371
+ * All NaN values are represented by `std::numeric_limits<T>::quiet_NaN()` independently if
1372
+ * `std::numeric_limits<T>::has_quiet_NaN` is true or not.
1373
+ *
1374
+ * @param val First element of the division.
1375
+ * @param f Second element of the division.
1376
+ */
1377
+ friend Multi_parameter_filtration operator/(const T &val, Multi_parameter_filtration f)
1378
+ {
1379
+ f._apply_operation(val, [](T &valF, const T &valR) {
1380
+ T tmp = valF;
1381
+ valF = valR;
1382
+ _divide(valF, tmp);
1383
+ });
1384
+ return f;
1385
+ }
1386
+
1387
+ /**
1388
+ * @brief Modifies the first parameter such that an entry at index \f$ (g,p) \f$, with
1389
+ * \f$ 0 \leq g \leq num_generators \f$ and \f$ 0 \leq p \leq num_parameters \f$ is equal to \f$ f(g,p) / r(p) \f$
1390
+ * if \f$ p < length_r \f$ and to \f$ f(g,p) \f$ otherwise.
1391
+ *
1392
+ * Used conventions:
1393
+ * - \f$ a / 0 = NaN \f$,
1394
+ * - \f$ inf / inf = NaN \f$,
1395
+ * - \f$ -inf / inf = NaN \f$,
1396
+ * - \f$ inf / -inf = NaN \f$,
1397
+ * - \f$ -inf / -inf = NaN \f$,
1398
+ * - \f$ NaN / b = NaN \f$,
1399
+ * - \f$ a / NaN = NaN \f$,
1400
+ * - \f$ a / inf = 0 \f$,
1401
+ * - \f$ a / -inf = 0 \f$.
1402
+ *
1403
+ * All NaN values are represented by `std::numeric_limits<T>::quiet_NaN()` independently if
1404
+ * `std::numeric_limits<T>::has_quiet_NaN` is true or not.
1405
+ *
1406
+ * @tparam ValueRange Range with a begin() and end() method.
1407
+ * @param f First element of the division.
1408
+ * @param r Second element of the division.
1409
+ */
1410
+ template <class ValueRange, class = std::enable_if_t<RangeTraits<ValueRange>::has_begin> >
1411
+ friend Multi_parameter_filtration &operator/=(Multi_parameter_filtration &f, const ValueRange &r)
1412
+ {
1413
+ f._apply_operation(r, [](T &valF, const T &valR) { _divide(valF, valR); });
1414
+ return f;
1415
+ }
1416
+
1417
+ /**
1418
+ * @brief Modifies the first parameter such that an entry at index \f$ (g,p) \f$ is equal to \f$ f(g,p) / val \f$.
1419
+ *
1420
+ * Used conventions:
1421
+ * - \f$ a / 0 = NaN \f$,
1422
+ * - \f$ inf / inf = NaN \f$,
1423
+ * - \f$ -inf / inf = NaN \f$,
1424
+ * - \f$ inf / -inf = NaN \f$,
1425
+ * - \f$ -inf / -inf = NaN \f$,
1426
+ * - \f$ NaN / b = NaN \f$,
1427
+ * - \f$ a / NaN = NaN \f$,
1428
+ * - \f$ a / inf = 0 \f$,
1429
+ * - \f$ a / -inf = 0 \f$.
1430
+ *
1431
+ * All NaN values are represented by `std::numeric_limits<T>::quiet_NaN()` independently if
1432
+ * `std::numeric_limits<T>::has_quiet_NaN` is true or not.
1433
+ *
1434
+ * @param f First element of the division.
1435
+ * @param val Second element of the division.
1436
+ */
1437
+ friend Multi_parameter_filtration &operator/=(Multi_parameter_filtration &f, const T &val)
1438
+ {
1439
+ f._apply_operation(val, [](T &valF, const T &valR) { _divide(valF, valR); });
1440
+ return f;
1441
+ }
1442
+
1443
+ // MODIFIERS
1444
+
1445
+ /**
1446
+ * @brief Sets the number of generators. If there were less generators before, new generators with default values are
1447
+ * constructed. If there were more generators before, the exceed of generators is destroyed (any generator with index
1448
+ * higher or equal to @p g to be more precise). If @p g is zero, the methods does nothing.
1449
+ *
1450
+ * Fails to compile if `Ensure1Criticality` is true.
1451
+ *
1452
+ * @warning All new generators will be set to infinity (`Co` is true) or -infinity (`Co` is false). That is, the new
1453
+ * filtration value is not minimal anymore. Make sure to fill them with real generators or to remove them before
1454
+ * using other methods.
1455
+ *
1456
+ * @warning Be sure to call @ref simplify if necessary after initializing all the generators. Most methods will have
1457
+ * an undefined behaviour if the set of generators is not minimal or sorted.
1458
+ *
1459
+ * @param g New number of generators.
1460
+ */
1461
+ void set_num_generators(size_type g)
1462
+ {
1463
+ static_assert(!Ensure1Criticality, "Number of generators cannot be set for a 1-critical only filtration value.");
1464
+
1465
+ if (g == 0) return;
1466
+ generators_.resize(g * num_parameters(), _get_default_value());
1467
+ generator_view_.update_data(generators_.data()); // in case it was relocated
1468
+ generator_view_.update_extent(0, g);
1469
+ }
1470
+
1471
+ /**
1472
+ * @brief Adds the given generator to the filtration value such that the set remains minimal and sorted.
1473
+ * It is therefore possible that the generator is ignored if it does not generated any new lifetime or that
1474
+ * old generators disappear if they are overshadowed by the new one.
1475
+ *
1476
+ * @tparam GeneratorRange Range of elements convertible to `T`. Must have a begin(), end() method and the iterator
1477
+ * type should satisfy the requirements of the standard `LegacyForwardIterator`.
1478
+ * @param x New generator to add. Has to have the same number of parameters than @ref num_parameters().
1479
+ * @return true If and only if the generator is actually added to the set of generators.
1480
+ * @return false Otherwise.
1481
+ */
1482
+ template <class GeneratorRange = std::initializer_list<T>,
1483
+ class = std::enable_if_t<RangeTraits<GeneratorRange>::has_begin> >
1484
+ bool add_generator(const GeneratorRange &x)
1485
+ {
1486
+ return add_generator(x.begin(), x.end());
1487
+ }
1488
+
1489
+ /**
1490
+ * @brief Adds the given generator to the filtration value such that the set remains minimal and sorted.
1491
+ * It is therefore possible that the generator is ignored if it does not generated any new lifetime or that
1492
+ * old generators disappear if they are overshadowed by the new one.
1493
+ *
1494
+ * @tparam Iterator Iterator class satisfying the requirements of the standard `LegacyForwardIterator`.
1495
+ * The dereferenced type has to be convertible to `T`.
1496
+ * @param genStart Iterator pointing to the begining of the range.
1497
+ * @param genEnd Iterator pointing to the end of the range.
1498
+ * @return true If and only if the generator is actually added to the set of generators.
1499
+ * @return false Otherwise.
1500
+ */
1501
+ template <class Iterator>
1502
+ bool add_generator(Iterator genStart, Iterator genEnd)
1503
+ {
1504
+ GUDHI_CHECK(std::distance(genStart, genEnd) == static_cast<int>(num_parameters()),
1505
+ std::invalid_argument("Wrong range size. Should correspond to the number of parameters."));
1506
+
1507
+ const int newIndex = -1;
1508
+
1509
+ std::size_t end = num_generators();
1510
+ std::vector<int> indices(end);
1511
+ std::iota(indices.begin(), indices.end(), 0);
1512
+
1513
+ if (_generator_can_be_added(genStart, 0, end, indices)) {
1514
+ indices.resize(end);
1515
+ indices.push_back(newIndex);
1516
+ _build_from(indices, newIndex, genStart, genEnd);
1517
+ if constexpr (Ensure1Criticality) {
1518
+ if (generator_view_.extent(0) != 1)
1519
+ throw std::logic_error("Multiparameter filtration value is not 1-critical anymore.");
1520
+ }
1521
+ return true;
1522
+ }
1523
+
1524
+ return false;
1525
+ }
1526
+
1527
+ /**
1528
+ * @brief Adds the given generator to the filtration value without any verifications or simplifications at the end
1529
+ * of the set.
1530
+ *
1531
+ * Fails to compile if `Ensure1Criticality` is true.
1532
+ *
1533
+ * @warning If the resulting set of generators is not minimal or sorted after modification, some methods will have an
1534
+ * undefined behaviour. Be sure to call @ref simplify() before using them.
1535
+ *
1536
+ * @tparam GeneratorRange Range of elements convertible to `T`. Must have a begin(), end() and size() method.
1537
+ * @param x New generator to add. Must have the same number of parameters than @ref num_parameters().
1538
+ */
1539
+ template <class GeneratorRange = std::initializer_list<T>,
1540
+ class = std::enable_if_t<RangeTraits<GeneratorRange>::has_begin> >
1541
+ void add_guaranteed_generator(const GeneratorRange &x)
1542
+ {
1543
+ static_assert(!Ensure1Criticality, "Cannot add additional generator to a 1-critical only filtration value.");
1544
+
1545
+ GUDHI_CHECK(x.size() == num_parameters(),
1546
+ std::invalid_argument("Wrong range size. Should correspond to the number of parameters."));
1547
+
1548
+ generators_.insert(generators_.end(), x.begin(), x.end());
1549
+ generator_view_.update_data(generators_.data()); // in case it was relocated
1550
+ generator_view_.update_extent(0, num_generators() + 1);
1551
+ }
1552
+
1553
+ /**
1554
+ * @brief Simplifies the current set of generators such that it becomes minimal. Also orders it in increasing
1555
+ * lexicographical order. Only necessary if generators were added "by hand" without verification either trough the
1556
+ * constructor or with @ref add_guaranteed_generator "", etc.
1557
+ */
1558
+ void simplify()
1559
+ {
1560
+ if constexpr (Ensure1Criticality) {
1561
+ return;
1562
+ } else {
1563
+ std::size_t end = 0;
1564
+ std::vector<int> indices(num_generators());
1565
+ std::iota(indices.begin(), indices.end(), 0);
1566
+
1567
+ for (std::size_t curr = 0; curr < num_generators(); ++curr) {
1568
+ if (_generator_can_be_added(
1569
+ generators_.begin() + generator_view_.mapping()(indices[curr], 0), 0, end, indices)) {
1570
+ std::swap(indices[end], indices[curr]);
1571
+ ++end;
1572
+ }
1573
+ }
1574
+
1575
+ indices.resize(end);
1576
+ _build_from(indices);
1577
+ }
1578
+ }
1579
+
1580
+ /**
1581
+ * @brief Removes all empty generators from the filtration value. If @p include_infinities is true, it also
1582
+ * removes the generators at infinity or minus infinity. As empty generators are not possible (except if number of
1583
+ * parameters is 0), the method does nothing except sorting the set of generators if @p include_infinities is false.
1584
+ * Exists mostly for interface purposes.
1585
+ * If the set of generators is empty after removals, it is set to minus infinity if `Co` is false or to infinity
1586
+ * if `Co` is true.
1587
+ *
1588
+ * @warning If the resulting set of generators is not minimal after the removals/sorting, some methods will have an
1589
+ * undefined behaviour. Be sure to call @ref simplify() before using them.
1590
+ *
1591
+ * @param include_infinities If true, removes also infinity values.
1592
+ */
1593
+ void remove_empty_generators(bool include_infinities = false)
1594
+ {
1595
+ // TODO: verify if this really makes a differences in the 1-critical case, otherwise just keep the general case
1596
+ if constexpr (Ensure1Criticality) {
1597
+ if (!include_infinities) return;
1598
+ bool allNaN = true, allInf = true;
1599
+ bool toEmpty = true;
1600
+ for (size_type p = 0; p < num_parameters() && toEmpty; ++p) {
1601
+ if (!_is_nan(generators_[p])) allNaN = false;
1602
+ if constexpr (Co) {
1603
+ if (generators_[p] != T_m_inf) allInf = false;
1604
+ } else {
1605
+ if (generators_[p] != T_inf) allInf = false;
1606
+ }
1607
+ if (!allNaN && !allInf) toEmpty = false;
1608
+ }
1609
+ if (toEmpty) generators_.clear();
1610
+ } else {
1611
+ std::vector<int> indices;
1612
+ indices.reserve(num_generators());
1613
+ for (int i = 0; i < static_cast<int>(num_generators()); ++i) {
1614
+ if (!include_infinities || _is_finite(i)) indices.push_back(i);
1615
+ }
1616
+ _build_from(indices); // sorts
1617
+ }
1618
+
1619
+ if (generators_.empty()) {
1620
+ generators_.resize(num_parameters(), _get_default_value());
1621
+ generator_view_.update_data(generators_.data()); // in case it was relocated
1622
+ generator_view_.update_extent(0, 1);
1623
+ }
1624
+ }
1625
+
1626
+ /**
1627
+ * @brief Sets each generator of the filtration value to the least common upper bound between it and the given value.
1628
+ *
1629
+ * More formally, it pushes the current generator to the cone \f$ \{ y \in \mathbb R^n : y \ge x \} \f$
1630
+ * originating in \f$ x \f$. The resulting value corresponds to the intersection of both
1631
+ * cones: \f$ \mathrm{this} = \min \{ y \in \mathbb R^n : y \ge this \} \cap \{ y \in \mathbb R^n : y \ge x \} \f$.
1632
+ *
1633
+ * @tparam GeneratorRange Range of elements convertible to `T`. Must have a begin(), end() and size() method.
1634
+ * @param x Range towards to push. Has to have as many elements than @ref num_parameters().
1635
+ * @param exclude_infinite_values If true, values at infinity or minus infinity are not affected.
1636
+ * @return true If the filtration value was actually modified.
1637
+ * @return false Otherwise.
1638
+ */
1639
+ template <class GeneratorRange = std::initializer_list<value_type>,
1640
+ class = std::enable_if_t<RangeTraits<GeneratorRange>::has_begin> >
1641
+ bool push_to_least_common_upper_bound(const GeneratorRange &x, bool exclude_infinite_values = false)
1642
+ {
1643
+ GUDHI_CHECK(x.size() == num_parameters(),
1644
+ std::invalid_argument("Wrong range size. Should correspond to the number of parameters."));
1645
+
1646
+ bool xIsInf = true, xIsMinusInf = true, xIsNaN = true;
1647
+ bool thisIsInf = true, thisIsMinusInf = true, thisIsNaN = true;
1648
+
1649
+ // if one is not finite, we can avoid the heavy simplification process
1650
+ _get_infinity_statuses(generator_view_, x, thisIsInf, thisIsMinusInf, thisIsNaN, xIsInf, xIsMinusInf, xIsNaN);
1651
+
1652
+ if (thisIsInf || thisIsNaN || xIsNaN || xIsMinusInf || (xIsInf && exclude_infinite_values)) return false;
1653
+
1654
+ if (xIsInf || thisIsMinusInf) {
1655
+ generators_ = Underlying_container(x.begin(), x.end());
1656
+ generator_view_.update_data(generators_.data()); // in case it was relocated
1657
+ generator_view_.update_extent(0, 1);
1658
+ return true;
1659
+ }
1660
+
1661
+ bool modified = false;
1662
+
1663
+ auto push_generator_value = [&](T &val, T valX) {
1664
+ if (exclude_infinite_values && (valX == T_inf || valX == T_m_inf)) return;
1665
+ modified |= val < valX;
1666
+ val = valX > val ? valX : val;
1667
+ };
1668
+
1669
+ // TODO: verify if this really makes a differences in the 1-critical case, otherwise just keep the general case
1670
+ if constexpr (Ensure1Criticality) {
1671
+ auto it = x.begin();
1672
+ for (size_type p = 0; p < num_parameters(); ++p) {
1673
+ push_generator_value(generators_[p], *it);
1674
+ ++it;
1675
+ }
1676
+ } else {
1677
+ for (size_type g = 0; g < num_generators(); ++g) {
1678
+ auto it = x.begin();
1679
+ for (size_type p = 0; p < num_parameters(); ++p) {
1680
+ push_generator_value(generator_view_(g, p), *it);
1681
+ ++it;
1682
+ }
1683
+ }
1684
+
1685
+ if (modified && num_generators() > 1) simplify();
1686
+ }
1687
+
1688
+ return modified;
1689
+ }
1690
+
1691
+ /**
1692
+ * @brief Sets each generator of the filtration value to the greatest common lower bound between it and the given
1693
+ * value.
1694
+ *
1695
+ * More formally, it pulls the current generator to the cone \f$ \{ y \in \mathbb R^n : y \le x \} \f$
1696
+ * originating in \f$ x \f$. The resulting value corresponds to the intersection of both
1697
+ * cones: \f$ \mathrm{this} = \min \{ y \in \mathbb R^n : y \le this \} \cap \{ y \in \mathbb R^n : y \le x \} \f$.
1698
+ *
1699
+ * @tparam GeneratorRange Range of elements convertible to `T`. Must have a begin(), end() and size() method.
1700
+ * @param x Range towards to pull. Has to have as many elements than @ref num_parameters().
1701
+ * @param exclude_infinite_values If true, values at infinity or minus infinity are not affected.
1702
+ * @return true If the filtration value was actually modified.
1703
+ * @return false Otherwise.
1704
+ */
1705
+ template <class GeneratorRange = std::initializer_list<value_type>,
1706
+ class = std::enable_if_t<RangeTraits<GeneratorRange>::has_begin> >
1707
+ bool pull_to_greatest_common_lower_bound(const GeneratorRange &x, bool exclude_infinite_values = false)
1708
+ {
1709
+ GUDHI_CHECK(x.size() == num_parameters(),
1710
+ std::invalid_argument("Wrong range size. Should correspond to the number of parameters."));
1711
+
1712
+ bool xIsInf = true, xIsMinusInf = true, xIsNaN = true;
1713
+ bool thisIsInf = true, thisIsMinusInf = true, thisIsNaN = true;
1714
+
1715
+ // if one is not finite, we can avoid the heavy simplification process
1716
+ _get_infinity_statuses(generator_view_, x, thisIsInf, thisIsMinusInf, thisIsNaN, xIsInf, xIsMinusInf, xIsNaN);
1717
+
1718
+ if (xIsInf || thisIsNaN || xIsNaN || thisIsMinusInf || (xIsMinusInf && exclude_infinite_values)) return false;
1719
+
1720
+ if (thisIsInf || xIsMinusInf) {
1721
+ generators_ = Underlying_container(x.begin(), x.end());
1722
+ generator_view_.update_data(generators_.data()); // in case it was relocated
1723
+ generator_view_.update_extent(0, 1);
1724
+ return true;
1725
+ }
1726
+
1727
+ bool modified = false;
1728
+
1729
+ auto pull_generator_value = [&](T &val, T valX) {
1730
+ if (exclude_infinite_values && (valX == T_inf || valX == T_m_inf)) return;
1731
+ modified |= val > valX;
1732
+ val = valX < val ? valX : val;
1733
+ };
1734
+
1735
+ // TODO: verify if this really makes a differences in the 1-critical case, otherwise just keep the general case
1736
+ if constexpr (Ensure1Criticality) {
1737
+ auto it = x.begin();
1738
+ for (size_type p = 0; p < num_parameters(); ++p) {
1739
+ pull_generator_value(generators_[p], *it);
1740
+ ++it;
1741
+ }
1742
+ } else {
1743
+ for (size_type g = 0; g < num_generators(); ++g) {
1744
+ auto it = x.begin();
1745
+ for (size_type p = 0; p < num_parameters(); ++p) {
1746
+ pull_generator_value(generator_view_(g, p), *it);
1747
+ ++it;
1748
+ }
1749
+ }
1750
+
1751
+ if (modified && num_generators() > 1) simplify();
1752
+ }
1753
+
1754
+ return modified;
1755
+ }
1756
+
1757
+ /**
1758
+ * @brief Projects the filtration value into the given grid. If @p coordinate is false, the entries are set to
1759
+ * the nearest upper bound value with the same parameter in the grid. Otherwise, the entries are set to the indices
1760
+ * of those nearest upper bound values.
1761
+ * The grid has to be represented as a vector of ordered ranges of values convertible into `T`. An index
1762
+ * \f$ i \f$ of the vector corresponds to the same parameter as the index \f$ i \f$ in a generator of the filtration
1763
+ * value. The ranges correspond to the possible values of the parameters, ordered by increasing value, forming
1764
+ * therefore all together a 2D grid.
1765
+ *
1766
+ * @tparam OneDimArray A range of values convertible into `T` ordered by increasing value. Has to implement
1767
+ * a begin, end and operator[] method.
1768
+ * @param grid Vector of @p OneDimArray with size at least number of filtration parameters.
1769
+ * @param coordinate If true, the values are set to the coordinates of the projection in the grid. If false,
1770
+ * the values are set to the values at the coordinates of the projection.
1771
+ */
1772
+ template <typename OneDimArray>
1773
+ void project_onto_grid(const std::vector<OneDimArray> &grid, bool coordinate = true)
1774
+ {
1775
+ GUDHI_CHECK(
1776
+ grid.size() >= num_parameters(),
1777
+ std::invalid_argument("The grid should not be smaller than the number of parameters in the filtration value."));
1778
+
1779
+ auto project_generator_value = [&](T &val, const OneDimArray &filtration) {
1780
+ auto v = static_cast<typename OneDimArray::value_type>(val);
1781
+ auto d = std::distance(filtration.begin(), std::lower_bound(filtration.begin(), filtration.end(), v));
1782
+ if (d != 0 && std::abs(v - filtration[d]) > std::abs(v - filtration[d - 1])) {
1783
+ --d;
1784
+ }
1785
+ val = coordinate ? static_cast<T>(d) : static_cast<T>(filtration[d]);
1786
+ };
1787
+
1788
+ // TODO: verify if this really makes a differences in the 1-critical case, otherwise just keep the general case
1789
+ if constexpr (Ensure1Criticality) {
1790
+ #ifdef GUDHI_USE_TBB
1791
+ tbb::parallel_for(size_type(0), num_parameters(), [&](size_type p){
1792
+ project_generator_value(generators_[p], grid[p]);
1793
+ });
1794
+ #else
1795
+ for (size_type p = 0; p < num_parameters(); ++p) {
1796
+ project_generator_value(generators_[p], grid[p]);
1797
+ }
1798
+ #endif
1799
+ } else {
1800
+
1801
+ #ifdef GUDHI_USE_TBB
1802
+
1803
+ tbb::parallel_for(size_type(0), num_generators(), [&](size_type g) {
1804
+ tbb::parallel_for(size_type(0), num_parameters(), [&](size_type p) {
1805
+ project_generator_value(generator_view_(g, p), grid[p]);
1806
+ });
1807
+ });
1808
+ #else
1809
+ for (size_type g = 0; g < num_generators(); ++g) {
1810
+ for (size_type p = 0; p < num_parameters(); ++p) {
1811
+ project_generator_value(generator_view_(g, p), grid[p]);
1812
+ }
1813
+ }
1814
+ #endif
1815
+
1816
+ if (!coordinate && num_generators() > 1) simplify();
1817
+ }
1818
+ }
1819
+
1820
+ // FONCTIONNALITIES
1821
+
1822
+ /**
1823
+ * @brief Returns a generator with the minimal values of all parameters in any generator of the given filtration
1824
+ * value. That is, the greatest lower bound of all generators.
1825
+ */
1826
+ friend Multi_parameter_filtration factorize_below(const Multi_parameter_filtration &f)
1827
+ {
1828
+ if (f.num_generators() <= 1) return f;
1829
+
1830
+ bool nan = true;
1831
+ Underlying_container result(f.num_parameters(), T_inf);
1832
+ for (size_type p = 0; p < f.num_parameters(); ++p) {
1833
+ for (size_type g = 0; g < f.num_generators(); ++g) {
1834
+ T val = f(g, p);
1835
+ if (!_is_nan(val)) {
1836
+ nan = false;
1837
+ result[p] = val < result[p] ? val : result[p];
1838
+ }
1839
+ }
1840
+ if (nan)
1841
+ result[p] = std::numeric_limits<T>::quiet_NaN();
1842
+ else
1843
+ nan = true;
1844
+ }
1845
+ return Multi_parameter_filtration(std::move(result), f.num_parameters());
1846
+ }
1847
+
1848
+ /**
1849
+ * @brief Returns a generator with the maximal values of all parameters in any generator of the given filtration
1850
+ * value. That is, the least upper bound of all generators.
1851
+ */
1852
+ friend Multi_parameter_filtration factorize_above(const Multi_parameter_filtration &f)
1853
+ {
1854
+ if (f.num_generators() <= 1) return f;
1855
+
1856
+ bool nan = true;
1857
+ Underlying_container result(f.num_parameters(), T_m_inf);
1858
+ for (size_type p = 0; p < f.num_parameters(); ++p) {
1859
+ for (size_type g = 0; g < f.num_generators(); ++g) {
1860
+ T val = f(g, p);
1861
+ if (!_is_nan(val)) {
1862
+ nan = false;
1863
+ result[p] = val > result[p] ? val : result[p];
1864
+ }
1865
+ }
1866
+ if (nan)
1867
+ result[p] = std::numeric_limits<T>::quiet_NaN();
1868
+ else
1869
+ nan = true;
1870
+ }
1871
+ return Multi_parameter_filtration(std::move(result), f.num_parameters());
1872
+ }
1873
+
1874
+ /**
1875
+ * @brief Computes the smallest (resp. the greatest if `Co` is true) scalar product of the all generators with the
1876
+ * given vector.
1877
+ *
1878
+ * @tparam U Arithmetic type of the result. Default value: `T`.
1879
+ * @param f Filtration value.
1880
+ * @param x Vector of coefficients.
1881
+ * @return Scalar product of @p f with @p x.
1882
+ */
1883
+ template <typename U = T>
1884
+ friend U compute_linear_projection(const Multi_parameter_filtration &f, const std::vector<U> &x)
1885
+ {
1886
+ auto project_generator = [&](size_type g) -> U {
1887
+ U projection = 0;
1888
+ std::size_t size = std::min(x.size(), f.num_parameters());
1889
+ for (std::size_t i = 0; i < size; i++) projection += x[i] * static_cast<U>(f(g, i));
1890
+ return projection;
1891
+ };
1892
+
1893
+ if (f.num_generators() == 1) return project_generator(0);
1894
+
1895
+ if constexpr (Co) {
1896
+ U projection = std::numeric_limits<U>::lowest();
1897
+ for (size_type g = 0; g < f.num_generators(); ++g) {
1898
+ // Order in the max important to spread possible NaNs
1899
+ projection = std::max(project_generator(g), projection);
1900
+ }
1901
+ return projection;
1902
+ } else {
1903
+ U projection = std::numeric_limits<U>::max();
1904
+ for (size_type g = 0; g < f.num_generators(); ++g) {
1905
+ // Order in the min important to spread possible NaNs
1906
+ projection = std::min(project_generator(g), projection);
1907
+ }
1908
+ return projection;
1909
+ }
1910
+ }
1911
+
1912
+ /**
1913
+ * @brief Computes the euclidean distance from the first parameter to the second parameter as the minimum of
1914
+ * all Euclidean distances between a generator of @p f and a generator of @p other.
1915
+ *
1916
+ * @param f Source filtration value.
1917
+ * @param other Target filtration value.
1918
+ * @return Euclidean distance between @p f and @p other.
1919
+ */
1920
+ template <typename U = T>
1921
+ friend U compute_euclidean_distance_to(const Multi_parameter_filtration &f, const Multi_parameter_filtration &other)
1922
+ {
1923
+ GUDHI_CHECK(f.num_parameters() == other.num_parameters(),
1924
+ std::invalid_argument("We cannot compute the distance between two points of different dimensions."));
1925
+
1926
+ // TODO: verify if this really makes a differences in the 1-critical case, otherwise just keep the general case
1927
+ if constexpr (Ensure1Criticality) {
1928
+ return _compute_frobenius_norm(f.num_parameters(),
1929
+ [&](size_type p) -> T { return f.generators_[p] - other.generators_[p]; });
1930
+ } else {
1931
+ U res = std::numeric_limits<U>::max();
1932
+ for (size_type g1 = 0; g1 < f.num_generators(); ++g1) {
1933
+ for (size_type g2 = 0; g2 < other.num_generators(); ++g2) {
1934
+ // Euclidean distance as a Frobenius norm with matrix 1 x p and values 'f(g1, p) - other(g2, p)'
1935
+ // Order in the min important to spread possible NaNs
1936
+ res = std::min(
1937
+ _compute_frobenius_norm(f.num_parameters(), [&](size_type p) -> T { return f(g1, p) - other(g2, p); }),
1938
+ res);
1939
+ }
1940
+ }
1941
+ return res;
1942
+ }
1943
+ }
1944
+
1945
+ /**
1946
+ * @brief Computes the norm of the given filtration value.
1947
+ *
1948
+ * The filtration value is seen as a \f$ num_generators x num_parameters \f$ matrix and a standard Frobenius norm
1949
+ * is computed from it: the square root of the sum of the squares of all elements in the matrix.
1950
+ *
1951
+ * @param f Filtration value.
1952
+ * @return The norm of @p f.
1953
+ */
1954
+ template <typename U = T>
1955
+ friend U compute_norm(const Multi_parameter_filtration &f)
1956
+ {
1957
+ // Frobenius norm with matrix g x p based on Euclidean norm
1958
+ return _compute_frobenius_norm(f.num_entries(), [&](size_type i) -> T { return f.generators_[i]; });
1959
+ }
1960
+
1961
+ /**
1962
+ * @brief Computes the coordinates in the given grid, corresponding to the nearest upper bounds of the entries
1963
+ * in the given filtration value.
1964
+ * The grid has to be represented as a vector of vectors of ordered values convertible into `OutValue`. An index
1965
+ * \f$ i \f$ of the vector corresponds to the same parameter as the index \f$ i \f$ in a generator of the filtration
1966
+ * value. The ranges correspond to the possible values of the parameters, ordered by increasing value, forming
1967
+ * therefore all together a 2D grid.
1968
+ *
1969
+ * @tparam OutValue Signed arithmetic type. Default value: std::int32_t.
1970
+ * @tparam U Type which is convertible into `OutValue`.
1971
+ * @param f Filtration value to project.
1972
+ * @param grid Vector of vectors to project into.
1973
+ * @return Filtration value \f$ out \f$ whose entry correspond to the indices of the projected values. That is,
1974
+ * the projection of \f$ f(g,p) \f$ is \f$ grid[p][out(g,p)] \f$.
1975
+ */
1976
+ template <typename OutValue = std::int32_t, typename U = T>
1977
+ friend Multi_parameter_filtration<OutValue, Co, Ensure1Criticality> compute_coordinates_in_grid(
1978
+ Multi_parameter_filtration f,
1979
+ const std::vector<std::vector<U> > &grid)
1980
+ {
1981
+ // TODO: by replicating the code of "project_onto_grid", this could be done with just one copy
1982
+ // instead of two. But it is not clear if it is really worth it, i.e., how much the change in type is really
1983
+ // necessary in the use cases. To see later.
1984
+ f.project_onto_grid(grid);
1985
+ if constexpr (std::is_same_v<OutValue, T>) {
1986
+ return f;
1987
+ } else {
1988
+ return f.as_type<OutValue>();
1989
+ }
1990
+ }
1991
+
1992
+ /**
1993
+ * @brief Computes the values in the given grid corresponding to the coordinates given by the given filtration
1994
+ * value. That is, if \f$ out \f$ is the result, \f$ out(g,p) = grid[p][f(g,p)] \f$. Assumes therefore, that the
1995
+ * values stored in the filtration value corresponds to indices existing in the given grid.
1996
+ *
1997
+ * @tparam U Signed arithmetic type.
1998
+ * @param f Filtration value storing coordinates compatible with `grid`.
1999
+ * @param grid Vector of vector.
2000
+ * @return Filtration value \f$ out \f$ whose entry correspond to \f$ out(g,p) = grid[p][f(g,p)] \f$.
2001
+ */
2002
+ template <typename U>
2003
+ friend Multi_parameter_filtration<U, Co, Ensure1Criticality> evaluate_coordinates_in_grid(
2004
+ const Multi_parameter_filtration &f,
2005
+ const std::vector<std::vector<U> > &grid)
2006
+ {
2007
+ GUDHI_CHECK(grid.size() >= f.num_parameters(),
2008
+ std::invalid_argument(
2009
+ "The size of the grid should correspond to the number of parameters in the filtration value."));
2010
+
2011
+ U grid_inf = Multi_parameter_filtration<U, Co, Ensure1Criticality>::T_inf;
2012
+ std::vector<U> outVec(f.num_entries());
2013
+
2014
+ // TODO: verify if this really makes a differences in the 1-critical case, otherwise just keep the general case
2015
+ if constexpr (Ensure1Criticality) {
2016
+ for (size_type p = 0; p < f.num_parameters(); ++p) {
2017
+ const std::vector<U> &filtration = grid[p];
2018
+ const T &c = f.generators_[p];
2019
+ outVec[p] = (c == T_inf ? grid_inf : filtration[c]);
2020
+ }
2021
+ } else {
2022
+ for (size_type g = 0; g < f.num_generators(); ++g) {
2023
+ for (size_type p = 0; p < f.num_parameters(); ++p) {
2024
+ const std::vector<U> &filtration = grid[p];
2025
+ const T &c = f(g, p);
2026
+ outVec[f.generator_view_.mapping()(g, p)] = (c == T_inf ? grid_inf : filtration[c]);
2027
+ }
2028
+ }
2029
+ }
2030
+
2031
+ Multi_parameter_filtration<U, Co, Ensure1Criticality> out(std::move(outVec), f.num_parameters());
2032
+ if constexpr (!Ensure1Criticality)
2033
+ if (out.num_generators() > 1) out.simplify();
2034
+ return out;
2035
+ }
2036
+
2037
+ // UTILITIES
2038
+
2039
+ /**
2040
+ * @brief Outstream operator.
2041
+ */
2042
+ friend std::ostream &operator<<(std::ostream &stream, const Multi_parameter_filtration &f)
2043
+ {
2044
+ const size_type num_gen = f.num_generators();
2045
+ const size_type num_param = f.num_parameters();
2046
+
2047
+ stream << "( k = " << num_gen << " ) ( p = " << num_param << " ) [ ";
2048
+ for (size_type g = 0; g < num_gen; ++g) {
2049
+ stream << "[";
2050
+ for (size_type p = 0; p < num_param; ++p) {
2051
+ stream << f(g, p);
2052
+ if (p < num_param - 1) stream << ", ";
2053
+ }
2054
+ stream << "]";
2055
+ if (g < num_gen - 1) stream << "; ";
2056
+ }
2057
+ stream << " ]";
2058
+
2059
+ return stream;
2060
+ }
2061
+
2062
+ /**
2063
+ * @brief Instream operator.
2064
+ */
2065
+ friend std::istream &operator>>(std::istream &stream, Multi_parameter_filtration &f)
2066
+ {
2067
+ size_type num_gen;
2068
+ size_type num_param;
2069
+ char delimiter;
2070
+ stream >> delimiter; // (
2071
+ stream >> delimiter; // k
2072
+ stream >> delimiter; // =
2073
+ stream >> num_gen;
2074
+ if (!stream.good()) throw std::invalid_argument("Invalid incoming stream format for Multi_parameter_filtration.");
2075
+ stream >> delimiter; // )
2076
+ stream >> delimiter; // (
2077
+ stream >> delimiter; // p
2078
+ stream >> delimiter; // =
2079
+ stream >> num_param;
2080
+ if (!stream.good()) throw std::invalid_argument("Invalid incoming stream format for Multi_parameter_filtration.");
2081
+ f.generators_.resize(num_gen * num_param);
2082
+ f.generator_view_ = Viewer(f.generators_.data(), num_gen, num_param);
2083
+ stream >> delimiter; // )
2084
+ stream >> delimiter; // [
2085
+ if (delimiter != '[') throw std::invalid_argument("Invalid incoming stream format for Multi_parameter_filtration.");
2086
+ if (num_gen == 0) return stream;
2087
+ for (size_type i = 0; i < num_gen; ++i) {
2088
+ stream >> delimiter; // [
2089
+ for (size_type j = 0; j < num_param; ++j) {
2090
+ f(i, j) = _get_value<T>(stream);
2091
+ if (!stream.good())
2092
+ throw std::invalid_argument("Invalid incoming stream format for Multi_parameter_filtration.");
2093
+ stream >> delimiter; // , or last ]
2094
+ }
2095
+ stream >> delimiter; // ; or last ]
2096
+ }
2097
+ if (delimiter != ']') throw std::invalid_argument("Invalid incoming stream format for Multi_parameter_filtration.");
2098
+
2099
+ return stream;
2100
+ }
2101
+
2102
+ /**
2103
+ * @brief Returns true if and only if the given filtration value is at plus infinity.
2104
+ */
2105
+ friend bool is_positive_infinity(const Multi_parameter_filtration &f)
2106
+ {
2107
+ return f.is_plus_inf();
2108
+ }
2109
+
2110
+ /**
2111
+ * @brief Adds the generators of the second argument to the first argument. If `Ensure1Criticality` is true,
2112
+ * the method assumes that the two filtration values are comparable, that is, that the result of the union is also
2113
+ * 1-critical. A check for this is only done in Debug Mode, as it is costly.
2114
+ *
2115
+ * @param f1 Filtration value to modify.
2116
+ * @param f2 Filtration value to merge with the first one. Should have the same number of parameters than the other.
2117
+ * @return true If the first argument was actually modified.
2118
+ * @return false Otherwise.
2119
+ */
2120
+ friend bool unify_lifetimes(Multi_parameter_filtration &f1, const Multi_parameter_filtration &f2)
2121
+ {
2122
+ GUDHI_CHECK(f1.num_parameters() == f2.num_parameters(),
2123
+ std::invalid_argument("Cannot unify two filtration values with different number of parameters."));
2124
+
2125
+ // TODO: verify if this really makes a differences in the 1-critical case, otherwise just keep the general case
2126
+ // if general case is kept: add (num_gen == 1) test to throw if unification is not 1-critical anymore.
2127
+ if constexpr (Ensure1Criticality) {
2128
+ // WARNING: costly check
2129
+ GUDHI_CHECK(
2130
+ f1 <= f2 || f2 <= f1,
2131
+ std::invalid_argument("When 1-critical only, two non-comparable filtration values cannot be unified."));
2132
+
2133
+ if constexpr (Co) {
2134
+ return f1.push_to_least_common_upper_bound(f2);
2135
+ } else {
2136
+ return f1.pull_to_greatest_common_lower_bound(f2);
2137
+ }
2138
+ } else {
2139
+ bool modified = false;
2140
+ for (size_type g = 0; g < f2.num_generators(); ++g) {
2141
+ auto start = f2.begin();
2142
+ start += g * f2.num_parameters();
2143
+ auto end = start + f2.num_parameters();
2144
+ modified |= f1.add_generator(start, end);
2145
+ }
2146
+ return modified;
2147
+ }
2148
+ }
2149
+
2150
+ /**
2151
+ * @brief Stores in the first argument the origins of the cones in the intersection of the positive
2152
+ * (negative if `Co` is true) cones generated by the two arguments.
2153
+ *
2154
+ * @param f1 First set of cones which will be modified.
2155
+ * @param f2 Second set of cones. Should have the same number of parameters than the first one.
2156
+ * @return true If the first argument was actually modified.
2157
+ * @return false Otherwise.
2158
+ */
2159
+ friend bool intersect_lifetimes(Multi_parameter_filtration &f1, const Multi_parameter_filtration &f2)
2160
+ {
2161
+ GUDHI_CHECK(f1.num_parameters() == f2.num_parameters(),
2162
+ std::invalid_argument("Cannot intersect two filtration values with different number of parameters."));
2163
+
2164
+ if constexpr (Ensure1Criticality) {
2165
+ if constexpr (Co) {
2166
+ return f1.pull_to_greatest_common_lower_bound(f2);
2167
+ } else {
2168
+ return f1.push_to_least_common_upper_bound(f2);
2169
+ }
2170
+ } else {
2171
+ bool f2IsInf = true, f2IsMinusInf = true, f2IsNaN = true;
2172
+ bool f1IsInf = true, f1IsMinusInf = true, f1IsNaN = true;
2173
+
2174
+ // if one is not finite, we can avoid the heavy simplification process
2175
+ _get_infinity_statuses(f1.generator_view_, f2, f1IsInf, f1IsMinusInf, f1IsNaN, f2IsInf, f2IsMinusInf, f2IsNaN);
2176
+
2177
+ if (f1IsNaN || f2IsNaN) return false;
2178
+
2179
+ // inf cases first to avoid costly g1 * g2 check
2180
+ if constexpr (Co) {
2181
+ if (f1IsInf) {
2182
+ if (f2IsInf) return false;
2183
+ f1 = f2;
2184
+ return true;
2185
+ }
2186
+ if (f1IsMinusInf) {
2187
+ return false;
2188
+ }
2189
+ } else {
2190
+ if (f1IsMinusInf) {
2191
+ if (f2IsMinusInf) return false;
2192
+ f1 = f2;
2193
+ return true;
2194
+ }
2195
+ if (f1IsInf) {
2196
+ return false;
2197
+ }
2198
+ }
2199
+
2200
+ const size_type num_param = f1.num_parameters();
2201
+ Multi_parameter_filtration res(num_param, Co ? T_m_inf : T_inf);
2202
+ std::vector<T> newGen(num_param);
2203
+ // TODO: see if the order can be used to avoid g1 * g2 add_generator and
2204
+ // perhaps even to replace add_generator by add_guaranteed_generator
2205
+ for (size_type g1 = 0; g1 < f1.num_generators(); ++g1) {
2206
+ for (size_type g2 = 0; g2 < f2.num_generators(); ++g2) {
2207
+ for (size_type p = 0; p < num_param; ++p) {
2208
+ if constexpr (Co) {
2209
+ newGen[p] = std::min(f1(g1, p), f2(g2, p));
2210
+ } else {
2211
+ newGen[p] = std::max(f1(g1, p), f2(g2, p));
2212
+ }
2213
+ }
2214
+ res.add_generator(newGen);
2215
+ }
2216
+ }
2217
+ swap(f1, res);
2218
+
2219
+ return f1 != res;
2220
+ }
2221
+ }
2222
+
2223
+ /**
2224
+ * @brief Serialize given value into the buffer at given pointer.
2225
+ *
2226
+ * @param value Value to serialize.
2227
+ * @param start Pointer to the start of the space in the buffer where to store the serialization.
2228
+ * @return End position of the serialization in the buffer.
2229
+ */
2230
+ friend char *serialize_value_to_char_buffer(const Multi_parameter_filtration &value, char *start)
2231
+ {
2232
+ const size_type length = value.generators_.size();
2233
+ const size_type num_param = value.num_parameters();
2234
+ const std::size_t arg_size = sizeof(T) * length;
2235
+ const std::size_t type_size = sizeof(size_type);
2236
+ memcpy(start, &num_param, type_size);
2237
+ memcpy(start + type_size, &length, type_size);
2238
+ memcpy(start + (type_size * 2), value.generators_.data(), arg_size);
2239
+ return start + arg_size + (type_size * 2);
2240
+ }
2241
+
2242
+ /**
2243
+ * @brief Deserialize the value from a buffer at given pointer and stores it in given value.
2244
+ *
2245
+ * @param value Value to fill with the deserialized filtration value.
2246
+ * @param start Pointer to the start of the space in the buffer where the serialization is stored.
2247
+ * @return End position of the serialization in the buffer.
2248
+ */
2249
+ friend const char *deserialize_value_from_char_buffer(Multi_parameter_filtration &value, const char *start)
2250
+ {
2251
+ const std::size_t type_size = sizeof(size_type);
2252
+ size_type length;
2253
+ size_type num_param;
2254
+ memcpy(&num_param, start, type_size);
2255
+ memcpy(&length, start + type_size, type_size);
2256
+ std::size_t arg_size = sizeof(T) * length;
2257
+ value.generators_.resize(length);
2258
+ memcpy(value.generators_.data(), start + (type_size * 2), arg_size);
2259
+ value.generator_view_ =
2260
+ Viewer(value.generators_.data(), num_param == 0 ? 0 : value.generators_.size() / num_param, num_param);
2261
+ return start + arg_size + (type_size * 2);
2262
+ }
2263
+
2264
+ /**
2265
+ * @brief Returns the serialization size of the given filtration value.
2266
+ */
2267
+ friend std::size_t get_serialization_size_of(const Multi_parameter_filtration &value)
2268
+ {
2269
+ return (sizeof(size_type) * 2) + (sizeof(T) * value.num_entries());
2270
+ }
2271
+
2272
+ /**
2273
+ * @brief Plus infinity value of an entry of the filtration value.
2274
+ */
2275
+ constexpr static const T T_inf = MF_T_inf<T>;
2276
+
2277
+ /**
2278
+ * @brief Minus infinity value of an entry of the filtration value.
2279
+ */
2280
+ constexpr static const T T_m_inf = MF_T_m_inf<T>;
2281
+
2282
+ private:
2283
+ Underlying_container generators_; /**< Container of the filtration value elements. */
2284
+ Viewer generator_view_; /**< Matrix view of the container. Has to be created after generators_. */
2285
+
2286
+ /**
2287
+ * @brief Default value of an element in the filtration value.
2288
+ */
2289
+ constexpr static T _get_default_value() { return Co ? T_inf : T_m_inf; }
2290
+
2291
+ /**
2292
+ * @brief Verifies if @p b is strictly contained in the positive cone originating in `a`.
2293
+ */
2294
+ static bool _strictly_contains(const Viewer &a, size_type g_a, const Viewer &b, size_type g_b)
2295
+ {
2296
+ bool isSame = true;
2297
+ for (auto i = 0U; i < a.extent(1); ++i) {
2298
+ T a_i, b_i;
2299
+ if constexpr (Co) {
2300
+ a_i = b(g_b, i);
2301
+ b_i = a(g_a, i);
2302
+ } else {
2303
+ a_i = a(g_a, i);
2304
+ b_i = b(g_b, i);
2305
+ }
2306
+ if (a_i > b_i || _is_nan(a_i) || _is_nan(b_i)) return false;
2307
+ if (isSame && a_i != b_i) isSame = false;
2308
+ }
2309
+ return !isSame;
2310
+ }
2311
+
2312
+ /**
2313
+ * @brief Verifies if @p b is contained in the positive cone originating in `a`.
2314
+ */
2315
+ static bool _contains(const Viewer &a, size_type g_a, const Viewer &b, size_type g_b)
2316
+ {
2317
+ for (std::size_t i = 0U; i < a.extent(1); ++i) {
2318
+ T a_i, b_i;
2319
+ if constexpr (Co) {
2320
+ a_i = b(g_b, i);
2321
+ b_i = a(g_a, i);
2322
+ } else {
2323
+ a_i = a(g_a, i);
2324
+ b_i = b(g_b, i);
2325
+ }
2326
+ if (a_i > b_i || (!_is_nan(a_i) && _is_nan(b_i)) || (_is_nan(a_i) && !_is_nan(b_i))) return false;
2327
+ }
2328
+ return true;
2329
+ }
2330
+
2331
+ /**
2332
+ * @brief Verifies if the first element of @p b strictly dominates the first element of `a`.
2333
+ */
2334
+ static bool _first_strictly_dominates(const Viewer &a, size_type g_a, const Viewer &b, size_type g_b)
2335
+ {
2336
+ if constexpr (Co) {
2337
+ return a(g_a, 0) < b(g_b, 0);
2338
+ } else {
2339
+ return a(g_a, 0) > b(g_b, 0);
2340
+ }
2341
+ }
2342
+
2343
+ /**
2344
+ * @brief Verifies if the first element of @p b dominates the first element of `a`.
2345
+ */
2346
+ static bool _first_dominates(const Viewer &a, size_type g_a, const Viewer &b, size_type g_b)
2347
+ {
2348
+ if constexpr (Co) {
2349
+ return a(g_a, 0) <= b(g_b, 0);
2350
+ } else {
2351
+ return a(g_a, 0) >= b(g_b, 0);
2352
+ }
2353
+ }
2354
+
2355
+ /**
2356
+ * @brief Applies operation on the elements of the filtration value.
2357
+ */
2358
+ template <class ValueRange, class F, class = std::enable_if_t<RangeTraits<ValueRange>::has_begin> >
2359
+ void _apply_operation(const ValueRange &range, F &&operate)
2360
+ {
2361
+ auto &view = generator_view_;
2362
+ for (unsigned int g = 0; g < num_generators(); ++g) {
2363
+ auto it = range.begin();
2364
+ for (unsigned int p = 0; p < num_parameters() && it != range.end(); ++p) {
2365
+ std::forward<F>(operate)(view(g, p), *it);
2366
+ ++it;
2367
+ }
2368
+ }
2369
+ }
2370
+
2371
+ /**
2372
+ * @brief Applies operation on the elements of the filtration value.
2373
+ */
2374
+ template <class F>
2375
+ void _apply_operation(const T &val, F &&operate)
2376
+ {
2377
+ auto &gens = generators_;
2378
+ for (unsigned int i = 0; i < gens.size(); ++i) {
2379
+ std::forward<F>(operate)(gens[i], val);
2380
+ }
2381
+ }
2382
+
2383
+ template <class GeneratorRange>
2384
+ static void _get_infinity_statuses(const Viewer &a,
2385
+ const GeneratorRange &b,
2386
+ bool &aIsInf,
2387
+ bool &aIsMinusInf,
2388
+ bool &aIsNaN,
2389
+ bool &bIsInf,
2390
+ bool &bIsMinusInf,
2391
+ bool &bIsNaN)
2392
+ {
2393
+ auto itB = b.begin();
2394
+ for (std::size_t i = 0; i < a.extent(1); ++i) {
2395
+ if (a(0, i) != T_inf) aIsInf = false;
2396
+ if (a(0, i) != T_m_inf) aIsMinusInf = false;
2397
+ if (!_is_nan(a(0, i))) aIsNaN = false;
2398
+ if (*itB != T_inf) bIsInf = false;
2399
+ if (*itB != T_m_inf) bIsMinusInf = false;
2400
+ if (!_is_nan(*itB)) bIsNaN = false;
2401
+ if (!aIsInf && !aIsMinusInf && !aIsNaN && !bIsInf && !bIsMinusInf && !bIsNaN) return;
2402
+ ++itB;
2403
+ }
2404
+ }
2405
+
2406
+ enum class Rel : std::uint8_t { EQUAL, DOMINATES, IS_DOMINATED, NONE };
2407
+
2408
+ template <class Iterator>
2409
+ static Rel _get_domination_relation(const Viewer &a, size_type g_a, Iterator itB)
2410
+ {
2411
+ bool equal = true;
2412
+ bool allGreater = true;
2413
+ bool allSmaller = true;
2414
+ bool allNaNA = true;
2415
+ bool allNaNB = true;
2416
+ for (unsigned int i = 0; i < a.extent(1); ++i) {
2417
+ if (a(g_a, i) < *itB) {
2418
+ if (!allSmaller) return Rel::NONE;
2419
+ equal = false;
2420
+ allGreater = false;
2421
+ } else if (a(g_a, i) > *itB) {
2422
+ if (!allGreater) return Rel::NONE;
2423
+ equal = false;
2424
+ allSmaller = false;
2425
+ }
2426
+ if (!_is_nan(a(g_a, i))) allNaNA = false;
2427
+ if (!_is_nan(*itB)) allNaNB = false;
2428
+ ++itB;
2429
+ }
2430
+ if (allNaNA || allNaNB) return Rel::IS_DOMINATED;
2431
+ if (equal) return Rel::EQUAL;
2432
+
2433
+ if constexpr (Co) {
2434
+ if (allSmaller) return Rel::DOMINATES;
2435
+ return Rel::IS_DOMINATED;
2436
+ } else {
2437
+ if (allGreater) return Rel::DOMINATES;
2438
+ return Rel::IS_DOMINATED;
2439
+ }
2440
+ }
2441
+
2442
+ /**
2443
+ * @brief Verifies how x can be added as a new generator with respect to an already existing generator, represented
2444
+ * by `indices[curr]`. If x is dominated by or is equal to `indices[curr]`, it cannot be added. If it dominates
2445
+ * `indices[curr]`, it has to replace `indices[curr]`. If there is no relation between both, `indices[curr]` has
2446
+ * no influence on the addition of x.
2447
+ *
2448
+ * Assumes between 'curr' and 'end' everything is simplified:
2449
+ * no nan values and if there is an inf/-inf, then 'end - curr == 1'.
2450
+ */
2451
+ template <class Iterator>
2452
+ bool _generator_can_be_added(Iterator x, std::size_t curr, std::size_t &end, std::vector<int> &indices)
2453
+ {
2454
+ while (curr != end) {
2455
+ Rel res = _get_domination_relation(generator_view_, indices[curr], x);
2456
+ if (res == Rel::IS_DOMINATED || res == Rel::EQUAL) return false; // x dominates or is equal
2457
+ if (res == Rel::DOMINATES) { // x is dominated
2458
+ --end;
2459
+ std::swap(indices[curr], indices[end]);
2460
+ } else { // no relation
2461
+ ++curr;
2462
+ }
2463
+ }
2464
+ return true;
2465
+ }
2466
+
2467
+ /**
2468
+ * @brief Rebuild the generators from the given set.
2469
+ */
2470
+ template <class Iterator>
2471
+ void _build_from(std::vector<int> &indices, const int newIndex, Iterator xStart, Iterator xEnd)
2472
+ {
2473
+ auto comp = [&](int g1, int g2) -> bool {
2474
+ if (g1 == g2) {
2475
+ return false;
2476
+ }
2477
+
2478
+ if (g1 == newIndex) {
2479
+ auto it = xStart;
2480
+ for (size_type i = 0; i < num_parameters(); ++i) {
2481
+ T v = generator_view_(g2, i);
2482
+ if (*it != v) {
2483
+ if (_is_nan(v)) return true;
2484
+ return *it < v;
2485
+ }
2486
+ ++it;
2487
+ }
2488
+ return false;
2489
+ }
2490
+
2491
+ if (g2 == newIndex) {
2492
+ auto it = xStart;
2493
+ for (size_type i = 0; i < num_parameters(); ++i) {
2494
+ T v = generator_view_(g1, i);
2495
+ if (v != *it) {
2496
+ if (_is_nan(*it)) return true;
2497
+ return v < *it;
2498
+ }
2499
+ ++it;
2500
+ }
2501
+ return false;
2502
+ }
2503
+
2504
+ for (size_type i = 0; i < num_parameters(); ++i) {
2505
+ T v1 = generator_view_(g1, i);
2506
+ T v2 = generator_view_(g2, i);
2507
+ if (v1 != v2) {
2508
+ if (_is_nan(v2)) return true;
2509
+ return v1 < v2;
2510
+ }
2511
+ }
2512
+ return false;
2513
+ };
2514
+ std::sort(indices.begin(), indices.end(), comp);
2515
+
2516
+ Underlying_container new_container;
2517
+ new_container.reserve((indices.size() + 1) * num_parameters());
2518
+ for (int i : indices) {
2519
+ if (i == newIndex) {
2520
+ new_container.insert(new_container.end(), xStart, xEnd);
2521
+ } else {
2522
+ T *ptr = &generator_view_(i, 0);
2523
+ for (size_type p = 0; p < num_parameters(); ++p) {
2524
+ new_container.push_back(*ptr);
2525
+ ++ptr;
2526
+ }
2527
+ }
2528
+ }
2529
+ generators_.swap(new_container);
2530
+ generator_view_ = Viewer(generators_.data(), generators_.size() / num_parameters(), num_parameters());
2531
+ }
2532
+
2533
+ /**
2534
+ * @brief Rebuild the generators from the given set.
2535
+ */
2536
+ void _build_from(std::vector<int> &indices)
2537
+ {
2538
+ auto comp = [&](int g1, int g2) -> bool {
2539
+ for (std::size_t i = 0; i < num_parameters(); ++i) {
2540
+ T v1 = generator_view_(g1, i);
2541
+ T v2 = generator_view_(g2, i);
2542
+ if (v1 != v2) {
2543
+ if (_is_nan(v2)) return true;
2544
+ return v1 < v2;
2545
+ }
2546
+ }
2547
+ return false;
2548
+ };
2549
+ std::sort(indices.begin(), indices.end(), comp);
2550
+
2551
+ Underlying_container new_container;
2552
+ new_container.reserve(indices.size() * num_parameters());
2553
+ for (int i : indices) {
2554
+ T *ptr = &generator_view_(i, 0);
2555
+ for (size_type p = 0; p < num_parameters(); ++p) {
2556
+ new_container.push_back(*ptr);
2557
+ ++ptr;
2558
+ }
2559
+ }
2560
+ generators_.swap(new_container);
2561
+ generator_view_ = Viewer(generators_.data(), generators_.size() / num_parameters(), num_parameters());
2562
+ }
2563
+
2564
+ bool _is_finite(size_type g)
2565
+ {
2566
+ bool isInf = true, isMinusInf = true, isNan = true;
2567
+ for (size_type p = 0; p < num_parameters(); ++p) {
2568
+ T v = generator_view_(g, p);
2569
+ if (v != T_inf) isInf = false;
2570
+ if (v != T_m_inf) isMinusInf = false;
2571
+ if (!_is_nan(v)) isNan = false;
2572
+ if (!isInf && !isMinusInf && !isNan) return true;
2573
+ }
2574
+ return false;
2575
+ }
2576
+
2577
+ template <class F, typename U = T>
2578
+ static U _compute_frobenius_norm(size_type number_of_elements, F &&norm)
2579
+ {
2580
+ if (number_of_elements == 1) return std::forward<F>(norm)(0);
2581
+
2582
+ U out = 0;
2583
+ for (size_type p = 0; p < number_of_elements; ++p) {
2584
+ T v = std::forward<F>(norm)(p);
2585
+ out += v * v;
2586
+ }
2587
+ if constexpr (std::is_integral_v<U>) {
2588
+ // to avoid Windows issue that don't know how to cast integers for cmath methods
2589
+ return std::sqrt(static_cast<double>(out));
2590
+ } else {
2591
+ return std::sqrt(out);
2592
+ }
2593
+ }
2594
+ };
2595
+
2596
+ } // namespace Gudhi::multi_filtration
2597
+
2598
+ namespace std {
2599
+
2600
+ template <typename T, bool Co, bool Ensure1Criticality>
2601
+ class numeric_limits<Gudhi::multi_filtration::Multi_parameter_filtration<T, Co, Ensure1Criticality> >
2602
+ {
2603
+ public:
2604
+ using Filtration_value = Gudhi::multi_filtration::Multi_parameter_filtration<T, Co, Ensure1Criticality>;
2605
+
2606
+ static constexpr bool has_infinity = true;
2607
+ static constexpr bool has_quiet_NaN = std::numeric_limits<T>::has_quiet_NaN;
2608
+
2609
+ static constexpr Filtration_value infinity(std::size_t p = 1) noexcept { return Filtration_value::inf(p); };
2610
+
2611
+ // non-standard
2612
+ static constexpr Filtration_value minus_infinity(std::size_t p = 1) noexcept
2613
+ {
2614
+ return Filtration_value::minus_inf(p);
2615
+ };
2616
+
2617
+ static constexpr Filtration_value max() noexcept(false)
2618
+ {
2619
+ throw std::logic_error(
2620
+ "The max value cannot be represented with no finite numbers of parameters."
2621
+ "Use `max(number_of_parameters)` instead");
2622
+ };
2623
+
2624
+ static constexpr Filtration_value max(std::size_t p) noexcept
2625
+ {
2626
+ return Filtration_value(p, std::numeric_limits<T>::max());
2627
+ };
2628
+
2629
+ static constexpr Filtration_value lowest(std::size_t p = 1) noexcept { return Filtration_value::minus_inf(p); };
2630
+
2631
+ static constexpr Filtration_value quiet_NaN(std::size_t p = 1) noexcept(false)
2632
+ {
2633
+ if constexpr (std::numeric_limits<T>::has_quiet_NaN) {
2634
+ return Filtration_value::nan(p);
2635
+ } else {
2636
+ throw std::logic_error("Does not have a NaN value.");
2637
+ }
2638
+ };
2639
+ };
2640
+
2641
+ } // namespace std
2642
+
2643
+ #endif // MF_MULTI_PARAMETER_FILTRATION_H_