multipers 2.2.3__cp311-cp311-win_amd64.whl → 2.3.1__cp311-cp311-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

Files changed (182) hide show
  1. multipers/__init__.py +33 -31
  2. multipers/_signed_measure_meta.py +430 -430
  3. multipers/_slicer_meta.py +211 -212
  4. multipers/data/MOL2.py +458 -458
  5. multipers/data/UCR.py +18 -18
  6. multipers/data/graphs.py +466 -466
  7. multipers/data/immuno_regions.py +27 -27
  8. multipers/data/pytorch2simplextree.py +90 -90
  9. multipers/data/shape3d.py +101 -101
  10. multipers/data/synthetic.py +113 -111
  11. multipers/distances.py +198 -198
  12. multipers/filtration_conversions.pxd.tp +84 -84
  13. multipers/filtrations/__init__.py +18 -0
  14. multipers/{ml/convolutions.py → filtrations/density.py} +563 -520
  15. multipers/filtrations/filtrations.py +289 -0
  16. multipers/filtrations.pxd +224 -224
  17. multipers/function_rips.cp311-win_amd64.pyd +0 -0
  18. multipers/function_rips.pyx +105 -105
  19. multipers/grids.cp311-win_amd64.pyd +0 -0
  20. multipers/grids.pyx +350 -350
  21. multipers/gudhi/Persistence_slices_interface.h +132 -132
  22. multipers/gudhi/Simplex_tree_interface.h +239 -245
  23. multipers/gudhi/Simplex_tree_multi_interface.h +516 -561
  24. multipers/gudhi/cubical_to_boundary.h +59 -59
  25. multipers/gudhi/gudhi/Bitmap_cubical_complex.h +450 -450
  26. multipers/gudhi/gudhi/Bitmap_cubical_complex_base.h +1070 -1070
  27. multipers/gudhi/gudhi/Bitmap_cubical_complex_periodic_boundary_conditions_base.h +579 -579
  28. multipers/gudhi/gudhi/Debug_utils.h +45 -45
  29. multipers/gudhi/gudhi/Fields/Multi_field.h +484 -484
  30. multipers/gudhi/gudhi/Fields/Multi_field_operators.h +455 -455
  31. multipers/gudhi/gudhi/Fields/Multi_field_shared.h +450 -450
  32. multipers/gudhi/gudhi/Fields/Multi_field_small.h +531 -531
  33. multipers/gudhi/gudhi/Fields/Multi_field_small_operators.h +507 -507
  34. multipers/gudhi/gudhi/Fields/Multi_field_small_shared.h +531 -531
  35. multipers/gudhi/gudhi/Fields/Z2_field.h +355 -355
  36. multipers/gudhi/gudhi/Fields/Z2_field_operators.h +376 -376
  37. multipers/gudhi/gudhi/Fields/Zp_field.h +420 -420
  38. multipers/gudhi/gudhi/Fields/Zp_field_operators.h +400 -400
  39. multipers/gudhi/gudhi/Fields/Zp_field_shared.h +418 -418
  40. multipers/gudhi/gudhi/Flag_complex_edge_collapser.h +337 -337
  41. multipers/gudhi/gudhi/Matrix.h +2107 -2107
  42. multipers/gudhi/gudhi/Multi_critical_filtration.h +1038 -1038
  43. multipers/gudhi/gudhi/Multi_persistence/Box.h +171 -171
  44. multipers/gudhi/gudhi/Multi_persistence/Line.h +282 -282
  45. multipers/gudhi/gudhi/Off_reader.h +173 -173
  46. multipers/gudhi/gudhi/One_critical_filtration.h +1433 -1431
  47. multipers/gudhi/gudhi/Persistence_matrix/Base_matrix.h +769 -769
  48. multipers/gudhi/gudhi/Persistence_matrix/Base_matrix_with_column_compression.h +686 -686
  49. multipers/gudhi/gudhi/Persistence_matrix/Boundary_matrix.h +842 -842
  50. multipers/gudhi/gudhi/Persistence_matrix/Chain_matrix.h +1350 -1350
  51. multipers/gudhi/gudhi/Persistence_matrix/Id_to_index_overlay.h +1105 -1105
  52. multipers/gudhi/gudhi/Persistence_matrix/Position_to_index_overlay.h +859 -859
  53. multipers/gudhi/gudhi/Persistence_matrix/RU_matrix.h +910 -910
  54. multipers/gudhi/gudhi/Persistence_matrix/allocators/entry_constructors.h +139 -139
  55. multipers/gudhi/gudhi/Persistence_matrix/base_pairing.h +230 -230
  56. multipers/gudhi/gudhi/Persistence_matrix/base_swap.h +211 -211
  57. multipers/gudhi/gudhi/Persistence_matrix/boundary_cell_position_to_id_mapper.h +60 -60
  58. multipers/gudhi/gudhi/Persistence_matrix/boundary_face_position_to_id_mapper.h +60 -60
  59. multipers/gudhi/gudhi/Persistence_matrix/chain_pairing.h +136 -136
  60. multipers/gudhi/gudhi/Persistence_matrix/chain_rep_cycles.h +190 -190
  61. multipers/gudhi/gudhi/Persistence_matrix/chain_vine_swap.h +616 -616
  62. multipers/gudhi/gudhi/Persistence_matrix/columns/chain_column_extra_properties.h +150 -150
  63. multipers/gudhi/gudhi/Persistence_matrix/columns/column_dimension_holder.h +106 -106
  64. multipers/gudhi/gudhi/Persistence_matrix/columns/column_utilities.h +219 -219
  65. multipers/gudhi/gudhi/Persistence_matrix/columns/entry_types.h +327 -327
  66. multipers/gudhi/gudhi/Persistence_matrix/columns/heap_column.h +1140 -1140
  67. multipers/gudhi/gudhi/Persistence_matrix/columns/intrusive_list_column.h +934 -934
  68. multipers/gudhi/gudhi/Persistence_matrix/columns/intrusive_set_column.h +934 -934
  69. multipers/gudhi/gudhi/Persistence_matrix/columns/list_column.h +980 -980
  70. multipers/gudhi/gudhi/Persistence_matrix/columns/naive_vector_column.h +1092 -1092
  71. multipers/gudhi/gudhi/Persistence_matrix/columns/row_access.h +192 -192
  72. multipers/gudhi/gudhi/Persistence_matrix/columns/set_column.h +921 -921
  73. multipers/gudhi/gudhi/Persistence_matrix/columns/small_vector_column.h +1093 -1093
  74. multipers/gudhi/gudhi/Persistence_matrix/columns/unordered_set_column.h +1012 -1012
  75. multipers/gudhi/gudhi/Persistence_matrix/columns/vector_column.h +1244 -1244
  76. multipers/gudhi/gudhi/Persistence_matrix/matrix_dimension_holders.h +186 -186
  77. multipers/gudhi/gudhi/Persistence_matrix/matrix_row_access.h +164 -164
  78. multipers/gudhi/gudhi/Persistence_matrix/ru_pairing.h +156 -156
  79. multipers/gudhi/gudhi/Persistence_matrix/ru_rep_cycles.h +376 -376
  80. multipers/gudhi/gudhi/Persistence_matrix/ru_vine_swap.h +540 -540
  81. multipers/gudhi/gudhi/Persistent_cohomology/Field_Zp.h +118 -118
  82. multipers/gudhi/gudhi/Persistent_cohomology/Multi_field.h +173 -173
  83. multipers/gudhi/gudhi/Persistent_cohomology/Persistent_cohomology_column.h +128 -128
  84. multipers/gudhi/gudhi/Persistent_cohomology.h +745 -745
  85. multipers/gudhi/gudhi/Points_off_io.h +171 -171
  86. multipers/gudhi/gudhi/Simple_object_pool.h +69 -69
  87. multipers/gudhi/gudhi/Simplex_tree/Simplex_tree_iterators.h +463 -463
  88. multipers/gudhi/gudhi/Simplex_tree/Simplex_tree_node_explicit_storage.h +83 -83
  89. multipers/gudhi/gudhi/Simplex_tree/Simplex_tree_siblings.h +106 -106
  90. multipers/gudhi/gudhi/Simplex_tree/Simplex_tree_star_simplex_iterators.h +277 -277
  91. multipers/gudhi/gudhi/Simplex_tree/hooks_simplex_base.h +62 -62
  92. multipers/gudhi/gudhi/Simplex_tree/indexing_tag.h +27 -27
  93. multipers/gudhi/gudhi/Simplex_tree/serialization_utils.h +62 -62
  94. multipers/gudhi/gudhi/Simplex_tree/simplex_tree_options.h +157 -157
  95. multipers/gudhi/gudhi/Simplex_tree.h +2794 -2794
  96. multipers/gudhi/gudhi/Simplex_tree_multi.h +152 -163
  97. multipers/gudhi/gudhi/distance_functions.h +62 -62
  98. multipers/gudhi/gudhi/graph_simplicial_complex.h +104 -104
  99. multipers/gudhi/gudhi/persistence_interval.h +253 -253
  100. multipers/gudhi/gudhi/persistence_matrix_options.h +170 -170
  101. multipers/gudhi/gudhi/reader_utils.h +367 -367
  102. multipers/gudhi/mma_interface_coh.h +256 -255
  103. multipers/gudhi/mma_interface_h0.h +223 -231
  104. multipers/gudhi/mma_interface_matrix.h +291 -282
  105. multipers/gudhi/naive_merge_tree.h +536 -575
  106. multipers/gudhi/scc_io.h +310 -289
  107. multipers/gudhi/truc.h +957 -888
  108. multipers/io.cp311-win_amd64.pyd +0 -0
  109. multipers/io.pyx +714 -711
  110. multipers/ml/accuracies.py +90 -90
  111. multipers/ml/invariants_with_persistable.py +79 -79
  112. multipers/ml/kernels.py +176 -176
  113. multipers/ml/mma.py +713 -714
  114. multipers/ml/one.py +472 -472
  115. multipers/ml/point_clouds.py +352 -346
  116. multipers/ml/signed_measures.py +1589 -1589
  117. multipers/ml/sliced_wasserstein.py +461 -461
  118. multipers/ml/tools.py +113 -113
  119. multipers/mma_structures.cp311-win_amd64.pyd +0 -0
  120. multipers/mma_structures.pxd +127 -127
  121. multipers/mma_structures.pyx +4 -8
  122. multipers/mma_structures.pyx.tp +1083 -1085
  123. multipers/multi_parameter_rank_invariant/diff_helpers.h +84 -93
  124. multipers/multi_parameter_rank_invariant/euler_characteristic.h +97 -97
  125. multipers/multi_parameter_rank_invariant/function_rips.h +322 -322
  126. multipers/multi_parameter_rank_invariant/hilbert_function.h +769 -769
  127. multipers/multi_parameter_rank_invariant/persistence_slices.h +148 -148
  128. multipers/multi_parameter_rank_invariant/rank_invariant.h +369 -369
  129. multipers/multiparameter_edge_collapse.py +41 -41
  130. multipers/multiparameter_module_approximation/approximation.h +2298 -2295
  131. multipers/multiparameter_module_approximation/combinatory.h +129 -129
  132. multipers/multiparameter_module_approximation/debug.h +107 -107
  133. multipers/multiparameter_module_approximation/format_python-cpp.h +286 -286
  134. multipers/multiparameter_module_approximation/heap_column.h +238 -238
  135. multipers/multiparameter_module_approximation/images.h +79 -79
  136. multipers/multiparameter_module_approximation/list_column.h +174 -174
  137. multipers/multiparameter_module_approximation/list_column_2.h +232 -232
  138. multipers/multiparameter_module_approximation/ru_matrix.h +347 -347
  139. multipers/multiparameter_module_approximation/set_column.h +135 -135
  140. multipers/multiparameter_module_approximation/structure_higher_dim_barcode.h +36 -36
  141. multipers/multiparameter_module_approximation/unordered_set_column.h +166 -166
  142. multipers/multiparameter_module_approximation/utilities.h +403 -419
  143. multipers/multiparameter_module_approximation/vector_column.h +223 -223
  144. multipers/multiparameter_module_approximation/vector_matrix.h +331 -331
  145. multipers/multiparameter_module_approximation/vineyards.h +464 -464
  146. multipers/multiparameter_module_approximation/vineyards_trajectories.h +649 -649
  147. multipers/multiparameter_module_approximation.cp311-win_amd64.pyd +0 -0
  148. multipers/multiparameter_module_approximation.pyx +218 -217
  149. multipers/pickle.py +90 -53
  150. multipers/plots.py +342 -334
  151. multipers/point_measure.cp311-win_amd64.pyd +0 -0
  152. multipers/point_measure.pyx +322 -320
  153. multipers/simplex_tree_multi.cp311-win_amd64.pyd +0 -0
  154. multipers/simplex_tree_multi.pxd +133 -133
  155. multipers/simplex_tree_multi.pyx +115 -48
  156. multipers/simplex_tree_multi.pyx.tp +1947 -1935
  157. multipers/slicer.cp311-win_amd64.pyd +0 -0
  158. multipers/slicer.pxd +301 -120
  159. multipers/slicer.pxd.tp +218 -214
  160. multipers/slicer.pyx +1570 -507
  161. multipers/slicer.pyx.tp +931 -914
  162. multipers/tensor/tensor.h +672 -672
  163. multipers/tensor.pxd +13 -13
  164. multipers/test.pyx +44 -44
  165. multipers/tests/__init__.py +57 -57
  166. multipers/torch/diff_grids.py +217 -217
  167. multipers/torch/rips_density.py +310 -304
  168. {multipers-2.2.3.dist-info → multipers-2.3.1.dist-info}/LICENSE +21 -21
  169. {multipers-2.2.3.dist-info → multipers-2.3.1.dist-info}/METADATA +21 -11
  170. multipers-2.3.1.dist-info/RECORD +182 -0
  171. {multipers-2.2.3.dist-info → multipers-2.3.1.dist-info}/WHEEL +1 -1
  172. multipers/tests/test_diff_helper.py +0 -73
  173. multipers/tests/test_hilbert_function.py +0 -82
  174. multipers/tests/test_mma.py +0 -83
  175. multipers/tests/test_point_clouds.py +0 -49
  176. multipers/tests/test_python-cpp_conversion.py +0 -82
  177. multipers/tests/test_signed_betti.py +0 -181
  178. multipers/tests/test_signed_measure.py +0 -89
  179. multipers/tests/test_simplextreemulti.py +0 -221
  180. multipers/tests/test_slicer.py +0 -221
  181. multipers-2.2.3.dist-info/RECORD +0 -189
  182. {multipers-2.2.3.dist-info → multipers-2.3.1.dist-info}/top_level.txt +0 -0
@@ -1,1085 +1,1083 @@
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
+ aspect = (box[1][0]-box[0][0]) / (box[1][1]-box[0][1])
606
+ extent = [box[0][0], box[1][0], box[0][1], box[1][1]]
607
+ plt.imshow(out.T, origin="lower", extent=extent, aspect=aspect)
608
+ return out
609
+
610
+ def landscapes(self, degree:int, ks:list|np.ndarray=[0],box=None, resolution:list|np.ndarray=[100,100], bool plot=False):
611
+ r"""Computes the multiparameter landscape from a PyModule. Python interface only bifiltrations.
612
+
613
+ Parameters
614
+ ----------
615
+
616
+ - degree : integer
617
+ The homology degree of the landscape.
618
+ - ks = 0 : list of int
619
+ the k-th landscape
620
+ - resolution = [50,50] : pair of integers
621
+ Resolution of the image.
622
+ - box = None : in the format [[a,b], [c,d]]
623
+ If nontrivial, compute the landscape of this box. Default is the PyModule box.
624
+ - plot = True : bool
625
+ If true, plots the images;
626
+ Returns
627
+ -------
628
+
629
+ The landscapes of the module with parameters ks.
630
+
631
+ """
632
+ import matplotlib.pyplot as plt
633
+ if box is None:
634
+ box = self.get_box()
635
+ out = np.array(self.cmod.get_landscapes(degree, ks, Box[{{CTYPE}}](box), resolution))
636
+ if plot:
637
+ to_plot = np.sum(out, axis=0)
638
+ aspect = (box[1][0]-box[0][0]) / (box[1][1]-box[0][1])
639
+ extent = [box[0][0], box[1][0], box[0][1], box[1][1]]
640
+ plt.imshow(to_plot.T, origin="lower", extent=extent, aspect=aspect)
641
+ return out
642
+
643
+
644
+ def representation(self, degrees=None, double bandwidth=0.1,
645
+ resolution:Sequence[int]|int=50,
646
+ kernel:Literal["gaussian","linear","linear2","exponential"]|Callable = "gaussian",
647
+ bool signed=False,
648
+ bool normalize=False, bool plot=False,
649
+ bool save=False, int dpi=200,double p=2., box=None,
650
+ bool flatten=False, int n_jobs=0,
651
+ grid = None)->np.ndarray:
652
+ r"""Computes a representation of the module, using
653
+
654
+ [A Framework for Fast and Stable Representations of Multiparameter Persistent Homology Decompositions, Neurips2023]
655
+
656
+ Parameters
657
+ ----------
658
+
659
+ - degrees = None : integer list
660
+ If given returns only the image(s) of homology degrees `degrees`.
661
+ - bandwidth = 0.1 : float
662
+ Image parameter.
663
+ - resolution = [100,100] : pair of integers
664
+ Resolution of the image(s).
665
+ - normalize = True : Boolean
666
+ Ensures that the image belongs to [0,1].
667
+ - plot = False : Boolean
668
+ If true, plots the images;
669
+ - flatten=False :
670
+ If True, reshapes the output to a flattened shape.
671
+ - kernel: Either linear, gaussian, or callable
672
+ The kernel to apply to the matrix $(d(x,I), x \in \mathrm{pixels}, I\in \mathrm{summands})$.
673
+ signature should be : (distance matrix, summand_weights, bandwidth, p) -> representation
674
+
675
+ Returns
676
+ -------
677
+
678
+ The list of images, or the image of fixed dimension.
679
+ """
680
+ import matplotlib.pyplot as plt
681
+ # box = kwargs.get("box",[self.get_bottom(),self.get_top()])
682
+ if box is None:
683
+ box = self.get_box()
684
+ num_parameters = self.num_parameters
685
+ if degrees is None:
686
+ degrees = np.arange(self.max_degree +1)
687
+ num_degrees = len(degrees)
688
+ try:
689
+ int(resolution)
690
+ resolution = [resolution]*num_parameters
691
+ except:
692
+ pass
693
+
694
+ if grid is None:
695
+ grid = [np.linspace(*np.asarray(box)[:,parameter], num=res) for parameter, res in zip(range(num_parameters), resolution)]
696
+ else:
697
+ resolution = tuple(len(g) for g in grid)
698
+ coordinates = mpg.todense(grid)
699
+
700
+ if kernel == "linear":
701
+ assert not signed, "This kernel is not compatible with signed."
702
+ concatenated_images = self._compute_pixels(coordinates, degrees=degrees, box=box, delta=bandwidth, p=p, normalize=normalize,n_jobs=n_jobs)
703
+ else:
704
+ if kernel == "linear2":
705
+ def todo(PyModule_{{SHORT}} mod_degree):
706
+ x = mod_degree.distance_to(coordinates,signed=signed)
707
+ w = mod_degree.get_interleavings()[None]**p
708
+ s = np.where(x>=0,1,-1) if signed else 1
709
+ x = np.abs(x)
710
+ return (s*np.where(x<bandwidth,(bandwidth-x)/bandwidth,0)*w).sum(1)
711
+ elif kernel == "gaussian":
712
+ def todo(PyModule_{{SHORT}} mod_degree):
713
+ x = mod_degree.distance_to(coordinates,signed=signed)
714
+ w = mod_degree.get_interleavings()[None]**p
715
+ s = np.where(x>=0,1,-1) if signed else 1
716
+ return (s*np.exp( -.5*((x/bandwidth)**2))*w).sum(1)
717
+ elif kernel == "exponential":
718
+ def todo(PyModule_{{SHORT}} mod_degree):
719
+ x = mod_degree.distance_to(coordinates,signed=signed)
720
+ w = mod_degree.get_interleavings()[None]**p
721
+ s = np.where(x>=0,1,-1) if signed else 1
722
+ x = np.abs(x)
723
+ return (s*np.exp( -((np.abs(x)/bandwidth)))*w).sum(1)
724
+ else:
725
+ assert callable(kernel), r"""
726
+ Kernel should be
727
+ gaussian, linear, linear2, exponential or callable,
728
+ with signature
729
+ (array[shape=(N, M)], array[shape=(M)])-> array(shape=(N)),
730
+ the first argument being a distance matrix (pts) vs (summands of the module)
731
+ and the second argument is a weight vector (weight(summand) for summand in module).
732
+ Note that the distance can be signed.
733
+ """
734
+ def todo(PyModule_{{SHORT}} mod_degree):
735
+ x = mod_degree.distance_to(coordinates,signed=signed, n_jobs=n_jobs)
736
+ w = mod_degree.get_interleavings()[None]**p
737
+ return kernel(x/bandwidth,w)
738
+ concatenated_images = np.stack(
739
+ Parallel(n_jobs = n_jobs if n_jobs else -1, backend= "threading")(
740
+ delayed(todo)(self.get_module_of_degree(degree))
741
+ for degree in degrees
742
+ )
743
+ )
744
+
745
+ if flatten:
746
+ image_vector = concatenated_images.reshape((len(degrees),-1))
747
+ if plot:
748
+ raise ValueError("Unflatten to plot.")
749
+ return image_vector
750
+ else:
751
+ image_vector = concatenated_images.reshape((len(degrees),*resolution))
752
+ if plot:
753
+ assert num_parameters == 2, "Plot only available for 2-parameter modules"
754
+ import multipers.plots
755
+ i=0
756
+ n_plots = len(image_vector)
757
+ scale = 4
758
+ if n_plots >1:
759
+ fig, axs = plt.subplots(1,n_plots, figsize=(n_plots*scale,scale))
760
+ else:
761
+ fig = plt.gcf()
762
+ axs = [plt.gca()]
763
+ for image, degree, i in zip(image_vector, degrees, range(num_degrees)):
764
+ ax = axs[i]
765
+ temp = multipers.plots.plot_surface(grid, image.T, ax=ax)
766
+ plt.colorbar(temp, ax = ax)
767
+ if degree < 0 :
768
+ ax.set_title(rf"$H_{i}$ $2$-persistence image")
769
+ if degree >= 0:
770
+ ax.set_title(rf"$H_{degree}$ $2$-persistence image")
771
+ return image_vector
772
+
773
+ def euler_char(self, points:list|np.ndarray) -> np.ndarray:
774
+ r""" Computes the Euler Characteristic of the filtered complex at given (multiparameter) time
775
+
776
+ Parameters
777
+ ----------
778
+
779
+ points: list[float] | list[list[float]] | np.ndarray
780
+ List of filtration values on which to compute the euler characteristic.
781
+ WARNING FIXME : the points have to have the same dimension as the simplextree.
782
+
783
+ Returns
784
+ -------
785
+
786
+ The list of euler characteristic values
787
+ """
788
+ if len(points) == 0:
789
+ return []
790
+ if type(points[0]) is float:
791
+ points = [points]
792
+ if type(points) is np.ndarray:
793
+ assert len(points.shape) in [1,2]
794
+ if len(points.shape) == 1:
795
+ points = [points]
796
+
797
+ cdef {{CTYPE}}[:,:] points_view = np.asarray(points, dtype = {{PYTYPE}})
798
+ cdef vector[One_critical_filtration[{{CTYPE}}]] c_points = _py2v1c_{{SHORT}}(points_view)
799
+ # cdef One_critical_filtration temp
800
+ # for point in points:
801
+ # temp.clear()
802
+ # for truc in point:
803
+ # temp.push_back(<{{CTYPE}}>(truc))
804
+ # c_points.push_back(temp)
805
+ cdef Module[{{CTYPE}}] c_mod = self.cmod
806
+ with nogil:
807
+ c_euler = c_mod.euler_curve(c_points)
808
+ euler = c_euler
809
+ return np.asarray(euler, dtype=int)
810
+ def to_idx(self,grid):
811
+ cdef vector[vector[{{CTYPE}}]] cgrid = grid
812
+ cdef vector[vector[pair[vector[vector[int]],vector[vector[int]]]]] out
813
+ with nogil:
814
+ out = self.cmod.to_idx(cgrid)
815
+ 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)
816
+
817
+ @cython.wraparound(False)
818
+ @cython.boundscheck(False)
819
+ def to_flat_idx(self,grid):
820
+ if len(self) == 0:
821
+ 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)
822
+ cdef vector[vector[{{CTYPE}}]] cgrid = grid
823
+ cdef vector[vector[vector[int]]] out
824
+ cdef int num_summands, num_births, num_deaths
825
+ cdef int num_parameters = self.num_parameters
826
+ with nogil:
827
+ out = self.cmod.to_flat_idx(cgrid)
828
+ num_summands = out[0][0].size()
829
+ num_births = out[1].size()
830
+ num_deaths = out[2].size()
831
+ idx = np.empty((2, num_summands),dtype=np.int32 )
832
+ births = np.empty((num_births,num_parameters),dtype=np.int32)
833
+ deaths = np.empty((num_deaths,num_parameters),dtype=np.int32)
834
+
835
+ cdef int32_t[:,:] idx_view = idx
836
+ cdef int32_t[:,:] births_view = births
837
+ cdef int32_t[:,:] deaths_view = deaths
838
+
839
+ with nogil:
840
+ for i in range(num_summands):
841
+ idx_view[0,i] = out[0][0][i]
842
+ idx_view[1,i] = out[0][1][i]
843
+ for i in range(num_births):
844
+ for j in range(num_parameters):
845
+ births_view[i,j] = out[1][i][j]
846
+ for i in range(num_deaths):
847
+ for j in range(num_parameters):
848
+ deaths_view[i,j] = out[2][i][j]
849
+
850
+ return idx, births,deaths
851
+
852
+ def distances_idx_to(self, pts, bool full=False, int n_jobs=1):
853
+ pts = np.asarray(pts)
854
+ if pts.ndim == 1:
855
+ pts = pts[None]
856
+ cdef vector[vector[{{CTYPE}}]] cpts = pts
857
+ cdef vector[vector[vector[int]]] out
858
+ with nogil:
859
+ out = self.cmod.compute_distances_idx_to(cpts,full, n_jobs)
860
+ return np.asarray(out, dtype=np.int32)
861
+
862
+ def distance_to(self, pts, bool signed=False, int n_jobs = 0)->np.ndarray:
863
+ r"""
864
+ Distance from a point to each summand's support.
865
+ Signed distance is the distance to the boundary,
866
+ with negative values inside the summands.
867
+
868
+ pts of shape (num_pts, num_parameters)
869
+
870
+ output shape : (num_pts,num_summands)
871
+ """
872
+ pts = np.asarray(pts)
873
+ if pts.ndim == 1:
874
+ pts = pts[None]
875
+ assert pts.shape[-1] == self.num_parameters
876
+ cdef vector[vector[{{CTYPE}}]] cpts = pts
877
+ # cdef vector[vector[{{CTYPE}}]] out
878
+ to_fill = np.empty(shape=(cpts.size(),len(self)), dtype = {{PYTYPE}})
879
+ cdef {{CTYPE}}[:,:] c_to_fill = to_fill
880
+ cdef {{CTYPE}}* data_ptr = &c_to_fill[0,0]
881
+ with nogil:
882
+ self.cmod.compute_distances_to(data_ptr, cpts,signed, n_jobs)
883
+ return to_fill
884
+
885
+ def get_interleavings(self,box=None):
886
+ if box is None:
887
+ box = self.get_box()
888
+ cdef Box[{{CTYPE}}] cbox = Box[{{CTYPE}}](box)
889
+ return np.asarray(self.cmod.get_interleavings(cbox))
890
+
891
+ cdef class PyMultiDiagramPoint_{{SHORT}}:
892
+ cdef MultiDiagram_point[One_critical_filtration[{{CTYPE}}]] point
893
+ cdef set(self, MultiDiagram_point[One_critical_filtration[{{CTYPE}}]] pt):
894
+ self.point = pt
895
+ return self
896
+
897
+ def get_degree(self):
898
+ return self.point.get_dimension()
899
+ def get_birth(self):
900
+ cdef One_critical_filtration[{{CTYPE}}] v = self.point.get_birth()
901
+ return _ff21cview_{{SHORT}}(&v, copy=True)
902
+ def get_death(self):
903
+ cdef One_critical_filtration[{{CTYPE}}] v = self.point.get_death()
904
+ return _ff21cview_{{SHORT}}(&v, copy=True)
905
+
906
+
907
+ cdef class PyMultiDiagram_{{SHORT}}:
908
+ r"""
909
+ Stores the diagram of a PyModule on a line
910
+ """
911
+ cdef MultiDiagram[One_critical_filtration[{{CTYPE}}], {{CTYPE}}] multiDiagram
912
+ cdef set(self, MultiDiagram[One_critical_filtration[{{CTYPE}}], {{CTYPE}}] m):
913
+ self.multiDiagram = m
914
+ return self
915
+ def get_points(self, degree:int=-1) -> np.ndarray:
916
+ cdef vector[pair[vector[{{CTYPE}}],vector[{{CTYPE}}]]] out = self.multiDiagram.get_points(degree)
917
+ if len(out) == 0 and len(self) == 0:
918
+ return np.empty() # TODO Retrieve good number of parameters if there is no points in diagram
919
+ if len(out) == 0:
920
+ return np.empty((0,2,self.multiDiagram.at(0).get_dimension())) # gets the number of parameters
921
+ return np.array(out)
922
+ def to_multipers(self, dimension:int):
923
+ return self.multiDiagram.to_multipers(dimension)
924
+ def __len__(self) -> int:
925
+ return self.multiDiagram.size()
926
+ def __getitem__(self,i:int) -> PyMultiDiagramPoint_{{SHORT}}:
927
+ return PyMultiDiagramPoint_{{SHORT}}().set(self.multiDiagram.at(i % self.multiDiagram.size()))
928
+ cdef class PyMultiDiagrams_{{SHORT}}:
929
+ """
930
+ Stores the barcodes of a PyModule on multiple lines
931
+ """
932
+ cdef MultiDiagrams[One_critical_filtration[{{CTYPE}}], {{CTYPE}}] multiDiagrams
933
+ cdef set(self,MultiDiagrams[One_critical_filtration[{{CTYPE}}], {{CTYPE}}] m):
934
+ self.multiDiagrams = m
935
+ return self
936
+ def to_multipers(self):
937
+ out = self.multiDiagrams.to_multipers()
938
+ return [np.asarray(summand) for summand in out]
939
+ def __getitem__(self,i:int):
940
+ if i >=0 :
941
+ return PyMultiDiagram_{{SHORT}}().set(self.multiDiagrams.at(i))
942
+ else:
943
+ return PyMultiDiagram_{{SHORT}}().set(self.multiDiagrams.at( self.multiDiagrams.size() - i))
944
+ def __len__(self):
945
+ return self.multiDiagrams.size()
946
+ def get_points(self, degree:int=-1):
947
+ return self.multiDiagrams.get_points()
948
+ cdef _get_plot_bars(self, dimension:int=-1, min_persistence:float=0):
949
+ return self.multiDiagrams._for_python_plot(dimension, min_persistence);
950
+ def plot(self, degree:int=-1, min_persistence:float=0):
951
+ """
952
+ Plots the barcodes.
953
+
954
+ Parameters
955
+ ----------
956
+
957
+ - degree:int=-1
958
+ Only plots the bars of specified homology degree. Useful when the multidiagrams contains multiple dimenions
959
+ - min_persistence:float=0
960
+ Only plot bars of length greater than this value. Useful to reduce the time to plot.
961
+
962
+ Warning
963
+ -------
964
+
965
+ If the barcodes are not thresholded, essential barcodes will not be displayed !
966
+
967
+ """
968
+ from cycler import cycler
969
+ import matplotlib
970
+ import matplotlib.pyplot as plt
971
+ if len(self) == 0: return
972
+ _cmap = matplotlib.colormaps["Spectral"]
973
+ multibarcodes_, colors = self._get_plot_bars(degree, min_persistence)
974
+ n_summands = np.max(colors)+1 if len(colors)>0 else 1
975
+
976
+ plt.rc('axes', prop_cycle = cycler('color', [_cmap(i/n_summands) for i in colors]))
977
+ return plt.plot(*multibarcodes_)
978
+ {{endif}}
979
+
980
+
981
+ cdef dump_summand_{{SHORT}}(Summand[{{CTYPE}}]& summand):
982
+ cdef vector[One_critical_filtration[{{CTYPE}}]] births = summand.get_birth_list()
983
+ cdef vector[One_critical_filtration[{{CTYPE}}]] deaths = summand.get_death_list()
984
+ return (
985
+ _vff21cview_{{SHORT}}(births, copy=True), ## copy as local variables
986
+ _vff21cview_{{SHORT}}(deaths, copy=True),
987
+ summand.get_dimension(),
988
+ )
989
+
990
+ cdef inline Summand[{{CTYPE}}] summand_from_dump_{{SHORT}}(summand_dump):
991
+ cdef vector[One_critical_filtration[{{CTYPE}}]] births = _py2v1c_{{SHORT}}(summand_dump[0])
992
+ cdef vector[One_critical_filtration[{{CTYPE}}]] deaths = _py2v1c_{{SHORT}}(summand_dump[1])
993
+ cdef int dim = summand_dump[2]
994
+ return Summand[{{CTYPE}}](births,deaths,dim)
995
+
996
+ cdef dump_cmod_{{SHORT}}(Module[{{CTYPE}}]& mod):
997
+ cdef Box[{{CTYPE}}] cbox = mod.get_box()
998
+ cdef int dim = mod.get_dimension()
999
+ cdef cnp.ndarray[{{CTYPE}}, ndim=1] bottom_corner = _ff21cview_{{SHORT}}(&cbox.get_lower_corner())
1000
+ cdef cnp.ndarray[{{CTYPE}}, ndim=1] top_corner = _ff21cview_{{SHORT}}(&cbox.get_upper_corner())
1001
+ box = np.asarray([bottom_corner, top_corner])
1002
+ summands = tuple(dump_summand_{{SHORT}}(summand) for summand in mod)
1003
+ return box, summands
1004
+
1005
+ cdef Module[{{CTYPE}}] cmod_from_dump_{{SHORT}}(module_dump):
1006
+ box = module_dump[0]
1007
+ summands = module_dump[1]
1008
+ cdef Module[{{CTYPE}}] out_module = Module[{{CTYPE}}]()
1009
+ out_module.set_box(Box[{{CTYPE}}](box))
1010
+ for i in range(len(summands)):
1011
+ out_module.add_summand(summand_from_dump_{{SHORT}}(summands[i]))
1012
+ return out_module
1013
+
1014
+
1015
+ def from_dump_{{SHORT}}(dump)->PyModule_{{SHORT}}:
1016
+ r"""Retrieves a PyModule from a previous dump.
1017
+
1018
+ Parameters
1019
+ ----------
1020
+
1021
+ dump: either the output of the dump function, or a file containing the output of a dump.
1022
+ The dumped module to retrieve
1023
+
1024
+ Returns
1025
+ -------
1026
+
1027
+ PyModule
1028
+ The retrieved module.
1029
+ """
1030
+ # TODO : optimize...
1031
+ mod = PyModule_{{SHORT}}()
1032
+ if type(dump) is str:
1033
+ dump = pickle.load(open(dump, "rb"))
1034
+ cdef Module[{{CTYPE}}] cmod = cmod_from_dump_{{SHORT}}(dump)
1035
+ mod.cmod = cmod
1036
+ return mod
1037
+
1038
+
1039
+ {{endfor}}
1040
+
1041
+
1042
+ global PyModule_type, PySummand_type
1043
+ PyModule_type = Union[
1044
+ {{for CTYPE, PYTYPE, SHORT in value_types}}
1045
+ PyModule_{{SHORT}},
1046
+ {{endfor}}
1047
+ ]
1048
+ PySummand_type = Union[
1049
+ {{for CTYPE, PYTYPE, SHORT in value_types}}
1050
+ PySummand_{{SHORT}},
1051
+ {{endfor}}
1052
+ ]
1053
+
1054
+ PyBox_type = Union[
1055
+ {{for CTYPE, PYTYPE, SHORT in value_types}}
1056
+ PyBox_{{SHORT}},
1057
+ {{endfor}}
1058
+ ]
1059
+
1060
+
1061
+ PyMultiDiagram_type = Union[
1062
+ {{for CTYPE, PYTYPE, SHORT in value_types}}
1063
+ {{if SHORT[0] == 'f'}}
1064
+ PyMultiDiagram_{{SHORT}},
1065
+ {{endif}}
1066
+ {{endfor}}
1067
+ ]
1068
+
1069
+
1070
+ PyMultiDiagrams_type = Union[
1071
+ {{for CTYPE, PYTYPE, SHORT in value_types}}
1072
+ {{if SHORT[0] == 'f'}}
1073
+ PyMultiDiagrams_{{SHORT}},
1074
+ {{endif}}
1075
+ {{endfor}}
1076
+ ]
1077
+
1078
+ def is_mma(stuff):
1079
+ return (False
1080
+ {{for CTYPE, PYTYPE, SHORT in value_types}}
1081
+ or isinstance(stuff,PyModule_{{SHORT}})
1082
+ {{endfor}}
1083
+ )