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