multipers 2.3.3b6__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.

Potentially problematic release.


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

Files changed (183) hide show
  1. multipers/.dylibs/libc++.1.0.dylib +0 -0
  2. multipers/.dylibs/libtbb.12.16.dylib +0 -0
  3. multipers/__init__.py +33 -0
  4. multipers/_signed_measure_meta.py +453 -0
  5. multipers/_slicer_meta.py +211 -0
  6. multipers/array_api/__init__.py +45 -0
  7. multipers/array_api/numpy.py +41 -0
  8. multipers/array_api/torch.py +58 -0
  9. multipers/data/MOL2.py +458 -0
  10. multipers/data/UCR.py +18 -0
  11. multipers/data/__init__.py +1 -0
  12. multipers/data/graphs.py +466 -0
  13. multipers/data/immuno_regions.py +27 -0
  14. multipers/data/minimal_presentation_to_st_bf.py +0 -0
  15. multipers/data/pytorch2simplextree.py +91 -0
  16. multipers/data/shape3d.py +101 -0
  17. multipers/data/synthetic.py +113 -0
  18. multipers/distances.py +202 -0
  19. multipers/filtration_conversions.pxd +229 -0
  20. multipers/filtration_conversions.pxd.tp +84 -0
  21. multipers/filtrations/__init__.py +18 -0
  22. multipers/filtrations/density.py +574 -0
  23. multipers/filtrations/filtrations.py +361 -0
  24. multipers/filtrations.pxd +224 -0
  25. multipers/function_rips.cpython-312-darwin.so +0 -0
  26. multipers/function_rips.pyx +105 -0
  27. multipers/grids.cpython-312-darwin.so +0 -0
  28. multipers/grids.pyx +433 -0
  29. multipers/gudhi/Persistence_slices_interface.h +132 -0
  30. multipers/gudhi/Simplex_tree_interface.h +239 -0
  31. multipers/gudhi/Simplex_tree_multi_interface.h +551 -0
  32. multipers/gudhi/cubical_to_boundary.h +59 -0
  33. multipers/gudhi/gudhi/Bitmap_cubical_complex.h +450 -0
  34. multipers/gudhi/gudhi/Bitmap_cubical_complex_base.h +1070 -0
  35. multipers/gudhi/gudhi/Bitmap_cubical_complex_periodic_boundary_conditions_base.h +579 -0
  36. multipers/gudhi/gudhi/Debug_utils.h +45 -0
  37. multipers/gudhi/gudhi/Fields/Multi_field.h +484 -0
  38. multipers/gudhi/gudhi/Fields/Multi_field_operators.h +455 -0
  39. multipers/gudhi/gudhi/Fields/Multi_field_shared.h +450 -0
  40. multipers/gudhi/gudhi/Fields/Multi_field_small.h +531 -0
  41. multipers/gudhi/gudhi/Fields/Multi_field_small_operators.h +507 -0
  42. multipers/gudhi/gudhi/Fields/Multi_field_small_shared.h +531 -0
  43. multipers/gudhi/gudhi/Fields/Z2_field.h +355 -0
  44. multipers/gudhi/gudhi/Fields/Z2_field_operators.h +376 -0
  45. multipers/gudhi/gudhi/Fields/Zp_field.h +420 -0
  46. multipers/gudhi/gudhi/Fields/Zp_field_operators.h +400 -0
  47. multipers/gudhi/gudhi/Fields/Zp_field_shared.h +418 -0
  48. multipers/gudhi/gudhi/Flag_complex_edge_collapser.h +337 -0
  49. multipers/gudhi/gudhi/Matrix.h +2107 -0
  50. multipers/gudhi/gudhi/Multi_critical_filtration.h +1038 -0
  51. multipers/gudhi/gudhi/Multi_persistence/Box.h +174 -0
  52. multipers/gudhi/gudhi/Multi_persistence/Line.h +282 -0
  53. multipers/gudhi/gudhi/Off_reader.h +173 -0
  54. multipers/gudhi/gudhi/One_critical_filtration.h +1441 -0
  55. multipers/gudhi/gudhi/Persistence_matrix/Base_matrix.h +769 -0
  56. multipers/gudhi/gudhi/Persistence_matrix/Base_matrix_with_column_compression.h +686 -0
  57. multipers/gudhi/gudhi/Persistence_matrix/Boundary_matrix.h +842 -0
  58. multipers/gudhi/gudhi/Persistence_matrix/Chain_matrix.h +1350 -0
  59. multipers/gudhi/gudhi/Persistence_matrix/Id_to_index_overlay.h +1105 -0
  60. multipers/gudhi/gudhi/Persistence_matrix/Position_to_index_overlay.h +859 -0
  61. multipers/gudhi/gudhi/Persistence_matrix/RU_matrix.h +910 -0
  62. multipers/gudhi/gudhi/Persistence_matrix/allocators/entry_constructors.h +139 -0
  63. multipers/gudhi/gudhi/Persistence_matrix/base_pairing.h +230 -0
  64. multipers/gudhi/gudhi/Persistence_matrix/base_swap.h +211 -0
  65. multipers/gudhi/gudhi/Persistence_matrix/boundary_cell_position_to_id_mapper.h +60 -0
  66. multipers/gudhi/gudhi/Persistence_matrix/boundary_face_position_to_id_mapper.h +60 -0
  67. multipers/gudhi/gudhi/Persistence_matrix/chain_pairing.h +136 -0
  68. multipers/gudhi/gudhi/Persistence_matrix/chain_rep_cycles.h +190 -0
  69. multipers/gudhi/gudhi/Persistence_matrix/chain_vine_swap.h +616 -0
  70. multipers/gudhi/gudhi/Persistence_matrix/columns/chain_column_extra_properties.h +150 -0
  71. multipers/gudhi/gudhi/Persistence_matrix/columns/column_dimension_holder.h +106 -0
  72. multipers/gudhi/gudhi/Persistence_matrix/columns/column_utilities.h +219 -0
  73. multipers/gudhi/gudhi/Persistence_matrix/columns/entry_types.h +327 -0
  74. multipers/gudhi/gudhi/Persistence_matrix/columns/heap_column.h +1140 -0
  75. multipers/gudhi/gudhi/Persistence_matrix/columns/intrusive_list_column.h +934 -0
  76. multipers/gudhi/gudhi/Persistence_matrix/columns/intrusive_set_column.h +934 -0
  77. multipers/gudhi/gudhi/Persistence_matrix/columns/list_column.h +980 -0
  78. multipers/gudhi/gudhi/Persistence_matrix/columns/naive_vector_column.h +1092 -0
  79. multipers/gudhi/gudhi/Persistence_matrix/columns/row_access.h +192 -0
  80. multipers/gudhi/gudhi/Persistence_matrix/columns/set_column.h +921 -0
  81. multipers/gudhi/gudhi/Persistence_matrix/columns/small_vector_column.h +1093 -0
  82. multipers/gudhi/gudhi/Persistence_matrix/columns/unordered_set_column.h +1012 -0
  83. multipers/gudhi/gudhi/Persistence_matrix/columns/vector_column.h +1244 -0
  84. multipers/gudhi/gudhi/Persistence_matrix/matrix_dimension_holders.h +186 -0
  85. multipers/gudhi/gudhi/Persistence_matrix/matrix_row_access.h +164 -0
  86. multipers/gudhi/gudhi/Persistence_matrix/ru_pairing.h +156 -0
  87. multipers/gudhi/gudhi/Persistence_matrix/ru_rep_cycles.h +376 -0
  88. multipers/gudhi/gudhi/Persistence_matrix/ru_vine_swap.h +540 -0
  89. multipers/gudhi/gudhi/Persistent_cohomology/Field_Zp.h +118 -0
  90. multipers/gudhi/gudhi/Persistent_cohomology/Multi_field.h +173 -0
  91. multipers/gudhi/gudhi/Persistent_cohomology/Persistent_cohomology_column.h +128 -0
  92. multipers/gudhi/gudhi/Persistent_cohomology.h +745 -0
  93. multipers/gudhi/gudhi/Points_off_io.h +171 -0
  94. multipers/gudhi/gudhi/Simple_object_pool.h +69 -0
  95. multipers/gudhi/gudhi/Simplex_tree/Simplex_tree_iterators.h +463 -0
  96. multipers/gudhi/gudhi/Simplex_tree/Simplex_tree_node_explicit_storage.h +83 -0
  97. multipers/gudhi/gudhi/Simplex_tree/Simplex_tree_siblings.h +106 -0
  98. multipers/gudhi/gudhi/Simplex_tree/Simplex_tree_star_simplex_iterators.h +277 -0
  99. multipers/gudhi/gudhi/Simplex_tree/hooks_simplex_base.h +62 -0
  100. multipers/gudhi/gudhi/Simplex_tree/indexing_tag.h +27 -0
  101. multipers/gudhi/gudhi/Simplex_tree/serialization_utils.h +62 -0
  102. multipers/gudhi/gudhi/Simplex_tree/simplex_tree_options.h +157 -0
  103. multipers/gudhi/gudhi/Simplex_tree.h +2794 -0
  104. multipers/gudhi/gudhi/Simplex_tree_multi.h +152 -0
  105. multipers/gudhi/gudhi/distance_functions.h +62 -0
  106. multipers/gudhi/gudhi/graph_simplicial_complex.h +104 -0
  107. multipers/gudhi/gudhi/persistence_interval.h +253 -0
  108. multipers/gudhi/gudhi/persistence_matrix_options.h +170 -0
  109. multipers/gudhi/gudhi/reader_utils.h +367 -0
  110. multipers/gudhi/mma_interface_coh.h +256 -0
  111. multipers/gudhi/mma_interface_h0.h +223 -0
  112. multipers/gudhi/mma_interface_matrix.h +293 -0
  113. multipers/gudhi/naive_merge_tree.h +536 -0
  114. multipers/gudhi/scc_io.h +310 -0
  115. multipers/gudhi/truc.h +1403 -0
  116. multipers/io.cpython-312-darwin.so +0 -0
  117. multipers/io.pyx +644 -0
  118. multipers/ml/__init__.py +0 -0
  119. multipers/ml/accuracies.py +90 -0
  120. multipers/ml/invariants_with_persistable.py +79 -0
  121. multipers/ml/kernels.py +176 -0
  122. multipers/ml/mma.py +713 -0
  123. multipers/ml/one.py +472 -0
  124. multipers/ml/point_clouds.py +352 -0
  125. multipers/ml/signed_measures.py +1589 -0
  126. multipers/ml/sliced_wasserstein.py +461 -0
  127. multipers/ml/tools.py +113 -0
  128. multipers/mma_structures.cpython-312-darwin.so +0 -0
  129. multipers/mma_structures.pxd +128 -0
  130. multipers/mma_structures.pyx +2786 -0
  131. multipers/mma_structures.pyx.tp +1094 -0
  132. multipers/multi_parameter_rank_invariant/diff_helpers.h +84 -0
  133. multipers/multi_parameter_rank_invariant/euler_characteristic.h +97 -0
  134. multipers/multi_parameter_rank_invariant/function_rips.h +322 -0
  135. multipers/multi_parameter_rank_invariant/hilbert_function.h +769 -0
  136. multipers/multi_parameter_rank_invariant/persistence_slices.h +148 -0
  137. multipers/multi_parameter_rank_invariant/rank_invariant.h +369 -0
  138. multipers/multiparameter_edge_collapse.py +41 -0
  139. multipers/multiparameter_module_approximation/approximation.h +2330 -0
  140. multipers/multiparameter_module_approximation/combinatory.h +129 -0
  141. multipers/multiparameter_module_approximation/debug.h +107 -0
  142. multipers/multiparameter_module_approximation/euler_curves.h +0 -0
  143. multipers/multiparameter_module_approximation/format_python-cpp.h +286 -0
  144. multipers/multiparameter_module_approximation/heap_column.h +238 -0
  145. multipers/multiparameter_module_approximation/images.h +79 -0
  146. multipers/multiparameter_module_approximation/list_column.h +174 -0
  147. multipers/multiparameter_module_approximation/list_column_2.h +232 -0
  148. multipers/multiparameter_module_approximation/ru_matrix.h +347 -0
  149. multipers/multiparameter_module_approximation/set_column.h +135 -0
  150. multipers/multiparameter_module_approximation/structure_higher_dim_barcode.h +36 -0
  151. multipers/multiparameter_module_approximation/unordered_set_column.h +166 -0
  152. multipers/multiparameter_module_approximation/utilities.h +403 -0
  153. multipers/multiparameter_module_approximation/vector_column.h +223 -0
  154. multipers/multiparameter_module_approximation/vector_matrix.h +331 -0
  155. multipers/multiparameter_module_approximation/vineyards.h +464 -0
  156. multipers/multiparameter_module_approximation/vineyards_trajectories.h +649 -0
  157. multipers/multiparameter_module_approximation.cpython-312-darwin.so +0 -0
  158. multipers/multiparameter_module_approximation.pyx +235 -0
  159. multipers/pickle.py +90 -0
  160. multipers/plots.py +456 -0
  161. multipers/point_measure.cpython-312-darwin.so +0 -0
  162. multipers/point_measure.pyx +395 -0
  163. multipers/simplex_tree_multi.cpython-312-darwin.so +0 -0
  164. multipers/simplex_tree_multi.pxd +134 -0
  165. multipers/simplex_tree_multi.pyx +10840 -0
  166. multipers/simplex_tree_multi.pyx.tp +2009 -0
  167. multipers/slicer.cpython-312-darwin.so +0 -0
  168. multipers/slicer.pxd +3034 -0
  169. multipers/slicer.pxd.tp +234 -0
  170. multipers/slicer.pyx +20481 -0
  171. multipers/slicer.pyx.tp +1088 -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 +62 -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-2.3.3b6.dist-info/METADATA +128 -0
  180. multipers-2.3.3b6.dist-info/RECORD +183 -0
  181. multipers-2.3.3b6.dist-info/WHEEL +6 -0
  182. multipers-2.3.3b6.dist-info/licenses/LICENSE +21 -0
  183. multipers-2.3.3b6.dist-info/top_level.txt +1 -0
@@ -0,0 +1,2009 @@
1
+ {{py:
2
+
3
+ """
4
+ Templated simplextree multi
5
+ """
6
+
7
+ import pickle
8
+ with open("build/tmp/_simplextrees_.pkl", "rb") as f:
9
+ to_iter = pickle.load(f)
10
+
11
+ st_map = {}
12
+ for CTYPE,PYTYPE, SHORT,Filtration, is_kcritical, FSHORT in to_iter:
13
+ st_map[PYTYPE, is_kcritical] = CTYPE
14
+
15
+ }}
16
+
17
+
18
+
19
+
20
+ # This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT.
21
+ # See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details.
22
+ # Author(s): Vincent Rouvreau
23
+ #
24
+ # Copyright (C) 2016 Inria
25
+ #
26
+ # Modification(s):
27
+ # - 2023 David Loiseaux : Conversions with standard simplextree, scc2020 format, edge collapses, euler characteristic, grid filtrations.
28
+ # - 2022/11 Hannah Schreiber / David Loiseaux : adapt for multipersistence.
29
+ # - YYYY/MM Author: Description of the modification
30
+
31
+
32
+
33
+ __author__ = "Vincent Rouvreau"
34
+ __copyright__ = "Copyright (C) 2016 Inria"
35
+ __license__ = "MIT"
36
+
37
+ from libc.stdint cimport intptr_t, int32_t, int64_t
38
+ from cython.operator import dereference, preincrement
39
+ from libc.stdint cimport intptr_t
40
+ from libc.stdint cimport uintptr_t, intptr_t
41
+ from libcpp.map cimport map
42
+ from libcpp.utility cimport pair
43
+
44
+ ctypedef fused some_int:
45
+ int32_t
46
+ int64_t
47
+ int
48
+
49
+ ctypedef fused some_float:
50
+ float
51
+ double
52
+ int32_t
53
+ int64_t
54
+
55
+ ctypedef vector[pair[pair[int,int],pair[float,float]]] edge_list_type
56
+
57
+ from typing import Any, Union, ClassVar
58
+
59
+ cimport numpy as cnp
60
+ import numpy as np
61
+ cnp.import_array()
62
+
63
+ from multipers.simplex_tree_multi cimport *
64
+ from multipers.filtration_conversions cimport *
65
+ cimport cython
66
+ from gudhi.simplex_tree import SimplexTree ## Small hack for typing
67
+ from typing import Iterable,Literal,Optional
68
+ from tqdm import tqdm
69
+ from multipers.grids import Lstrategies, compute_grid, sanitize_grid
70
+ from multipers.array_api import api_from_tensor
71
+ from multipers.point_measure import signed_betti, rank_decomposition_by_rectangles, sparsify
72
+
73
+ from warnings import warn
74
+
75
+ SAFE_CONVERSION=False #Slower but at least it works everywhere
76
+
77
+ _available_strategies =Lstrategies
78
+
79
+
80
+
81
+ {{for CTYPE,PYTYPE,SHORT,Filtration, is_kcritical, FSHORT in to_iter}}
82
+
83
+
84
+
85
+ ctypedef {{Filtration}} {{FSHORT}}
86
+
87
+
88
+
89
+ # SimplexTree python interface
90
+ cdef class SimplexTreeMulti_{{FSHORT}}:
91
+ """The simplex tree is an efficient and flexible data structure for
92
+ representing general (filtered) simplicial complexes. The data structure
93
+ is described in Jean-Daniel Boissonnat and Clément Maria. The Simplex
94
+ Tree: An Efficient Data Structure for General Simplicial Complexes.
95
+ Algorithmica, pages 1–22, 2014.
96
+
97
+ This class is a multi-filtered, with keys, and non contiguous vertices version
98
+ of the simplex tree.
99
+ """
100
+ cdef public intptr_t thisptr
101
+
102
+ cdef public object filtration_grid
103
+ cdef public bool _is_function_simplextree # TODO : deprecate
104
+ # Get the pointer casted as it should be
105
+ cdef Simplex_tree_multi_interface[{{FSHORT}}, {{CTYPE}}]* get_ptr(self) noexcept nogil:
106
+ return <Simplex_tree_multi_interface[{{FSHORT}}, {{CTYPE}}]*>(self.thisptr)
107
+
108
+ # cdef Simplex_tree_persistence_interface * pcohptr
109
+ # Fake constructor that does nothing but documenting the constructor
110
+ def __init__(self, other = None, num_parameters:int=-1,default_values=[], safe_conversion=False):
111
+ """SimplexTreeMulti constructor.
112
+
113
+ :param other: If `other` is `None` (default value), an empty `SimplexTreeMulti` is created.
114
+ If `other` is a `SimplexTree`, the `SimplexTreeMulti` is constructed from a deep copy of `other`.
115
+ If `other` is a `SimplexTreeMulti`, the `SimplexTreeMulti` is constructed from a deep copy of `other`.
116
+ :type other: SimplexTree or SimplexTreeMulti (Optional)
117
+ :param num_parameters: The number of parameter of the multi-parameter filtration.
118
+ :type num_parameters: int
119
+ :returns: An empty or a copy simplex tree.
120
+ :rtype: SimplexTreeMulti
121
+
122
+ :raises TypeError: In case `other` is neither `None`, nor a `SimplexTree`, nor a `SimplexTreeMulti`.
123
+ """
124
+
125
+ @staticmethod
126
+ cdef {{CTYPE}} T_minus_inf():
127
+ {{if SHORT[0] == 'f'}}
128
+ return <{{CTYPE}}>(-np.inf)
129
+ {{else}}
130
+ return <{{CTYPE}}>(np.iinfo({{PYTYPE}}).min)
131
+ {{endif}}
132
+ @staticmethod
133
+ cdef {{CTYPE}} T_inf():
134
+ {{if SHORT[0] == 'f'}}
135
+ return <{{CTYPE}}>(np.inf)
136
+ {{else}}
137
+ return <{{CTYPE}}>(np.iinfo({{PYTYPE}}).max)
138
+ {{endif}}
139
+ @property
140
+ def is_kcritical(self)->bool:
141
+ return {{is_kcritical}}
142
+ # The real cython constructor
143
+ def __cinit__(self, other = None, int num_parameters=-1,
144
+ default_values=np.asarray([SimplexTreeMulti_{{FSHORT}}.T_minus_inf()]), # I'm not sure why `[]` does not work. Cython bug ?
145
+ bool safe_conversion=False,
146
+ ): #TODO doc
147
+ {{if is_kcritical}}
148
+ cdef {{FSHORT}} c_default_values = _py2kc_{{SHORT}}(np.asarray([default_values], dtype= {{PYTYPE}}))
149
+ {{else}}
150
+ cdef {{FSHORT}} c_default_values = _py21c_{{SHORT}}(np.asarray(default_values, dtype={{PYTYPE}}))
151
+ {{endif}}
152
+ cdef intptr_t other_ptr
153
+ cdef char[:] buffer
154
+ cdef size_t buffer_size
155
+ cdef char* buffer_start
156
+ if other is not None:
157
+ if isinstance(other, SimplexTreeMulti_{{FSHORT}}):
158
+ other_ptr = other.thisptr
159
+ self.thisptr = <intptr_t>(new Simplex_tree_multi_interface[{{FSHORT}}, {{CTYPE}}](dereference(<Simplex_tree_multi_interface[{{FSHORT}}, {{CTYPE}}]*>other_ptr))) ## prevents calling destructor of other
160
+ if num_parameters <=0:
161
+ num_parameters = other.num_parameters
162
+ self.filtration_grid = other.filtration_grid
163
+ elif isinstance(other, SimplexTree): # Constructs a SimplexTreeMulti from a SimplexTree
164
+ self.thisptr = <intptr_t>(new Simplex_tree_multi_interface[{{FSHORT}}, {{CTYPE}}]())
165
+ if num_parameters <= 0:
166
+ num_parameters = 1
167
+ if safe_conversion or SAFE_CONVERSION:
168
+ new_st_multi = _safe_simplextree_multify_{{FSHORT}}(other, num_parameters = num_parameters, default_values=np.asarray(default_values))
169
+ self.thisptr, new_st_multi.thisptr = new_st_multi.thisptr, self.thisptr
170
+ else:
171
+ stree_buffer = other.__getstate__()
172
+ buffer = stree_buffer
173
+ buffer_size = buffer.shape[0]
174
+ buffer_start = &buffer[0]
175
+
176
+ with nogil:
177
+ self.get_ptr().from_std(buffer_start, buffer_size, num_parameters, c_default_values)
178
+ else:
179
+ raise TypeError("`other` argument requires to be of type `SimplexTree`, `SimplexTreeMulti`, or `None`.")
180
+ else:
181
+ if num_parameters <=0:
182
+ num_parameters = 2 # I don't know how dangerous this is, but this is mostly used.
183
+ self.thisptr = <intptr_t>(new Simplex_tree_multi_interface[{{FSHORT}}, {{CTYPE}}]())
184
+ self.set_num_parameter(num_parameters)
185
+ self._is_function_simplextree = False
186
+ self.filtration_grid=[]
187
+
188
+ def __dealloc__(self):
189
+ cdef Simplex_tree_multi_interface[{{FSHORT}},{{CTYPE}}]* ptr = self.get_ptr()
190
+ if ptr != NULL:
191
+ del ptr
192
+ # TODO : is that enough ??
193
+
194
+ def __repr__(self):
195
+ return f"SimplexTreeMulti[dtype={np.dtype(self.dtype).name},num_param={self.num_parameters},kcritical={self.is_kcritical},is_squeezed={self.is_squeezed},max_dim={self.dimension}]"
196
+ def __len__(self):
197
+ return self.num_simplices
198
+
199
+ def __getstate__(self):
200
+ """:returns: Serialized (or flattened) SimplexTree data structure in order to pickle SimplexTree.
201
+ :rtype: numpy.array of shape (n,)
202
+ """
203
+ cdef size_t buffer_size = self.get_ptr().get_serialization_size()
204
+ # Let's use numpy to allocate a buffer. Will be deleted automatically
205
+ np_buffer = np.empty(buffer_size, dtype='B')
206
+ cdef char[:] buffer = np_buffer
207
+ cdef char* buffer_start = &buffer[0]
208
+ with nogil:
209
+ self.get_ptr().serialize(buffer_start, buffer_size)
210
+
211
+ return np_buffer
212
+ def __setstate__(self, state):
213
+ """Construct the SimplexTree data structure from a Numpy Array (cf. :func:`~gudhi.SimplexTree.__getstate__`)
214
+ in order to unpickle a SimplexTree.
215
+
216
+ :param state: Serialized SimplexTree data structure
217
+ :type state: numpy.array of shape (n,)
218
+ """
219
+ cdef char[:] buffer = state
220
+ cdef size_t buffer_size = state.shape[0]
221
+ cdef char* buffer_start = &buffer[0]
222
+ with nogil:
223
+ # deserialization requires an empty SimplexTree
224
+ self.get_ptr().clear()
225
+ # New pointer is a deserialized simplex tree
226
+ self.get_ptr().deserialize(buffer_start, buffer_size)
227
+
228
+
229
+ def copy(self)->SimplexTreeMulti_{{FSHORT}}:
230
+ """
231
+ :returns: A simplex tree that is a deep copy of itself.
232
+ :rtype: SimplexTreeMulti
233
+
234
+ :note: The persistence information is not copied. If you need it in the clone, you have to call
235
+ :func:`compute_persistence` on it even if you had already computed it in the original.
236
+ """
237
+ stree = SimplexTreeMulti_{{FSHORT}}(self,num_parameters=self.num_parameters)
238
+ stree.filtration_grid = self.filtration_grid
239
+ stree._is_function_simplextree = self._is_function_simplextree
240
+ return stree
241
+
242
+ def __deepcopy__(self):
243
+ return self.copy()
244
+
245
+ def filtration(self, simplex:list|np.ndarray)->np.ndarray:
246
+ """This function returns the filtration value for a given N-simplex in
247
+ this simplicial complex, or +infinity if it is not in the complex.
248
+ :param simplex: The N-simplex, represented by a list of vertex.
249
+ :type simplex: list of int
250
+ :returns: The simplicial complex multi-critical filtration value.
251
+ :rtype: numpy array of shape (-1, num_parameters)
252
+ """
253
+ return self[simplex]
254
+
255
+ def assign_filtration(self, simplex:list|np.ndarray, filtration:list|np.ndarray)->SimplexTreeMulti_{{FSHORT}}:
256
+ """This function assigns a new multi-critical filtration value to a
257
+ given N-simplex.
258
+
259
+ :param simplex: The N-simplex, represented by a list of vertex.
260
+ :type simplex: list of int
261
+ :param filtration: The new filtration(s) value(s), concatenated.
262
+ :type filtration: list[float] or np.ndarray[float, ndim=1]
263
+
264
+ .. note::
265
+ Beware that after this operation, the structure may not be a valid
266
+ filtration anymore, a simplex could have a lower filtration value
267
+ than one of its faces. Callers are responsible for fixing this
268
+ (with more :meth:`assign_filtration` or
269
+ :meth:`make_filtration_non_decreasing` for instance) before calling
270
+ any function that relies on the filtration property, like
271
+ :meth:`persistence`.
272
+ """
273
+ assert len(filtration)>0 and len(filtration) % self.get_ptr().get_number_of_parameters() == 0
274
+ # self.get_ptr().assign_simplex_filtration(simplex, {{FSHORT}}(<python_filtration_type>filtration))
275
+ {{if is_kcritical}}
276
+ filtration = np.asarray(filtration, dtype={{PYTYPE}})[None,:]
277
+ self.get_ptr().assign_simplex_filtration(simplex, _py2kc_{{SHORT}}(filtration))
278
+ {{else}}
279
+ filtration = np.asarray(filtration, dtype={{PYTYPE}})
280
+ self.get_ptr().assign_simplex_filtration(simplex, _py21c_{{SHORT}}(filtration))
281
+ {{endif}}
282
+ return self
283
+
284
+ def __getitem__(self, simplex)->np.ndarray:
285
+ cdef vector[int] csimplex = simplex
286
+ cdef {{FSHORT}}* f_ptr = self.get_ptr().simplex_filtration(csimplex)
287
+ {{if is_kcritical}}
288
+ return _ff2kcview_{{SHORT}}(f_ptr)
289
+ {{else}}
290
+ return _ff21cview_{{SHORT}}(f_ptr)
291
+ {{endif}}
292
+
293
+
294
+ @property
295
+ def num_vertices(self)->int:
296
+ """This function returns the number of vertices of the simplicial
297
+ complex.
298
+
299
+ :returns: The simplicial complex number of vertices.
300
+ :rtype: int
301
+ """
302
+ return self.get_ptr().num_vertices()
303
+
304
+ @property
305
+ def num_simplices(self)->int:
306
+ """This function returns the number of simplices of the simplicial
307
+ complex.
308
+
309
+ :returns: the simplicial complex number of simplices.
310
+ :rtype: int
311
+ """
312
+ return self.get_ptr().num_simplices()
313
+
314
+ @property
315
+ def dimension(self)->int:
316
+ """This function returns the dimension of the simplicial complex.
317
+
318
+ :returns: the simplicial complex dimension.
319
+ :rtype: int
320
+
321
+ .. note::
322
+
323
+ This function is not constant time because it can recompute
324
+ dimension if required (can be triggered by
325
+ :func:`remove_maximal_simplex`
326
+ or
327
+ :func:`prune_above_filtration`
328
+ methods).
329
+ """
330
+ return self.get_ptr().dimension()
331
+ def upper_bound_dimension(self)->int:
332
+ """This function returns a valid dimension upper bound of the
333
+ simplicial complex.
334
+
335
+ :returns: an upper bound on the dimension of the simplicial complex.
336
+ :rtype: int
337
+ """
338
+ return self.get_ptr().upper_bound_dimension()
339
+
340
+ def __iter__(self):
341
+ for stuff in self.get_simplices():
342
+ yield stuff
343
+
344
+ def flagify(self, const int dim=2):
345
+ """
346
+ Turns this simplicial complex into a flag
347
+ complex by resetting filtration values of
348
+ simplices of dimension > `dim` by
349
+ lower-star values.
350
+ """
351
+ cdef Simplex_tree_multi_simplices_iterator[{{FSHORT}}] it = self.get_ptr().get_simplices_iterator_begin()
352
+ cdef Simplex_tree_multi_simplices_iterator[{{FSHORT}}] end = self.get_ptr().get_simplices_iterator_end()
353
+
354
+ cdef One_critical_filtration[{{CTYPE}}] minf = One_critical_filtration[{{CTYPE}}](self.get_ptr().get_number_of_parameters())
355
+ cdef int simplex_dimension
356
+ with nogil:
357
+ while it != end:
358
+ pair_sf =<pair[simplex_type, {{FSHORT}}*]> self.get_ptr().get_simplex_and_filtration(dereference(it))
359
+ simplex_dimension = <int>(pair_sf.first.size())-1
360
+ if simplex_dimension<dim:
361
+ preincrement(it)
362
+ continue
363
+ dereference(pair_sf.second).pull_to_greatest_common_lower_bound(minf)
364
+ preincrement(it)
365
+ self.make_filtration_non_decreasing()
366
+ return self
367
+
368
+ def set_dimension(self, int dimension)->None:
369
+ """This function sets the dimension of the simplicial complex.
370
+
371
+ :param dimension: The new dimension value.
372
+ :type dimension: int
373
+
374
+ .. note::
375
+
376
+ This function must be used with caution because it disables
377
+ dimension recomputation when required
378
+ (this recomputation can be triggered by
379
+ :func:`remove_maximal_simplex`
380
+ or
381
+ :func:`prune_above_filtration`
382
+ ).
383
+ """
384
+ self.get_ptr().set_dimension(dimension)
385
+
386
+ def __contains__(self, simplex):
387
+ """This function returns if the N-simplex was found in the simplicial
388
+ complex or not.
389
+
390
+ :param simplex: The N-simplex to find, represented by a list of vertex.
391
+ :type simplex: list of int
392
+ :returns: true if the simplex was found, false otherwise.
393
+ :rtype: bool
394
+ """
395
+ if len(simplex) == 0:
396
+ return False
397
+ if isinstance(simplex[0], Iterable):
398
+ s,f = simplex
399
+ if not self.get_ptr().find_simplex(simplex):
400
+ return False
401
+ current_f = np.asarray(self[s])
402
+ return np.all(np.asarray(f)>=current_f)
403
+
404
+ return self.get_ptr().find_simplex(simplex)
405
+
406
+ def insert(self, vector[int] simplex, filtration:list|np.ndarray|None=None)->bool:
407
+ """This function inserts the given N-simplex and its subfaces with the
408
+ given filtration value (default value is '0.0'). If some of those
409
+ simplices are already present with a higher filtration value, their
410
+ filtration value is lowered.
411
+
412
+ :param simplex: The N-simplex to insert, represented by a list of
413
+ vertex.
414
+ :type simplex: list of int
415
+ :param filtration: The filtration value of the simplex.
416
+ :type filtration: float
417
+ :returns: true if the simplex was not yet in the complex, false
418
+ otherwise (whatever its original filtration value).
419
+ :rtype: bool
420
+ """
421
+ # TODO C++, to be compatible with insert_batch and multicritical filtrations
422
+ num_parameters = self.get_ptr().get_number_of_parameters()
423
+ assert filtration is None or len(filtration) % num_parameters == 0, f"Invalid number \
424
+ of parameters. Should be {num_parameters}, got {len(filtration)}"
425
+ if filtration is None:
426
+ filtration = np.array([-np.inf]*num_parameters, dtype = float)
427
+
428
+ filtration = np.asarray(filtration, dtype = {{PYTYPE}})
429
+
430
+ {{if is_kcritical}}
431
+ from itertools import chain, combinations
432
+
433
+ def powerset(iterable):
434
+ s = tuple(iterable)
435
+ return chain.from_iterable(combinations(s, r) for r in range(1,len(s)+1)) # skip the empty splx
436
+ cdef {{FSHORT}}* current_filtration_ptr
437
+ cdef vector[vector[int]] simplices_filtration_to_insert = powerset(simplex) # TODO : optimize
438
+
439
+ if self.get_ptr().find_simplex(simplex):
440
+ for i in range(simplices_filtration_to_insert.size()):
441
+ current_filtration_ptr = self.get_ptr().simplex_filtration(simplices_filtration_to_insert[i])
442
+ dereference(current_filtration_ptr).add_generator(_py21c_{{SHORT}}(filtration))
443
+ return True ## TODO : we may want to return false if this birth wasn't necessary
444
+ cdef {{Filtration}} cfiltration = _py2kc_{{SHORT}}(filtration[None,:])
445
+ {{else}}
446
+ cdef {{Filtration}} cfiltration = _py21c_{{SHORT}}(filtration)
447
+ {{endif}}
448
+ return self.get_ptr().insert(simplex,cfiltration)
449
+
450
+ {{if not is_kcritical}}
451
+ @cython.boundscheck(False)
452
+ @cython.wraparound(False)
453
+ def insert_batch(self, vertex_array, filtrations)->SimplexTreeMulti_{{FSHORT}} :
454
+ """Inserts k-simplices given by a sparse array in a format similar
455
+ to `torch.sparse <https://pytorch.org/docs/stable/sparse.html>`_.
456
+ The n-th simplex has vertices `vertex_array[0,n]`, ...,
457
+ `vertex_array[k,n]` and filtration value `filtrations[n,num_parameters]`.
458
+ /!\ Only compatible with 1-critical filtrations. If a simplex is repeated,
459
+ only one filtration value will be taken into account.
460
+
461
+ :param vertex_array: the k-simplices to insert.
462
+ :type vertex_array: numpy.array of shape (k+1,n)
463
+ :param filtrations: the filtration values.
464
+ :type filtrations: numpy.array of shape (n,num_parameters)
465
+ """
466
+ # TODO : multi-critical
467
+ # cdef vector[int] vertices = np.unique(vertex_array)
468
+ cdef int[:,:] vertex_array_ = np.asarray(vertex_array, dtype=np.int32)
469
+ cdef Py_ssize_t k = vertex_array_.shape[0]
470
+ cdef Py_ssize_t n = vertex_array_.shape[1]
471
+ cdef int num_parameters = self.get_ptr().get_number_of_parameters()
472
+ cdef bool empty_filtration = (filtrations.size == 0)
473
+ cdef {{CTYPE}}[:,:] F_view = np.asarray(filtrations, dtype = {{PYTYPE}})
474
+
475
+ if not empty_filtration :
476
+ assert filtrations.shape[0] == n, f"inconsistent sizes for vertex_array and filtrations\
477
+ Filtrations should be of shape ({n},{self.num_parameters})"
478
+ assert filtrations.shape[1] == num_parameters, f"Inconsistent number of parameters.\
479
+ Filtrations should be of shape ({n},{self.num_parameters})"
480
+ cdef Py_ssize_t i
481
+ cdef Py_ssize_t j
482
+ cdef vector[int] v
483
+ cdef {{Filtration}} w
484
+ if empty_filtration:
485
+ w = One_critical_filtration[{{CTYPE}}](num_parameters) # at -inf by default
486
+ with nogil:
487
+ for i in range(n):
488
+ # vertex
489
+ for j in range(k):
490
+ v.push_back(vertex_array_[j, i])
491
+ #filtration
492
+ if not empty_filtration:
493
+ # for j in range(num_parameters):
494
+ # w.push_back(<{{CTYPE}}>filtrations[i,j])
495
+ w = _py21c_{{SHORT}}(F_view[i,:])
496
+ self.get_ptr().insert(v, w)
497
+ v.clear()
498
+ #repair filtration if necessary
499
+ if empty_filtration:
500
+ self.make_filtration_non_decreasing()
501
+ return self
502
+
503
+ def lower_star_multi_filtration_update(self, nodes_filtrations):
504
+ """
505
+ Updates the multi filtration of the simplextree to the lower-star
506
+ filtration defined on the vertices, by `node_filtrations`.
507
+ """
508
+ cdef Py_ssize_t num_vertices = nodes_filtrations.shape[0]
509
+ cdef Py_ssize_t num_parameters = nodes_filtrations.shape[1]
510
+ assert self.get_ptr().get_number_of_parameters() == num_parameters and self.num_vertices == num_vertices, f"Invalid shape {nodes_filtrations.shape}. Should be (?,{self.num_parameters=})."
511
+
512
+ cdef Simplex_tree_multi_simplices_iterator[{{FSHORT}}] it = self.get_ptr().get_simplices_iterator_begin()
513
+ cdef Simplex_tree_multi_simplices_iterator[{{FSHORT}}] end = self.get_ptr().get_simplices_iterator_end()
514
+ cdef Py_ssize_t node_idx = 0
515
+ cdef {{CTYPE}}[:,:] F = nodes_filtrations
516
+ cdef {{CTYPE}} minus_inf = -np.inf
517
+ with nogil:
518
+ while it != end:
519
+ pair_sf = <pair[simplex_type, {{FSHORT}}*]> self.get_ptr().get_simplex_and_filtration(dereference(it))
520
+ if pair_sf.first.size() == 1: # dimension == 0
521
+ {{if is_kcritical}}
522
+ for i in range(pair_sf.second.size()):
523
+ for j in range(num_parameters):
524
+ dereference(pair_sf.second)[i][j] = F[node_idx,j]
525
+ {{else}}
526
+ for i in range(num_parameters):
527
+ dereference(pair_sf.second)[i] = F[node_idx,i]
528
+ {{endif}}
529
+ node_idx += 1
530
+ # with gil:
531
+ # print(pair_sf.first, node_idx,i, F[node_idx,i])
532
+ else:
533
+ {{if is_kcritical}}
534
+ for i in range(pair_sf.second.size()):
535
+ for j in range(num_parameters):
536
+ dereference(pair_sf.second)[i][j] = minus_inf
537
+ {{else}}
538
+ for i in range(num_parameters):
539
+ dereference(pair_sf.second)[i] = minus_inf
540
+ {{endif}}
541
+ preincrement(it)
542
+ self.make_filtration_non_decreasing()
543
+ return self
544
+
545
+
546
+ def assign_all(self, filtration_values)-> SimplexTreeMulti_{{FSHORT}}:
547
+ """
548
+ Updates the filtration values of all of the simplices, with `filtration_values`
549
+ with order given by the simplextree iterator, e.g. self.get_simplices().
550
+ """
551
+ cdef Py_ssize_t num_simplices = filtration_values.shape[0]
552
+ cdef Py_ssize_t num_parameters = filtration_values.shape[1]
553
+
554
+ assert num_simplices == self.num_simplices, f"Number of filtration values {filtration_values.shape[0]} is not the number of simplices {self.num_simplices}"
555
+ assert num_parameters == self.num_parameters, f"Number of parameter do not coincide {filtration_values.shape[1]} vs {self.num_parameters}"
556
+ cdef Simplex_tree_multi_simplices_iterator[{{FSHORT}}] it = self.get_ptr().get_simplices_iterator_begin()
557
+ cdef Simplex_tree_multi_simplices_iterator[{{FSHORT}}] end = self.get_ptr().get_simplices_iterator_end()
558
+ cdef Simplex_tree_multi_simplex_handle[{{FSHORT}}] sh = dereference(it)
559
+ cdef int counter =0
560
+ # cdef cnp.ndarray[{{CTYPE}},ndim=1] current_filtration
561
+ cdef {{CTYPE}}[:,:] F = filtration_values
562
+ with nogil:
563
+ while it != end:
564
+ pair_sf =<pair[simplex_type, {{FSHORT}}*]> self.get_ptr().get_simplex_and_filtration(dereference(it))
565
+
566
+ for i in range(num_parameters):
567
+ dereference(pair_sf.second)[i] = F[counter,i]
568
+ # current_filtration= F[counter]
569
+ counter += 1
570
+ # yield SimplexTreeMulti._pair_simplex_filtration_to_python(out)
571
+ preincrement(it)
572
+
573
+
574
+
575
+ @cython.boundscheck(False)
576
+ @cython.wraparound(False)
577
+ def assign_batch_filtration(self, some_int[:,:] vertex_array, some_float[:,:] filtrations, bool propagate=True)->SimplexTreeMulti_{{FSHORT}}:
578
+ """Assign k-simplices given by a sparse array in a format similar
579
+ to `torch.sparse <https://pytorch.org/docs/stable/sparse.html>`_.
580
+ The n-th simplex has vertices `vertex_array[0,n]`, ...,
581
+ `vertex_array[k,n]` and filtration value `filtrations[n,num_parameters]`.
582
+ /!\ Only compatible with 1-critical filtrations. If a simplex is repeated,
583
+ only one filtration value will be taken into account.
584
+
585
+ :param vertex_array: the k-simplices to assign.
586
+ :type vertex_array: numpy.array of shape (k+1,n)
587
+ :param filtrations: the filtration values.
588
+ :type filtrations: numpy.array of shape (n,num_parameters)
589
+ """
590
+ cdef Py_ssize_t k = vertex_array.shape[0]
591
+ cdef Py_ssize_t n = vertex_array.shape[1]
592
+ assert filtrations.shape[0] == n, 'inconsistent sizes for vertex_array and filtrations'
593
+ assert filtrations.shape[1] == self.num_parameters, "wrong number of parameters"
594
+ cdef Py_ssize_t i
595
+ cdef Py_ssize_t j
596
+ cdef vector[int] v
597
+ cdef One_critical_filtration[{{CTYPE}}] w
598
+ cdef int n_parameters = self.num_parameters
599
+ with nogil:
600
+ for i in range(n):
601
+ for j in range(k):
602
+ v.push_back(vertex_array[j, i])
603
+ for j in range(n_parameters):
604
+ w.push_back(<{{CTYPE}}>filtrations[i,j])
605
+ self.get_ptr().assign_simplex_filtration(v, w)
606
+ v.clear()
607
+ w.clear()
608
+ if propagate: self.make_filtration_non_decreasing()
609
+ return self
610
+
611
+ def euler_characteristic(self, dtype = {{PYTYPE}}):
612
+ """This function returns a generator with simplices and their given
613
+ filtration values.
614
+
615
+ :returns: The simplices.
616
+ :rtype: generator with tuples(simplex, filtration)
617
+ """
618
+ cdef Simplex_tree_multi_simplices_iterator[{{FSHORT}}] it = self.get_ptr().get_simplices_iterator_begin()
619
+ cdef Simplex_tree_multi_simplices_iterator[{{FSHORT}}] end = self.get_ptr().get_simplices_iterator_end()
620
+ cdef Simplex_tree_multi_simplex_handle[{{FSHORT}}] sh = dereference(it)
621
+ cdef int num_parameters = self.get_ptr().get_number_of_parameters()
622
+ cdef dict[tuple,int] out = {}
623
+ cdef int dim
624
+ while it != end:
625
+ pair_sf = <pair[simplex_type, {{FSHORT}}*]> self.get_ptr().get_simplex_and_filtration(dereference(it))
626
+ # TODO: optimize https://stackoverflow.com/questions/16589791/most-efficient-property-to-hash-for-numpy-array
627
+ key = tuple(_ff21cview_{{SHORT}}(pair_sf.second))
628
+ dim = pair_sf.first.size() -1 % 2
629
+ out[key] = out.get(key,0)+(-1)**dim
630
+ num_keys = len(out)
631
+ new_pts = np.fromiter(out.keys(), dtype=np.dtype((dtype,num_parameters)), count=num_keys)
632
+ new_weights = np.fromiter(out.values(), dtype=np.int32, count=num_keys)
633
+ idx = np.nonzero(new_weights)
634
+ new_pts = new_pts[idx]
635
+ new_weights = new_weights[idx]
636
+ return (new_pts, new_weights)
637
+
638
+ {{else}}
639
+
640
+ @cython.boundscheck(False)
641
+ @cython.wraparound(False)
642
+ def insert_batch(self, vertex_array, filtrations)->SimplexTreeMulti_{{FSHORT}} :
643
+ """Inserts k-simplices given by a sparse array in a format similar
644
+ to `torch.sparse <https://pytorch.org/docs/stable/sparse.html>`_.
645
+ The i-th simplex has vertices `vertex_array[0,i]`, ...,
646
+ `vertex_array[n,i]` and j-th filtration value `filtrations[i,j,num_parameters]`.
647
+
648
+ :param vertex_array: the k-simplices to insert.
649
+ :type vertex_array: numpy.array of shape (k+1,n)
650
+ :param filtrations: the filtration values.
651
+ :type filtrations: numpy.array of shape (n, max_critical_degree, num_parameters)
652
+ """
653
+ # TODO : multi-critical
654
+ # cdef vector[int] vertices = np.unique(vertex_array)
655
+
656
+ cdef int[:,:] vertex_array_ = np.asarray(vertex_array, dtype=np.int32)
657
+ cdef {{CTYPE}}[:,:,:] filtrations_ = np.asarray(filtrations, dtype={{PYTYPE}})
658
+ cdef Py_ssize_t k = vertex_array_.shape[0]
659
+ cdef Py_ssize_t n = vertex_array_.shape[1]
660
+ cdef Py_ssize_t max_crit_deg = filtrations_.shape[1]
661
+ cdef int num_parameters = self.get_ptr().get_number_of_parameters()
662
+ cdef bool empty_filtration = (filtrations_.size == 0)
663
+
664
+ if not empty_filtration :
665
+ assert filtrations_.shape[0] == n and filtrations_.shape[2] == num_parameters, f"""
666
+ Inconsistent sizes for vertex_array and filtrations\n
667
+ Filtrations should be of shape ({n},k, {self.num_parameters})
668
+ """
669
+ cdef Py_ssize_t i
670
+ cdef Py_ssize_t j
671
+ cdef vector[int] v
672
+ cdef One_critical_filtration[{{CTYPE}}] w_temp
673
+ cdef {{Filtration}} w
674
+ if empty_filtration:
675
+ w = {{Filtration}}() # at -inf by default
676
+ with nogil:
677
+ for i in range(n):
678
+ v.clear()
679
+ # vertex
680
+ for j in range(k):
681
+ v.push_back(vertex_array_[j, i])
682
+ #filtration
683
+ if not empty_filtration:
684
+ w = _py2kc_{{SHORT}}(filtrations_[i])
685
+ self.get_ptr().insert(v, w)
686
+
687
+ #repair filtration if necessary
688
+ if empty_filtration:
689
+ self.make_filtration_non_decreasing()
690
+ return self
691
+ {{endif}}
692
+
693
+
694
+ def get_simplices(self)->Iterable[tuple[np.ndarray, np.ndarray]]:
695
+ """This function returns a generator with simplices and their given
696
+ filtration values.
697
+
698
+ :returns: The simplices.
699
+ :rtype: generator with tuples(simplex, filtration)
700
+ """
701
+ cdef Simplex_tree_multi_simplices_iterator[{{FSHORT}}] it = self.get_ptr().get_simplices_iterator_begin()
702
+ cdef Simplex_tree_multi_simplices_iterator[{{FSHORT}}] end = self.get_ptr().get_simplices_iterator_end()
703
+ cdef Simplex_tree_multi_simplex_handle[{{FSHORT}}] sh = dereference(it)
704
+ cdef int num_parameters = self.get_ptr().get_number_of_parameters()
705
+ while it != end:
706
+ pair_sf = <pair[simplex_type, {{FSHORT}}*]> self.get_ptr().get_simplex_and_filtration(dereference(it))
707
+ yield (
708
+ np.asarray(pair_sf.first, dtype=int),
709
+ {{if is_kcritical}}
710
+ _ff2kcview_{{SHORT}}(pair_sf.second)
711
+ {{else}}
712
+ _ff21cview_{{SHORT}}(pair_sf.second)
713
+ {{endif}}
714
+ )
715
+ preincrement(it)
716
+
717
+
718
+
719
+ def persistence_approximation(self, **kwargs):
720
+ from multipers.multiparameter_module_approximation import module_approximation
721
+ return module_approximation(self, **kwargs)
722
+
723
+
724
+ def get_skeleton(self, dimension)->Iterable[tuple[np.ndarray,np.ndarray]]:
725
+ """This function returns a generator with the (simplices of the) skeleton of a maximum given dimension.
726
+
727
+ :param dimension: The skeleton dimension value.
728
+ :type dimension: int
729
+ :returns: The (simplices of the) skeleton of a maximum dimension.
730
+ :rtype: generator with tuples(simplex, filtration)
731
+ """
732
+ cdef Simplex_tree_multi_skeleton_iterator[{{FSHORT}}] it = self.get_ptr().get_skeleton_iterator_begin(dimension)
733
+ cdef Simplex_tree_multi_skeleton_iterator[{{FSHORT}}] end = self.get_ptr().get_skeleton_iterator_end(dimension)
734
+ cdef int num_parameters = self.get_ptr().get_number_of_parameters()
735
+ while it != end:
736
+ # yield self.get_ptr().get_simplex_and_filtration(dereference(it))
737
+ pair = self.get_ptr().get_simplex_and_filtration(dereference(it))
738
+ yield (np.asarray(pair.first, dtype=int),
739
+ {{if is_kcritical}}
740
+ _ff2kcview_{{SHORT}}(pair.second)
741
+ {{else}}
742
+ _ff21cview_{{SHORT}}(pair.second)
743
+ {{endif}}
744
+ )
745
+ preincrement(it)
746
+
747
+ # def get_star(self, simplex):
748
+ # """This function returns the star of a given N-simplex.
749
+
750
+ # :param simplex: The N-simplex, represented by a list of vertex.
751
+ # :type simplex: list of int
752
+ # :returns: The (simplices of the) star of a simplex.
753
+ # :rtype: list of tuples(simplex, filtration)
754
+ # """
755
+ # cdef simplex_type csimplex = simplex
756
+ # cdef int num_parameters = self.num_parameters
757
+ # # for i in simplex:
758
+ # # csimplex.push_back(i)
759
+ # cdef vector[simplex_filtration_type] star \
760
+ # = self.get_ptr().get_star(csimplex)
761
+ # ct = []
762
+
763
+ # for filtered_simplex in star:
764
+ # v = []
765
+ # for vertex in filtered_simplex.first:
766
+ # v.append(vertex)
767
+ # ct.append((v, np.asarray(<{{CTYPE}}[:num_parameters]>filtered_simplex.second)))
768
+ # return ct
769
+
770
+ # def get_cofaces(self, simplex, codimension):
771
+ # """This function returns the cofaces of a given N-simplex with a
772
+ # given codimension.
773
+
774
+ # :param simplex: The N-simplex, represented by a list of vertex.
775
+ # :type simplex: list of int
776
+ # :param codimension: The codimension. If codimension = 0, all cofaces
777
+ # are returned (equivalent of get_star function)
778
+ # :type codimension: int
779
+ # :returns: The (simplices of the) cofaces of a simplex
780
+ # :rtype: list of tuples(simplex, filtration)
781
+ # """
782
+ # cdef vector[int] csimplex = simplex
783
+ # cdef int num_parameters = self.num_parameters
784
+ # # for i in simplex:
785
+ # # csimplex.push_back(i)
786
+ # cdef vector[simplex_filtration_type] cofaces \
787
+ # = self.get_ptr().get_cofaces(csimplex, <int>codimension)
788
+ # ct = []
789
+ # for filtered_simplex in cofaces:
790
+ # v = []
791
+ # for vertex in filtered_simplex.first:
792
+ # v.append(vertex)
793
+ # ct.append((v, np.asarray(<{{CTYPE}}[:num_parameters]>filtered_simplex.second)))
794
+ # return ct
795
+
796
+ def get_boundaries(self, simplex)->Iterable[tuple[np.ndarray, np.ndarray]]:
797
+ """This function returns a generator with the boundaries of a given N-simplex.
798
+ If you do not need the filtration values, the boundary can also be obtained as
799
+ :code:`itertools.combinations(simplex,len(simplex)-1)`.
800
+
801
+ :param simplex: The N-simplex, represented by a list of vertex.
802
+ :type simplex: list of int.
803
+ :returns: The (simplices of the) boundary of a simplex
804
+ :rtype: generator with tuples(simplex, filtration)
805
+ """
806
+ cdef pair[Simplex_tree_multi_boundary_iterator[{{FSHORT}}], Simplex_tree_multi_boundary_iterator[{{FSHORT}}]] it = self.get_ptr().get_boundary_iterators(simplex)
807
+
808
+ cdef int num_parameters = self.get_ptr().get_number_of_parameters()
809
+ while it.first != it.second:
810
+ # yield self.get_ptr().get_simplex_and_filtration(dereference(it))
811
+ pair = self.get_ptr().get_simplex_and_filtration(dereference(it.first))
812
+ yield (np.asarray(pair.first, dtype=int),
813
+ {{if is_kcritical}}
814
+ _ff2kcview_{{SHORT}}(pair.second)
815
+ {{else}}
816
+ _ff21cview_{{SHORT}}(pair.second)
817
+ {{endif}}
818
+ )
819
+ preincrement(it.first)
820
+ def remove_maximal_simplex(self, simplex)->SimplexTreeMulti_{{FSHORT}}:
821
+ """This function removes a given maximal N-simplex from the simplicial
822
+ complex.
823
+
824
+ :param simplex: The N-simplex, represented by a list of vertex.
825
+ :type simplex: list of int
826
+
827
+ .. note::
828
+
829
+ The dimension of the simplicial complex may be lower after calling
830
+ remove_maximal_simplex than it was before. However,
831
+ :func:`upper_bound_dimension`
832
+ method will return the old value, which
833
+ remains a valid upper bound. If you care, you can call
834
+ :func:`dimension`
835
+ to recompute the exact dimension.
836
+ """
837
+ self.get_ptr().remove_maximal_simplex(simplex)
838
+ return self
839
+
840
+ # def prune_above_filtration(self, filtration)->bool:
841
+ # """Prune above filtration value given as parameter.
842
+
843
+ # :param filtration: Maximum threshold value.
844
+ # :type filtration: float
845
+ # :returns: The filtration modification information.
846
+ # :rtype: bool
847
+
848
+
849
+ # .. note::
850
+
851
+ # Note that the dimension of the simplicial complex may be lower
852
+ # after calling
853
+ # :func:`prune_above_filtration`
854
+ # than it was before. However,
855
+ # :func:`upper_bound_dimension`
856
+ # will return the old value, which remains a
857
+ # valid upper bound. If you care, you can call
858
+ # :func:`dimension`
859
+ # method to recompute the exact dimension.
860
+ # """
861
+ # return self.get_ptr().prune_above_filtration(filtration)
862
+ def prune_above_dimension(self, int dimension):
863
+ """Remove all simplices of dimension greater than a given value.
864
+
865
+ :param dimension: Maximum dimension value.
866
+ :type dimension: int
867
+ :returns: The modification information.
868
+ :rtype: bool
869
+ """
870
+ return self.get_ptr().prune_above_dimension(dimension)
871
+
872
+ {{if not is_kcritical}}
873
+ def expansion(self, int max_dim)->SimplexTreeMulti_{{FSHORT}}:
874
+ """Expands the simplex tree containing only its one skeleton
875
+ until dimension max_dim.
876
+
877
+ The expanded simplicial complex until dimension :math:`d`
878
+ attached to a graph :math:`G` is the maximal simplicial complex of
879
+ dimension at most :math:`d` admitting the graph :math:`G` as
880
+ :math:`1`-skeleton.
881
+ The filtration value assigned to a simplex is the maximal filtration
882
+ value of one of its edges.
883
+
884
+ The simplex tree must contain no simplex of dimension bigger than
885
+ 1 when calling the method.
886
+
887
+ :param max_dim: The maximal dimension.
888
+ :type max_dim: int
889
+ """
890
+ with nogil:
891
+ self.get_ptr().expansion(max_dim)
892
+ # This is a fix for multipersistence. FIXME expansion in c++
893
+ self.get_ptr().make_filtration_non_decreasing()
894
+ return self
895
+
896
+ def make_filtration_non_decreasing(self)->bool:
897
+ """This function ensures that each simplex has a higher filtration
898
+ value than its faces by increasing the filtration values.
899
+
900
+ :returns: True if any filtration value was modified,
901
+ False if the filtration was already non-decreasing.
902
+ :rtype: bool
903
+ """
904
+ cdef bool out
905
+ with nogil:
906
+ out = self.get_ptr().make_filtration_non_decreasing()
907
+ return out
908
+ {{endif}}
909
+
910
+ def reset_filtration(self, filtration, min_dim = 0)->SimplexTreeMulti_{{FSHORT}}:
911
+ """This function resets the filtration value of all the simplices of dimension at least min_dim. Resets all the
912
+ simplex tree when `min_dim = 0`.
913
+ `reset_filtration` may break the filtration property with `min_dim > 0`, and it is the user's responsibility to
914
+ make it a valid filtration (using a large enough `filt_value`, or calling `make_filtration_non_decreasing`
915
+ afterwards for instance).
916
+
917
+ :param filtration: New threshold value.
918
+ :type filtration: float.
919
+ :param min_dim: The minimal dimension. Default value is 0.
920
+ :type min_dim: int.
921
+ """
922
+ {{if is_kcritical}}
923
+ cdef {{Filtration}} cfiltration = _py2kc_{{SHORT}}(np.asarray(filtration, dtype = {{PYTYPE}})[None,:])
924
+ {{else}}
925
+ cdef {{Filtration}} cfiltration = _py21c_{{SHORT}}(np.asarray(filtration, dtype = {{PYTYPE}}))
926
+ {{endif}}
927
+ self.get_ptr().reset_filtration(cfiltration, min_dim)
928
+ return self
929
+
930
+ {{if not is_kcritical}}
931
+ def pts_to_indices(self,pts:np.ndarray, simplices_dimensions:Iterable[int]) -> tuple[np.ndarray,np.ndarray]:
932
+ """
933
+ Returns the indices of the simplex tree with corresponding filtrations.
934
+
935
+ Args:
936
+ - st: SimplexTreeMulti on which to recover the indices.
937
+ - pts: (num_pts, num_parameters,) array of points to recover.
938
+ simplices_dimensions: (num_parameters,) the simplices dimension to take into account for each parameter.
939
+
940
+ Returns:
941
+ - A (m, num_parameters) array containing the found indices (m <= num_pts).
942
+ - A (m, 2) array containing the non-found indices (pt_index, parameter failing).
943
+ """
944
+
945
+ # TODO detect rank or not
946
+ cdef bool is_rank_invariant = pts.shape[1] == 2*self.num_parameters
947
+ if is_rank_invariant:
948
+ births = pts[:,:self.num_parameters]
949
+ deaths = pts[:,self.num_parameters:]
950
+ births_indices,non_recovered_births = self.pts_to_indices(births, simplices_dimensions)
951
+ deaths_indices,non_recovered_deaths = self.pts_to_indices(deaths, simplices_dimensions)
952
+ non_recovered_pts = np.concatenate([non_recovered_births,non_recovered_deaths], axis=0)
953
+ pts_indices = np.concatenate([births_indices,deaths_indices], axis=1)
954
+ return pts_indices, non_recovered_pts
955
+
956
+ cdef vector[vector[{{CTYPE}}]] cpts = pts
957
+ cdef vector[int] csimplices_dimensions = simplices_dimensions
958
+ # cdef pair[indices_type,indices_type] out
959
+ found_indices,not_found_indices = self.get_ptr().pts_to_indices(cpts,csimplices_dimensions)
960
+ if len(found_indices) == 0:
961
+ found_indices = np.empty(shape=(0,self.num_parameters), dtype = np.int32)
962
+ if len(not_found_indices) == 0:
963
+ not_found_indices = np.empty(shape=(0,2), dtype = np.int32)
964
+ return np.asarray(found_indices), np.asarray(not_found_indices)
965
+ ## This function is only meant for the edge collapse interface.
966
+ def get_edge_list(self):
967
+ """
968
+ in the filtration-domination's format
969
+ """
970
+ cdef edge_list out
971
+ with nogil:
972
+ out = self.get_ptr().get_edge_list()
973
+ return out
974
+
975
+ def collapse_edges(
976
+ self,
977
+ int num=1,
978
+ int max_dimension = 0,
979
+ bool progress=False,
980
+ bool strong=True,
981
+ bool full=False,
982
+ bool ignore_warning=False,
983
+ bool auto_clean=True,
984
+ )->SimplexTreeMulti_{{FSHORT}}:
985
+ """Edge collapse for 1-critical 2-parameter clique complex (see https://arxiv.org/abs/2211.05574).
986
+ It uses the code from the github repository https://github.com/aj-alonso/filtration_domination .
987
+
988
+ Parameters
989
+ ----------
990
+
991
+ max_dimension:int
992
+ Max simplicial dimension of the complex. Unless specified, keeps the same dimension.
993
+ num:int
994
+ The number of collapses to do.
995
+ strong:bool
996
+ Whether to use strong collapses or standard collapses (slower, but may remove more edges)
997
+ full:bool
998
+ Collapses the maximum number of edges if true, i.e., will do (at most) 100 strong collapses and (at most) 100 non-strong collapses afterward.
999
+ progress:bool
1000
+ If true, shows the progress of the number of collapses.
1001
+
1002
+ WARNING
1003
+ -------
1004
+
1005
+ - This will destroy all of the k-simplices, with k>=2. Be sure to use this with a clique complex, if you want to preserve the homology >= dimension 1.
1006
+ - This is for 1 critical simplices, with 2 parameter persistence.
1007
+ Returns
1008
+ -------
1009
+
1010
+ self:SimplexTreeMulti
1011
+ A (smaller) simplex tree that has the same homology over this bifiltration.
1012
+
1013
+ """
1014
+ # TODO : find a way to do multiple edge collapses without python conversions.
1015
+ if num == 0:
1016
+ return self
1017
+ elif num == -1:
1018
+ num=100
1019
+ full=False
1020
+ elif num == -2:
1021
+ num=100
1022
+ full=True
1023
+ assert self.num_parameters == 2, "Number of parameters has to be 2 to use edge collapses ! This is a limitation of Filtration-domination"
1024
+ if self.dimension > 1 and not ignore_warning: warn("This method ignores simplices of dimension > 1 !")
1025
+
1026
+ max_dimension = self.dimension if max_dimension <=0 else max_dimension
1027
+
1028
+ # Retrieves the edge list, and send it to filration_domination
1029
+ edges = self.get_edge_list()
1030
+ from multipers.multiparameter_edge_collapse import _collapse_edge_list
1031
+ edges = _collapse_edge_list(edges, num=num, full=full, strong=strong, progress=progress)
1032
+ # Retrieves the collapsed simplicial complex
1033
+ self._reconstruct_from_edge_list(edges, swap=True, expand_dimension=max_dimension)
1034
+ if self.is_squeezed and auto_clean:
1035
+ self._clean_filtration_grid()
1036
+ return self
1037
+
1038
+ @cython.inline
1039
+ cdef _reconstruct_from_edge_list(self,edge_list edges, bool swap=True, int expand_dimension=0):
1040
+ """
1041
+ Generates a 1-dimensional copy of self, with the edges given as input. Useful for edge collapses
1042
+
1043
+ Input
1044
+ -----
1045
+
1046
+ - edges : Iterable[(int,int),(float,float)] ## This is the format of the rust library filtration-domination
1047
+ - swap : bool
1048
+ If true, will swap self and the collapsed simplextrees.
1049
+ - expand_dim : int
1050
+ expands back the simplextree to this dimension
1051
+ Ouput
1052
+ -----
1053
+
1054
+ The reduced SimplexTreeMulti having only these edges.
1055
+ """
1056
+ reduced_tree = SimplexTreeMulti_{{FSHORT}}(num_parameters=self.num_parameters)
1057
+
1058
+ ## Adds vertices back, with good filtration
1059
+ if self.num_vertices > 0:
1060
+ vertices = np.fromiter((splx[0] for splx, f in self.get_skeleton(0)), dtype=np.int32)[None,:]
1061
+ vertices_filtration = np.fromiter((f for splx, f in self.get_skeleton(0)), dtype=np.dtype(({{PYTYPE}},2)))
1062
+ reduced_tree.insert_batch(vertices, vertices_filtration)
1063
+
1064
+ ## Adds edges again
1065
+ if self.num_simplices - self.num_vertices > 0:
1066
+ edges_filtration = np.fromiter(((e.second.first, e.second.second) for e in edges), dtype=np.dtype(({{PYTYPE}},2)))
1067
+ edges_idx = np.fromiter(((e.first.first, e.first.second) for e in edges), dtype=np.dtype((np.int32,2))).T
1068
+ reduced_tree.insert_batch(edges_idx, edges_filtration)
1069
+ if swap:
1070
+ # Swaps the simplextrees pointers
1071
+ self.thisptr, reduced_tree.thisptr = reduced_tree.thisptr, self.thisptr # Swaps self and reduced tree (self is a local variable)
1072
+ if expand_dimension > 0:
1073
+ self.expansion(expand_dimension) # Expands back the simplextree to the original dimension.
1074
+ return self if swap else reduced_tree
1075
+ {{endif}}
1076
+
1077
+ @property
1078
+ def num_parameters(self)->int:
1079
+ return self.get_ptr().get_number_of_parameters()
1080
+ def get_simplices_of_dimension(self, dim:int)->np.ndarray:
1081
+ return np.asarray(self.get_ptr().get_simplices_of_dimension(dim), dtype=int)
1082
+ def key(self, simplex:list|np.ndarray):
1083
+ return self.get_ptr().get_key(simplex)
1084
+ def set_keys_to_enumerate(self)->None:
1085
+ self.get_ptr().set_keys_to_enumerate()
1086
+ return
1087
+ def set_key(self,simplex:list|np.ndarray, key:int)->None:
1088
+ self.get_ptr().set_key(simplex, key)
1089
+ return
1090
+
1091
+
1092
+
1093
+ def _to_scc(self,filtration_dtype={{PYTYPE}}, bool flattened=False):
1094
+ """
1095
+ Turns a simplextree into a (simplicial) module presentation.
1096
+ """
1097
+ cdef bool is_function_st = self._is_function_simplextree
1098
+
1099
+ cdef pair[vector[vector[{{CTYPE}}]], boundary_matrix] out
1100
+ if flattened:
1101
+ # out = simplextree_to_ordered_bf(cptr)
1102
+ out = self.get_ptr().simplextree_to_ordered_bf()
1103
+ return np.asarray(out.first,dtype=filtration_dtype), tuple(out.second)
1104
+ if is_function_st:
1105
+ {{if not is_kcritical}}
1106
+ blocks = self.get_ptr().function_simplextree_to_scc()
1107
+ {{else}}
1108
+ raise Exception("Kcritical cannot be a function simplextree ?? TODO: Fixme")
1109
+ {{endif}}
1110
+ else:
1111
+ {{if not is_kcritical}}
1112
+ blocks = self.get_ptr().simplextree_to_scc()
1113
+ {{else}}
1114
+ blocks = self.get_ptr().kcritical_simplextree_to_scc()
1115
+ {{endif}}
1116
+ # reduces the space in memory
1117
+ if is_function_st:
1118
+ blocks = [(tuple(f), tuple(b)) for f,b in blocks[::-1]]
1119
+ else:
1120
+ {{if not is_kcritical}}
1121
+ blocks = [(np.asarray(f,dtype=filtration_dtype), tuple(b)) for f,b in blocks[::-1]] ## presentation is on the other order
1122
+ {{else}}
1123
+ blocks = [(tuple(f), tuple(b)) for f,b in blocks[::-1]] ## presentation is on the other order
1124
+ {{endif}}
1125
+ return blocks # +[(np.empty(0,dtype=filtration_dtype),[])] ## TODO : investigate
1126
+
1127
+ def to_scc_kcritical(self,
1128
+ path:os.PathLike|str,
1129
+ bool rivet_compatible=False,
1130
+ bool strip_comments=False,
1131
+ bool ignore_last_generators=False,
1132
+ bool overwrite=False,
1133
+ bool reverse_block=False,
1134
+ ):
1135
+ """
1136
+ TODO: function-simplextree, from squeezed
1137
+ """
1138
+ from os.path import exists
1139
+ from os import remove
1140
+ if exists(path):
1141
+ if not(overwrite):
1142
+ raise Exception(f"The file {path} already exists. Use the `overwrite` flag if you want to overwrite.")
1143
+ remove(path)
1144
+ # blocks = simplextree2scc(self)
1145
+ blocks = self._to_scc()
1146
+ from multipers.io import scc2disk
1147
+ scc2disk(blocks,
1148
+ path=path,
1149
+ num_parameters=self.num_parameters,
1150
+ reverse_block=reverse_block,
1151
+ rivet_compatible=rivet_compatible,
1152
+ ignore_last_generators=ignore_last_generators,
1153
+ strip_comments=strip_comments,
1154
+ )
1155
+
1156
+ def to_scc_function_st(self,
1157
+ path="scc_dataset.scc",
1158
+ bool rivet_compatible=False,
1159
+ bool strip_comments=False,
1160
+ bool ignore_last_generators=False,
1161
+ bool overwrite=False,
1162
+ bool reverse_block=True,
1163
+ ):
1164
+ from multipers.io import scc2disk
1165
+ from warnings import warn
1166
+ warn("This function is not tested yet.")
1167
+ from os.path import exists
1168
+ from os import remove
1169
+ if exists(path):
1170
+ if not(overwrite):
1171
+ raise Exception(f"The file {path} already exists. Use the `overwrite` flag if you want to overwrite.")
1172
+ remove(path)
1173
+ stuff = self._to_scc(self)
1174
+ if reverse_block: stuff.reverse()
1175
+ cdef int num_parameters = self.num_parameters
1176
+ with open(path, "w") as f:
1177
+ f.write("scc2020\n") if not rivet_compatible else f.write("firep\n")
1178
+ if not strip_comments and not rivet_compatible: f.write("# Number of parameters\n")
1179
+ num_parameters = self.num_parameters
1180
+ if rivet_compatible:
1181
+ assert num_parameters == 2
1182
+ f.write("Filtration 1\n")
1183
+ f.write("Filtration 2\n")
1184
+ else:
1185
+ f.write(f"{num_parameters}\n")
1186
+
1187
+ if not strip_comments: f.write("# Sizes of generating sets\n")
1188
+ for block in stuff: f.write(f"{len(block[1])} ")
1189
+ f.write("\n")
1190
+
1191
+ for i,block in enumerate(stuff):
1192
+ if (rivet_compatible or ignore_last_generators) and i == len(stuff)-1: continue
1193
+ if not strip_comments: f.write(f"# Block of dimension {len(stuff)-i}\n")
1194
+ for filtration, boundary in zip(*block):
1195
+ k = len(filtration)
1196
+ for j in range(k):
1197
+ if filtration[j] != np.inf:
1198
+ line = f"{filtration[j]} {k} ; " + " ".join([str(x) for x in boundary]) +"\n"
1199
+ f.write(line)
1200
+ def to_scc(self,**kwargs):
1201
+ """
1202
+ Returns an scc representation of the simplextree.
1203
+ """
1204
+ if self._is_function_simplextree:
1205
+ return self.to_scc_function_st(**kwargs)
1206
+ else:
1207
+ return self.to_scc_kcritical(**kwargs)
1208
+
1209
+ def to_rivet(self, path="rivet_dataset.txt", degree:int|None = None, progress:bool=False, overwrite:bool=False, xbins:int|None=None, ybins:int|None=None)->None:
1210
+ """ Create a file that can be imported by rivet, representing the filtration of the simplextree.
1211
+
1212
+ Parameters
1213
+ ----------
1214
+
1215
+ path:str
1216
+ path of the file.
1217
+ degree:int
1218
+ The homological degree to ask rivet to compute.
1219
+ progress:bool = True
1220
+ Shows the progress bar.
1221
+ overwrite:bool = False
1222
+ If true, will overwrite the previous file if it already exists.
1223
+ """
1224
+ ...
1225
+ from os.path import exists
1226
+ from os import remove
1227
+ if exists(path):
1228
+ if not(overwrite):
1229
+ print(f"The file {path} already exists. Use the `overwrite` flag if you want to overwrite.")
1230
+ return
1231
+ remove(path)
1232
+ file = open(path, "a")
1233
+ file.write("# This file was generated by multipers.\n")
1234
+ file.write("--datatype bifiltration\n")
1235
+ file.write(f"--homology {degree}\n") if degree is not None else None
1236
+ file.write(f"-x {xbins}\n") if xbins is not None else None
1237
+ file.write(f"-y {ybins}\n") if ybins is not None else None
1238
+ file.write("--xlabel time of appearance\n")
1239
+ file.write("--ylabel density\n\n")
1240
+ from tqdm import tqdm
1241
+ with tqdm(total=self.num_simplices, position=0, disable = not(progress), desc="Writing simplex to file") as bar:
1242
+ for dim in range(0,self.dimension+1): # Not sure if dimension sort is necessary for rivet. Check ?
1243
+ file.write(f"# block of dimension {dim}\n")
1244
+ for s,F in self.get_skeleton(dim):
1245
+ if len(s) != dim+1: continue
1246
+ for i in s:
1247
+ file.write(str(i) + " ")
1248
+ file.write("; ")
1249
+ for f in F:
1250
+ {{if is_kcritical}}
1251
+ for fi in f:
1252
+ file.write(str(fi) + " ")
1253
+ {{else}}
1254
+ file.write(str(f) + " ")
1255
+ {{endif}}
1256
+ file.write("\n")
1257
+ bar.update(1)
1258
+ file.close()
1259
+ return
1260
+
1261
+
1262
+
1263
+ def _get_filtration_values(self, vector[int] degrees, bool inf_to_nan:bool=False, bool return_raw = False)->Iterable[np.ndarray]:
1264
+ # cdef vector[int] c_degrees = degrees
1265
+ # out = get_filtration_values_from_ptr[{{FSHORT}}](ptr, degrees)
1266
+ cdef intptr_t ptr = self.thisptr
1267
+ cdef vector[vector[vector[{{CTYPE}}]]] out
1268
+ with nogil:
1269
+ out = self.get_ptr().get_filtration_values(degrees)
1270
+ filtrations_values = [np.asarray(filtration) for filtration in out]
1271
+ # Removes infs
1272
+ if inf_to_nan and np.dtype(filtrations_values[0].dtype).kind == 'f':
1273
+ for i,f in enumerate(filtrations_values):
1274
+ filtrations_values[i][f == np.inf] = np.nan
1275
+ filtrations_values[i][f == - np.inf] = np.nan
1276
+ return filtrations_values
1277
+ def _clean_filtration_grid(self):
1278
+ """
1279
+ Removes the values in filtration_grid that are not linked to any splx.
1280
+ """
1281
+ if not self.is_squeezed:
1282
+ raise ValueError("No grid to clean.")
1283
+ F = self.filtration_grid
1284
+ self.filtration_grid=None
1285
+ cleaned_coordinates = compute_grid(self)
1286
+ new_st = self.grid_squeeze(cleaned_coordinates)
1287
+
1288
+ self.thisptr, new_st.thisptr = new_st.thisptr, self.thisptr
1289
+ self.filtration_grid = tuple(f[g] for f,g in zip(F,cleaned_coordinates))
1290
+ return self
1291
+
1292
+
1293
+
1294
+ def get_filtration_grid(self, resolution:Iterable[int]|None=None, degrees:Iterable[int]|None=None, drop_quantiles:float|tuple=0, grid_strategy:_available_strategies="exact")->Iterable[np.ndarray]:
1295
+ """
1296
+ Returns a grid over the n-filtration, from the simplextree. Usefull for grid_squeeze. TODO : multicritical
1297
+
1298
+ Parameters
1299
+ ----------
1300
+
1301
+ resolution: list[int]
1302
+ resolution of the grid, for each parameter
1303
+ box=None : pair[list[float]]
1304
+ Grid bounds. format : [low bound, high bound]
1305
+ If None is given, will use the filtration bounds of the simplextree.
1306
+ grid_strategy="regular" : string
1307
+ Either "regular", "quantile", or "exact".
1308
+ Returns
1309
+ -------
1310
+
1311
+ List of filtration values, for each parameter, defining the grid.
1312
+ """
1313
+ if degrees is None:
1314
+ degrees = range(self.dimension+1)
1315
+
1316
+
1317
+ ## preprocesses the filtration values:
1318
+ filtrations_values = np.concatenate(self._get_filtration_values(degrees, inf_to_nan=True), axis=1)
1319
+ # removes duplicate + sort (nan at the end)
1320
+ filtrations_values = [np.unique(filtration) for filtration in filtrations_values]
1321
+ # removes nan
1322
+ filtrations_values = [filtration[:-1] if np.isnan(filtration[-1]) else filtration for filtration in filtrations_values]
1323
+
1324
+ return compute_grid(filtrations_values, resolution=resolution,strategy=grid_strategy,drop_quantiles=drop_quantiles)
1325
+
1326
+
1327
+ def grid_squeeze(
1328
+ self,
1329
+ filtration_grid:np.ndarray|list|None=None,
1330
+ bool coordinate_values=True,
1331
+ bool force=False,
1332
+ str strategy:_available_strategies = "exact",
1333
+ resolution:Optional[int|list[int]] = None,
1334
+ bool coordinates = False,
1335
+ grid_strategy=None,
1336
+ bool inplace=False,
1337
+ **filtration_grid_kwargs
1338
+ )->SimplexTreeMulti_{{FSHORT[:-3] + "i32"}} | SimplexTreeMulti_{{FSHORT}}:
1339
+ """
1340
+ Fit the filtration of the simplextree to a grid.
1341
+
1342
+ :param filtration_grid: The grid on which to squeeze. An example of grid can be given by the `get_filtration_grid` method.
1343
+ :type filtration_grid: list[list[float]]
1344
+ :param coordinate_values: If true, the filtrations values of the simplices will be set to the coordinate of the filtration grid.
1345
+ :type coordinate_values: bool
1346
+ """
1347
+ if not force and self.is_squeezed:
1348
+ raise Exception("SimplexTree already squeezed, use `force=True` if that's really what you want to do.")
1349
+
1350
+ if grid_strategy is not None:
1351
+ warn("`grid_strategy` is deprecated, use `strategy` instead.",DeprecationWarning)
1352
+ strategy=grid_strategy
1353
+
1354
+ if self.is_squeezed:
1355
+ warn("(copy warning) Squeezing an already squeezed slicer.")
1356
+ temp = self.unsqueeze()
1357
+ subgrid = compute_grid(self.filtration_grid, strategy=strategy, resolution=resolution)
1358
+ return temp.grid_squeeze(subgrid, coordinates=coordinates, inplace=inplace)
1359
+
1360
+ #TODO : multi-critical
1361
+ if filtration_grid is None:
1362
+ filtration_grid = self.get_filtration_grid(grid_strategy=strategy, resolution=resolution, **filtration_grid_kwargs)
1363
+ else:
1364
+ filtration_grid = sanitize_grid(filtration_grid)
1365
+ if len(filtration_grid) != self.num_parameters:
1366
+ raise ValueError(f"Invalid grid to squeeze onto. Got {len(filtration_grid)=} != {self.num_parameters=}.")
1367
+ api = api_from_tensor(filtration_grid[0])
1368
+ cdef vector[vector[double]] c_filtration_grid = tuple(api.asnumpy(f).astype(np.float64) for f in filtration_grid) # may be faster with loop on views
1369
+ if coordinate_values and inplace:
1370
+ self.filtration_grid = filtration_grid
1371
+ if inplace or not coordinate_values:
1372
+ self.get_ptr().squeeze_filtration_inplace(c_filtration_grid, coordinate_values)
1373
+ else:
1374
+ out = SimplexTreeMulti_{{FSHORT[:-3] + "i32"}}(num_parameters=self.num_parameters)
1375
+ self.get_ptr().squeeze_filtration(out.thisptr, c_filtration_grid)
1376
+ out.filtration_grid = filtration_grid
1377
+ return out
1378
+ return self
1379
+
1380
+ def unsqueeze(self, grid=None)->SimplexTreeMulti_{{FSHORT[:-3] + "f64"}}:
1381
+ from multipers.grids import sanitize_grid
1382
+ grid = self.filtration_grid if grid is None else grid
1383
+
1384
+ cdef vector[vector[double]] cgrid = sanitize_grid(grid, numpyfy=True)
1385
+ new_slicer = SimplexTreeMulti_{{FSHORT[:-3] + "f64"}}()
1386
+ new_slicer.get_ptr().unsqueeze_filtration(self.thisptr, cgrid)
1387
+
1388
+ return new_slicer
1389
+
1390
+ @property
1391
+ def is_squeezed(self)->bool:
1392
+ return self.num_vertices > 0 and self.filtration_grid is not None and len(self.filtration_grid)>0 and len(self.filtration_grid[0]) > 0
1393
+
1394
+ @property
1395
+ def dtype(self)->type:
1396
+ return {{PYTYPE}}
1397
+ def filtration_bounds(self, degrees:Iterable[int]|None=None, q:float|tuple=0, split_dimension:bool=False)->np.ndarray:
1398
+ """
1399
+ Returns the filtrations bounds of the finite filtration values.
1400
+ """
1401
+ try:
1402
+ a,b =q
1403
+ except:
1404
+ a,b,=q,q
1405
+ degrees = range(self.dimension+1) if degrees is None else degrees
1406
+ filtrations_values = self._get_filtration_values(degrees, inf_to_nan=True) ## degree, parameter, pt
1407
+ boxes = np.array([np.nanquantile(filtration, [a, 1-b], axis=1) for filtration in filtrations_values],dtype=float)
1408
+ if split_dimension: return boxes
1409
+ return np.asarray([np.nanmin(boxes, axis=(0,1)), np.nanmax(boxes, axis=(0,1))]) # box, birth/death, filtration
1410
+
1411
+
1412
+
1413
+
1414
+ def fill_lowerstar(self, F, int parameter)->SimplexTreeMulti_{{FSHORT}}:
1415
+ """ Fills the `dimension`th filtration by the lower-star filtration defined by F.
1416
+
1417
+ Parameters
1418
+ ----------
1419
+
1420
+ F:1d array
1421
+ The density over the vertices, that induces a lowerstar filtration.
1422
+ parameter:int
1423
+ Which filtration parameter to fill. /!\ python starts at 0.
1424
+
1425
+ Returns
1426
+ -------
1427
+
1428
+ self:SimplexTreeMulti
1429
+ """
1430
+ # for s, sf in self.get_simplices():
1431
+ # self.assign_filtration(s, [f if i != dimension else np.max(np.array(F)[s]) for i,f in enumerate(sf)])
1432
+ # cdef int c_parameter = parameter
1433
+ {{if is_kcritical}}
1434
+ cdef {{FSHORT}} c_F = _py2kc_{{SHORT}}(np.asarray(F,dtype={{PYTYPE}})[None,:])
1435
+ {{else}}
1436
+ cdef {{FSHORT}} c_F = _py21c_{{SHORT}}(np.asarray(F,dtype={{PYTYPE}}))
1437
+ {{endif}}
1438
+ with nogil:
1439
+ self.get_ptr().fill_lowerstar(c_F, parameter)
1440
+ return self
1441
+
1442
+ def fill_distance_matrix(self, distance_matrix, int parameter, {{CTYPE}} node_value = 0)->SimplexTreeMulti_{{FSHORT}}:
1443
+ """
1444
+ Fills a specific parameter with a rips filtration with the given matrix.
1445
+
1446
+ Warning. Undefined behaviour if `node_value` is not finite or not
1447
+ smaller or equal to `min(distance_matrix)`.
1448
+
1449
+ Parameters:
1450
+ - `distance_matrix`: a (N,N) matrix aligned with the vertices keys, i.e.,
1451
+ `distance_matrix[i,j]` is the distance between simplices [i] and [j].
1452
+ - `node_value`: a float giving the values to the nodes in this filtration.
1453
+
1454
+ Returns
1455
+ self. The updated simplextree.
1456
+ """
1457
+ assert parameter < self.num_parameters, f"This simplextree has only {self.num_parameters} parameters. Cannot fill parameter {parameter}"
1458
+ cdef {{CTYPE}}[:,:] c_distance_matrix = np.asarray(distance_matrix,dtype={{PYTYPE}})
1459
+
1460
+ ## Resets the filtration. should be optimizable
1461
+ F = np.zeros(shape = self.num_vertices, dtype={{PYTYPE}}) + node_value
1462
+ {{if is_kcritical}}
1463
+ cdef {{FSHORT}} c_F = _py2kc_{{SHORT}}(F[None,:])
1464
+ {{else}}
1465
+ cdef {{FSHORT}} c_F = _py21c_{{SHORT}}(F)
1466
+ {{endif}}
1467
+
1468
+ with nogil:
1469
+ self.get_ptr().fill_lowerstar(c_F, parameter)
1470
+
1471
+ ## Fills the matrix in the 1-skeleton
1472
+ cdef Simplex_tree_multi_skeleton_iterator[{{FSHORT}}] it = self.get_ptr().get_skeleton_iterator_begin(1)
1473
+ cdef Simplex_tree_multi_skeleton_iterator[{{FSHORT}}] end = self.get_ptr().get_skeleton_iterator_end(1)
1474
+ cdef int num_parameters = self.get_ptr().get_number_of_parameters()
1475
+ cdef int num_generators
1476
+ cdef int N = c_distance_matrix.shape[0]
1477
+ with nogil:
1478
+ while it != end:
1479
+ pair = self.get_ptr().get_simplex_and_filtration(dereference(it))
1480
+ if pair.first.size() == 2:
1481
+ i = pair.first[0]
1482
+ j = pair.first[1]
1483
+ assert i<N, f"Got vertex {i} which is greater than the matrix's size {c_distance_matrix.shape}."
1484
+ assert j<N, f"Got vertex {j} which is greater than the matrix's size {c_distance_matrix.shape}."
1485
+
1486
+ {{if is_kcritical}}
1487
+ num_generators =pair.second.num_generators()
1488
+ for k in range(num_generators):
1489
+ dereference(pair.second)[k][parameter] = c_distance_matrix[i,j]
1490
+ {{else}}
1491
+ dereference(pair.second)[parameter] = c_distance_matrix[i,j]
1492
+ {{endif}}
1493
+ preincrement(it)
1494
+
1495
+ self.make_filtration_non_decreasing()
1496
+ return self
1497
+
1498
+
1499
+
1500
+
1501
+
1502
+ def project_on_line(self, parameter:int=0, basepoint:None|list|np.ndarray= None, direction:None|list|np.ndarray= None)->SimplexTree:
1503
+ """Converts an multi simplextree to a gudhi simplextree.
1504
+
1505
+ Parameters
1506
+ ----------
1507
+
1508
+ parameter:int = 0
1509
+ The parameter to keep. WARNING will crash if the multi simplextree is not well filled.
1510
+ basepoint:None
1511
+ Instead of keeping a single parameter, will consider the filtration defined by the diagonal line crossing the basepoint.
1512
+
1513
+ WARNING
1514
+ -------
1515
+
1516
+ There are no safeguard yet, it WILL crash if asking for a parameter that is not filled.
1517
+
1518
+ Returns
1519
+ -------
1520
+
1521
+ A SimplexTree with chosen 1D filtration.
1522
+ """
1523
+ # FIXME : deal with multicritical filtrations
1524
+ import gudhi as gd
1525
+ new_simplextree = gd.SimplexTree()
1526
+ assert parameter < self.get_ptr().get_number_of_parameters()
1527
+ cdef int c_parameter = parameter
1528
+ cdef intptr_t old_ptr = self.thisptr
1529
+ cdef intptr_t new_ptr = new_simplextree.thisptr
1530
+ if basepoint is None:
1531
+ basepoint = np.array([np.inf]*self.get_ptr().get_number_of_parameters())
1532
+ basepoint[parameter] = 0
1533
+ if direction is None:
1534
+ direction = np.array([0]*self.get_ptr().get_number_of_parameters())
1535
+ direction[parameter] = 1
1536
+
1537
+ basepoint = np.asarray(basepoint, dtype=np.float64)
1538
+ direction = np.asarray(direction, dtype=np.float64)
1539
+ cdef One_critical_filtration[double] c_basepoint = _py21c_f64(basepoint)
1540
+ cdef One_critical_filtration[double] c_direction = _py21c_f64(direction)
1541
+ cdef Line[double] c_line = Line[double](c_basepoint, c_direction)
1542
+ with nogil:
1543
+ self.get_ptr().to_std(new_ptr, c_line, c_parameter)
1544
+ return new_simplextree
1545
+
1546
+ def linear_projections(self, linear_forms:np.ndarray)->Iterable[SimplexTree]:
1547
+ """
1548
+ Compute the 1-parameter projections, w.r.t. given the linear forms, of this simplextree.
1549
+
1550
+ Input
1551
+ -----
1552
+
1553
+ Array of shape (num_linear_forms, num_parameters)
1554
+
1555
+ Output
1556
+ ------
1557
+
1558
+ List of projected (gudhi) simplextrees.
1559
+ """
1560
+ cdef Py_ssize_t num_projections = linear_forms.shape[0]
1561
+ cdef Py_ssize_t num_parameters = linear_forms.shape[1]
1562
+ if num_projections == 0: return []
1563
+ cdef vector[vector[double]] c_linear_forms = linear_forms
1564
+ assert num_parameters==self.num_parameters, f"The linear forms has to have the same number of parameter as the simplextree ({self.num_parameters})."
1565
+
1566
+ # Gudhi copies are faster than inserting simplices one by one
1567
+ import gudhi as gd
1568
+ # flattened_simplextree = gd.SimplexTree()
1569
+ # cdef intptr_t multi_prt = self.thisptr
1570
+ # cdef intptr_t flattened_ptr = flattened_simplextree.thisptr
1571
+ # with nogil:
1572
+ # # flatten_from_ptr(multi_prt, flattened_ptr, num_parameters)
1573
+ # self.get_ptr().to_std(flattened_ptr, num_parameters)
1574
+ flattened_simplextree = self.project_on_line()
1575
+ out = [flattened_simplextree] + [gd.SimplexTree(flattened_simplextree) for _ in range(num_projections-1)]
1576
+
1577
+ # Fills the 1-parameter simplextrees.
1578
+ cdef vector[intptr_t] out_ptrs = [st.thisptr for st in out]
1579
+ with nogil:
1580
+ for i in range(num_projections):
1581
+ self.get_ptr().to_std_linear_projection(out_ptrs[i], c_linear_forms[i])
1582
+ return out
1583
+
1584
+
1585
+ def set_num_parameter(self, num:int):
1586
+ """
1587
+ Sets the numbers of parameters.
1588
+ WARNING : it will resize all the filtrations to this size.
1589
+ """
1590
+ self.get_ptr().resize_all_filtrations(num)
1591
+ self.get_ptr().set_number_of_parameters(num)
1592
+ return
1593
+
1594
+ def __eq__(self, other:SimplexTreeMulti_{{FSHORT}}):
1595
+ """Test for structural equality
1596
+ :returns: True if the 2 simplex trees are equal, False otherwise.
1597
+ :rtype: bool
1598
+ """
1599
+ return dereference(self.get_ptr()) == dereference(other.get_ptr())
1600
+
1601
+
1602
+
1603
+
1604
+
1605
+ # def _simplextree_multify_{{FSHORT}}(simplextree:SimplexTree, int num_parameters, default_values=[])->SimplexTreeMulti_{{FSHORT}}:
1606
+ # """Converts a gudhi simplextree to a multi simplextree.
1607
+ # Parameters
1608
+ # ----------
1609
+
1610
+ # parameters:int = 2
1611
+ # The number of filtrations
1612
+
1613
+ # Returns
1614
+ # -------
1615
+
1616
+ # A multi simplextree, with first filtration value being the one from the original simplextree.
1617
+ # """
1618
+ # if isinstance(simplextree, SimplexTreeMulti_{{FSHORT}}):
1619
+ # return simplextree
1620
+ # st = SimplexTreeMulti_{{FSHORT}}(num_parameters=num_parameters)
1621
+ # cdef intptr_t old_ptr = simplextree.thisptr
1622
+ # {{if is_kcritical}}
1623
+ # cdef {{FSHORT}} c_default_values= _py2kc_{{SHORT}}([default_values])
1624
+ # {{else}}
1625
+ # cdef {{FSHORT}} c_default_values= _py21c_{{SHORT}}([default_values])
1626
+ # {{endif}}
1627
+ # with nogil:
1628
+ # st.get_ptr().from_std(old_ptr, num_parameters, c_default_values)
1629
+ # return st
1630
+
1631
+ @cython.boundscheck(False)
1632
+ @cython.wraparound(False)
1633
+ def _safe_simplextree_multify_{{FSHORT}}(simplextree:SimplexTree,int num_parameters=2, cnp.ndarray default_values=np.array(-np.inf))->SimplexTreeMulti_{{FSHORT}}:
1634
+ if isinstance(simplextree, SimplexTreeMulti_{{FSHORT}}):
1635
+ return simplextree
1636
+ simplices = [[] for _ in range(simplextree.dimension()+1)]
1637
+ filtration_values = [[] for _ in range(simplextree.dimension()+1)]
1638
+ st_multi = SimplexTreeMulti_{{FSHORT}}(num_parameters=1)
1639
+ if num_parameters > 1:
1640
+ st_multi.set_num_parameter(num_parameters)
1641
+ if default_values.squeeze().ndim == 0:
1642
+ default_values = np.zeros(num_parameters-1) + default_values
1643
+
1644
+ # TODO : Optimize with Python.h
1645
+ for s,f in simplextree.get_simplices():
1646
+ filtration_values[len(s)-1].append(np.concatenate([[f],default_values]))
1647
+ simplices[len(s)-1].append(s)
1648
+ for batch_simplices, batch_filtrations in zip(simplices,filtration_values):
1649
+ st_multi.insert_batch(np.asarray(batch_simplices, dtype=np.int32).T, np.asarray(batch_filtrations, dtype={{PYTYPE}}))
1650
+ return st_multi
1651
+
1652
+
1653
+
1654
+ @cython.boundscheck(False)
1655
+ @cython.wraparound(False)
1656
+ def _safe_simplextree_multify_{{FSHORT}}2(simplextree:SimplexTree,int num_parameters=2, cnp.ndarray default_values=np.array(-np.inf))->SimplexTreeMulti_{{FSHORT}}:
1657
+ if isinstance(simplextree, SimplexTreeMulti_{{FSHORT}}):
1658
+ return simplextree
1659
+ st_multi = SimplexTreeMulti_{{FSHORT}}(num_parameters=num_parameters)
1660
+ if default_values.squeeze().ndim == 0:
1661
+ default_values = np.zeros(num_parameters-1) + default_values
1662
+ cdef int num_simplices = simplextree.num_simplices()
1663
+ cdef int max_dim = simplextree.dimension()
1664
+ dims = np.fromiter((len(s)-1 for s,_ in simplextree.get_simplices()), dtype=np.int32, count=num_simplices)
1665
+ dims = np.unique_counts(dims).counts
1666
+ cdef int[:] cdims = np.cumsum(np.concatenate([[0],dims])).astype(np.int32)
1667
+
1668
+ # filtration_values = np.asarray([np.concatenate(([f],default_values)) for s,f in simplextree.get_simplices()])
1669
+ cdef double [:,:] filtration_values = np.fromiter((np.concatenate((np.asarray(f)[None],default_values)) for s,f in simplextree.get_simplices()), dtype=np.dtype((np.float64,num_parameters)), count=num_simplices)
1670
+
1671
+ cdef int32_t[:,:] batch_simplices = np.fromiter((np.concatenate([s,np.empty(max_dim+2-len(s))]) for s,f in simplextree.get_simplices()), dtype=np.dtype((np.int32,max_dim+2)), count=num_simplices)
1672
+ # print(filtration_values.shape, batch_simplices.shape)
1673
+ for i in range(max_dim+1):
1674
+ S = batch_simplices[cdims[i]:cdims[i+1]][:,:i+1].T
1675
+ F = filtration_values[cdims[i]:cdims[i+1]]
1676
+ st_multi.insert_batch(S, F)
1677
+ # print(f"adding {S.shape,F.shape}")
1678
+ return st_multi
1679
+ {{endfor}}
1680
+
1681
+ global available_simplextrees, SimplexTreeMulti_type
1682
+ available_simplextrees = tuple((
1683
+ {{for CTYPE,PYTYPE,SHORT,Filtration, is_kcritical, FSHORT in to_iter}}
1684
+ SimplexTreeMulti_{{FSHORT}},
1685
+ {{endfor}}
1686
+ ))
1687
+ SimplexTreeMulti_type:type= Union[
1688
+ {{for CTYPE,PYTYPE,SHORT,Filtration, is_kcritical, FSHORT in to_iter}}
1689
+ SimplexTreeMulti_{{FSHORT}},
1690
+ {{endfor}}
1691
+ ]
1692
+
1693
+ def is_simplextree_multi(input)->bool:
1694
+ return (False
1695
+ {{for CTYPE,PYTYPE,SHORT,Filtration, is_kcritical, FSHORT in to_iter}}
1696
+ or isinstance(input, SimplexTreeMulti_{{FSHORT}})
1697
+ {{endfor}}
1698
+ )
1699
+
1700
+
1701
+
1702
+
1703
+
1704
+ def SimplexTreeMulti(input=None, int num_parameters=-1, dtype:type = np.float64, bool kcritical = False,**kwargs) -> SimplexTreeMulti_type:
1705
+ """SimplexTreeMulti constructor.
1706
+
1707
+ :param other: If `other` is `None` (default value), an empty `SimplexTreeMulti` is created.
1708
+ If `other` is a `SimplexTree`, the `SimplexTreeMulti` is constructed from a deep copy of `other`.
1709
+ If `other` is a `SimplexTreeMulti`, the `SimplexTreeMulti` is constructed from a deep copy of `other`.
1710
+ :type other: SimplexTree or SimplexTreeMulti (Optional)
1711
+ :param num_parameters: The number of parameter of the multi-parameter filtration.
1712
+ :type num_parameters: int
1713
+ :returns: An empty or a copy simplex tree.
1714
+ :rtype: SimplexTreeMulti
1715
+
1716
+ :raises TypeError: In case `other` is neither `None`, nor a `SimplexTree`, nor a `SimplexTreeMulti`.
1717
+ """
1718
+ cdef dict[tuple[type, bool], type] _st_map = {
1719
+ {{for CTYPE,PYTYPE,SHORT,Filtration, is_kcritical, FSHORT in to_iter}}
1720
+ (np.dtype({{PYTYPE}}),{{is_kcritical}}): SimplexTreeMulti_{{FSHORT}},
1721
+ {{endfor}}
1722
+ }
1723
+ # TODO : check that + kcriticality
1724
+ # if input is not None:
1725
+ # assert input.dtype is dtype, "SimplexTree conversions are not yet implemented"
1726
+
1727
+ return _st_map[(np.dtype(dtype), kcritical)](input, num_parameters=num_parameters, **kwargs)
1728
+
1729
+
1730
+
1731
+
1732
+ ## SIGNED MEASURES STUFF
1733
+ from multipers.grids import sms_in_grid
1734
+
1735
+ ctypedef int32_t indices_type # uint fails for some reason
1736
+ python_indices_type=np.int32
1737
+
1738
+ ctypedef int32_t tensor_dtype
1739
+ python_tensor_dtype = np.int32
1740
+
1741
+
1742
+ ctypedef pair[vector[vector[indices_type]], vector[tensor_dtype]] signed_measure_type
1743
+
1744
+ cdef extern from "multi_parameter_rank_invariant/hilbert_function.h" namespace "Gudhi::multiparameter::hilbert_function":
1745
+ {{for CTYPE,PYTYPE,SHORT,Filtration, is_kcritical, FSHORT in to_iter}}
1746
+ signed_measure_type get_hilbert_signed_measure(Simplex_tree_multi_interface[{{FSHORT}}, {{CTYPE}}]&, tensor_dtype* , const vector[indices_type], const vector[indices_type], bool, indices_type, bool, bool) except + nogil
1747
+ {{endfor}}
1748
+
1749
+
1750
+
1751
+
1752
+ ## Aligns python/cpp
1753
+ cdef inline signed_measure_type _hilbert_sm_from_simplextree(object simplextree, tensor_dtype* container_ptr, const vector[indices_type]& c_grid_shape, const vector[indices_type]& degrees, bool zero_pad, indices_type n_jobs, bool verbose, bool expand_collapse):
1754
+ cdef intptr_t simplextree_ptr = simplextree.thisptr
1755
+ if False:
1756
+ pass
1757
+ {{for CTYPE,PYTYPE,SHORT,Filtration, is_kcritical, FSHORT in to_iter}}
1758
+ elif isinstance(simplextree, SimplexTreeMulti_{{FSHORT}}):
1759
+ with nogil:
1760
+ return get_hilbert_signed_measure(dereference(<Simplex_tree_multi_interface[{{FSHORT}}, {{CTYPE}}]*>simplextree_ptr), container_ptr, c_grid_shape, degrees, zero_pad, n_jobs, verbose, expand_collapse)
1761
+ {{endfor}}
1762
+ else:
1763
+ raise ValueError("Input {simplextree} not supported.")
1764
+
1765
+ def _hilbert_signed_measure(simplextree,
1766
+ vector[indices_type] degrees,
1767
+ mass_default=None,
1768
+ plot=False,
1769
+ indices_type n_jobs=0,
1770
+ bool verbose=False,
1771
+ bool expand_collapse=False,
1772
+ # grid_conversion = None,
1773
+ ):
1774
+ """
1775
+ Computes the signed measures given by the decomposition of the hilbert function.
1776
+
1777
+ Input
1778
+ -----
1779
+
1780
+ - simplextree:SimplexTreeMulti, the multifiltered simplicial complex
1781
+ - degrees:array-like of ints, the degrees to compute
1782
+ - mass_default: Either None, or 'auto' or 'inf', or array-like of floats. Where to put the default mass to get a zero-mass measure.
1783
+ - plot:bool, plots the computed measures if true.
1784
+ - n_jobs:int, number of jobs. Defaults to #cpu, but when doing parallel computations of signed measures, we recommend setting this to 1.
1785
+ - verbose:bool, prints c++ logs.
1786
+
1787
+ Output
1788
+ ------
1789
+
1790
+ `[signed_measure_of_degree for degree in degrees]`
1791
+ with `signed_measure_of_degree` of the form `(dirac location, dirac weights)`.
1792
+ """
1793
+
1794
+ assert simplextree.is_squeezed, "Squeeze grid first."
1795
+ cdef bool zero_pad = mass_default is not None
1796
+ # assert simplextree.num_parameters == 2
1797
+ grid_shape = np.array([len(f) for f in simplextree.filtration_grid])
1798
+ if mass_default is None:
1799
+ mass_default = mass_default
1800
+ else:
1801
+ mass_default = np.asarray(mass_default)
1802
+ assert mass_default.ndim == 1 and mass_default.shape[0] == simplextree.num_parameters
1803
+ if zero_pad:
1804
+ for i, _ in enumerate(grid_shape):
1805
+ grid_shape[i] += 1 # adds a 0
1806
+ # if grid_conversion is not None:
1807
+ # grid_conversion = tuple(np.concatenate([f, [mass_default[i]]]) for i,f in enumerate(grid_conversion))
1808
+ assert len(grid_shape) == simplextree.num_parameters, "Grid shape size has to be the number of parameters."
1809
+ grid_shape_with_degree = np.asarray(np.concatenate([[len(degrees)], grid_shape]), dtype=python_indices_type)
1810
+ container_array = np.ascontiguousarray(np.zeros(grid_shape_with_degree, dtype=python_tensor_dtype).flatten())
1811
+ assert len(container_array) < np.iinfo(np.uint32).max, "Too large container. Raise an issue on github if you encounter this issue. (Due to tensor's operator[])"
1812
+ cdef intptr_t simplextree_ptr = simplextree.thisptr
1813
+ cdef vector[indices_type] c_grid_shape = grid_shape_with_degree
1814
+ cdef tensor_dtype[::1] container = container_array
1815
+ cdef tensor_dtype* container_ptr = &container[0]
1816
+ cdef signed_measure_type out = _hilbert_sm_from_simplextree(simplextree, container_ptr, c_grid_shape, degrees, zero_pad, n_jobs, verbose, expand_collapse)
1817
+ pts, weights = np.asarray(out.first, dtype=python_indices_type).reshape(-1, simplextree.num_parameters+1), np.asarray(out.second, dtype=python_tensor_dtype)
1818
+ slices = np.concatenate([np.searchsorted(pts[:,0], np.arange(degrees.size())), [pts.shape[0]] ])
1819
+ sms = [
1820
+ (pts[slices[i]:slices[i+1],1:],weights[slices[i]:slices[i+1]])
1821
+ for i in range(slices.shape[0]-1)
1822
+ ]
1823
+ # if grid_conversion is not None:
1824
+ # sms = sms_in_grid(sms,grid_conversion)
1825
+
1826
+ # if plot:
1827
+ # from multipers.plots import plot_signed_measures
1828
+ # plot_signed_measures(sms)
1829
+ return sms
1830
+
1831
+
1832
+ #### EULER CHAR
1833
+
1834
+ cdef extern from "multi_parameter_rank_invariant/euler_characteristic.h" namespace "Gudhi::multiparameter::euler_characteristic":
1835
+ # void get_euler_surface_python(const intptr_t, tensor_dtype*, const vector[indices_type], bool, bool, bool) except + nogil
1836
+ {{for CTYPE,PYTYPE,SHORT,Filtration, is_kcritical, FSHORT in to_iter}}
1837
+ signed_measure_type get_euler_signed_measure(Simplex_tree_multi_interface[{{FSHORT}}, {{CTYPE}}]&, tensor_dtype* , const vector[indices_type], bool, bool) except + nogil
1838
+ {{endfor}}
1839
+
1840
+
1841
+ ## Aligns python/cpp
1842
+ cdef inline signed_measure_type _euler_sm_from_simplextree(object simplextree, tensor_dtype* container_ptr, const vector[indices_type]& c_grid_shape, bool zero_pad, bool verbose):
1843
+ cdef intptr_t simplextree_ptr = simplextree.thisptr
1844
+ if False:
1845
+ pass
1846
+ {{for CTYPE,PYTYPE,SHORT,Filtration, is_kcritical, FSHORT in to_iter}}
1847
+ {{if not is_kcritical}}
1848
+ elif isinstance(simplextree, SimplexTreeMulti_{{FSHORT}}):
1849
+ with nogil:
1850
+ return get_euler_signed_measure(dereference(<Simplex_tree_multi_interface[{{FSHORT}}, {{CTYPE}}]*>simplextree_ptr), container_ptr, c_grid_shape, zero_pad, verbose)
1851
+ {{endif}}
1852
+ {{endfor}}
1853
+ else:
1854
+ raise ValueError("Input {simplextree} not supported.")
1855
+
1856
+
1857
+ def _euler_signed_measure(simplextree, mass_default=None, bool verbose=False):
1858
+ """
1859
+ Computes the signed measures given by the decomposition of the hilbert function.
1860
+
1861
+ Input
1862
+ -----
1863
+
1864
+ - simplextree:SimplexTreeMulti, the multifiltered simplicial complex
1865
+ - mass_default: Either None, or 'auto' or 'inf', or array-like of floats. Where to put the default mass to get a zero-mass measure.
1866
+ - plot:bool, plots the computed measures if true.
1867
+ - n_jobs:int, number of jobs. Defaults to #cpu, but when doing parallel computations of signed measures, we recommend setting this to 1.
1868
+ - verbose:bool, prints c++ logs.
1869
+
1870
+ Output
1871
+ ------
1872
+
1873
+ `[signed_measure_of_degree for degree in degrees]`
1874
+ with `signed_measure_of_degree` of the form `(dirac location, dirac weights)`.
1875
+ """
1876
+ if not simplextree.is_squeezed:
1877
+ raise ValueError("Squeeze grid first.")
1878
+ cdef bool zero_pad = mass_default is not None
1879
+ # assert simplextree.num_parameters == 2
1880
+ grid_shape = np.array([len(f) for f in simplextree.filtration_grid])
1881
+
1882
+ if mass_default is None:
1883
+ mass_default = mass_default
1884
+ else:
1885
+ mass_default = np.asarray(mass_default)
1886
+ assert mass_default.ndim == 1 and mass_default.shape[0] == simplextree.num_parameters
1887
+ if zero_pad:
1888
+ for i, _ in enumerate(grid_shape):
1889
+ grid_shape[i] += 1 # adds a 0
1890
+ # if grid_conversion is not None:
1891
+ # grid_conversion = tuple(np.concatenate([f, [mass_default[i]]]) for i,f in enumerate(grid_conversion))
1892
+ assert len(grid_shape) == simplextree.num_parameters, "Grid shape size has to be the number of parameters."
1893
+ container_array = np.ascontiguousarray(np.zeros(grid_shape, dtype=python_tensor_dtype).flatten())
1894
+ assert len(container_array) < np.iinfo(python_indices_type).max, "Too large container. Raise an issue on github if you encounter this issue. (Due to tensor's operator[])"
1895
+ cdef intptr_t simplextree_ptr = simplextree.thisptr
1896
+ cdef vector[indices_type] c_grid_shape = grid_shape
1897
+ cdef tensor_dtype[::1] container = container_array
1898
+ cdef tensor_dtype* container_ptr = &container[0]
1899
+ cdef signed_measure_type out
1900
+
1901
+ out = _euler_sm_from_simplextree(simplextree, container_ptr, c_grid_shape, zero_pad, verbose)
1902
+
1903
+ pts, weights = np.asarray(out.first, dtype=python_indices_type).reshape(-1, simplextree.num_parameters), np.asarray(out.second, dtype=python_tensor_dtype)
1904
+ # return pts, weights
1905
+ sm = (pts,weights)
1906
+
1907
+ return sm
1908
+
1909
+
1910
+
1911
+
1912
+
1913
+
1914
+
1915
+
1916
+
1917
+ ## Rank invariant
1918
+
1919
+ cdef extern from "multi_parameter_rank_invariant/rank_invariant.h" namespace "Gudhi::multiparameter::rank_invariant":
1920
+ {{for CTYPE,PYTYPE,SHORT,Filtration, is_kcritical, FSHORT in to_iter}}
1921
+ void compute_rank_invariant_python(Simplex_tree_multi_interface[{{FSHORT}}, {{CTYPE}}]&, tensor_dtype* , const vector[indices_type], const vector[indices_type], indices_type, bool) except + nogil
1922
+ {{endfor}}
1923
+
1924
+
1925
+ ## Aligns python/cpp
1926
+ cdef inline void _rank_sm_from_simplextree(object simplextree, tensor_dtype* container_ptr, const vector[indices_type]& c_grid_shape, const vector[indices_type]& degrees, indices_type n_jobs, bool expand_collapse):
1927
+ cdef intptr_t simplextree_ptr = simplextree.thisptr
1928
+ if False:
1929
+ pass
1930
+ {{for CTYPE,PYTYPE,SHORT,Filtration, is_kcritical, FSHORT in to_iter}}
1931
+ elif isinstance(simplextree, SimplexTreeMulti_{{FSHORT}}):
1932
+ with nogil:
1933
+ compute_rank_invariant_python(dereference(<Simplex_tree_multi_interface[{{FSHORT}}, {{CTYPE}}]*>simplextree_ptr), container_ptr, c_grid_shape, degrees, n_jobs, expand_collapse)
1934
+ {{endfor}}
1935
+ else:
1936
+ raise ValueError("Input {simplextree} not supported.")
1937
+
1938
+
1939
+ def _rank_signed_measure(simplextree, vector[indices_type] degrees, mass_default=None, plot=False, indices_type n_jobs=0, bool verbose=False, bool expand_collapse=False):
1940
+ """
1941
+ Computes the signed measures given by the decomposition of the hilbert function.
1942
+
1943
+ Input
1944
+ -----
1945
+
1946
+ - simplextree:SimplexTreeMulti, the multifiltered simplicial complex
1947
+ - degrees:array-like of ints, the degrees to compute
1948
+ - mass_default: Either None, or 'auto' or 'inf', or array-like of floats. Where to put the default mass to get a zero-mass measure.
1949
+ - plot:bool, plots the computed measures if true.
1950
+ - n_jobs:int, number of jobs. Defaults to #cpu, but when doing parallel computations of signed measures, we recommend setting this to 1.
1951
+ - verbose:bool, prints c++ logs.
1952
+
1953
+ Output
1954
+ ------
1955
+
1956
+ `[signed_measure_of_degree for degree in degrees]`
1957
+ with `signed_measure_of_degree` of the form `(dirac location, dirac weights)`.
1958
+ """
1959
+ assert simplextree.is_squeezed, "Squeeze grid first."
1960
+ cdef bool zero_pad = mass_default is not None
1961
+ # grid_conversion = [np.asarray(f) for f in simplextree.filtration_grid]
1962
+ # assert simplextree.num_parameters == 2
1963
+ # grid_shape = np.array([len(f) for f in grid_conversion])
1964
+ grid_shape = np.array([len(f) for f in simplextree.filtration_grid])
1965
+
1966
+ if mass_default is None:
1967
+ mass_default = mass_default
1968
+ else:
1969
+ mass_default = np.asarray(mass_default)
1970
+ assert mass_default.ndim == 1 and mass_default.shape[0] == simplextree.num_parameters, "Mass default has to be an array like of shape (num_parameters,)"
1971
+ # if zero_pad:
1972
+ # for i, _ in enumerate(grid_shape):
1973
+ # grid_shape[i] += 1 # adds a 0
1974
+ # grid_conversion = tuple(np.concatenate([f, [mass_default[i]]]) for i,f in enumerate(grid_conversion))
1975
+
1976
+ assert len(grid_shape) == simplextree.num_parameters, "Grid shape size has to be the number of parameters."
1977
+ grid_shape_with_degree = np.asarray(np.concatenate([[len(degrees)], grid_shape, grid_shape]), dtype=python_indices_type)
1978
+ container_array = np.ascontiguousarray(np.zeros(grid_shape_with_degree, dtype=python_tensor_dtype).flatten())
1979
+ assert len(container_array) < np.iinfo(python_indices_type).max, "Too large container. Raise an issue on github if you encounter this issue. (Due to tensor's operator[])"
1980
+ cdef intptr_t simplextree_ptr = simplextree.thisptr
1981
+ cdef vector[indices_type] c_grid_shape = grid_shape_with_degree
1982
+ cdef tensor_dtype[::1] container = container_array
1983
+ cdef tensor_dtype* container_ptr = &container[0]
1984
+ _rank_sm_from_simplextree(simplextree, container_ptr,c_grid_shape,degrees, n_jobs, expand_collapse)
1985
+ rank = container_array.reshape(grid_shape_with_degree)
1986
+ rank = tuple(rank_decomposition_by_rectangles(rank_of_degree, threshold = zero_pad) for rank_of_degree in rank)
1987
+ out = []
1988
+ cdef int num_parameters = simplextree.num_parameters
1989
+ for rank_decomposition in rank:
1990
+ (coords, weights) = sparsify(np.ascontiguousarray(rank_decomposition))
1991
+ births = coords[:,:num_parameters]
1992
+ deaths = coords[:,num_parameters:]
1993
+ correct_indices = np.all(births<=deaths, axis=1) # TODO : correct this
1994
+ coords = coords[correct_indices]
1995
+ weights = weights[correct_indices]
1996
+ if len(correct_indices) == 0:
1997
+ coords, weights = np.empty((0, 2*num_parameters)), np.empty((0))
1998
+ # else:
1999
+ # pts = np.empty(shape=coords.shape, dtype=grid_conversion[0].dtype)
2000
+ # for i in range(pts.shape[1]):
2001
+ # pts[:,i] = grid_conversion[i % num_parameters][coords[:,i]]
2002
+ rank_decomposition = (coords,weights)
2003
+ out.append(rank_decomposition)
2004
+
2005
+ # if plot:
2006
+ # from multipers.plots import plot_signed_measures
2007
+ # plot_signed_measures(out)
2008
+ return out
2009
+