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