multipers 2.3.3b6__cp311-cp311-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.

Potentially problematic release.


This version of multipers might be problematic. Click here for more details.

Files changed (183) hide show
  1. multipers/.dylibs/libc++.1.0.dylib +0 -0
  2. multipers/.dylibs/libtbb.12.16.dylib +0 -0
  3. multipers/__init__.py +33 -0
  4. multipers/_signed_measure_meta.py +453 -0
  5. multipers/_slicer_meta.py +211 -0
  6. multipers/array_api/__init__.py +45 -0
  7. multipers/array_api/numpy.py +41 -0
  8. multipers/array_api/torch.py +58 -0
  9. multipers/data/MOL2.py +458 -0
  10. multipers/data/UCR.py +18 -0
  11. multipers/data/__init__.py +1 -0
  12. multipers/data/graphs.py +466 -0
  13. multipers/data/immuno_regions.py +27 -0
  14. multipers/data/minimal_presentation_to_st_bf.py +0 -0
  15. multipers/data/pytorch2simplextree.py +91 -0
  16. multipers/data/shape3d.py +101 -0
  17. multipers/data/synthetic.py +113 -0
  18. multipers/distances.py +202 -0
  19. multipers/filtration_conversions.pxd +229 -0
  20. multipers/filtration_conversions.pxd.tp +84 -0
  21. multipers/filtrations/__init__.py +18 -0
  22. multipers/filtrations/density.py +574 -0
  23. multipers/filtrations/filtrations.py +361 -0
  24. multipers/filtrations.pxd +224 -0
  25. multipers/function_rips.cpython-311-darwin.so +0 -0
  26. multipers/function_rips.pyx +105 -0
  27. multipers/grids.cpython-311-darwin.so +0 -0
  28. multipers/grids.pyx +433 -0
  29. multipers/gudhi/Persistence_slices_interface.h +132 -0
  30. multipers/gudhi/Simplex_tree_interface.h +239 -0
  31. multipers/gudhi/Simplex_tree_multi_interface.h +551 -0
  32. multipers/gudhi/cubical_to_boundary.h +59 -0
  33. multipers/gudhi/gudhi/Bitmap_cubical_complex.h +450 -0
  34. multipers/gudhi/gudhi/Bitmap_cubical_complex_base.h +1070 -0
  35. multipers/gudhi/gudhi/Bitmap_cubical_complex_periodic_boundary_conditions_base.h +579 -0
  36. multipers/gudhi/gudhi/Debug_utils.h +45 -0
  37. multipers/gudhi/gudhi/Fields/Multi_field.h +484 -0
  38. multipers/gudhi/gudhi/Fields/Multi_field_operators.h +455 -0
  39. multipers/gudhi/gudhi/Fields/Multi_field_shared.h +450 -0
  40. multipers/gudhi/gudhi/Fields/Multi_field_small.h +531 -0
  41. multipers/gudhi/gudhi/Fields/Multi_field_small_operators.h +507 -0
  42. multipers/gudhi/gudhi/Fields/Multi_field_small_shared.h +531 -0
  43. multipers/gudhi/gudhi/Fields/Z2_field.h +355 -0
  44. multipers/gudhi/gudhi/Fields/Z2_field_operators.h +376 -0
  45. multipers/gudhi/gudhi/Fields/Zp_field.h +420 -0
  46. multipers/gudhi/gudhi/Fields/Zp_field_operators.h +400 -0
  47. multipers/gudhi/gudhi/Fields/Zp_field_shared.h +418 -0
  48. multipers/gudhi/gudhi/Flag_complex_edge_collapser.h +337 -0
  49. multipers/gudhi/gudhi/Matrix.h +2107 -0
  50. multipers/gudhi/gudhi/Multi_critical_filtration.h +1038 -0
  51. multipers/gudhi/gudhi/Multi_persistence/Box.h +174 -0
  52. multipers/gudhi/gudhi/Multi_persistence/Line.h +282 -0
  53. multipers/gudhi/gudhi/Off_reader.h +173 -0
  54. multipers/gudhi/gudhi/One_critical_filtration.h +1441 -0
  55. multipers/gudhi/gudhi/Persistence_matrix/Base_matrix.h +769 -0
  56. multipers/gudhi/gudhi/Persistence_matrix/Base_matrix_with_column_compression.h +686 -0
  57. multipers/gudhi/gudhi/Persistence_matrix/Boundary_matrix.h +842 -0
  58. multipers/gudhi/gudhi/Persistence_matrix/Chain_matrix.h +1350 -0
  59. multipers/gudhi/gudhi/Persistence_matrix/Id_to_index_overlay.h +1105 -0
  60. multipers/gudhi/gudhi/Persistence_matrix/Position_to_index_overlay.h +859 -0
  61. multipers/gudhi/gudhi/Persistence_matrix/RU_matrix.h +910 -0
  62. multipers/gudhi/gudhi/Persistence_matrix/allocators/entry_constructors.h +139 -0
  63. multipers/gudhi/gudhi/Persistence_matrix/base_pairing.h +230 -0
  64. multipers/gudhi/gudhi/Persistence_matrix/base_swap.h +211 -0
  65. multipers/gudhi/gudhi/Persistence_matrix/boundary_cell_position_to_id_mapper.h +60 -0
  66. multipers/gudhi/gudhi/Persistence_matrix/boundary_face_position_to_id_mapper.h +60 -0
  67. multipers/gudhi/gudhi/Persistence_matrix/chain_pairing.h +136 -0
  68. multipers/gudhi/gudhi/Persistence_matrix/chain_rep_cycles.h +190 -0
  69. multipers/gudhi/gudhi/Persistence_matrix/chain_vine_swap.h +616 -0
  70. multipers/gudhi/gudhi/Persistence_matrix/columns/chain_column_extra_properties.h +150 -0
  71. multipers/gudhi/gudhi/Persistence_matrix/columns/column_dimension_holder.h +106 -0
  72. multipers/gudhi/gudhi/Persistence_matrix/columns/column_utilities.h +219 -0
  73. multipers/gudhi/gudhi/Persistence_matrix/columns/entry_types.h +327 -0
  74. multipers/gudhi/gudhi/Persistence_matrix/columns/heap_column.h +1140 -0
  75. multipers/gudhi/gudhi/Persistence_matrix/columns/intrusive_list_column.h +934 -0
  76. multipers/gudhi/gudhi/Persistence_matrix/columns/intrusive_set_column.h +934 -0
  77. multipers/gudhi/gudhi/Persistence_matrix/columns/list_column.h +980 -0
  78. multipers/gudhi/gudhi/Persistence_matrix/columns/naive_vector_column.h +1092 -0
  79. multipers/gudhi/gudhi/Persistence_matrix/columns/row_access.h +192 -0
  80. multipers/gudhi/gudhi/Persistence_matrix/columns/set_column.h +921 -0
  81. multipers/gudhi/gudhi/Persistence_matrix/columns/small_vector_column.h +1093 -0
  82. multipers/gudhi/gudhi/Persistence_matrix/columns/unordered_set_column.h +1012 -0
  83. multipers/gudhi/gudhi/Persistence_matrix/columns/vector_column.h +1244 -0
  84. multipers/gudhi/gudhi/Persistence_matrix/matrix_dimension_holders.h +186 -0
  85. multipers/gudhi/gudhi/Persistence_matrix/matrix_row_access.h +164 -0
  86. multipers/gudhi/gudhi/Persistence_matrix/ru_pairing.h +156 -0
  87. multipers/gudhi/gudhi/Persistence_matrix/ru_rep_cycles.h +376 -0
  88. multipers/gudhi/gudhi/Persistence_matrix/ru_vine_swap.h +540 -0
  89. multipers/gudhi/gudhi/Persistent_cohomology/Field_Zp.h +118 -0
  90. multipers/gudhi/gudhi/Persistent_cohomology/Multi_field.h +173 -0
  91. multipers/gudhi/gudhi/Persistent_cohomology/Persistent_cohomology_column.h +128 -0
  92. multipers/gudhi/gudhi/Persistent_cohomology.h +745 -0
  93. multipers/gudhi/gudhi/Points_off_io.h +171 -0
  94. multipers/gudhi/gudhi/Simple_object_pool.h +69 -0
  95. multipers/gudhi/gudhi/Simplex_tree/Simplex_tree_iterators.h +463 -0
  96. multipers/gudhi/gudhi/Simplex_tree/Simplex_tree_node_explicit_storage.h +83 -0
  97. multipers/gudhi/gudhi/Simplex_tree/Simplex_tree_siblings.h +106 -0
  98. multipers/gudhi/gudhi/Simplex_tree/Simplex_tree_star_simplex_iterators.h +277 -0
  99. multipers/gudhi/gudhi/Simplex_tree/hooks_simplex_base.h +62 -0
  100. multipers/gudhi/gudhi/Simplex_tree/indexing_tag.h +27 -0
  101. multipers/gudhi/gudhi/Simplex_tree/serialization_utils.h +62 -0
  102. multipers/gudhi/gudhi/Simplex_tree/simplex_tree_options.h +157 -0
  103. multipers/gudhi/gudhi/Simplex_tree.h +2794 -0
  104. multipers/gudhi/gudhi/Simplex_tree_multi.h +152 -0
  105. multipers/gudhi/gudhi/distance_functions.h +62 -0
  106. multipers/gudhi/gudhi/graph_simplicial_complex.h +104 -0
  107. multipers/gudhi/gudhi/persistence_interval.h +253 -0
  108. multipers/gudhi/gudhi/persistence_matrix_options.h +170 -0
  109. multipers/gudhi/gudhi/reader_utils.h +367 -0
  110. multipers/gudhi/mma_interface_coh.h +256 -0
  111. multipers/gudhi/mma_interface_h0.h +223 -0
  112. multipers/gudhi/mma_interface_matrix.h +293 -0
  113. multipers/gudhi/naive_merge_tree.h +536 -0
  114. multipers/gudhi/scc_io.h +310 -0
  115. multipers/gudhi/truc.h +1403 -0
  116. multipers/io.cpython-311-darwin.so +0 -0
  117. multipers/io.pyx +644 -0
  118. multipers/ml/__init__.py +0 -0
  119. multipers/ml/accuracies.py +90 -0
  120. multipers/ml/invariants_with_persistable.py +79 -0
  121. multipers/ml/kernels.py +176 -0
  122. multipers/ml/mma.py +713 -0
  123. multipers/ml/one.py +472 -0
  124. multipers/ml/point_clouds.py +352 -0
  125. multipers/ml/signed_measures.py +1589 -0
  126. multipers/ml/sliced_wasserstein.py +461 -0
  127. multipers/ml/tools.py +113 -0
  128. multipers/mma_structures.cpython-311-darwin.so +0 -0
  129. multipers/mma_structures.pxd +128 -0
  130. multipers/mma_structures.pyx +2786 -0
  131. multipers/mma_structures.pyx.tp +1094 -0
  132. multipers/multi_parameter_rank_invariant/diff_helpers.h +84 -0
  133. multipers/multi_parameter_rank_invariant/euler_characteristic.h +97 -0
  134. multipers/multi_parameter_rank_invariant/function_rips.h +322 -0
  135. multipers/multi_parameter_rank_invariant/hilbert_function.h +769 -0
  136. multipers/multi_parameter_rank_invariant/persistence_slices.h +148 -0
  137. multipers/multi_parameter_rank_invariant/rank_invariant.h +369 -0
  138. multipers/multiparameter_edge_collapse.py +41 -0
  139. multipers/multiparameter_module_approximation/approximation.h +2330 -0
  140. multipers/multiparameter_module_approximation/combinatory.h +129 -0
  141. multipers/multiparameter_module_approximation/debug.h +107 -0
  142. multipers/multiparameter_module_approximation/euler_curves.h +0 -0
  143. multipers/multiparameter_module_approximation/format_python-cpp.h +286 -0
  144. multipers/multiparameter_module_approximation/heap_column.h +238 -0
  145. multipers/multiparameter_module_approximation/images.h +79 -0
  146. multipers/multiparameter_module_approximation/list_column.h +174 -0
  147. multipers/multiparameter_module_approximation/list_column_2.h +232 -0
  148. multipers/multiparameter_module_approximation/ru_matrix.h +347 -0
  149. multipers/multiparameter_module_approximation/set_column.h +135 -0
  150. multipers/multiparameter_module_approximation/structure_higher_dim_barcode.h +36 -0
  151. multipers/multiparameter_module_approximation/unordered_set_column.h +166 -0
  152. multipers/multiparameter_module_approximation/utilities.h +403 -0
  153. multipers/multiparameter_module_approximation/vector_column.h +223 -0
  154. multipers/multiparameter_module_approximation/vector_matrix.h +331 -0
  155. multipers/multiparameter_module_approximation/vineyards.h +464 -0
  156. multipers/multiparameter_module_approximation/vineyards_trajectories.h +649 -0
  157. multipers/multiparameter_module_approximation.cpython-311-darwin.so +0 -0
  158. multipers/multiparameter_module_approximation.pyx +235 -0
  159. multipers/pickle.py +90 -0
  160. multipers/plots.py +456 -0
  161. multipers/point_measure.cpython-311-darwin.so +0 -0
  162. multipers/point_measure.pyx +395 -0
  163. multipers/simplex_tree_multi.cpython-311-darwin.so +0 -0
  164. multipers/simplex_tree_multi.pxd +134 -0
  165. multipers/simplex_tree_multi.pyx +10840 -0
  166. multipers/simplex_tree_multi.pyx.tp +2009 -0
  167. multipers/slicer.cpython-311-darwin.so +0 -0
  168. multipers/slicer.pxd +3034 -0
  169. multipers/slicer.pxd.tp +234 -0
  170. multipers/slicer.pyx +20481 -0
  171. multipers/slicer.pyx.tp +1088 -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 +62 -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-2.3.3b6.dist-info/METADATA +128 -0
  180. multipers-2.3.3b6.dist-info/RECORD +183 -0
  181. multipers-2.3.3b6.dist-info/WHEEL +6 -0
  182. multipers-2.3.3b6.dist-info/licenses/LICENSE +21 -0
  183. multipers-2.3.3b6.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1088 @@
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
+ }}
17
+
18
+ from multipers.simplex_tree_multi import SimplexTreeMulti, SimplexTreeMulti_type
19
+ import multipers
20
+ from typing import Optional,Literal
21
+ import threading
22
+ import os
23
+ from joblib import Parallel, delayed
24
+ from warnings import warn
25
+
26
+ from multipers.slicer cimport *
27
+ from multipers.filtrations cimport *
28
+ from multipers.filtration_conversions cimport *
29
+ ## TODO: these two are not needed, remove that by updating rank code.
30
+ from multipers.point_measure import sparsify, rank_decomposition_by_rectangles
31
+ from multipers.grids import compute_grid, sanitize_grid, evaluate_in_grid, _push_pts_to_line
32
+ from multipers.array_api import api_from_tensor
33
+
34
+ import numpy as np
35
+ cimport cython
36
+ from libcpp.string cimport string
37
+ # python_value_type = np.float32
38
+ from typing import Union
39
+ from cython.operator cimport dereference
40
+
41
+ ## WARNING : This is repeated in the pxd file ...
42
+ python_indices_type=np.int32
43
+ python_tensor_dtype = np.int32
44
+
45
+ global available_slicers
46
+ available_slicers = tuple((
47
+ {{for D in slicers}}
48
+ {{D['PYTHON_TYPE']}},
49
+ {{endfor}}
50
+ ))
51
+
52
+ global available_columns
53
+ available_columns = set((
54
+ {{for D in slicers}}
55
+ "{{D['COLUMN_TYPE']}}",
56
+ {{endfor}}
57
+ ))
58
+
59
+ global available_dtype
60
+ available_dtype = set([
61
+ {{for D in slicers}}
62
+ "{{D['PY_VALUE_TYPE']}}",
63
+ {{endfor}}
64
+ ])
65
+
66
+
67
+ global available_pers_backend
68
+ available_pers_backend = set([
69
+ {{for D in slicers}}
70
+ "{{D['PERS_BACKEND_TYPE']}}",
71
+ {{endfor}}
72
+ ])
73
+
74
+
75
+
76
+ global column_type
77
+ _column_type = Literal[
78
+ {{for D in slicers}}
79
+ "{{D['COLUMN_TYPE']}}",
80
+ {{endfor}}
81
+ ]
82
+
83
+ global _slicers_type
84
+ Slicer_type = Union[
85
+ {{for D in slicers}}
86
+ {{D['PYTHON_TYPE']}},
87
+ {{endfor}}
88
+ ]
89
+
90
+ global _valid_dtypes
91
+ _valid_dtype = Union[
92
+ {{for D in slicers}}
93
+ {{D['PY_VALUE_TYPE']}},
94
+ {{endfor}}
95
+ ]
96
+
97
+
98
+ global _valid_pers_backend
99
+ _valid_pers_backend = Literal[
100
+ {{for D in slicers}}
101
+ "{{D['PERS_BACKEND_TYPE']}}",
102
+ {{endfor}}
103
+ ]
104
+
105
+ {{for D in slicers}}
106
+
107
+ #------------------------------------------------------------------------------
108
+ cdef class {{D['PYTHON_TYPE']}}:
109
+ cdef {{D['C_TEMPLATE_TYPE']}} truc
110
+ cdef public object filtration_grid
111
+ cdef public int minpres_degree ## TODO : maybe change directly the degree in the minpres ?
112
+
113
+ def __repr__(self):
114
+ 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}]"
115
+
116
+ @property
117
+ def is_squeezed(self)->bool:
118
+ return self.filtration_grid is not None and len(self.filtration_grid) > 0 and len(self.filtration_grid[0]) > 0
119
+ @property
120
+ def is_minpres(self)->bool:
121
+ return self.minpres_degree>=0
122
+ @staticmethod
123
+ def _inf_value():
124
+ 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
125
+
126
+ def get_ptr(self):
127
+ """
128
+ Returns a pointer to the underlying C++ slicer.
129
+ """
130
+ return <intptr_t>(&self.truc)
131
+ def _from_ptr(self, intptr_t slicer_ptr):
132
+ self.truc = dereference(<{{D['C_TEMPLATE_TYPE']}}*>(slicer_ptr))
133
+ return self
134
+
135
+ {{if D['IS_SIMPLICIAL']}}
136
+ def __init__(self, st=None):
137
+ """
138
+ Constructs a slicer from a simplex tree.
139
+ """
140
+ pass
141
+ def __cinit__(self, st=None):
142
+ if st is None:
143
+ return
144
+ cdef intptr_t ptr = st.thisptr
145
+ 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)
146
+ self.truc = {{D['C_TEMPLATE_TYPE']}}(st_ptr)
147
+ self.minpres_degree = -1
148
+ {{elif D['IS_KCRITICAL']}}
149
+ def __init__(self, generator_maps=[], generator_dimensions=[], filtration_values=[]):
150
+ """
151
+ Constructs a slicer from
152
+ - scc-like blocks
153
+ or
154
+ - generator maps (Iterable of list of ints)
155
+ - generator dimensions (Iterable of int)
156
+ - filtration values (Iterable of filtration values)
157
+ """
158
+ pass
159
+ def __cinit__(self, generator_maps=[], generator_dimensions=[], filtration_values=[]):
160
+ """
161
+ Cython constructor
162
+ """
163
+ if len(generator_maps)>0 and len(generator_dimensions) == 0 and len(filtration_values) == 0:
164
+ from multipers._slicer_meta import _blocks2boundary_dimension_grades
165
+ generator_maps, generator_dimensions, filtration_values = _blocks2boundary_dimension_grades(
166
+ generator_maps,
167
+ inplace=False,
168
+ )
169
+ cdef uint32_t num_generators = len(generator_maps)
170
+ cdef vector[vector[uint32_t]] c_generator_maps
171
+ cdef vector[Multi_critical_filtration[{{D['C_VALUE_TYPE']}}]] c_filtration_values
172
+ for stuff in generator_maps:
173
+ c_generator_maps.push_back(<vector[uint32_t]>(stuff))
174
+ cdef Multi_critical_filtration[{{D['C_VALUE_TYPE']}}] cf
175
+ cdef One_critical_filtration[{{D['C_VALUE_TYPE']}}] inf
176
+ inf[0] = -inf[0]
177
+ cdef {{D['C_VALUE_TYPE']}}[:,:] F_view
178
+ for F in filtration_values:
179
+ cf.push_to_least_common_upper_bound(inf)
180
+ F_view = np.asarray(F, dtype = {{D['PY_VALUE_TYPE']}} )
181
+ for i in range(F_view.shape[0]):
182
+ cf.add_generator(_py21c_{{D['SHORT_VALUE_TYPE']}}(F_view[i]))
183
+ c_filtration_values.push_back(cf)
184
+ cdef vector[int] c_generator_dimensions = generator_dimensions
185
+ assert num_generators == c_generator_maps.size() == c_filtration_values.size(), "Invalid input, shape do not coincide."
186
+ self.truc = {{D['C_TEMPLATE_TYPE']}}(c_generator_maps,c_generator_dimensions, c_filtration_values)
187
+ self.minpres_degree = -1
188
+ {{else}}
189
+ def __init__(self, generator_maps=[], generator_dimensions=[], filtration_values=[]):
190
+ """
191
+ Constructs a slicer from
192
+ - generator maps (Iterable of list of ints)
193
+ - generator dimensions (Iterable of int)
194
+ - filtration values (Iterable of filtration values)
195
+ """
196
+ pass
197
+ @cython.boundscheck(False)
198
+ @cython.wraparound(False)
199
+ def __cinit__(self, generator_maps=[], generator_dimensions=[], filtration_values=[]):
200
+ """
201
+ Cython constructor
202
+ """
203
+ cdef uint32_t num_generators = len(generator_maps)
204
+ filtration_values = np.asarray(filtration_values, dtype = {{D['PY_VALUE_TYPE']}} )
205
+ assert len(filtration_values) == num_generators, f"Invalid input, shape do not coicide. Got sizes {num_generators,len(generator_dimensions),len(filtration_values)}."
206
+ cdef vector[vector[uint32_t]] c_generator_maps
207
+ cdef vector[One_critical_filtration[{{D['C_VALUE_TYPE']}}]] c_filtration_values
208
+
209
+ c_generator_maps.resize(num_generators)
210
+ c_filtration_values.resize(num_generators)
211
+ for i in range(num_generators):
212
+ c_generator_maps[i] = <vector[uint32_t]>(generator_maps[i])
213
+ c_filtration_values[i] = _py21c_{{D['SHORT_VALUE_TYPE']}}(filtration_values[i])
214
+ cdef vector[int] c_generator_dimensions = generator_dimensions
215
+
216
+ assert num_generators == c_generator_maps.size() == c_filtration_values.size() == c_generator_dimensions.size(), "Invalid input, shape do not coincide."
217
+ self.truc = {{D['C_TEMPLATE_TYPE']}}(c_generator_maps,c_generator_dimensions, c_filtration_values)
218
+ self.minpres_degree = -1
219
+ {{endif}}
220
+
221
+ def to_colexical(self, bool return_permutation = False)->{{D['PYTHON_TYPE']}}|tuple[{{D['PYTHON_TYPE']}},np.ndarray]:
222
+ # assert not self.is_squeezed, "Unsqueeze first, this is not implented yet for squeezed slicers"
223
+ new_slicer = {{D['PYTHON_TYPE']}}()
224
+ cdef pair[{{D['C_TEMPLATE_TYPE']}}, vector[unsigned int]] stuff = self.truc.colexical_rearange()
225
+
226
+ new_slicer.truc = stuff.first
227
+ new_slicer.minpres_degree = self.minpres_degree
228
+ new_slicer.filtration_grid = self.filtration_grid
229
+
230
+ if return_permutation:
231
+ return new_slicer, np.array(stuff.second, dtype=np.int32)
232
+ return new_slicer
233
+ def permute_generators(self, permutation)->{{D['PYTHON_TYPE']}}:
234
+ cdef vector[unsigned int] c_perm = permutation
235
+ new_slicer = {{D['PYTHON_TYPE']}}()
236
+ new_slicer.truc = self.truc.permute(c_perm)
237
+ new_slicer.minpres_degree = self.minpres_degree
238
+ return new_slicer
239
+
240
+ def copy(self)->{{D['PYTHON_TYPE']}}:
241
+ """
242
+ Returns a copy of the slicer.
243
+ """
244
+ copy_ = {{D['PYTHON_TYPE']}}()
245
+ copy_.truc = self.truc
246
+ copy_.minpres_degree = self.minpres_degree
247
+ copy_.filtration_grid = self.filtration_grid
248
+ return copy_
249
+ def compute_kernel_projective_cover(self, dim:Optional[int]=None)->{{D['PYTHON_TYPE']}}:
250
+ if len(self) == 0:
251
+ return {{D['PYTHON_TYPE']}}()
252
+ if dim is None:
253
+ dim = self.truc.get_dimension(0)
254
+ out = {{D['PYTHON_TYPE']}}()
255
+ out.truc = self.truc.projective_cover_kernel(dim)
256
+ out.filtration_grid = self.filtration_grid
257
+ return out
258
+
259
+ def get_barcode_idx(self, bool keep_inf = False):
260
+ """
261
+ Returns the current barcode.
262
+ """
263
+ return tuple(np.asarray(x) if len(x) else np.empty((0,2), dtype=int)for x in self.truc.get_barcode_idx())
264
+ def get_barcode(self, bool keep_inf = False):
265
+ """
266
+ Returns the current barcode.
267
+ """
268
+ if keep_inf:
269
+ bcs = tuple(np.asarray(stuff, dtype = {{D['PY_VALUE_TYPE']}}) for stuff in self.truc.get_barcode())
270
+ else:
271
+ bcs = {{D['PYTHON_TYPE']}}._threshold_bcs(self.truc.get_barcode())
272
+ return bcs
273
+ def push_to_line(self, basepoint, direction=None)->{{D['PYTHON_TYPE']}}:
274
+ """
275
+ Pushes the current slicer to the line defined by a basepoint and an optional direction.
276
+ If the direction is not provided, it is assumed to be diagonal.
277
+ """
278
+ {{if D['IS_FLOAT']}}
279
+ basepoint = np.asarray(basepoint, dtype = {{D['PY_VALUE_TYPE']}})
280
+ cdef Line[{{D['C_VALUE_TYPE']}}] line
281
+ if direction is None:
282
+ line = Line[{{D['C_VALUE_TYPE']}}](_py21c_{{D['SHORT_VALUE_TYPE']}}(basepoint))
283
+ else:
284
+ direction = np.asarray(direction, dtype = {{D['PY_VALUE_TYPE']}})
285
+ line = Line[{{D['C_VALUE_TYPE']}}](_py21c_{{D['SHORT_VALUE_TYPE']}}(basepoint),_py21c_{{D['SHORT_VALUE_TYPE']}}(direction))
286
+ self.truc.push_to(line)
287
+ return self
288
+ {{else}}
289
+ raise NotImplementedError("There is no `int` slicing.")
290
+ {{endif}}
291
+
292
+ @staticmethod
293
+ cdef _threshold_bcs(vector[vector[pair[{{D['C_VALUE_TYPE']}}, {{D['C_VALUE_TYPE']}}]]] bcs):
294
+ return tuple(np.fromiter((a for a in stuff if a.first < {{D['PYTHON_TYPE']}}._inf_value()), dtype=np.dtype(({{D['PY_VALUE_TYPE']}},2))) for stuff in bcs)
295
+ @staticmethod
296
+ def _bc_to_full(bcs, basepoint, direction=None):
297
+ # i, (b sv d), coords
298
+ basepoint = basepoint[None,None,:]
299
+ direction = 1 if direction is None else direction[None,None,:]
300
+ return tuple(bc[:,:,None]*direction + basepoint for bc in bcs)
301
+
302
+ def persistence_on_line(self,basepoint,direction=None, bool keep_inf=True, bool full=False, bool ignore_infinite_filtration_values = True):
303
+ """
304
+ Computes the persistence on a line L defined by
305
+ - a basepoint (num_parameters,) array
306
+ - an optional direction (num_parameters,) array
307
+
308
+ Warning: This is not parallelizable. Use `persitence_on_lines`.
309
+ """
310
+ {{if D['IS_FLOAT']}}
311
+ self.push_to_line(basepoint,direction)
312
+ self.truc.compute_persistence(ignore_infinite_filtration_values)
313
+ if keep_inf:
314
+ bcs = tuple(np.asarray(stuff, dtype = {{D['PY_VALUE_TYPE']}}) for stuff in self.truc.get_barcode())
315
+ else:
316
+ bcs = {{D['PYTHON_TYPE']}}._threshold_bcs(self.truc.get_barcode())
317
+
318
+ if full:
319
+ bcs = {{D['PYTHON_TYPE']}}._bc_to_full(bcs, basepoint, direction)
320
+ return bcs
321
+ {{else}}
322
+ if not self.is_squeezed:
323
+ raise ValueError("Unsqueeze tensor, or provide a filtration grid. Cannot slice lines with integers...")
324
+ api = api_from_tensor(self.filtration_grid[0])
325
+ s = self.unsqueeze()
326
+ fil = evaluate_in_grid(np.asarray(self.get_filtrations()), self.filtration_grid)
327
+ projected_fil =_push_pts_to_line(fil, basepoint, direction)
328
+ s.compute_persistence(projected_fil)
329
+ bcs_idx = s.get_barcode_idx()
330
+ bcs = tuple(
331
+ api.stack([projected_fil[bc[:,0]], projected_fil[bc[:,1]]], axis=1)
332
+ if bc.size>0
333
+ else api.empty((0,2), dtype = self.filtration_grid[0].dtype)
334
+ for bc in bcs_idx
335
+ )
336
+ if full:
337
+ bcs = self._bc_to_full(bcs, basepoint, direction)
338
+ return bcs
339
+ {{endif}}
340
+
341
+ def _custom_persistences_idx(self, filtration_array, bool ignore_inf=True):
342
+ filtration_array = np.asarray(filtration_array, dtype= {{D['PY_VALUE_TYPE']}})
343
+ cdef {{D['C_VALUE_TYPE']}}[:,:] arr_view = filtration_array
344
+ cdef int size = arr_view.shape[0]
345
+ if <int>arr_view.shape[1] != <int>self.truc.num_generators():
346
+ raise ValueError(f"Got filtration array of shape {filtration_array.shape=} / {arr_view.shape=}. Was expecting (-1, {len(self)=})")
347
+
348
+ return tuple(tuple(np.array(bc_idx_degree, dtype=int) for bc_idx_degree in bc_idx) for bc_idx in self.truc.custom_persistences(&arr_view[0,0], size, ignore_inf))
349
+
350
+ def persistence_on_lines(self, basepoints=None, directions=None, bool keep_inf=True, bool full=False, bool ignore_infinite_filtration_values = True):
351
+ """
352
+ Same as `persistence_on_line`, but with vineyards operation between
353
+ lines if `self.is_vine`, and in parallel otherwise.
354
+ """
355
+ cdef vector[vector[{{D['C_VALUE_TYPE']}}]] c_basepoints
356
+ cdef vector[pair[vector[{{D['C_VALUE_TYPE']}}], vector[{{D['C_VALUE_TYPE']}}]]] c_truc
357
+ cdef vector[vector[vector[pair[{{D['C_VALUE_TYPE']}}, {{D['C_VALUE_TYPE']}}]]]] c_out
358
+ if directions is None:
359
+ c_basepoints = basepoints
360
+ with nogil:
361
+ c_out = self.truc.persistence_on_lines(c_basepoints, ignore_infinite_filtration_values)
362
+ else:
363
+ c_truc = zip(basepoints,directions)
364
+ with nogil:
365
+ c_out = self.truc.persistence_on_lines(c_truc, ignore_infinite_filtration_values)
366
+ cdef int num_bc = c_basepoints.size()
367
+
368
+ if keep_inf:
369
+ out = tuple(tuple(
370
+ np.asarray(y, dtype = {{D['PY_VALUE_TYPE']}}) if len(y)>0 else np.empty((0,2), dtype = {{D['PY_VALUE_TYPE']}})
371
+ for y in x) for x in c_out)
372
+ else:
373
+ out = tuple({{D['PYTHON_TYPE']}}._threshold_bcs(x) for x in c_out)
374
+
375
+ if full:
376
+ _dirs = [None]*len(basepoints) if directions is None else directions
377
+ out = tuple({{D['PYTHON_TYPE']}}._bc_to_full(bcs, bp, dir) for bcs, bp, dir in zip(out,basepoints,_dirs))
378
+ return out
379
+
380
+
381
+ def __getstate__(self):
382
+ return (
383
+ self.get_boundaries(),
384
+ self.get_dimensions(),
385
+ self.get_filtrations(),
386
+ self.filtration_grid,
387
+ self.minpres_degree,
388
+ )
389
+ def __setstate__(self, tuple dump):
390
+ B, D, F, filtration_grid, minpres_degree = dump
391
+ copy = {{D['PYTHON_TYPE']}}(B,D,F)
392
+ self.truc = copy.truc
393
+ self.minpres_degree= minpres_degree
394
+ self.filtration_grid=filtration_grid
395
+
396
+ def __eq__(self, other):
397
+ ## True if they rpz the same complex (no reordering allowed yet),
398
+ # i.e., ignores minpres, squeeze
399
+ if other.is_squeezed:
400
+ return self == other.unsqueeze()
401
+ if self.is_squeezed:
402
+ return self.unsqueeze() == other
403
+ return (
404
+ np.array_equal(self.get_dimensions(), other.get_dimensions())
405
+ and
406
+ self.get_boundaries() == other.get_boundaries()
407
+ and
408
+ ## Kcritical are sorted + filtrationvalues is a dump.
409
+ # for non kcritical there is an unnecessary copy though
410
+ np.array_equal(self.get_filtrations_values(), other.get_filtrations_values())
411
+ )
412
+
413
+
414
+
415
+ def compute_persistence(self,one_filtration=None, bool ignore_infinite_filtration_values = True)->tuple:
416
+ """
417
+ Computes the current persistence, or the persistence
418
+ given by the filtration one_filtration (num_generators,).
419
+ """
420
+ if one_filtration is not None:
421
+ api = api_from_tensor(one_filtration)
422
+ one_filtration=api.astensor(one_filtration)
423
+ # self.truc.set_one_filtration(one_filtration)
424
+ # s = self.unsqueeze()
425
+ # fil = evaluate_in_grid(np.asarray(self.get_filtrations()), self.filtration_grid)
426
+ # projected_fil =_push_pts_to_line(fil, basepoint, direction)
427
+ if one_filtration.ndim > 2 or one_filtration.ndim == 0:
428
+ raise ValueError(f"Expected a filtration shape of the form ((num_1_param), num_generators). Got {one_filtration.shape=}")
429
+ squeeze = False
430
+ if one_filtration.ndim == 1:
431
+ one_filtration = one_filtration[None]
432
+ squeeze = True
433
+
434
+ bcs = self._custom_persistences_idx(api.asnumpy(one_filtration),ignore_infinite_filtration_values)
435
+
436
+ bcs = tuple(tuple(
437
+ api.stack([F[bc[:,0]], F[bc[:,1]]], axis=1)
438
+ if bc.size>0
439
+ else api.empty((0,2), dtype = F.dtype)
440
+ for bc in bcs_idx
441
+ )
442
+ for bcs_idx,F in zip(bcs,one_filtration)
443
+ )
444
+ return bcs[0] if squeeze else bcs
445
+
446
+ # TODO: Later
447
+ # if len(degrees)>0:
448
+ # self.truc.compute_persistence(degrees)
449
+ # else:
450
+ # self.truc.compute_persistence()
451
+ self.truc.compute_persistence(ignore_infinite_filtration_values)
452
+ # return self
453
+ return self.get_barcode()
454
+ def get_barcode(self):
455
+ """
456
+ Returns the barcode of the current 1d-persistence.
457
+ """
458
+ return tuple(np.asarray(bc) for bc in self.truc.get_barcode())
459
+ def sliced_filtration(self,basepoint, direction=None):
460
+ """
461
+ Computes the filtration on a line L defined by
462
+ - a basepoint (num_parameters,) array
463
+ - an optional direction (num_parameters,) array
464
+ """
465
+ self.push_to_line(basepoint,direction)
466
+ return np.asarray(self.truc.get_one_filtration())
467
+ def __len__(self):
468
+ return self.truc.num_generators()
469
+ @property
470
+ def num_generators(self):
471
+ return self.truc.num_generators()
472
+ @property
473
+ def num_parameters(self):
474
+ return self.truc.num_parameters()
475
+ @property
476
+ def info(self):
477
+ print(self.truc.to_str().decode())
478
+ def filtration_bounds(self) -> np.ndarray:
479
+ """
480
+ Computes the bounding box of the current multifiltration.
481
+ """
482
+ cdef pair[One_critical_filtration[{{D['C_VALUE_TYPE']}}],One_critical_filtration[{{D['C_VALUE_TYPE']}}]] box = self.truc.get_bounding_box()
483
+ cdef cnp.ndarray[{{D['C_VALUE_TYPE']}}, ndim=1] a = _ff21cview_{{D['SHORT_VALUE_TYPE']}}(&box.first)
484
+ cdef cnp.ndarray[{{D['C_VALUE_TYPE']}}, ndim=1] b = _ff21cview_{{D['SHORT_VALUE_TYPE']}}(&box.second)
485
+ return np.asarray([a,b])
486
+ def get_filtrations_values(self)->np.ndarray:
487
+ """
488
+ Returns the current filtration values of the slicer.
489
+ """
490
+ cdef vector[One_critical_filtration[{{D['C_VALUE_TYPE']}}]] v = self.truc.get_filtration_values()
491
+ out = _vff21cview_{{D['SHORT_VALUE_TYPE']}}(v, copy=True, duplicate=self.num_parameters)
492
+ return np.asarray(out)
493
+ def get_filtration_grid(self,grid_strategy:str="exact", **infer_grid_kwargs):
494
+ return compute_grid(
495
+ self.get_filtrations_values().T,
496
+ strategy=grid_strategy,
497
+ **infer_grid_kwargs,
498
+ )
499
+ def get_filtrations(self):
500
+ """
501
+ Returns a view of the filtration values, as a list of numpy arrays.
502
+ """
503
+ {{if D['IS_KCRITICAL']}}
504
+ return _vff2kcview_{{D['SHORT_VALUE_TYPE']}}(self.truc.get_filtrations(), copy=False, duplicate=self.num_parameters)
505
+ {{else}}
506
+ return _vff21cview_{{D['SHORT_VALUE_TYPE']}}(self.truc.get_filtrations(), copy=False, duplicate=self.num_parameters)
507
+ {{endif}}
508
+
509
+ def get_dimensions(self)-> np.ndarray:
510
+ """
511
+ Returns the ordered dimensions of the generators.
512
+ """
513
+ return np.asarray(self.truc.get_dimensions())
514
+ @property
515
+ def dimension(self)-> int:
516
+ """
517
+ Returns the maximum dimension of the complex.
518
+ """
519
+ return self.get_dimensions()[-1] if len(self)>0 else -np.inf
520
+ def prune_above_dimension(self,int max_dimension)->{{D['PYTHON_TYPE']}}:
521
+ """
522
+ Prunes the generators above a given dimension.
523
+ """
524
+ self.truc.prune_above_dimension(max_dimension)
525
+ return self
526
+ def get_boundaries(self)->tuple[tuple]:
527
+ """
528
+ Returns the boundaries of the generators.
529
+ """
530
+ return tuple(tuple(b) for b in self.truc.get_boundaries())
531
+ def grid_squeeze(
532
+ self,
533
+ filtration_grid=None,
534
+ strategy="exact",
535
+ resolution:Optional[int]=None,
536
+ bool coordinates=True,
537
+ bool inplace = False,
538
+ grid_strategy=None
539
+ )->{{D['PYTHON_TYPE'][:-3]+"i32"}}|{{D['PYTHON_TYPE']}}:
540
+ """
541
+ Coarsen the filtration values on a grid. This is necessary to compute some invariants.
542
+
543
+ If the filtration grid is not given, it is infered from filtration values,
544
+ using the :func:`multipers.grids.compute_grid` function, whose args are
545
+ - grid_strategy:str see `multipers.grids.available_strategies`. Defaults to exact.
546
+ - resolution:int if strategy is not exact.
547
+
548
+ - inplace:bool if true, does the operation inplace, i.e., doesn't return a copy.
549
+ """
550
+ if grid_strategy is not None:
551
+ warn("`grid_strategy` is deprecated, use `strategy` instead.",DeprecationWarning)
552
+ strategy=grid_strategy
553
+
554
+ if self.is_squeezed:
555
+ warn("(copy warning) Squeezing an already squeezed slicer.")
556
+ temp = self.unsqueeze()
557
+ subgrid = compute_grid(self.filtration_grid, strategy=strategy, resolution=resolution)
558
+ return temp.grid_squeeze(subgrid, coordinates=coordinates, inplace=inplace)
559
+
560
+ if filtration_grid is None:
561
+ filtration_grid = compute_grid(
562
+ self.get_filtrations_values().T,
563
+ strategy=strategy,
564
+ resolution=resolution)
565
+ cdef vector[vector[{{D['C_VALUE_TYPE']}}]] grid = filtration_grid
566
+ if inplace or not coordinates:
567
+ self.truc.coarsen_on_grid_inplace(grid, coordinates)
568
+ if coordinates:
569
+ self.filtration_grid = filtration_grid
570
+ else:
571
+ {{if D['COLUMN_TYPE'] is None}}
572
+ raise ValueError("WIP")
573
+ {{else}}
574
+ out = {{D['PYTHON_TYPE'][:-3]+"i32"}}()
575
+ out.truc = self.truc.coarsen_on_grid(grid)
576
+ if coordinates:
577
+ out.filtration_grid = sanitize_grid(filtration_grid)
578
+ out.minpres_degree = self.minpres_degree
579
+ return out
580
+ {{endif}}
581
+ return self
582
+ def _clean_filtration_grid(self):
583
+ """
584
+ Removes the values in filtration_grid that are not linked to any splx.
585
+ """
586
+ if not self.is_squeezed:
587
+ raise ValueError("No grid to clean.")
588
+ F = self.filtration_grid
589
+ self.filtration_grid=None
590
+ cleaned_coordinates = compute_grid(self)
591
+ new_slicer = self.grid_squeeze(cleaned_coordinates)
592
+
593
+ self._from_ptr(new_slicer.get_ptr())
594
+ self.filtration_grid = tuple(f[g] for f,g in zip(F,cleaned_coordinates))
595
+ return self
596
+
597
+ def minpres(self,
598
+ int degree=-1,
599
+ list[int] degrees=[],
600
+ str backend:Literal["mpfree", "2pac"]="mpfree",
601
+ str slicer_backend:Literal["matrix","clement","graph"]="matrix",
602
+ bool vineyard={{D['IS_VINE']}},
603
+ id :Optional[str] = None,
604
+ dtype = {{D['PY_VALUE_TYPE']}},
605
+ **minpres_kwargs
606
+ )->Slicer_type:
607
+ """
608
+ Computes the minimal presentation of the slicer, and returns it as a new slicer.
609
+ See :func:`multipers.slicer.minimal_presentation`.
610
+ """
611
+ new_slicer = minimal_presentation(self, degree=degree, degrees=degrees, backend=backend, slicer_backend=slicer_backend, dtype=dtype, vineyard=vineyard, id=id, **minpres_kwargs)
612
+ return new_slicer
613
+
614
+ @property
615
+ def dtype(self)->type:
616
+ return {{D['PY_VALUE_TYPE']}}
617
+ @property
618
+ def col_type(self)->str:
619
+ return "{{D['COLUMN_TYPE']}}"
620
+ @property
621
+ def is_vine(self)->bool:
622
+ return {{D['IS_VINE']}}
623
+ @property
624
+ def is_kcritical(self)->bool:
625
+ return {{D['IS_KCRITICAL']}}
626
+
627
+ @property
628
+ def pers_backend(self)->str:
629
+ return "{{D['PERS_BACKEND_TYPE']}}"
630
+
631
+ {{if D['IS_VINE']}}
632
+ def vine_update(self,basepoint,direction=None)->{{D['PYTHON_TYPE']}}:
633
+ """
634
+ Updates the barcode, on a line, using the vineyard algorithm.
635
+ """
636
+ self.push_to_line(basepoint,direction)
637
+ self.truc.vineyard_update()
638
+ return self
639
+ def get_representative_cycles(self, bool update=True, bool detailed=False):
640
+ """
641
+ Returns the representative cycles of the current barcode.
642
+ Recomputes the generators if update=True
643
+ """
644
+ return self.truc.get_representative_cycles(update, detailed)
645
+ def get_permutation(self):
646
+ """
647
+ Returns the current generator permutation (w.r.t. vineyard).
648
+ """
649
+ return self.truc.get_current_order()
650
+ {{endif}}
651
+
652
+ def to_scc(
653
+ self,
654
+ path:os.PathLike,
655
+ int num_parameters = -1,
656
+ int degree = -1,
657
+ bool rivet_compatible = False,
658
+ bool ignore_last_generators = False,
659
+ bool strip_comments = False,
660
+ bool reverse = False,
661
+ bool unsqueeze = True,
662
+ ):
663
+ """
664
+ Writes current slicer to a file in scc format.
665
+ """
666
+ if degree == -1 and not rivet_compatible:
667
+ degree = 1
668
+ cdef string c_path = path.encode(encoding="utf-8")
669
+ if self.is_squeezed and unsqueeze:
670
+ kwargs = locals()
671
+ kwargs.pop("self",0)
672
+ kwargs.pop("c_path",0)
673
+ self.unsqueeze().to_scc(**kwargs)
674
+ return
675
+ with nogil:
676
+ self.truc.write_to_scc_file(c_path, num_parameters, degree, rivet_compatible, ignore_last_generators, strip_comments, reverse)
677
+
678
+ {{if not D['IS_KCRITICAL']}}
679
+ def _build_from_scc_file(self, path:os.PathLike, bool rivet_compatible = False, bool reverse = False, int shift_dimension = 0)->{{D['PYTHON_TYPE']}}:
680
+ """
681
+ Builds the slicer from the given scc file. Should be empty before, otherwise will be completely overwritten.
682
+ """
683
+ cdef string c_path = path.encode(encoding="utf-8")
684
+ with nogil:
685
+ self.truc.build_from_scc_file(c_path, rivet_compatible, reverse, shift_dimension)
686
+ return self
687
+
688
+ def unsqueeze(self, grid=None)->{{D['PYTHON_TYPE'][:-3]+"f64"}}:
689
+ from multipers.grids import evaluate_in_grid, sanitize_grid
690
+ from multipers import Slicer
691
+ grid = self.filtration_grid if grid is None else grid
692
+ grid = sanitize_grid(grid, numpyfy=True)
693
+ new_filtrations = evaluate_in_grid(np.asarray(self.get_filtrations(), dtype=np.int32), grid)
694
+ new_slicer = {{D['PYTHON_TYPE'][:-3]+"f64"}}(
695
+ self.get_boundaries(),
696
+ self.get_dimensions(),
697
+ new_filtrations,
698
+ )
699
+ new_slicer.minpres_degree=self.minpres_degree
700
+ return new_slicer
701
+ {{endif}}
702
+
703
+
704
+ {{endfor}}
705
+
706
+ from libcpp.vector cimport vector
707
+ from libcpp.set cimport set as cset
708
+ cdef extern from "gudhi/cubical_to_boundary.h" namespace "":
709
+ void _to_boundary(const vector[unsigned int]&, vector[vector[unsigned int]]&, vector[int]&) except + nogil
710
+ void get_vertices(unsigned int, cset[unsigned int]&, const vector[vector[unsigned int]]&) nogil
711
+
712
+
713
+ {{for pytype,ctype,fshort in dtypes}}
714
+ def _from_bitmap{{fshort}}(image, **slicer_kwargs):
715
+ from multipers import Slicer
716
+ dtype = slicer_kwargs.get("dtype", image.dtype)
717
+ slicer_kwargs["dtype"] = dtype
718
+ if image.dtype != dtype:
719
+ raise ValueError(f"Invalid type matching. Got {dtype=} and {image.dtype=}")
720
+ _Slicer = Slicer(return_type_only=True, **slicer_kwargs)
721
+ cdef vector[unsigned int] img_shape = image.shape[:-1]
722
+ cdef unsigned int num_parameters = image.shape[-1]
723
+ cdef vector[vector[unsigned int]] gen_maps
724
+ cdef vector[int] gen_dims
725
+
726
+ with nogil:
727
+ _to_boundary(img_shape,gen_maps, gen_dims)
728
+
729
+ cdef cset[unsigned int] vertices
730
+
731
+ cdef unsigned int num_gens = gen_dims.size()
732
+ filtration_values = np.zeros(shape=(num_gens, num_parameters), dtype = {{pytype}}) - _Slicer._inf_value()
733
+ cdef {{ctype}}[:,:] F = filtration_values
734
+ cdef {{ctype}}[:,:] c_img = image.reshape(-1,num_parameters)
735
+ with nogil:
736
+ for i in range(num_gens):
737
+ # with gil:
738
+ # print(f"idx {i}:", end="")
739
+ vertices.clear()
740
+ get_vertices(i,vertices,gen_maps)
741
+
742
+ # with gil:
743
+ # print(f"v = {vertices}:", end="")
744
+ for k in vertices:
745
+ for j in range(num_parameters):
746
+ F[i,j] = max(F[i,j], c_img[k,j])
747
+
748
+ # with gil:
749
+ # print(f"F = {np.asarray(F[i])}")
750
+ slicer = _Slicer(gen_maps, gen_dims, filtration_values)
751
+ return slicer
752
+ {{endfor}}
753
+
754
+ def from_bitmap(img, **kwargs):
755
+ img = np.asarray(img)
756
+ {{for pytype,ctype,stype in dtypes}}
757
+ if img.dtype == {{pytype}}:
758
+ return _from_bitmap{{stype}}(img, **kwargs)
759
+ {{endfor}}
760
+ raise ValueError(f"Invalid dtype. Got {img.dtype=}, was expecting {available_dtype=}.")
761
+
762
+ def from_function_delaunay(
763
+ points,
764
+ grades,
765
+ int degree=-1,
766
+ backend:Optional[_valid_pers_backend]=None,
767
+ vineyard=None, # Optionmal[bool], wait for cython
768
+ dtype=np.float64,
769
+ bool verbose = False,
770
+ bool clear = True,
771
+ ):
772
+ """
773
+ Given points in $\mathbb R^n$ and function grades, compute the function-delaunay
774
+ bifiltration as a in an scc format, and converts it into a slicer.
775
+
776
+ points : (num_pts, n) float array
777
+ grades : (num_pts,) float array
778
+ degree (opt) : if given, computes a minimal presentation of this homological degree first
779
+ backend : slicer backend, e.g. "matrix", "clement"
780
+ vineyard : bool, use a vineyard-compatible backend
781
+ """
782
+ from multipers.io import function_delaunay_presentation_to_slicer, _init_external_softwares
783
+ s = multipers.Slicer(None, backend=backend, vineyard=vineyard, dtype=dtype)
784
+ _init_external_softwares(requires=["function_delaunay"])
785
+ function_delaunay_presentation_to_slicer(s, points, grades, degree=degree, verbose=verbose,clear=clear)
786
+ if degree >= 0:
787
+ s.minpres_degree = degree
788
+ # s = s.minpres(degree=degree, force=True)
789
+ return s
790
+
791
+ def slicer2blocks(slicer, int degree = -1, bool reverse=True):
792
+ """
793
+ Convert any slicer to the block format a.k.a. scc format for python
794
+ """
795
+ dims = slicer.get_dimensions()
796
+ num_empty_blocks_to_add = 1 if degree == -1 else dims.min()-degree +1
797
+ _,counts = np.unique(dims, return_counts=True, )
798
+ indices = np.concatenate([[0],counts], dtype=np.int32).cumsum()
799
+ filtration_values = slicer.get_filtrations()
800
+ filtration_values = [filtration_values[indices[i]:indices[i+1]] for i in range(len(indices)-1)]
801
+ boundaries = slicer.get_boundaries()
802
+ boundaries = [boundaries[indices[i]:indices[i+1]] for i in range(len(indices)-1)]
803
+ shift = np.concatenate([[0], indices], dtype=np.int32)
804
+ boundaries = [tuple(np.asarray(x-s, dtype=np.int32) for x in block) for s,block in zip(shift,boundaries)]
805
+ blocks = [tuple((f,tuple(b))) for b,f in zip(boundaries,filtration_values)]
806
+ blocks = ([(np.empty((0,)),[])]*num_empty_blocks_to_add) + blocks
807
+ if reverse:
808
+ blocks.reverse()
809
+ return blocks
810
+
811
+ def minimal_presentation(
812
+ slicer,
813
+ int degree = -1,
814
+ degrees:Iterable[int]=[],
815
+ str backend:Literal["mpfree", "2pac", ""]="mpfree",
816
+ str slicer_backend:Literal["matrix","clement","graph"]="matrix",
817
+ bool vineyard=True,
818
+ id :Optional[str] =None,
819
+ dtype:type|_valid_dtypes=None,
820
+ int n_jobs = -1,
821
+ bool force=False,
822
+ bool auto_clean = True,
823
+ **minpres_kwargs
824
+ ):
825
+ """
826
+ Computes a minimal presentation of the multifiltered complex given by the slicer,
827
+ and returns it as a slicer.
828
+ Backends differents than `mpfree` are unstable.
829
+ """
830
+ from multipers.io import _init_external_softwares, input_path, scc_reduce_from_str_to_slicer
831
+ if is_slicer(slicer) and slicer.is_minpres and not force:
832
+ from warnings import warn
833
+ warn(f"(unnecessary computation) The slicer seems to be already reduced, from homology of degree {slicer.minpres_degree}.")
834
+ return slicer
835
+ _init_external_softwares(requires=[backend])
836
+ if len(degrees)>0:
837
+ def todo(int degree):
838
+ return minimal_presentation(slicer, degree=degree, backend=backend, slicer_backend=slicer_backend, vineyard=vineyard, id=id, **minpres_kwargs)
839
+ return tuple(
840
+ Parallel(n_jobs=n_jobs, backend="threading")(delayed(todo)(d) for d in degrees)
841
+ )
842
+ # return tuple(minimal_presentation(slicer, degree=d, backend=backend, slicer_backend=slicer_backend, vineyard=vineyard, id=id, **minpres_kwargs) for d in degrees)
843
+ assert degree>=0, f"Degree not provided."
844
+ if not np.any(slicer.get_dimensions() == degree):
845
+ return type(slicer)()
846
+ if id is None:
847
+ id = str(threading.get_native_id())
848
+ if dtype is None:
849
+ dtype = slicer.dtype
850
+ dimension = slicer.dimension - degree # latest = L-1, which is empty, -1 for degree 0, -2 for degree 1 etc.
851
+ slicer.to_scc(path=input_path+id, strip_comments=True, degree=degree-1, unsqueeze = False)
852
+ new_slicer = multipers.Slicer(None,backend=slicer_backend, vineyard=vineyard, dtype=dtype)
853
+ if backend=="mpfree":
854
+ shift_dimension=degree-1
855
+ else:
856
+ shift_dimension=degree
857
+ scc_reduce_from_str_to_slicer(path=input_path+id, slicer=new_slicer, dimension=dimension, backend=backend, shift_dimension=shift_dimension, **minpres_kwargs)
858
+
859
+ new_slicer.minpres_degree = degree
860
+ new_slicer.filtration_grid = slicer.filtration_grid if slicer.is_squeezed else None
861
+ if new_slicer.is_squeezed and auto_clean:
862
+ new_slicer = new_slicer._clean_filtration_grid()
863
+ return new_slicer
864
+
865
+
866
+ def to_simplextree(s:Slicer_type, max_dim:int=-1) -> SimplexTreeMulti_type:
867
+ """
868
+ Turns a --simplicial-- slicer into a simplextree.
869
+
870
+ Warning: Won't work for non-simplicial complexes,
871
+ i.e., complexes $K$ not satisfying
872
+ $\forall \sigma \in K,\, \mathrm{dim}(\sigma) = |\partial \sigma|-1$
873
+ """
874
+ dims = s.get_dimensions()
875
+ assert np.all(dims[:-1] <= dims[1:]), "Dims is not sorted."
876
+ idx = np.searchsorted(dims, np.unique(dims))
877
+ idx = np.concatenate([idx, [dims.shape[0]]])
878
+ if max_dim>=0:
879
+ idx = idx[:max_dim+2]
880
+
881
+ cdef vector[vector[int]] boundaries_ = s.get_boundaries()
882
+ cdef int a
883
+ cdef int b
884
+ if len(idx)>2:
885
+ a = idx[2]
886
+ b = idx[-1]
887
+ for i in range(a, b):
888
+ boundaries_[i] = np.unique(np.concatenate([boundaries_[k] for k in boundaries_[i]]))
889
+ cdef int num_dims = len(idx)-1
890
+ boundaries = [np.asarray(boundaries_[idx[i]:idx[i+1]], dtype=np.int32).T for i in range(num_dims)]
891
+ boundaries[0] = np.arange(boundaries[0].shape[1])[None,:]
892
+ filtrations = s.get_filtrations()
893
+ num_parameters = s.num_parameters
894
+ filtrations=tuple(filtrations[idx[i]:idx[i+1]] for i in range(num_dims)) # TODO : optimize ?
895
+ st = SimplexTreeMulti(num_parameters = num_parameters, dtype = s.dtype, kcritical=s.is_kcritical)
896
+ for dim in range(num_dims):
897
+ if s.is_kcritical: ## TODO : this may be very slow
898
+ # for b,f in zip(boundaries[i].T,filtrations[i]):
899
+ # for g in np.asarray(f):
900
+ # st.insert(np.asarray(b, dtype = np.int32),np.asarray(g, dtype=s.dtype))
901
+ for j in range(boundaries[i].shape[1]):
902
+ splx = boundaries[i][:,j]
903
+ for k in range(filtrations[i][j]):
904
+ st.insert(splx, np.asarray(filtrations[i][j][k], dtype = s.dtype))
905
+ else:
906
+ st.insert_batch(np.asarray(boundaries[dim], dtype= np.int32),np.asarray(filtrations[dim], dtype=s.dtype))
907
+ return st
908
+
909
+
910
+ def _is_slicer(object input)->bool:
911
+ """
912
+ Checks if the input is a slicer. Equivalent (but faster) to `isinstance(input, multipers.slicer.Slicer_type)`.
913
+ """
914
+ return (False
915
+ {{for D in slicers}}
916
+ or isinstance(input, {{D['PYTHON_TYPE']}})
917
+ {{endfor}}
918
+ )
919
+ def is_slicer(input, bool allow_minpres=True)->bool:
920
+ if _is_slicer(input):
921
+ return True
922
+ if allow_minpres and isinstance(input, list) or isinstance(input, tuple):
923
+ if len(input)>0 and all((_is_slicer(s) and s.is_minpres for s in input)):
924
+ return True
925
+ return False
926
+
927
+
928
+ def to_blocks(input):
929
+ """
930
+ Converts input to blocks, if possible.
931
+ """
932
+ if is_slicer(input):
933
+ return slicer2blocks(input)
934
+ if isinstance(input, list) or isinstance(input, tuple):
935
+ return input
936
+ from multipers.simplex_tree_multi import is_simplextree_multi
937
+ if is_simplextree_multi(input):
938
+ return input._to_scc()
939
+ if isinstance(input, str) or isinstance(input, os.PathLike):
940
+ from multipers.io import scc_parser
941
+ return scc_parser(input)
942
+ raise ValueError("Input cannot be converted to blocks.")
943
+
944
+
945
+ def get_matrix_slicer(bool is_vineyard, bool is_k_critical, dtype:type|_valid_dtypes, str col, str pers_backend):
946
+ """
947
+ Given various parameters, returns the specific slicer type associated with them.
948
+ """
949
+ if False:
950
+ pass
951
+ {{for D in slicers}}
952
+ {{if not D['IS_SIMPLICIAL']}}
953
+ elif is_vineyard == {{D['IS_VINE']}} and is_k_critical == {{D['IS_KCRITICAL']}} and np.dtype(dtype) is np.dtype({{D['PY_VALUE_TYPE']}}) and col.lower() == "{{D['COLUMN_TYPE']}}".lower() and "{{D['PERS_BACKEND_TYPE']}}".lower() == pers_backend.lower():
954
+ return {{D['PYTHON_TYPE']}}
955
+ {{endif}}
956
+ {{endfor}}
957
+ else:
958
+ raise ValueError(f"Unimplemented combo for {pers_backend} : {is_vineyard=}, {is_k_critical=}, {dtype=}, {col=}")
959
+
960
+
961
+
962
+
963
+ def _hilbert_signed_measure(slicer,
964
+ vector[indices_type] degrees,
965
+ bool zero_pad=False,
966
+ indices_type n_jobs=0,
967
+ bool verbose=False,
968
+ # bool expand_collapse=False,
969
+ # grid_conversion = None,
970
+ bool ignore_inf = True,
971
+ ):
972
+ """
973
+ Computes the signed measures given by the decomposition of the hilbert function.
974
+
975
+ Input
976
+ -----
977
+
978
+ - simplextree:SimplexTreeMulti, the multifiltered simplicial complex
979
+ - degrees:array-like of ints, the degrees to compute
980
+ - n_jobs:int, number of jobs. Defaults to #cpu, but when doing parallel computations of signed measures, we recommend setting this to 1.
981
+ - verbose:bool, prints c++ logs.
982
+
983
+ Output
984
+ ------
985
+
986
+ `[signed_measure_of_degree for degree in degrees]`
987
+ with `signed_measure_of_degree` of the form `(dirac location, dirac weights)`.
988
+ """
989
+
990
+ assert slicer.is_squeezed, "Squeeze grid first."
991
+ if slicer.is_squeezed:
992
+ grid_shape = np.array([len(f) for f in slicer.filtration_grid])
993
+ else:
994
+ grid_shape = (slicer.filtration_bounds()[1]).astype(python_indices_type)+1
995
+ if zero_pad:
996
+ for i, _ in enumerate(grid_shape):
997
+ grid_shape[i] += 1 # adds a 0
998
+ assert len(grid_shape) == slicer.num_parameters, "Grid shape size has to be the number of parameters."
999
+ grid_shape_with_degree = np.asarray(np.concatenate([[len(degrees)], grid_shape]), dtype=python_indices_type)
1000
+ container_array = np.ascontiguousarray(np.zeros(grid_shape_with_degree, dtype=python_tensor_dtype).flatten())
1001
+ 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[])"
1002
+ cdef vector[indices_type] c_grid_shape = grid_shape_with_degree
1003
+ cdef tensor_dtype[::1] container = container_array
1004
+ cdef tensor_dtype* container_ptr = &container[0]
1005
+ cdef signed_measure_type out = _compute_hilbert_sm(slicer, container_ptr, c_grid_shape, degrees,n_jobs, verbose, zero_pad, ignore_inf)
1006
+ pts, weights = np.asarray(out.first, dtype=python_indices_type).reshape(-1, slicer.num_parameters+1), np.asarray(out.second, dtype=python_tensor_dtype)
1007
+ slices = np.concatenate([np.searchsorted(pts[:,0], np.arange(degrees.size())), [pts.shape[0]] ])
1008
+ sms = [
1009
+ (pts[slices[i]:slices[i+1],1:],weights[slices[i]:slices[i+1]])
1010
+ for i in range(slices.shape[0]-1)
1011
+ ]
1012
+ return sms
1013
+
1014
+
1015
+ ## Rank invariant
1016
+
1017
+
1018
+ ## TODO : It is not necessary to do the Möbius inversion in python.
1019
+ ## fill rank in flipped death, then differentiate in cpp, then reflip with numpy.
1020
+ def _rank_from_slicer(
1021
+ slicer,
1022
+ vector[indices_type] degrees,
1023
+ bool verbose=False,
1024
+ indices_type n_jobs=1,
1025
+ bool zero_pad = False,
1026
+ grid_shape=None,
1027
+ bool plot=False,
1028
+ bool return_raw=False,
1029
+ bool ignore_inf = True,
1030
+ ):
1031
+ # cdef intptr_t slicer_ptr = <intptr_t>(slicer.get_ptr())
1032
+ if grid_shape is None:
1033
+ if slicer.is_squeezed:
1034
+ grid_shape = np.array([len(f) for f in slicer.filtration_grid])
1035
+ else:
1036
+ grid_shape = (slicer.filtration_bounds()[1]).astype(python_indices_type)+1
1037
+ grid_shape = np.asarray(grid_shape)
1038
+
1039
+ cdef int num_parameters = len(grid_shape)
1040
+
1041
+ # if zero_pad:
1042
+ # grid_shape += 1
1043
+ # for i, _ in enumerate(grid_shape):
1044
+ # grid_shape[i] += 1 # adds a 0
1045
+ # for i,f in enumerate(grid_conversion):
1046
+ # grid_conversion[i] = np.concatenate([f, [mass_default[i]]])
1047
+
1048
+
1049
+ grid_shape_with_degree = np.asarray(np.concatenate([[len(degrees)], grid_shape, grid_shape]), dtype=python_indices_type)
1050
+ if verbose:
1051
+ print("Container shape: ", grid_shape_with_degree)
1052
+ container_array = np.ascontiguousarray(np.zeros(grid_shape_with_degree, dtype=python_tensor_dtype).ravel())
1053
+ 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[])"
1054
+ # if zero_pad:
1055
+ # grid_shape_with_degree[1:] -= 1
1056
+ cdef vector[indices_type] c_grid_shape = grid_shape_with_degree
1057
+ cdef tensor_dtype[::1] container = container_array
1058
+ cdef tensor_dtype* container_ptr = &container[0]
1059
+
1060
+ ## SLICERS
1061
+ if verbose:
1062
+ print("Computing rank invariant...", end="")
1063
+ _compute_rank_invariant(slicer, container_ptr, c_grid_shape, degrees, n_jobs, ignore_inf)
1064
+ if verbose:
1065
+ print("Done.")
1066
+
1067
+ if verbose:
1068
+ print("Computing Möbius inversion...", end="")
1069
+ # if zero_pad:
1070
+ # grid_shape_with_degree[1:] += 1
1071
+ rank = container_array.reshape(grid_shape_with_degree)
1072
+ rank = tuple(rank_decomposition_by_rectangles(rank_of_degree, threshold=zero_pad) for rank_of_degree in rank)
1073
+ if return_raw:
1074
+ return rank
1075
+ out = []
1076
+ def clean_rank(rank_decomposition):
1077
+ (coords, weights) = sparsify(np.ascontiguousarray(rank_decomposition))
1078
+ births = coords[:,:num_parameters]
1079
+ deaths = coords[:,num_parameters:]
1080
+ correct_indices = np.all(births<=deaths, axis=1)
1081
+ coords = coords[correct_indices]
1082
+ weights = weights[correct_indices]
1083
+ return coords, weights
1084
+
1085
+ out = tuple(clean_rank(rank_decomposition) for rank_decomposition in rank)
1086
+ if verbose:
1087
+ print("Done.")
1088
+ return out