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