multipers 2.2.3__cp312-cp312-win_amd64.whl → 2.3.0__cp312-cp312-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/filtrations/filtrations.py +289 -0
  15. multipers/filtrations.pxd +224 -224
  16. multipers/function_rips.cp312-win_amd64.pyd +0 -0
  17. multipers/function_rips.pyx +105 -105
  18. multipers/grids.cp312-win_amd64.pyd +0 -0
  19. multipers/grids.pyx +350 -350
  20. multipers/gudhi/Persistence_slices_interface.h +132 -132
  21. multipers/gudhi/Simplex_tree_interface.h +239 -245
  22. multipers/gudhi/Simplex_tree_multi_interface.h +516 -561
  23. multipers/gudhi/cubical_to_boundary.h +59 -59
  24. multipers/gudhi/gudhi/Bitmap_cubical_complex.h +450 -450
  25. multipers/gudhi/gudhi/Bitmap_cubical_complex_base.h +1070 -1070
  26. multipers/gudhi/gudhi/Bitmap_cubical_complex_periodic_boundary_conditions_base.h +579 -579
  27. multipers/gudhi/gudhi/Debug_utils.h +45 -45
  28. multipers/gudhi/gudhi/Fields/Multi_field.h +484 -484
  29. multipers/gudhi/gudhi/Fields/Multi_field_operators.h +455 -455
  30. multipers/gudhi/gudhi/Fields/Multi_field_shared.h +450 -450
  31. multipers/gudhi/gudhi/Fields/Multi_field_small.h +531 -531
  32. multipers/gudhi/gudhi/Fields/Multi_field_small_operators.h +507 -507
  33. multipers/gudhi/gudhi/Fields/Multi_field_small_shared.h +531 -531
  34. multipers/gudhi/gudhi/Fields/Z2_field.h +355 -355
  35. multipers/gudhi/gudhi/Fields/Z2_field_operators.h +376 -376
  36. multipers/gudhi/gudhi/Fields/Zp_field.h +420 -420
  37. multipers/gudhi/gudhi/Fields/Zp_field_operators.h +400 -400
  38. multipers/gudhi/gudhi/Fields/Zp_field_shared.h +418 -418
  39. multipers/gudhi/gudhi/Flag_complex_edge_collapser.h +337 -337
  40. multipers/gudhi/gudhi/Matrix.h +2107 -2107
  41. multipers/gudhi/gudhi/Multi_critical_filtration.h +1038 -1038
  42. multipers/gudhi/gudhi/Multi_persistence/Box.h +171 -171
  43. multipers/gudhi/gudhi/Multi_persistence/Line.h +282 -282
  44. multipers/gudhi/gudhi/Off_reader.h +173 -173
  45. multipers/gudhi/gudhi/One_critical_filtration.h +1432 -1431
  46. multipers/gudhi/gudhi/Persistence_matrix/Base_matrix.h +769 -769
  47. multipers/gudhi/gudhi/Persistence_matrix/Base_matrix_with_column_compression.h +686 -686
  48. multipers/gudhi/gudhi/Persistence_matrix/Boundary_matrix.h +842 -842
  49. multipers/gudhi/gudhi/Persistence_matrix/Chain_matrix.h +1350 -1350
  50. multipers/gudhi/gudhi/Persistence_matrix/Id_to_index_overlay.h +1105 -1105
  51. multipers/gudhi/gudhi/Persistence_matrix/Position_to_index_overlay.h +859 -859
  52. multipers/gudhi/gudhi/Persistence_matrix/RU_matrix.h +910 -910
  53. multipers/gudhi/gudhi/Persistence_matrix/allocators/entry_constructors.h +139 -139
  54. multipers/gudhi/gudhi/Persistence_matrix/base_pairing.h +230 -230
  55. multipers/gudhi/gudhi/Persistence_matrix/base_swap.h +211 -211
  56. multipers/gudhi/gudhi/Persistence_matrix/boundary_cell_position_to_id_mapper.h +60 -60
  57. multipers/gudhi/gudhi/Persistence_matrix/boundary_face_position_to_id_mapper.h +60 -60
  58. multipers/gudhi/gudhi/Persistence_matrix/chain_pairing.h +136 -136
  59. multipers/gudhi/gudhi/Persistence_matrix/chain_rep_cycles.h +190 -190
  60. multipers/gudhi/gudhi/Persistence_matrix/chain_vine_swap.h +616 -616
  61. multipers/gudhi/gudhi/Persistence_matrix/columns/chain_column_extra_properties.h +150 -150
  62. multipers/gudhi/gudhi/Persistence_matrix/columns/column_dimension_holder.h +106 -106
  63. multipers/gudhi/gudhi/Persistence_matrix/columns/column_utilities.h +219 -219
  64. multipers/gudhi/gudhi/Persistence_matrix/columns/entry_types.h +327 -327
  65. multipers/gudhi/gudhi/Persistence_matrix/columns/heap_column.h +1140 -1140
  66. multipers/gudhi/gudhi/Persistence_matrix/columns/intrusive_list_column.h +934 -934
  67. multipers/gudhi/gudhi/Persistence_matrix/columns/intrusive_set_column.h +934 -934
  68. multipers/gudhi/gudhi/Persistence_matrix/columns/list_column.h +980 -980
  69. multipers/gudhi/gudhi/Persistence_matrix/columns/naive_vector_column.h +1092 -1092
  70. multipers/gudhi/gudhi/Persistence_matrix/columns/row_access.h +192 -192
  71. multipers/gudhi/gudhi/Persistence_matrix/columns/set_column.h +921 -921
  72. multipers/gudhi/gudhi/Persistence_matrix/columns/small_vector_column.h +1093 -1093
  73. multipers/gudhi/gudhi/Persistence_matrix/columns/unordered_set_column.h +1012 -1012
  74. multipers/gudhi/gudhi/Persistence_matrix/columns/vector_column.h +1244 -1244
  75. multipers/gudhi/gudhi/Persistence_matrix/matrix_dimension_holders.h +186 -186
  76. multipers/gudhi/gudhi/Persistence_matrix/matrix_row_access.h +164 -164
  77. multipers/gudhi/gudhi/Persistence_matrix/ru_pairing.h +156 -156
  78. multipers/gudhi/gudhi/Persistence_matrix/ru_rep_cycles.h +376 -376
  79. multipers/gudhi/gudhi/Persistence_matrix/ru_vine_swap.h +540 -540
  80. multipers/gudhi/gudhi/Persistent_cohomology/Field_Zp.h +118 -118
  81. multipers/gudhi/gudhi/Persistent_cohomology/Multi_field.h +173 -173
  82. multipers/gudhi/gudhi/Persistent_cohomology/Persistent_cohomology_column.h +128 -128
  83. multipers/gudhi/gudhi/Persistent_cohomology.h +745 -745
  84. multipers/gudhi/gudhi/Points_off_io.h +171 -171
  85. multipers/gudhi/gudhi/Simple_object_pool.h +69 -69
  86. multipers/gudhi/gudhi/Simplex_tree/Simplex_tree_iterators.h +463 -463
  87. multipers/gudhi/gudhi/Simplex_tree/Simplex_tree_node_explicit_storage.h +83 -83
  88. multipers/gudhi/gudhi/Simplex_tree/Simplex_tree_siblings.h +106 -106
  89. multipers/gudhi/gudhi/Simplex_tree/Simplex_tree_star_simplex_iterators.h +277 -277
  90. multipers/gudhi/gudhi/Simplex_tree/hooks_simplex_base.h +62 -62
  91. multipers/gudhi/gudhi/Simplex_tree/indexing_tag.h +27 -27
  92. multipers/gudhi/gudhi/Simplex_tree/serialization_utils.h +62 -62
  93. multipers/gudhi/gudhi/Simplex_tree/simplex_tree_options.h +157 -157
  94. multipers/gudhi/gudhi/Simplex_tree.h +2794 -2794
  95. multipers/gudhi/gudhi/Simplex_tree_multi.h +152 -163
  96. multipers/gudhi/gudhi/distance_functions.h +62 -62
  97. multipers/gudhi/gudhi/graph_simplicial_complex.h +104 -104
  98. multipers/gudhi/gudhi/persistence_interval.h +253 -253
  99. multipers/gudhi/gudhi/persistence_matrix_options.h +170 -170
  100. multipers/gudhi/gudhi/reader_utils.h +367 -367
  101. multipers/gudhi/mma_interface_coh.h +256 -255
  102. multipers/gudhi/mma_interface_h0.h +223 -231
  103. multipers/gudhi/mma_interface_matrix.h +284 -282
  104. multipers/gudhi/naive_merge_tree.h +536 -575
  105. multipers/gudhi/scc_io.h +310 -289
  106. multipers/gudhi/truc.h +890 -888
  107. multipers/io.cp312-win_amd64.pyd +0 -0
  108. multipers/io.pyx +711 -711
  109. multipers/ml/accuracies.py +90 -90
  110. multipers/ml/convolutions.py +520 -520
  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.cp312-win_amd64.pyd +0 -0
  120. multipers/mma_structures.pxd +127 -127
  121. multipers/mma_structures.pyx +4 -4
  122. multipers/mma_structures.pyx.tp +1085 -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 +2296 -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.cp312-win_amd64.pyd +0 -0
  148. multipers/multiparameter_module_approximation.pyx +216 -217
  149. multipers/pickle.py +90 -53
  150. multipers/plots.py +342 -334
  151. multipers/point_measure.cp312-win_amd64.pyd +0 -0
  152. multipers/point_measure.pyx +322 -320
  153. multipers/simplex_tree_multi.cp312-win_amd64.pyd +0 -0
  154. multipers/simplex_tree_multi.pxd +133 -133
  155. multipers/simplex_tree_multi.pyx +18 -15
  156. multipers/simplex_tree_multi.pyx.tp +1939 -1935
  157. multipers/slicer.cp312-win_amd64.pyd +0 -0
  158. multipers/slicer.pxd +81 -20
  159. multipers/slicer.pxd.tp +215 -214
  160. multipers/slicer.pyx +1091 -308
  161. multipers/slicer.pyx.tp +924 -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.0.dist-info}/LICENSE +21 -21
  169. {multipers-2.2.3.dist-info → multipers-2.3.0.dist-info}/METADATA +21 -11
  170. multipers-2.3.0.dist-info/RECORD +182 -0
  171. multipers/tests/test_diff_helper.py +0 -73
  172. multipers/tests/test_hilbert_function.py +0 -82
  173. multipers/tests/test_mma.py +0 -83
  174. multipers/tests/test_point_clouds.py +0 -49
  175. multipers/tests/test_python-cpp_conversion.py +0 -82
  176. multipers/tests/test_signed_betti.py +0 -181
  177. multipers/tests/test_signed_measure.py +0 -89
  178. multipers/tests/test_simplextreemulti.py +0 -221
  179. multipers/tests/test_slicer.py +0 -221
  180. multipers-2.2.3.dist-info/RECORD +0 -189
  181. {multipers-2.2.3.dist-info → multipers-2.3.0.dist-info}/WHEEL +0 -0
  182. {multipers-2.2.3.dist-info → multipers-2.3.0.dist-info}/top_level.txt +0 -0
@@ -1,1085 +1,1085 @@
1
- {{py:
2
-
3
- """
4
- MMA core structures.
5
- """
6
-
7
- ## Value types : CTYPE, PYTHON_TYPE, short
8
- # value_types = [
9
- # ("int32_t", "np.int32", "i32"),
10
- # ("int64_t", "np.int64", "i64"),
11
- # ("float", "np.float32", "f32"),
12
- # ("double", "np.float64", "f64"),
13
- # ]
14
-
15
- import pickle as pkl
16
- import numpy as np
17
-
18
- D = pkl.load(open("build/tmp/_slicer_names.pkl", "rb"))
19
- value_types = np.unique([tuple((x['C_VALUE_TYPE'],x['PY_VALUE_TYPE'], x['SHORT_VALUE_TYPE'])) for x in D], axis=0)
20
-
21
- }}
22
-
23
-
24
-
25
-
26
-
27
- """!
28
- @package mma
29
- @brief Files containing the C++ cythonized functions.
30
- @author David Loiseaux
31
- @copyright Copyright (c) 2022 Inria.
32
- """
33
-
34
- # distutils: language = c++
35
-
36
- ###########################################################################
37
- ## PYTHON LIBRARIES
38
- import gudhi as gd
39
- import numpy as np
40
- from typing import Union, Literal
41
- from collections.abc import Callable, Iterable, Sequence
42
- import pickle
43
- import multipers.grids as mpg
44
-
45
- ###########################################################################
46
- ## CPP CLASSES
47
- from libc.stdint cimport intptr_t
48
- from libc.stdint cimport uintptr_t
49
-
50
- ###########################################################################
51
- ## CYTHON TYPES
52
- from libcpp.vector cimport vector
53
- from libcpp.utility cimport pair
54
- #from libcpp.list cimport list as clist
55
- from libcpp cimport bool
56
- from libcpp cimport int
57
- from cython.operator cimport dereference
58
- from libcpp.utility cimport move
59
- cimport cython
60
- #########################################################################
61
- ## Multipersistence Module Approximation Classes
62
- from multipers.mma_structures cimport *
63
- from multipers.filtration_conversions cimport *
64
- cimport numpy as cnp
65
-
66
-
67
- #########################################################################
68
- ## Small hack for typing
69
- from gudhi import SimplexTree
70
- from multipers.simplex_tree_multi import SimplexTreeMulti
71
- from joblib import Parallel, delayed
72
-
73
- available_pymodules = [
74
- {{for CTYPE, PYTYPE, SHORT in value_types}}
75
- PyModule_{{SHORT}},
76
- {{endfor}}
77
- ]
78
-
79
- PyModule_type = Union[
80
- {{for CTYPE, PYTYPE, SHORT in value_types}}
81
- PyModule_{{SHORT}},
82
- {{endfor}}
83
- ]
84
-
85
- {{for CTYPE, PYTYPE, SHORT in value_types}}
86
- cdef class PySummand_{{SHORT}}:
87
- r"""
88
- Stores a Summand of a PyModule
89
- """
90
- cdef Summand[{{CTYPE}}] sum
91
-
92
- def get_birth_list(self):
93
- cdef vector[One_critical_filtration[{{CTYPE}}]] v = self.sum.get_birth_list()
94
- return _vff21cview_{{SHORT}}(v, copy = True, duplicate = self.num_parameters())
95
-
96
- def get_death_list(self):
97
- cdef vector[One_critical_filtration[{{CTYPE}}]] v = self.sum.get_death_list()
98
- return _vff21cview_{{SHORT}}(v, copy = True, duplicate = self.num_parameters())
99
- @property
100
- def degree(self)->int:
101
- return self.sum.get_dimension()
102
-
103
- cdef set(self, Summand[{{CTYPE}}]& summand):
104
- self.sum = summand
105
- return self
106
- def get_bounds(self):
107
- cdef pair[One_critical_filtration[{{CTYPE}}],One_critical_filtration[{{CTYPE}}]] cbounds
108
- with nogil:
109
- cbounds = self.sum.get_bounds().get_bounding_corners()
110
- return _ff21cview_{{SHORT}}(&cbounds.first).copy(), _ff21cview_{{SHORT}}(&cbounds.second).copy()
111
- @property
112
- def dtype(self):
113
- return {{PYTYPE}}
114
-
115
- def num_parameters(self):
116
- cdef vector[One_critical_filtration[{{CTYPE}}]] v = self.sum.get_birth_list()
117
- if v[0].is_finite():
118
- return v[0].num_parameters()
119
- v = self.sum.get_death_list()
120
- return v[0].num_parameters()
121
-
122
- cdef inline get_summand_filtration_values_{{SHORT}}(Summand[{{CTYPE}}] summand):
123
- r"""
124
- Returns a list (over parameter) of the filtrations values of this parameter.
125
- """
126
- cdef vector[One_critical_filtration[{{CTYPE}}]] vb = summand.get_birth_list()
127
- cdef vector[One_critical_filtration[{{CTYPE}}]] vd = summand.get_death_list()
128
-
129
- if vb[0].is_finite():
130
- if vd[0].is_finite():
131
- pts = np.concatenate([_vff21cview_{{SHORT}}(vb, copy=True),
132
- _vff21cview_{{SHORT}}(vd, copy=True)],axis=0)
133
- else:
134
- pts = np.array(_vff21cview_{{SHORT}}(vb, copy=True))
135
- else:
136
- if vd[0].is_finite():
137
- pts = np.array(_vff21cview_{{SHORT}}(vd, copy=True))
138
- else:
139
- return []
140
-
141
- num_parameters = pts.shape[1]
142
- out = [np.unique(pts[:,parameter]) for parameter in range(num_parameters)]
143
- out = [f[:-1] if f[-1] == np.inf else f for f in out]
144
- out = [f[1:] if f[0] == -np.inf else f for f in out]
145
- return out
146
-
147
- cdef class PyBox_{{SHORT}}:
148
- cdef Box[{{CTYPE}}] box
149
- def __cinit__(self, vector[{{CTYPE}}]& bottomCorner, vector[{{CTYPE}}]& topCorner):
150
- self.box = Box[{{CTYPE}}](bottomCorner, topCorner)
151
- @property
152
- def num_parameters(self):
153
- cdef size_t dim = self.box.get_lower_corner().num_parameters()
154
- if dim == self.box.get_upper_corner().num_parameters(): return dim
155
- else: print("Bad box definition.")
156
- def contains(self, x):
157
- return self.box.contains(x)
158
- cdef set(self, Box[{{CTYPE}}]& b):
159
- self.box = b
160
- return self
161
-
162
- def get(self):
163
- return [<vector[{{CTYPE}}]>self.box.get_lower_corner(), <vector[{{CTYPE}}]>self.box.get_upper_corner()]
164
- def to_multipers(self):
165
- #assert (self.get_dimension() == 2) "Multipers only works in dimension 2 !"
166
- return np.array(self.get()).flatten(order = 'F')
167
- @property
168
- def dtype(self):
169
- return {{PYTYPE}}
170
-
171
-
172
-
173
- cdef class PyModule_{{SHORT}}:
174
- r"""
175
- Stores a representation of a n-persistence module.
176
- """
177
- cdef Module[{{CTYPE}}] cmod
178
-
179
- @property
180
- def dtype(self):
181
- return {{PYTYPE}}
182
-
183
- cdef set(self, Module[{{CTYPE}}] m):
184
- self.cmod = m
185
- def merge(self, PyModule_{{SHORT}} other, int dim=-1):
186
- r"""
187
- Merges two modules into one
188
- """
189
- cdef Module[{{CTYPE}}] c_other = other.cmod
190
- with nogil:
191
- for summand in c_other:
192
- self.cmod.add_summand(summand, dim)
193
- return self
194
-
195
- def _set_from_ptr(self, intptr_t module_ptr):
196
- r"""
197
- Copy module from a memory pointer. Unsafe.
198
- """
199
- self.cmod = move(dereference(<Module[{{CTYPE}}]*>(module_ptr)))
200
- def set_box(self, PyBox_{{SHORT}} pybox):
201
- cdef Box[{{CTYPE}}] cbox = pybox.box
202
- with nogil:
203
- self.cmod.set_box(cbox)
204
- return self
205
- def get_module_of_degree(self, int degree)->PyModule_{{SHORT}}: # TODO : in c++ ?
206
- r"""
207
- Returns a copy of a module of fixed degree.
208
- """
209
- pmodule = PyModule_{{SHORT}}()
210
- cdef Box[{{CTYPE}}] c_box = self.cmod.get_box()
211
- pmodule.cmod.set_box(c_box)
212
- with nogil:
213
- for summand in self.cmod:
214
- if summand.get_dimension() == degree:
215
- pmodule.cmod.add_summand(summand)
216
- return pmodule
217
- def get_module_of_degrees(self, degrees:Iterable[int])->PyModule_{{SHORT}}: # TODO : in c++ ?
218
- r"""
219
- Returns a copy of the summands of degrees in `degrees`
220
- """
221
- pmodule = PyModule_{{SHORT}}()
222
- cdef Box[{{CTYPE}}] c_box = self.cmod.get_box()
223
- pmodule.cmod.set_box(c_box)
224
- cdef vector[int] cdegrees = degrees
225
- with nogil:
226
- for summand in self.cmod:
227
- for d in cdegrees:
228
- if d == summand.get_dimension():
229
- pmodule.cmod.add_summand(summand)
230
- return pmodule
231
- def __len__(self)->int:
232
- return self.cmod.size()
233
- def get_bottom(self)->np.ndarray:
234
- r"""
235
- Bottom of the box of the module
236
- """
237
- return np.asarray(<vector[{{CTYPE}}]>(self.cmod.get_box().get_lower_corner()))
238
- def get_top(self)->np.ndarray:
239
- r"""
240
- Top of the box of the module
241
- """
242
- return np.asarray(<vector[{{CTYPE}}]>(self.cmod.get_box().get_upper_corner()))
243
- def get_box(self)->np.ndarray:
244
- r"""
245
- Returns the current bounding box of the module.
246
- """
247
- return np.asarray([self.get_bottom(), self.get_top()])
248
- @property
249
- def max_degree(self)->int:
250
- r"""
251
- Returns the maximum degree of the module.
252
- """
253
- return self.cmod.get_dimension()
254
- @property
255
- def num_parameters(self)->int:
256
- cdef size_t dim = self.cmod.get_box().get_lower_corner().num_parameters()
257
- assert dim == self.cmod.get_box().get_upper_corner().num_parameters(), "Bad box definition, cannot infer num_parameters."
258
- return dim
259
- def dump(self, path:str|None=None):
260
- r"""
261
- Dumps the module into a pickle-able format.
262
-
263
- Parameters
264
- ----------
265
-
266
- path:str=None (optional) saves the pickled module in specified path
267
-
268
- Returns
269
- -------
270
-
271
- list of list, encoding the module, which can be retrieved with the function `from_dump`.
272
- """
273
- ## TODO : optimize, but not really used.
274
- return dump_cmod_{{SHORT}}(self.cmod)
275
- def __getstate__(self):
276
- return self.dump()
277
- def __setstate__(self,dump):
278
- cdef Module[{{CTYPE}}] cmod = cmod_from_dump_{{SHORT}}(dump)
279
- self.cmod = cmod
280
- return
281
- def __getitem__(self, int i) -> PySummand_{{SHORT}}:
282
- if i == slice(None):
283
- return self
284
- summand = PySummand_{{SHORT}}()
285
- summand.set(self.cmod.at(i % self.cmod.size()))
286
- return summand
287
- def __iter__(self):
288
- cdef int num_summands = self.cmod.size()
289
- for i in range(num_summands):
290
- summand = PySummand_{{SHORT}}()
291
- summand.set(self.cmod.at(i)) ## hmm copy. maybe do summand from ptr
292
- yield summand
293
-
294
- def get_bounds(self):
295
- r"""
296
- Computes bounds from the summands' bounds.
297
- Useful to change this' box.
298
- """
299
- cdef pair[One_critical_filtration[{{CTYPE}}],One_critical_filtration[{{CTYPE}}]] cbounds
300
- with nogil:
301
- cbounds = self.cmod.get_bounds().get_bounding_corners()
302
- return _ff21cview_{{SHORT}}(&cbounds.first).copy(), _ff21cview_{{SHORT}}(&cbounds.second).copy()
303
- def rescale(self,rescale_factors, int degree=-1):
304
- r"""
305
- Rescales the fitlration values of the summands by this rescaling vector.
306
- """
307
- cdef vector[{{CTYPE}}] crescale_factors = rescale_factors
308
- with nogil:
309
- self.cmod.rescale(crescale_factors,degree)
310
- def translate(self,translation, int degree=-1):
311
- r"""
312
- Translates the module in the filtration space by this vector.
313
- """
314
- cdef vector[{{CTYPE}}] ctranslation = translation
315
- with nogil:
316
- self.cmod.translate(ctranslation,degree)
317
-
318
- def get_filtration_values(self, bool unique=True):
319
- r"""
320
- Retrieves all filtration values of the summands of the module.
321
-
322
- Output format
323
- -------------
324
-
325
- list of filtration values for parameter.
326
- """
327
- if len(self) ==0:
328
- return np.empty((self.num_parameters,0))
329
- values = tuple(tuple(stuff) if len(stuff:=get_summand_filtration_values_{{SHORT}}(summand)) == self.num_parameters else list(stuff) + [[]]*(self.num_parameters - len(stuff)) for summand in self.cmod)
330
- try:
331
- values = tuple(np.concatenate([
332
- f[parameter]
333
- for f in values
334
- ], axis=0) for parameter in range(self.num_parameters)
335
- )
336
- except:
337
- return values
338
- if unique:
339
- return [np.unique(f) for f in values]
340
- return values
341
-
342
- def plot(self, int degree=-1,**kwargs)->None:
343
- r"""Shows the module on a plot. Each color corresponds to an apprimation summand of the module, and its shape corresponds to its support.
344
- Only works with 2-parameter modules.
345
-
346
- Parameters
347
- ----------
348
- degree = -1 : integer
349
- If positive returns only the image of dimension `dimension`.
350
- box=None : of the form [[b_x,b_y], [d_x,d_y]] where b,d are the bottom and top corner of the rectangle.
351
- If non-None, will plot the module on this specific rectangle.
352
- min_persistence =0 : float
353
- Only plots the summand with a persistence above this threshold.
354
- separated=False : bool
355
- If true, plot each summand in a different plot.
356
- alpha=1 : float
357
- Transparancy parameter
358
- save = False : string
359
- if nontrivial, will save the figure at this path
360
-
361
-
362
- Returns
363
- -------
364
- The figure of the plot.
365
- """
366
- from multipers.plots import plot2d_PyModule
367
- import matplotlib.pyplot as plt
368
- box = kwargs.pop('box', self.get_box())
369
- if (len(box[0]) != 2):
370
- print("Filtration size :", len(box[0]), " != 2")
371
- return
372
- if(degree < 0):
373
- dims = np.unique(self.get_dimensions())
374
- separated = kwargs.pop("separated", False)
375
- ndim = len(dims)
376
- scale = kwargs.pop("scale", 4)
377
- if separated:
378
- fig = None
379
- axes = None
380
- elif ndim > 1:
381
- fig, axes = plt.subplots(1,ndim, figsize=(ndim*scale,scale))
382
- else:
383
- fig = plt.gcf()
384
- axes = [plt.gca()]
385
- for dim_idx in range(ndim):
386
- if not separated:
387
- plt.sca(axes[dim_idx]) if ndim > 1 else plt.sca(axes)
388
- self.plot(dims[dim_idx],box=box, separated = separated, **kwargs)
389
- return
390
- corners = self.cmod.get_corners_of_dimension(degree)
391
- plot2d_PyModule(corners, box=box, dimension=degree, **kwargs)
392
- return
393
- def degree_splits(self):
394
- return np.asarray(self.cmod.get_degree_splits(), dtype=np.int64)
395
- {{if SHORT[0] == 'f'}}
396
- def _compute_pixels(self,coordinates:np.ndarray,
397
- degrees=None, box=None, {{CTYPE}} delta=.1,
398
- {{CTYPE}} p=1., bool normalize=False, int n_jobs=0):
399
- r"""
400
- Computes the image of the module at the given coordinates
401
- """
402
- if degrees is not None: assert np.all(degrees[:-1] <= degrees[1:]), "Degrees have to be sorted"
403
- cdef vector[int] cdegrees = np.arange(self.max_degree +1) if degrees is None else degrees
404
- pybox = PyBox_{{SHORT}}(*self.get_box()) if box is None else PyBox_{{SHORT}}(*box)
405
- cdef Box[{{CTYPE}}] cbox = pybox.box
406
- cdef vector[vector[{{CTYPE}}]] ccoords = coordinates
407
- cdef vector[vector[{{CTYPE}}]] out
408
- with nogil:
409
- out = self.cmod.compute_pixels(ccoords, cdegrees, cbox, delta, p, normalize, n_jobs)
410
- return np.asarray(out)
411
- def barcode(self, basepoint, int degree = -1,*, bool threshold = False): # TODO direction vector interface
412
- r"""Computes the barcode of module along a lines.
413
-
414
- Parameters
415
- ----------
416
-
417
- basepoint : vector
418
- basepoint of the lines on which to compute the barcodes, i.e. a point on the line
419
- degree = -1 : integer
420
- Homology degree on which to compute the bars. If negative, every dimension is computed
421
- box (default) :
422
- box on which to compute the barcodes if basepoints is not specified. Default is a linspace of lines crossing that box.
423
- threshold = False :
424
- Thre
425
-
426
- Warning
427
- -------
428
-
429
- If the barcodes are not thresholded, essential barcodes will not be plot-able.
430
-
431
- Returns
432
- -------
433
-
434
- PyMultiDiagrams
435
- Structure that holds the barcodes. Barcodes can be retrieved with a .get_points() or a .to_multipers() or a .plot().
436
- """
437
- out = PyMultiDiagram_{{SHORT}}()
438
- out.set(self.cmod.get_barcode(Line[{{CTYPE}}](_py21c_{{SHORT}}(np.asarray(basepoint, dtype={{PYTYPE}}))), degree, threshold))
439
- return out
440
- @staticmethod
441
- cdef _threshold_bc(bc):
442
- return tuple(np.fromiter((a for a in stuff if a[0] < np.inf), dtype=np.dtype(({{PYTYPE}},2)) ) for stuff in bc)
443
- @staticmethod
444
- def _bc_to_full(bcs, basepoint, direction=None):
445
- # i, (b sv d), coords
446
- basepoint = np.asarray(basepoint)[None,None,:]
447
- direction = 1 if direction is None else np.asarray(direction)[None,None,:]
448
- return tuple(bc[:,:,None]*direction + basepoint for bc in bcs)
449
- def barcode2(self, basepoint, direction=None, int degree = -1,*, bool threshold = False, bool keep_inf = True, bool full = False): # TODO direction vector interface
450
- r"""
451
- Compute the 1d-barcode a diagonal line based on basepoint, with some direction.
452
-
453
- Parameters
454
- ----------
455
-
456
- - basepoint: 1d array
457
- - directiont: 1d array or None, if None: diagonal
458
- - degree: int the degree to compute (-1 means all)
459
- - threshold: bool if True, threshold the barcode to the modules box
460
- - keep_inf: bool if False, removes trivial bars
461
- Note that this removes the order w.r.t. the summands in that case
462
- - full:bool if True, returns the coordinates of the barcode instead of the coordinate in the line.
463
-
464
- The output is of the form
465
-
466
- tuple[np.ndarray of shape (num_bars,2)] or tuple[array of shape (num_bar, 2, num_parameters)]
467
- """
468
- basepoint = np.asarray(basepoint, dtype={{PYTYPE}})
469
- if direction is None:
470
- bc = tuple(np.asarray(x).reshape(-1,2) for x in self.cmod.get_barcode2(Line[{{CTYPE}}](_py21c_{{SHORT}}(basepoint)), degree))
471
- else:
472
- direction = np.asarray(direction, dtype = {{PYTYPE}})
473
- bc = tuple(np.asarray(x).reshape(-1,2) for x in self.cmod.get_barcode2(Line[{{CTYPE}}](_py21c_{{SHORT}}(basepoint), _py21c_{{SHORT}}(direction)), degree))
474
- if not keep_inf:
475
- bc = PyModule_{{SHORT}}._threshold_bc(bc)
476
- if full:
477
- bc = PyModule_{{SHORT}}._bc_to_full(bc, basepoint, direction)
478
-
479
- return bc
480
-
481
- def get_dimensions(self):
482
- cdef int num_summands = len(self)
483
- out = np.empty(shape=num_summands, dtype=np.int32)
484
- cdef int32_t[:] c_out = out
485
- for i in range(num_summands):
486
- c_out[i] = self.cmod.at(i).get_dimension()
487
- return out
488
-
489
- def barcodes(self, int degree, basepoints = None, num=100, box = None,threshold = False):
490
- r"""Computes barcodes of module along a set of lines.
491
-
492
- Parameters
493
- ----------
494
-
495
- basepoints = None : list of vectors
496
- basepoints of the lines on which to compute the barcodes.
497
- degree = -1 : integer
498
- Homology degree on which to compute the bars. If negative, every dimension is computed
499
- box (default) :
500
- box on which to compute the barcodes if basepoints is not specified. Default is a linspace of lines crossing that box.
501
- num:int=100
502
- if basepoints is not specified, defines the number of lines to consider.
503
- threshold = False : threshold t
504
- Resolution of the image(s).
505
-
506
- Warning
507
- -------
508
-
509
- If the barcodes are not thresholded, essential barcodes will not be plot-able.
510
-
511
- Returns
512
- -------
513
-
514
- PyMultiDiagrams
515
- Structure that holds the barcodes. Barcodes can be retrieved with a .get_points() or a .to_multipers() or a .plot().
516
- """
517
- out = PyMultiDiagrams_{{SHORT}}()
518
- if box is None:
519
- box = [self.get_bottom(), self.get_top()]
520
- if (len(box[0]) != 2) and (basepoints is None):
521
- raise ValueError("Basepoints has to be specified for filtration dimension >= 3 !")
522
- elif basepoints is None:
523
- h = box[1][1] - box[0][1]
524
- basepoints = np.linspace([box[0][0] - h,box[0][1]], [box[1][0],box[0][1]], num=num)
525
- else :
526
- num=len(basepoints)
527
-
528
- cdef {{CTYPE}}[:,:] basepoints_view = np.asarray(basepoints, dtype = {{PYTYPE}})
529
- cdef vector[One_critical_filtration[{{CTYPE}}]] cbasepoints = _py2v1c_{{SHORT}}(basepoints_view)
530
-
531
- out.set(self.cmod.get_barcodes(cbasepoints, degree, threshold))
532
- return out
533
-
534
- def barcodes2(self, int degree = -1, basepoints = None, int num=100, box = None, bool threshold = False):
535
- r"""Computes barcodes of module along a set of lines.
536
-
537
- Parameters
538
- ----------
539
-
540
- basepoints = None : list of vectors
541
- basepoints of the lines on which to compute the barcodes.
542
- degree = -1 : integer
543
- Homology degree on which to compute the bars. If negative, every dimension is computed
544
- box (default) :
545
- box on which to compute the barcodes if basepoints is not specified. Default is a linspace of lines crossing that box.
546
- num:int=100
547
- if basepoints is not specified, defines the number of lines to consider.
548
- threshold = False : threshold t
549
- Resolution of the image(s).
550
-
551
- Warning
552
- -------
553
-
554
- If the barcodes are not thresholded, essential barcodes will not be plot-able.
555
-
556
- Returns
557
- -------
558
-
559
- tuple of 1d barcodes, based on basepoint, with direction (1,1)
560
- """
561
- if box is None:
562
- box = [self.get_bottom(), self.get_top()]
563
- if (len(box[0]) != 2) and (basepoints is None):
564
- raise ValueError("Basepoints has to be specified for filtration dimension >= 3 !")
565
- elif basepoints is None:
566
- h = box[1][1] - box[0][1]
567
- basepoints = np.linspace([box[0][0] - h,box[0][1]], [box[1][0],box[0][1]], num=num)
568
- else :
569
- num=len(basepoints)
570
-
571
- basepoints = np.asarray(basepoints, dtype={{PYTYPE}})
572
- cdef vector[Line[{{CTYPE}}]] cbasepoints
573
- for i in range(num):
574
- cbasepoints.push_back(Line[{{CTYPE}}](_py21c_{{SHORT}}(basepoints[i])))
575
- return tuple(np.asarray(bc) for bc in self.cmod.get_barcodes2(cbasepoints, degree))
576
-
577
- def landscape(self, degree:int, k:int=0,box:Sequence|np.ndarray|None=None, resolution:Sequence=[100,100], bool plot=False):
578
- r"""Computes the multiparameter landscape from a PyModule. Python interface only bifiltrations.
579
-
580
- Parameters
581
- ----------
582
-
583
- degree : integer
584
- The homology degree of the landscape.
585
- k = 0 : int
586
- the k-th landscape
587
- resolution = [50,50] : pair of integers
588
- Resolution of the image.
589
- box = None : in the format [[a,b], [c,d]]
590
- If nontrivial, compute the landscape of this box. Default is the PyModule box.
591
- plot = True : Boolean
592
- If true, plots the images;
593
- Returns
594
- -------
595
-
596
- The landscape of the module.
597
-
598
- """
599
- import matplotlib.pyplot as plt
600
- if box is None:
601
- box = self.get_box()
602
- cdef Box[{{CTYPE}}] c_box = Box[{{CTYPE}}](box)
603
- out = np.array(self.cmod.get_landscape(degree, k, c_box, resolution))
604
- if plot:
605
- plt.figure()
606
- aspect = (box[1][0]-box[0][0]) / (box[1][1]-box[0][1])
607
- extent = [box[0][0], box[1][0], box[0][1], box[1][1]]
608
- plt.imshow(out.T, origin="lower", extent=extent, aspect=aspect)
609
- return out
610
-
611
- def landscapes(self, degree:int, ks:list|np.ndarray=[0],box=None, resolution:list|np.ndarray=[100,100], bool plot=False):
612
- r"""Computes the multiparameter landscape from a PyModule. Python interface only bifiltrations.
613
-
614
- Parameters
615
- ----------
616
-
617
- - degree : integer
618
- The homology degree of the landscape.
619
- - ks = 0 : list of int
620
- the k-th landscape
621
- - resolution = [50,50] : pair of integers
622
- Resolution of the image.
623
- - box = None : in the format [[a,b], [c,d]]
624
- If nontrivial, compute the landscape of this box. Default is the PyModule box.
625
- - plot = True : bool
626
- If true, plots the images;
627
- Returns
628
- -------
629
-
630
- The landscapes of the module with parameters ks.
631
-
632
- """
633
- import matplotlib.pyplot as plt
634
- if box is None:
635
- box = self.get_box()
636
- out = np.array(self.cmod.get_landscapes(degree, ks, Box[{{CTYPE}}](box), resolution))
637
- if plot:
638
- to_plot = np.sum(out, axis=0)
639
- plt.figure()
640
- aspect = (box[1][0]-box[0][0]) / (box[1][1]-box[0][1])
641
- extent = [box[0][0], box[1][0], box[0][1], box[1][1]]
642
- plt.imshow(to_plot.T, origin="lower", extent=extent, aspect=aspect)
643
- return out
644
-
645
-
646
- def representation(self, degrees=None, double bandwidth=0.1,
647
- resolution:Sequence[int]|int=50,
648
- kernel:Literal["gaussian","linear","linear2","exponential"]|Callable = "gaussian",
649
- bool signed=False,
650
- bool normalize=False, bool plot=False,
651
- bool save=False, int dpi=200,double p=2., box=None,
652
- bool flatten=False, int n_jobs=0,
653
- grid = None)->np.ndarray:
654
- r"""Computes a representation of the module, using
655
-
656
- [A Framework for Fast and Stable Representations of Multiparameter Persistent Homology Decompositions, Neurips2023]
657
-
658
- Parameters
659
- ----------
660
-
661
- - degrees = None : integer list
662
- If given returns only the image(s) of homology degrees `degrees`.
663
- - bandwidth = 0.1 : float
664
- Image parameter.
665
- - resolution = [100,100] : pair of integers
666
- Resolution of the image(s).
667
- - normalize = True : Boolean
668
- Ensures that the image belongs to [0,1].
669
- - plot = False : Boolean
670
- If true, plots the images;
671
- - flatten=False :
672
- If True, reshapes the output to a flattened shape.
673
- - kernel: Either linear, gaussian, or callable
674
- The kernel to apply to the matrix $(d(x,I), x \in \mathrm{pixels}, I\in \mathrm{summands})$.
675
- signature should be : (distance matrix, summand_weights, bandwidth, p) -> representation
676
-
677
- Returns
678
- -------
679
-
680
- The list of images, or the image of fixed dimension.
681
- """
682
- import matplotlib.pyplot as plt
683
- # box = kwargs.get("box",[self.get_bottom(),self.get_top()])
684
- if box is None:
685
- box = self.get_box()
686
- num_parameters = self.num_parameters
687
- if degrees is None:
688
- degrees = np.arange(self.max_degree +1)
689
- num_degrees = len(degrees)
690
- try:
691
- int(resolution)
692
- resolution = [resolution]*num_parameters
693
- except:
694
- pass
695
-
696
- if grid is None:
697
- grid = [np.linspace(*np.asarray(box)[:,parameter], num=res) for parameter, res in zip(range(num_parameters), resolution)]
698
- else:
699
- resolution = tuple(len(g) for g in grid)
700
- coordinates = mpg.todense(grid)
701
-
702
- if kernel == "linear":
703
- assert not signed, "This kernel is not compatible with signed."
704
- concatenated_images = self._compute_pixels(coordinates, degrees=degrees, box=box, delta=bandwidth, p=p, normalize=normalize,n_jobs=n_jobs)
705
- else:
706
- if kernel == "linear2":
707
- def todo(PyModule_{{SHORT}} mod_degree):
708
- x = mod_degree.distance_to(coordinates,signed=signed)
709
- w = mod_degree.get_interleavings()[None]**p
710
- s = np.where(x>=0,1,-1) if signed else 1
711
- x = np.abs(x)
712
- return (s*np.where(x<bandwidth,(bandwidth-x)/bandwidth,0)*w).sum(1)
713
- elif kernel == "gaussian":
714
- def todo(PyModule_{{SHORT}} mod_degree):
715
- x = mod_degree.distance_to(coordinates,signed=signed)
716
- w = mod_degree.get_interleavings()[None]**p
717
- s = np.where(x>=0,1,-1) if signed else 1
718
- return (s*np.exp( -.5*((x/bandwidth)**2))*w).sum(1)
719
- elif kernel == "exponential":
720
- def todo(PyModule_{{SHORT}} mod_degree):
721
- x = mod_degree.distance_to(coordinates,signed=signed)
722
- w = mod_degree.get_interleavings()[None]**p
723
- s = np.where(x>=0,1,-1) if signed else 1
724
- x = np.abs(x)
725
- return (s*np.exp( -((np.abs(x)/bandwidth)))*w).sum(1)
726
- else:
727
- assert callable(kernel), r"""
728
- Kernel should be
729
- gaussian, linear, linear2, exponential or callable,
730
- with signature
731
- (array[shape=(N, M)], array[shape=(M)])-> array(shape=(N)),
732
- the first argument being a distance matrix (pts) vs (summands of the module)
733
- and the second argument is a weight vector (weight(summand) for summand in module).
734
- Note that the distance can be signed.
735
- """
736
- def todo(PyModule_{{SHORT}} mod_degree):
737
- x = mod_degree.distance_to(coordinates,signed=signed, n_jobs=n_jobs)
738
- w = mod_degree.get_interleavings()[None]**p
739
- return kernel(x/bandwidth,w)
740
- concatenated_images = np.stack(
741
- Parallel(n_jobs = n_jobs if n_jobs else -1, backend= "threading")(
742
- delayed(todo)(self.get_module_of_degree(degree))
743
- for degree in degrees
744
- )
745
- )
746
-
747
- if flatten:
748
- image_vector = concatenated_images.reshape((len(degrees),-1))
749
- if plot:
750
- raise ValueError("Unflatten to plot.")
751
- return image_vector
752
- else:
753
- image_vector = concatenated_images.reshape((len(degrees),*resolution))
754
- if plot:
755
- assert num_parameters == 2, "Plot only available for 2-parameter modules"
756
- import multipers.plots
757
- i=0
758
- n_plots = len(image_vector)
759
- scale = 4
760
- if n_plots >1:
761
- fig, axs = plt.subplots(1,n_plots, figsize=(n_plots*scale,scale))
762
- else:
763
- fig = plt.gcf()
764
- axs = [plt.gca()]
765
- for image, degree, i in zip(image_vector, degrees, range(num_degrees)):
766
- ax = axs[i]
767
- temp = multipers.plots.plot_surface(grid, image.T, ax=ax)
768
- plt.colorbar(temp, ax = ax)
769
- if degree < 0 :
770
- ax.set_title(rf"$H_{i}$ $2$-persistence image")
771
- if degree >= 0:
772
- ax.set_title(rf"$H_{degree}$ $2$-persistence image")
773
- return image_vector
774
-
775
- def euler_char(self, points:list|np.ndarray) -> np.ndarray:
776
- r""" Computes the Euler Characteristic of the filtered complex at given (multiparameter) time
777
-
778
- Parameters
779
- ----------
780
-
781
- points: list[float] | list[list[float]] | np.ndarray
782
- List of filtration values on which to compute the euler characteristic.
783
- WARNING FIXME : the points have to have the same dimension as the simplextree.
784
-
785
- Returns
786
- -------
787
-
788
- The list of euler characteristic values
789
- """
790
- if len(points) == 0:
791
- return []
792
- if type(points[0]) is float:
793
- points = [points]
794
- if type(points) is np.ndarray:
795
- assert len(points.shape) in [1,2]
796
- if len(points.shape) == 1:
797
- points = [points]
798
-
799
- cdef {{CTYPE}}[:,:] points_view = np.asarray(points, dtype = {{PYTYPE}})
800
- cdef vector[One_critical_filtration[{{CTYPE}}]] c_points = _py2v1c_{{SHORT}}(points_view)
801
- # cdef One_critical_filtration temp
802
- # for point in points:
803
- # temp.clear()
804
- # for truc in point:
805
- # temp.push_back(<{{CTYPE}}>(truc))
806
- # c_points.push_back(temp)
807
- cdef Module[{{CTYPE}}] c_mod = self.cmod
808
- with nogil:
809
- c_euler = c_mod.euler_curve(c_points)
810
- euler = c_euler
811
- return np.asarray(euler, dtype=int)
812
- def to_idx(self,grid):
813
- cdef vector[vector[{{CTYPE}}]] cgrid = grid
814
- cdef vector[vector[pair[vector[vector[int]],vector[vector[int]]]]] out
815
- with nogil:
816
- out = self.cmod.to_idx(cgrid)
817
- return tuple(tuple((np.asarray(I.first,dtype=np.int64), np.asarray(I.second, dtype=np.int64)) for I in Is_of_degree) for Is_of_degree in out)
818
-
819
- @cython.wraparound(False)
820
- @cython.boundscheck(False)
821
- def to_flat_idx(self,grid):
822
- if len(self) == 0:
823
- return np.empty((2,0), dtype = np.int32), np.empty((0, self.num_parameters), dtype = np.int32), np.empty((0, self.num_parameters), dtype = np.int32)
824
- cdef vector[vector[{{CTYPE}}]] cgrid = grid
825
- cdef vector[vector[vector[int]]] out
826
- cdef int num_summands, num_births, num_deaths
827
- cdef int num_parameters = self.num_parameters
828
- with nogil:
829
- out = self.cmod.to_flat_idx(cgrid)
830
- num_summands = out[0][0].size()
831
- num_births = out[1].size()
832
- num_deaths = out[2].size()
833
- idx = np.empty((2, num_summands),dtype=np.int32 )
834
- births = np.empty((num_births,num_parameters),dtype=np.int32)
835
- deaths = np.empty((num_deaths,num_parameters),dtype=np.int32)
836
-
837
- cdef int32_t[:,:] idx_view = idx
838
- cdef int32_t[:,:] births_view = births
839
- cdef int32_t[:,:] deaths_view = deaths
840
-
841
- with nogil:
842
- for i in range(num_summands):
843
- idx_view[0,i] = out[0][0][i]
844
- idx_view[1,i] = out[0][1][i]
845
- for i in range(num_births):
846
- for j in range(num_parameters):
847
- births_view[i,j] = out[1][i][j]
848
- for i in range(num_deaths):
849
- for j in range(num_parameters):
850
- deaths_view[i,j] = out[2][i][j]
851
-
852
- return idx, births,deaths
853
-
854
- def distances_idx_to(self, pts, bool full=False, int n_jobs=1):
855
- pts = np.asarray(pts)
856
- if pts.ndim == 1:
857
- pts = pts[None]
858
- cdef vector[vector[{{CTYPE}}]] cpts = pts
859
- cdef vector[vector[vector[int]]] out
860
- with nogil:
861
- out = self.cmod.compute_distances_idx_to(cpts,full, n_jobs)
862
- return np.asarray(out, dtype=np.int32)
863
-
864
- def distance_to(self, pts, bool signed=False, int n_jobs = 0)->np.ndarray:
865
- r"""
866
- Distance from a point to each summand's support.
867
- Signed distance is the distance to the boundary,
868
- with negative values inside the summands.
869
-
870
- pts of shape (num_pts, num_parameters)
871
-
872
- output shape : (num_pts,num_summands)
873
- """
874
- pts = np.asarray(pts)
875
- if pts.ndim == 1:
876
- pts = pts[None]
877
- assert pts.shape[-1] == self.num_parameters
878
- cdef vector[vector[{{CTYPE}}]] cpts = pts
879
- # cdef vector[vector[{{CTYPE}}]] out
880
- to_fill = np.empty(shape=(cpts.size(),len(self)), dtype = {{PYTYPE}})
881
- cdef {{CTYPE}}[:,:] c_to_fill = to_fill
882
- cdef {{CTYPE}}* data_ptr = &c_to_fill[0,0]
883
- with nogil:
884
- self.cmod.compute_distances_to(data_ptr, cpts,signed, n_jobs)
885
- return to_fill
886
-
887
- def get_interleavings(self,box=None):
888
- if box is None:
889
- box = self.get_box()
890
- cdef Box[{{CTYPE}}] cbox = Box[{{CTYPE}}](box)
891
- return np.asarray(self.cmod.get_interleavings(cbox))
892
-
893
- cdef class PyMultiDiagramPoint_{{SHORT}}:
894
- cdef MultiDiagram_point[One_critical_filtration[{{CTYPE}}]] point
895
- cdef set(self, MultiDiagram_point[One_critical_filtration[{{CTYPE}}]] pt):
896
- self.point = pt
897
- return self
898
-
899
- def get_degree(self):
900
- return self.point.get_dimension()
901
- def get_birth(self):
902
- cdef One_critical_filtration[{{CTYPE}}] v = self.point.get_birth()
903
- return _ff21cview_{{SHORT}}(&v, copy=True)
904
- def get_death(self):
905
- cdef One_critical_filtration[{{CTYPE}}] v = self.point.get_death()
906
- return _ff21cview_{{SHORT}}(&v, copy=True)
907
-
908
-
909
- cdef class PyMultiDiagram_{{SHORT}}:
910
- r"""
911
- Stores the diagram of a PyModule on a line
912
- """
913
- cdef MultiDiagram[One_critical_filtration[{{CTYPE}}], {{CTYPE}}] multiDiagram
914
- cdef set(self, MultiDiagram[One_critical_filtration[{{CTYPE}}], {{CTYPE}}] m):
915
- self.multiDiagram = m
916
- return self
917
- def get_points(self, degree:int=-1) -> np.ndarray:
918
- cdef vector[pair[vector[{{CTYPE}}],vector[{{CTYPE}}]]] out = self.multiDiagram.get_points(degree)
919
- if len(out) == 0 and len(self) == 0:
920
- return np.empty() # TODO Retrieve good number of parameters if there is no points in diagram
921
- if len(out) == 0:
922
- return np.empty((0,2,self.multiDiagram.at(0).get_dimension())) # gets the number of parameters
923
- return np.array(out)
924
- def to_multipers(self, dimension:int):
925
- return self.multiDiagram.to_multipers(dimension)
926
- def __len__(self) -> int:
927
- return self.multiDiagram.size()
928
- def __getitem__(self,i:int) -> PyMultiDiagramPoint_{{SHORT}}:
929
- return PyMultiDiagramPoint_{{SHORT}}().set(self.multiDiagram.at(i % self.multiDiagram.size()))
930
- cdef class PyMultiDiagrams_{{SHORT}}:
931
- """
932
- Stores the barcodes of a PyModule on multiple lines
933
- """
934
- cdef MultiDiagrams[One_critical_filtration[{{CTYPE}}], {{CTYPE}}] multiDiagrams
935
- cdef set(self,MultiDiagrams[One_critical_filtration[{{CTYPE}}], {{CTYPE}}] m):
936
- self.multiDiagrams = m
937
- return self
938
- def to_multipers(self):
939
- out = self.multiDiagrams.to_multipers()
940
- return [np.asarray(summand) for summand in out]
941
- def __getitem__(self,i:int):
942
- if i >=0 :
943
- return PyMultiDiagram_{{SHORT}}().set(self.multiDiagrams.at(i))
944
- else:
945
- return PyMultiDiagram_{{SHORT}}().set(self.multiDiagrams.at( self.multiDiagrams.size() - i))
946
- def __len__(self):
947
- return self.multiDiagrams.size()
948
- def get_points(self, degree:int=-1):
949
- return self.multiDiagrams.get_points()
950
- cdef _get_plot_bars(self, dimension:int=-1, min_persistence:float=0):
951
- return self.multiDiagrams._for_python_plot(dimension, min_persistence);
952
- def plot(self, degree:int=-1, min_persistence:float=0):
953
- """
954
- Plots the barcodes.
955
-
956
- Parameters
957
- ----------
958
-
959
- - degree:int=-1
960
- Only plots the bars of specified homology degree. Useful when the multidiagrams contains multiple dimenions
961
- - min_persistence:float=0
962
- Only plot bars of length greater than this value. Useful to reduce the time to plot.
963
-
964
- Warning
965
- -------
966
-
967
- If the barcodes are not thresholded, essential barcodes will not be displayed !
968
-
969
- """
970
- from cycler import cycler
971
- import matplotlib
972
- import matplotlib.pyplot as plt
973
- if len(self) == 0: return
974
- _cmap = matplotlib.colormaps["Spectral"]
975
- multibarcodes_, colors = self._get_plot_bars(degree, min_persistence)
976
- n_summands = np.max(colors)+1 if len(colors)>0 else 1
977
-
978
- plt.rc('axes', prop_cycle = cycler('color', [_cmap(i/n_summands) for i in colors]))
979
- return plt.plot(*multibarcodes_)
980
- {{endif}}
981
-
982
-
983
- cdef dump_summand_{{SHORT}}(Summand[{{CTYPE}}]& summand):
984
- cdef vector[One_critical_filtration[{{CTYPE}}]] births = summand.get_birth_list()
985
- cdef vector[One_critical_filtration[{{CTYPE}}]] deaths = summand.get_death_list()
986
- return (
987
- _vff21cview_{{SHORT}}(births, copy=True), ## copy as local variables
988
- _vff21cview_{{SHORT}}(deaths, copy=True),
989
- summand.get_dimension(),
990
- )
991
-
992
- cdef inline Summand[{{CTYPE}}] summand_from_dump_{{SHORT}}(summand_dump):
993
- cdef vector[One_critical_filtration[{{CTYPE}}]] births = _py2v1c_{{SHORT}}(summand_dump[0])
994
- cdef vector[One_critical_filtration[{{CTYPE}}]] deaths = _py2v1c_{{SHORT}}(summand_dump[1])
995
- cdef int dim = summand_dump[2]
996
- return Summand[{{CTYPE}}](births,deaths,dim)
997
-
998
- cdef dump_cmod_{{SHORT}}(Module[{{CTYPE}}]& mod):
999
- cdef Box[{{CTYPE}}] cbox = mod.get_box()
1000
- cdef int dim = mod.get_dimension()
1001
- cdef cnp.ndarray[{{CTYPE}}, ndim=1] bottom_corner = _ff21cview_{{SHORT}}(&cbox.get_lower_corner())
1002
- cdef cnp.ndarray[{{CTYPE}}, ndim=1] top_corner = _ff21cview_{{SHORT}}(&cbox.get_upper_corner())
1003
- box = np.asarray([bottom_corner, top_corner])
1004
- summands = tuple(dump_summand_{{SHORT}}(summand) for summand in mod)
1005
- return box, summands
1006
-
1007
- cdef Module[{{CTYPE}}] cmod_from_dump_{{SHORT}}(module_dump):
1008
- box = module_dump[0]
1009
- summands = module_dump[1]
1010
- cdef Module[{{CTYPE}}] out_module = Module[{{CTYPE}}]()
1011
- out_module.set_box(Box[{{CTYPE}}](box))
1012
- for i in range(len(summands)):
1013
- out_module.add_summand(summand_from_dump_{{SHORT}}(summands[i]))
1014
- return out_module
1015
-
1016
-
1017
- def from_dump_{{SHORT}}(dump)->PyModule_{{SHORT}}:
1018
- r"""Retrieves a PyModule from a previous dump.
1019
-
1020
- Parameters
1021
- ----------
1022
-
1023
- dump: either the output of the dump function, or a file containing the output of a dump.
1024
- The dumped module to retrieve
1025
-
1026
- Returns
1027
- -------
1028
-
1029
- PyModule
1030
- The retrieved module.
1031
- """
1032
- # TODO : optimize...
1033
- mod = PyModule_{{SHORT}}()
1034
- if type(dump) is str:
1035
- dump = pickle.load(open(dump, "rb"))
1036
- cdef Module[{{CTYPE}}] cmod = cmod_from_dump_{{SHORT}}(dump)
1037
- mod.cmod = cmod
1038
- return mod
1039
-
1040
-
1041
- {{endfor}}
1042
-
1043
-
1044
- global PyModule_type, PySummand_type
1045
- PyModule_type = Union[
1046
- {{for CTYPE, PYTYPE, SHORT in value_types}}
1047
- PyModule_{{SHORT}},
1048
- {{endfor}}
1049
- ]
1050
- PySummand_type = Union[
1051
- {{for CTYPE, PYTYPE, SHORT in value_types}}
1052
- PySummand_{{SHORT}},
1053
- {{endfor}}
1054
- ]
1055
-
1056
- PyBox_type = Union[
1057
- {{for CTYPE, PYTYPE, SHORT in value_types}}
1058
- PyBox_{{SHORT}},
1059
- {{endfor}}
1060
- ]
1061
-
1062
-
1063
- PyMultiDiagram_type = Union[
1064
- {{for CTYPE, PYTYPE, SHORT in value_types}}
1065
- {{if SHORT[0] == 'f'}}
1066
- PyMultiDiagram_{{SHORT}},
1067
- {{endif}}
1068
- {{endfor}}
1069
- ]
1070
-
1071
-
1072
- PyMultiDiagrams_type = Union[
1073
- {{for CTYPE, PYTYPE, SHORT in value_types}}
1074
- {{if SHORT[0] == 'f'}}
1075
- PyMultiDiagrams_{{SHORT}},
1076
- {{endif}}
1077
- {{endfor}}
1078
- ]
1079
-
1080
- def is_mma(stuff):
1081
- return (False
1082
- {{for CTYPE, PYTYPE, SHORT in value_types}}
1083
- or isinstance(stuff,PyModule_{{SHORT}})
1084
- {{endfor}}
1085
- )
1
+ {{py:
2
+
3
+ """
4
+ MMA core structures.
5
+ """
6
+
7
+ ## Value types : CTYPE, PYTHON_TYPE, short
8
+ # value_types = [
9
+ # ("int32_t", "np.int32", "i32"),
10
+ # ("int64_t", "np.int64", "i64"),
11
+ # ("float", "np.float32", "f32"),
12
+ # ("double", "np.float64", "f64"),
13
+ # ]
14
+
15
+ import pickle as pkl
16
+ import numpy as np
17
+
18
+ D = pkl.load(open("build/tmp/_slicer_names.pkl", "rb"))
19
+ value_types = np.unique([tuple((x['C_VALUE_TYPE'],x['PY_VALUE_TYPE'], x['SHORT_VALUE_TYPE'])) for x in D], axis=0)
20
+
21
+ }}
22
+
23
+
24
+
25
+
26
+
27
+ """!
28
+ @package mma
29
+ @brief Files containing the C++ cythonized functions.
30
+ @author David Loiseaux
31
+ @copyright Copyright (c) 2022 Inria.
32
+ """
33
+
34
+ # distutils: language = c++
35
+
36
+ ###########################################################################
37
+ ## PYTHON LIBRARIES
38
+ import gudhi as gd
39
+ import numpy as np
40
+ from typing import Union, Literal
41
+ from collections.abc import Callable, Iterable, Sequence
42
+ import pickle
43
+ import multipers.grids as mpg
44
+
45
+ ###########################################################################
46
+ ## CPP CLASSES
47
+ from libc.stdint cimport intptr_t
48
+ from libc.stdint cimport uintptr_t
49
+
50
+ ###########################################################################
51
+ ## CYTHON TYPES
52
+ from libcpp.vector cimport vector
53
+ from libcpp.utility cimport pair
54
+ #from libcpp.list cimport list as clist
55
+ from libcpp cimport bool
56
+ from libcpp cimport int
57
+ from cython.operator cimport dereference
58
+ from libcpp.utility cimport move
59
+ cimport cython
60
+ #########################################################################
61
+ ## Multipersistence Module Approximation Classes
62
+ from multipers.mma_structures cimport *
63
+ from multipers.filtration_conversions cimport *
64
+ cimport numpy as cnp
65
+
66
+
67
+ #########################################################################
68
+ ## Small hack for typing
69
+ from gudhi import SimplexTree
70
+ from multipers.simplex_tree_multi import SimplexTreeMulti
71
+ from joblib import Parallel, delayed
72
+
73
+ available_pymodules = [
74
+ {{for CTYPE, PYTYPE, SHORT in value_types}}
75
+ PyModule_{{SHORT}},
76
+ {{endfor}}
77
+ ]
78
+
79
+ PyModule_type = Union[
80
+ {{for CTYPE, PYTYPE, SHORT in value_types}}
81
+ PyModule_{{SHORT}},
82
+ {{endfor}}
83
+ ]
84
+
85
+ {{for CTYPE, PYTYPE, SHORT in value_types}}
86
+ cdef class PySummand_{{SHORT}}:
87
+ r"""
88
+ Stores a Summand of a PyModule
89
+ """
90
+ cdef Summand[{{CTYPE}}] sum
91
+
92
+ def get_birth_list(self):
93
+ cdef vector[One_critical_filtration[{{CTYPE}}]] v = self.sum.get_birth_list()
94
+ return _vff21cview_{{SHORT}}(v, copy = True, duplicate = self.num_parameters())
95
+
96
+ def get_death_list(self):
97
+ cdef vector[One_critical_filtration[{{CTYPE}}]] v = self.sum.get_death_list()
98
+ return _vff21cview_{{SHORT}}(v, copy = True, duplicate = self.num_parameters())
99
+ @property
100
+ def degree(self)->int:
101
+ return self.sum.get_dimension()
102
+
103
+ cdef set(self, Summand[{{CTYPE}}]& summand):
104
+ self.sum = summand
105
+ return self
106
+ def get_bounds(self):
107
+ cdef pair[One_critical_filtration[{{CTYPE}}],One_critical_filtration[{{CTYPE}}]] cbounds
108
+ with nogil:
109
+ cbounds = self.sum.get_bounds().get_bounding_corners()
110
+ return _ff21cview_{{SHORT}}(&cbounds.first).copy(), _ff21cview_{{SHORT}}(&cbounds.second).copy()
111
+ @property
112
+ def dtype(self):
113
+ return {{PYTYPE}}
114
+
115
+ def num_parameters(self):
116
+ cdef vector[One_critical_filtration[{{CTYPE}}]] v = self.sum.get_birth_list()
117
+ if v[0].is_finite():
118
+ return v[0].num_parameters()
119
+ v = self.sum.get_death_list()
120
+ return v[0].num_parameters()
121
+
122
+ cdef inline get_summand_filtration_values_{{SHORT}}(Summand[{{CTYPE}}] summand):
123
+ r"""
124
+ Returns a list (over parameter) of the filtrations values of this parameter.
125
+ """
126
+ cdef vector[One_critical_filtration[{{CTYPE}}]] vb = summand.get_birth_list()
127
+ cdef vector[One_critical_filtration[{{CTYPE}}]] vd = summand.get_death_list()
128
+
129
+ if vb[0].is_finite():
130
+ if vd[0].is_finite():
131
+ pts = np.concatenate([_vff21cview_{{SHORT}}(vb, copy=True),
132
+ _vff21cview_{{SHORT}}(vd, copy=True)],axis=0)
133
+ else:
134
+ pts = np.array(_vff21cview_{{SHORT}}(vb, copy=True))
135
+ else:
136
+ if vd[0].is_finite():
137
+ pts = np.array(_vff21cview_{{SHORT}}(vd, copy=True))
138
+ else:
139
+ return []
140
+
141
+ num_parameters = pts.shape[1]
142
+ out = [np.unique(pts[:,parameter]) for parameter in range(num_parameters)]
143
+ out = [f[:-1] if f[-1] == np.inf else f for f in out]
144
+ out = [f[1:] if f[0] == -np.inf else f for f in out]
145
+ return out
146
+
147
+ cdef class PyBox_{{SHORT}}:
148
+ cdef Box[{{CTYPE}}] box
149
+ def __cinit__(self, vector[{{CTYPE}}]& bottomCorner, vector[{{CTYPE}}]& topCorner):
150
+ self.box = Box[{{CTYPE}}](bottomCorner, topCorner)
151
+ @property
152
+ def num_parameters(self):
153
+ cdef size_t dim = self.box.get_lower_corner().num_parameters()
154
+ if dim == self.box.get_upper_corner().num_parameters(): return dim
155
+ else: print("Bad box definition.")
156
+ def contains(self, x):
157
+ return self.box.contains(x)
158
+ cdef set(self, Box[{{CTYPE}}]& b):
159
+ self.box = b
160
+ return self
161
+
162
+ def get(self):
163
+ return [<vector[{{CTYPE}}]>self.box.get_lower_corner(), <vector[{{CTYPE}}]>self.box.get_upper_corner()]
164
+ def to_multipers(self):
165
+ #assert (self.get_dimension() == 2) "Multipers only works in dimension 2 !"
166
+ return np.array(self.get()).flatten(order = 'F')
167
+ @property
168
+ def dtype(self):
169
+ return {{PYTYPE}}
170
+
171
+
172
+
173
+ cdef class PyModule_{{SHORT}}:
174
+ r"""
175
+ Stores a representation of a n-persistence module.
176
+ """
177
+ cdef Module[{{CTYPE}}] cmod
178
+
179
+ @property
180
+ def dtype(self):
181
+ return {{PYTYPE}}
182
+
183
+ cdef set(self, Module[{{CTYPE}}] m):
184
+ self.cmod = m
185
+ def merge(self, PyModule_{{SHORT}} other, int dim=-1):
186
+ r"""
187
+ Merges two modules into one
188
+ """
189
+ cdef Module[{{CTYPE}}] c_other = other.cmod
190
+ with nogil:
191
+ for summand in c_other:
192
+ self.cmod.add_summand(summand, dim)
193
+ return self
194
+
195
+ def _set_from_ptr(self, intptr_t module_ptr):
196
+ r"""
197
+ Copy module from a memory pointer. Unsafe.
198
+ """
199
+ self.cmod = move(dereference(<Module[{{CTYPE}}]*>(module_ptr)))
200
+ def set_box(self, PyBox_{{SHORT}} pybox):
201
+ cdef Box[{{CTYPE}}] cbox = pybox.box
202
+ with nogil:
203
+ self.cmod.set_box(cbox)
204
+ return self
205
+ def get_module_of_degree(self, int degree)->PyModule_{{SHORT}}: # TODO : in c++ ?
206
+ r"""
207
+ Returns a copy of a module of fixed degree.
208
+ """
209
+ pmodule = PyModule_{{SHORT}}()
210
+ cdef Box[{{CTYPE}}] c_box = self.cmod.get_box()
211
+ pmodule.cmod.set_box(c_box)
212
+ with nogil:
213
+ for summand in self.cmod:
214
+ if summand.get_dimension() == degree:
215
+ pmodule.cmod.add_summand(summand)
216
+ return pmodule
217
+ def get_module_of_degrees(self, degrees:Iterable[int])->PyModule_{{SHORT}}: # TODO : in c++ ?
218
+ r"""
219
+ Returns a copy of the summands of degrees in `degrees`
220
+ """
221
+ pmodule = PyModule_{{SHORT}}()
222
+ cdef Box[{{CTYPE}}] c_box = self.cmod.get_box()
223
+ pmodule.cmod.set_box(c_box)
224
+ cdef vector[int] cdegrees = degrees
225
+ with nogil:
226
+ for summand in self.cmod:
227
+ for d in cdegrees:
228
+ if d == summand.get_dimension():
229
+ pmodule.cmod.add_summand(summand)
230
+ return pmodule
231
+ def __len__(self)->int:
232
+ return self.cmod.size()
233
+ def get_bottom(self)->np.ndarray:
234
+ r"""
235
+ Bottom of the box of the module
236
+ """
237
+ return np.asarray(<vector[{{CTYPE}}]>(self.cmod.get_box().get_lower_corner()))
238
+ def get_top(self)->np.ndarray:
239
+ r"""
240
+ Top of the box of the module
241
+ """
242
+ return np.asarray(<vector[{{CTYPE}}]>(self.cmod.get_box().get_upper_corner()))
243
+ def get_box(self)->np.ndarray:
244
+ r"""
245
+ Returns the current bounding box of the module.
246
+ """
247
+ return np.asarray([self.get_bottom(), self.get_top()])
248
+ @property
249
+ def max_degree(self)->int:
250
+ r"""
251
+ Returns the maximum degree of the module.
252
+ """
253
+ return self.cmod.get_dimension()
254
+ @property
255
+ def num_parameters(self)->int:
256
+ cdef size_t dim = self.cmod.get_box().get_lower_corner().num_parameters()
257
+ assert dim == self.cmod.get_box().get_upper_corner().num_parameters(), "Bad box definition, cannot infer num_parameters."
258
+ return dim
259
+ def dump(self, path:str|None=None):
260
+ r"""
261
+ Dumps the module into a pickle-able format.
262
+
263
+ Parameters
264
+ ----------
265
+
266
+ path:str=None (optional) saves the pickled module in specified path
267
+
268
+ Returns
269
+ -------
270
+
271
+ list of list, encoding the module, which can be retrieved with the function `from_dump`.
272
+ """
273
+ ## TODO : optimize, but not really used.
274
+ return dump_cmod_{{SHORT}}(self.cmod)
275
+ def __getstate__(self):
276
+ return self.dump()
277
+ def __setstate__(self,dump):
278
+ cdef Module[{{CTYPE}}] cmod = cmod_from_dump_{{SHORT}}(dump)
279
+ self.cmod = cmod
280
+ return
281
+ def __getitem__(self, int i) -> PySummand_{{SHORT}}:
282
+ if i == slice(None):
283
+ return self
284
+ summand = PySummand_{{SHORT}}()
285
+ summand.set(self.cmod.at(i % self.cmod.size()))
286
+ return summand
287
+ def __iter__(self):
288
+ cdef int num_summands = self.cmod.size()
289
+ for i in range(num_summands):
290
+ summand = PySummand_{{SHORT}}()
291
+ summand.set(self.cmod.at(i)) ## hmm copy. maybe do summand from ptr
292
+ yield summand
293
+
294
+ def get_bounds(self):
295
+ r"""
296
+ Computes bounds from the summands' bounds.
297
+ Useful to change this' box.
298
+ """
299
+ cdef pair[One_critical_filtration[{{CTYPE}}],One_critical_filtration[{{CTYPE}}]] cbounds
300
+ with nogil:
301
+ cbounds = self.cmod.get_bounds().get_bounding_corners()
302
+ return _ff21cview_{{SHORT}}(&cbounds.first).copy(), _ff21cview_{{SHORT}}(&cbounds.second).copy()
303
+ def rescale(self,rescale_factors, int degree=-1):
304
+ r"""
305
+ Rescales the fitlration values of the summands by this rescaling vector.
306
+ """
307
+ cdef vector[{{CTYPE}}] crescale_factors = rescale_factors
308
+ with nogil:
309
+ self.cmod.rescale(crescale_factors,degree)
310
+ def translate(self,translation, int degree=-1):
311
+ r"""
312
+ Translates the module in the filtration space by this vector.
313
+ """
314
+ cdef vector[{{CTYPE}}] ctranslation = translation
315
+ with nogil:
316
+ self.cmod.translate(ctranslation,degree)
317
+
318
+ def get_filtration_values(self, bool unique=True):
319
+ r"""
320
+ Retrieves all filtration values of the summands of the module.
321
+
322
+ Output format
323
+ -------------
324
+
325
+ list of filtration values for parameter.
326
+ """
327
+ if len(self) ==0:
328
+ return np.empty((self.num_parameters,0))
329
+ values = tuple(tuple(stuff) if len(stuff:=get_summand_filtration_values_{{SHORT}}(summand)) == self.num_parameters else list(stuff) + [[]]*(self.num_parameters - len(stuff)) for summand in self.cmod)
330
+ try:
331
+ values = tuple(np.concatenate([
332
+ f[parameter]
333
+ for f in values
334
+ ], axis=0) for parameter in range(self.num_parameters)
335
+ )
336
+ except:
337
+ return values
338
+ if unique:
339
+ return [np.unique(f) for f in values]
340
+ return values
341
+
342
+ def plot(self, int degree=-1,**kwargs)->None:
343
+ r"""Shows the module on a plot. Each color corresponds to an apprimation summand of the module, and its shape corresponds to its support.
344
+ Only works with 2-parameter modules.
345
+
346
+ Parameters
347
+ ----------
348
+ degree = -1 : integer
349
+ If positive returns only the image of dimension `dimension`.
350
+ box=None : of the form [[b_x,b_y], [d_x,d_y]] where b,d are the bottom and top corner of the rectangle.
351
+ If non-None, will plot the module on this specific rectangle.
352
+ min_persistence =0 : float
353
+ Only plots the summand with a persistence above this threshold.
354
+ separated=False : bool
355
+ If true, plot each summand in a different plot.
356
+ alpha=1 : float
357
+ Transparancy parameter
358
+ save = False : string
359
+ if nontrivial, will save the figure at this path
360
+
361
+
362
+ Returns
363
+ -------
364
+ The figure of the plot.
365
+ """
366
+ from multipers.plots import plot2d_PyModule
367
+ import matplotlib.pyplot as plt
368
+ box = kwargs.pop('box', self.get_box())
369
+ if (len(box[0]) != 2):
370
+ print("Filtration size :", len(box[0]), " != 2")
371
+ return
372
+ if(degree < 0):
373
+ dims = np.unique(self.get_dimensions())
374
+ separated = kwargs.pop("separated", False)
375
+ ndim = len(dims)
376
+ scale = kwargs.pop("scale", 4)
377
+ if separated:
378
+ fig = None
379
+ axes = None
380
+ elif ndim > 1:
381
+ fig, axes = plt.subplots(1,ndim, figsize=(ndim*scale,scale))
382
+ else:
383
+ fig = plt.gcf()
384
+ axes = [plt.gca()]
385
+ for dim_idx in range(ndim):
386
+ if not separated:
387
+ plt.sca(axes[dim_idx])
388
+ self.plot(dims[dim_idx],box=box, separated = separated, **kwargs)
389
+ return
390
+ corners = self.cmod.get_corners_of_dimension(degree)
391
+ plot2d_PyModule(corners, box=box, dimension=degree, **kwargs)
392
+ return
393
+ def degree_splits(self):
394
+ return np.asarray(self.cmod.get_degree_splits(), dtype=np.int64)
395
+ {{if SHORT[0] == 'f'}}
396
+ def _compute_pixels(self,coordinates:np.ndarray,
397
+ degrees=None, box=None, {{CTYPE}} delta=.1,
398
+ {{CTYPE}} p=1., bool normalize=False, int n_jobs=0):
399
+ r"""
400
+ Computes the image of the module at the given coordinates
401
+ """
402
+ if degrees is not None: assert np.all(degrees[:-1] <= degrees[1:]), "Degrees have to be sorted"
403
+ cdef vector[int] cdegrees = np.arange(self.max_degree +1) if degrees is None else degrees
404
+ pybox = PyBox_{{SHORT}}(*self.get_box()) if box is None else PyBox_{{SHORT}}(*box)
405
+ cdef Box[{{CTYPE}}] cbox = pybox.box
406
+ cdef vector[vector[{{CTYPE}}]] ccoords = coordinates
407
+ cdef vector[vector[{{CTYPE}}]] out
408
+ with nogil:
409
+ out = self.cmod.compute_pixels(ccoords, cdegrees, cbox, delta, p, normalize, n_jobs)
410
+ return np.asarray(out)
411
+ def barcode(self, basepoint, int degree = -1,*, bool threshold = False): # TODO direction vector interface
412
+ r"""Computes the barcode of module along a lines.
413
+
414
+ Parameters
415
+ ----------
416
+
417
+ basepoint : vector
418
+ basepoint of the lines on which to compute the barcodes, i.e. a point on the line
419
+ degree = -1 : integer
420
+ Homology degree on which to compute the bars. If negative, every dimension is computed
421
+ box (default) :
422
+ box on which to compute the barcodes if basepoints is not specified. Default is a linspace of lines crossing that box.
423
+ threshold = False :
424
+ Thre
425
+
426
+ Warning
427
+ -------
428
+
429
+ If the barcodes are not thresholded, essential barcodes will not be plot-able.
430
+
431
+ Returns
432
+ -------
433
+
434
+ PyMultiDiagrams
435
+ Structure that holds the barcodes. Barcodes can be retrieved with a .get_points() or a .to_multipers() or a .plot().
436
+ """
437
+ out = PyMultiDiagram_{{SHORT}}()
438
+ out.set(self.cmod.get_barcode(Line[{{CTYPE}}](_py21c_{{SHORT}}(np.asarray(basepoint, dtype={{PYTYPE}}))), degree, threshold))
439
+ return out
440
+ @staticmethod
441
+ cdef _threshold_bc(bc):
442
+ return tuple(np.fromiter((a for a in stuff if a[0] < np.inf), dtype=np.dtype(({{PYTYPE}},2)) ) for stuff in bc)
443
+ @staticmethod
444
+ def _bc_to_full(bcs, basepoint, direction=None):
445
+ # i, (b sv d), coords
446
+ basepoint = np.asarray(basepoint)[None,None,:]
447
+ direction = 1 if direction is None else np.asarray(direction)[None,None,:]
448
+ return tuple(bc[:,:,None]*direction + basepoint for bc in bcs)
449
+ def barcode2(self, basepoint, direction=None, int degree = -1,*, bool threshold = False, bool keep_inf = True, bool full = False): # TODO direction vector interface
450
+ r"""
451
+ Compute the 1d-barcode a diagonal line based on basepoint, with some direction.
452
+
453
+ Parameters
454
+ ----------
455
+
456
+ - basepoint: 1d array
457
+ - directiont: 1d array or None, if None: diagonal
458
+ - degree: int the degree to compute (-1 means all)
459
+ - threshold: bool if True, threshold the barcode to the modules box
460
+ - keep_inf: bool if False, removes trivial bars
461
+ Note that this removes the order w.r.t. the summands in that case
462
+ - full:bool if True, returns the coordinates of the barcode instead of the coordinate in the line.
463
+
464
+ The output is of the form
465
+
466
+ tuple[np.ndarray of shape (num_bars,2)] or tuple[array of shape (num_bar, 2, num_parameters)]
467
+ """
468
+ basepoint = np.asarray(basepoint, dtype={{PYTYPE}})
469
+ if direction is None:
470
+ bc = tuple(np.asarray(x).reshape(-1,2) for x in self.cmod.get_barcode2(Line[{{CTYPE}}](_py21c_{{SHORT}}(basepoint)), degree))
471
+ else:
472
+ direction = np.asarray(direction, dtype = {{PYTYPE}})
473
+ bc = tuple(np.asarray(x).reshape(-1,2) for x in self.cmod.get_barcode2(Line[{{CTYPE}}](_py21c_{{SHORT}}(basepoint), _py21c_{{SHORT}}(direction)), degree))
474
+ if not keep_inf:
475
+ bc = PyModule_{{SHORT}}._threshold_bc(bc)
476
+ if full:
477
+ bc = PyModule_{{SHORT}}._bc_to_full(bc, basepoint, direction)
478
+
479
+ return bc
480
+
481
+ def get_dimensions(self):
482
+ cdef int num_summands = len(self)
483
+ out = np.empty(shape=num_summands, dtype=np.int32)
484
+ cdef int32_t[:] c_out = out
485
+ for i in range(num_summands):
486
+ c_out[i] = self.cmod.at(i).get_dimension()
487
+ return out
488
+
489
+ def barcodes(self, int degree, basepoints = None, num=100, box = None,threshold = False):
490
+ r"""Computes barcodes of module along a set of lines.
491
+
492
+ Parameters
493
+ ----------
494
+
495
+ basepoints = None : list of vectors
496
+ basepoints of the lines on which to compute the barcodes.
497
+ degree = -1 : integer
498
+ Homology degree on which to compute the bars. If negative, every dimension is computed
499
+ box (default) :
500
+ box on which to compute the barcodes if basepoints is not specified. Default is a linspace of lines crossing that box.
501
+ num:int=100
502
+ if basepoints is not specified, defines the number of lines to consider.
503
+ threshold = False : threshold t
504
+ Resolution of the image(s).
505
+
506
+ Warning
507
+ -------
508
+
509
+ If the barcodes are not thresholded, essential barcodes will not be plot-able.
510
+
511
+ Returns
512
+ -------
513
+
514
+ PyMultiDiagrams
515
+ Structure that holds the barcodes. Barcodes can be retrieved with a .get_points() or a .to_multipers() or a .plot().
516
+ """
517
+ out = PyMultiDiagrams_{{SHORT}}()
518
+ if box is None:
519
+ box = [self.get_bottom(), self.get_top()]
520
+ if (len(box[0]) != 2) and (basepoints is None):
521
+ raise ValueError("Basepoints has to be specified for filtration dimension >= 3 !")
522
+ elif basepoints is None:
523
+ h = box[1][1] - box[0][1]
524
+ basepoints = np.linspace([box[0][0] - h,box[0][1]], [box[1][0],box[0][1]], num=num)
525
+ else :
526
+ num=len(basepoints)
527
+
528
+ cdef {{CTYPE}}[:,:] basepoints_view = np.asarray(basepoints, dtype = {{PYTYPE}})
529
+ cdef vector[One_critical_filtration[{{CTYPE}}]] cbasepoints = _py2v1c_{{SHORT}}(basepoints_view)
530
+
531
+ out.set(self.cmod.get_barcodes(cbasepoints, degree, threshold))
532
+ return out
533
+
534
+ def barcodes2(self, int degree = -1, basepoints = None, int num=100, box = None, bool threshold = False):
535
+ r"""Computes barcodes of module along a set of lines.
536
+
537
+ Parameters
538
+ ----------
539
+
540
+ basepoints = None : list of vectors
541
+ basepoints of the lines on which to compute the barcodes.
542
+ degree = -1 : integer
543
+ Homology degree on which to compute the bars. If negative, every dimension is computed
544
+ box (default) :
545
+ box on which to compute the barcodes if basepoints is not specified. Default is a linspace of lines crossing that box.
546
+ num:int=100
547
+ if basepoints is not specified, defines the number of lines to consider.
548
+ threshold = False : threshold t
549
+ Resolution of the image(s).
550
+
551
+ Warning
552
+ -------
553
+
554
+ If the barcodes are not thresholded, essential barcodes will not be plot-able.
555
+
556
+ Returns
557
+ -------
558
+
559
+ tuple of 1d barcodes, based on basepoint, with direction (1,1)
560
+ """
561
+ if box is None:
562
+ box = [self.get_bottom(), self.get_top()]
563
+ if (len(box[0]) != 2) and (basepoints is None):
564
+ raise ValueError("Basepoints has to be specified for filtration dimension >= 3 !")
565
+ elif basepoints is None:
566
+ h = box[1][1] - box[0][1]
567
+ basepoints = np.linspace([box[0][0] - h,box[0][1]], [box[1][0],box[0][1]], num=num)
568
+ else :
569
+ num=len(basepoints)
570
+
571
+ basepoints = np.asarray(basepoints, dtype={{PYTYPE}})
572
+ cdef vector[Line[{{CTYPE}}]] cbasepoints
573
+ for i in range(num):
574
+ cbasepoints.push_back(Line[{{CTYPE}}](_py21c_{{SHORT}}(basepoints[i])))
575
+ return tuple(np.asarray(bc) for bc in self.cmod.get_barcodes2(cbasepoints, degree))
576
+
577
+ def landscape(self, degree:int, k:int=0,box:Sequence|np.ndarray|None=None, resolution:Sequence=[100,100], bool plot=False):
578
+ r"""Computes the multiparameter landscape from a PyModule. Python interface only bifiltrations.
579
+
580
+ Parameters
581
+ ----------
582
+
583
+ degree : integer
584
+ The homology degree of the landscape.
585
+ k = 0 : int
586
+ the k-th landscape
587
+ resolution = [50,50] : pair of integers
588
+ Resolution of the image.
589
+ box = None : in the format [[a,b], [c,d]]
590
+ If nontrivial, compute the landscape of this box. Default is the PyModule box.
591
+ plot = True : Boolean
592
+ If true, plots the images;
593
+ Returns
594
+ -------
595
+
596
+ The landscape of the module.
597
+
598
+ """
599
+ import matplotlib.pyplot as plt
600
+ if box is None:
601
+ box = self.get_box()
602
+ cdef Box[{{CTYPE}}] c_box = Box[{{CTYPE}}](box)
603
+ out = np.array(self.cmod.get_landscape(degree, k, c_box, resolution))
604
+ if plot:
605
+ plt.figure()
606
+ aspect = (box[1][0]-box[0][0]) / (box[1][1]-box[0][1])
607
+ extent = [box[0][0], box[1][0], box[0][1], box[1][1]]
608
+ plt.imshow(out.T, origin="lower", extent=extent, aspect=aspect)
609
+ return out
610
+
611
+ def landscapes(self, degree:int, ks:list|np.ndarray=[0],box=None, resolution:list|np.ndarray=[100,100], bool plot=False):
612
+ r"""Computes the multiparameter landscape from a PyModule. Python interface only bifiltrations.
613
+
614
+ Parameters
615
+ ----------
616
+
617
+ - degree : integer
618
+ The homology degree of the landscape.
619
+ - ks = 0 : list of int
620
+ the k-th landscape
621
+ - resolution = [50,50] : pair of integers
622
+ Resolution of the image.
623
+ - box = None : in the format [[a,b], [c,d]]
624
+ If nontrivial, compute the landscape of this box. Default is the PyModule box.
625
+ - plot = True : bool
626
+ If true, plots the images;
627
+ Returns
628
+ -------
629
+
630
+ The landscapes of the module with parameters ks.
631
+
632
+ """
633
+ import matplotlib.pyplot as plt
634
+ if box is None:
635
+ box = self.get_box()
636
+ out = np.array(self.cmod.get_landscapes(degree, ks, Box[{{CTYPE}}](box), resolution))
637
+ if plot:
638
+ to_plot = np.sum(out, axis=0)
639
+ plt.figure()
640
+ aspect = (box[1][0]-box[0][0]) / (box[1][1]-box[0][1])
641
+ extent = [box[0][0], box[1][0], box[0][1], box[1][1]]
642
+ plt.imshow(to_plot.T, origin="lower", extent=extent, aspect=aspect)
643
+ return out
644
+
645
+
646
+ def representation(self, degrees=None, double bandwidth=0.1,
647
+ resolution:Sequence[int]|int=50,
648
+ kernel:Literal["gaussian","linear","linear2","exponential"]|Callable = "gaussian",
649
+ bool signed=False,
650
+ bool normalize=False, bool plot=False,
651
+ bool save=False, int dpi=200,double p=2., box=None,
652
+ bool flatten=False, int n_jobs=0,
653
+ grid = None)->np.ndarray:
654
+ r"""Computes a representation of the module, using
655
+
656
+ [A Framework for Fast and Stable Representations of Multiparameter Persistent Homology Decompositions, Neurips2023]
657
+
658
+ Parameters
659
+ ----------
660
+
661
+ - degrees = None : integer list
662
+ If given returns only the image(s) of homology degrees `degrees`.
663
+ - bandwidth = 0.1 : float
664
+ Image parameter.
665
+ - resolution = [100,100] : pair of integers
666
+ Resolution of the image(s).
667
+ - normalize = True : Boolean
668
+ Ensures that the image belongs to [0,1].
669
+ - plot = False : Boolean
670
+ If true, plots the images;
671
+ - flatten=False :
672
+ If True, reshapes the output to a flattened shape.
673
+ - kernel: Either linear, gaussian, or callable
674
+ The kernel to apply to the matrix $(d(x,I), x \in \mathrm{pixels}, I\in \mathrm{summands})$.
675
+ signature should be : (distance matrix, summand_weights, bandwidth, p) -> representation
676
+
677
+ Returns
678
+ -------
679
+
680
+ The list of images, or the image of fixed dimension.
681
+ """
682
+ import matplotlib.pyplot as plt
683
+ # box = kwargs.get("box",[self.get_bottom(),self.get_top()])
684
+ if box is None:
685
+ box = self.get_box()
686
+ num_parameters = self.num_parameters
687
+ if degrees is None:
688
+ degrees = np.arange(self.max_degree +1)
689
+ num_degrees = len(degrees)
690
+ try:
691
+ int(resolution)
692
+ resolution = [resolution]*num_parameters
693
+ except:
694
+ pass
695
+
696
+ if grid is None:
697
+ grid = [np.linspace(*np.asarray(box)[:,parameter], num=res) for parameter, res in zip(range(num_parameters), resolution)]
698
+ else:
699
+ resolution = tuple(len(g) for g in grid)
700
+ coordinates = mpg.todense(grid)
701
+
702
+ if kernel == "linear":
703
+ assert not signed, "This kernel is not compatible with signed."
704
+ concatenated_images = self._compute_pixels(coordinates, degrees=degrees, box=box, delta=bandwidth, p=p, normalize=normalize,n_jobs=n_jobs)
705
+ else:
706
+ if kernel == "linear2":
707
+ def todo(PyModule_{{SHORT}} mod_degree):
708
+ x = mod_degree.distance_to(coordinates,signed=signed)
709
+ w = mod_degree.get_interleavings()[None]**p
710
+ s = np.where(x>=0,1,-1) if signed else 1
711
+ x = np.abs(x)
712
+ return (s*np.where(x<bandwidth,(bandwidth-x)/bandwidth,0)*w).sum(1)
713
+ elif kernel == "gaussian":
714
+ def todo(PyModule_{{SHORT}} mod_degree):
715
+ x = mod_degree.distance_to(coordinates,signed=signed)
716
+ w = mod_degree.get_interleavings()[None]**p
717
+ s = np.where(x>=0,1,-1) if signed else 1
718
+ return (s*np.exp( -.5*((x/bandwidth)**2))*w).sum(1)
719
+ elif kernel == "exponential":
720
+ def todo(PyModule_{{SHORT}} mod_degree):
721
+ x = mod_degree.distance_to(coordinates,signed=signed)
722
+ w = mod_degree.get_interleavings()[None]**p
723
+ s = np.where(x>=0,1,-1) if signed else 1
724
+ x = np.abs(x)
725
+ return (s*np.exp( -((np.abs(x)/bandwidth)))*w).sum(1)
726
+ else:
727
+ assert callable(kernel), r"""
728
+ Kernel should be
729
+ gaussian, linear, linear2, exponential or callable,
730
+ with signature
731
+ (array[shape=(N, M)], array[shape=(M)])-> array(shape=(N)),
732
+ the first argument being a distance matrix (pts) vs (summands of the module)
733
+ and the second argument is a weight vector (weight(summand) for summand in module).
734
+ Note that the distance can be signed.
735
+ """
736
+ def todo(PyModule_{{SHORT}} mod_degree):
737
+ x = mod_degree.distance_to(coordinates,signed=signed, n_jobs=n_jobs)
738
+ w = mod_degree.get_interleavings()[None]**p
739
+ return kernel(x/bandwidth,w)
740
+ concatenated_images = np.stack(
741
+ Parallel(n_jobs = n_jobs if n_jobs else -1, backend= "threading")(
742
+ delayed(todo)(self.get_module_of_degree(degree))
743
+ for degree in degrees
744
+ )
745
+ )
746
+
747
+ if flatten:
748
+ image_vector = concatenated_images.reshape((len(degrees),-1))
749
+ if plot:
750
+ raise ValueError("Unflatten to plot.")
751
+ return image_vector
752
+ else:
753
+ image_vector = concatenated_images.reshape((len(degrees),*resolution))
754
+ if plot:
755
+ assert num_parameters == 2, "Plot only available for 2-parameter modules"
756
+ import multipers.plots
757
+ i=0
758
+ n_plots = len(image_vector)
759
+ scale = 4
760
+ if n_plots >1:
761
+ fig, axs = plt.subplots(1,n_plots, figsize=(n_plots*scale,scale))
762
+ else:
763
+ fig = plt.gcf()
764
+ axs = [plt.gca()]
765
+ for image, degree, i in zip(image_vector, degrees, range(num_degrees)):
766
+ ax = axs[i]
767
+ temp = multipers.plots.plot_surface(grid, image.T, ax=ax)
768
+ plt.colorbar(temp, ax = ax)
769
+ if degree < 0 :
770
+ ax.set_title(rf"$H_{i}$ $2$-persistence image")
771
+ if degree >= 0:
772
+ ax.set_title(rf"$H_{degree}$ $2$-persistence image")
773
+ return image_vector
774
+
775
+ def euler_char(self, points:list|np.ndarray) -> np.ndarray:
776
+ r""" Computes the Euler Characteristic of the filtered complex at given (multiparameter) time
777
+
778
+ Parameters
779
+ ----------
780
+
781
+ points: list[float] | list[list[float]] | np.ndarray
782
+ List of filtration values on which to compute the euler characteristic.
783
+ WARNING FIXME : the points have to have the same dimension as the simplextree.
784
+
785
+ Returns
786
+ -------
787
+
788
+ The list of euler characteristic values
789
+ """
790
+ if len(points) == 0:
791
+ return []
792
+ if type(points[0]) is float:
793
+ points = [points]
794
+ if type(points) is np.ndarray:
795
+ assert len(points.shape) in [1,2]
796
+ if len(points.shape) == 1:
797
+ points = [points]
798
+
799
+ cdef {{CTYPE}}[:,:] points_view = np.asarray(points, dtype = {{PYTYPE}})
800
+ cdef vector[One_critical_filtration[{{CTYPE}}]] c_points = _py2v1c_{{SHORT}}(points_view)
801
+ # cdef One_critical_filtration temp
802
+ # for point in points:
803
+ # temp.clear()
804
+ # for truc in point:
805
+ # temp.push_back(<{{CTYPE}}>(truc))
806
+ # c_points.push_back(temp)
807
+ cdef Module[{{CTYPE}}] c_mod = self.cmod
808
+ with nogil:
809
+ c_euler = c_mod.euler_curve(c_points)
810
+ euler = c_euler
811
+ return np.asarray(euler, dtype=int)
812
+ def to_idx(self,grid):
813
+ cdef vector[vector[{{CTYPE}}]] cgrid = grid
814
+ cdef vector[vector[pair[vector[vector[int]],vector[vector[int]]]]] out
815
+ with nogil:
816
+ out = self.cmod.to_idx(cgrid)
817
+ return tuple(tuple((np.asarray(I.first,dtype=np.int64), np.asarray(I.second, dtype=np.int64)) for I in Is_of_degree) for Is_of_degree in out)
818
+
819
+ @cython.wraparound(False)
820
+ @cython.boundscheck(False)
821
+ def to_flat_idx(self,grid):
822
+ if len(self) == 0:
823
+ return np.empty((2,0), dtype = np.int32), np.empty((0, self.num_parameters), dtype = np.int32), np.empty((0, self.num_parameters), dtype = np.int32)
824
+ cdef vector[vector[{{CTYPE}}]] cgrid = grid
825
+ cdef vector[vector[vector[int]]] out
826
+ cdef int num_summands, num_births, num_deaths
827
+ cdef int num_parameters = self.num_parameters
828
+ with nogil:
829
+ out = self.cmod.to_flat_idx(cgrid)
830
+ num_summands = out[0][0].size()
831
+ num_births = out[1].size()
832
+ num_deaths = out[2].size()
833
+ idx = np.empty((2, num_summands),dtype=np.int32 )
834
+ births = np.empty((num_births,num_parameters),dtype=np.int32)
835
+ deaths = np.empty((num_deaths,num_parameters),dtype=np.int32)
836
+
837
+ cdef int32_t[:,:] idx_view = idx
838
+ cdef int32_t[:,:] births_view = births
839
+ cdef int32_t[:,:] deaths_view = deaths
840
+
841
+ with nogil:
842
+ for i in range(num_summands):
843
+ idx_view[0,i] = out[0][0][i]
844
+ idx_view[1,i] = out[0][1][i]
845
+ for i in range(num_births):
846
+ for j in range(num_parameters):
847
+ births_view[i,j] = out[1][i][j]
848
+ for i in range(num_deaths):
849
+ for j in range(num_parameters):
850
+ deaths_view[i,j] = out[2][i][j]
851
+
852
+ return idx, births,deaths
853
+
854
+ def distances_idx_to(self, pts, bool full=False, int n_jobs=1):
855
+ pts = np.asarray(pts)
856
+ if pts.ndim == 1:
857
+ pts = pts[None]
858
+ cdef vector[vector[{{CTYPE}}]] cpts = pts
859
+ cdef vector[vector[vector[int]]] out
860
+ with nogil:
861
+ out = self.cmod.compute_distances_idx_to(cpts,full, n_jobs)
862
+ return np.asarray(out, dtype=np.int32)
863
+
864
+ def distance_to(self, pts, bool signed=False, int n_jobs = 0)->np.ndarray:
865
+ r"""
866
+ Distance from a point to each summand's support.
867
+ Signed distance is the distance to the boundary,
868
+ with negative values inside the summands.
869
+
870
+ pts of shape (num_pts, num_parameters)
871
+
872
+ output shape : (num_pts,num_summands)
873
+ """
874
+ pts = np.asarray(pts)
875
+ if pts.ndim == 1:
876
+ pts = pts[None]
877
+ assert pts.shape[-1] == self.num_parameters
878
+ cdef vector[vector[{{CTYPE}}]] cpts = pts
879
+ # cdef vector[vector[{{CTYPE}}]] out
880
+ to_fill = np.empty(shape=(cpts.size(),len(self)), dtype = {{PYTYPE}})
881
+ cdef {{CTYPE}}[:,:] c_to_fill = to_fill
882
+ cdef {{CTYPE}}* data_ptr = &c_to_fill[0,0]
883
+ with nogil:
884
+ self.cmod.compute_distances_to(data_ptr, cpts,signed, n_jobs)
885
+ return to_fill
886
+
887
+ def get_interleavings(self,box=None):
888
+ if box is None:
889
+ box = self.get_box()
890
+ cdef Box[{{CTYPE}}] cbox = Box[{{CTYPE}}](box)
891
+ return np.asarray(self.cmod.get_interleavings(cbox))
892
+
893
+ cdef class PyMultiDiagramPoint_{{SHORT}}:
894
+ cdef MultiDiagram_point[One_critical_filtration[{{CTYPE}}]] point
895
+ cdef set(self, MultiDiagram_point[One_critical_filtration[{{CTYPE}}]] pt):
896
+ self.point = pt
897
+ return self
898
+
899
+ def get_degree(self):
900
+ return self.point.get_dimension()
901
+ def get_birth(self):
902
+ cdef One_critical_filtration[{{CTYPE}}] v = self.point.get_birth()
903
+ return _ff21cview_{{SHORT}}(&v, copy=True)
904
+ def get_death(self):
905
+ cdef One_critical_filtration[{{CTYPE}}] v = self.point.get_death()
906
+ return _ff21cview_{{SHORT}}(&v, copy=True)
907
+
908
+
909
+ cdef class PyMultiDiagram_{{SHORT}}:
910
+ r"""
911
+ Stores the diagram of a PyModule on a line
912
+ """
913
+ cdef MultiDiagram[One_critical_filtration[{{CTYPE}}], {{CTYPE}}] multiDiagram
914
+ cdef set(self, MultiDiagram[One_critical_filtration[{{CTYPE}}], {{CTYPE}}] m):
915
+ self.multiDiagram = m
916
+ return self
917
+ def get_points(self, degree:int=-1) -> np.ndarray:
918
+ cdef vector[pair[vector[{{CTYPE}}],vector[{{CTYPE}}]]] out = self.multiDiagram.get_points(degree)
919
+ if len(out) == 0 and len(self) == 0:
920
+ return np.empty() # TODO Retrieve good number of parameters if there is no points in diagram
921
+ if len(out) == 0:
922
+ return np.empty((0,2,self.multiDiagram.at(0).get_dimension())) # gets the number of parameters
923
+ return np.array(out)
924
+ def to_multipers(self, dimension:int):
925
+ return self.multiDiagram.to_multipers(dimension)
926
+ def __len__(self) -> int:
927
+ return self.multiDiagram.size()
928
+ def __getitem__(self,i:int) -> PyMultiDiagramPoint_{{SHORT}}:
929
+ return PyMultiDiagramPoint_{{SHORT}}().set(self.multiDiagram.at(i % self.multiDiagram.size()))
930
+ cdef class PyMultiDiagrams_{{SHORT}}:
931
+ """
932
+ Stores the barcodes of a PyModule on multiple lines
933
+ """
934
+ cdef MultiDiagrams[One_critical_filtration[{{CTYPE}}], {{CTYPE}}] multiDiagrams
935
+ cdef set(self,MultiDiagrams[One_critical_filtration[{{CTYPE}}], {{CTYPE}}] m):
936
+ self.multiDiagrams = m
937
+ return self
938
+ def to_multipers(self):
939
+ out = self.multiDiagrams.to_multipers()
940
+ return [np.asarray(summand) for summand in out]
941
+ def __getitem__(self,i:int):
942
+ if i >=0 :
943
+ return PyMultiDiagram_{{SHORT}}().set(self.multiDiagrams.at(i))
944
+ else:
945
+ return PyMultiDiagram_{{SHORT}}().set(self.multiDiagrams.at( self.multiDiagrams.size() - i))
946
+ def __len__(self):
947
+ return self.multiDiagrams.size()
948
+ def get_points(self, degree:int=-1):
949
+ return self.multiDiagrams.get_points()
950
+ cdef _get_plot_bars(self, dimension:int=-1, min_persistence:float=0):
951
+ return self.multiDiagrams._for_python_plot(dimension, min_persistence);
952
+ def plot(self, degree:int=-1, min_persistence:float=0):
953
+ """
954
+ Plots the barcodes.
955
+
956
+ Parameters
957
+ ----------
958
+
959
+ - degree:int=-1
960
+ Only plots the bars of specified homology degree. Useful when the multidiagrams contains multiple dimenions
961
+ - min_persistence:float=0
962
+ Only plot bars of length greater than this value. Useful to reduce the time to plot.
963
+
964
+ Warning
965
+ -------
966
+
967
+ If the barcodes are not thresholded, essential barcodes will not be displayed !
968
+
969
+ """
970
+ from cycler import cycler
971
+ import matplotlib
972
+ import matplotlib.pyplot as plt
973
+ if len(self) == 0: return
974
+ _cmap = matplotlib.colormaps["Spectral"]
975
+ multibarcodes_, colors = self._get_plot_bars(degree, min_persistence)
976
+ n_summands = np.max(colors)+1 if len(colors)>0 else 1
977
+
978
+ plt.rc('axes', prop_cycle = cycler('color', [_cmap(i/n_summands) for i in colors]))
979
+ return plt.plot(*multibarcodes_)
980
+ {{endif}}
981
+
982
+
983
+ cdef dump_summand_{{SHORT}}(Summand[{{CTYPE}}]& summand):
984
+ cdef vector[One_critical_filtration[{{CTYPE}}]] births = summand.get_birth_list()
985
+ cdef vector[One_critical_filtration[{{CTYPE}}]] deaths = summand.get_death_list()
986
+ return (
987
+ _vff21cview_{{SHORT}}(births, copy=True), ## copy as local variables
988
+ _vff21cview_{{SHORT}}(deaths, copy=True),
989
+ summand.get_dimension(),
990
+ )
991
+
992
+ cdef inline Summand[{{CTYPE}}] summand_from_dump_{{SHORT}}(summand_dump):
993
+ cdef vector[One_critical_filtration[{{CTYPE}}]] births = _py2v1c_{{SHORT}}(summand_dump[0])
994
+ cdef vector[One_critical_filtration[{{CTYPE}}]] deaths = _py2v1c_{{SHORT}}(summand_dump[1])
995
+ cdef int dim = summand_dump[2]
996
+ return Summand[{{CTYPE}}](births,deaths,dim)
997
+
998
+ cdef dump_cmod_{{SHORT}}(Module[{{CTYPE}}]& mod):
999
+ cdef Box[{{CTYPE}}] cbox = mod.get_box()
1000
+ cdef int dim = mod.get_dimension()
1001
+ cdef cnp.ndarray[{{CTYPE}}, ndim=1] bottom_corner = _ff21cview_{{SHORT}}(&cbox.get_lower_corner())
1002
+ cdef cnp.ndarray[{{CTYPE}}, ndim=1] top_corner = _ff21cview_{{SHORT}}(&cbox.get_upper_corner())
1003
+ box = np.asarray([bottom_corner, top_corner])
1004
+ summands = tuple(dump_summand_{{SHORT}}(summand) for summand in mod)
1005
+ return box, summands
1006
+
1007
+ cdef Module[{{CTYPE}}] cmod_from_dump_{{SHORT}}(module_dump):
1008
+ box = module_dump[0]
1009
+ summands = module_dump[1]
1010
+ cdef Module[{{CTYPE}}] out_module = Module[{{CTYPE}}]()
1011
+ out_module.set_box(Box[{{CTYPE}}](box))
1012
+ for i in range(len(summands)):
1013
+ out_module.add_summand(summand_from_dump_{{SHORT}}(summands[i]))
1014
+ return out_module
1015
+
1016
+
1017
+ def from_dump_{{SHORT}}(dump)->PyModule_{{SHORT}}:
1018
+ r"""Retrieves a PyModule from a previous dump.
1019
+
1020
+ Parameters
1021
+ ----------
1022
+
1023
+ dump: either the output of the dump function, or a file containing the output of a dump.
1024
+ The dumped module to retrieve
1025
+
1026
+ Returns
1027
+ -------
1028
+
1029
+ PyModule
1030
+ The retrieved module.
1031
+ """
1032
+ # TODO : optimize...
1033
+ mod = PyModule_{{SHORT}}()
1034
+ if type(dump) is str:
1035
+ dump = pickle.load(open(dump, "rb"))
1036
+ cdef Module[{{CTYPE}}] cmod = cmod_from_dump_{{SHORT}}(dump)
1037
+ mod.cmod = cmod
1038
+ return mod
1039
+
1040
+
1041
+ {{endfor}}
1042
+
1043
+
1044
+ global PyModule_type, PySummand_type
1045
+ PyModule_type = Union[
1046
+ {{for CTYPE, PYTYPE, SHORT in value_types}}
1047
+ PyModule_{{SHORT}},
1048
+ {{endfor}}
1049
+ ]
1050
+ PySummand_type = Union[
1051
+ {{for CTYPE, PYTYPE, SHORT in value_types}}
1052
+ PySummand_{{SHORT}},
1053
+ {{endfor}}
1054
+ ]
1055
+
1056
+ PyBox_type = Union[
1057
+ {{for CTYPE, PYTYPE, SHORT in value_types}}
1058
+ PyBox_{{SHORT}},
1059
+ {{endfor}}
1060
+ ]
1061
+
1062
+
1063
+ PyMultiDiagram_type = Union[
1064
+ {{for CTYPE, PYTYPE, SHORT in value_types}}
1065
+ {{if SHORT[0] == 'f'}}
1066
+ PyMultiDiagram_{{SHORT}},
1067
+ {{endif}}
1068
+ {{endfor}}
1069
+ ]
1070
+
1071
+
1072
+ PyMultiDiagrams_type = Union[
1073
+ {{for CTYPE, PYTYPE, SHORT in value_types}}
1074
+ {{if SHORT[0] == 'f'}}
1075
+ PyMultiDiagrams_{{SHORT}},
1076
+ {{endif}}
1077
+ {{endfor}}
1078
+ ]
1079
+
1080
+ def is_mma(stuff):
1081
+ return (False
1082
+ {{for CTYPE, PYTYPE, SHORT in value_types}}
1083
+ or isinstance(stuff,PyModule_{{SHORT}})
1084
+ {{endfor}}
1085
+ )