multipers 2.2.3__cp310-cp310-win_amd64.whl

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

Potentially problematic release.


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

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