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,2102 @@
1
+ {{py:
2
+
3
+ """
4
+ Templated simplextree multi
5
+ """
6
+
7
+ import pickle
8
+ with open("build/tmp/_simplextrees_.pkl", "rb") as f:
9
+ to_iter = pickle.load(f)
10
+
11
+ st_map = {}
12
+ for D in to_iter:
13
+ locals().update(D)
14
+ st_map[PY_VALUE_TYPE, IS_KCRITICAL] = PY_CLASS_NAME
15
+
16
+ }}
17
+
18
+
19
+
20
+
21
+ # This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT.
22
+ # See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details.
23
+ # Author(s): Vincent Rouvreau
24
+ #
25
+ # Copyright (C) 2016 Inria
26
+ #
27
+ # Modification(s):
28
+ # - 2023 David Loiseaux : Conversions with standard simplextree, scc2020 format, edge collapses, euler characteristic, grid filtrations.
29
+ # - 2022/11 Hannah Schreiber / David Loiseaux : adapt for multipersistence.
30
+ # - YYYY/MM Author: Description of the modification
31
+
32
+
33
+
34
+ __author__ = "Vincent Rouvreau"
35
+ __copyright__ = "Copyright (C) 2016 Inria"
36
+ __license__ = "MIT"
37
+
38
+ from libc.stdint cimport intptr_t, int32_t, int64_t
39
+ from cython.operator import dereference, preincrement
40
+ from libc.stdint cimport intptr_t
41
+ from libc.stdint cimport uintptr_t, intptr_t
42
+ from libcpp.map cimport map
43
+ from libcpp.utility cimport pair
44
+
45
+ ctypedef fused some_int:
46
+ int32_t
47
+ int64_t
48
+ int
49
+
50
+ ctypedef fused some_float:
51
+ float
52
+ double
53
+ int32_t
54
+ int64_t
55
+
56
+ ctypedef vector[pair[pair[int,int],pair[float,float]]] edge_list_type
57
+
58
+ from typing import Any, Union, ClassVar
59
+ from collections.abc import Sequence
60
+
61
+ cimport numpy as cnp
62
+ import numpy as np
63
+ cnp.import_array()
64
+
65
+ from multipers.simplex_tree_multi cimport *
66
+ from multipers.filtration_conversions cimport *
67
+ cimport cython
68
+ from gudhi.simplex_tree import SimplexTree ## Small hack for typing
69
+ from typing import Iterable,Literal,Optional
70
+ from tqdm import tqdm
71
+ from multipers.grids import Lstrategies, compute_grid, sanitize_grid
72
+ from multipers.array_api import api_from_tensor
73
+ from multipers.point_measure import signed_betti, rank_decomposition_by_rectangles, sparsify
74
+
75
+ from warnings import warn
76
+
77
+ SAFE_CONVERSION=False #Slower but at least it works everywhere
78
+
79
+ _available_strategies =Lstrategies
80
+
81
+ global available_dtype
82
+ available_dtype = set([
83
+ {{for D in to_iter}}
84
+ {{D['PY_VALUE_TYPE']}},
85
+ {{endfor}}
86
+ ])
87
+
88
+
89
+ {{for D in to_iter}}
90
+ # WARN : updating locals {{locals().update(D)}}
91
+ # SimplexTree python interface
92
+ cdef class {{D["PY_CLASS_NAME"]}}:
93
+ """The simplex tree is an efficient and flexible data structure for
94
+ representing general (filtered) simplicial complexes. The data structure
95
+ is described in Jean-Daniel Boissonnat and Clément Maria. The Simplex
96
+ Tree: An Efficient Data Structure for General Simplicial Complexes.
97
+ Algorithmica, pages 1–22, 2014.
98
+
99
+ This class is a multi-filtered, with keys, and non contiguous vertices version
100
+ of the simplex tree.
101
+ """
102
+ cdef public intptr_t thisptr
103
+
104
+ cdef public object filtration_grid
105
+ cdef public bool _is_function_simplextree # TODO : deprecate
106
+ # Get the pointer casted as it should be
107
+ cdef {{ST_INTERFACE}}* get_ptr(self) noexcept nogil:
108
+ return <{{ST_INTERFACE}}*>(self.thisptr)
109
+
110
+ # cdef Simplex_tree_persistence_interface * pcohptr
111
+ # Fake constructor that does nothing but documenting the constructor
112
+ def __init__(self, other = None, num_parameters:int=-1,default_values=[], safe_conversion=False):
113
+ """SimplexTreeMulti constructor.
114
+
115
+ :param other: If `other` is `None` (default value), an empty `SimplexTreeMulti` is created.
116
+ If `other` is a `SimplexTree`, the `SimplexTreeMulti` is constructed from a deep copy of `other`.
117
+ If `other` is a `SimplexTreeMulti`, the `SimplexTreeMulti` is constructed from a deep copy of `other`.
118
+ :type other: SimplexTree or SimplexTreeMulti (Optional)
119
+ :param num_parameters: The number of parameter of the multi-parameter filtration.
120
+ :type num_parameters: int
121
+ :returns: An empty or a copy simplex tree.
122
+ :rtype: SimplexTreeMulti
123
+
124
+ :raises TypeError: In case `other` is neither `None`, nor a `SimplexTree`, nor a `SimplexTreeMulti`.
125
+ """
126
+
127
+ @staticmethod
128
+ cdef {{CTYPE}} T_minus_inf():
129
+ {{if IS_FLOAT}}
130
+ return <{{CTYPE}}>(-np.inf)
131
+ {{else}}
132
+ return <{{CTYPE}}>(np.iinfo({{PYTYPE}}).min)
133
+ {{endif}}
134
+ @staticmethod
135
+ cdef {{CTYPE}} T_inf():
136
+ {{if IS_FLOAT}}
137
+ return <{{CTYPE}}>(np.inf)
138
+ {{else}}
139
+ return <{{CTYPE}}>(np.iinfo({{PYTYPE}}).max)
140
+ {{endif}}
141
+ @property
142
+ def is_kcritical(self)->bool:
143
+ return {{IS_KCRITICAL}}
144
+ @staticmethod
145
+ def _isinstance(other)->bool:
146
+ return isinstance(other, {{PY_CLASS_NAME}})
147
+ def __cinit__(self, other = None, int num_parameters=-1,
148
+ default_values=np.asarray([{{D["PY_CLASS_NAME"]}}.T_minus_inf()]), # I'm not sure why `[]` does not work. Cython bug ?
149
+ bool safe_conversion=False,
150
+ ): #TODO doc
151
+ cdef vector[{{CTYPE}}] c_default_values
152
+ cdef intptr_t other_ptr
153
+ cdef char[:] buffer
154
+ cdef size_t buffer_size
155
+ cdef char* buffer_start
156
+ if other is not None:
157
+ if isinstance(other, {{D["PY_CLASS_NAME"]}}):
158
+ other_ptr = other.thisptr
159
+ self.thisptr = <intptr_t>(new Simplex_tree_multi_interface[{{PyFil}}, {{CTYPE}}](dereference(<Simplex_tree_multi_interface[{{PyFil}}, {{CTYPE}}]*>other_ptr))) ## prevents calling destructor of other
160
+ if num_parameters <=0:
161
+ num_parameters = other.num_parameters
162
+ self.filtration_grid = other.filtration_grid
163
+ elif isinstance(other, SimplexTree): # Constructs a SimplexTreeMulti from a SimplexTree
164
+ self.thisptr = <intptr_t>(new Simplex_tree_multi_interface[{{PyFil}}, {{CTYPE}}]())
165
+ if num_parameters <= 0:
166
+ num_parameters = 1
167
+ if safe_conversion or SAFE_CONVERSION:
168
+ new_st_multi = _safe_simplextree_multify_{{PyFil}}(other, num_parameters = num_parameters, default_values=np.asarray(default_values))
169
+ self.thisptr, new_st_multi.thisptr = new_st_multi.thisptr, self.thisptr
170
+ else:
171
+ stree_buffer = other.__getstate__()
172
+ buffer = stree_buffer
173
+ buffer_size = buffer.shape[0]
174
+ buffer_start = &buffer[0]
175
+
176
+ assert num_parameters > 0
177
+ if isinstance(default_values, Sequence):
178
+ # tedious way to do a resize
179
+ if len(default_values) > num_parameters:
180
+ del default_values[num_parameters:]
181
+ elif len(default_values) < num_parameters:
182
+ padding = [{{PY_CLASS_NAME}}.T_minus_inf()] * (num_parameters - len(default_values))
183
+ # padding is added at the beginning for the case someone just omitted index 0 which gets
184
+ # replaced by the original value.
185
+ default_values = padding + default_values
186
+ assert len(default_values) == num_parameters
187
+ else:
188
+ assert default_values.ndim == 1
189
+ # tedious way to do a resize
190
+ if default_values.shape[0] > num_parameters:
191
+ default_values = default_values[:num_parameters]
192
+ elif default_values.shape[0] < num_parameters:
193
+ padding = np.full(num_parameters - default_values.shape[0], {{PY_CLASS_NAME}}.T_minus_inf(), dtype=default_values.dtype)
194
+ # padding is added at the beginning for the case someone just omitted index 0 which gets
195
+ # replaced by the original value.
196
+ default_values = np.concatenate((padding, default_values))
197
+ assert default_values.shape[0] == num_parameters
198
+ c_default_values = np.asarray(default_values, dtype={{PYTYPE}})
199
+
200
+ with nogil:
201
+ self.get_ptr().from_std(buffer_start, buffer_size, 0, c_default_values)
202
+ else:
203
+ raise TypeError("`other` argument requires to be of type `SimplexTree`, `SimplexTreeMulti`, or `None`.")
204
+ else:
205
+ if num_parameters <=0:
206
+ num_parameters = 2 # I don't know how dangerous this is, but this is mostly used.
207
+ self.thisptr = <intptr_t>(new Simplex_tree_multi_interface[{{PyFil}}, {{CTYPE}}]())
208
+ self.set_num_parameter(num_parameters)
209
+ self._is_function_simplextree = False
210
+ self.filtration_grid=[]
211
+
212
+ def __dealloc__(self):
213
+ cdef Simplex_tree_multi_interface[{{PyFil}},{{CTYPE}}]* ptr = self.get_ptr()
214
+ if ptr != NULL:
215
+ del ptr
216
+ # TODO : is that enough ??
217
+
218
+ def __repr__(self):
219
+ return f"SimplexTreeMulti[dtype={np.dtype(self.dtype).name},num_param={self.num_parameters},kcritical={self.is_kcritical},is_squeezed={self.is_squeezed},max_dim={self.dimension}]"
220
+ def __len__(self):
221
+ return self.num_simplices
222
+
223
+ def __getstate__(self):
224
+ """:returns: Serialized (or flattened) SimplexTree data structure in order to pickle SimplexTree.
225
+ :rtype: numpy.array of shape (n,)
226
+ """
227
+ cdef size_t buffer_size = self.get_ptr().get_serialization_size()
228
+ # Let's use numpy to allocate a buffer. Will be deleted automatically
229
+ np_buffer = np.empty(buffer_size, dtype='B')
230
+ cdef char[:] buffer = np_buffer
231
+ cdef char* buffer_start = &buffer[0]
232
+ with nogil:
233
+ self.get_ptr().serialize(buffer_start, buffer_size)
234
+
235
+ return np_buffer
236
+
237
+ def __setstate__(self, state):
238
+ """Construct the SimplexTree data structure from a Numpy Array (cf. :func:`~gudhi.SimplexTree.__getstate__`)
239
+ in order to unpickle a SimplexTree.
240
+
241
+ :param state: Serialized SimplexTree data structure
242
+ :type state: numpy.array of shape (n,)
243
+ """
244
+ cdef char[:] buffer = state
245
+ cdef size_t buffer_size = state.shape[0]
246
+ cdef char* buffer_start = &buffer[0]
247
+ with nogil:
248
+ # deserialization requires an empty SimplexTree
249
+ self.get_ptr().clear()
250
+ # New pointer is a deserialized simplex tree
251
+ self.get_ptr().deserialize(buffer_start, buffer_size)
252
+ #temporary: deserialize should also update the number of parameters, but to keep compatibility with
253
+ #current gudhi simplex tree, we have to externalize it for now
254
+ cdef Simplex_tree_multi_simplex_handle[{{PyFil}}] sh
255
+ cdef Simplex_tree_multi_simplices_iterator[{{PyFil}}] it = self.get_ptr().get_simplices_iterator_begin()
256
+ cdef Simplex_tree_multi_simplices_iterator[{{PyFil}}] end = self.get_ptr().get_simplices_iterator_end()
257
+ cdef pair[vector[int],{{PyFil}}*] p
258
+ if it != end:
259
+ sh = dereference(it)
260
+ p = self.get_ptr().get_simplex_and_filtration(sh)
261
+ self.get_ptr().set_num_parameters((p.second)[0].num_parameters())
262
+ else:
263
+ #not ideal, but hopefully sufficient for now
264
+ #will be corrected once the gudhi simplex tree can dump the number of parameters
265
+ self.get_ptr().set_num_parameters(0)
266
+
267
+
268
+ def copy(self)->{{PY_CLASS_NAME}}:
269
+ """
270
+ :returns: A simplex tree that is a deep copy of itself.
271
+ :rtype: SimplexTreeMulti
272
+
273
+ :note: The persistence information is not copied. If you need it in the clone, you have to call
274
+ :func:`compute_persistence` on it even if you had already computed it in the original.
275
+ """
276
+ stree = {{PY_CLASS_NAME}}(self,num_parameters=self.num_parameters)
277
+ stree.filtration_grid = self.filtration_grid
278
+ stree._is_function_simplextree = self._is_function_simplextree
279
+ return stree
280
+
281
+ def __deepcopy__(self):
282
+ return self.copy()
283
+
284
+ def filtration(self, simplex:list|np.ndarray)->np.ndarray:
285
+ """This function returns the filtration value for a given N-simplex in
286
+ this simplicial complex, or +infinity if it is not in the complex.
287
+ :param simplex: The N-simplex, represented by a list of vertex.
288
+ :type simplex: list of int
289
+ :returns: The simplicial complex multi-critical filtration value.
290
+ :rtype: numpy array of shape (-1, num_parameters)
291
+ """
292
+ return self[simplex]
293
+
294
+ def assign_filtration(self, simplex:list|np.ndarray, filtration:list|np.ndarray)->{{PY_CLASS_NAME}}:
295
+ """This function assigns a new multi-critical filtration value to a
296
+ given N-simplex.
297
+
298
+ :param simplex: The N-simplex, represented by a list of vertex.
299
+ :type simplex: list of int
300
+ :param filtration: The new filtration(s) value(s), concatenated.
301
+ :type filtration: list[float] or np.ndarray[float, ndim=1]
302
+
303
+ .. note::
304
+ Beware that after this operation, the structure may not be a valid
305
+ filtration anymore, a simplex could have a lower filtration value
306
+ than one of its faces. Callers are responsible for fixing this
307
+ (with more :meth:`assign_filtration` or
308
+ :meth:`make_filtration_non_decreasing` for instance) before calling
309
+ any function that relies on the filtration property, like
310
+ :meth:`persistence`.
311
+ """
312
+ assert len(filtration)>0 and len(filtration) % self.get_ptr().num_parameters() == 0
313
+ # self.get_ptr().assign_simplex_filtration(simplex, {{PyFil}}(<python_filtration_type>filtration))
314
+ # filtration = np.asarray(filtration, dtype={{PYTYPE}})[None,:]
315
+ # self.get_ptr().assign_simplex_filtration(simplex, _py2kc_{{SHORT_VALUE_TYPE}}(filtration))
316
+ {{if IS_KCRITICAL}}
317
+ filtration = np.asarray(filtration, dtype={{PYTYPE}})[None,:]
318
+ {{else}}
319
+ filtration = np.asarray(filtration, dtype={{PYTYPE}})
320
+ {{endif}}
321
+ self.get_ptr().assign_simplex_filtration(simplex, {{P2C_Fil}}(filtration))
322
+ return self
323
+
324
+ def __getitem__(self, simplex)->np.ndarray:
325
+ cdef vector[int] csimplex = simplex
326
+ cdef {{PyFil}}* f_ptr = self.get_ptr().simplex_filtration(csimplex)
327
+ {{if IS_KCRITICAL}}
328
+ return {{C2P_Fil}}(f_ptr)
329
+ {{else}}
330
+ return {{C2P_Fil}}(f_ptr)
331
+ {{endif}}
332
+
333
+
334
+ @property
335
+ def num_vertices(self)->int:
336
+ """This function returns the number of vertices of the simplicial
337
+ complex.
338
+
339
+ :returns: The simplicial complex number of vertices.
340
+ :rtype: int
341
+ """
342
+ return self.get_ptr().num_vertices()
343
+
344
+ @property
345
+ def num_simplices(self)->int:
346
+ """This function returns the number of simplices of the simplicial
347
+ complex.
348
+
349
+ :returns: the simplicial complex number of simplices.
350
+ :rtype: int
351
+ """
352
+ return self.get_ptr().num_simplices()
353
+
354
+ @property
355
+ def dimension(self)->int:
356
+ """This function returns the dimension of the simplicial complex.
357
+
358
+ :returns: the simplicial complex dimension.
359
+ :rtype: int
360
+
361
+ .. note::
362
+
363
+ This function is not constant time because it can recompute
364
+ dimension if required (can be triggered by
365
+ :func:`remove_maximal_simplex`
366
+ or
367
+ :func:`prune_above_filtration`
368
+ methods).
369
+ """
370
+ return self.get_ptr().dimension()
371
+ def simplex_dimension(self, vector[int] simplex)->int:
372
+ """This function returns the dimension of the simplex.
373
+
374
+ :returns: simplex dimension.
375
+ :rtype: int
376
+ """
377
+ return self.get_ptr().simplex_dimension(simplex)
378
+ def upper_bound_dimension(self)->int:
379
+ """This function returns a valid dimension upper bound of the
380
+ simplicial complex.
381
+
382
+ :returns: an upper bound on the dimension of the simplicial complex.
383
+ :rtype: int
384
+ """
385
+ return self.get_ptr().upper_bound_dimension()
386
+
387
+ def __iter__(self):
388
+ for stuff in self.get_simplices():
389
+ yield stuff
390
+
391
+ def flagify(self, const int dim=2):
392
+ """
393
+ Turns this simplicial complex into a flag
394
+ complex by resetting filtration values of
395
+ simplices of dimension > `dim` by
396
+ lower-star values.
397
+ """
398
+ cdef Simplex_tree_multi_simplices_iterator[{{PyFil}}] it = self.get_ptr().get_simplices_iterator_begin()
399
+ cdef Simplex_tree_multi_simplices_iterator[{{PyFil}}] end = self.get_ptr().get_simplices_iterator_end()
400
+
401
+ # TODO: there is probably a better way to get the inf value...
402
+ cdef {{OneCriticalFil}} tmp = {{OneCriticalFil}}(self.get_ptr().num_parameters())
403
+ cdef vector[{{CTYPE}}] minf = vector[{{CTYPE}}](self.get_ptr().num_parameters(), tmp(0,0))
404
+ cdef int simplex_dimension
405
+ with nogil:
406
+ while it != end:
407
+ pair_sf =<pair[simplex_type, {{PyFil}}*]> self.get_ptr().get_simplex_and_filtration(dereference(it))
408
+ simplex_dimension = <int>(pair_sf.first.size())-1
409
+ if simplex_dimension<dim:
410
+ preincrement(it)
411
+ continue
412
+ dereference(pair_sf.second).pull_to_greatest_common_lower_bound(minf, False)
413
+ preincrement(it)
414
+ self.make_filtration_non_decreasing()
415
+ return self
416
+
417
+ def set_dimension(self, int dimension)->None:
418
+ """This function sets the dimension of the simplicial complex.
419
+
420
+ :param dimension: The new dimension value.
421
+ :type dimension: int
422
+
423
+ .. note::
424
+
425
+ This function must be used with caution because it disables
426
+ dimension recomputation when required
427
+ (this recomputation can be triggered by
428
+ :func:`remove_maximal_simplex`
429
+ or
430
+ :func:`prune_above_filtration`
431
+ ).
432
+ """
433
+ self.get_ptr().set_dimension(dimension)
434
+
435
+ def __contains__(self, simplex):
436
+ """This function returns if the N-simplex was found in the simplicial
437
+ complex or not.
438
+
439
+ :param simplex: The N-simplex to find, represented by a list of vertex.
440
+ :type simplex: list of int
441
+ :returns: true if the simplex was found, false otherwise.
442
+ :rtype: bool
443
+ """
444
+ if len(simplex) == 0:
445
+ return False
446
+ if isinstance(simplex[0], Iterable):
447
+ s,f = simplex
448
+ if not self.get_ptr().find_simplex(simplex):
449
+ return False
450
+ current_f = np.asarray(self[s])
451
+ return np.all(np.asarray(f)>=current_f)
452
+
453
+ return self.get_ptr().find_simplex(simplex)
454
+
455
+ def insert(self, vector[int] simplex, filtration:list|np.ndarray|None=None)->bool:
456
+ """This function inserts the given N-simplex and its subfaces with the
457
+ given filtration value (default value is '0.0'). If some of those
458
+ simplices are already present with a higher filtration value, their
459
+ filtration value is lowered.
460
+
461
+ :param simplex: The N-simplex to insert, represented by a list of
462
+ vertex.
463
+ :type simplex: list of int
464
+ :param filtration: The filtration value of the simplex.
465
+ :type filtration: float
466
+ :returns: true if the simplex was not yet in the complex, false
467
+ otherwise (whatever its original filtration value).
468
+ :rtype: bool
469
+ """
470
+ # TODO C++, to be compatible with insert_batch and multicritical filtrations
471
+ num_parameters = self.get_ptr().num_parameters()
472
+ assert filtration is None or len(filtration) % num_parameters == 0, f"Invalid number \
473
+ of parameters. Should be {num_parameters}, got {len(filtration)}"
474
+ if filtration is None:
475
+ filtration = np.array([-np.inf]*num_parameters, dtype = float)
476
+
477
+ filtration = np.asarray(filtration, dtype = {{PYTYPE}})
478
+
479
+ {{if IS_KCRITICAL}}
480
+ from itertools import chain, combinations
481
+
482
+ def powerset(iterable):
483
+ s = tuple(iterable)
484
+ return chain.from_iterable(combinations(s, r) for r in range(1,len(s)+1)) # skip the empty splx
485
+ cdef {{PyFil}}* current_filtration_ptr
486
+ cdef vector[vector[int]] simplices_filtration_to_insert = powerset(simplex) # TODO : optimize
487
+
488
+ if self.get_ptr().find_simplex(simplex):
489
+ for i in range(simplices_filtration_to_insert.size()):
490
+ current_filtration_ptr = self.get_ptr().simplex_filtration(simplices_filtration_to_insert[i])
491
+ dereference(current_filtration_ptr).add_generator(_py2p_{{SHORT_VALUE_TYPE}}(filtration))
492
+ return True ## TODO : we may want to return false if this birth wasn't necessary
493
+ cdef {{PyFil}} cfiltration = _py2kc_{{SHORT_VALUE_TYPE}}(filtration[None,:])
494
+ {{else}}
495
+ cdef {{PyFil}} cfiltration = _py21c_{{SHORT_VALUE_TYPE}}(filtration)
496
+ {{endif}}
497
+ return self.get_ptr().insert(simplex,cfiltration)
498
+
499
+ {{if not IS_KCRITICAL}}
500
+ @cython.boundscheck(False)
501
+ @cython.wraparound(False)
502
+ def insert_batch(self, vertex_array, filtrations)->{{PY_CLASS_NAME}}:
503
+ """Inserts k-simplices given by a sparse array in a format similar
504
+ to `torch.sparse <https://pytorch.org/docs/stable/sparse.html>`_.
505
+ The n-th simplex has vertices `vertex_array[0,n]`, ...,
506
+ `vertex_array[k,n]` and filtration value `filtrations[n,num_parameters]`.
507
+ /!\ Only compatible with 1-critical filtrations. If a simplex is repeated,
508
+ only one filtration value will be taken into account.
509
+
510
+ :param vertex_array: the k-simplices to insert.
511
+ :type vertex_array: numpy.array of shape (k+1,n)
512
+ :param filtrations: the filtration values.
513
+ :type filtrations: numpy.array of shape (n,num_parameters)
514
+ """
515
+ # TODO : multi-critical
516
+ # cdef vector[int] vertices = np.unique(vertex_array)
517
+ cdef int[:,:] vertex_array_ = np.asarray(vertex_array, dtype=np.int32)
518
+ cdef Py_ssize_t k = vertex_array_.shape[0]
519
+ cdef Py_ssize_t n = vertex_array_.shape[1]
520
+ cdef int num_parameters = self.get_ptr().num_parameters()
521
+ cdef bool empty_filtration = (filtrations.size == 0)
522
+ cdef {{CTYPE}}[:,:] F_view = np.asarray(filtrations, dtype = {{PYTYPE}})
523
+
524
+ if not empty_filtration :
525
+ assert filtrations.shape[0] == n, f"inconsistent sizes for vertex_array and filtrations\
526
+ Filtrations should be of shape ({n},{self.num_parameters})"
527
+ assert filtrations.shape[1] == num_parameters, f"Inconsistent number of parameters.\
528
+ Filtrations should be of shape ({n},{self.num_parameters})"
529
+ cdef Py_ssize_t i
530
+ cdef Py_ssize_t j
531
+ cdef vector[int] v
532
+ cdef {{PyFil}} w
533
+ if empty_filtration:
534
+ w = {{PyFil}}(num_parameters) # at -inf by default
535
+ with nogil:
536
+ for i in range(n):
537
+ # vertex
538
+ for j in range(k):
539
+ v.push_back(vertex_array_[j, i])
540
+ #filtration
541
+ if not empty_filtration:
542
+ # for j in range(num_parameters):
543
+ # w.push_back(<{{CTYPE}}>filtrations[i,j])
544
+ w = _py21c_{{SHORT_VALUE_TYPE}}(F_view[i,:])
545
+ self.get_ptr().insert(v, w)
546
+ v.clear()
547
+ #repair filtration if necessary
548
+ if empty_filtration:
549
+ self.make_filtration_non_decreasing()
550
+ return self
551
+
552
+ def lower_star_multi_filtration_update(self, nodes_filtrations):
553
+ """
554
+ Updates the multi filtration of the simplextree to the lower-star
555
+ filtration defined on the vertices, by `node_filtrations`.
556
+ """
557
+ cdef Py_ssize_t num_vertices = nodes_filtrations.shape[0]
558
+ cdef Py_ssize_t num_parameters = nodes_filtrations.shape[1]
559
+ assert self.get_ptr().num_parameters() == num_parameters and self.num_vertices == num_vertices, f"Invalid shape {nodes_filtrations.shape}. Should be (?,{self.num_parameters=})."
560
+
561
+ cdef Simplex_tree_multi_simplices_iterator[{{PyFil}}] it = self.get_ptr().get_simplices_iterator_begin()
562
+ cdef Simplex_tree_multi_simplices_iterator[{{PyFil}}] end = self.get_ptr().get_simplices_iterator_end()
563
+ cdef Py_ssize_t node_idx = 0
564
+ cdef {{CTYPE}}[:,:] F = nodes_filtrations
565
+ cdef {{CTYPE}} minus_inf = -np.inf
566
+ cdef {{CTYPE}}* f
567
+ with nogil:
568
+ while it != end:
569
+ pair_sf = <pair[simplex_type, {{PyFil}}*]> self.get_ptr().get_simplex_and_filtration(dereference(it))
570
+ if pair_sf.first.size() == 1: # dimension == 0
571
+ {{if IS_KCRITICAL}}
572
+ for i in range(pair_sf.second.size()):
573
+ for j in range(num_parameters):
574
+ dereference(pair_sf.second)[i][j] = F[node_idx,j]
575
+ {{else}}
576
+ for i in range(num_parameters):
577
+ # Weird cython bug with "Cannot assign to or delete this" otherwise
578
+ f = &(dereference(pair_sf.second)(0,i))
579
+ f[0] = F[node_idx,i]
580
+ {{endif}}
581
+ node_idx += 1
582
+ # with gil:
583
+ # print(pair_sf.first, node_idx,i, F[node_idx,i])
584
+ else:
585
+ {{if IS_KCRITICAL}}
586
+ for i in range(pair_sf.second.size()):
587
+ for j in range(num_parameters):
588
+ f = &(dereference(pair_sf.second)(i,j))
589
+ f[0] = minus_inf
590
+ {{else}}
591
+ for i in range(num_parameters):
592
+ f = &(dereference(pair_sf.second)(0,i))
593
+ f[0] = minus_inf
594
+ {{endif}}
595
+ preincrement(it)
596
+ self.make_filtration_non_decreasing()
597
+ return self
598
+
599
+
600
+ def assign_all(self, filtration_values)-> {{PY_CLASS_NAME}}:
601
+ """
602
+ Updates the filtration values of all of the simplices, with `filtration_values`
603
+ with order given by the simplextree iterator, e.g. self.get_simplices().
604
+ """
605
+ cdef Py_ssize_t num_simplices = filtration_values.shape[0]
606
+ cdef Py_ssize_t num_parameters = filtration_values.shape[1]
607
+
608
+ assert num_simplices == self.num_simplices, f"Number of filtration values {filtration_values.shape[0]} is not the number of simplices {self.num_simplices}"
609
+ assert num_parameters == self.num_parameters, f"Number of parameter do not coincide {filtration_values.shape[1]} vs {self.num_parameters}"
610
+ cdef Simplex_tree_multi_simplices_iterator[{{PyFil}}] it = self.get_ptr().get_simplices_iterator_begin()
611
+ cdef Simplex_tree_multi_simplices_iterator[{{PyFil}}] end = self.get_ptr().get_simplices_iterator_end()
612
+ cdef Simplex_tree_multi_simplex_handle[{{PyFil}}] sh = dereference(it)
613
+ cdef int counter =0
614
+ # cdef cnp.ndarray[{{CTYPE}},ndim=1] current_filtration
615
+ cdef {{CTYPE}}[:,:] F = filtration_values
616
+ cdef {{CTYPE}}* f
617
+ with nogil:
618
+ while it != end:
619
+ pair_sf =<pair[simplex_type, {{PyFil}}*]> self.get_ptr().get_simplex_and_filtration(dereference(it))
620
+
621
+ for i in range(num_parameters):
622
+ # Weird cython bug with "Cannot assign to or delete this" otherwise
623
+ f = &(dereference(pair_sf.second)(0,i))
624
+ f[0] = F[counter,i]
625
+ # current_filtration= F[counter]
626
+ counter += 1
627
+ # yield SimplexTreeMulti._pair_simplex_filtration_to_python(out)
628
+ preincrement(it)
629
+
630
+
631
+
632
+ @cython.boundscheck(False)
633
+ @cython.wraparound(False)
634
+ def assign_batch_filtration(self, some_int[:,:] vertex_array, some_float[:,:] filtrations, bool propagate=True)->{{PY_CLASS_NAME}}:
635
+ """Assign k-simplices given by a sparse array in a format similar
636
+ to `torch.sparse <https://pytorch.org/docs/stable/sparse.html>`_.
637
+ The n-th simplex has vertices `vertex_array[0,n]`, ...,
638
+ `vertex_array[k,n]` and filtration value `filtrations[n,num_parameters]`.
639
+ /!\ Only compatible with 1-critical filtrations. If a simplex is repeated,
640
+ only one filtration value will be taken into account.
641
+
642
+ :param vertex_array: the k-simplices to assign.
643
+ :type vertex_array: numpy.array of shape (k+1,n)
644
+ :param filtrations: the filtration values.
645
+ :type filtrations: numpy.array of shape (n,num_parameters)
646
+ """
647
+ cdef Py_ssize_t k = vertex_array.shape[0]
648
+ cdef Py_ssize_t n = vertex_array.shape[1]
649
+ assert filtrations.shape[0] == n, 'inconsistent sizes for vertex_array and filtrations'
650
+ assert filtrations.shape[1] == self.num_parameters, "wrong number of parameters"
651
+ cdef Py_ssize_t i
652
+ cdef Py_ssize_t j
653
+ cdef vector[int] v
654
+ cdef vector[{{CTYPE}}] w
655
+ cdef {{PyFil}} f
656
+ cdef int n_parameters = self.num_parameters
657
+ with nogil:
658
+ for i in range(n):
659
+ for j in range(k):
660
+ v.push_back(vertex_array[j, i])
661
+ for j in range(n_parameters):
662
+ w.push_back(<{{CTYPE}}>filtrations[i,j])
663
+ f = {{PyFil}}(w.begin(), w.end(), n_parameters)
664
+ self.get_ptr().assign_simplex_filtration(v, f)
665
+ v.clear()
666
+ w.clear()
667
+ if propagate: self.make_filtration_non_decreasing()
668
+ return self
669
+
670
+ def euler_characteristic(self, dtype = {{PYTYPE}}):
671
+ """This function returns a generator with simplices and their given
672
+ filtration values.
673
+
674
+ :returns: The simplices.
675
+ :rtype: generator with tuples(simplex, filtration)
676
+ """
677
+ cdef Simplex_tree_multi_simplices_iterator[{{PyFil}}] it = self.get_ptr().get_simplices_iterator_begin()
678
+ cdef Simplex_tree_multi_simplices_iterator[{{PyFil}}] end = self.get_ptr().get_simplices_iterator_end()
679
+ cdef Simplex_tree_multi_simplex_handle[{{PyFil}}] sh = dereference(it)
680
+ cdef int num_parameters = self.get_ptr().num_parameters()
681
+ cdef dict[tuple,int] out = {}
682
+ cdef int dim
683
+ while it != end:
684
+ pair_sf = <pair[simplex_type, {{PyFil}}*]> self.get_ptr().get_simplex_and_filtration(dereference(it))
685
+ # TODO: optimize https://stackoverflow.com/questions/16589791/most-efficient-property-to-hash-for-numpy-array
686
+ key = tuple({{C2P_Fil}}(pair_sf.second))
687
+ dim = pair_sf.first.size() -1 % 2
688
+ out[key] = out.get(key,0)+(-1)**dim
689
+ num_keys = len(out)
690
+ new_pts = np.fromiter(out.keys(), dtype=np.dtype((dtype,num_parameters)), count=num_keys)
691
+ new_weights = np.fromiter(out.values(), dtype=np.int32, count=num_keys)
692
+ idx = np.nonzero(new_weights)
693
+ new_pts = new_pts[idx]
694
+ new_weights = new_weights[idx]
695
+ return (new_pts, new_weights)
696
+
697
+ {{else}}
698
+
699
+ @cython.boundscheck(False)
700
+ @cython.wraparound(False)
701
+ def insert_batch(self, vertex_array, filtrations)->{{PY_CLASS_NAME}} :
702
+ """Inserts k-simplices given by a sparse array in a format similar
703
+ to `torch.sparse <https://pytorch.org/docs/stable/sparse.html>`_.
704
+ The i-th simplex has vertices `vertex_array[0,i]`, ...,
705
+ `vertex_array[n,i]` and j-th filtration value `filtrations[i,j,num_parameters]`.
706
+
707
+ If the given filtration is not empty, it has to represent a valid filtration even with respect to faces already
708
+ present in the simplex tree. Contrary to the 1-critical case, no automatic revision is made.
709
+
710
+ :param vertex_array: the k-simplices to insert.
711
+ :type vertex_array: numpy.array of shape (k+1,n)
712
+ :param filtrations: the filtration values.
713
+ :type filtrations: numpy.array of shape (n, max_critical_degree, num_parameters)
714
+ """
715
+ # TODO : multi-critical
716
+ # cdef vector[int] vertices = np.unique(vertex_array)
717
+
718
+ cdef int[:,:] vertex_array_ = np.asarray(vertex_array, dtype=np.int32)
719
+ cdef {{CTYPE}}[:,:,:] filtrations_ = np.asarray(filtrations, dtype={{PYTYPE}})
720
+ cdef Py_ssize_t k = vertex_array_.shape[0]
721
+ cdef Py_ssize_t n = vertex_array_.shape[1]
722
+ cdef Py_ssize_t max_crit_deg = filtrations_.shape[1]
723
+ cdef int num_parameters = self.get_ptr().num_parameters()
724
+ cdef bool empty_filtration = (filtrations_.size == 0)
725
+
726
+ if not empty_filtration :
727
+ assert filtrations_.shape[0] == n and filtrations_.shape[2] == num_parameters, f"""
728
+ Inconsistent sizes for vertex_array and filtrations\n
729
+ Filtrations should be of shape ({n},k, {self.num_parameters})
730
+ """
731
+ cdef Py_ssize_t i
732
+ cdef Py_ssize_t j
733
+ cdef vector[int] v
734
+ cdef {{OneCriticalFil}} w_temp
735
+ cdef {{PyFil}} w
736
+ if empty_filtration:
737
+ w = {{PyFil}}(num_parameters) # at -inf by default
738
+ with nogil:
739
+ for i in range(n):
740
+ v.clear()
741
+ # vertex
742
+ for j in range(k):
743
+ v.push_back(vertex_array_[j, i])
744
+ #filtration
745
+ if not empty_filtration:
746
+ w = {{P2C_Fil}}(filtrations_[i])
747
+ self.get_ptr().insert_force(v, w)
748
+
749
+ #repair filtration if necessary
750
+ if empty_filtration:
751
+ self.make_filtration_non_decreasing()
752
+ return self
753
+ {{endif}}
754
+
755
+
756
+ def get_simplices(self)->Iterable[tuple[np.ndarray, np.ndarray]]:
757
+ """This function returns a generator with simplices and their given
758
+ filtration values.
759
+
760
+ :returns: The simplices.
761
+ :rtype: generator with tuples(simplex, filtration)
762
+ """
763
+ cdef Simplex_tree_multi_simplices_iterator[{{PyFil}}] it = self.get_ptr().get_simplices_iterator_begin()
764
+ cdef Simplex_tree_multi_simplices_iterator[{{PyFil}}] end = self.get_ptr().get_simplices_iterator_end()
765
+ cdef Simplex_tree_multi_simplex_handle[{{PyFil}}] sh = dereference(it)
766
+ cdef int num_parameters = self.get_ptr().num_parameters()
767
+ while it != end:
768
+ pair_sf = <pair[simplex_type, {{PyFil}}*]> self.get_ptr().get_simplex_and_filtration(dereference(it))
769
+ yield (
770
+ np.asarray(pair_sf.first, dtype=int),
771
+ {{if IS_KCRITICAL}}
772
+ {{C2P_Fil}}(pair_sf.second)
773
+ {{else}}
774
+ {{C2P_Fil}}(pair_sf.second)
775
+ {{endif}}
776
+ )
777
+ preincrement(it)
778
+
779
+
780
+ def _get_raw_filtration(self):
781
+ {{if IS_KCRITICAL}}
782
+ raise NotImplementedError("Not implemented for multicritical filtrations")
783
+ {{else}}
784
+ if self.is_squeezed:
785
+ raise NotImplementedError("Not implemented for squeezed filtrations")
786
+ cdef int num_parameters = self.num_parameters
787
+ cdef {{CTYPE}}[:,:] current_filtration = np.empty((self.num_simplices, self.num_parameters), dtype=self.dtype)
788
+
789
+ cdef Simplex_tree_multi_simplices_iterator[{{PyFil}}] it = self.get_ptr().get_simplices_iterator_begin()
790
+ cdef Simplex_tree_multi_simplices_iterator[{{PyFil}}] end = self.get_ptr().get_simplices_iterator_end()
791
+
792
+ cdef int i=0
793
+ with nogil:
794
+ while it != end:
795
+ pair_sf = <pair[simplex_type, {{PyFil}}*]> self.get_ptr().get_simplex_and_filtration(dereference(it))
796
+ for j in range(num_parameters):
797
+ current_filtration[i,j] = dereference(pair_sf.second)(0,j)
798
+ preincrement(it)
799
+ i = i+1
800
+ return np.asarray(current_filtration, dtype=self.dtype)
801
+ {{endif}}
802
+
803
+
804
+
805
+ def get_skeleton(self, dimension)->Iterable[tuple[np.ndarray,np.ndarray]]:
806
+ """This function returns a generator with the (simplices of the) skeleton of a maximum given dimension.
807
+
808
+ :param dimension: The skeleton dimension value.
809
+ :type dimension: int
810
+ :returns: The (simplices of the) skeleton of a maximum dimension.
811
+ :rtype: generator with tuples(simplex, filtration)
812
+ """
813
+ cdef Simplex_tree_multi_skeleton_iterator[{{PyFil}}] it = self.get_ptr().get_skeleton_iterator_begin(dimension)
814
+ cdef Simplex_tree_multi_skeleton_iterator[{{PyFil}}] end = self.get_ptr().get_skeleton_iterator_end(dimension)
815
+ cdef int num_parameters = self.get_ptr().num_parameters()
816
+ while it != end:
817
+ # yield self.get_ptr().get_simplex_and_filtration(dereference(it))
818
+ pair = self.get_ptr().get_simplex_and_filtration(dereference(it))
819
+ yield (np.asarray(pair.first, dtype=int),
820
+ {{if IS_KCRITICAL}}
821
+ {{C2P_Fil}}(pair.second)
822
+ {{else}}
823
+ {{C2P_Fil}}(pair.second)
824
+ {{endif}}
825
+ )
826
+ preincrement(it)
827
+
828
+ # def get_star(self, simplex):
829
+ # """This function returns the star of a given N-simplex.
830
+
831
+ # :param simplex: The N-simplex, represented by a list of vertex.
832
+ # :type simplex: list of int
833
+ # :returns: The (simplices of the) star of a simplex.
834
+ # :rtype: list of tuples(simplex, filtration)
835
+ # """
836
+ # cdef simplex_type csimplex = simplex
837
+ # cdef int num_parameters = self.num_parameters
838
+ # # for i in simplex:
839
+ # # csimplex.push_back(i)
840
+ # cdef vector[simplex_filtration_type] star \
841
+ # = self.get_ptr().get_star(csimplex)
842
+ # ct = []
843
+
844
+ # for filtered_simplex in star:
845
+ # v = []
846
+ # for vertex in filtered_simplex.first:
847
+ # v.append(vertex)
848
+ # ct.append((v, np.asarray(<{{CTYPE}}[:num_parameters]>filtered_simplex.second)))
849
+ # return ct
850
+
851
+ # def get_cofaces(self, simplex, codimension):
852
+ # """This function returns the cofaces of a given N-simplex with a
853
+ # given codimension.
854
+
855
+ # :param simplex: The N-simplex, represented by a list of vertex.
856
+ # :type simplex: list of int
857
+ # :param codimension: The codimension. If codimension = 0, all cofaces
858
+ # are returned (equivalent of get_star function)
859
+ # :type codimension: int
860
+ # :returns: The (simplices of the) cofaces of a simplex
861
+ # :rtype: list of tuples(simplex, filtration)
862
+ # """
863
+ # cdef vector[int] csimplex = simplex
864
+ # cdef int num_parameters = self.num_parameters
865
+ # # for i in simplex:
866
+ # # csimplex.push_back(i)
867
+ # cdef vector[simplex_filtration_type] cofaces \
868
+ # = self.get_ptr().get_cofaces(csimplex, <int>codimension)
869
+ # ct = []
870
+ # for filtered_simplex in cofaces:
871
+ # v = []
872
+ # for vertex in filtered_simplex.first:
873
+ # v.append(vertex)
874
+ # ct.append((v, np.asarray(<{{CTYPE}}[:num_parameters]>filtered_simplex.second)))
875
+ # return ct
876
+
877
+ def get_boundaries(self, simplex)->Iterable[tuple[np.ndarray, np.ndarray]]:
878
+ """This function returns a generator with the boundaries of a given N-simplex.
879
+ If you do not need the filtration values, the boundary can also be obtained as
880
+ :code:`itertools.combinations(simplex,len(simplex)-1)`.
881
+
882
+ :param simplex: The N-simplex, represented by a list of vertex.
883
+ :type simplex: list of int.
884
+ :returns: The (simplices of the) boundary of a simplex
885
+ :rtype: generator with tuples(simplex, filtration)
886
+ """
887
+ cdef pair[Simplex_tree_multi_boundary_iterator[{{PyFil}}], Simplex_tree_multi_boundary_iterator[{{PyFil}}]] it = self.get_ptr().get_boundary_iterators(simplex)
888
+
889
+ cdef int num_parameters = self.get_ptr().num_parameters()
890
+ while it.first != it.second:
891
+ # yield self.get_ptr().get_simplex_and_filtration(dereference(it))
892
+ pair = self.get_ptr().get_simplex_and_filtration(dereference(it.first))
893
+ yield (np.asarray(pair.first, dtype=int),
894
+ {{if IS_KCRITICAL}}
895
+ {{C2P_Fil}}(pair.second)
896
+ {{else}}
897
+ {{C2P_Fil}}(pair.second)
898
+ {{endif}}
899
+ )
900
+ preincrement(it.first)
901
+ def remove_maximal_simplex(self, simplex)->{{PY_CLASS_NAME}}:
902
+ """This function removes a given maximal N-simplex from the simplicial
903
+ complex.
904
+
905
+ :param simplex: The N-simplex, represented by a list of vertex.
906
+ :type simplex: list of int
907
+
908
+ .. note::
909
+
910
+ The dimension of the simplicial complex may be lower after calling
911
+ remove_maximal_simplex than it was before. However,
912
+ :func:`upper_bound_dimension`
913
+ method will return the old value, which
914
+ remains a valid upper bound. If you care, you can call
915
+ :func:`dimension`
916
+ to recompute the exact dimension.
917
+ """
918
+ self.get_ptr().remove_maximal_simplex(simplex)
919
+ return self
920
+
921
+ # def prune_above_filtration(self, filtration)->bool:
922
+ # """Prune above filtration value given as parameter.
923
+
924
+ # :param filtration: Maximum threshold value.
925
+ # :type filtration: float
926
+ # :returns: The filtration modification information.
927
+ # :rtype: bool
928
+
929
+
930
+ # .. note::
931
+
932
+ # Note that the dimension of the simplicial complex may be lower
933
+ # after calling
934
+ # :func:`prune_above_filtration`
935
+ # than it was before. However,
936
+ # :func:`upper_bound_dimension`
937
+ # will return the old value, which remains a
938
+ # valid upper bound. If you care, you can call
939
+ # :func:`dimension`
940
+ # method to recompute the exact dimension.
941
+ # """
942
+ # return self.get_ptr().prune_above_filtration(filtration)
943
+ def prune_above_dimension(self, int dimension):
944
+ """Remove all simplices of dimension greater than a given value.
945
+
946
+ :param dimension: Maximum dimension value.
947
+ :type dimension: int
948
+ :returns: The modification information.
949
+ :rtype: bool
950
+ """
951
+ return self.get_ptr().prune_above_dimension(dimension)
952
+
953
+ def expansion(self, int max_dim)->{{PY_CLASS_NAME}}:
954
+ """Expands the simplex tree containing only its one skeleton
955
+ until dimension max_dim.
956
+
957
+ The expanded simplicial complex until dimension :math:`d`
958
+ attached to a graph :math:`G` is the maximal simplicial complex of
959
+ dimension at most :math:`d` admitting the graph :math:`G` as
960
+ :math:`1`-skeleton.
961
+ The filtration value assigned to a simplex is the maximal filtration
962
+ value of one of its edges.
963
+
964
+ The simplex tree must contain no simplex of dimension bigger than
965
+ 1 when calling the method.
966
+
967
+ :param max_dim: The maximal dimension.
968
+ :type max_dim: int
969
+ """
970
+ with nogil:
971
+ self.get_ptr().expansion(max_dim)
972
+ # This is a fix for multipersistence. FIXME expansion in c++
973
+ self.get_ptr().make_filtration_non_decreasing()
974
+ return self
975
+
976
+ def make_filtration_non_decreasing(self)->bool:
977
+ """This function ensures that each simplex has a higher filtration
978
+ value than its faces by increasing the filtration values.
979
+
980
+ :returns: True if any filtration value was modified,
981
+ False if the filtration was already non-decreasing.
982
+ :rtype: bool
983
+ """
984
+ cdef bool out
985
+ with nogil:
986
+ out = self.get_ptr().make_filtration_non_decreasing()
987
+ return out
988
+
989
+ def reset_filtration(self, filtration, min_dim = 0)->{{PY_CLASS_NAME}}:
990
+ """This function resets the filtration value of all the simplices of dimension at least min_dim. Resets all the
991
+ simplex tree when `min_dim = 0`.
992
+ `reset_filtration` may break the filtration property with `min_dim > 0`, and it is the user's responsibility to
993
+ make it a valid filtration (using a large enough `filt_value`, or calling `make_filtration_non_decreasing`
994
+ afterwards for instance).
995
+
996
+ :param filtration: New threshold value.
997
+ :type filtration: float.
998
+ :param min_dim: The minimal dimension. Default value is 0.
999
+ :type min_dim: int.
1000
+ """
1001
+ {{if IS_KCRITICAL}}
1002
+ cdef {{PyFil}} cfiltration = _py2kc_{{SHORT_VALUE_TYPE}}(np.asarray(filtration, dtype = {{PYTYPE}})[None,:])
1003
+ {{else}}
1004
+ cdef {{PyFil}} cfiltration = _py21c_{{SHORT_VALUE_TYPE}}(np.asarray(filtration, dtype = {{PYTYPE}}))
1005
+ {{endif}}
1006
+ self.get_ptr().reset_filtration(cfiltration, min_dim)
1007
+ return self
1008
+
1009
+ {{if not IS_KCRITICAL}}
1010
+ def pts_to_indices(self,pts:np.ndarray, simplices_dimensions:Iterable[int]) -> tuple[np.ndarray,np.ndarray]:
1011
+ """
1012
+ Returns the indices of the simplex tree with corresponding filtrations.
1013
+
1014
+ Args:
1015
+ - st: SimplexTreeMulti on which to recover the indices.
1016
+ - pts: (num_pts, num_parameters,) array of points to recover.
1017
+ simplices_dimensions: (num_parameters,) the simplices dimension to take into account for each parameter.
1018
+
1019
+ Returns:
1020
+ - A (m, num_parameters) array containing the found indices (m <= num_pts).
1021
+ - A (m, 2) array containing the non-found indices (pt_index, parameter failing).
1022
+ """
1023
+
1024
+ # TODO detect rank or not
1025
+ cdef bool is_rank_invariant = pts.shape[1] == 2*self.num_parameters
1026
+ if is_rank_invariant:
1027
+ births = pts[:,:self.num_parameters]
1028
+ deaths = pts[:,self.num_parameters:]
1029
+ births_indices,non_recovered_births = self.pts_to_indices(births, simplices_dimensions)
1030
+ deaths_indices,non_recovered_deaths = self.pts_to_indices(deaths, simplices_dimensions)
1031
+ non_recovered_pts = np.concatenate([non_recovered_births,non_recovered_deaths], axis=0)
1032
+ pts_indices = np.concatenate([births_indices,deaths_indices], axis=1)
1033
+ return pts_indices, non_recovered_pts
1034
+
1035
+ cdef vector[vector[{{CTYPE}}]] cpts = pts
1036
+ cdef vector[int] csimplices_dimensions = simplices_dimensions
1037
+ # cdef pair[indices_type,indices_type] out
1038
+ found_indices,not_found_indices = self.get_ptr().pts_to_indices(cpts,csimplices_dimensions)
1039
+ if len(found_indices) == 0:
1040
+ found_indices = np.empty(shape=(0,self.num_parameters), dtype = np.int32)
1041
+ if len(not_found_indices) == 0:
1042
+ not_found_indices = np.empty(shape=(0,2), dtype = np.int32)
1043
+ return np.asarray(found_indices), np.asarray(not_found_indices)
1044
+ ## This function is only meant for the edge collapse interface.
1045
+ def get_edge_list(self):
1046
+ """
1047
+ in the filtration-domination's format
1048
+ """
1049
+ cdef edge_list out
1050
+ with nogil:
1051
+ out = self.get_ptr().get_edge_list()
1052
+ return out
1053
+
1054
+ def collapse_edges(
1055
+ self,
1056
+ int num=1,
1057
+ int max_dimension = 0,
1058
+ bool progress=False,
1059
+ bool strong=True,
1060
+ bool full=False,
1061
+ bool ignore_warning=False,
1062
+ bool auto_clean=True,
1063
+ )->{{PY_CLASS_NAME}}:
1064
+ """Edge collapse for 1-critical 2-parameter clique complex (see https://arxiv.org/abs/2211.05574).
1065
+ It uses the code from the github repository https://github.com/aj-alonso/filtration_domination .
1066
+
1067
+ Parameters
1068
+ ----------
1069
+
1070
+ max_dimension:int
1071
+ Max simplicial dimension of the complex. Unless specified, keeps the same dimension.
1072
+ num:int
1073
+ The number of collapses to do.
1074
+ strong:bool
1075
+ Whether to use strong collapses or standard collapses (slower, but may remove more edges)
1076
+ full:bool
1077
+ Collapses the maximum number of edges if true, i.e., will do (at most) 100 strong collapses and (at most) 100 non-strong collapses afterward.
1078
+ progress:bool
1079
+ If true, shows the progress of the number of collapses.
1080
+
1081
+ WARNING
1082
+ -------
1083
+
1084
+ - This will destroy all of the k-simplices, with k>=2. Be sure to use this with a clique complex, if you want to preserve the homology >= dimension 1.
1085
+ - This is for 1 critical simplices, with 2 parameter persistence.
1086
+ Returns
1087
+ -------
1088
+
1089
+ self:SimplexTreeMulti
1090
+ A (smaller) simplex tree that has the same homology over this bifiltration.
1091
+
1092
+ """
1093
+ # TODO : find a way to do multiple edge collapses without python conversions.
1094
+ if num == 0:
1095
+ return self
1096
+ elif num == -1:
1097
+ num=100
1098
+ full=False
1099
+ elif num == -2:
1100
+ num=100
1101
+ full=True
1102
+ assert self.num_parameters == 2, "Number of parameters has to be 2 to use edge collapses ! This is a limitation of Filtration-domination"
1103
+ if self.dimension > 1 and not ignore_warning: warn("This method ignores simplices of dimension > 1 !")
1104
+
1105
+ max_dimension = self.dimension if max_dimension <=0 else max_dimension
1106
+
1107
+ # Retrieves the edge list, and send it to filration_domination
1108
+ edges = self.get_edge_list()
1109
+ from multipers.multiparameter_edge_collapse import _collapse_edge_list
1110
+ edges = _collapse_edge_list(edges, num=num, full=full, strong=strong, progress=progress)
1111
+ # Retrieves the collapsed simplicial complex
1112
+ self._reconstruct_from_edge_list(edges, swap=True, expand_dimension=max_dimension)
1113
+ if self.is_squeezed and auto_clean:
1114
+ self._clean_filtration_grid()
1115
+ return self
1116
+
1117
+ @cython.inline
1118
+ cdef _reconstruct_from_edge_list(self,edge_list edges, bool swap=True, int expand_dimension=0):
1119
+ """
1120
+ Generates a 1-dimensional copy of self, with the edges given as input. Useful for edge collapses
1121
+
1122
+ Input
1123
+ -----
1124
+
1125
+ - edges : Iterable[(int,int),(float,float)] ## This is the format of the rust library filtration-domination
1126
+ - swap : bool
1127
+ If true, will swap self and the collapsed simplextrees.
1128
+ - expand_dim : int
1129
+ expands back the simplextree to this dimension
1130
+ Ouput
1131
+ -----
1132
+
1133
+ The reduced SimplexTreeMulti having only these edges.
1134
+ """
1135
+ reduced_tree = {{PY_CLASS_NAME}}(num_parameters=self.num_parameters)
1136
+
1137
+ ## Adds vertices back, with good filtration
1138
+ if self.num_vertices > 0:
1139
+ vertices = np.fromiter((splx[0] for splx, f in self.get_skeleton(0)), dtype=np.int32)[None,:]
1140
+ vertices_filtration = np.fromiter((f for splx, f in self.get_skeleton(0)), dtype=np.dtype(({{PYTYPE}},2)))
1141
+ reduced_tree.insert_batch(vertices, vertices_filtration)
1142
+
1143
+ ## Adds edges again
1144
+ if self.num_simplices - self.num_vertices > 0:
1145
+ edges_filtration = np.fromiter(((e.second.first, e.second.second) for e in edges), dtype=np.dtype(({{PYTYPE}},2)))
1146
+ edges_idx = np.fromiter(((e.first.first, e.first.second) for e in edges), dtype=np.dtype((np.int32,2))).T
1147
+ reduced_tree.insert_batch(edges_idx, edges_filtration)
1148
+ if swap:
1149
+ # Swaps the simplextrees pointers
1150
+ self.thisptr, reduced_tree.thisptr = reduced_tree.thisptr, self.thisptr # Swaps self and reduced tree (self is a local variable)
1151
+ if expand_dimension > 0:
1152
+ self.expansion(expand_dimension) # Expands back the simplextree to the original dimension.
1153
+ return self if swap else reduced_tree
1154
+ {{endif}}
1155
+
1156
+ @property
1157
+ def num_parameters(self)->int:
1158
+ return self.get_ptr().num_parameters()
1159
+ def get_simplices_of_dimension(self, dim:int)->np.ndarray:
1160
+ cdef vector[int] out = self.get_ptr().get_simplices_of_dimension(dim)
1161
+ cdef int[:] out_view = <int[:out.size()]>(&(out[0]))
1162
+ return np.array(out_view).reshape(-1, dim+1)
1163
+ def key(self, simplex:list|np.ndarray):
1164
+ return self.get_ptr().get_key(simplex)
1165
+ def set_keys_to_enumerate(self)->None:
1166
+ self.get_ptr().set_keys_to_enumerate()
1167
+ return
1168
+ def set_key(self,simplex:list|np.ndarray, key:int)->None:
1169
+ self.get_ptr().set_key(simplex, key)
1170
+ return
1171
+
1172
+
1173
+
1174
+ def _to_scc(self,filtration_dtype={{PYTYPE}}, bool flattened=False):
1175
+ """
1176
+ Turns a simplextree into a (simplicial) module presentation.
1177
+ """
1178
+ cdef bool is_function_st = self._is_function_simplextree
1179
+
1180
+ cdef pair[vector[vector[{{CTYPE}}]], boundary_matrix] out
1181
+ if flattened:
1182
+ # out = simplextree_to_ordered_bf(cptr)
1183
+ out = self.get_ptr().simplextree_to_ordered_bf()
1184
+ return np.asarray(out.first,dtype=filtration_dtype), tuple(out.second)
1185
+ if is_function_st:
1186
+ {{if not IS_KCRITICAL}}
1187
+ blocks = self.get_ptr().function_simplextree_to_scc()
1188
+ {{else}}
1189
+ raise Exception("Kcritical cannot be a function simplextree ?? TODO: Fixme")
1190
+ {{endif}}
1191
+ else:
1192
+ {{if not IS_KCRITICAL}}
1193
+ blocks = self.get_ptr().simplextree_to_scc()
1194
+ {{else}}
1195
+ blocks = self.get_ptr().kcritical_simplextree_to_scc()
1196
+ {{endif}}
1197
+ # reduces the space in memory
1198
+ if is_function_st:
1199
+ blocks = [(tuple(f), tuple(b)) for f,b in blocks[::-1]]
1200
+ else:
1201
+ {{if not IS_KCRITICAL}}
1202
+ blocks = [(np.asarray(f,dtype=filtration_dtype), tuple(b)) for f,b in blocks[::-1]] ## presentation is on the other order
1203
+ {{else}}
1204
+ blocks = [(tuple(f), tuple(b)) for f,b in blocks[::-1]] ## presentation is on the other order
1205
+ {{endif}}
1206
+ return blocks # +[(np.empty(0,dtype=filtration_dtype),[])] ## TODO : investigate
1207
+
1208
+ def to_scc_kcritical(self,
1209
+ path:os.PathLike|str,
1210
+ bool rivet_compatible=False,
1211
+ bool strip_comments=False,
1212
+ bool ignore_last_generators=False,
1213
+ bool overwrite=False,
1214
+ bool reverse_block=False,
1215
+ ):
1216
+ """
1217
+ TODO: function-simplextree, from squeezed
1218
+ """
1219
+ from os.path import exists
1220
+ from os import remove
1221
+ if exists(path):
1222
+ if not(overwrite):
1223
+ raise Exception(f"The file {path} already exists. Use the `overwrite` flag if you want to overwrite.")
1224
+ remove(path)
1225
+ # blocks = simplextree2scc(self)
1226
+ blocks = self._to_scc()
1227
+ from multipers.io import scc2disk
1228
+ scc2disk(blocks,
1229
+ path=path,
1230
+ num_parameters=self.num_parameters,
1231
+ reverse_block=reverse_block,
1232
+ rivet_compatible=rivet_compatible,
1233
+ ignore_last_generators=ignore_last_generators,
1234
+ strip_comments=strip_comments,
1235
+ )
1236
+
1237
+ def to_scc_function_st(self,
1238
+ path="scc_dataset.scc",
1239
+ bool rivet_compatible=False,
1240
+ bool strip_comments=False,
1241
+ bool ignore_last_generators=False,
1242
+ bool overwrite=False,
1243
+ bool reverse_block=True,
1244
+ ):
1245
+ from multipers.io import scc2disk
1246
+ from warnings import warn
1247
+ warn("This function is not tested yet.")
1248
+ from os.path import exists
1249
+ from os import remove
1250
+ if exists(path):
1251
+ if not(overwrite):
1252
+ raise Exception(f"The file {path} already exists. Use the `overwrite` flag if you want to overwrite.")
1253
+ remove(path)
1254
+ stuff = self._to_scc(self)
1255
+ if reverse_block: stuff.reverse()
1256
+ cdef int num_parameters = self.num_parameters
1257
+ with open(path, "w") as f:
1258
+ f.write("scc2020\n") if not rivet_compatible else f.write("firep\n")
1259
+ if not strip_comments and not rivet_compatible: f.write("# Number of parameters\n")
1260
+ num_parameters = self.num_parameters
1261
+ if rivet_compatible:
1262
+ assert num_parameters == 2
1263
+ f.write("Filtration 1\n")
1264
+ f.write("Filtration 2\n")
1265
+ else:
1266
+ f.write(f"{num_parameters}\n")
1267
+
1268
+ if not strip_comments: f.write("# Sizes of generating sets\n")
1269
+ for block in stuff: f.write(f"{len(block[1])} ")
1270
+ f.write("\n")
1271
+
1272
+ for i,block in enumerate(stuff):
1273
+ if (rivet_compatible or ignore_last_generators) and i == len(stuff)-1: continue
1274
+ if not strip_comments: f.write(f"# Block of dimension {len(stuff)-i}\n")
1275
+ for filtration, boundary in zip(*block):
1276
+ k = len(filtration)
1277
+ for j in range(k):
1278
+ if filtration[j] != np.inf:
1279
+ line = f"{filtration[j]} {k} ; " + " ".join([str(x) for x in boundary]) +"\n"
1280
+ f.write(line)
1281
+ def to_scc(self,**kwargs):
1282
+ """
1283
+ Returns an scc representation of the simplextree.
1284
+ """
1285
+ if self._is_function_simplextree:
1286
+ return self.to_scc_function_st(**kwargs)
1287
+ else:
1288
+ return self.to_scc_kcritical(**kwargs)
1289
+
1290
+ def to_rivet(self, path="rivet_dataset.txt", degree:int|None = None, progress:bool=False, overwrite:bool=False, xbins:int|None=None, ybins:int|None=None)->None:
1291
+ """ Create a file that can be imported by rivet, representing the filtration of the simplextree.
1292
+
1293
+ Parameters
1294
+ ----------
1295
+
1296
+ path:str
1297
+ path of the file.
1298
+ degree:int
1299
+ The homological degree to ask rivet to compute.
1300
+ progress:bool = True
1301
+ Shows the progress bar.
1302
+ overwrite:bool = False
1303
+ If true, will overwrite the previous file if it already exists.
1304
+ """
1305
+ ...
1306
+ from os.path import exists
1307
+ from os import remove
1308
+ if exists(path):
1309
+ if not(overwrite):
1310
+ print(f"The file {path} already exists. Use the `overwrite` flag if you want to overwrite.")
1311
+ return
1312
+ remove(path)
1313
+ file = open(path, "a")
1314
+ file.write("# This file was generated by multipers.\n")
1315
+ file.write("--datatype bifiltration\n")
1316
+ file.write(f"--homology {degree}\n") if degree is not None else None
1317
+ file.write(f"-x {xbins}\n") if xbins is not None else None
1318
+ file.write(f"-y {ybins}\n") if ybins is not None else None
1319
+ file.write("--xlabel time of appearance\n")
1320
+ file.write("--ylabel density\n\n")
1321
+ from tqdm import tqdm
1322
+ with tqdm(total=self.num_simplices, position=0, disable = not(progress), desc="Writing simplex to file") as bar:
1323
+ for dim in range(0,self.dimension+1): # Not sure if dimension sort is necessary for rivet. Check ?
1324
+ file.write(f"# block of dimension {dim}\n")
1325
+ for s,F in self.get_skeleton(dim):
1326
+ if len(s) != dim+1: continue
1327
+ for i in s:
1328
+ file.write(str(i) + " ")
1329
+ file.write("; ")
1330
+ for f in F:
1331
+ {{if IS_KCRITICAL}}
1332
+ for fi in f:
1333
+ file.write(str(fi) + " ")
1334
+ {{else}}
1335
+ file.write(str(f) + " ")
1336
+ {{endif}}
1337
+ file.write("\n")
1338
+ bar.update(1)
1339
+ file.close()
1340
+ return
1341
+
1342
+
1343
+
1344
+ def _get_filtration_values(self, vector[int] degrees, bool inf_to_nan:bool=False, bool return_raw = False)->Iterable[np.ndarray]:
1345
+ # cdef vector[int] c_degrees = degrees
1346
+ # out = get_filtration_values_from_ptr[{{PyFil}}](ptr, degrees)
1347
+ cdef intptr_t ptr = self.thisptr
1348
+ cdef vector[vector[vector[{{CTYPE}}]]] out
1349
+ with nogil:
1350
+ out = self.get_ptr().get_filtration_values(degrees)
1351
+ filtrations_values = [np.asarray(filtration) for filtration in out]
1352
+ # Removes infs
1353
+ if inf_to_nan and np.dtype(filtrations_values[0].dtype).kind == 'f':
1354
+ for i,f in enumerate(filtrations_values):
1355
+ filtrations_values[i][f == np.inf] = np.nan
1356
+ filtrations_values[i][f == - np.inf] = np.nan
1357
+ return filtrations_values
1358
+ def _clean_filtration_grid(self):
1359
+ """
1360
+ Removes the values in filtration_grid that are not linked to any splx.
1361
+ """
1362
+ if not self.is_squeezed:
1363
+ raise ValueError("No grid to clean.")
1364
+ F = self.filtration_grid
1365
+ self.filtration_grid=None
1366
+ cleaned_coordinates = compute_grid(self)
1367
+ new_st = self.grid_squeeze(cleaned_coordinates)
1368
+
1369
+ self.thisptr, new_st.thisptr = new_st.thisptr, self.thisptr
1370
+ self.filtration_grid = tuple(f[g] for f,g in zip(F,cleaned_coordinates))
1371
+ return self
1372
+
1373
+
1374
+
1375
+ def get_filtration_grid(self, resolution:Iterable[int]|None=None,
1376
+ degrees:Iterable[int]|None=None,
1377
+ drop_quantiles:float|tuple=0,
1378
+ grid_strategy:_available_strategies="exact",
1379
+ threshold_min=None,
1380
+ threshold_max=None,)->Iterable[np.ndarray]:
1381
+ """
1382
+ Returns a grid over the n-filtration, from the simplextree. Usefull for grid_squeeze. TODO : multicritical
1383
+
1384
+ Parameters
1385
+ ----------
1386
+
1387
+ resolution: list[int]
1388
+ resolution of the grid, for each parameter
1389
+ box=None : pair[list[float]]
1390
+ Grid bounds. format : [low bound, high bound]
1391
+ If None is given, will use the filtration bounds of the simplextree.
1392
+ grid_strategy="regular" : string
1393
+ Either "regular", "quantile", or "exact".
1394
+ Returns
1395
+ -------
1396
+
1397
+ List of filtration values, for each parameter, defining the grid.
1398
+ """
1399
+ if degrees is None:
1400
+ degrees = range(self.dimension+1)
1401
+
1402
+
1403
+ ## preprocesses the filtration values:
1404
+ filtrations_values = np.concatenate(self._get_filtration_values(degrees, inf_to_nan=True), axis=1)
1405
+ # removes duplicate + sort (nan at the end)
1406
+ filtrations_values = [np.unique(filtration) for filtration in filtrations_values]
1407
+ # removes nan
1408
+ filtrations_values = [filtration[:-1] if np.isnan(filtration[-1]) else
1409
+ filtration for filtration in filtrations_values]
1410
+
1411
+ return compute_grid(filtrations_values,
1412
+ resolution=resolution,strategy=grid_strategy,drop_quantiles=drop_quantiles,
1413
+ threshold_min=threshold_min,
1414
+ threshold_max=threshold_max)
1415
+
1416
+
1417
+ def grid_squeeze(
1418
+ self,
1419
+ filtration_grid:np.ndarray|list|None=None,
1420
+ bool coordinate_values=True,
1421
+ str strategy:_available_strategies = "exact",
1422
+ resolution:Optional[int|list[int]] = None,
1423
+ bool coordinates = False,
1424
+ grid_strategy=None,
1425
+ bool inplace=False,
1426
+ threshold_min=None,
1427
+ threshold_max=None,
1428
+ **filtration_grid_kwargs
1429
+ )->{{COARSENNED_PY_CLASS_NAME}} | {{PY_CLASS_NAME}}:
1430
+ """
1431
+ Fit the filtration of the simplextree to a grid.
1432
+
1433
+ :param filtration_grid: The grid on which to squeeze. An example of grid can be given by the `get_filtration_grid` method.
1434
+ :type filtration_grid: list[list[float]]
1435
+ :param coordinate_values: If true, the filtrations values of the simplices will be set to the coordinate of the filtration grid.
1436
+ :type coordinate_values: bool
1437
+ """
1438
+
1439
+ if grid_strategy is not None:
1440
+ warn("`grid_strategy` is deprecated, use `strategy` instead.",DeprecationWarning)
1441
+ strategy=grid_strategy
1442
+
1443
+ if self.is_squeezed:
1444
+ warn("(copy warning) Squeezing an already squeezed slicer.")
1445
+ temp = self.unsqueeze()
1446
+ subgrid = compute_grid(
1447
+ self.filtration_grid, strategy=strategy, resolution=resolution,
1448
+ threshold_min=threshold_min, threshold_max=threshold_max
1449
+ )
1450
+ return temp.grid_squeeze(sanitize_grid(subgrid, numpyfy=True), coordinates=coordinates, inplace=inplace)
1451
+
1452
+ #TODO : multi-critical
1453
+ if filtration_grid is None:
1454
+ filtration_grid = self.get_filtration_grid(grid_strategy=strategy,
1455
+ resolution=resolution,
1456
+ threshold_min=threshold_min,
1457
+ threshold_max=threshold_max,
1458
+ **filtration_grid_kwargs)
1459
+ else:
1460
+ filtration_grid = sanitize_grid(filtration_grid)
1461
+ if len(filtration_grid) != self.num_parameters:
1462
+ raise ValueError(f"Invalid grid to squeeze onto. Got {len(filtration_grid)=} != {self.num_parameters=}.")
1463
+ api = api_from_tensor(filtration_grid[0])
1464
+ cdef vector[vector[double]] c_filtration_grid = tuple(api.asnumpy(f).astype(np.float64) for f in filtration_grid) # may be faster with loop on views
1465
+ if coordinate_values and inplace:
1466
+ self.filtration_grid = filtration_grid
1467
+ if inplace or not coordinate_values:
1468
+ self.get_ptr().squeeze_filtration_inplace(c_filtration_grid, coordinate_values)
1469
+ else:
1470
+ out = {{COARSENNED_PY_CLASS_NAME}}(num_parameters=self.num_parameters)
1471
+ self.get_ptr().squeeze_filtration(out.thisptr, c_filtration_grid)
1472
+ out.filtration_grid = filtration_grid
1473
+ return out
1474
+ return self
1475
+
1476
+ def unsqueeze(self, grid=None)->{{REAL_PY_CLASS_NAME}}:
1477
+ from multipers.grids import sanitize_grid
1478
+ grid = self.filtration_grid if grid is None else grid
1479
+
1480
+ cdef vector[vector[double]] cgrid = sanitize_grid(grid, numpyfy=True)
1481
+ new_slicer = {{REAL_PY_CLASS_NAME}}()
1482
+ new_slicer.get_ptr().unsqueeze_filtration(self.thisptr, cgrid)
1483
+
1484
+ return new_slicer
1485
+
1486
+ @property
1487
+ def is_squeezed(self)->bool:
1488
+ return self.num_vertices > 0 and self.filtration_grid is not None and len(self.filtration_grid)>0 and len(self.filtration_grid[0]) > 0
1489
+
1490
+ @property
1491
+ def dtype(self)->type:
1492
+ return {{PYTYPE}}
1493
+ @property
1494
+ def ftype(self)->type:
1495
+ return "{{PyFil}}"
1496
+ @property
1497
+ def filtration_container(self)->type:
1498
+ return "{{SHORT_FILTRATION_TYPE}}"
1499
+ def filtration_bounds(self, degrees:Iterable[int]|None=None, q:float|tuple=0, split_dimension:bool=False)->np.ndarray:
1500
+ """
1501
+ Returns the filtrations bounds of the finite filtration values.
1502
+ """
1503
+ try:
1504
+ a,b =q
1505
+ except:
1506
+ a,b,=q,q
1507
+ degrees = range(self.dimension+1) if degrees is None else degrees
1508
+ filtrations_values = self._get_filtration_values(degrees, inf_to_nan=True) ## degree, parameter, pt
1509
+ boxes = np.array([np.nanquantile(filtration, [a, 1-b], axis=1) for filtration in filtrations_values],dtype=float)
1510
+ if split_dimension: return boxes
1511
+ return np.asarray([np.nanmin(boxes, axis=(0,1)), np.nanmax(boxes, axis=(0,1))]) # box, birth/death, filtration
1512
+
1513
+
1514
+
1515
+
1516
+ def fill_lowerstar(self, F, int parameter)->{{PY_CLASS_NAME}}:
1517
+ """ Fills the `dimension`th filtration by the lower-star filtration defined by F.
1518
+
1519
+ Parameters
1520
+ ----------
1521
+
1522
+ F:1d array
1523
+ The density over the vertices, that induces a lowerstar filtration.
1524
+ parameter:int
1525
+ Which filtration parameter to fill. /!\ python starts at 0.
1526
+
1527
+ Returns
1528
+ -------
1529
+
1530
+ self:SimplexTreeMulti
1531
+ """
1532
+ cdef vector[{{CTYPE}}] c_F = _py2p_{{SHORT_VALUE_TYPE}}(np.asarray(F,dtype={{PYTYPE}}))
1533
+ with nogil:
1534
+ self.get_ptr().fill_lowerstar(c_F, parameter)
1535
+ return self
1536
+
1537
+ def fill_distance_matrix(self, distance_matrix, int parameter, {{CTYPE}} node_value = 0)->{{PY_CLASS_NAME}}:
1538
+ """
1539
+ Fills a specific parameter with a rips filtration with the given matrix.
1540
+
1541
+ Warning. Undefined behaviour if `node_value` is not finite or not
1542
+ smaller or equal to `min(distance_matrix)`.
1543
+
1544
+ Parameters:
1545
+ - `distance_matrix`: a (N,N) matrix aligned with the vertices keys, i.e.,
1546
+ `distance_matrix[i,j]` is the distance between simplices [i] and [j].
1547
+ - `node_value`: a float giving the values to the nodes in this filtration.
1548
+
1549
+ Returns
1550
+ self. The updated simplextree.
1551
+ """
1552
+ assert parameter < self.num_parameters, f"This simplextree has only {self.num_parameters} parameters. Cannot fill parameter {parameter}"
1553
+ cdef {{CTYPE}}[:,:] c_distance_matrix = np.asarray(distance_matrix,dtype={{PYTYPE}})
1554
+
1555
+ ## Resets the filtration. should be optimizable
1556
+ F = np.zeros(shape = self.num_vertices, dtype={{PYTYPE}}) + node_value
1557
+ cdef vector[{{CTYPE}}] c_F = _py2p_{{SHORT_VALUE_TYPE}}(F)
1558
+
1559
+ with nogil:
1560
+ self.get_ptr().fill_lowerstar(c_F, parameter)
1561
+
1562
+ ## Fills the matrix in the 1-skeleton
1563
+ cdef Simplex_tree_multi_skeleton_iterator[{{PyFil}}] it = self.get_ptr().get_skeleton_iterator_begin(1)
1564
+ cdef Simplex_tree_multi_skeleton_iterator[{{PyFil}}] end = self.get_ptr().get_skeleton_iterator_end(1)
1565
+ cdef int num_parameters = self.get_ptr().num_parameters()
1566
+ cdef int num_generators
1567
+ cdef int N = c_distance_matrix.shape[0]
1568
+ cdef {{CTYPE}}* f
1569
+ with nogil:
1570
+ while it != end:
1571
+ pair = self.get_ptr().get_simplex_and_filtration(dereference(it))
1572
+ if pair.first.size() == 2:
1573
+ i = pair.first[0]
1574
+ j = pair.first[1]
1575
+ assert i<N, f"Got vertex {i} which is greater than the matrix's size {c_distance_matrix.shape}."
1576
+ assert j<N, f"Got vertex {j} which is greater than the matrix's size {c_distance_matrix.shape}."
1577
+
1578
+ {{if IS_KCRITICAL}}
1579
+ num_generators =pair.second.num_generators()
1580
+ for k in range(num_generators):
1581
+ # Weird cython bug with "Cannot assign to or delete this" otherwise
1582
+ f = &(dereference(pair.second)(k,parameter))
1583
+ f[0] = c_distance_matrix[i,j]
1584
+ {{else}}
1585
+ f = &(dereference(pair.second)(0,parameter))
1586
+ f[0] = c_distance_matrix[i,j]
1587
+ {{endif}}
1588
+ preincrement(it)
1589
+
1590
+ self.make_filtration_non_decreasing()
1591
+ return self
1592
+
1593
+
1594
+
1595
+
1596
+
1597
+ def project_on_line(self, parameter:int=0, basepoint:None|list|np.ndarray= None, direction:None|list|np.ndarray= None)->SimplexTree:
1598
+ """Converts an multi simplextree to a gudhi simplextree.
1599
+
1600
+ Parameters
1601
+ ----------
1602
+
1603
+ parameter:int = 0
1604
+ The parameter to keep. WARNING will crash if the multi simplextree is not well filled.
1605
+ basepoint:None
1606
+ Instead of keeping a single parameter, will consider the filtration defined by the diagonal line crossing the basepoint.
1607
+
1608
+ WARNING
1609
+ -------
1610
+
1611
+ There are no safeguard yet, it WILL crash if asking for a parameter that is not filled.
1612
+
1613
+ Returns
1614
+ -------
1615
+
1616
+ A SimplexTree with chosen 1D filtration.
1617
+ """
1618
+ # FIXME : deal with multicritical filtrations
1619
+ import gudhi as gd
1620
+ new_simplextree = gd.SimplexTree()
1621
+ assert parameter < self.get_ptr().num_parameters()
1622
+ cdef int c_parameter = parameter
1623
+ cdef intptr_t old_ptr = self.thisptr
1624
+ cdef intptr_t new_ptr = new_simplextree.thisptr
1625
+ if basepoint is None:
1626
+ basepoint = np.array([np.inf]*self.get_ptr().num_parameters())
1627
+ basepoint[parameter] = 0
1628
+ if direction is None:
1629
+ direction = np.array([0]*self.get_ptr().num_parameters())
1630
+ direction[parameter] = 1
1631
+
1632
+ basepoint = np.asarray(basepoint, dtype=np.float64)
1633
+ direction = np.asarray(direction, dtype=np.float64)
1634
+ cdef vector[double] c_basepoint = _py2p_f64(basepoint)
1635
+ cdef vector[double] c_direction = _py2p_f64(direction)
1636
+ cdef Line[double] c_line = Line[double](c_basepoint, c_direction)
1637
+ with nogil:
1638
+ self.get_ptr().to_std(new_ptr, c_line, c_parameter)
1639
+ return new_simplextree
1640
+
1641
+ def linear_projections(self, linear_forms:np.ndarray)->Iterable[SimplexTree]:
1642
+ """
1643
+ Compute the 1-parameter projections, w.r.t. given the linear forms, of this simplextree.
1644
+
1645
+ Input
1646
+ -----
1647
+
1648
+ Array of shape (num_linear_forms, num_parameters)
1649
+
1650
+ Output
1651
+ ------
1652
+
1653
+ List of projected (gudhi) simplextrees.
1654
+ """
1655
+ cdef Py_ssize_t num_projections = linear_forms.shape[0]
1656
+ cdef Py_ssize_t num_parameters = linear_forms.shape[1]
1657
+ if num_projections == 0: return []
1658
+ cdef vector[vector[double]] c_linear_forms = linear_forms
1659
+ assert num_parameters==self.num_parameters, f"The linear forms has to have the same number of parameter as the simplextree ({self.num_parameters})."
1660
+
1661
+ # Gudhi copies are faster than inserting simplices one by one
1662
+ import gudhi as gd
1663
+ # flattened_simplextree = gd.SimplexTree()
1664
+ # cdef intptr_t multi_prt = self.thisptr
1665
+ # cdef intptr_t flattened_ptr = flattened_simplextree.thisptr
1666
+ # with nogil:
1667
+ # # flatten_from_ptr(multi_prt, flattened_ptr, num_parameters)
1668
+ # self.get_ptr().to_std(flattened_ptr, num_parameters)
1669
+ flattened_simplextree = self.project_on_line()
1670
+ out = [flattened_simplextree] + [gd.SimplexTree(flattened_simplextree) for _ in range(num_projections-1)]
1671
+
1672
+ # Fills the 1-parameter simplextrees.
1673
+ cdef vector[intptr_t] out_ptrs = [st.thisptr for st in out]
1674
+ with nogil:
1675
+ for i in range(num_projections):
1676
+ self.get_ptr().to_std_linear_projection(out_ptrs[i], c_linear_forms[i])
1677
+ return out
1678
+
1679
+
1680
+ def set_num_parameter(self, num:int):
1681
+ """
1682
+ Sets the numbers of parameters.
1683
+ WARNING : it will resize all the filtrations to this size.
1684
+ """
1685
+ self.get_ptr().resize_all_filtrations(num)
1686
+ self.get_ptr().set_num_parameters(num)
1687
+ return
1688
+
1689
+ def __eq__(self, other:{{PY_CLASS_NAME}}):
1690
+ """Test for structural equality
1691
+ :returns: True if the 2 simplex trees are equal, False otherwise.
1692
+ :rtype: bool
1693
+ """
1694
+ return dereference(self.get_ptr()) == dereference(other.get_ptr())
1695
+
1696
+
1697
+
1698
+
1699
+
1700
+ # def _simplextree_multify_{{PyFil}}(simplextree:SimplexTree, int num_parameters, default_values=[])->{{PY_CLASS_NAME}}:
1701
+ # """Converts a gudhi simplextree to a multi simplextree.
1702
+ # Parameters
1703
+ # ----------
1704
+
1705
+ # parameters:int = 2
1706
+ # The number of filtrations
1707
+
1708
+ # Returns
1709
+ # -------
1710
+
1711
+ # A multi simplextree, with first filtration value being the one from the original simplextree.
1712
+ # """
1713
+ # if isinstance(simplextree, {{PY_CLASS_NAME}}):
1714
+ # return simplextree
1715
+ # st = {{PY_CLASS_NAME}}(num_parameters=num_parameters)
1716
+ # cdef intptr_t old_ptr = simplextree.thisptr
1717
+ # {{if IS_KCRITICAL}}
1718
+ # cdef {{PyFil}} c_default_values= _py2kc_{{SHORT_VALUE_TYPE}}([default_values])
1719
+ # {{else}}
1720
+ # cdef {{PyFil}} c_default_values= _py21c_{{SHORT_VALUE_TYPE}}([default_values])
1721
+ # {{endif}}
1722
+ # with nogil:
1723
+ # st.get_ptr().from_std(old_ptr, num_parameters, c_default_values)
1724
+ # return st
1725
+
1726
+ @cython.boundscheck(False)
1727
+ @cython.wraparound(False)
1728
+ def _safe_simplextree_multify_{{PyFil}}(simplextree:SimplexTree,int num_parameters=2, cnp.ndarray default_values=np.array(-np.inf))->{{PY_CLASS_NAME}}:
1729
+ if isinstance(simplextree, {{PY_CLASS_NAME}}):
1730
+ return simplextree
1731
+ simplices = [[] for _ in range(simplextree.dimension()+1)]
1732
+ filtration_values = [[] for _ in range(simplextree.dimension()+1)]
1733
+ st_multi = {{PY_CLASS_NAME}}(num_parameters=1)
1734
+ if num_parameters > 1:
1735
+ st_multi.set_num_parameter(num_parameters)
1736
+ if default_values.squeeze().ndim == 0:
1737
+ default_values = np.zeros(num_parameters-1) + default_values
1738
+
1739
+ # TODO : Optimize with Python.h
1740
+ for s,f in simplextree.get_simplices():
1741
+ filtration_values[len(s)-1].append(np.concatenate([[f],default_values]))
1742
+ simplices[len(s)-1].append(s)
1743
+ for batch_simplices, batch_filtrations in zip(simplices,filtration_values):
1744
+ st_multi.insert_batch(np.asarray(batch_simplices, dtype=np.int32).T, np.asarray(batch_filtrations, dtype={{PYTYPE}}))
1745
+ return st_multi
1746
+
1747
+
1748
+
1749
+ @cython.boundscheck(False)
1750
+ @cython.wraparound(False)
1751
+ def _safe_simplextree_multify_{{PyFil}}2(simplextree:SimplexTree,int num_parameters=2, cnp.ndarray default_values=np.array(-np.inf))->{{PY_CLASS_NAME}}:
1752
+ if isinstance(simplextree, {{PY_CLASS_NAME}}):
1753
+ return simplextree
1754
+ st_multi = {{PY_CLASS_NAME}}(num_parameters=num_parameters)
1755
+ if default_values.squeeze().ndim == 0:
1756
+ default_values = np.zeros(num_parameters-1) + default_values
1757
+ cdef int num_simplices = simplextree.num_simplices()
1758
+ cdef int max_dim = simplextree.dimension()
1759
+ dims = np.fromiter((len(s)-1 for s,_ in simplextree.get_simplices()), dtype=np.int32, count=num_simplices)
1760
+ dims = np.unique_counts(dims).counts
1761
+ cdef int[:] cdims = np.cumsum(np.concatenate([[0],dims])).astype(np.int32)
1762
+
1763
+ # filtration_values = np.asarray([np.concatenate(([f],default_values)) for s,f in simplextree.get_simplices()])
1764
+ cdef double [:,:] filtration_values = np.fromiter((np.concatenate((np.asarray(f)[None],default_values)) for s,f in simplextree.get_simplices()), dtype=np.dtype((np.float64,num_parameters)), count=num_simplices)
1765
+
1766
+ cdef int32_t[:,:] batch_simplices = np.fromiter((np.concatenate([s,np.empty(max_dim+2-len(s))]) for s,f in simplextree.get_simplices()), dtype=np.dtype((np.int32,max_dim+2)), count=num_simplices)
1767
+ # print(filtration_values.shape, batch_simplices.shape)
1768
+ for i in range(max_dim+1):
1769
+ S = batch_simplices[cdims[i]:cdims[i+1]][:,:i+1].T
1770
+ F = filtration_values[cdims[i]:cdims[i+1]]
1771
+ st_multi.insert_batch(S, F)
1772
+ # print(f"adding {S.shape,F.shape}")
1773
+ return st_multi
1774
+ {{endfor}}
1775
+
1776
+ global available_simplextrees, SimplexTreeMulti_type
1777
+ available_simplextrees = tuple((
1778
+ {{for D in to_iter}}
1779
+ # WARN : updating locals {{locals().update(D)}}
1780
+ {{PY_CLASS_NAME}},
1781
+ {{endfor}}
1782
+ ))
1783
+ SimplexTreeMulti_type:type= Union[
1784
+ {{for D in to_iter}}
1785
+ # WARN : updating locals {{locals().update(D)}}
1786
+ {{PY_CLASS_NAME}},
1787
+ {{endfor}}
1788
+ ]
1789
+
1790
+ def is_simplextree_multi(input)->bool:
1791
+ return (False
1792
+ {{for D in to_iter}}
1793
+ # WARN : updating locals {{locals().update(D)}}
1794
+ or isinstance(input, {{D["PY_CLASS_NAME"]}})
1795
+ {{endfor}}
1796
+ )
1797
+
1798
+
1799
+
1800
+
1801
+
1802
+ def SimplexTreeMulti(input=None, int num_parameters=-1, dtype:type = np.float64, bool kcritical = False, ftype="Contiguous", **kwargs) -> SimplexTreeMulti_type:
1803
+ """SimplexTreeMulti constructor.
1804
+
1805
+ :param other: If `other` is `None` (default value), an empty `SimplexTreeMulti` is created.
1806
+ If `other` is a `SimplexTree`, the `SimplexTreeMulti` is constructed from a deep copy of `other`.
1807
+ If `other` is a `SimplexTreeMulti`, the `SimplexTreeMulti` is constructed from a deep copy of `other`.
1808
+ :type other: SimplexTree or SimplexTreeMulti (Optional)
1809
+ :param num_parameters: The number of parameter of the multi-parameter filtration.
1810
+ :type num_parameters: int
1811
+ :returns: An empty or a copy simplex tree.
1812
+ :rtype: SimplexTreeMulti
1813
+
1814
+ :raises TypeError: In case `other` is neither `None`, nor a `SimplexTree`, nor a `SimplexTreeMulti`.
1815
+ """
1816
+ {{for D in to_iter}}
1817
+ # WARN: updating locals {{locals().update(D)}}
1818
+ if dtype == np.dtype({{PYTYPE}}) and kcritical == {{IS_KCRITICAL}} and ftype == "{{SHORT_FILTRATION_TYPE}}":
1819
+ return {{PY_CLASS_NAME}}(input, num_parameters=num_parameters, **kwargs)
1820
+ {{endfor}}
1821
+ raise TypeError(f"No SimplexTreeMulti implementation for dtype={dtype}, kcritical={kcritical}, ftype={ftype}.")
1822
+
1823
+
1824
+
1825
+ ## SIGNED MEASURES STUFF
1826
+ from multipers.grids import sms_in_grid
1827
+
1828
+ ctypedef int32_t indices_type # uint fails for some reason
1829
+ python_indices_type=np.int32
1830
+
1831
+ ctypedef int32_t tensor_dtype
1832
+ python_tensor_dtype = np.int32
1833
+
1834
+
1835
+ ctypedef pair[vector[vector[indices_type]], vector[tensor_dtype]] signed_measure_type
1836
+
1837
+ cdef extern from "multi_parameter_rank_invariant/hilbert_function.h" namespace "Gudhi::multiparameter::hilbert_function":
1838
+ {{for D in to_iter}}
1839
+ # WARN: updating locals {{locals().update(D)}}
1840
+ signed_measure_type get_hilbert_signed_measure({{ST_INTERFACE}}&, tensor_dtype* , const vector[indices_type], const vector[indices_type], bool, indices_type, bool, bool) except + nogil
1841
+ {{endfor}}
1842
+
1843
+
1844
+
1845
+
1846
+ ## Aligns python/cpp
1847
+ cdef inline signed_measure_type _hilbert_sm_from_simplextree(object simplextree, tensor_dtype* container_ptr, const vector[indices_type]& c_grid_shape, const vector[indices_type]& degrees, bool zero_pad, indices_type n_jobs, bool verbose, bool expand_collapse):
1848
+ cdef intptr_t simplextree_ptr = simplextree.thisptr
1849
+ {{for D in to_iter}}
1850
+ # WARN: updating locals {{locals().update(D)}}
1851
+ if isinstance(simplextree, {{PY_CLASS_NAME}}):
1852
+ with nogil:
1853
+ return get_hilbert_signed_measure(dereference(<{{ST_INTERFACE}}*>simplextree_ptr), container_ptr, c_grid_shape, degrees, zero_pad, n_jobs, verbose, expand_collapse)
1854
+ {{endfor}}
1855
+ raise ValueError(f"Input {simplextree} not supported.")
1856
+
1857
+ def _hilbert_signed_measure(simplextree,
1858
+ vector[indices_type] degrees,
1859
+ mass_default=None,
1860
+ plot=False,
1861
+ indices_type n_jobs=0,
1862
+ bool verbose=False,
1863
+ bool expand_collapse=False,
1864
+ # grid_conversion = None,
1865
+ ):
1866
+ """
1867
+ Computes the signed measures given by the decomposition of the hilbert function.
1868
+
1869
+ Input
1870
+ -----
1871
+
1872
+ - simplextree:SimplexTreeMulti, the multifiltered simplicial complex
1873
+ - degrees:array-like of ints, the degrees to compute
1874
+ - mass_default: Either None, or 'auto' or 'inf', or array-like of floats. Where to put the default mass to get a zero-mass measure.
1875
+ - plot:bool, plots the computed measures if true.
1876
+ - n_jobs:int, number of jobs. Defaults to #cpu, but when doing parallel computations of signed measures, we recommend setting this to 1.
1877
+ - verbose:bool, prints c++ logs.
1878
+
1879
+ Output
1880
+ ------
1881
+
1882
+ `[signed_measure_of_degree for degree in degrees]`
1883
+ with `signed_measure_of_degree` of the form `(dirac location, dirac weights)`.
1884
+ """
1885
+
1886
+ assert simplextree.is_squeezed, "Squeeze grid first."
1887
+ cdef bool zero_pad = mass_default is not None
1888
+ # assert simplextree.num_parameters == 2
1889
+ grid_shape = np.array([len(f) for f in simplextree.filtration_grid])
1890
+ if mass_default is None:
1891
+ mass_default = mass_default
1892
+ else:
1893
+ mass_default = np.asarray(mass_default)
1894
+ assert mass_default.ndim == 1 and mass_default.shape[0] == simplextree.num_parameters
1895
+ if zero_pad:
1896
+ for i, _ in enumerate(grid_shape):
1897
+ grid_shape[i] += 1 # adds a 0
1898
+ # if grid_conversion is not None:
1899
+ # grid_conversion = tuple(np.concatenate([f, [mass_default[i]]]) for i,f in enumerate(grid_conversion))
1900
+ assert len(grid_shape) == simplextree.num_parameters, "Grid shape size has to be the number of parameters."
1901
+ grid_shape_with_degree = np.asarray(np.concatenate([[len(degrees)], grid_shape]), dtype=python_indices_type)
1902
+ container_array = np.ascontiguousarray(np.zeros(grid_shape_with_degree, dtype=python_tensor_dtype).flatten())
1903
+ assert len(container_array) < np.iinfo(np.uint32).max, "Too large container. Raise an issue on github if you encounter this issue. (Due to tensor's operator[])"
1904
+ cdef intptr_t simplextree_ptr = simplextree.thisptr
1905
+ cdef vector[indices_type] c_grid_shape = grid_shape_with_degree
1906
+ cdef tensor_dtype[::1] container = container_array
1907
+ cdef tensor_dtype* container_ptr = &container[0]
1908
+ cdef signed_measure_type out = _hilbert_sm_from_simplextree(simplextree, container_ptr, c_grid_shape, degrees, zero_pad, n_jobs, verbose, expand_collapse)
1909
+ pts, weights = np.asarray(out.first, dtype=python_indices_type).reshape(-1, simplextree.num_parameters+1), np.asarray(out.second, dtype=python_tensor_dtype)
1910
+ slices = np.concatenate([np.searchsorted(pts[:,0], np.arange(degrees.size())), [pts.shape[0]] ])
1911
+ sms = [
1912
+ (pts[slices[i]:slices[i+1],1:],weights[slices[i]:slices[i+1]])
1913
+ for i in range(slices.shape[0]-1)
1914
+ ]
1915
+ # if grid_conversion is not None:
1916
+ # sms = sms_in_grid(sms,grid_conversion)
1917
+
1918
+ # if plot:
1919
+ # from multipers.plots import plot_signed_measures
1920
+ # plot_signed_measures(sms)
1921
+ return sms
1922
+
1923
+
1924
+ #### EULER CHAR
1925
+
1926
+ cdef extern from "multi_parameter_rank_invariant/euler_characteristic.h" namespace "Gudhi::multiparameter::euler_characteristic":
1927
+ # void get_euler_surface_python(const intptr_t, tensor_dtype*, const vector[indices_type], bool, bool, bool) except + nogil
1928
+ {{for D in to_iter}}
1929
+ # WARN : updating locals {{locals().update(D)}}
1930
+ signed_measure_type get_euler_signed_measure({{ST_INTERFACE}}&, tensor_dtype* , const vector[indices_type], bool, bool) except + nogil
1931
+ {{endfor}}
1932
+
1933
+
1934
+ ## Aligns python/cpp
1935
+ cdef inline signed_measure_type _euler_sm_from_simplextree(object simplextree, tensor_dtype* container_ptr, const vector[indices_type]& c_grid_shape, bool zero_pad, bool verbose):
1936
+ cdef intptr_t simplextree_ptr = simplextree.thisptr
1937
+ {{for D in to_iter}}
1938
+ # WARN : updating locals {{locals().update(D)}}
1939
+ {{if not IS_KCRITICAL}}
1940
+ if isinstance(simplextree, {{PY_CLASS_NAME}}):
1941
+ with nogil:
1942
+ return get_euler_signed_measure(dereference(<{{ST_INTERFACE}}*>simplextree_ptr), container_ptr, c_grid_shape, zero_pad, verbose)
1943
+ {{endif}}
1944
+ {{endfor}}
1945
+ raise ValueError(f"Input {simplextree} not supported.")
1946
+
1947
+
1948
+ def _euler_signed_measure(simplextree, mass_default=None, bool verbose=False):
1949
+ """
1950
+ Computes the signed measures given by the decomposition of the hilbert function.
1951
+
1952
+ Input
1953
+ -----
1954
+
1955
+ - simplextree:SimplexTreeMulti, the multifiltered simplicial complex
1956
+ - mass_default: Either None, or 'auto' or 'inf', or array-like of floats. Where to put the default mass to get a zero-mass measure.
1957
+ - plot:bool, plots the computed measures if true.
1958
+ - n_jobs:int, number of jobs. Defaults to #cpu, but when doing parallel computations of signed measures, we recommend setting this to 1.
1959
+ - verbose:bool, prints c++ logs.
1960
+
1961
+ Output
1962
+ ------
1963
+
1964
+ `[signed_measure_of_degree for degree in degrees]`
1965
+ with `signed_measure_of_degree` of the form `(dirac location, dirac weights)`.
1966
+ """
1967
+ if not simplextree.is_squeezed:
1968
+ raise ValueError("Squeeze grid first.")
1969
+ cdef bool zero_pad = mass_default is not None
1970
+ # assert simplextree.num_parameters == 2
1971
+ grid_shape = np.array([len(f) for f in simplextree.filtration_grid])
1972
+
1973
+ if mass_default is None:
1974
+ mass_default = mass_default
1975
+ else:
1976
+ mass_default = np.asarray(mass_default)
1977
+ assert mass_default.ndim == 1 and mass_default.shape[0] == simplextree.num_parameters
1978
+ if zero_pad:
1979
+ for i, _ in enumerate(grid_shape):
1980
+ grid_shape[i] += 1 # adds a 0
1981
+ # if grid_conversion is not None:
1982
+ # grid_conversion = tuple(np.concatenate([f, [mass_default[i]]]) for i,f in enumerate(grid_conversion))
1983
+ assert len(grid_shape) == simplextree.num_parameters, "Grid shape size has to be the number of parameters."
1984
+ container_array = np.ascontiguousarray(np.zeros(grid_shape, dtype=python_tensor_dtype).flatten())
1985
+ assert len(container_array) < np.iinfo(python_indices_type).max, "Too large container. Raise an issue on github if you encounter this issue. (Due to tensor's operator[])"
1986
+ cdef intptr_t simplextree_ptr = simplextree.thisptr
1987
+ cdef vector[indices_type] c_grid_shape = grid_shape
1988
+ cdef tensor_dtype[::1] container = container_array
1989
+ cdef tensor_dtype* container_ptr = &container[0]
1990
+ cdef signed_measure_type out
1991
+
1992
+ out = _euler_sm_from_simplextree(simplextree, container_ptr, c_grid_shape, zero_pad, verbose)
1993
+
1994
+ pts, weights = np.asarray(out.first, dtype=python_indices_type).reshape(-1, simplextree.num_parameters), np.asarray(out.second, dtype=python_tensor_dtype)
1995
+ # return pts, weights
1996
+ sm = (pts,weights)
1997
+
1998
+ return sm
1999
+
2000
+
2001
+
2002
+
2003
+
2004
+
2005
+
2006
+
2007
+
2008
+ ## Rank invariant
2009
+
2010
+ cdef extern from "multi_parameter_rank_invariant/rank_invariant.h" namespace "Gudhi::multiparameter::rank_invariant":
2011
+ {{for D in to_iter}}
2012
+ # WARN : updating locals {{locals().update(D)}}
2013
+ void compute_rank_invariant_python({{ST_INTERFACE}}&, tensor_dtype* , const vector[indices_type], const vector[indices_type], indices_type, bool) except + nogil
2014
+ {{endfor}}
2015
+
2016
+
2017
+ ## Aligns python/cpp
2018
+ cdef inline void _rank_sm_from_simplextree(object simplextree, tensor_dtype* container_ptr, const vector[indices_type]& c_grid_shape, const vector[indices_type]& degrees, indices_type n_jobs, bool expand_collapse):
2019
+ cdef intptr_t simplextree_ptr = simplextree.thisptr
2020
+ if False:
2021
+ pass
2022
+ {{for D in to_iter}}
2023
+ # WARN : updating locals {{locals().update(D)}}
2024
+ elif isinstance(simplextree, {{PY_CLASS_NAME}}):
2025
+ with nogil:
2026
+ compute_rank_invariant_python(dereference(<{{ST_INTERFACE}}*>simplextree_ptr), container_ptr, c_grid_shape, degrees, n_jobs, expand_collapse)
2027
+ {{endfor}}
2028
+ else:
2029
+ raise ValueError(f"Input {simplextree} not supported.")
2030
+
2031
+
2032
+ def _rank_signed_measure(simplextree, vector[indices_type] degrees, mass_default=None, plot=False, indices_type n_jobs=0, bool verbose=False, bool expand_collapse=False):
2033
+ """
2034
+ Computes the signed measures given by the decomposition of the hilbert function.
2035
+
2036
+ Input
2037
+ -----
2038
+
2039
+ - simplextree:SimplexTreeMulti, the multifiltered simplicial complex
2040
+ - degrees:array-like of ints, the degrees to compute
2041
+ - mass_default: Either None, or 'auto' or 'inf', or array-like of floats. Where to put the default mass to get a zero-mass measure.
2042
+ - plot:bool, plots the computed measures if true.
2043
+ - n_jobs:int, number of jobs. Defaults to #cpu, but when doing parallel computations of signed measures, we recommend setting this to 1.
2044
+ - verbose:bool, prints c++ logs.
2045
+
2046
+ Output
2047
+ ------
2048
+
2049
+ `[signed_measure_of_degree for degree in degrees]`
2050
+ with `signed_measure_of_degree` of the form `(dirac location, dirac weights)`.
2051
+ """
2052
+ assert simplextree.is_squeezed, "Squeeze grid first."
2053
+ cdef bool zero_pad = mass_default is not None
2054
+ # grid_conversion = [np.asarray(f) for f in simplextree.filtration_grid]
2055
+ # assert simplextree.num_parameters == 2
2056
+ # grid_shape = np.array([len(f) for f in grid_conversion])
2057
+ grid_shape = np.array([len(f) for f in simplextree.filtration_grid])
2058
+
2059
+ if mass_default is None:
2060
+ mass_default = mass_default
2061
+ else:
2062
+ mass_default = np.asarray(mass_default)
2063
+ assert mass_default.ndim == 1 and mass_default.shape[0] == simplextree.num_parameters, "Mass default has to be an array like of shape (num_parameters,)"
2064
+ # if zero_pad:
2065
+ # for i, _ in enumerate(grid_shape):
2066
+ # grid_shape[i] += 1 # adds a 0
2067
+ # grid_conversion = tuple(np.concatenate([f, [mass_default[i]]]) for i,f in enumerate(grid_conversion))
2068
+
2069
+ assert len(grid_shape) == simplextree.num_parameters, "Grid shape size has to be the number of parameters."
2070
+ grid_shape_with_degree = np.asarray(np.concatenate([[len(degrees)], grid_shape, grid_shape]), dtype=python_indices_type)
2071
+ container_array = np.ascontiguousarray(np.zeros(grid_shape_with_degree, dtype=python_tensor_dtype).flatten())
2072
+ assert len(container_array) < np.iinfo(python_indices_type).max, "Too large container. Raise an issue on github if you encounter this issue. (Due to tensor's operator[])"
2073
+ cdef intptr_t simplextree_ptr = simplextree.thisptr
2074
+ cdef vector[indices_type] c_grid_shape = grid_shape_with_degree
2075
+ cdef tensor_dtype[::1] container = container_array
2076
+ cdef tensor_dtype* container_ptr = &container[0]
2077
+ _rank_sm_from_simplextree(simplextree, container_ptr,c_grid_shape,degrees, n_jobs, expand_collapse)
2078
+ rank = container_array.reshape(grid_shape_with_degree)
2079
+ rank = tuple(rank_decomposition_by_rectangles(rank_of_degree, threshold = zero_pad) for rank_of_degree in rank)
2080
+ out = []
2081
+ cdef int num_parameters = simplextree.num_parameters
2082
+ for rank_decomposition in rank:
2083
+ (coords, weights) = sparsify(np.ascontiguousarray(rank_decomposition))
2084
+ births = coords[:,:num_parameters]
2085
+ deaths = coords[:,num_parameters:]
2086
+ correct_indices = np.all(births<=deaths, axis=1) # TODO : correct this
2087
+ coords = coords[correct_indices]
2088
+ weights = weights[correct_indices]
2089
+ if len(correct_indices) == 0:
2090
+ coords, weights = np.empty((0, 2*num_parameters)), np.empty((0))
2091
+ # else:
2092
+ # pts = np.empty(shape=coords.shape, dtype=grid_conversion[0].dtype)
2093
+ # for i in range(pts.shape[1]):
2094
+ # pts[:,i] = grid_conversion[i % num_parameters][coords[:,i]]
2095
+ rank_decomposition = (coords,weights)
2096
+ out.append(rank_decomposition)
2097
+
2098
+ # if plot:
2099
+ # from multipers.plots import plot_signed_measures
2100
+ # plot_signed_measures(out)
2101
+ return out
2102
+