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,1259 @@
1
+ {{py:
2
+
3
+ """
4
+ Vine and non-vine slicers.
5
+ both type have the same interface, defined the slicer.pyx file
6
+ """
7
+
8
+
9
+ import pickle
10
+
11
+ with open("build/tmp/_slicer_names.pkl", "rb") as f:
12
+ slicers=pickle.load(f)
13
+
14
+ dtypes = set([(D['PY_VALUE_TYPE'], D['C_VALUE_TYPE'], D['SHORT_VALUE_TYPE']) for D in slicers])
15
+
16
+ with open("build/tmp/_simplextrees_.pkl", "rb") as f:
17
+ simplex_trees = pickle.load(f)
18
+
19
+ }}
20
+
21
+ from multipers.simplex_tree_multi import SimplexTreeMulti, SimplexTreeMulti_type
22
+ import multipers
23
+ from typing import Optional,Literal
24
+ import threading
25
+ import os
26
+ from joblib import Parallel, delayed
27
+ from warnings import warn
28
+
29
+ from multipers.slicer cimport *
30
+ from multipers.filtrations cimport *
31
+ from multipers.filtration_conversions cimport *
32
+ ## TODO: these two are not needed, remove that by updating rank code.
33
+ from multipers.point_measure import sparsify, rank_decomposition_by_rectangles
34
+ from multipers.grids import compute_grid, sanitize_grid, evaluate_in_grid, _push_pts_to_lines, _inf_value
35
+ from multipers.array_api import api_from_tensor, api_from_tensors
36
+ import multipers.simplex_tree_multi as _st_
37
+ import numpy as np
38
+ cimport cython
39
+ from libcpp.string cimport string
40
+ # python_value_type = np.float32
41
+ from typing import Union
42
+ from cython.operator cimport dereference
43
+ import time
44
+
45
+ from libcpp.algorithm cimport sort
46
+
47
+ ## WARNING : This is repeated in the pxd file ...
48
+ python_indices_type=np.int32
49
+ python_tensor_dtype = np.int32
50
+
51
+ global available_slicers
52
+ available_slicers = tuple((
53
+ {{for D in slicers}}
54
+ {{D['PYTHON_TYPE']}},
55
+ {{endfor}}
56
+ ))
57
+
58
+ global available_columns
59
+ available_columns = set((
60
+ {{for D in slicers}}
61
+ "{{D['COLUMN_TYPE']}}",
62
+ {{endfor}}
63
+ ))
64
+
65
+ global available_dtype
66
+ available_dtype = set([
67
+ {{for D in slicers}}
68
+ {{D['PY_VALUE_TYPE']}},
69
+ {{endfor}}
70
+ ])
71
+
72
+
73
+ global available_pers_backend
74
+ available_pers_backend = set([
75
+ {{for D in slicers}}
76
+ "{{D['PERS_BACKEND_TYPE']}}",
77
+ {{endfor}}
78
+ ])
79
+
80
+
81
+ global available_filtration_container
82
+ available_filtration_container = set([
83
+ {{for D in slicers}}
84
+ "{{D['SHORT_FILTRATION_TYPE']}}",
85
+ {{endfor}}
86
+ ])
87
+ _filtration_container_type = Literal[
88
+ {{for D in slicers}}
89
+ "{{D['FILTRATION_CONTAINER_STR']}}",
90
+ {{endfor}}
91
+ ]
92
+
93
+
94
+ global column_type
95
+ _column_type = Literal[
96
+ {{for D in slicers}}
97
+ "{{D['COLUMN_TYPE']}}",
98
+ {{endfor}}
99
+ ]
100
+
101
+ global _slicers_type
102
+ Slicer_type = Union[
103
+ {{for D in slicers}}
104
+ {{D['PYTHON_TYPE']}},
105
+ {{endfor}}
106
+ ]
107
+
108
+ global _valid_dtypes
109
+ _valid_dtype = Union[
110
+ {{for D in slicers}}
111
+ {{D['PY_VALUE_TYPE']}},
112
+ {{endfor}}
113
+ ]
114
+
115
+
116
+ global _valid_pers_backend
117
+ _valid_pers_backend = Literal[
118
+ {{for D in slicers}}
119
+ "{{D['PERS_BACKEND_TYPE']}}",
120
+ {{endfor}}
121
+ ]
122
+
123
+ {{for D in slicers}}
124
+ # WARN : updating locals {{locals().update(D)}}
125
+
126
+ #------------------------------------------------------------------------------
127
+ cdef class {{D['PYTHON_TYPE']}}:
128
+ cdef {{D['C_TEMPLATE_TYPE']}} truc
129
+ cdef public object filtration_grid
130
+ cdef public int minpres_degree ## TODO : maybe change directly the degree in the minpres ?
131
+
132
+ def __repr__(self):
133
+ return f"slicer[backend={self.pers_backend},dtype={np.dtype(self.dtype).name},num_param={self.num_parameters},vineyard={self.is_vine},kcritical={self.is_kcritical},is_squeezed={self.is_squeezed},is_minpres={self.is_minpres},max_dim={self.dimension}]"
134
+
135
+ @property
136
+ def is_squeezed(self)->bool:
137
+ return self.filtration_grid is not None and len(self.filtration_grid) > 0 and len(self.filtration_grid[0]) > 0
138
+ @property
139
+ def is_minpres(self)->bool:
140
+ return self.minpres_degree>=0
141
+ @property
142
+ def ftype(self)->str:
143
+ return "{{FILTRATION_TYPE}}"
144
+ @staticmethod
145
+ def _inf_value():
146
+ return np.asarray(np.inf,dtype={{D['PY_VALUE_TYPE']}}) if issubclass({{D['PY_VALUE_TYPE']}},np.floating) else np.iinfo({{D['PY_VALUE_TYPE']}}).max
147
+
148
+ def get_ptr(self):
149
+ """
150
+ Returns a pointer to the underlying C++ slicer.
151
+ """
152
+ return <intptr_t>(&self.truc)
153
+ def _from_ptr(self, intptr_t slicer_ptr):
154
+ self.truc = dereference(<{{D['C_TEMPLATE_TYPE']}}*>(slicer_ptr))
155
+ return self
156
+
157
+ {{if D['IS_SIMPLICIAL']}}
158
+ def __init__(self, st=None):
159
+ """
160
+ Constructs a slicer from a simplex tree.
161
+ """
162
+ pass
163
+ def __cinit__(self, st=None):
164
+ if st is None:
165
+ return
166
+ cdef intptr_t ptr = st.thisptr
167
+ cdef Simplex_tree_multi_interface[{{D['FILTRATION_TYPE']}},{{D['C_VALUE_TYPE']}}]* st_ptr = <Simplex_tree_multi_interface[{{D['FILTRATION_TYPE']}},{{D['C_VALUE_TYPE']}}]*>(ptr)
168
+ self.truc = build_slicer_from_simplex_tree_{{D['C_TEMPLATE_TYPE']}}_{{D['FILTRATION_TYPE']}}(dereference(st_ptr))
169
+ self.minpres_degree = -1
170
+ {{elif D['IS_KCRITICAL']}}
171
+ def __init__(self, generator_maps=[], generator_dimensions=[], filtration_values=[]):
172
+ """
173
+ Constructs a slicer from
174
+ - scc-like blocks
175
+ or
176
+ - generator maps (Iterable of list of ints)
177
+ - generator dimensions (Iterable of int)
178
+ - filtration values (Iterable of filtration values)
179
+ """
180
+ pass
181
+ def __cinit__(self, generator_maps=[], generator_dimensions=[], filtration_values=[]):
182
+ """
183
+ Cython constructor
184
+ """
185
+ if len(generator_maps) == 0:
186
+ self.minpres_degree = -1
187
+ return
188
+
189
+ # copy constructors / astypes
190
+ cdef intptr_t other_ptr
191
+ {{for D2 in slicers}}
192
+ if isinstance(generator_maps,{{D2['PYTHON_TYPE']}}):
193
+ other_ptr = <intptr_t>(generator_maps.get_ptr())
194
+ with nogil:
195
+ self.truc = {{D['C_TEMPLATE_TYPE']}}((<{{D2['C_TEMPLATE_TYPE']}} *>(other_ptr))[0])
196
+ self.filtration_grid = generator_maps.filtration_grid
197
+ self.minpres_degree=generator_maps.minpres_degree
198
+ return
199
+ {{endfor}}
200
+
201
+ if len(generator_maps)>0 and len(generator_dimensions) == 0 and len(filtration_values) == 0:
202
+ from multipers._slicer_meta import _blocks2boundary_dimension_grades
203
+ generator_maps, generator_dimensions, filtration_values = _blocks2boundary_dimension_grades(
204
+ generator_maps,
205
+ inplace=False,
206
+ )
207
+ cdef uint32_t num_generators = len(generator_maps)
208
+ filtration_values = [np.asarray(f, dtype = {{D['PY_VALUE_TYPE']}} ) for f in filtration_values]
209
+ cdef uint32_t num_param = filtration_values[0].shape[1]
210
+ cdef vector[vector[uint32_t]] c_generator_maps
211
+ cdef vector[{{D['FILTRATION_TYPE']}}] c_filtration_values
212
+ for stuff in generator_maps:
213
+ c_generator_maps.push_back(<vector[uint32_t]>(stuff))
214
+ sort(c_generator_maps.back().begin(), c_generator_maps.back().end())
215
+ cdef {{D['FILTRATION_TYPE']}} cf = {{D['FILTRATION_TYPE']}}(num_param)
216
+ cdef {{D['FILTRATION_TYPE']}} inf = {{D['FILTRATION_TYPE']}}.inf(num_param)
217
+ cdef {{D['C_VALUE_TYPE']}}[:,:] F_view
218
+ for F in filtration_values:
219
+ cf.push_to_least_common_upper_bound(inf, False)
220
+ F_view = np.asarray(F, dtype = {{D['PY_VALUE_TYPE']}} )
221
+ for i in range(F_view.shape[0]):
222
+ cf.add_generator(_py2p_{{D['SHORT_VALUE_TYPE']}}(F_view[i]))
223
+ c_filtration_values.push_back(cf)
224
+ cdef vector[int] c_generator_dimensions = generator_dimensions
225
+ assert num_generators == c_generator_maps.size() == c_filtration_values.size(), "Invalid input, shape do not coincide."
226
+ cdef Multi_parameter_filtered_complex[{{D['FILTRATION_TYPE']}}] cpx = Multi_parameter_filtered_complex[{{D['FILTRATION_TYPE']}}](c_generator_maps,c_generator_dimensions, c_filtration_values)
227
+ self.truc = {{D['C_TEMPLATE_TYPE']}}(cpx)
228
+ self.minpres_degree = -1
229
+ {{else}}
230
+ def __init__(self, generator_maps=[], generator_dimensions=[], filtration_values=[]):
231
+ """
232
+ Constructs a slicer from
233
+ - generator maps (Iterable of list of ints)
234
+ - generator dimensions (Iterable of int)
235
+ - filtration values (Iterable of filtration values)
236
+ """
237
+ pass
238
+ @cython.boundscheck(False)
239
+ @cython.wraparound(False)
240
+ def __cinit__(self, generator_maps=[], generator_dimensions=[], filtration_values=[]):
241
+ """
242
+ Cython constructor
243
+ """
244
+
245
+
246
+ {{for D2 in slicers}}
247
+ if isinstance(generator_maps,{{D2['PYTHON_TYPE']}}):
248
+ other_ptr = <intptr_t>(generator_maps.get_ptr())
249
+ with nogil:
250
+ self.truc = {{D['C_TEMPLATE_TYPE']}}((<{{D2['C_TEMPLATE_TYPE']}} *>(other_ptr))[0])
251
+ self.filtration_grid = generator_maps.filtration_grid
252
+ self.minpres_degree=generator_maps.minpres_degree
253
+ return
254
+ {{endfor}}
255
+
256
+ cdef uint32_t num_generators = len(generator_maps)
257
+ filtration_values = np.asarray(filtration_values, dtype = {{D['PY_VALUE_TYPE']}} )
258
+ assert len(filtration_values) == num_generators, f"Invalid input, shape do not coicide. Got sizes {num_generators,len(generator_dimensions),len(filtration_values)}."
259
+ cdef vector[vector[uint32_t]] c_generator_maps
260
+ cdef vector[{{D['FILTRATION_TYPE']}}] c_filtration_values
261
+
262
+ c_generator_maps.resize(num_generators)
263
+ c_filtration_values.resize(num_generators)
264
+ for i in range(num_generators):
265
+ c_generator_maps[i] = <vector[uint32_t]>(generator_maps[i])
266
+ sort(c_generator_maps[i].begin(), c_generator_maps[i].end())
267
+ c_filtration_values[i] = _py21c_{{D['SHORT_VALUE_TYPE']}}(filtration_values[i])
268
+ cdef vector[int] c_generator_dimensions = generator_dimensions
269
+
270
+ assert num_generators == c_generator_maps.size() == c_filtration_values.size() == c_generator_dimensions.size(), "Invalid input, shape do not coincide."
271
+ cdef Multi_parameter_filtered_complex[{{D['FILTRATION_TYPE']}}] cpx = Multi_parameter_filtered_complex[{{D['FILTRATION_TYPE']}}](c_generator_maps,c_generator_dimensions, c_filtration_values)
272
+ self.truc = {{D['C_TEMPLATE_TYPE']}}(cpx)
273
+ self.minpres_degree = -1
274
+ {{endif}}
275
+
276
+ def astype(self,
277
+ bool vineyard={{IS_VINE}},
278
+ bool kcritical={{IS_KCRITICAL}},
279
+ dtype:type|_valid_dtypes={{PY_VALUE_TYPE}},
280
+ str col = "{{COLUMN_TYPE}}",
281
+ str pers_backend="{{D['PERS_BACKEND_TYPE']}}",
282
+ str filtration_container:_filtration_container_type = "{{D['SHORT_FILTRATION_TYPE']}}"
283
+ ):
284
+ newSlicer = get_matrix_slicer(vineyard, kcritical, dtype, col, pers_backend, filtration_container)
285
+ if newSlicer == {{D['PYTHON_TYPE']}}:
286
+ return self
287
+ return newSlicer(self)
288
+
289
+ def to_colexical(self, bool return_permutation = False)->{{D['PYTHON_TYPE']}}|tuple[{{D['PYTHON_TYPE']}},np.ndarray]:
290
+ # assert not self.is_squeezed, "Unsqueeze first, this is not implented yet for squeezed slicers"
291
+ new_slicer = {{D['PYTHON_TYPE']}}()
292
+ cdef pair[{{D['C_TEMPLATE_TYPE']}}, vector[uint32_t]] stuff = build_permuted_slicer(self.truc)
293
+
294
+ new_slicer.truc = stuff.first
295
+ new_slicer.minpres_degree = self.minpres_degree
296
+ new_slicer.filtration_grid = self.filtration_grid
297
+
298
+ if return_permutation:
299
+ return new_slicer, np.array(stuff.second, dtype=np.int32)
300
+ return new_slicer
301
+ def permute_generators(self, permutation)->{{D['PYTHON_TYPE']}}:
302
+ cdef vector[uint32_t] c_perm = permutation
303
+ new_slicer = {{D['PYTHON_TYPE']}}()
304
+ new_slicer.truc = build_permuted_slicer(self.truc, c_perm)
305
+ new_slicer.minpres_degree = self.minpres_degree
306
+ return new_slicer
307
+
308
+ def copy(self)->{{D['PYTHON_TYPE']}}:
309
+ """
310
+ Returns a copy of the slicer.
311
+ """
312
+ copy_ = {{D['PYTHON_TYPE']}}()
313
+ copy_.truc = self.truc
314
+ copy_.minpres_degree = self.minpres_degree
315
+ copy_.filtration_grid = self.filtration_grid
316
+ return copy_
317
+ def compute_kernel_projective_cover(self, dim:Optional[int]=None)->{{D['PYTHON_TYPE']}}:
318
+ if len(self) == 0:
319
+ return {{D['PYTHON_TYPE']}}()
320
+ if dim is None:
321
+ dim = self.truc.get_dimension(0)
322
+ cdef int cdim = dim
323
+ out = {{D['PYTHON_TYPE']}}()
324
+ out.truc = build_slicer_from_projective_cover_kernel(self.truc, cdim)
325
+ out.filtration_grid = self.filtration_grid
326
+ return out
327
+
328
+ def get_barcode_idx(self, bool keep_inf = False):
329
+ """
330
+ Returns the current barcode.
331
+ """
332
+ return tuple(np.asarray(x) if len(x) else np.empty((0,2), dtype=int)for x in _db2np_{{D['C_TEMPLATE_TYPE']}}_idx(self.truc.get_barcode_idx()))
333
+ def get_barcode(self, bool keep_inf = False):
334
+ """
335
+ Returns the current barcode.
336
+ """
337
+ if keep_inf:
338
+ bcs = tuple(np.asarray(stuff, dtype = {{D['PY_VALUE_TYPE']}}) for stuff in _db2np_{{D['C_TEMPLATE_TYPE']}}(self.truc.get_barcode()))
339
+ else:
340
+ bcs = {{D['PYTHON_TYPE']}}._threshold_bcs(self.truc.get_barcode())
341
+ return bcs
342
+ def push_to_line(self, basepoint, direction=None)->{{D['PYTHON_TYPE']}}:
343
+ """
344
+ Pushes the current slicer to the line defined by a basepoint and an optional direction.
345
+ If the direction is not provided, it is assumed to be diagonal.
346
+ """
347
+ {{if D['IS_FLOAT']}}
348
+ basepoint = np.asarray(basepoint, dtype = {{D['PY_VALUE_TYPE']}})
349
+ cdef Line[{{D['C_VALUE_TYPE']}}] line
350
+ if direction is None:
351
+ line = Line[{{D['C_VALUE_TYPE']}}](_py2p_{{D['SHORT_VALUE_TYPE']}}(basepoint))
352
+ else:
353
+ direction = np.asarray(direction, dtype = {{D['PY_VALUE_TYPE']}})
354
+ line = Line[{{D['C_VALUE_TYPE']}}](_py2p_{{D['SHORT_VALUE_TYPE']}}(basepoint),_py2p_{{D['SHORT_VALUE_TYPE']}}(direction))
355
+ self.truc.push_to(line)
356
+ return self
357
+ {{else}}
358
+ raise NotImplementedError("There is no `int` slicing.")
359
+ {{endif}}
360
+
361
+ @staticmethod
362
+ cdef _threshold_bcs(const Dim_barcode[{{D['C_TEMPLATE_TYPE']}}, {{D['C_VALUE_TYPE']}}]& bcs):
363
+ return tuple(
364
+ np.fromiter(
365
+ (a for a in stuff if a[0] < {{D['PYTHON_TYPE']}}._inf_value()),
366
+ dtype=np.dtype(({{D['PY_VALUE_TYPE']}},2))
367
+ )
368
+ for stuff in _db2np_{{D['C_TEMPLATE_TYPE']}}(bcs)
369
+ )
370
+ @staticmethod
371
+ def _bc_to_full(bcs, basepoint, direction=None):
372
+ # i, (b sv d), coords
373
+ basepoint = basepoint[None,None,:]
374
+ direction = 1 if direction is None else direction[None,None,:]
375
+ return tuple(bc[:,:,None]*direction + basepoint for bc in bcs)
376
+
377
+ def persistence_on_line(self,basepoint,direction=None, bool keep_inf=True, bool full=False, bool ignore_infinite_filtration_values = True):
378
+ """
379
+ Computes the persistence on a line L defined by
380
+ - a basepoint (num_parameters,) or (num_basepoints, num_parameters,) array
381
+ - an optional direction (num_parameters,) or (num_basepoints, num_parameters,) array
382
+ """
383
+ {{if D['IS_FLOAT']}}
384
+ self.push_to_line(basepoint,direction)
385
+ self.truc.initialize_persistence_computation(ignore_infinite_filtration_values)
386
+ if keep_inf:
387
+ bcs = tuple(np.asarray(stuff, dtype = {{D['PY_VALUE_TYPE']}}) for stuff in _db2np_{{D['C_TEMPLATE_TYPE']}}(self.truc.get_barcode()))
388
+ else:
389
+ bcs = {{D['PYTHON_TYPE']}}._threshold_bcs(self.truc.get_barcode())
390
+
391
+ if full:
392
+ bcs = {{D['PYTHON_TYPE']}}._bc_to_full(bcs, basepoint, direction)
393
+ return bcs
394
+ {{else}}
395
+ if not self.is_squeezed:
396
+ raise ValueError("Unsqueeze tensor, or provide a filtration grid. Cannot slice lines with integers.")
397
+ api = api_from_tensors(basepoint, *self.filtration_grid)
398
+ basepoint = api.astensor(basepoint)
399
+ fil = evaluate_in_grid(np.asarray(self.get_filtrations()), self.filtration_grid)
400
+ if basepoint.ndim == 0 or basepoint.ndim >2:
401
+ raise ValueError(f"Expected a basepoint shape of the form (num_parameters,). Got {basepoint.shape=}")
402
+ if basepoint.ndim == 1:
403
+ basepoint = basepoint[None]
404
+ if direction is not None:
405
+ direction = api.astensor(direction)
406
+ if direction.ndim == 0 or direction.ndim >2:
407
+ raise ValueError(f"Expected a direction shape of the form (num_parameters,). Got {direction.shape=}")
408
+ if direction.ndim == 1:
409
+ direction = direction[None]
410
+ projected_fil = _push_pts_to_lines(fil, basepoint, direction, api=api)
411
+ bcs = self.compute_persistence(projected_fil, ignore_infinite_filtration_values=ignore_infinite_filtration_values)
412
+ if full:
413
+ _dirs = [None]*len(basepoint) if direction is None else direction
414
+ bcs = tuple({{D['PYTHON_TYPE']}}._bc_to_full(bcs, bp, dir) for bcs, bp, dir in zip(bcs,basepoint,_dirs))
415
+ return bcs
416
+ {{endif}}
417
+
418
+ def _custom_persistences_idx(self, filtration_array, bool ignore_inf=True):
419
+ filtration_array = np.asarray(filtration_array, dtype= {{D['PY_VALUE_TYPE']}})
420
+ cdef {{D['C_VALUE_TYPE']}}[:,:] arr_view = filtration_array
421
+ cdef int size = arr_view.shape[0]
422
+ if <int>arr_view.shape[1] != <int>self.truc.num_generators():
423
+ raise ValueError(f"Got filtration array of shape {filtration_array.shape=} / {arr_view.shape=}. Was expecting (-1, {len(self)=})")
424
+
425
+ return tuple(tuple(np.array(bc_idx_degree, dtype=int) for bc_idx_degree in _db2np_{{D['C_TEMPLATE_TYPE']}}_idx(bc_idx)) for bc_idx in custom_persistences(self.truc, &arr_view[0,0], size, ignore_inf))
426
+
427
+ def persistence_on_lines(self, basepoints=None, directions=None, bool keep_inf=True, bool full=False, bool ignore_infinite_filtration_values = True):
428
+ """
429
+ Same as `persistence_on_line`, but with vineyards operation between
430
+ lines if `self.is_vine`, and in parallel otherwise.
431
+ """
432
+ cdef vector[vector[{{D['C_VALUE_TYPE']}}]] c_basepoints = basepoints
433
+ cdef vector[vector[{{D['C_VALUE_TYPE']}}]] c_directions
434
+ cdef vector[pair[vector[{{D['C_VALUE_TYPE']}}], vector[{{D['C_VALUE_TYPE']}}]]] c_truc
435
+ cdef vector[Dim_barcode[{{D['C_TEMPLATE_TYPE']}}, {{D['C_VALUE_TYPE']}}]] c_out
436
+ if directions is not None:
437
+ c_directions = directions
438
+ assert c_directions.size() == c_basepoints.size(), "Not as many directions than base points"
439
+
440
+ with nogil:
441
+ c_out = persistence_on_lines(self.truc, c_basepoints, c_directions, ignore_infinite_filtration_values)
442
+ cdef int num_bc = c_basepoints.size()
443
+
444
+ if keep_inf:
445
+ out = tuple(tuple(
446
+ np.asarray(y, dtype = {{D['PY_VALUE_TYPE']}}) if len(y)>0 else np.empty((0,2), dtype = {{D['PY_VALUE_TYPE']}})
447
+ for y in _db2np_{{D['C_TEMPLATE_TYPE']}}(x)) for x in c_out)
448
+ else:
449
+ out = tuple({{D['PYTHON_TYPE']}}._threshold_bcs(x) for x in c_out)
450
+
451
+ if full:
452
+ _dirs = [None]*len(basepoints) if directions is None else directions
453
+ out = tuple({{D['PYTHON_TYPE']}}._bc_to_full(bcs, bp, dir) for bcs, bp, dir in zip(out,basepoints,_dirs))
454
+ return out
455
+
456
+
457
+ def __getstate__(self):
458
+ return (
459
+ self.get_boundaries(),
460
+ self.get_dimensions(),
461
+ self.get_filtrations(),
462
+ self.filtration_grid,
463
+ self.minpres_degree,
464
+ )
465
+ def __setstate__(self, tuple dump):
466
+ B, D, F, filtration_grid, minpres_degree = dump
467
+ copy = {{D['PYTHON_TYPE']}}(B,D,F)
468
+ self.truc = copy.truc
469
+ self.minpres_degree= minpres_degree
470
+ self.filtration_grid=filtration_grid
471
+
472
+ def __eq__(self, other):
473
+ ## True if they rpz the same complex (no reordering allowed yet),
474
+ # i.e., ignores minpres, squeeze
475
+ if other.is_squeezed:
476
+ return self == other.unsqueeze()
477
+ if self.is_squeezed:
478
+ return self.unsqueeze() == other
479
+ return (
480
+ np.array_equal(self.get_dimensions(), other.get_dimensions())
481
+ and
482
+ self.get_boundaries() == other.get_boundaries()
483
+ and
484
+ ## Kcritical are sorted + filtrationvalues is a dump.
485
+ # for non kcritical there is an unnecessary copy though
486
+ np.array_equal(self.get_filtrations_values(), other.get_filtrations_values())
487
+ )
488
+
489
+
490
+
491
+ def compute_persistence(self,one_filtration=None, bool ignore_infinite_filtration_values = True, bool verbose=False)->tuple:
492
+ """
493
+ Computes the current persistence, or the persistence
494
+ given by the filtration one_filtration (num_generators,) or (num_filtrations, num_generators,).
495
+ """
496
+ if one_filtration is not None:
497
+ if verbose:
498
+ print(f"Computing persistence on custom filtration: {one_filtration.shape=} {one_filtration.dtype=}")
499
+ api = api_from_tensor(one_filtration)
500
+ one_filtration=api.astensor(one_filtration)
501
+ if one_filtration.ndim > 2 or one_filtration.ndim == 0:
502
+ raise ValueError(f"Expected a filtration shape of the form ((num_1_param), num_generators). Got {one_filtration.shape=}")
503
+ squeeze = False
504
+ if one_filtration.ndim == 1:
505
+ one_filtration = one_filtration[None]
506
+ squeeze = True
507
+
508
+ if self.is_squeezed:
509
+ if verbose:
510
+ print(f"Input is squeezed. Unsqueezing it for the line projection.")
511
+ temp = self.unsqueeze() ## TODO : optimize
512
+ bcs = temp._custom_persistences_idx(api.asnumpy(one_filtration),ignore_infinite_filtration_values)
513
+ else:
514
+ bcs = self._custom_persistences_idx(api.asnumpy(one_filtration),ignore_infinite_filtration_values)
515
+
516
+ num_filtrations = one_filtration.shape[0]
517
+ bcs = tuple(tuple(
518
+ evaluate_in_grid(
519
+ np.asarray(bcs[i][j]) if len(bcs[i][j]) else np.empty((0,2), dtype=np.int32),
520
+ [one_filtration[i]]*2, # 2 bc of 1d barcode
521
+ input_inf_value=-1,
522
+ )
523
+ for j in range(len(bcs[i])))
524
+ for i in range(num_filtrations)
525
+ )
526
+
527
+ return bcs[0] if squeeze else bcs
528
+
529
+ # TODO: Later
530
+ # if len(degrees)>0:
531
+ # self.truc.initialize_persistence_computation(degrees)
532
+ # else:
533
+ # self.truc.initialize_persistence_computation()
534
+ self.truc.initialize_persistence_computation(ignore_infinite_filtration_values)
535
+ return self.get_barcode()
536
+ def get_barcode(self):
537
+ """
538
+ Returns the barcode of the current 1d-persistence.
539
+ """
540
+ return tuple(np.asarray(bc) for bc in _db2np_{{D['C_TEMPLATE_TYPE']}}(self.truc.get_barcode()))
541
+ def sliced_filtration(self,basepoint, direction=None):
542
+ """
543
+ Computes the filtration on a line L defined by
544
+ - a basepoint (num_parameters,) array
545
+ - an optional direction (num_parameters,) array
546
+ """
547
+ self.push_to_line(basepoint,direction)
548
+ return np.asarray(self.truc.get_one_filtration())
549
+ def __len__(self):
550
+ return self.truc.num_generators()
551
+ @property
552
+ def num_generators(self):
553
+ return self.truc.num_generators()
554
+ @property
555
+ def num_parameters(self):
556
+ return self.truc.num_parameters()
557
+ @property
558
+ def info(self):
559
+ print(slicer_to_str(self.truc).decode())
560
+ def filtration_bounds(self) -> np.ndarray:
561
+ """
562
+ Computes the bounding box of the current multifiltration.
563
+ """
564
+ cdef pair[{{D['FILTRATION_TYPE']}}, {{D['FILTRATION_TYPE']}}] box = self.truc.get_bounding_box()
565
+
566
+ {{if "Flat" in D['FILTRATION_TYPE']}}
567
+
568
+ a = np.asarray([box.first(0,0), box.first(0,1)])
569
+ g = box.second.num_generators() - 1
570
+ b = np.asarray([box.second(g,0), box.second(g,1)])
571
+
572
+ {{else}}
573
+
574
+ if box.first.is_finite():
575
+ duplicate = 0
576
+ else:
577
+ duplicate = box.first.num_parameters()
578
+ cdef cnp.ndarray[{{D['C_VALUE_TYPE']}}, ndim=1] a = _ff21cview2_{{D['SHORT_VALUE_TYPE']}}(&box.first(0,0), box.first.num_parameters(), duplicate, True)
579
+
580
+ if box.second.is_finite():
581
+ duplicate = 0
582
+ else:
583
+ duplicate = box.second.num_parameters()
584
+ cdef cnp.ndarray[{{D['C_VALUE_TYPE']}}, ndim=1] b = _ff21cview2_{{D['SHORT_VALUE_TYPE']}}(&box.second(0,0), box.first.num_parameters(), duplicate, True)
585
+
586
+ {{endif}}
587
+
588
+ return np.asarray([a,b])
589
+
590
+ def get_filtrations_values(self)->np.ndarray:
591
+ """
592
+ Returns the current filtration values of the slicer.
593
+ """
594
+ cdef vector[{{D['FILTRATION_TYPE']}}] v = self.truc.get_filtrations()
595
+ # shape num_splx, num_gens, num_parameters
596
+ cdef vector[{{C_VALUE_TYPE}}] out
597
+ with nogil:
598
+ out.reserve(self.truc.num_generators()*self.truc.num_parameters())
599
+ for i in range(v.size()):
600
+ for g in range(v[i].num_generators()):
601
+ for p in range(v[i].num_parameters()):
602
+ out.push_back(v[i](g,p))
603
+ # out = [[v[i](g,p) for p in range(self.num_parameters)] for i in range(v.size()) for g in range(v[i].num_generators())]
604
+ return np.asarray(out, dtype={{D['PY_VALUE_TYPE']}}).reshape(-1, self.num_parameters)
605
+ def get_filtration_grid(self,grid_strategy:str="exact", **infer_grid_kwargs):
606
+ return compute_grid(
607
+ self.get_filtrations_values().T,
608
+ strategy=grid_strategy,
609
+ **infer_grid_kwargs,
610
+ )
611
+ def get_filtrations(self, bool unsqueeze = False, bool raw=False):
612
+ """
613
+ Returns a view of the filtration values, as a list of numpy arrays.
614
+ """
615
+ {{if D['IS_KCRITICAL']}}
616
+ if unsqueeze:
617
+ raise NotImplementedError("Unsqueezed version not implemented for multicritical filtrations.")
618
+ return vect_{{D['FILTRATION_TYPE']}}_2_python(self.truc.get_filtrations(), copy=False, raw=raw)
619
+ {{else}}
620
+ out = vect_{{D['FILTRATION_TYPE']}}_2_python(self.truc.get_filtrations(), copy=False, raw=raw)
621
+ if not unsqueeze:
622
+ return out
623
+ if not self.is_squeezed:
624
+ raise ValueError(f"Already unsqueezed. Got {unsqueeze=}")
625
+ return evaluate_in_grid(np.asarray(out), self.filtration_grid)
626
+ {{endif}}
627
+
628
+ def get_dimensions(self)-> np.ndarray:
629
+ """
630
+ Returns the ordered dimensions of the generators.
631
+ """
632
+ return np.asarray(self.truc.get_dimensions())
633
+ @property
634
+ def dimension(self)-> int:
635
+ """
636
+ Returns the maximum dimension of the complex.
637
+ """
638
+ return self.get_dimensions()[-1] if len(self)>0 else -np.inf
639
+ def prune_above_dimension(self,int max_dimension)->{{D['PYTHON_TYPE']}}:
640
+ """
641
+ Prunes the generators above a given dimension.
642
+ """
643
+ self.truc.prune_above_dimension(max_dimension)
644
+ return self
645
+ {{if not D["IS_KCRITICAL"]}}
646
+ def make_filtration_non_decreasing(self, bool safe=True):
647
+ if self.is_squeezed and safe:
648
+ for i_ in range(self.num_parameters):
649
+ F = self.filtration_grid[i_]
650
+ if not (F[1:]>=F[:-1]).all():
651
+ raise ValueError("Found non-sorted grid.")
652
+
653
+ cdef int i = 0
654
+ cdef int N = len(self)
655
+ cdef vector[int] dims = self.truc.get_dimensions()
656
+ cdef Py_ssize_t num_parameters = self.num_parameters
657
+ cdef vector[vector[unsigned int]] B = self.truc.get_boundaries() # const issues, I was lazy
658
+ cdef {{D['C_VALUE_TYPE']}}* f
659
+
660
+ with nogil:
661
+ for i in range(N):
662
+ for b in B[i]:
663
+ for j in range(num_parameters):
664
+ # Weird cython bug with "Cannot assign to or delete this"
665
+ f = &(self.truc.get_filtrations()[b](0,j))
666
+ f[0] = max(f[0], self.truc.get_filtrations()[i](0,j))
667
+ return self
668
+
669
+
670
+
671
+
672
+ {{endif}}
673
+
674
+ def get_boundaries(self)->tuple[tuple]:
675
+ """
676
+ Returns the boundaries of the generators.
677
+ """
678
+ return tuple(tuple(b) for b in self.truc.get_boundaries())
679
+ def grid_squeeze(
680
+ self,
681
+ filtration_grid=None,
682
+ strategy="exact",
683
+ resolution:Optional[int|Iterable[int]]=None,
684
+ bool coordinates=True,
685
+ bool inplace = False,
686
+ grid_strategy=None,
687
+ threshold_min=None,
688
+ threshold_max=None,
689
+ )->{{COARSENNED_PY_CLASS_NAME}}|{{D['PYTHON_TYPE']}}:
690
+ """
691
+ Coarsen the filtration values on a grid. This is necessary to compute some invariants.
692
+
693
+ If the filtration grid is not given, it is infered from filtration values,
694
+ using the :func:`multipers.grids.compute_grid` function, whose args are
695
+ - grid_strategy:str see `multipers.grids.available_strategies`. Defaults to exact.
696
+ - resolution:int if strategy is not exact.
697
+
698
+ - inplace:bool if true, does the operation inplace, i.e., doesn't return a copy.
699
+ """
700
+ if grid_strategy is not None:
701
+ warn("`grid_strategy` is deprecated, use `strategy` instead.",DeprecationWarning)
702
+ strategy=grid_strategy
703
+
704
+ if self.is_squeezed:
705
+ warn("(copy warning) Squeezing an already squeezed slicer.")
706
+ temp = self.unsqueeze()
707
+ subgrid = compute_grid(self.filtration_grid, strategy=strategy,
708
+ resolution=resolution,
709
+ threshold_min=threshold_min,
710
+ threshold_max=threshold_max)
711
+ return temp.grid_squeeze(subgrid, coordinates=coordinates, inplace=inplace)
712
+
713
+ if filtration_grid is None:
714
+ filtration_grid = compute_grid(
715
+ self.get_filtrations_values().T,
716
+ strategy=strategy,
717
+ resolution=resolution,
718
+ threshold_min=threshold_min,
719
+ threshold_max=threshold_max,)
720
+ cdef vector[vector[{{D['C_VALUE_TYPE']}}]] grid = filtration_grid
721
+ if inplace or not coordinates:
722
+ self.truc.coarsen_on_grid_inplace(grid, coordinates)
723
+ if coordinates:
724
+ self.filtration_grid = filtration_grid
725
+ else:
726
+ {{if D['COLUMN_TYPE'] is None}}
727
+ raise ValueError("WIP")
728
+ {{else}}
729
+ out = {{COARSENNED_PY_CLASS_NAME}}()
730
+ out.truc = build_slicer_coarsen_on_grid(self.truc, grid)
731
+ if coordinates:
732
+ out.filtration_grid = sanitize_grid(filtration_grid)
733
+ out.minpres_degree = self.minpres_degree
734
+ return out
735
+ {{endif}}
736
+ return self
737
+ def _clean_filtration_grid(self):
738
+ """
739
+ Removes the values in filtration_grid that are not linked to any splx.
740
+ """
741
+ if not self.is_squeezed:
742
+ raise ValueError("No grid to clean.")
743
+ F = self.filtration_grid
744
+ self.filtration_grid=None
745
+ cleaned_coordinates = compute_grid(self)
746
+ new_slicer = self.grid_squeeze(cleaned_coordinates)
747
+
748
+ self._from_ptr(new_slicer.get_ptr())
749
+ self.filtration_grid = tuple(f[g] for f,g in zip(F,cleaned_coordinates))
750
+ return self
751
+
752
+ def minpres(self,
753
+ int degree=-1,
754
+ list[int] degrees=[],
755
+ str backend:Literal["mpfree", "2pac"]="mpfree",
756
+ bool vineyard={{D['IS_VINE']}},
757
+ dtype = {{D['PY_VALUE_TYPE']}},
758
+ **minpres_kwargs
759
+ )->Slicer_type:
760
+ """
761
+ Computes the minimal presentation of the slicer, and returns it as a new slicer.
762
+ See :func:`multipers.slicer.minimal_presentation`.
763
+ """
764
+ from multipers.ops import minimal_presentation
765
+ new_slicer = minimal_presentation(self, degree=degree, degrees=degrees, backend=backend)
766
+ return new_slicer
767
+
768
+ @property
769
+ def dtype(self)->type:
770
+ return {{D['PY_VALUE_TYPE']}}
771
+ @property
772
+ def col_type(self)->str:
773
+ return "{{D['COLUMN_TYPE']}}"
774
+ @property
775
+ def filtration_container(self)->str:
776
+ return "{{D['SHORT_FILTRATION_TYPE']}}"
777
+ @property
778
+ def is_vine(self)->bool:
779
+ return {{D['IS_VINE']}}
780
+ @property
781
+ def is_kcritical(self)->bool:
782
+ return {{D['IS_KCRITICAL']}}
783
+
784
+ @property
785
+ def pers_backend(self)->str:
786
+ return "{{D['PERS_BACKEND_TYPE']}}"
787
+
788
+ {{if D['IS_VINE']}}
789
+ def vine_update(self,basepoint,direction=None)->{{D['PYTHON_TYPE']}}:
790
+ """
791
+ Updates the barcode, on a line, using the vineyard algorithm.
792
+ """
793
+ self.push_to_line(basepoint,direction)
794
+ self.truc.vineyard_update()
795
+ return self
796
+ def get_representative_cycles(self, bool update=True):
797
+ """
798
+ Returns the representative cycles of the current barcode.
799
+ Recomputes the generators if update=True
800
+ """
801
+ cdef vector[vector[vector[uint32_t]]] cycle_idx = self.truc.get_representative_cycles(update)
802
+ cdef vector[vector[vector[vector[uint32_t]]]] out = vector[vector[vector[vector[uint32_t]]]](cycle_idx.size())
803
+
804
+ with nogil:
805
+ for i in range(cycle_idx.size()):
806
+ out[i].resize(cycle_idx[i].size())
807
+ for j in range(cycle_idx[i].size()):
808
+ if cycle_idx[i][j].size() > 0:
809
+ # to match the old format, otherwise we can have several empty boundaries for one cycle
810
+ if self.truc.get_boundary(cycle_idx[i][j][0]).empty():
811
+ out[i][j].push_back(vector[uint32_t]())
812
+ else:
813
+ out[i][j].resize(cycle_idx[i][j].size())
814
+ for k in range(cycle_idx[i][j].size()):
815
+ out[i][j][k] = self.truc.get_boundary(cycle_idx[i][j][k])
816
+ return out
817
+ def get_most_persistent_cycle(self, int dim=1, bool update=True, bool idx=False):
818
+ """
819
+ Returns the representative cycles with longest bar in the given dimension.
820
+ Recomputes the generators if update=True
821
+ """
822
+ cdef vector[uint32_t] cycle_idx = self.truc.get_most_persistent_cycle(dim, update)
823
+
824
+ if idx:
825
+ return cycle_idx
826
+
827
+ cdef vector[vector[uint32_t]] out
828
+
829
+ with nogil:
830
+ if cycle_idx.size() > 0:
831
+ # to match the old format, otherwise we can have several empty boundaries for one cycle
832
+ if self.truc.get_boundary(cycle_idx[0]).empty():
833
+ out.push_back(vector[uint32_t]())
834
+ else:
835
+ out.resize(cycle_idx.size())
836
+ for k in range(cycle_idx.size()):
837
+ out[k] = self.truc.get_boundary(cycle_idx[k])
838
+ return out
839
+
840
+ def get_permutation(self):
841
+ """
842
+ Returns the current generator permutation (w.r.t. vineyard).
843
+ """
844
+ return self.truc.get_current_order()
845
+ {{endif}}
846
+
847
+ def to_scc(
848
+ self,
849
+ path:os.PathLike,
850
+ int degree = -1,
851
+ bool rivet_compatible = False,
852
+ bool ignore_last_generators = False,
853
+ bool strip_comments = False,
854
+ bool reverse = False,
855
+ bool unsqueeze = True,
856
+ ):
857
+ """
858
+ Writes current slicer to a file in scc format.
859
+ """
860
+ if degree == -1 and not rivet_compatible:
861
+ degree = 1
862
+ cdef string c_path = path.encode(encoding="utf-8")
863
+ if self.is_squeezed and unsqueeze:
864
+ kwargs = locals()
865
+ kwargs.pop("self",0)
866
+ kwargs.pop("c_path",0)
867
+ self.unsqueeze().to_scc(**kwargs)
868
+ return
869
+ with nogil:
870
+ write_slicer_to_scc_file(c_path, self.truc, degree, rivet_compatible, ignore_last_generators, strip_comments, reverse)
871
+
872
+ def _build_from_scc_file(self, path:os.PathLike, bool rivet_compatible = False, bool reverse = False, int shift_dimension = 0)->{{D['PYTHON_TYPE']}}:
873
+ """
874
+ Builds the slicer from the given scc file. Should be empty before, otherwise will be completely overwritten.
875
+ """
876
+ cdef string c_path = path.encode(encoding="utf-8")
877
+ with nogil:
878
+ self.truc = build_slicer_from_scc_file_{{D['C_TEMPLATE_TYPE']}}(c_path, rivet_compatible, reverse, shift_dimension)
879
+ return self
880
+
881
+ @cython.boundscheck(False)
882
+ @cython.wraparound(False)
883
+ def unsqueeze(self, grid=None, bool inf_overflow = True)->{{REAL_PY_CLASS_NAME}}:
884
+ {{if SHORT_FILTRATION_TYPE=="Flat"}}
885
+ raise NotImplementedError("There is no reasonable implementation (yet).")
886
+ {{else}}
887
+ grid = self.filtration_grid if grid is None else grid
888
+ grid = sanitize_grid(grid, numpyfy=True, add_inf = inf_overflow)
889
+
890
+ cdef int num_generators = len(self)
891
+ grid_size = np.array([len(g) for g in grid], dtype=np.int32)
892
+
893
+ {{if D['IS_KCRITICAL']}}
894
+ current_filtration = self.get_filtrations()
895
+ new_filtrations = tuple(
896
+ evaluate_in_grid(np.asarray(current_filtration[i], dtype=np.int32).clip(None, grid_size-1), grid)
897
+ for i in range(num_generators)
898
+ )
899
+ {{else}}
900
+ F = np.asarray(self.get_filtrations(), dtype=np.int32).clip(None, grid_size-1)
901
+ new_filtrations = evaluate_in_grid(F, grid)
902
+ {{endif}}
903
+ # TODO : optimize
904
+ new_slicer = {{REAL_PY_CLASS_NAME}}(
905
+ self.get_boundaries(),
906
+ self.get_dimensions(),
907
+ new_filtrations,
908
+ )
909
+ new_slicer.minpres_degree=self.minpres_degree
910
+ return new_slicer
911
+ {{endif}}
912
+
913
+ def build_from_simplex_tree(self, st):
914
+ cdef intptr_t ptr = st.thisptr
915
+ # st_name = type(st).__name__
916
+ # f_short = st_name[17:]
917
+ {{for ST in simplex_trees}}
918
+ cdef {{ST["ST_INTERFACE"]}}* st_ptr_{{ST["PyFil"]}} = <{{ST["ST_INTERFACE"]}}*>(ptr)
919
+ if _st_.{{ST["PY_CLASS_NAME"]}}._isinstance(st):
920
+ self.truc = build_slicer_from_simplex_tree_{{D['C_TEMPLATE_TYPE']}}_{{ST["PyFil"]}}(dereference(st_ptr_{{ST["PyFil"]}}))
921
+ self.minpres_degree = -1
922
+ return self
923
+ {{endfor}}
924
+ raise ValueError(f"Invalid st {st}.")
925
+
926
+ {{endfor}}
927
+
928
+ from libcpp.vector cimport vector
929
+ from libcpp.set cimport set as cset
930
+ # cdef extern from "gudhi/cubical_to_boundary.h" namespace "":
931
+ # void _to_boundary(const vector[unsigned int]&, vector[vector[unsigned int]]&, vector[int]&) except + nogil
932
+ # void get_vertices(unsigned int, cset[unsigned int]&, const vector[vector[unsigned int]]&) nogil
933
+
934
+ {{for D in slicers}}
935
+ def _from_bitmap{{D['SHORT_VALUE_TYPE']}}(image, **slicer_kwargs):
936
+ from multipers import Slicer
937
+ dtype = slicer_kwargs.get("dtype", image.dtype)
938
+ slicer_kwargs["dtype"] = dtype
939
+ if image.dtype != dtype:
940
+ raise ValueError(f"Invalid type matching. Got {dtype=} and {image.dtype=}")
941
+ _Slicer = Slicer(return_type_only=True, **slicer_kwargs)
942
+
943
+ cdef unsigned int num_parameters = image.shape[-1]
944
+ cdef vector[unsigned int] img_shape = image.shape[:-1]
945
+ {{if D['IS_KCRITICAL']}}
946
+ cdef vector[{{D['FILTRATION_TYPE']}}] vertices = python_2_vect_{{D['FILTRATION_TYPE']}}(image.reshape(-1,num_parameters))
947
+ {{else}}
948
+ cdef vector[{{D['FILTRATION_TYPE']}}] vertices = python_2_vect_{{D['FILTRATION_TYPE']}}(image.reshape(-1,num_parameters))
949
+ {{endif}}
950
+ cdef {{D['C_TEMPLATE_TYPE']}} cslicer = build_slicer_from_bitmap_{{D['C_TEMPLATE_TYPE']}}(vertices, img_shape)
951
+
952
+ slicer = _Slicer()
953
+ slicer._from_ptr(<intptr_t>(&cslicer)) #makes a copy (as before), but perhaps there is a way to do without?
954
+ return slicer
955
+ {{endfor}}
956
+
957
+ def from_bitmap(img, **kwargs):
958
+ img = np.asarray(img)
959
+ {{for pytype,ctype,stype in dtypes}}
960
+ if img.dtype == {{pytype}}:
961
+ return _from_bitmap{{stype}}(img, **kwargs)
962
+ {{endfor}}
963
+ raise ValueError(f"Invalid dtype. Got {img.dtype=}, was expecting {available_dtype=}.")
964
+
965
+ def from_function_delaunay(
966
+ points,
967
+ grades,
968
+ int degree=-1,
969
+ backend:Optional[_valid_pers_backend]=None,
970
+ vineyard=None, # Optionmal[bool], wait for cython
971
+ dtype=np.float64,
972
+ bool verbose = False,
973
+ bool clear = True,
974
+ ):
975
+ """
976
+ Given points in $\mathbb R^n$ and function grades, compute the function-delaunay
977
+ bifiltration as a in an scc format, and converts it into a slicer.
978
+
979
+ points : (num_pts, n) float array
980
+ grades : (num_pts,) float array
981
+ degree (opt) : if given, computes a minimal presentation of this homological degree first
982
+ backend : slicer backend, e.g. "matrix", "clement"
983
+ vineyard : bool, use a vineyard-compatible backend
984
+ """
985
+ from multipers.io import function_delaunay_presentation_to_slicer, _init_external_softwares
986
+ s = multipers.Slicer(None, backend=backend, vineyard=vineyard, dtype=dtype)
987
+ _init_external_softwares(requires=["function_delaunay"])
988
+ function_delaunay_presentation_to_slicer(s, points, grades, degree=degree, verbose=verbose,clear=clear)
989
+ if degree >= 0:
990
+ s.minpres_degree = degree
991
+ # s = s.minpres(degree=degree, force=True)
992
+ return s
993
+
994
+ def slicer2blocks(slicer, int degree = -1, bool reverse=True):
995
+ """
996
+ Convert any slicer to the block format a.k.a. scc format for python
997
+ """
998
+ dims = slicer.get_dimensions()
999
+ num_empty_blocks_to_add = 1 if degree == -1 else dims.min()-degree +1
1000
+ _,counts = np.unique(dims, return_counts=True, )
1001
+ indices = np.concatenate([[0],counts], dtype=np.int32).cumsum()
1002
+ filtration_values = slicer.get_filtrations()
1003
+ filtration_values = [filtration_values[indices[i]:indices[i+1]] for i in range(len(indices)-1)]
1004
+ boundaries = slicer.get_boundaries()
1005
+ boundaries = [boundaries[indices[i]:indices[i+1]] for i in range(len(indices)-1)]
1006
+ shift = np.concatenate([[0], indices], dtype=np.int32)
1007
+ boundaries = [tuple(np.asarray(x-s, dtype=np.int32) for x in block) for s,block in zip(shift,boundaries)]
1008
+ blocks = [tuple((f,tuple(b))) for b,f in zip(boundaries,filtration_values)]
1009
+ blocks = ([(np.empty((0,)),[])]*num_empty_blocks_to_add) + blocks
1010
+ if reverse:
1011
+ blocks.reverse()
1012
+ return blocks
1013
+
1014
+
1015
+
1016
+ def to_simplextree(s:Slicer_type, max_dim:int=-1) -> SimplexTreeMulti_type:
1017
+ """
1018
+ Turns a --simplicial-- slicer into a simplextree.
1019
+
1020
+ Warning: Won't work for non-simplicial complexes,
1021
+ i.e., complexes $K$ not satisfying
1022
+ $\forall \sigma \in K,\, \mathrm{dim}(\sigma) = |\partial \sigma|-1$
1023
+ """
1024
+ dims = s.get_dimensions()
1025
+ assert np.all(dims[:-1] <= dims[1:]), "Dims is not sorted."
1026
+ idx = np.searchsorted(dims, np.unique(dims))
1027
+ idx = np.concatenate([idx, [dims.shape[0]]])
1028
+ if max_dim>=0:
1029
+ idx = idx[:max_dim+2]
1030
+
1031
+ cdef vector[vector[int]] boundaries_ = s.get_boundaries()
1032
+ cdef int a
1033
+ cdef int b
1034
+ if len(idx)>2:
1035
+ a = idx[2]
1036
+ b = idx[-1]
1037
+ for i in range(a, b):
1038
+ boundaries_[i] = np.unique(np.concatenate([boundaries_[k] for k in boundaries_[i]]))
1039
+ cdef int num_dims = len(idx)-1
1040
+ boundaries = [np.asarray(boundaries_[idx[i]:idx[i+1]], dtype=np.int32).T for i in range(num_dims)]
1041
+ boundaries[0] = np.arange(boundaries[0].shape[1])[None,:]
1042
+ filtrations = s.get_filtrations()
1043
+ num_parameters = s.num_parameters
1044
+ filtrations=tuple(filtrations[idx[i]:idx[i+1]] for i in range(num_dims)) # TODO : optimize ?
1045
+ st = SimplexTreeMulti(num_parameters = num_parameters, dtype = s.dtype, kcritical=s.is_kcritical)
1046
+ for dim in range(num_dims):
1047
+ if s.is_kcritical: ## TODO : this may be very slow
1048
+ # for b,f in zip(boundaries[i].T,filtrations[i]):
1049
+ # for g in np.asarray(f):
1050
+ # st.insert(np.asarray(b, dtype = np.int32),np.asarray(g, dtype=s.dtype))
1051
+ for j in range(boundaries[i].shape[1]):
1052
+ splx = boundaries[i][:,j]
1053
+ for k in range(filtrations[i][j]):
1054
+ st.insert(splx, np.asarray(filtrations[i][j][k], dtype = s.dtype))
1055
+ else:
1056
+ st.insert_batch(np.asarray(boundaries[dim], dtype= np.int32),np.asarray(filtrations[dim], dtype=s.dtype))
1057
+ return st
1058
+
1059
+
1060
+ def _is_slicer(object input)->bool:
1061
+ """
1062
+ Checks if the input is a slicer. Equivalent (but faster) to `isinstance(input, multipers.slicer.Slicer_type)`.
1063
+ """
1064
+ return (False
1065
+ {{for D in slicers}}
1066
+ or isinstance(input, {{D['PYTHON_TYPE']}})
1067
+ {{endfor}}
1068
+ )
1069
+ def is_slicer(input, bool allow_minpres=True)->bool:
1070
+ if _is_slicer(input):
1071
+ return True
1072
+ if allow_minpres and isinstance(input, list) or isinstance(input, tuple):
1073
+ if len(input)>0 and all((_is_slicer(s) and s.is_minpres for s in input)):
1074
+ return True
1075
+ return False
1076
+
1077
+
1078
+ def to_blocks(input):
1079
+ """
1080
+ Converts input to blocks, if possible.
1081
+ """
1082
+ if is_slicer(input):
1083
+ return slicer2blocks(input)
1084
+ if isinstance(input, list) or isinstance(input, tuple):
1085
+ return input
1086
+ from multipers.simplex_tree_multi import is_simplextree_multi
1087
+ if is_simplextree_multi(input):
1088
+ return input._to_scc()
1089
+ if isinstance(input, str) or isinstance(input, os.PathLike):
1090
+ from multipers.io import scc_parser
1091
+ return scc_parser(input)
1092
+ raise ValueError("Input cannot be converted to blocks.")
1093
+
1094
+
1095
+ def get_matrix_slicer(bool is_vineyard, bool is_k_critical, dtype:type|_valid_dtypes, str col, str pers_backend, str filtration_container:_filtration_container_type):
1096
+ """
1097
+ Given various parameters, returns the specific slicer type associated with them.
1098
+ """
1099
+ if False:
1100
+ pass
1101
+ {{for D in slicers}}
1102
+ {{if not D['IS_SIMPLICIAL']}}
1103
+ elif (
1104
+ is_vineyard == {{D['IS_VINE']}}
1105
+ and is_k_critical == {{D['IS_KCRITICAL']}}
1106
+ and np.dtype(dtype) is np.dtype({{D['PY_VALUE_TYPE']}})
1107
+ and col.lower() == "{{D['COLUMN_TYPE']}}".lower()
1108
+ and "{{D['PERS_BACKEND_TYPE']}}".lower() == pers_backend.lower()
1109
+ and filtration_container.lower() == "{{D['SHORT_FILTRATION_TYPE']}}".lower()
1110
+ ):
1111
+ return {{D['PYTHON_TYPE']}}
1112
+ {{endif}}
1113
+ {{endfor}}
1114
+ else:
1115
+ raise ValueError(f"Unimplemented combo for {pers_backend} : {is_vineyard=}, {is_k_critical=}, {dtype=}, {col=}, {filtration_container=}")
1116
+
1117
+
1118
+
1119
+
1120
+ def _hilbert_signed_measure(slicer,
1121
+ vector[indices_type] degrees,
1122
+ bool zero_pad=False,
1123
+ indices_type n_jobs=0,
1124
+ bool verbose=False,
1125
+ # bool expand_collapse=False,
1126
+ # grid_conversion = None,
1127
+ bool ignore_inf = True,
1128
+ ):
1129
+ """
1130
+ Computes the signed measures given by the decomposition of the hilbert function.
1131
+
1132
+ Input
1133
+ -----
1134
+
1135
+ - simplextree:SimplexTreeMulti, the multifiltered simplicial complex
1136
+ - degrees:array-like of ints, the degrees to compute
1137
+ - n_jobs:int, number of jobs. Defaults to #cpu, but when doing parallel computations of signed measures, we recommend setting this to 1.
1138
+ - verbose:bool, prints c++ logs.
1139
+
1140
+ Output
1141
+ ------
1142
+
1143
+ `[signed_measure_of_degree for degree in degrees]`
1144
+ with `signed_measure_of_degree` of the form `(dirac location, dirac weights)`.
1145
+ """
1146
+
1147
+ assert slicer.is_squeezed, "Squeeze grid first."
1148
+ if slicer.is_squeezed:
1149
+ grid_shape = np.array([len(f) for f in slicer.filtration_grid])
1150
+ else:
1151
+ grid_shape = (slicer.filtration_bounds()[1]).astype(python_indices_type)+1
1152
+ if zero_pad:
1153
+ for i, _ in enumerate(grid_shape):
1154
+ grid_shape[i] += 1 # adds a 0
1155
+ assert len(grid_shape) == slicer.num_parameters, "Grid shape size has to be the number of parameters."
1156
+ grid_shape_with_degree = np.asarray(np.concatenate([[len(degrees)], grid_shape]), dtype=python_indices_type)
1157
+ container_array = np.ascontiguousarray(np.zeros(grid_shape_with_degree, dtype=python_tensor_dtype).flatten())
1158
+ 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[])"
1159
+ cdef vector[indices_type] c_grid_shape = grid_shape_with_degree
1160
+ cdef tensor_dtype[::1] container = container_array
1161
+ cdef tensor_dtype* container_ptr = &container[0]
1162
+ cdef signed_measure_type out = _compute_hilbert_sm(slicer, container_ptr, c_grid_shape, degrees,n_jobs, verbose, zero_pad, ignore_inf)
1163
+ pts, weights = np.asarray(out.first, dtype=python_indices_type).reshape(-1, slicer.num_parameters+1), np.asarray(out.second, dtype=python_tensor_dtype)
1164
+ slices = np.concatenate([np.searchsorted(pts[:,0], np.arange(degrees.size())), [pts.shape[0]] ])
1165
+ sms = [
1166
+ (pts[slices[i]:slices[i+1],1:],weights[slices[i]:slices[i+1]])
1167
+ for i in range(slices.shape[0]-1)
1168
+ ]
1169
+ return sms
1170
+
1171
+
1172
+ ## Rank invariant
1173
+
1174
+
1175
+ ## TODO : It is not necessary to do the Möbius inversion in python.
1176
+ ## fill rank in flipped death, then differentiate in cpp, then reflip with numpy.
1177
+ def _rank_from_slicer(
1178
+ slicer,
1179
+ vector[indices_type] degrees,
1180
+ bool verbose=False,
1181
+ indices_type n_jobs=1,
1182
+ bool zero_pad = False,
1183
+ grid_shape=None,
1184
+ bool plot=False,
1185
+ bool return_raw=False,
1186
+ bool ignore_inf = True,
1187
+ ):
1188
+ # cdef intptr_t slicer_ptr = <intptr_t>(slicer.get_ptr())
1189
+ if grid_shape is None:
1190
+ if slicer.is_squeezed:
1191
+ grid_shape = np.array([len(f) for f in slicer.filtration_grid])
1192
+ else:
1193
+ grid_shape = (slicer.filtration_bounds()[1]).astype(python_indices_type)+1
1194
+ grid_shape = np.asarray(grid_shape)
1195
+
1196
+ cdef int num_parameters = len(grid_shape)
1197
+
1198
+ # if zero_pad:
1199
+ # grid_shape += 1
1200
+ # for i, _ in enumerate(grid_shape):
1201
+ # grid_shape[i] += 1 # adds a 0
1202
+ # for i,f in enumerate(grid_conversion):
1203
+ # grid_conversion[i] = np.concatenate([f, [mass_default[i]]])
1204
+
1205
+
1206
+ grid_shape_with_degree = np.asarray(np.concatenate([[len(degrees)], grid_shape, grid_shape]), dtype=python_indices_type)
1207
+ if verbose:
1208
+ print("Container shape: ", grid_shape_with_degree)
1209
+ print("Mallocing...", end="", flush=True)
1210
+ t0 = time.time()
1211
+ container_array = np.ascontiguousarray(np.zeros(grid_shape_with_degree, dtype=python_tensor_dtype).ravel())
1212
+ if verbose:
1213
+ print(f" Done. ({time.time()-t0:.3f}s).")
1214
+ 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[])"
1215
+ # if zero_pad:
1216
+ # grid_shape_with_degree[1:] -= 1
1217
+ cdef vector[indices_type] c_grid_shape = grid_shape_with_degree
1218
+ cdef tensor_dtype[::1] container = container_array
1219
+ cdef tensor_dtype* container_ptr = &container[0]
1220
+
1221
+ ## SLICERS
1222
+ if verbose:
1223
+ print("Computing rank invariant...", end="", flush=True)
1224
+ t0 = time.time()
1225
+ _compute_rank_invariant(slicer, container_ptr, c_grid_shape, degrees, n_jobs, ignore_inf)
1226
+ if verbose:
1227
+ print(f" Done. ({time.time()-t0:.3f}s).")
1228
+
1229
+ if verbose:
1230
+ print("Computing Möbius inversion...", end="", flush=True)
1231
+ t0 = time.time()
1232
+ # if zero_pad:
1233
+ # grid_shape_with_degree[1:] += 1
1234
+ rank = container_array.reshape(grid_shape_with_degree)
1235
+ rank = tuple(rank_decomposition_by_rectangles(rank_of_degree, threshold=zero_pad) for rank_of_degree in rank)
1236
+ if verbose:
1237
+ print(f" Done. ({time.time()-t0:.3f}s).")
1238
+
1239
+ if return_raw:
1240
+ return rank
1241
+
1242
+ def clean_rank(rank_decomposition):
1243
+ (coords, weights) = sparsify(np.ascontiguousarray(rank_decomposition))
1244
+ births = coords[:,:num_parameters]
1245
+ deaths = coords[:,num_parameters:]
1246
+ correct_indices = np.all(births<=deaths, axis=1)
1247
+ coords = coords[correct_indices]
1248
+ weights = weights[correct_indices]
1249
+ return coords, weights
1250
+
1251
+ if verbose:
1252
+ print("Cleaning output...", end="", flush=True)
1253
+ t0 = time.time()
1254
+ out = tuple(clean_rank(rank_decomposition) for rank_decomposition in rank)
1255
+ if verbose:
1256
+ print(f" Done. ({time.time()-t0:.3f}s).")
1257
+ return out
1258
+ # %%
1259
+