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

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

Potentially problematic release.


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

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