multipers 2.4.0b1__cp312-cp312-macosx_11_0_arm64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (184) hide show
  1. multipers/.dylibs/libboost_timer.dylib +0 -0
  2. multipers/.dylibs/libc++.1.0.dylib +0 -0
  3. multipers/.dylibs/libtbb.12.17.dylib +0 -0
  4. multipers/__init__.py +33 -0
  5. multipers/_signed_measure_meta.py +426 -0
  6. multipers/_slicer_meta.py +231 -0
  7. multipers/array_api/__init__.py +62 -0
  8. multipers/array_api/numpy.py +124 -0
  9. multipers/array_api/torch.py +133 -0
  10. multipers/data/MOL2.py +458 -0
  11. multipers/data/UCR.py +18 -0
  12. multipers/data/__init__.py +1 -0
  13. multipers/data/graphs.py +466 -0
  14. multipers/data/immuno_regions.py +27 -0
  15. multipers/data/minimal_presentation_to_st_bf.py +0 -0
  16. multipers/data/pytorch2simplextree.py +91 -0
  17. multipers/data/shape3d.py +101 -0
  18. multipers/data/synthetic.py +113 -0
  19. multipers/distances.py +202 -0
  20. multipers/filtration_conversions.pxd +736 -0
  21. multipers/filtration_conversions.pxd.tp +226 -0
  22. multipers/filtrations/__init__.py +21 -0
  23. multipers/filtrations/density.py +529 -0
  24. multipers/filtrations/filtrations.py +480 -0
  25. multipers/filtrations.pxd +534 -0
  26. multipers/filtrations.pxd.tp +332 -0
  27. multipers/function_rips.cpython-312-darwin.so +0 -0
  28. multipers/function_rips.pyx +104 -0
  29. multipers/grids.cpython-312-darwin.so +0 -0
  30. multipers/grids.pyx +538 -0
  31. multipers/gudhi/Persistence_slices_interface.h +213 -0
  32. multipers/gudhi/Simplex_tree_interface.h +274 -0
  33. multipers/gudhi/Simplex_tree_multi_interface.h +648 -0
  34. multipers/gudhi/gudhi/Bitmap_cubical_complex.h +450 -0
  35. multipers/gudhi/gudhi/Bitmap_cubical_complex_base.h +1070 -0
  36. multipers/gudhi/gudhi/Bitmap_cubical_complex_periodic_boundary_conditions_base.h +579 -0
  37. multipers/gudhi/gudhi/Debug_utils.h +52 -0
  38. multipers/gudhi/gudhi/Degree_rips_bifiltration.h +2307 -0
  39. multipers/gudhi/gudhi/Dynamic_multi_parameter_filtration.h +2524 -0
  40. multipers/gudhi/gudhi/Fields/Multi_field.h +453 -0
  41. multipers/gudhi/gudhi/Fields/Multi_field_operators.h +460 -0
  42. multipers/gudhi/gudhi/Fields/Multi_field_shared.h +444 -0
  43. multipers/gudhi/gudhi/Fields/Multi_field_small.h +584 -0
  44. multipers/gudhi/gudhi/Fields/Multi_field_small_operators.h +490 -0
  45. multipers/gudhi/gudhi/Fields/Multi_field_small_shared.h +580 -0
  46. multipers/gudhi/gudhi/Fields/Z2_field.h +391 -0
  47. multipers/gudhi/gudhi/Fields/Z2_field_operators.h +389 -0
  48. multipers/gudhi/gudhi/Fields/Zp_field.h +493 -0
  49. multipers/gudhi/gudhi/Fields/Zp_field_operators.h +384 -0
  50. multipers/gudhi/gudhi/Fields/Zp_field_shared.h +492 -0
  51. multipers/gudhi/gudhi/Flag_complex_edge_collapser.h +337 -0
  52. multipers/gudhi/gudhi/Matrix.h +2200 -0
  53. multipers/gudhi/gudhi/Multi_filtration/Multi_parameter_generator.h +1712 -0
  54. multipers/gudhi/gudhi/Multi_filtration/multi_filtration_conversions.h +237 -0
  55. multipers/gudhi/gudhi/Multi_filtration/multi_filtration_utils.h +225 -0
  56. multipers/gudhi/gudhi/Multi_parameter_filtered_complex.h +485 -0
  57. multipers/gudhi/gudhi/Multi_parameter_filtration.h +2643 -0
  58. multipers/gudhi/gudhi/Multi_persistence/Box.h +233 -0
  59. multipers/gudhi/gudhi/Multi_persistence/Line.h +309 -0
  60. multipers/gudhi/gudhi/Multi_persistence/Multi_parameter_filtered_complex_pcoh_interface.h +268 -0
  61. multipers/gudhi/gudhi/Multi_persistence/Persistence_interface_cohomology.h +159 -0
  62. multipers/gudhi/gudhi/Multi_persistence/Persistence_interface_matrix.h +463 -0
  63. multipers/gudhi/gudhi/Multi_persistence/Point.h +853 -0
  64. multipers/gudhi/gudhi/Off_reader.h +173 -0
  65. multipers/gudhi/gudhi/Persistence_matrix/Base_matrix.h +834 -0
  66. multipers/gudhi/gudhi/Persistence_matrix/Base_matrix_with_column_compression.h +838 -0
  67. multipers/gudhi/gudhi/Persistence_matrix/Boundary_matrix.h +833 -0
  68. multipers/gudhi/gudhi/Persistence_matrix/Chain_matrix.h +1367 -0
  69. multipers/gudhi/gudhi/Persistence_matrix/Id_to_index_overlay.h +1157 -0
  70. multipers/gudhi/gudhi/Persistence_matrix/Position_to_index_overlay.h +869 -0
  71. multipers/gudhi/gudhi/Persistence_matrix/RU_matrix.h +905 -0
  72. multipers/gudhi/gudhi/Persistence_matrix/allocators/entry_constructors.h +122 -0
  73. multipers/gudhi/gudhi/Persistence_matrix/base_pairing.h +260 -0
  74. multipers/gudhi/gudhi/Persistence_matrix/base_swap.h +288 -0
  75. multipers/gudhi/gudhi/Persistence_matrix/chain_pairing.h +170 -0
  76. multipers/gudhi/gudhi/Persistence_matrix/chain_rep_cycles.h +247 -0
  77. multipers/gudhi/gudhi/Persistence_matrix/chain_vine_swap.h +571 -0
  78. multipers/gudhi/gudhi/Persistence_matrix/columns/chain_column_extra_properties.h +182 -0
  79. multipers/gudhi/gudhi/Persistence_matrix/columns/column_dimension_holder.h +130 -0
  80. multipers/gudhi/gudhi/Persistence_matrix/columns/column_utilities.h +235 -0
  81. multipers/gudhi/gudhi/Persistence_matrix/columns/entry_types.h +312 -0
  82. multipers/gudhi/gudhi/Persistence_matrix/columns/heap_column.h +1092 -0
  83. multipers/gudhi/gudhi/Persistence_matrix/columns/intrusive_list_column.h +923 -0
  84. multipers/gudhi/gudhi/Persistence_matrix/columns/intrusive_set_column.h +914 -0
  85. multipers/gudhi/gudhi/Persistence_matrix/columns/list_column.h +930 -0
  86. multipers/gudhi/gudhi/Persistence_matrix/columns/naive_vector_column.h +1071 -0
  87. multipers/gudhi/gudhi/Persistence_matrix/columns/row_access.h +203 -0
  88. multipers/gudhi/gudhi/Persistence_matrix/columns/set_column.h +886 -0
  89. multipers/gudhi/gudhi/Persistence_matrix/columns/unordered_set_column.h +984 -0
  90. multipers/gudhi/gudhi/Persistence_matrix/columns/vector_column.h +1213 -0
  91. multipers/gudhi/gudhi/Persistence_matrix/index_mapper.h +58 -0
  92. multipers/gudhi/gudhi/Persistence_matrix/matrix_dimension_holders.h +227 -0
  93. multipers/gudhi/gudhi/Persistence_matrix/matrix_row_access.h +200 -0
  94. multipers/gudhi/gudhi/Persistence_matrix/ru_pairing.h +166 -0
  95. multipers/gudhi/gudhi/Persistence_matrix/ru_rep_cycles.h +319 -0
  96. multipers/gudhi/gudhi/Persistence_matrix/ru_vine_swap.h +562 -0
  97. multipers/gudhi/gudhi/Persistence_on_a_line.h +152 -0
  98. multipers/gudhi/gudhi/Persistence_on_rectangle.h +617 -0
  99. multipers/gudhi/gudhi/Persistent_cohomology/Field_Zp.h +118 -0
  100. multipers/gudhi/gudhi/Persistent_cohomology/Multi_field.h +173 -0
  101. multipers/gudhi/gudhi/Persistent_cohomology/Persistent_cohomology_column.h +128 -0
  102. multipers/gudhi/gudhi/Persistent_cohomology.h +769 -0
  103. multipers/gudhi/gudhi/Points_off_io.h +171 -0
  104. multipers/gudhi/gudhi/Projective_cover_kernel.h +379 -0
  105. multipers/gudhi/gudhi/Simple_object_pool.h +69 -0
  106. multipers/gudhi/gudhi/Simplex_tree/Simplex_tree_iterators.h +559 -0
  107. multipers/gudhi/gudhi/Simplex_tree/Simplex_tree_node_explicit_storage.h +83 -0
  108. multipers/gudhi/gudhi/Simplex_tree/Simplex_tree_siblings.h +121 -0
  109. multipers/gudhi/gudhi/Simplex_tree/Simplex_tree_star_simplex_iterators.h +277 -0
  110. multipers/gudhi/gudhi/Simplex_tree/filtration_value_utils.h +155 -0
  111. multipers/gudhi/gudhi/Simplex_tree/hooks_simplex_base.h +62 -0
  112. multipers/gudhi/gudhi/Simplex_tree/indexing_tag.h +27 -0
  113. multipers/gudhi/gudhi/Simplex_tree/serialization_utils.h +60 -0
  114. multipers/gudhi/gudhi/Simplex_tree/simplex_tree_options.h +105 -0
  115. multipers/gudhi/gudhi/Simplex_tree.h +3170 -0
  116. multipers/gudhi/gudhi/Slicer.h +848 -0
  117. multipers/gudhi/gudhi/Thread_safe_slicer.h +393 -0
  118. multipers/gudhi/gudhi/distance_functions.h +62 -0
  119. multipers/gudhi/gudhi/graph_simplicial_complex.h +104 -0
  120. multipers/gudhi/gudhi/multi_simplex_tree_helpers.h +147 -0
  121. multipers/gudhi/gudhi/persistence_interval.h +263 -0
  122. multipers/gudhi/gudhi/persistence_matrix_options.h +188 -0
  123. multipers/gudhi/gudhi/reader_utils.h +367 -0
  124. multipers/gudhi/gudhi/simple_mdspan.h +484 -0
  125. multipers/gudhi/gudhi/slicer_helpers.h +779 -0
  126. multipers/gudhi/tmp_h0_pers/mma_interface_h0.h +223 -0
  127. multipers/gudhi/tmp_h0_pers/naive_merge_tree.h +536 -0
  128. multipers/io.cpython-312-darwin.so +0 -0
  129. multipers/io.pyx +472 -0
  130. multipers/ml/__init__.py +0 -0
  131. multipers/ml/accuracies.py +90 -0
  132. multipers/ml/invariants_with_persistable.py +79 -0
  133. multipers/ml/kernels.py +176 -0
  134. multipers/ml/mma.py +713 -0
  135. multipers/ml/one.py +472 -0
  136. multipers/ml/point_clouds.py +352 -0
  137. multipers/ml/signed_measures.py +1667 -0
  138. multipers/ml/sliced_wasserstein.py +461 -0
  139. multipers/ml/tools.py +113 -0
  140. multipers/mma_structures.cpython-312-darwin.so +0 -0
  141. multipers/mma_structures.pxd +134 -0
  142. multipers/mma_structures.pyx +1483 -0
  143. multipers/mma_structures.pyx.tp +1126 -0
  144. multipers/multi_parameter_rank_invariant/diff_helpers.h +85 -0
  145. multipers/multi_parameter_rank_invariant/euler_characteristic.h +95 -0
  146. multipers/multi_parameter_rank_invariant/function_rips.h +317 -0
  147. multipers/multi_parameter_rank_invariant/hilbert_function.h +761 -0
  148. multipers/multi_parameter_rank_invariant/persistence_slices.h +149 -0
  149. multipers/multi_parameter_rank_invariant/rank_invariant.h +350 -0
  150. multipers/multiparameter_edge_collapse.py +41 -0
  151. multipers/multiparameter_module_approximation/approximation.h +2541 -0
  152. multipers/multiparameter_module_approximation/debug.h +107 -0
  153. multipers/multiparameter_module_approximation/format_python-cpp.h +292 -0
  154. multipers/multiparameter_module_approximation/utilities.h +428 -0
  155. multipers/multiparameter_module_approximation.cpython-312-darwin.so +0 -0
  156. multipers/multiparameter_module_approximation.pyx +286 -0
  157. multipers/ops.cpython-312-darwin.so +0 -0
  158. multipers/ops.pyx +231 -0
  159. multipers/pickle.py +89 -0
  160. multipers/plots.py +550 -0
  161. multipers/point_measure.cpython-312-darwin.so +0 -0
  162. multipers/point_measure.pyx +409 -0
  163. multipers/simplex_tree_multi.cpython-312-darwin.so +0 -0
  164. multipers/simplex_tree_multi.pxd +136 -0
  165. multipers/simplex_tree_multi.pyx +11719 -0
  166. multipers/simplex_tree_multi.pyx.tp +2102 -0
  167. multipers/slicer.cpython-312-darwin.so +0 -0
  168. multipers/slicer.pxd +2097 -0
  169. multipers/slicer.pxd.tp +263 -0
  170. multipers/slicer.pyx +13042 -0
  171. multipers/slicer.pyx.tp +1259 -0
  172. multipers/tensor/tensor.h +672 -0
  173. multipers/tensor.pxd +13 -0
  174. multipers/test.pyx +44 -0
  175. multipers/tests/__init__.py +70 -0
  176. multipers/torch/__init__.py +1 -0
  177. multipers/torch/diff_grids.py +240 -0
  178. multipers/torch/rips_density.py +310 -0
  179. multipers/vector_interface.pxd +46 -0
  180. multipers-2.4.0b1.dist-info/METADATA +131 -0
  181. multipers-2.4.0b1.dist-info/RECORD +184 -0
  182. multipers-2.4.0b1.dist-info/WHEEL +6 -0
  183. multipers-2.4.0b1.dist-info/licenses/LICENSE +21 -0
  184. multipers-2.4.0b1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1483 @@
1
+
2
+
3
+
4
+
5
+
6
+ """!
7
+ @package mma
8
+ @brief Files containing the C++ cythonized functions.
9
+ @author David Loiseaux
10
+ @copyright Copyright (c) 2022 Inria.
11
+ """
12
+
13
+ # distutils: language = c++
14
+
15
+ ###########################################################################
16
+ ## PYTHON LIBRARIES
17
+ import gudhi as gd
18
+ import numpy as np
19
+ from typing import Union, Literal
20
+ from collections.abc import Callable, Iterable, Sequence
21
+ import pickle
22
+ import multipers.grids as mpg
23
+
24
+ ###########################################################################
25
+ ## CPP CLASSES
26
+ from libc.stdint cimport intptr_t
27
+ from libc.stdint cimport uintptr_t
28
+
29
+ ###########################################################################
30
+ ## CYTHON TYPES
31
+ from libcpp.vector cimport vector
32
+ from libcpp.utility cimport pair
33
+ #from libcpp.list cimport list as clist
34
+ from libcpp cimport bool
35
+ from libcpp cimport int
36
+ from cython.operator cimport dereference
37
+ from libcpp.utility cimport move
38
+ cimport cython
39
+ #########################################################################
40
+ ## Multipersistence Module Approximation Classes
41
+ from multipers.mma_structures cimport *
42
+ from multipers.filtration_conversions cimport *
43
+ cimport numpy as cnp
44
+
45
+
46
+ #########################################################################
47
+ ## Small hack for typing
48
+ from gudhi import SimplexTree
49
+ from multipers.simplex_tree_multi import SimplexTreeMulti
50
+ from joblib import Parallel, delayed
51
+
52
+ available_pymodules = [
53
+ PyModule_f64,
54
+ PyModule_i32,
55
+ ]
56
+
57
+ PyModule_type = Union[
58
+ PyModule_f64,
59
+ PyModule_i32,
60
+ ]
61
+ cdef class PySummand_f64:
62
+ r"""
63
+ Stores a Summand of a PyModule
64
+ """
65
+ cdef Summand[double] sum
66
+
67
+ def get_birth_list(self):
68
+ cdef vector[One_critical_filtration[double]] v = self.sum.get_birth_list()
69
+ return _vff21cview_f64(v, copy = True)
70
+
71
+ def get_death_list(self):
72
+ cdef vector[One_critical_filtration[double]] v = self.sum.get_death_list()
73
+ return _vff21cview_f64(v, copy = True)
74
+ @property
75
+ def degree(self)->int:
76
+ return self.sum.get_dimension()
77
+
78
+ cdef set(self, Summand[double]& summand):
79
+ self.sum = summand
80
+ return self
81
+ def get_bounds(self):
82
+ cdef pair[vector[double],vector[double]] cbounds
83
+ with nogil:
84
+ cbounds = self.sum.get_bounds().get_bounding_corners()
85
+ # return _ff21cview_f64(&cbounds.first).copy(), _ff21cview_f64(&cbounds.second).copy()
86
+ return np.asarray(cbounds.first), np.asarray(cbounds.second)
87
+ @property
88
+ def dtype(self):
89
+ return np.float64
90
+
91
+ def num_parameters(self):
92
+ cdef vector[One_critical_filtration[double]] v = self.sum.get_birth_list()
93
+ if v[0].is_finite():
94
+ return v[0].num_parameters()
95
+ v = self.sum.get_death_list()
96
+ return v[0].num_parameters()
97
+ def __eq__(self, PySummand_f64 other):
98
+ return self.sum == other.sum
99
+
100
+
101
+
102
+
103
+ cdef inline get_summand_filtration_values_f64(Summand[double] summand):
104
+ r"""
105
+ Returns a list (over parameter) of the filtrations values of this parameter.
106
+ """
107
+ cdef vector[One_critical_filtration[double]] vb = summand.get_birth_list()
108
+ cdef vector[One_critical_filtration[double]] vd = summand.get_death_list()
109
+
110
+ if vb[0].is_finite():
111
+ if vd[0].is_finite():
112
+ pts = np.concatenate([_vff21cview_f64(vb, copy=True),
113
+ _vff21cview_f64(vd, copy=True)],axis=0)
114
+ else:
115
+ pts = np.array(_vff21cview_f64(vb, copy=True))
116
+ else:
117
+ if vd[0].is_finite():
118
+ pts = np.array(_vff21cview_f64(vd, copy=True))
119
+ else:
120
+ return []
121
+
122
+ num_parameters = pts.shape[1]
123
+ out = [np.unique(pts[:,parameter]) for parameter in range(num_parameters)]
124
+ out = [f[:-1] if len(f)>0 and f[-1] == np.inf else f for f in out]
125
+ out = [f[1:] if len(f)>0 and f[0] == -np.inf else f for f in out]
126
+ return out
127
+
128
+ cdef class PyBox_f64:
129
+ cdef Box[double] box
130
+ def __cinit__(self, vector[double]& bottomCorner, vector[double]& topCorner):
131
+ self.box = Box[double](bottomCorner, topCorner)
132
+ @property
133
+ def num_parameters(self):
134
+ cdef size_t dim = self.box.get_lower_corner().size()
135
+ if dim == self.box.get_upper_corner().size(): return dim
136
+ else: print("Bad box definition.")
137
+ def contains(self, x):
138
+ return self.box.contains(x)
139
+ cdef set(self, Box[double]& b):
140
+ self.box = b
141
+ return self
142
+
143
+ def get(self):
144
+ return [<vector[double]>self.box.get_lower_corner(), <vector[double]>self.box.get_upper_corner()]
145
+ def to_multipers(self):
146
+ #assert (self.get_dimension() == 2) "Multipers only works in dimension 2 !"
147
+ return np.array(self.get()).flatten(order = 'F')
148
+ @property
149
+ def dtype(self):
150
+ return np.float64
151
+
152
+
153
+
154
+ cdef class PyModule_f64:
155
+ r"""
156
+ Stores a representation of a n-persistence module.
157
+ """
158
+ cdef Module[double] cmod
159
+
160
+ @property
161
+ def dtype(self):
162
+ return np.float64
163
+
164
+ cdef set(self, Module[double] m):
165
+ self.cmod = m
166
+
167
+ def __eq__(self, PyModule_f64 other):
168
+ return self.cmod == other.cmod
169
+
170
+ def merge(self, PyModule_f64 other, int dim=-1):
171
+ r"""
172
+ Merges two modules into one
173
+ """
174
+ cdef Module[double] c_other = other.cmod
175
+ with nogil:
176
+ for summand in c_other:
177
+ self.cmod.add_summand(summand, dim)
178
+ return self
179
+ def _add_mmas(self, mmas):
180
+ for mma in mmas:
181
+ self.merge(mma)
182
+ return self
183
+
184
+ def permute_summands(self, permutation):
185
+ cdef int[:] c_perm = np.asarray(permutation, dtype=np.int32)
186
+ other = PyModule_f64()
187
+ other.cmod = self.cmod.permute_summands(permutation)
188
+ return other
189
+
190
+ def _set_from_ptr(self, intptr_t module_ptr):
191
+ r"""
192
+ Copy module from a memory pointer. Unsafe.
193
+ """
194
+ self.cmod = move(dereference(<Module[double]*>(module_ptr)))
195
+ def _get_ptr(self):
196
+ cdef intptr_t ptr = <intptr_t>(&self.cmod)
197
+ return ptr
198
+ def set_box(self, box):
199
+ assert len(box) == 2, "Box format is [low, hight]"
200
+ pybox = PyBox_f64(box[0], box[1])
201
+ cdef Box[double] 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_f64: # TODO : in c++ ?
206
+ r"""
207
+ Returns a copy of a module of fixed degree.
208
+ """
209
+ pmodule = PyModule_f64()
210
+ cdef Box[double] c_box = self.cmod.get_box()
211
+ with nogil:
212
+ pmodule.cmod.set_box(c_box)
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_f64: # TODO : in c++ ?
218
+ r"""
219
+ Returns a copy of the summands of degrees in `degrees`
220
+ """
221
+ pmodule = PyModule_f64()
222
+ cdef Box[double] 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[double]>(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[double]>(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().size()
257
+ assert dim == self.cmod.get_box().get_upper_corner().size(), "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_f64(self.cmod)
275
+ def __getstate__(self):
276
+ return self.dump()
277
+ def __setstate__(self,dump):
278
+ cdef Module[double] cmod = cmod_from_dump_f64(dump)
279
+ self.cmod = cmod
280
+ return
281
+ def __getitem__(self, int i) -> PySummand_f64:
282
+ if i == slice(None):
283
+ return self
284
+ summand = PySummand_f64()
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_f64()
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[vector[double],vector[double]] cbounds
300
+ with nogil:
301
+ cbounds = self.cmod.get_bounds().get_bounding_corners()
302
+ # return _ff21cview_f64(&cbounds.first).copy(), _ff21cview_f64(&cbounds.second).copy()
303
+ return np.asarray(cbounds.first), np.asarray(cbounds.second)
304
+ def rescale(self,rescale_factors, int degree=-1):
305
+ r"""
306
+ Rescales the fitlration values of the summands by this rescaling vector.
307
+ """
308
+ cdef vector[double] crescale_factors = rescale_factors
309
+ with nogil:
310
+ self.cmod.rescale(crescale_factors,degree)
311
+ def translate(self,translation, int degree=-1):
312
+ r"""
313
+ Translates the module in the filtration space by this vector.
314
+ """
315
+ cdef vector[double] ctranslation = translation
316
+ with nogil:
317
+ self.cmod.translate(ctranslation,degree)
318
+
319
+ def get_filtration_values(self, bool unique=True):
320
+ r"""
321
+ Retrieves all filtration values of the summands of the module.
322
+
323
+ Output format
324
+ -------------
325
+
326
+ list of filtration values for parameter.
327
+ """
328
+ if len(self) ==0:
329
+ return np.empty((self.num_parameters,0))
330
+ values = tuple(
331
+ tuple(stuff)
332
+ if len(stuff:=get_summand_filtration_values_f64(summand)) == self.num_parameters else list(stuff) + [[]]*(self.num_parameters - len(stuff)) for summand in self.cmod)
333
+ try:
334
+ values = tuple(np.concatenate([
335
+ f[parameter]
336
+ for f in values
337
+ ], axis=0) for parameter in range(self.num_parameters)
338
+ )
339
+ except:
340
+ return values
341
+ if unique:
342
+ return [np.unique(f) for f in values]
343
+ return values
344
+
345
+ def plot(self, int degree=-1,**kwargs)->None:
346
+ r"""Shows the module on a plot. Each color corresponds to an apprimation summand of the module, and its shape corresponds to its support.
347
+ Only works with 2-parameter modules.
348
+
349
+ Parameters
350
+ ----------
351
+ degree = -1 : integer
352
+ If positive returns only the image of dimension `dimension`.
353
+ 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.
354
+ If non-None, will plot the module on this specific rectangle.
355
+ min_persistence =0 : float
356
+ Only plots the summand with a persistence above this threshold.
357
+ separated=False : bool
358
+ If true, plot each summand in a different plot.
359
+ alpha=1 : float
360
+ Transparancy parameter
361
+ save = False : string
362
+ if nontrivial, will save the figure at this path
363
+
364
+
365
+ Returns
366
+ -------
367
+ The figure of the plot.
368
+ """
369
+ from multipers.plots import plot2d_PyModule
370
+ import matplotlib.pyplot as plt
371
+ box = kwargs.pop('box', self.get_box())
372
+ if (len(box[0]) != 2):
373
+ print("Filtration size :", len(box[0]), " != 2")
374
+ return
375
+ if(degree < 0):
376
+ dims = np.unique(self.get_dimensions())
377
+ separated = kwargs.pop("separated", False)
378
+ ndim = len(dims)
379
+ scale = kwargs.pop("scale", 4)
380
+ if separated:
381
+ fig = None
382
+ axes = None
383
+ elif ndim > 1:
384
+ fig, axes = plt.subplots(1,ndim, figsize=(ndim*scale,scale))
385
+ else:
386
+ fig = plt.gcf()
387
+ axes = [plt.gca()]
388
+ for dim_idx in range(ndim):
389
+ if not separated:
390
+ plt.sca(axes[dim_idx])
391
+ self.plot(dims[dim_idx],box=box, separated = separated, **kwargs)
392
+ return
393
+ corners = self.cmod.get_corners_of_dimension(degree)
394
+ interleavings = self.get_module_of_degree(degree).get_interleavings()
395
+ plot2d_PyModule(corners, box=box, dimension=degree, interleavings = interleavings, **kwargs)
396
+ return
397
+ def degree_splits(self):
398
+ return np.asarray(self.cmod.get_degree_splits(), dtype=np.int64)
399
+ def evaluate_in_grid(self, grid):
400
+ grid = mpg.sanitize_grid(grid, numpyfy=True)
401
+ cdef size_t num_parameters = len(grid)
402
+ if num_parameters != self.num_parameters:
403
+ raise ValueError(f"Invalid grid size. Got {len(grid)=} != {self.num_parameters=}")
404
+ cdef vector[vector[double]] cgrid = vector[vector[double]](num_parameters)
405
+ for i in range(num_parameters):
406
+ cgrid[i] = grid[i]
407
+ self.cmod.evaluate_in_grid(cgrid)
408
+ return self
409
+
410
+
411
+
412
+ def _compute_pixels(self,coordinates:np.ndarray,
413
+ degrees=None, box=None, double delta=.1,
414
+ double p=1., bool normalize=False, int n_jobs=0):
415
+ r"""
416
+ Computes the image of the module at the given coordinates
417
+ """
418
+ if degrees is not None: assert np.all(degrees[:-1] <= degrees[1:]), "Degrees have to be sorted"
419
+ cdef vector[int] cdegrees = np.arange(self.max_degree +1) if degrees is None else degrees
420
+ pybox = PyBox_f64(*self.get_box()) if box is None else PyBox_f64(*box)
421
+ cdef Box[double] cbox = pybox.box
422
+ cdef vector[vector[double]] ccoords = coordinates
423
+ cdef vector[vector[double]] out
424
+ with nogil:
425
+ out = self.cmod.compute_pixels(ccoords, cdegrees, cbox, delta, p, normalize, n_jobs)
426
+ return np.asarray(out)
427
+ def barcode(self, basepoint, int degree = -1,*, bool threshold = False): # TODO direction vector interface
428
+ r"""Computes the barcode of module along a lines.
429
+
430
+ Parameters
431
+ ----------
432
+
433
+ basepoint : vector
434
+ basepoint of the lines on which to compute the barcodes, i.e. a point on the line
435
+ degree = -1 : integer
436
+ Homology degree on which to compute the bars. If negative, every dimension is computed
437
+ box (default) :
438
+ box on which to compute the barcodes if basepoints is not specified. Default is a linspace of lines crossing that box.
439
+ threshold = False :
440
+ Thre
441
+
442
+ Warning
443
+ -------
444
+
445
+ If the barcodes are not thresholded, essential barcodes will not be plot-able.
446
+
447
+ Returns
448
+ -------
449
+
450
+ PyMultiDiagrams
451
+ Structure that holds the barcodes. Barcodes can be retrieved with a .get_points() or a .to_multipers() or a .plot().
452
+ """
453
+ out = PyMultiDiagram_f64()
454
+ out.set(self.cmod.get_barcode(Line[double](_py2p_f64(np.asarray(basepoint, dtype=np.float64))), degree, threshold))
455
+ return out
456
+ @staticmethod
457
+ cdef _threshold_bc(bc):
458
+ return tuple(np.fromiter((a for a in stuff if a[0] < np.inf), dtype=np.dtype((np.float64,2)) ) for stuff in bc)
459
+ @staticmethod
460
+ def _bc_to_full(bcs, basepoint, direction=None):
461
+ # i, (b sv d), coords
462
+ basepoint = np.asarray(basepoint)[None,None,:]
463
+ direction = 1 if direction is None else np.asarray(direction)[None,None,:]
464
+ return tuple(bc[:,:,None]*direction + basepoint for bc in bcs)
465
+ def barcode2(self, basepoint, direction=None, int degree = -1,*, bool threshold = False, bool keep_inf = True, bool full = False): # TODO direction vector interface
466
+ r"""
467
+ Compute the 1d-barcode a diagonal line based on basepoint, with some direction.
468
+
469
+ Parameters
470
+ ----------
471
+
472
+ - basepoint: 1d array
473
+ - directiont: 1d array or None, if None: diagonal
474
+ - degree: int the degree to compute (-1 means all)
475
+ - threshold: bool if True, threshold the barcode to the modules box
476
+ - keep_inf: bool if False, removes trivial bars
477
+ Note that this removes the order w.r.t. the summands in that case
478
+ - full:bool if True, returns the coordinates of the barcode instead of the coordinate in the line.
479
+
480
+ The output is of the form
481
+
482
+ tuple[np.ndarray of shape (num_bars,2)] or tuple[array of shape (num_bar, 2, num_parameters)]
483
+ """
484
+ basepoint = np.asarray(basepoint, dtype=np.float64)
485
+ if direction is None:
486
+ bc = tuple(np.asarray(x).reshape(-1,2) for x in self.cmod.get_barcode2(Line[double](_py2p_f64(basepoint)), degree))
487
+ else:
488
+ direction = np.asarray(direction, dtype = np.float64)
489
+ bc = tuple(np.asarray(x).reshape(-1,2) for x in self.cmod.get_barcode2(Line[double](_py2p_f64(basepoint), _py2p_f64(direction)), degree))
490
+ if not keep_inf:
491
+ bc = PyModule_f64._threshold_bc(bc)
492
+ if full:
493
+ bc = PyModule_f64._bc_to_full(bc, basepoint, direction)
494
+
495
+ return bc
496
+
497
+ def get_dimensions(self):
498
+ cdef int num_summands = len(self)
499
+ out = np.empty(shape=num_summands, dtype=np.int32)
500
+ cdef int32_t[:] c_out = out
501
+ for i in range(num_summands):
502
+ c_out[i] = self.cmod.at(i).get_dimension()
503
+ return out
504
+
505
+ def barcodes(self, int degree, basepoints = None, num=100, box = None,threshold = False):
506
+ r"""Computes barcodes of module along a set of lines.
507
+
508
+ Parameters
509
+ ----------
510
+
511
+ basepoints = None : list of vectors
512
+ basepoints of the lines on which to compute the barcodes.
513
+ degree = -1 : integer
514
+ Homology degree on which to compute the bars. If negative, every dimension is computed
515
+ box (default) :
516
+ box on which to compute the barcodes if basepoints is not specified. Default is a linspace of lines crossing that box.
517
+ num:int=100
518
+ if basepoints is not specified, defines the number of lines to consider.
519
+ threshold = False : threshold t
520
+ Resolution of the image(s).
521
+
522
+ Warning
523
+ -------
524
+
525
+ If the barcodes are not thresholded, essential barcodes will not be plot-able.
526
+
527
+ Returns
528
+ -------
529
+
530
+ PyMultiDiagrams
531
+ Structure that holds the barcodes. Barcodes can be retrieved with a .get_points() or a .to_multipers() or a .plot().
532
+ """
533
+ out = PyMultiDiagrams_f64()
534
+ if box is None:
535
+ box = [self.get_bottom(), self.get_top()]
536
+ if (len(box[0]) != 2) and (basepoints is None):
537
+ raise ValueError("Basepoints has to be specified for filtration dimension >= 3 !")
538
+ elif basepoints is None:
539
+ h = box[1][1] - box[0][1]
540
+ basepoints = np.linspace([box[0][0] - h,box[0][1]], [box[1][0],box[0][1]], num=num)
541
+ else :
542
+ num=len(basepoints)
543
+
544
+ cdef double[:,:] basepoints_view = np.asarray(basepoints, dtype = np.float64)
545
+ cdef vector[Point[double]] cbasepoints = _py2vp_f64(basepoints_view)
546
+
547
+ out.set(self.cmod.get_barcodes(cbasepoints, degree, threshold))
548
+ return out
549
+
550
+ def barcodes2(self, int degree = -1, basepoints = None, int num=100, box = None, bool threshold = False):
551
+ r"""Computes barcodes of module along a set of lines.
552
+
553
+ Parameters
554
+ ----------
555
+
556
+ basepoints = None : list of vectors
557
+ basepoints of the lines on which to compute the barcodes.
558
+ degree = -1 : integer
559
+ Homology degree on which to compute the bars. If negative, every dimension is computed
560
+ box (default) :
561
+ box on which to compute the barcodes if basepoints is not specified. Default is a linspace of lines crossing that box.
562
+ num:int=100
563
+ if basepoints is not specified, defines the number of lines to consider.
564
+ threshold = False : threshold t
565
+ Resolution of the image(s).
566
+
567
+ Warning
568
+ -------
569
+
570
+ If the barcodes are not thresholded, essential barcodes will not be plot-able.
571
+
572
+ Returns
573
+ -------
574
+
575
+ tuple of 1d barcodes, based on basepoint, with direction (1,1)
576
+ """
577
+ if box is None:
578
+ box = [self.get_bottom(), self.get_top()]
579
+ if (len(box[0]) != 2) and (basepoints is None):
580
+ raise ValueError("Basepoints has to be specified for filtration dimension >= 3 !")
581
+ elif basepoints is None:
582
+ h = box[1][1] - box[0][1]
583
+ basepoints = np.linspace([box[0][0] - h,box[0][1]], [box[1][0],box[0][1]], num=num)
584
+ else :
585
+ num=len(basepoints)
586
+
587
+ basepoints = np.asarray(basepoints, dtype=np.float64)
588
+ cdef vector[Line[double]] cbasepoints
589
+ for i in range(num):
590
+ cbasepoints.push_back(Line[double](_py2p_f64(basepoints[i])))
591
+ return tuple(np.asarray(bc) for bc in self.cmod.get_barcodes2(cbasepoints, degree))
592
+
593
+ def landscape(self, degree:int, k:int=0,box:Sequence|np.ndarray|None=None, resolution:Sequence=[100,100], bool plot=False):
594
+ r"""Computes the multiparameter landscape from a PyModule. Python interface only bifiltrations.
595
+
596
+ Parameters
597
+ ----------
598
+
599
+ degree : integer
600
+ The homology degree of the landscape.
601
+ k = 0 : int
602
+ the k-th landscape
603
+ resolution = [50,50] : pair of integers
604
+ Resolution of the image.
605
+ box = None : in the format [[a,b], [c,d]]
606
+ If nontrivial, compute the landscape of this box. Default is the PyModule box.
607
+ plot = True : Boolean
608
+ If true, plots the images;
609
+ Returns
610
+ -------
611
+
612
+ The landscape of the module.
613
+
614
+ """
615
+ import matplotlib.pyplot as plt
616
+ if box is None:
617
+ box = self.get_box()
618
+ cdef Box[double] c_box = Box[double](box)
619
+ out = np.array(self.cmod.get_landscape(degree, k, c_box, resolution))
620
+ if plot:
621
+ aspect = (box[1][0]-box[0][0]) / (box[1][1]-box[0][1])
622
+ extent = [box[0][0], box[1][0], box[0][1], box[1][1]]
623
+ plt.imshow(out.T, origin="lower", extent=extent, aspect=aspect)
624
+ return out
625
+
626
+ def landscapes(self, degree:int, ks:list|np.ndarray=[0],box=None, resolution:list|np.ndarray=[100,100], bool plot=False):
627
+ r"""Computes the multiparameter landscape from a PyModule. Python interface only bifiltrations.
628
+
629
+ Parameters
630
+ ----------
631
+
632
+ - degree : integer
633
+ The homology degree of the landscape.
634
+ - ks = 0 : list of int
635
+ the k-th landscape
636
+ - resolution = [50,50] : pair of integers
637
+ Resolution of the image.
638
+ - box = None : in the format [[a,b], [c,d]]
639
+ If nontrivial, compute the landscape of this box. Default is the PyModule box.
640
+ - plot = True : bool
641
+ If true, plots the images;
642
+ Returns
643
+ -------
644
+
645
+ The landscapes of the module with parameters ks.
646
+
647
+ """
648
+ import matplotlib.pyplot as plt
649
+ if box is None:
650
+ box = self.get_box()
651
+ out = np.array(self.cmod.get_landscapes(degree, ks, Box[double](box), resolution))
652
+ if plot:
653
+ to_plot = np.sum(out, axis=0)
654
+ aspect = (box[1][0]-box[0][0]) / (box[1][1]-box[0][1])
655
+ extent = [box[0][0], box[1][0], box[0][1], box[1][1]]
656
+ plt.imshow(to_plot.T, origin="lower", extent=extent, aspect=aspect)
657
+ return out
658
+
659
+
660
+ def representation(self, degrees=None, double bandwidth=0.1,
661
+ resolution:Sequence[int]|int=50,
662
+ kernel:Literal["gaussian","linear","linear2","exponential"]|Callable = "gaussian",
663
+ bool signed=False,
664
+ bool normalize=False, bool plot=False,
665
+ bool save=False, int dpi=200,double p=2., box=None,
666
+ bool flatten=False, int n_jobs=0,
667
+ grid = None)->np.ndarray:
668
+ r"""Computes a representation of the module, using
669
+
670
+ [A Framework for Fast and Stable Representations of Multiparameter Persistent Homology Decompositions, Neurips2023]
671
+
672
+ Parameters
673
+ ----------
674
+
675
+ - degrees = None : integer list
676
+ If given returns only the image(s) of homology degrees `degrees`.
677
+ - bandwidth = 0.1 : float
678
+ Image parameter.
679
+ - resolution = [100,100] : pair of integers
680
+ Resolution of the image(s).
681
+ - normalize = True : Boolean
682
+ Ensures that the image belongs to [0,1].
683
+ - plot = False : Boolean
684
+ If true, plots the images;
685
+ - flatten=False :
686
+ If True, reshapes the output to a flattened shape.
687
+ - kernel: Either linear, gaussian, or callable
688
+ The kernel to apply to the matrix $(d(x,I), x \in \mathrm{pixels}, I\in \mathrm{summands})$.
689
+ signature should be : (distance matrix, summand_weights, bandwidth, p) -> representation
690
+
691
+ Returns
692
+ -------
693
+
694
+ The list of images, or the image of fixed dimension.
695
+ """
696
+ import matplotlib.pyplot as plt
697
+ # box = kwargs.get("box",[self.get_bottom(),self.get_top()])
698
+ if box is None:
699
+ box = self.get_box()
700
+ num_parameters = self.num_parameters
701
+ if degrees is None:
702
+ degrees = np.arange(self.max_degree +1)
703
+ num_degrees = len(degrees)
704
+ try:
705
+ int(resolution)
706
+ resolution = [resolution]*num_parameters
707
+ except:
708
+ pass
709
+
710
+ if grid is None:
711
+ grid = [np.linspace(*np.asarray(box)[:,parameter], num=res) for parameter, res in zip(range(num_parameters), resolution)]
712
+ else:
713
+ resolution = tuple(len(g) for g in grid)
714
+ coordinates = mpg.todense(grid)
715
+
716
+ if kernel == "linear":
717
+ assert not signed, "This kernel is not compatible with signed."
718
+ concatenated_images = self._compute_pixels(coordinates, degrees=degrees, box=box, delta=bandwidth, p=p, normalize=normalize,n_jobs=n_jobs)
719
+ else:
720
+ if kernel == "linear2":
721
+ def todo(PyModule_f64 mod_degree):
722
+ x = mod_degree.distance_to(coordinates,signed=signed)
723
+ w = mod_degree.get_interleavings()[None]**p
724
+ s = np.where(x>=0,1,-1) if signed else 1
725
+ x = np.abs(x)
726
+ return (s*np.where(x<bandwidth,(bandwidth-x)/bandwidth,0)*w).sum(1)
727
+ elif kernel == "gaussian":
728
+ def todo(PyModule_f64 mod_degree):
729
+ x = mod_degree.distance_to(coordinates,signed=signed)
730
+ w = mod_degree.get_interleavings()[None]**p
731
+ s = np.where(x>=0,1,-1) if signed else 1
732
+ return (s*np.exp( -.5*((x/bandwidth)**2))*w).sum(1)
733
+ elif kernel == "exponential":
734
+ def todo(PyModule_f64 mod_degree):
735
+ x = mod_degree.distance_to(coordinates,signed=signed)
736
+ w = mod_degree.get_interleavings()[None]**p
737
+ s = np.where(x>=0,1,-1) if signed else 1
738
+ x = np.abs(x)
739
+ return (s*np.exp( -((np.abs(x)/bandwidth)))*w).sum(1)
740
+ else:
741
+ assert callable(kernel), r"""
742
+ Kernel should be
743
+ gaussian, linear, linear2, exponential or callable,
744
+ with signature
745
+ (array[shape=(N, M)], array[shape=(M)])-> array(shape=(N)),
746
+ the first argument being a distance matrix (pts) vs (summands of the module)
747
+ and the second argument is a weight vector (weight(summand) for summand in module).
748
+ Note that the distance can be signed.
749
+ """
750
+ def todo(PyModule_f64 mod_degree):
751
+ x = mod_degree.distance_to(coordinates,signed=signed, n_jobs=n_jobs)
752
+ w = mod_degree.get_interleavings()[None]**p
753
+ return kernel(x/bandwidth,w)
754
+ concatenated_images = np.stack(
755
+ Parallel(n_jobs = n_jobs if n_jobs else -1, backend= "threading")(
756
+ delayed(todo)(self.get_module_of_degree(degree))
757
+ for degree in degrees
758
+ )
759
+ )
760
+
761
+ if flatten:
762
+ image_vector = concatenated_images.reshape((len(degrees),-1))
763
+ if plot:
764
+ raise ValueError("Unflatten to plot.")
765
+ return image_vector
766
+ else:
767
+ image_vector = concatenated_images.reshape((len(degrees),*resolution))
768
+ if plot:
769
+ assert num_parameters == 2, "Plot only available for 2-parameter modules"
770
+ import multipers.plots
771
+ i=0
772
+ n_plots = len(image_vector)
773
+ scale = 4
774
+ if n_plots >1:
775
+ fig, axs = plt.subplots(1,n_plots, figsize=(n_plots*scale,scale))
776
+ else:
777
+ fig = plt.gcf()
778
+ axs = [plt.gca()]
779
+ for image, degree, i in zip(image_vector, degrees, range(num_degrees)):
780
+ ax = axs[i]
781
+ temp = multipers.plots.plot_surface(grid, image, ax=ax)
782
+ plt.colorbar(temp, ax = ax)
783
+ if degree < 0 :
784
+ ax.set_title(rf"$H_{i}$ $2$-persistence image")
785
+ if degree >= 0:
786
+ ax.set_title(rf"$H_{degree}$ $2$-persistence image")
787
+ return image_vector
788
+
789
+ def euler_char(self, points:list|np.ndarray) -> np.ndarray:
790
+ r""" Computes the Euler Characteristic of the filtered complex at given (multiparameter) time
791
+
792
+ Parameters
793
+ ----------
794
+
795
+ points: list[float] | list[list[float]] | np.ndarray
796
+ List of filtration values on which to compute the euler characteristic.
797
+ WARNING FIXME : the points have to have the same dimension as the simplextree.
798
+
799
+ Returns
800
+ -------
801
+
802
+ The list of euler characteristic values
803
+ """
804
+ if len(points) == 0:
805
+ return []
806
+ if type(points[0]) is float:
807
+ points = [points]
808
+ if type(points) is np.ndarray:
809
+ assert len(points.shape) in [1,2]
810
+ if len(points.shape) == 1:
811
+ points = [points]
812
+
813
+ cdef double[:,:] points_view = np.asarray(points, dtype = np.float64)
814
+ cdef vector[One_critical_filtration[double]] c_points = _py2v1c_f64(points_view)
815
+ # cdef One_critical_filtration temp
816
+ # for point in points:
817
+ # temp.clear()
818
+ # for truc in point:
819
+ # temp.push_back(<double>(truc))
820
+ # c_points.push_back(temp)
821
+ cdef Module[double] c_mod = self.cmod
822
+ with nogil:
823
+ c_euler = c_mod.euler_curve(c_points)
824
+ euler = c_euler
825
+ return np.asarray(euler, dtype=int)
826
+ def to_idx(self,grid):
827
+ cdef vector[vector[double]] cgrid = grid
828
+ cdef vector[vector[pair[vector[vector[int]],vector[vector[int]]]]] out
829
+ with nogil:
830
+ out = self.cmod.to_idx(cgrid)
831
+ 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)
832
+
833
+ @cython.wraparound(False)
834
+ @cython.boundscheck(False)
835
+ def to_flat_idx(self,grid):
836
+ if len(self) == 0:
837
+ 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)
838
+ cdef vector[vector[double]] cgrid = grid
839
+ cdef vector[vector[vector[int]]] out
840
+ cdef int num_summands, num_births, num_deaths
841
+ cdef int num_parameters = self.num_parameters
842
+ with nogil:
843
+ out = self.cmod.to_flat_idx(cgrid)
844
+ num_summands = out[0][0].size()
845
+ num_births = out[1].size()
846
+ num_deaths = out[2].size()
847
+ idx = np.empty((2, num_summands),dtype=np.int32 )
848
+ births = np.empty((num_births,num_parameters),dtype=np.int32)
849
+ deaths = np.empty((num_deaths,num_parameters),dtype=np.int32)
850
+
851
+ cdef int32_t[:,:] idx_view = idx
852
+ cdef int32_t[:,:] births_view = births
853
+ cdef int32_t[:,:] deaths_view = deaths
854
+
855
+ with nogil:
856
+ for i in range(num_summands):
857
+ idx_view[0,i] = out[0][0][i]
858
+ idx_view[1,i] = out[0][1][i]
859
+ for i in range(num_births):
860
+ for j in range(num_parameters):
861
+ births_view[i,j] = out[1][i][j]
862
+ for i in range(num_deaths):
863
+ for j in range(num_parameters):
864
+ deaths_view[i,j] = out[2][i][j]
865
+
866
+ return idx, births,deaths
867
+
868
+ def distances_idx_to(self, pts, bool full=False, int n_jobs=1):
869
+ pts = np.asarray(pts)
870
+ if pts.ndim == 1:
871
+ pts = pts[None]
872
+ cdef vector[vector[double]] cpts = pts
873
+ cdef vector[vector[vector[int]]] out
874
+ with nogil:
875
+ out = self.cmod.compute_distances_idx_to(cpts,full, n_jobs)
876
+ return np.asarray(out, dtype=np.int32)
877
+
878
+ def distance_to(self, pts, bool signed=False, int n_jobs = 0)->np.ndarray:
879
+ r"""
880
+ Distance from a point to each summand's support.
881
+ Signed distance is the distance to the boundary,
882
+ with negative values inside the summands.
883
+
884
+ pts of shape (num_pts, num_parameters)
885
+
886
+ output shape : (num_pts,num_summands)
887
+ """
888
+ pts = np.asarray(pts)
889
+ if pts.ndim == 1:
890
+ pts = pts[None]
891
+ assert pts.shape[-1] == self.num_parameters
892
+ cdef vector[vector[double]] cpts = pts
893
+ # cdef vector[vector[double]] out
894
+ to_fill = np.empty(shape=(cpts.size(),len(self)), dtype = np.float64)
895
+ cdef double[:,:] c_to_fill = to_fill
896
+ cdef double* data_ptr = &c_to_fill[0,0]
897
+ with nogil:
898
+ self.cmod.compute_distances_to(data_ptr, cpts,signed, n_jobs)
899
+ return to_fill
900
+
901
+ def get_interleavings(self,box=None):
902
+ if box is None:
903
+ box = self.get_box()
904
+ cdef Box[double] cbox = Box[double](box)
905
+ return np.asarray(self.cmod.get_interleavings(cbox))
906
+
907
+ cdef class PyMultiDiagramPoint_f64:
908
+ cdef MultiDiagram_point[One_critical_filtration[double]] point
909
+ cdef set(self, MultiDiagram_point[One_critical_filtration[double]] pt):
910
+ self.point = pt
911
+ return self
912
+
913
+ def get_degree(self):
914
+ return self.point.get_dimension()
915
+ def get_birth(self):
916
+ cdef One_critical_filtration[double] v = self.point.get_birth()
917
+ return _ff21cview_f64(&v, copy=True)
918
+ def get_death(self):
919
+ cdef One_critical_filtration[double] v = self.point.get_death()
920
+ return _ff21cview_f64(&v, copy=True)
921
+
922
+
923
+ cdef class PyMultiDiagram_f64:
924
+ r"""
925
+ Stores the diagram of a PyModule on a line
926
+ """
927
+ cdef MultiDiagram[One_critical_filtration[double], double] multiDiagram
928
+ cdef set(self, MultiDiagram[One_critical_filtration[double], double] m):
929
+ self.multiDiagram = m
930
+ return self
931
+ def get_points(self, degree:int=-1) -> np.ndarray:
932
+ cdef vector[pair[vector[double],vector[double]]] out = self.multiDiagram.get_points(degree)
933
+ if len(out) == 0 and len(self) == 0:
934
+ return np.empty() # TODO Retrieve good number of parameters if there is no points in diagram
935
+ if len(out) == 0:
936
+ return np.empty((0,2,self.multiDiagram.at(0).get_dimension())) # gets the number of parameters
937
+ return np.array(out)
938
+ def to_multipers(self, dimension:int):
939
+ return self.multiDiagram.to_multipers(dimension)
940
+ def __len__(self) -> int:
941
+ return self.multiDiagram.size()
942
+ def __getitem__(self,i:int) -> PyMultiDiagramPoint_f64:
943
+ return PyMultiDiagramPoint_f64().set(self.multiDiagram.at(i % self.multiDiagram.size()))
944
+ cdef class PyMultiDiagrams_f64:
945
+ """
946
+ Stores the barcodes of a PyModule on multiple lines
947
+ """
948
+ cdef MultiDiagrams[One_critical_filtration[double], double] multiDiagrams
949
+ cdef set(self,MultiDiagrams[One_critical_filtration[double], double] m):
950
+ self.multiDiagrams = m
951
+ return self
952
+ def to_multipers(self):
953
+ out = self.multiDiagrams.to_multipers()
954
+ return [np.asarray(summand) for summand in out]
955
+ def __getitem__(self,i:int):
956
+ if i >=0 :
957
+ return PyMultiDiagram_f64().set(self.multiDiagrams.at(i))
958
+ else:
959
+ return PyMultiDiagram_f64().set(self.multiDiagrams.at( self.multiDiagrams.size() - i))
960
+ def __len__(self):
961
+ return self.multiDiagrams.size()
962
+ def get_points(self, degree:int=-1):
963
+ return self.multiDiagrams.get_points()
964
+ cdef _get_plot_bars(self, dimension:int=-1, min_persistence:float=0):
965
+ return self.multiDiagrams._for_python_plot(dimension, min_persistence);
966
+ def plot(self, degree:int=-1, min_persistence:float=0):
967
+ """
968
+ Plots the barcodes.
969
+
970
+ Parameters
971
+ ----------
972
+
973
+ - degree:int=-1
974
+ Only plots the bars of specified homology degree. Useful when the multidiagrams contains multiple dimenions
975
+ - min_persistence:float=0
976
+ Only plot bars of length greater than this value. Useful to reduce the time to plot.
977
+
978
+ Warning
979
+ -------
980
+
981
+ If the barcodes are not thresholded, essential barcodes will not be displayed !
982
+
983
+ """
984
+ from cycler import cycler
985
+ import matplotlib
986
+ import matplotlib.pyplot as plt
987
+ if len(self) == 0: return
988
+ _cmap = matplotlib.colormaps["Spectral"]
989
+ multibarcodes_, colors = self._get_plot_bars(degree, min_persistence)
990
+ n_summands = np.max(colors)+1 if len(colors)>0 else 1
991
+
992
+ plt.rc('axes', prop_cycle = cycler('color', [_cmap(i/n_summands) for i in colors]))
993
+ return plt.plot(*multibarcodes_)
994
+
995
+
996
+ cdef dump_summand_f64(Summand[double]& summand):
997
+ cdef vector[One_critical_filtration[double]] births = summand.get_birth_list()
998
+ cdef vector[One_critical_filtration[double]] deaths = summand.get_death_list()
999
+ return (
1000
+ np.array(_vff21cview_f64(births)),
1001
+ np.array(_vff21cview_f64(deaths)),
1002
+ summand.get_dimension(),
1003
+ )
1004
+
1005
+ cdef inline Summand[double] summand_from_dump_f64(summand_dump):
1006
+ cdef vector[double] births = _py2p2_f64(summand_dump[0])
1007
+ cdef vector[double] deaths = _py2p2_f64(summand_dump[1])
1008
+ cdef int dim = summand_dump[2]
1009
+ return Summand[double](births, deaths, summand_dump[0].shape[1], dim)
1010
+
1011
+ cdef dump_cmod_f64(Module[double]& mod):
1012
+ cdef Box[double] cbox = mod.get_box()
1013
+ cdef int dim = mod.get_dimension()
1014
+ # cannot cast const vector[double]& to vector[double] without the explicit cast
1015
+ cdef vector[double] bottom_corner = <vector[double]>cbox.get_lower_corner()
1016
+ cdef vector[double] top_corner = <vector[double]>cbox.get_upper_corner()
1017
+ box = np.asarray([bottom_corner, top_corner])
1018
+ summands = tuple(dump_summand_f64(summand) for summand in mod)
1019
+ return box, summands
1020
+
1021
+ cdef Module[double] cmod_from_dump_f64(module_dump):
1022
+ box = module_dump[0]
1023
+ summands = module_dump[1]
1024
+ cdef Module[double] out_module = Module[double]()
1025
+ out_module.set_box(Box[double](box))
1026
+ for i in range(len(summands)):
1027
+ out_module.add_summand(summand_from_dump_f64(summands[i]))
1028
+ return out_module
1029
+
1030
+
1031
+ def from_dump_f64(dump)->PyModule_f64:
1032
+ r"""Retrieves a PyModule from a previous dump.
1033
+
1034
+ Parameters
1035
+ ----------
1036
+
1037
+ dump: either the output of the dump function, or a file containing the output of a dump.
1038
+ The dumped module to retrieve
1039
+
1040
+ Returns
1041
+ -------
1042
+
1043
+ PyModule
1044
+ The retrieved module.
1045
+ """
1046
+ # TODO : optimize...
1047
+ mod = PyModule_f64()
1048
+ if type(dump) is str:
1049
+ dump = pickle.load(open(dump, "rb"))
1050
+ cdef Module[double] cmod = cmod_from_dump_f64(dump)
1051
+ mod.cmod = cmod
1052
+ return mod
1053
+
1054
+ cdef class PySummand_i32:
1055
+ r"""
1056
+ Stores a Summand of a PyModule
1057
+ """
1058
+ cdef Summand[int32_t] sum
1059
+
1060
+ def get_birth_list(self):
1061
+ cdef vector[One_critical_filtration[int32_t]] v = self.sum.get_birth_list()
1062
+ return _vff21cview_i32(v, copy = True)
1063
+
1064
+ def get_death_list(self):
1065
+ cdef vector[One_critical_filtration[int32_t]] v = self.sum.get_death_list()
1066
+ return _vff21cview_i32(v, copy = True)
1067
+ @property
1068
+ def degree(self)->int:
1069
+ return self.sum.get_dimension()
1070
+
1071
+ cdef set(self, Summand[int32_t]& summand):
1072
+ self.sum = summand
1073
+ return self
1074
+ def get_bounds(self):
1075
+ cdef pair[vector[int32_t],vector[int32_t]] cbounds
1076
+ with nogil:
1077
+ cbounds = self.sum.get_bounds().get_bounding_corners()
1078
+ # return _ff21cview_i32(&cbounds.first).copy(), _ff21cview_i32(&cbounds.second).copy()
1079
+ return np.asarray(cbounds.first), np.asarray(cbounds.second)
1080
+ @property
1081
+ def dtype(self):
1082
+ return np.int32
1083
+
1084
+ def num_parameters(self):
1085
+ cdef vector[One_critical_filtration[int32_t]] v = self.sum.get_birth_list()
1086
+ if v[0].is_finite():
1087
+ return v[0].num_parameters()
1088
+ v = self.sum.get_death_list()
1089
+ return v[0].num_parameters()
1090
+ def __eq__(self, PySummand_i32 other):
1091
+ return self.sum == other.sum
1092
+
1093
+
1094
+
1095
+
1096
+ cdef inline get_summand_filtration_values_i32(Summand[int32_t] summand):
1097
+ r"""
1098
+ Returns a list (over parameter) of the filtrations values of this parameter.
1099
+ """
1100
+ cdef vector[One_critical_filtration[int32_t]] vb = summand.get_birth_list()
1101
+ cdef vector[One_critical_filtration[int32_t]] vd = summand.get_death_list()
1102
+
1103
+ if vb[0].is_finite():
1104
+ if vd[0].is_finite():
1105
+ pts = np.concatenate([_vff21cview_i32(vb, copy=True),
1106
+ _vff21cview_i32(vd, copy=True)],axis=0)
1107
+ else:
1108
+ pts = np.array(_vff21cview_i32(vb, copy=True))
1109
+ else:
1110
+ if vd[0].is_finite():
1111
+ pts = np.array(_vff21cview_i32(vd, copy=True))
1112
+ else:
1113
+ return []
1114
+
1115
+ num_parameters = pts.shape[1]
1116
+ out = [np.unique(pts[:,parameter]) for parameter in range(num_parameters)]
1117
+ out = [f[:-1] if len(f)>0 and f[-1] == np.inf else f for f in out]
1118
+ out = [f[1:] if len(f)>0 and f[0] == -np.inf else f for f in out]
1119
+ return out
1120
+
1121
+ cdef class PyBox_i32:
1122
+ cdef Box[int32_t] box
1123
+ def __cinit__(self, vector[int32_t]& bottomCorner, vector[int32_t]& topCorner):
1124
+ self.box = Box[int32_t](bottomCorner, topCorner)
1125
+ @property
1126
+ def num_parameters(self):
1127
+ cdef size_t dim = self.box.get_lower_corner().size()
1128
+ if dim == self.box.get_upper_corner().size(): return dim
1129
+ else: print("Bad box definition.")
1130
+ def contains(self, x):
1131
+ return self.box.contains(x)
1132
+ cdef set(self, Box[int32_t]& b):
1133
+ self.box = b
1134
+ return self
1135
+
1136
+ def get(self):
1137
+ return [<vector[int32_t]>self.box.get_lower_corner(), <vector[int32_t]>self.box.get_upper_corner()]
1138
+ def to_multipers(self):
1139
+ #assert (self.get_dimension() == 2) "Multipers only works in dimension 2 !"
1140
+ return np.array(self.get()).flatten(order = 'F')
1141
+ @property
1142
+ def dtype(self):
1143
+ return np.int32
1144
+
1145
+
1146
+
1147
+ cdef class PyModule_i32:
1148
+ r"""
1149
+ Stores a representation of a n-persistence module.
1150
+ """
1151
+ cdef Module[int32_t] cmod
1152
+
1153
+ @property
1154
+ def dtype(self):
1155
+ return np.int32
1156
+
1157
+ cdef set(self, Module[int32_t] m):
1158
+ self.cmod = m
1159
+
1160
+ def __eq__(self, PyModule_i32 other):
1161
+ return self.cmod == other.cmod
1162
+
1163
+ def merge(self, PyModule_i32 other, int dim=-1):
1164
+ r"""
1165
+ Merges two modules into one
1166
+ """
1167
+ cdef Module[int32_t] c_other = other.cmod
1168
+ with nogil:
1169
+ for summand in c_other:
1170
+ self.cmod.add_summand(summand, dim)
1171
+ return self
1172
+ def _add_mmas(self, mmas):
1173
+ for mma in mmas:
1174
+ self.merge(mma)
1175
+ return self
1176
+
1177
+ def permute_summands(self, permutation):
1178
+ cdef int[:] c_perm = np.asarray(permutation, dtype=np.int32)
1179
+ other = PyModule_i32()
1180
+ other.cmod = self.cmod.permute_summands(permutation)
1181
+ return other
1182
+
1183
+ def _set_from_ptr(self, intptr_t module_ptr):
1184
+ r"""
1185
+ Copy module from a memory pointer. Unsafe.
1186
+ """
1187
+ self.cmod = move(dereference(<Module[int32_t]*>(module_ptr)))
1188
+ def _get_ptr(self):
1189
+ cdef intptr_t ptr = <intptr_t>(&self.cmod)
1190
+ return ptr
1191
+ def set_box(self, box):
1192
+ assert len(box) == 2, "Box format is [low, hight]"
1193
+ pybox = PyBox_i32(box[0], box[1])
1194
+ cdef Box[int32_t] cbox = pybox.box
1195
+ with nogil:
1196
+ self.cmod.set_box(cbox)
1197
+ return self
1198
+ def get_module_of_degree(self, int degree)->PyModule_i32: # TODO : in c++ ?
1199
+ r"""
1200
+ Returns a copy of a module of fixed degree.
1201
+ """
1202
+ pmodule = PyModule_i32()
1203
+ cdef Box[int32_t] c_box = self.cmod.get_box()
1204
+ with nogil:
1205
+ pmodule.cmod.set_box(c_box)
1206
+ for summand in self.cmod:
1207
+ if summand.get_dimension() == degree:
1208
+ pmodule.cmod.add_summand(summand)
1209
+ return pmodule
1210
+ def get_module_of_degrees(self, degrees:Iterable[int])->PyModule_i32: # TODO : in c++ ?
1211
+ r"""
1212
+ Returns a copy of the summands of degrees in `degrees`
1213
+ """
1214
+ pmodule = PyModule_i32()
1215
+ cdef Box[int32_t] c_box = self.cmod.get_box()
1216
+ pmodule.cmod.set_box(c_box)
1217
+ cdef vector[int] cdegrees = degrees
1218
+ with nogil:
1219
+ for summand in self.cmod:
1220
+ for d in cdegrees:
1221
+ if d == summand.get_dimension():
1222
+ pmodule.cmod.add_summand(summand)
1223
+ return pmodule
1224
+ def __len__(self)->int:
1225
+ return self.cmod.size()
1226
+ def get_bottom(self)->np.ndarray:
1227
+ r"""
1228
+ Bottom of the box of the module
1229
+ """
1230
+ return np.asarray(<vector[int32_t]>(self.cmod.get_box().get_lower_corner()))
1231
+ def get_top(self)->np.ndarray:
1232
+ r"""
1233
+ Top of the box of the module
1234
+ """
1235
+ return np.asarray(<vector[int32_t]>(self.cmod.get_box().get_upper_corner()))
1236
+ def get_box(self)->np.ndarray:
1237
+ r"""
1238
+ Returns the current bounding box of the module.
1239
+ """
1240
+ return np.asarray([self.get_bottom(), self.get_top()])
1241
+ @property
1242
+ def max_degree(self)->int:
1243
+ r"""
1244
+ Returns the maximum degree of the module.
1245
+ """
1246
+ return self.cmod.get_dimension()
1247
+ @property
1248
+ def num_parameters(self)->int:
1249
+ cdef size_t dim = self.cmod.get_box().get_lower_corner().size()
1250
+ assert dim == self.cmod.get_box().get_upper_corner().size(), "Bad box definition, cannot infer num_parameters."
1251
+ return dim
1252
+ def dump(self, path:str|None=None):
1253
+ r"""
1254
+ Dumps the module into a pickle-able format.
1255
+
1256
+ Parameters
1257
+ ----------
1258
+
1259
+ path:str=None (optional) saves the pickled module in specified path
1260
+
1261
+ Returns
1262
+ -------
1263
+
1264
+ list of list, encoding the module, which can be retrieved with the function `from_dump`.
1265
+ """
1266
+ ## TODO : optimize, but not really used.
1267
+ return dump_cmod_i32(self.cmod)
1268
+ def __getstate__(self):
1269
+ return self.dump()
1270
+ def __setstate__(self,dump):
1271
+ cdef Module[int32_t] cmod = cmod_from_dump_i32(dump)
1272
+ self.cmod = cmod
1273
+ return
1274
+ def __getitem__(self, int i) -> PySummand_i32:
1275
+ if i == slice(None):
1276
+ return self
1277
+ summand = PySummand_i32()
1278
+ summand.set(self.cmod.at(i % self.cmod.size()))
1279
+ return summand
1280
+ def __iter__(self):
1281
+ cdef int num_summands = self.cmod.size()
1282
+ for i in range(num_summands):
1283
+ summand = PySummand_i32()
1284
+ summand.set(self.cmod.at(i)) ## hmm copy. maybe do summand from ptr
1285
+ yield summand
1286
+
1287
+ def get_bounds(self):
1288
+ r"""
1289
+ Computes bounds from the summands' bounds.
1290
+ Useful to change this' box.
1291
+ """
1292
+ cdef pair[vector[int32_t],vector[int32_t]] cbounds
1293
+ with nogil:
1294
+ cbounds = self.cmod.get_bounds().get_bounding_corners()
1295
+ # return _ff21cview_i32(&cbounds.first).copy(), _ff21cview_i32(&cbounds.second).copy()
1296
+ return np.asarray(cbounds.first), np.asarray(cbounds.second)
1297
+ def rescale(self,rescale_factors, int degree=-1):
1298
+ r"""
1299
+ Rescales the fitlration values of the summands by this rescaling vector.
1300
+ """
1301
+ cdef vector[int32_t] crescale_factors = rescale_factors
1302
+ with nogil:
1303
+ self.cmod.rescale(crescale_factors,degree)
1304
+ def translate(self,translation, int degree=-1):
1305
+ r"""
1306
+ Translates the module in the filtration space by this vector.
1307
+ """
1308
+ cdef vector[int32_t] ctranslation = translation
1309
+ with nogil:
1310
+ self.cmod.translate(ctranslation,degree)
1311
+
1312
+ def get_filtration_values(self, bool unique=True):
1313
+ r"""
1314
+ Retrieves all filtration values of the summands of the module.
1315
+
1316
+ Output format
1317
+ -------------
1318
+
1319
+ list of filtration values for parameter.
1320
+ """
1321
+ if len(self) ==0:
1322
+ return np.empty((self.num_parameters,0))
1323
+ values = tuple(
1324
+ tuple(stuff)
1325
+ if len(stuff:=get_summand_filtration_values_i32(summand)) == self.num_parameters else list(stuff) + [[]]*(self.num_parameters - len(stuff)) for summand in self.cmod)
1326
+ try:
1327
+ values = tuple(np.concatenate([
1328
+ f[parameter]
1329
+ for f in values
1330
+ ], axis=0) for parameter in range(self.num_parameters)
1331
+ )
1332
+ except:
1333
+ return values
1334
+ if unique:
1335
+ return [np.unique(f) for f in values]
1336
+ return values
1337
+
1338
+ def plot(self, int degree=-1,**kwargs)->None:
1339
+ r"""Shows the module on a plot. Each color corresponds to an apprimation summand of the module, and its shape corresponds to its support.
1340
+ Only works with 2-parameter modules.
1341
+
1342
+ Parameters
1343
+ ----------
1344
+ degree = -1 : integer
1345
+ If positive returns only the image of dimension `dimension`.
1346
+ 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.
1347
+ If non-None, will plot the module on this specific rectangle.
1348
+ min_persistence =0 : float
1349
+ Only plots the summand with a persistence above this threshold.
1350
+ separated=False : bool
1351
+ If true, plot each summand in a different plot.
1352
+ alpha=1 : float
1353
+ Transparancy parameter
1354
+ save = False : string
1355
+ if nontrivial, will save the figure at this path
1356
+
1357
+
1358
+ Returns
1359
+ -------
1360
+ The figure of the plot.
1361
+ """
1362
+ from multipers.plots import plot2d_PyModule
1363
+ import matplotlib.pyplot as plt
1364
+ box = kwargs.pop('box', self.get_box())
1365
+ if (len(box[0]) != 2):
1366
+ print("Filtration size :", len(box[0]), " != 2")
1367
+ return
1368
+ if(degree < 0):
1369
+ dims = np.unique(self.get_dimensions())
1370
+ separated = kwargs.pop("separated", False)
1371
+ ndim = len(dims)
1372
+ scale = kwargs.pop("scale", 4)
1373
+ if separated:
1374
+ fig = None
1375
+ axes = None
1376
+ elif ndim > 1:
1377
+ fig, axes = plt.subplots(1,ndim, figsize=(ndim*scale,scale))
1378
+ else:
1379
+ fig = plt.gcf()
1380
+ axes = [plt.gca()]
1381
+ for dim_idx in range(ndim):
1382
+ if not separated:
1383
+ plt.sca(axes[dim_idx])
1384
+ self.plot(dims[dim_idx],box=box, separated = separated, **kwargs)
1385
+ return
1386
+ corners = self.cmod.get_corners_of_dimension(degree)
1387
+ interleavings = self.get_module_of_degree(degree).get_interleavings()
1388
+ plot2d_PyModule(corners, box=box, dimension=degree, interleavings = interleavings, **kwargs)
1389
+ return
1390
+ def degree_splits(self):
1391
+ return np.asarray(self.cmod.get_degree_splits(), dtype=np.int64)
1392
+
1393
+
1394
+ cdef dump_summand_i32(Summand[int32_t]& summand):
1395
+ cdef vector[One_critical_filtration[int32_t]] births = summand.get_birth_list()
1396
+ cdef vector[One_critical_filtration[int32_t]] deaths = summand.get_death_list()
1397
+ return (
1398
+ np.array(_vff21cview_i32(births)),
1399
+ np.array(_vff21cview_i32(deaths)),
1400
+ summand.get_dimension(),
1401
+ )
1402
+
1403
+ cdef inline Summand[int32_t] summand_from_dump_i32(summand_dump):
1404
+ cdef vector[int32_t] births = _py2p2_i32(summand_dump[0])
1405
+ cdef vector[int32_t] deaths = _py2p2_i32(summand_dump[1])
1406
+ cdef int dim = summand_dump[2]
1407
+ return Summand[int32_t](births, deaths, summand_dump[0].shape[1], dim)
1408
+
1409
+ cdef dump_cmod_i32(Module[int32_t]& mod):
1410
+ cdef Box[int32_t] cbox = mod.get_box()
1411
+ cdef int dim = mod.get_dimension()
1412
+ # cannot cast const vector[int32_t]& to vector[int32_t] without the explicit cast
1413
+ cdef vector[int32_t] bottom_corner = <vector[int32_t]>cbox.get_lower_corner()
1414
+ cdef vector[int32_t] top_corner = <vector[int32_t]>cbox.get_upper_corner()
1415
+ box = np.asarray([bottom_corner, top_corner])
1416
+ summands = tuple(dump_summand_i32(summand) for summand in mod)
1417
+ return box, summands
1418
+
1419
+ cdef Module[int32_t] cmod_from_dump_i32(module_dump):
1420
+ box = module_dump[0]
1421
+ summands = module_dump[1]
1422
+ cdef Module[int32_t] out_module = Module[int32_t]()
1423
+ out_module.set_box(Box[int32_t](box))
1424
+ for i in range(len(summands)):
1425
+ out_module.add_summand(summand_from_dump_i32(summands[i]))
1426
+ return out_module
1427
+
1428
+
1429
+ def from_dump_i32(dump)->PyModule_i32:
1430
+ r"""Retrieves a PyModule from a previous dump.
1431
+
1432
+ Parameters
1433
+ ----------
1434
+
1435
+ dump: either the output of the dump function, or a file containing the output of a dump.
1436
+ The dumped module to retrieve
1437
+
1438
+ Returns
1439
+ -------
1440
+
1441
+ PyModule
1442
+ The retrieved module.
1443
+ """
1444
+ # TODO : optimize...
1445
+ mod = PyModule_i32()
1446
+ if type(dump) is str:
1447
+ dump = pickle.load(open(dump, "rb"))
1448
+ cdef Module[int32_t] cmod = cmod_from_dump_i32(dump)
1449
+ mod.cmod = cmod
1450
+ return mod
1451
+
1452
+
1453
+
1454
+ global PyModule_type, PySummand_type
1455
+ PyModule_type = Union[
1456
+ PyModule_f64,
1457
+ PyModule_i32,
1458
+ ]
1459
+ PySummand_type = Union[
1460
+ PySummand_f64,
1461
+ PySummand_i32,
1462
+ ]
1463
+
1464
+ PyBox_type = Union[
1465
+ PyBox_f64,
1466
+ PyBox_i32,
1467
+ ]
1468
+
1469
+
1470
+ PyMultiDiagram_type = Union[
1471
+ PyMultiDiagram_f64,
1472
+ ]
1473
+
1474
+
1475
+ PyMultiDiagrams_type = Union[
1476
+ PyMultiDiagrams_f64,
1477
+ ]
1478
+
1479
+ def is_mma(stuff):
1480
+ return (False
1481
+ or isinstance(stuff,PyModule_f64)
1482
+ or isinstance(stuff,PyModule_i32)
1483
+ )