multipers 2.0.0__cp310-cp310-macosx_13_0_arm64.whl

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

Potentially problematic release.


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

Files changed (78) hide show
  1. multipers/.dylibs/libc++.1.0.dylib +0 -0
  2. multipers/.dylibs/libtbb.12.12.dylib +0 -0
  3. multipers/.dylibs/libtbbmalloc.2.12.dylib +0 -0
  4. multipers/__init__.py +11 -0
  5. multipers/_signed_measure_meta.py +268 -0
  6. multipers/_slicer_meta.py +171 -0
  7. multipers/data/MOL2.py +350 -0
  8. multipers/data/UCR.py +18 -0
  9. multipers/data/__init__.py +1 -0
  10. multipers/data/graphs.py +466 -0
  11. multipers/data/immuno_regions.py +27 -0
  12. multipers/data/minimal_presentation_to_st_bf.py +0 -0
  13. multipers/data/pytorch2simplextree.py +91 -0
  14. multipers/data/shape3d.py +101 -0
  15. multipers/data/synthetic.py +68 -0
  16. multipers/distances.py +198 -0
  17. multipers/euler_characteristic.pyx +132 -0
  18. multipers/filtration_conversions.pxd +229 -0
  19. multipers/filtrations.pxd +225 -0
  20. multipers/function_rips.cpython-310-darwin.so +0 -0
  21. multipers/function_rips.pyx +105 -0
  22. multipers/grids.cpython-310-darwin.so +0 -0
  23. multipers/grids.pyx +281 -0
  24. multipers/hilbert_function.pyi +46 -0
  25. multipers/hilbert_function.pyx +153 -0
  26. multipers/io.cpython-310-darwin.so +0 -0
  27. multipers/io.pyx +571 -0
  28. multipers/ml/__init__.py +0 -0
  29. multipers/ml/accuracies.py +90 -0
  30. multipers/ml/convolutions.py +532 -0
  31. multipers/ml/invariants_with_persistable.py +79 -0
  32. multipers/ml/kernels.py +176 -0
  33. multipers/ml/mma.py +659 -0
  34. multipers/ml/one.py +472 -0
  35. multipers/ml/point_clouds.py +238 -0
  36. multipers/ml/signed_betti.py +50 -0
  37. multipers/ml/signed_measures.py +1542 -0
  38. multipers/ml/sliced_wasserstein.py +461 -0
  39. multipers/ml/tools.py +113 -0
  40. multipers/mma_structures.cpython-310-darwin.so +0 -0
  41. multipers/mma_structures.pxd +127 -0
  42. multipers/mma_structures.pyx +2433 -0
  43. multipers/multiparameter_edge_collapse.py +41 -0
  44. multipers/multiparameter_module_approximation.cpython-310-darwin.so +0 -0
  45. multipers/multiparameter_module_approximation.pyx +211 -0
  46. multipers/pickle.py +53 -0
  47. multipers/plots.py +326 -0
  48. multipers/point_measure_integration.cpython-310-darwin.so +0 -0
  49. multipers/point_measure_integration.pyx +139 -0
  50. multipers/rank_invariant.cpython-310-darwin.so +0 -0
  51. multipers/rank_invariant.pyx +229 -0
  52. multipers/simplex_tree_multi.cpython-310-darwin.so +0 -0
  53. multipers/simplex_tree_multi.pxd +129 -0
  54. multipers/simplex_tree_multi.pyi +715 -0
  55. multipers/simplex_tree_multi.pyx +4655 -0
  56. multipers/slicer.cpython-310-darwin.so +0 -0
  57. multipers/slicer.pxd +781 -0
  58. multipers/slicer.pyx +3393 -0
  59. multipers/tensor.pxd +13 -0
  60. multipers/test.pyx +44 -0
  61. multipers/tests/__init__.py +40 -0
  62. multipers/tests/old_test_rank_invariant.py +91 -0
  63. multipers/tests/test_diff_helper.py +74 -0
  64. multipers/tests/test_hilbert_function.py +82 -0
  65. multipers/tests/test_mma.py +51 -0
  66. multipers/tests/test_point_clouds.py +59 -0
  67. multipers/tests/test_python-cpp_conversion.py +82 -0
  68. multipers/tests/test_signed_betti.py +181 -0
  69. multipers/tests/test_simplextreemulti.py +98 -0
  70. multipers/tests/test_slicer.py +63 -0
  71. multipers/torch/__init__.py +1 -0
  72. multipers/torch/diff_grids.py +217 -0
  73. multipers/torch/rips_density.py +257 -0
  74. multipers-2.0.0.dist-info/LICENSE +21 -0
  75. multipers-2.0.0.dist-info/METADATA +29 -0
  76. multipers-2.0.0.dist-info/RECORD +78 -0
  77. multipers-2.0.0.dist-info/WHEEL +5 -0
  78. multipers-2.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,4655 @@
1
+ # WARNING: Do not edit this file directly.
2
+ # It is automatically generated from 'multipers/simplex_tree_multi.pyx.tp'.
3
+ # Changes must be made there.
4
+
5
+
6
+
7
+
8
+
9
+ # This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT.
10
+ # See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details.
11
+ # Author(s): Vincent Rouvreau
12
+ #
13
+ # Copyright (C) 2016 Inria
14
+ #
15
+ # Modification(s):
16
+ # - 2023 David Loiseaux : Conversions with standard simplextree, scc2020 format, edge collapses, euler characteristic, grid filtrations.
17
+ # - 2022/11 Hannah Schreiber / David Loiseaux : adapt for multipersistence.
18
+ # - YYYY/MM Author: Description of the modification
19
+
20
+
21
+
22
+ __author__ = "Vincent Rouvreau"
23
+ __copyright__ = "Copyright (C) 2016 Inria"
24
+ __license__ = "MIT"
25
+
26
+ from libc.stdint cimport intptr_t, int32_t, int64_t
27
+ from cython.operator import dereference, preincrement
28
+ from libc.stdint cimport intptr_t
29
+ from libc.stdint cimport uintptr_t, intptr_t
30
+ from libcpp.map cimport map
31
+ from libcpp.utility cimport pair
32
+
33
+ ctypedef fused some_int:
34
+ int32_t
35
+ int64_t
36
+ int
37
+
38
+ ctypedef fused some_float:
39
+ float
40
+ double
41
+ int32_t
42
+ int64_t
43
+
44
+ ctypedef vector[pair[pair[int,int],pair[float,float]]] edge_list_type
45
+
46
+ from typing import Any, Union, ClassVar
47
+
48
+ cimport numpy as cnp
49
+ import numpy as np
50
+ cnp.import_array()
51
+
52
+ from multipers.simplex_tree_multi cimport *
53
+ from multipers.filtration_conversions cimport *
54
+ cimport cython
55
+ from gudhi.simplex_tree import SimplexTree ## Small hack for typing
56
+ from typing import Iterable,Literal,Optional
57
+ from tqdm import tqdm
58
+ import multipers.grids as mpg
59
+
60
+ from multipers.ml.signed_betti import rank_decomposition_by_rectangles
61
+ from multipers.point_measure_integration import sparsify
62
+
63
+ from warnings import warn
64
+
65
+ SAFE_CONVERSION=True #Slower but at least it works everywhere
66
+
67
+ _available_strategies =mpg.Lstrategies
68
+
69
+
70
+
71
+
72
+
73
+ ctypedef Finitely_critical_multi_filtration[int32_t] Fi32
74
+
75
+
76
+
77
+ # SimplexTree python interface
78
+ cdef class SimplexTreeMulti_Fi32:
79
+ """The simplex tree is an efficient and flexible data structure for
80
+ representing general (filtered) simplicial complexes. The data structure
81
+ is described in Jean-Daniel Boissonnat and Clément Maria. The Simplex
82
+ Tree: An Efficient Data Structure for General Simplicial Complexes.
83
+ Algorithmica, pages 1–22, 2014.
84
+
85
+ This class is a multi-filtered, with keys, and non contiguous vertices version
86
+ of the simplex tree.
87
+ """
88
+ cdef public intptr_t thisptr
89
+
90
+ cdef public vector[vector[double]] filtration_grid
91
+ cdef public bool _is_function_simplextree
92
+ # Get the pointer casted as it should be
93
+ cdef Simplex_tree_multi_interface[Fi32, int32_t]* get_ptr(self) noexcept nogil:
94
+ return <Simplex_tree_multi_interface[Fi32, int32_t]*>(self.thisptr)
95
+
96
+ # cdef Simplex_tree_persistence_interface * pcohptr
97
+ # Fake constructor that does nothing but documenting the constructor
98
+ def __init__(self, other = None, num_parameters:int=2,default_values=[], safe_conversion=False):
99
+ """SimplexTreeMulti constructor.
100
+
101
+ :param other: If `other` is `None` (default value), an empty `SimplexTreeMulti` is created.
102
+ If `other` is a `SimplexTree`, the `SimplexTreeMulti` is constructed from a deep copy of `other`.
103
+ If `other` is a `SimplexTreeMulti`, the `SimplexTreeMulti` is constructed from a deep copy of `other`.
104
+ :type other: SimplexTree or SimplexTreeMulti (Optional)
105
+ :param num_parameters: The number of parameter of the multi-parameter filtration.
106
+ :type num_parameters: int
107
+ :returns: An empty or a copy simplex tree.
108
+ :rtype: SimplexTreeMulti
109
+
110
+ :raises TypeError: In case `other` is neither `None`, nor a `SimplexTree`, nor a `SimplexTreeMulti`.
111
+ """
112
+
113
+ @staticmethod
114
+ cdef int32_t T_minus_inf():
115
+ return <int32_t>(np.iinfo(np.int32).min)
116
+ @staticmethod
117
+ cdef int32_t T_inf():
118
+ return <int32_t>(np.iinfo(np.int32).max)
119
+ @staticmethod
120
+ def is_kcritical()->bool:
121
+ return False
122
+ # The real cython constructor
123
+ def __cinit__(self, other = None, int num_parameters=2,
124
+ default_values=np.asarray([SimplexTreeMulti_Fi32.T_minus_inf()]), # I'm not sure why `[]` does not work. Cython bug ?
125
+ bool safe_conversion=False,
126
+ ): #TODO doc
127
+ cdef Fi32 c_default_values = _py21c_i32(default_values)
128
+ cdef intptr_t other_ptr
129
+ if other is not None:
130
+ if isinstance(other, SimplexTreeMulti_Fi32):
131
+ other_ptr = other.thisptr
132
+ self.thisptr = <intptr_t>(new Simplex_tree_multi_interface[Fi32, int32_t](dereference(<Simplex_tree_multi_interface[Fi32, int32_t]*>other_ptr))) ## prevents calling destructor of other
133
+ num_parameters = other.num_parameters
134
+ self.filtration_grid = other.filtration_grid
135
+ elif isinstance(other, SimplexTree): # Constructs a SimplexTreeMulti from a SimplexTree
136
+ self.thisptr = <intptr_t>(new Simplex_tree_multi_interface[Fi32, int32_t]())
137
+ if safe_conversion or SAFE_CONVERSION:
138
+ new_st_multi = _safe_simplextree_multify_Fi32(other, num_parameters = num_parameters, default_values=np.asarray(default_values))
139
+ self.thisptr, new_st_multi.thisptr = new_st_multi.thisptr, self.thisptr
140
+ else:
141
+ other_ptr = other.thisptr
142
+ with nogil:
143
+ self.get_ptr().from_std(other_ptr, num_parameters, c_default_values)
144
+ else:
145
+ raise TypeError("`other` argument requires to be of type `SimplexTree`, `SimplexTreeMulti`, or `None`.")
146
+ else:
147
+ self.thisptr = <intptr_t>(new Simplex_tree_multi_interface[Fi32, int32_t]())
148
+ self.get_ptr().set_number_of_parameters(num_parameters)
149
+ self._is_function_simplextree = False
150
+ self.filtration_grid=[[]*num_parameters]
151
+
152
+ def __dealloc__(self):
153
+ cdef Simplex_tree_multi_interface[Fi32,int32_t]* ptr = self.get_ptr()
154
+ if ptr != NULL:
155
+ del ptr
156
+ # TODO : is that enough ??
157
+
158
+
159
+
160
+
161
+ def copy(self)->SimplexTreeMulti_Fi32:
162
+ """
163
+ :returns: A simplex tree that is a deep copy of itself.
164
+ :rtype: SimplexTreeMulti
165
+
166
+ :note: The persistence information is not copied. If you need it in the clone, you have to call
167
+ :func:`compute_persistence` on it even if you had already computed it in the original.
168
+ """
169
+ stree = SimplexTreeMulti_Fi32(self,num_parameters=self.num_parameters)
170
+ return stree
171
+
172
+ def __deepcopy__(self):
173
+ return self.copy()
174
+
175
+ def filtration(self, simplex:list|np.ndarray)->np.ndarray:
176
+ """This function returns the filtration value for a given N-simplex in
177
+ this simplicial complex, or +infinity if it is not in the complex.
178
+ :param simplex: The N-simplex, represented by a list of vertex.
179
+ :type simplex: list of int
180
+ :returns: The simplicial complex multi-critical filtration value.
181
+ :rtype: numpy array of shape (-1, num_parameters)
182
+ """
183
+ return self[simplex]
184
+
185
+ def assign_filtration(self, simplex:list|np.ndarray, filtration:list|np.ndarray)->SimplexTreeMulti_Fi32:
186
+ """This function assigns a new multi-critical filtration value to a
187
+ given N-simplex.
188
+
189
+ :param simplex: The N-simplex, represented by a list of vertex.
190
+ :type simplex: list of int
191
+ :param filtration: The new filtration(s) value(s), concatenated.
192
+ :type filtration: list[float] or np.ndarray[float, ndim=1]
193
+
194
+ .. note::
195
+ Beware that after this operation, the structure may not be a valid
196
+ filtration anymore, a simplex could have a lower filtration value
197
+ than one of its faces. Callers are responsible for fixing this
198
+ (with more :meth:`assign_filtration` or
199
+ :meth:`make_filtration_non_decreasing` for instance) before calling
200
+ any function that relies on the filtration property, like
201
+ :meth:`persistence`.
202
+ """
203
+ assert len(filtration)>0 and len(filtration) % self.get_ptr().get_number_of_parameters() == 0
204
+ # self.get_ptr().assign_simplex_filtration(simplex, Fi32(<python_filtration_type>filtration))
205
+ filtration = np.asarray(filtration, dtype=np.int32)
206
+ self.get_ptr().assign_simplex_filtration(simplex, _py21c_i32(filtration))
207
+ return self
208
+
209
+ def __getitem__(self, simplex)->np.ndarray:
210
+ cdef vector[int] csimplex = simplex
211
+ cdef Fi32* f_ptr = self.get_ptr().simplex_filtration(csimplex)
212
+ return _ff21cview_i32(f_ptr)
213
+
214
+
215
+ @property
216
+ def num_vertices(self)->int:
217
+ """This function returns the number of vertices of the simplicial
218
+ complex.
219
+
220
+ :returns: The simplicial complex number of vertices.
221
+ :rtype: int
222
+ """
223
+ return self.get_ptr().num_vertices()
224
+
225
+ @property
226
+ def num_simplices(self)->int:
227
+ """This function returns the number of simplices of the simplicial
228
+ complex.
229
+
230
+ :returns: the simplicial complex number of simplices.
231
+ :rtype: int
232
+ """
233
+ return self.get_ptr().num_simplices()
234
+
235
+ @property
236
+ def dimension(self)->int:
237
+ """This function returns the dimension of the simplicial complex.
238
+
239
+ :returns: the simplicial complex dimension.
240
+ :rtype: int
241
+
242
+ .. note::
243
+
244
+ This function is not constant time because it can recompute
245
+ dimension if required (can be triggered by
246
+ :func:`remove_maximal_simplex`
247
+ or
248
+ :func:`prune_above_filtration`
249
+ methods).
250
+ """
251
+ return self.get_ptr().dimension()
252
+ def upper_bound_dimension(self)->int:
253
+ """This function returns a valid dimension upper bound of the
254
+ simplicial complex.
255
+
256
+ :returns: an upper bound on the dimension of the simplicial complex.
257
+ :rtype: int
258
+ """
259
+ return self.get_ptr().upper_bound_dimension()
260
+
261
+ def __iter__(self):
262
+ for stuff in self.get_simplices():
263
+ yield stuff
264
+
265
+ def set_dimension(self, int dimension)->None:
266
+ """This function sets the dimension of the simplicial complex.
267
+
268
+ :param dimension: The new dimension value.
269
+ :type dimension: int
270
+
271
+ .. note::
272
+
273
+ This function must be used with caution because it disables
274
+ dimension recomputation when required
275
+ (this recomputation can be triggered by
276
+ :func:`remove_maximal_simplex`
277
+ or
278
+ :func:`prune_above_filtration`
279
+ ).
280
+ """
281
+ self.get_ptr().set_dimension(dimension)
282
+
283
+ def __contains__(self, simplex):
284
+ """This function returns if the N-simplex was found in the simplicial
285
+ complex or not.
286
+
287
+ :param simplex: The N-simplex to find, represented by a list of vertex.
288
+ :type simplex: list of int
289
+ :returns: true if the simplex was found, false otherwise.
290
+ :rtype: bool
291
+ """
292
+ if len(simplex) == 0:
293
+ return False
294
+ if isinstance(simplex[0], Iterable):
295
+ s,f = simplex
296
+ if not self.get_ptr().find_simplex(simplex):
297
+ return False
298
+ current_f = np.asarray(self[s])
299
+ return np.all(np.asarray(f)>=current_f)
300
+
301
+ return self.get_ptr().find_simplex(simplex)
302
+
303
+ def insert(self, vector[int] simplex, filtration:list|np.ndarray|None=None)->bool:
304
+ """This function inserts the given N-simplex and its subfaces with the
305
+ given filtration value (default value is '0.0'). If some of those
306
+ simplices are already present with a higher filtration value, their
307
+ filtration value is lowered.
308
+
309
+ :param simplex: The N-simplex to insert, represented by a list of
310
+ vertex.
311
+ :type simplex: list of int
312
+ :param filtration: The filtration value of the simplex.
313
+ :type filtration: float
314
+ :returns: true if the simplex was not yet in the complex, false
315
+ otherwise (whatever its original filtration value).
316
+ :rtype: bool
317
+ """
318
+ # TODO C++, to be compatible with insert_batch and multicritical filtrations
319
+ num_parameters = self.get_ptr().get_number_of_parameters()
320
+ assert filtration is None or len(filtration) % num_parameters == 0, f"Invalid number \
321
+ of parameters. Should be {num_parameters}, got {len(filtration)}"
322
+ if filtration is None:
323
+ filtration = np.array([-np.inf]*num_parameters, dtype = float)
324
+
325
+ cdef Finitely_critical_multi_filtration[int32_t] cfiltration = _py21c_i32(np.asarray(filtration, dtype = np.int32))
326
+ return self.get_ptr().insert(simplex,cfiltration)
327
+
328
+ @cython.boundscheck(False)
329
+ @cython.wraparound(False)
330
+ def insert_batch(self, some_int[:,:] vertex_array, some_float[:,:] filtrations)->SimplexTreeMulti_Fi32 :
331
+ """Inserts k-simplices given by a sparse array in a format similar
332
+ to `torch.sparse <https://pytorch.org/docs/stable/sparse.html>`_.
333
+ The n-th simplex has vertices `vertex_array[0,n]`, ...,
334
+ `vertex_array[k,n]` and filtration value `filtrations[n,num_parameters]`.
335
+ /!\ Only compatible with 1-critical filtrations. If a simplex is repeated,
336
+ only one filtration value will be taken into account.
337
+
338
+ :param vertex_array: the k-simplices to insert.
339
+ :type vertex_array: numpy.array of shape (k+1,n)
340
+ :param filtrations: the filtration values.
341
+ :type filtrations: numpy.array of shape (n,num_parameters)
342
+ """
343
+ # TODO : multi-critical
344
+ # cdef vector[int] vertices = np.unique(vertex_array)
345
+ cdef Py_ssize_t k = vertex_array.shape[0]
346
+ cdef Py_ssize_t n = vertex_array.shape[1]
347
+ cdef int num_parameters = self.get_ptr().get_number_of_parameters()
348
+ cdef bool empty_filtration = (filtrations.size == 0)
349
+ if not empty_filtration :
350
+ assert filtrations.shape[0] == n, f"inconsistent sizes for vertex_array and filtrations\
351
+ Filtrations should be of shape ({n},{self.num_parameters})"
352
+ assert filtrations.shape[1] == num_parameters, f"Inconsistent number of parameters.\
353
+ Filtrations should be of shape ({n},{self.num_parameters})"
354
+ cdef Py_ssize_t i
355
+ cdef Py_ssize_t j
356
+ cdef vector[int] v
357
+ cdef Finitely_critical_multi_filtration[int32_t] w
358
+ if empty_filtration:
359
+ w = Finitely_critical_multi_filtration[int32_t](num_parameters) # at -inf by default
360
+ with nogil:
361
+ for i in range(n):
362
+ # vertex
363
+ for j in range(k):
364
+ v.push_back(vertex_array[j, i])
365
+ #filtration
366
+ if not empty_filtration:
367
+ for j in range(num_parameters):
368
+ w.push_back(<int32_t>filtrations[i,j])
369
+ self.get_ptr().insert(v, w)
370
+ v.clear()
371
+ if not empty_filtration:
372
+ w.clear()
373
+ #repair filtration if necessary
374
+ if empty_filtration:
375
+ self.make_filtration_non_decreasing()
376
+ return self
377
+
378
+ def lower_star_multi_filtration_update(self, nodes_filtrations):
379
+ """
380
+ Updates the multi filtration of the simplextree to the lower-star
381
+ filtration defined on the vertices, by `node_filtrations`.
382
+ """
383
+ cdef Py_ssize_t num_vertices = nodes_filtrations.shape[0]
384
+ cdef Py_ssize_t num_parameters = nodes_filtrations.shape[1]
385
+ 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=})."
386
+
387
+ cdef Simplex_tree_multi_simplices_iterator[Fi32] it = self.get_ptr().get_simplices_iterator_begin()
388
+ cdef Simplex_tree_multi_simplices_iterator[Fi32] end = self.get_ptr().get_simplices_iterator_end()
389
+ cdef Py_ssize_t node_idx = 0
390
+ cdef int32_t[:,:] F = nodes_filtrations
391
+ cdef int32_t minus_inf = -np.inf
392
+ with nogil:
393
+ while it != end:
394
+ pair_sf = <pair[simplex_type, Fi32*]> self.get_ptr().get_simplex_and_filtration(dereference(it))
395
+ if pair_sf.first.size() == 1: # dimension == 0
396
+ for i in range(num_parameters):
397
+ dereference(pair_sf.second)[i] = F[node_idx,i]
398
+ node_idx += 1
399
+ # with gil:
400
+ # print(pair_sf.first, node_idx,i, F[node_idx,i])
401
+ else:
402
+ for i in range(num_parameters):
403
+ dereference(pair_sf.second)[i] = minus_inf
404
+ preincrement(it)
405
+ self.make_filtration_non_decreasing()
406
+ return self
407
+
408
+
409
+ def assign_all(self, filtration_values)-> SimplexTreeMulti_Fi32:
410
+ """
411
+ Updates the filtration values of all of the simplices, with `filtration_values`
412
+ with order given by the simplextree iterator, e.g. self.get_simplices().
413
+ """
414
+ cdef Py_ssize_t num_simplices = filtration_values.shape[0]
415
+ cdef Py_ssize_t num_parameters = filtration_values.shape[1]
416
+
417
+ assert num_simplices == self.num_simplices, f"Number of filtration values {filtration_values.shape[0]} is not the number of simplices {self.num_simplices}"
418
+ assert num_parameters == self.num_parameters, f"Number of parameter do not coincide {filtration_values.shape[1]} vs {self.num_parameters}"
419
+ cdef Simplex_tree_multi_simplices_iterator[Fi32] it = self.get_ptr().get_simplices_iterator_begin()
420
+ cdef Simplex_tree_multi_simplices_iterator[Fi32] end = self.get_ptr().get_simplices_iterator_end()
421
+ cdef Simplex_tree_multi_simplex_handle[Fi32] sh = dereference(it)
422
+ cdef int counter =0
423
+ # cdef cnp.ndarray[int32_t,ndim=1] current_filtration
424
+ cdef int32_t[:,:] F = filtration_values
425
+ with nogil:
426
+ while it != end:
427
+ pair_sf =<pair[simplex_type, Fi32*]> self.get_ptr().get_simplex_and_filtration(dereference(it))
428
+
429
+ for i in range(num_parameters):
430
+ dereference(pair_sf.second)[i] = F[counter,i]
431
+ # current_filtration= F[counter]
432
+ counter += 1
433
+ # yield SimplexTreeMulti._pair_simplex_filtration_to_python(out)
434
+ preincrement(it)
435
+
436
+
437
+
438
+ @cython.boundscheck(False)
439
+ @cython.wraparound(False)
440
+ def assign_batch_filtration(self, some_int[:,:] vertex_array, some_float[:,:] filtrations, bool propagate=True)->SimplexTreeMulti_Fi32:
441
+ """Assign k-simplices given by a sparse array in a format similar
442
+ to `torch.sparse <https://pytorch.org/docs/stable/sparse.html>`_.
443
+ The n-th simplex has vertices `vertex_array[0,n]`, ...,
444
+ `vertex_array[k,n]` and filtration value `filtrations[n,num_parameters]`.
445
+ /!\ Only compatible with 1-critical filtrations. If a simplex is repeated,
446
+ only one filtration value will be taken into account.
447
+
448
+ :param vertex_array: the k-simplices to assign.
449
+ :type vertex_array: numpy.array of shape (k+1,n)
450
+ :param filtrations: the filtration values.
451
+ :type filtrations: numpy.array of shape (n,num_parameters)
452
+ """
453
+ cdef Py_ssize_t k = vertex_array.shape[0]
454
+ cdef Py_ssize_t n = vertex_array.shape[1]
455
+ assert filtrations.shape[0] == n, 'inconsistent sizes for vertex_array and filtrations'
456
+ assert filtrations.shape[1] == self.num_parameters, "wrong number of parameters"
457
+ cdef Py_ssize_t i
458
+ cdef Py_ssize_t j
459
+ cdef vector[int] v
460
+ cdef Finitely_critical_multi_filtration[int32_t] w
461
+ cdef int n_parameters = self.num_parameters
462
+ with nogil:
463
+ for i in range(n):
464
+ for j in range(k):
465
+ v.push_back(vertex_array[j, i])
466
+ for j in range(n_parameters):
467
+ w.push_back(<int32_t>filtrations[i,j])
468
+ self.get_ptr().assign_simplex_filtration(v, w)
469
+ v.clear()
470
+ w.clear()
471
+ if propagate: self.make_filtration_non_decreasing()
472
+ return self
473
+
474
+ @cython.boundscheck(False)
475
+ @cython.wraparound(False)
476
+ def euler_characteristic(self, dtype = np.int32):
477
+ """This function returns a generator with simplices and their given
478
+ filtration values.
479
+
480
+ :returns: The simplices.
481
+ :rtype: generator with tuples(simplex, filtration)
482
+ """
483
+ cdef Simplex_tree_multi_simplices_iterator[Fi32] it = self.get_ptr().get_simplices_iterator_begin()
484
+ cdef Simplex_tree_multi_simplices_iterator[Fi32] end = self.get_ptr().get_simplices_iterator_end()
485
+ cdef Simplex_tree_multi_simplex_handle[Fi32] sh = dereference(it)
486
+ cdef int num_parameters = self.get_ptr().get_number_of_parameters()
487
+ cdef dict[tuple,int] out = {}
488
+ cdef int dim
489
+ while it != end:
490
+ pair_sf = <pair[simplex_type, Fi32*]> self.get_ptr().get_simplex_and_filtration(dereference(it))
491
+ # TODO: optimize https://stackoverflow.com/questions/16589791/most-efficient-property-to-hash-for-numpy-array
492
+ key = tuple(_ff21cview_i32(pair_sf.second))
493
+ dim = pair_sf.first.size() -1 % 2
494
+ out[key] = out.get(key,0)+(-1)**dim
495
+ num_keys = len(out)
496
+ new_pts = np.fromiter(out.keys(), dtype=np.dtype((dtype,num_parameters)), count=num_keys)
497
+ new_weights = np.fromiter(out.values(), dtype=np.int32, count=num_keys)
498
+ idx = np.nonzero(new_weights)
499
+ new_pts = new_pts[idx]
500
+ new_weights = new_weights[idx]
501
+ return (new_pts, new_weights)
502
+
503
+
504
+ def get_simplices(self)->Iterable[tuple[np.ndarray, np.ndarray]]:
505
+ """This function returns a generator with simplices and their given
506
+ filtration values.
507
+
508
+ :returns: The simplices.
509
+ :rtype: generator with tuples(simplex, filtration)
510
+ """
511
+ cdef Simplex_tree_multi_simplices_iterator[Fi32] it = self.get_ptr().get_simplices_iterator_begin()
512
+ cdef Simplex_tree_multi_simplices_iterator[Fi32] end = self.get_ptr().get_simplices_iterator_end()
513
+ cdef Simplex_tree_multi_simplex_handle[Fi32] sh = dereference(it)
514
+ cdef int num_parameters = self.get_ptr().get_number_of_parameters()
515
+ while it != end:
516
+ pair_sf = <pair[simplex_type, Fi32*]> self.get_ptr().get_simplex_and_filtration(dereference(it))
517
+ yield (
518
+ np.asarray(pair_sf.first, dtype=int),
519
+ _ff21cview_i32(pair_sf.second)
520
+ )
521
+ preincrement(it)
522
+
523
+
524
+
525
+ def persistence_approximation(self, **kwargs):
526
+ from multipers.multiparameter_module_approximation import module_approximation
527
+ return module_approximation(self, **kwargs)
528
+
529
+
530
+ def get_skeleton(self, dimension)->Iterable[tuple[np.ndarray,np.ndarray]]:
531
+ """This function returns a generator with the (simplices of the) skeleton of a maximum given dimension.
532
+
533
+ :param dimension: The skeleton dimension value.
534
+ :type dimension: int
535
+ :returns: The (simplices of the) skeleton of a maximum dimension.
536
+ :rtype: generator with tuples(simplex, filtration)
537
+ """
538
+ cdef Simplex_tree_multi_skeleton_iterator[Fi32] it = self.get_ptr().get_skeleton_iterator_begin(dimension)
539
+ cdef Simplex_tree_multi_skeleton_iterator[Fi32] end = self.get_ptr().get_skeleton_iterator_end(dimension)
540
+ cdef int num_parameters = self.get_ptr().get_number_of_parameters()
541
+ while it != end:
542
+ # yield self.get_ptr().get_simplex_and_filtration(dereference(it))
543
+ pair = self.get_ptr().get_simplex_and_filtration(dereference(it))
544
+ yield (np.asarray(pair.first, dtype=int),
545
+ _ff21cview_i32(pair.second)
546
+ )
547
+ preincrement(it)
548
+
549
+ # def get_star(self, simplex):
550
+ # """This function returns the star of a given N-simplex.
551
+
552
+ # :param simplex: The N-simplex, represented by a list of vertex.
553
+ # :type simplex: list of int
554
+ # :returns: The (simplices of the) star of a simplex.
555
+ # :rtype: list of tuples(simplex, filtration)
556
+ # """
557
+ # cdef simplex_type csimplex = simplex
558
+ # cdef int num_parameters = self.num_parameters
559
+ # # for i in simplex:
560
+ # # csimplex.push_back(i)
561
+ # cdef vector[simplex_filtration_type] star \
562
+ # = self.get_ptr().get_star(csimplex)
563
+ # ct = []
564
+
565
+ # for filtered_simplex in star:
566
+ # v = []
567
+ # for vertex in filtered_simplex.first:
568
+ # v.append(vertex)
569
+ # ct.append((v, np.asarray(<int32_t[:num_parameters]>filtered_simplex.second)))
570
+ # return ct
571
+
572
+ # def get_cofaces(self, simplex, codimension):
573
+ # """This function returns the cofaces of a given N-simplex with a
574
+ # given codimension.
575
+
576
+ # :param simplex: The N-simplex, represented by a list of vertex.
577
+ # :type simplex: list of int
578
+ # :param codimension: The codimension. If codimension = 0, all cofaces
579
+ # are returned (equivalent of get_star function)
580
+ # :type codimension: int
581
+ # :returns: The (simplices of the) cofaces of a simplex
582
+ # :rtype: list of tuples(simplex, filtration)
583
+ # """
584
+ # cdef vector[int] csimplex = simplex
585
+ # cdef int num_parameters = self.num_parameters
586
+ # # for i in simplex:
587
+ # # csimplex.push_back(i)
588
+ # cdef vector[simplex_filtration_type] cofaces \
589
+ # = self.get_ptr().get_cofaces(csimplex, <int>codimension)
590
+ # ct = []
591
+ # for filtered_simplex in cofaces:
592
+ # v = []
593
+ # for vertex in filtered_simplex.first:
594
+ # v.append(vertex)
595
+ # ct.append((v, np.asarray(<int32_t[:num_parameters]>filtered_simplex.second)))
596
+ # return ct
597
+
598
+ def get_boundaries(self, simplex)->Iterable[tuple[np.ndarray, np.ndarray]]:
599
+ """This function returns a generator with the boundaries of a given N-simplex.
600
+ If you do not need the filtration values, the boundary can also be obtained as
601
+ :code:`itertools.combinations(simplex,len(simplex)-1)`.
602
+
603
+ :param simplex: The N-simplex, represented by a list of vertex.
604
+ :type simplex: list of int.
605
+ :returns: The (simplices of the) boundary of a simplex
606
+ :rtype: generator with tuples(simplex, filtration)
607
+ """
608
+ cdef pair[Simplex_tree_multi_boundary_iterator[Fi32], Simplex_tree_multi_boundary_iterator[Fi32]] it = self.get_ptr().get_boundary_iterators(simplex)
609
+
610
+ cdef int num_parameters = self.get_ptr().get_number_of_parameters()
611
+ while it.first != it.second:
612
+ # yield self.get_ptr().get_simplex_and_filtration(dereference(it))
613
+ pair = self.get_ptr().get_simplex_and_filtration(dereference(it.first))
614
+ yield (np.asarray(pair.first, dtype=int),
615
+ _ff21cview_i32(pair.second)
616
+ )
617
+ preincrement(it.first)
618
+ def remove_maximal_simplex(self, simplex)->SimplexTreeMulti_Fi32:
619
+ """This function removes a given maximal N-simplex from the simplicial
620
+ complex.
621
+
622
+ :param simplex: The N-simplex, represented by a list of vertex.
623
+ :type simplex: list of int
624
+
625
+ .. note::
626
+
627
+ The dimension of the simplicial complex may be lower after calling
628
+ remove_maximal_simplex than it was before. However,
629
+ :func:`upper_bound_dimension`
630
+ method will return the old value, which
631
+ remains a valid upper bound. If you care, you can call
632
+ :func:`dimension`
633
+ to recompute the exact dimension.
634
+ """
635
+ self.get_ptr().remove_maximal_simplex(simplex)
636
+ return self
637
+
638
+ # def prune_above_filtration(self, filtration)->bool:
639
+ # """Prune above filtration value given as parameter.
640
+
641
+ # :param filtration: Maximum threshold value.
642
+ # :type filtration: float
643
+ # :returns: The filtration modification information.
644
+ # :rtype: bool
645
+
646
+
647
+ # .. note::
648
+
649
+ # Note that the dimension of the simplicial complex may be lower
650
+ # after calling
651
+ # :func:`prune_above_filtration`
652
+ # than it was before. However,
653
+ # :func:`upper_bound_dimension`
654
+ # will return the old value, which remains a
655
+ # valid upper bound. If you care, you can call
656
+ # :func:`dimension`
657
+ # method to recompute the exact dimension.
658
+ # """
659
+ # return self.get_ptr().prune_above_filtration(filtration)
660
+ def prune_above_dimension(self, int dimension):
661
+ """Remove all simplices of dimension greater than a given value.
662
+
663
+ :param dimension: Maximum dimension value.
664
+ :type dimension: int
665
+ :returns: The modification information.
666
+ :rtype: bool
667
+ """
668
+ return self.get_ptr().prune_above_dimension(dimension)
669
+
670
+ def expansion(self, int max_dim)->SimplexTreeMulti_Fi32:
671
+ """Expands the simplex tree containing only its one skeleton
672
+ until dimension max_dim.
673
+
674
+ The expanded simplicial complex until dimension :math:`d`
675
+ attached to a graph :math:`G` is the maximal simplicial complex of
676
+ dimension at most :math:`d` admitting the graph :math:`G` as
677
+ :math:`1`-skeleton.
678
+ The filtration value assigned to a simplex is the maximal filtration
679
+ value of one of its edges.
680
+
681
+ The simplex tree must contain no simplex of dimension bigger than
682
+ 1 when calling the method.
683
+
684
+ :param max_dim: The maximal dimension.
685
+ :type max_dim: int
686
+ """
687
+ with nogil:
688
+ self.get_ptr().expansion(max_dim)
689
+ # This is a fix for multipersistence. FIXME expansion in c++
690
+ self.get_ptr().make_filtration_non_decreasing()
691
+ return self
692
+
693
+ def make_filtration_non_decreasing(self)->bool:
694
+ """This function ensures that each simplex has a higher filtration
695
+ value than its faces by increasing the filtration values.
696
+
697
+ :returns: True if any filtration value was modified,
698
+ False if the filtration was already non-decreasing.
699
+ :rtype: bool
700
+ """
701
+ cdef bool out
702
+ with nogil:
703
+ out = self.get_ptr().make_filtration_non_decreasing()
704
+ return out
705
+
706
+ def reset_filtration(self, filtration, min_dim = 0)->SimplexTreeMulti_Fi32:
707
+ """This function resets the filtration value of all the simplices of dimension at least min_dim. Resets all the
708
+ simplex tree when `min_dim = 0`.
709
+ `reset_filtration` may break the filtration property with `min_dim > 0`, and it is the user's responsibility to
710
+ make it a valid filtration (using a large enough `filt_value`, or calling `make_filtration_non_decreasing`
711
+ afterwards for instance).
712
+
713
+ :param filtration: New threshold value.
714
+ :type filtration: float.
715
+ :param min_dim: The minimal dimension. Default value is 0.
716
+ :type min_dim: int.
717
+ """
718
+ cdef Finitely_critical_multi_filtration[int32_t] cfiltration = _py21c_i32(np.asarray(filtration, dtype = np.int32))
719
+ self.get_ptr().reset_filtration(cfiltration, min_dim)
720
+ return self
721
+
722
+ def pts_to_indices(self,pts:np.ndarray, simplices_dimensions:Iterable[int]) -> tuple[np.ndarray,np.ndarray]:
723
+ """
724
+ Returns the indices of the simplex tree with corresponding filtrations.
725
+
726
+ Args:
727
+ - st: SimplexTreeMulti on which to recover the indices.
728
+ - pts: (num_pts, num_parameters,) array of points to recover.
729
+ simplices_dimensions: (num_parameters,) the simplices dimension to take into account for each parameter.
730
+
731
+ Returns:
732
+ - A (m, num_parameters) array containing the found indices (m <= num_pts).
733
+ - A (m, 2) array containing the non-found indices (pt_index, parameter failing).
734
+ """
735
+
736
+ # TODO detect rank or not
737
+ cdef bool is_rank_invariant = pts.shape[1] == 2*self.num_parameters
738
+ if is_rank_invariant:
739
+ births = pts[:,:self.num_parameters]
740
+ deaths = pts[:,self.num_parameters:]
741
+ births_indices,non_recovered_births = self.pts_to_indices(births, simplices_dimensions)
742
+ deaths_indices,non_recovered_deaths = self.pts_to_indices(deaths, simplices_dimensions)
743
+ non_recovered_pts = np.concatenate([non_recovered_births,non_recovered_deaths], axis=0)
744
+ pts_indices = np.concatenate([births_indices,deaths_indices], axis=1)
745
+ return pts_indices, non_recovered_pts
746
+
747
+ cdef vector[vector[int32_t]] cpts = pts
748
+ cdef vector[int] csimplices_dimensions = simplices_dimensions
749
+ # cdef pair[indices_type,indices_type] out
750
+ found_indices,not_found_indices = self.get_ptr().pts_to_indices(cpts,csimplices_dimensions)
751
+ if len(found_indices) == 0:
752
+ found_indices = np.empty(shape=(0,self.num_parameters), dtype = np.int32)
753
+ if len(not_found_indices) == 0:
754
+ not_found_indices = np.empty(shape=(0,2), dtype = np.int32)
755
+ return np.asarray(found_indices), np.asarray(not_found_indices)
756
+ ## This function is only meant for the edge collapse interface.
757
+ def get_edge_list(self):
758
+ """
759
+ in the filtration-domination's format
760
+ """
761
+ cdef edge_list out
762
+ with nogil:
763
+ out = self.get_ptr().get_edge_list()
764
+ return out
765
+
766
+ 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_Fi32:
767
+ """Edge collapse for 1-critical 2-parameter clique complex (see https://arxiv.org/abs/2211.05574).
768
+ It uses the code from the github repository https://github.com/aj-alonso/filtration_domination .
769
+
770
+ Parameters
771
+ ----------
772
+
773
+ max_dimension:int
774
+ Max simplicial dimension of the complex. Unless specified, keeps the same dimension.
775
+ num:int
776
+ The number of collapses to do.
777
+ strong:bool
778
+ Whether to use strong collapses or standard collapses (slower, but may remove more edges)
779
+ full:bool
780
+ 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.
781
+ progress:bool
782
+ If true, shows the progress of the number of collapses.
783
+
784
+ WARNING
785
+ -------
786
+
787
+ - 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.
788
+ - This is for 1 critical simplices, with 2 parameter persistence.
789
+ Returns
790
+ -------
791
+
792
+ self:SimplexTreeMulti
793
+ A (smaller) simplex tree that has the same homology over this bifiltration.
794
+
795
+ """
796
+ # TODO : find a way to do multiple edge collapses without python conversions.
797
+ if num == 0:
798
+ return self
799
+ elif num == -1:
800
+ num=100
801
+ full=False
802
+ elif num == -2:
803
+ num=100
804
+ full=True
805
+ assert self.num_parameters == 2, "Number of parameters has to be 2 to use edge collapses ! This is a limitation of Filtration-domination"
806
+ if self.dimension > 1 and not ignore_warning: warn("This method ignores simplices of dimension > 1 !")
807
+
808
+ max_dimension = self.dimension if max_dimension <=0 else max_dimension
809
+
810
+ # Retrieves the edge list, and send it to filration_domination
811
+ edges = self.get_edge_list()
812
+ from multipers.multiparameter_edge_collapse import _collapse_edge_list
813
+ edges = _collapse_edge_list(edges, num=num, full=full, strong=strong, progress=progress)
814
+ # Retrieves the collapsed simplicial complex
815
+ self._reconstruct_from_edge_list(edges, swap=True, expand_dimension=max_dimension)
816
+ return self
817
+
818
+ @cython.inline
819
+ cdef _reconstruct_from_edge_list(self,edge_list edges, bool swap=True, int expand_dimension=0):
820
+ """
821
+ Generates a 1-dimensional copy of self, with the edges given as input. Useful for edge collapses
822
+
823
+ Input
824
+ -----
825
+
826
+ - edges : Iterable[(int,int),(float,float)] ## This is the format of the rust library filtration-domination
827
+ - swap : bool
828
+ If true, will swap self and the collapsed simplextrees.
829
+ - expand_dim : int
830
+ expands back the simplextree to this dimension
831
+ Ouput
832
+ -----
833
+
834
+ The reduced SimplexTreeMulti having only these edges.
835
+ """
836
+ reduced_tree = SimplexTreeMulti_Fi32(num_parameters=self.num_parameters)
837
+
838
+ ## Adds vertices back, with good filtration
839
+ if self.num_vertices > 0:
840
+ vertices = np.fromiter((splx[0] for splx, f in self.get_skeleton(0)), dtype=np.int32)[None,:]
841
+ vertices_filtration = np.fromiter((f for splx, f in self.get_skeleton(0)), dtype=np.dtype((np.int32,2)))
842
+ reduced_tree.insert_batch(vertices, vertices_filtration)
843
+
844
+ ## Adds edges again
845
+ if self.num_simplices - self.num_vertices > 0:
846
+ edges_filtration = np.fromiter(((e.second.first, e.second.second) for e in edges), dtype=np.dtype((np.int32,2)))
847
+ edges_idx = np.fromiter(((e.first.first, e.first.second) for e in edges), dtype=np.dtype((np.int32,2))).T
848
+ reduced_tree.insert_batch(edges_idx, edges_filtration)
849
+ if swap:
850
+ # Swaps the simplextrees pointers
851
+ self.thisptr, reduced_tree.thisptr = reduced_tree.thisptr, self.thisptr # Swaps self and reduced tree (self is a local variable)
852
+ if expand_dimension > 0:
853
+ self.expansion(expand_dimension) # Expands back the simplextree to the original dimension.
854
+ return self if swap else reduced_tree
855
+
856
+ @property
857
+ def num_parameters(self)->int:
858
+ return self.get_ptr().get_number_of_parameters()
859
+ def get_simplices_of_dimension(self, dim:int)->np.ndarray:
860
+ return np.asarray(self.get_ptr().get_simplices_of_dimension(dim), dtype=int)
861
+ def key(self, simplex:list|np.ndarray):
862
+ return self.get_ptr().get_key(simplex)
863
+ def set_keys_to_enumerate(self)->None:
864
+ self.get_ptr().set_keys_to_enumerate()
865
+ return
866
+ def set_key(self,simplex:list|np.ndarray, key:int)->None:
867
+ self.get_ptr().set_key(simplex, key)
868
+ return
869
+
870
+
871
+
872
+ def _to_scc(self,filtration_dtype=np.int32, bool flattened=False):
873
+ """
874
+ Turns a simplextree into a (simplicial) module presentation.
875
+ """
876
+ cdef bool is_function_st = self._is_function_simplextree
877
+
878
+ cdef pair[vector[vector[int32_t]], boundary_matrix] out
879
+ if flattened:
880
+ # out = simplextree_to_ordered_bf(cptr)
881
+ out = self.get_ptr().simplextree_to_ordered_bf()
882
+ return np.asarray(out.first,dtype=filtration_dtype), tuple(out.second)
883
+ if is_function_st:
884
+ blocks = self.get_ptr().function_simplextree_to_scc()
885
+ else:
886
+ blocks = self.get_ptr().simplextree_to_scc()
887
+ # reduces the space in memory
888
+ if is_function_st:
889
+ blocks = [(tuple(f), tuple(b)) for f,b in blocks[::-1]]
890
+ else:
891
+ blocks = [(np.asarray(f,dtype=filtration_dtype), tuple(b)) for f,b in blocks[::-1]] ## presentation is on the other order
892
+ return blocks # +[(np.empty(0,dtype=filtration_dtype),[])] ## TODO : investigate
893
+
894
+ def to_scc_kcritical(self,
895
+ path:os.PathLike|str,
896
+ bool rivet_compatible=False,
897
+ bool strip_comments=False,
898
+ bool ignore_last_generators=False,
899
+ bool overwrite=False,
900
+ bool reverse_block=False,
901
+ ):
902
+ """
903
+ TODO: function-simplextree, from squeezed
904
+ """
905
+ from os.path import exists
906
+ from os import remove
907
+ if exists(path):
908
+ if not(overwrite):
909
+ raise Exception(f"The file {path} already exists. Use the `overwrite` flag if you want to overwrite.")
910
+ remove(path)
911
+ # blocks = simplextree2scc(self)
912
+ blocks = self._to_scc()
913
+ from multipers.io import scc2disk
914
+ scc2disk(blocks,
915
+ path=path,
916
+ num_parameters=self.num_parameters,
917
+ reverse_block=reverse_block,
918
+ rivet_compatible=rivet_compatible,
919
+ ignore_last_generators=ignore_last_generators,
920
+ strip_comments=strip_comments,
921
+ )
922
+
923
+ def to_scc_function_st(self,
924
+ path="scc_dataset.scc",
925
+ bool rivet_compatible=False,
926
+ bool strip_comments=False,
927
+ bool ignore_last_generators=False,
928
+ bool overwrite=False,
929
+ bool reverse_block=True,
930
+ ):
931
+ from multipers.io import scc2disk
932
+ from warnings import warn
933
+ warn("This function is not tested yet.")
934
+ from os.path import exists
935
+ from os import remove
936
+ if exists(path):
937
+ if not(overwrite):
938
+ raise Exception(f"The file {path} already exists. Use the `overwrite` flag if you want to overwrite.")
939
+ remove(path)
940
+ stuff = self._to_scc(self)
941
+ if reverse_block: stuff.reverse()
942
+ cdef int num_parameters = self.num_parameters
943
+ with open(path, "w") as f:
944
+ f.write("scc2020\n") if not rivet_compatible else f.write("firep\n")
945
+ if not strip_comments and not rivet_compatible: f.write("# Number of parameters\n")
946
+ num_parameters = self.num_parameters
947
+ if rivet_compatible:
948
+ assert num_parameters == 2
949
+ f.write("Filtration 1\n")
950
+ f.write("Filtration 2\n")
951
+ else:
952
+ f.write(f"{num_parameters}\n")
953
+
954
+ if not strip_comments: f.write("# Sizes of generating sets\n")
955
+ for block in stuff: f.write(f"{len(block[1])} ")
956
+ f.write("\n")
957
+
958
+ for i,block in enumerate(stuff):
959
+ if (rivet_compatible or ignore_last_generators) and i == len(stuff)-1: continue
960
+ if not strip_comments: f.write(f"# Block of dimension {len(stuff)-i}\n")
961
+ for filtration, boundary in zip(*block):
962
+ k = len(filtration)
963
+ for j in range(k):
964
+ if filtration[j] != np.inf:
965
+ line = f"{filtration[j]} {k} ; " + " ".join([str(x) for x in boundary]) +"\n"
966
+ f.write(line)
967
+ def to_scc(self,**kwargs):
968
+ """
969
+ Returns an scc representation of the simplextree.
970
+ """
971
+ if self._is_function_simplextree:
972
+ return self.to_scc_function_st(**kwargs)
973
+ else:
974
+ return self.to_scc_kcritical(**kwargs)
975
+
976
+ 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:
977
+ """ Create a file that can be imported by rivet, representing the filtration of the simplextree.
978
+
979
+ Parameters
980
+ ----------
981
+
982
+ path:str
983
+ path of the file.
984
+ degree:int
985
+ The homological degree to ask rivet to compute.
986
+ progress:bool = True
987
+ Shows the progress bar.
988
+ overwrite:bool = False
989
+ If true, will overwrite the previous file if it already exists.
990
+ """
991
+ ...
992
+ from os.path import exists
993
+ from os import remove
994
+ if exists(path):
995
+ if not(overwrite):
996
+ print(f"The file {path} already exists. Use the `overwrite` flag if you want to overwrite.")
997
+ return
998
+ remove(path)
999
+ file = open(path, "a")
1000
+ file.write("# This file was generated by multipers.\n")
1001
+ file.write("--datatype bifiltration\n")
1002
+ file.write(f"--homology {degree}\n") if degree is not None else None
1003
+ file.write(f"-x {xbins}\n") if xbins is not None else None
1004
+ file.write(f"-y {ybins}\n") if ybins is not None else None
1005
+ file.write("--xlabel time of appearance\n")
1006
+ file.write("--ylabel density\n\n")
1007
+ from tqdm import tqdm
1008
+ with tqdm(total=self.num_simplices, position=0, disable = not(progress), desc="Writing simplex to file") as bar:
1009
+ for dim in range(0,self.dimension+1): # Not sure if dimension sort is necessary for rivet. Check ?
1010
+ file.write(f"# block of dimension {dim}\n")
1011
+ for s,F in self.get_skeleton(dim):
1012
+ if len(s) != dim+1: continue
1013
+ for i in s:
1014
+ file.write(str(i) + " ")
1015
+ file.write("; ")
1016
+ for f in F:
1017
+ file.write(str(f) + " ")
1018
+ file.write("\n")
1019
+ bar.update(1)
1020
+ file.close()
1021
+ return
1022
+
1023
+
1024
+
1025
+ def _get_filtration_values(self, vector[int] degrees, bool inf_to_nan:bool=False, bool return_raw = False)->Iterable[np.ndarray]:
1026
+ # cdef vector[int] c_degrees = degrees
1027
+ # out = get_filtration_values_from_ptr[Fi32](ptr, degrees)
1028
+ cdef intptr_t ptr = self.thisptr
1029
+ cdef vector[vector[vector[int32_t]]] out
1030
+ with nogil:
1031
+ out = self.get_ptr().get_filtration_values(degrees)
1032
+ filtrations_values = [np.asarray(filtration) for filtration in out]
1033
+ # Removes infs
1034
+ if inf_to_nan:
1035
+ for i,f in enumerate(filtrations_values):
1036
+ filtrations_values[i][f == np.inf] = np.nan
1037
+ filtrations_values[i][f == - np.inf] = np.nan
1038
+ return filtrations_values
1039
+
1040
+
1041
+ 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]:
1042
+ """
1043
+ Returns a grid over the n-filtration, from the simplextree. Usefull for grid_squeeze. TODO : multicritical
1044
+
1045
+ Parameters
1046
+ ----------
1047
+
1048
+ resolution: list[int]
1049
+ resolution of the grid, for each parameter
1050
+ box=None : pair[list[float]]
1051
+ Grid bounds. format : [low bound, high bound]
1052
+ If None is given, will use the filtration bounds of the simplextree.
1053
+ grid_strategy="regular" : string
1054
+ Either "regular", "quantile", or "exact".
1055
+ Returns
1056
+ -------
1057
+
1058
+ List of filtration values, for each parameter, defining the grid.
1059
+ """
1060
+ if degrees is None:
1061
+ degrees = range(self.dimension+1)
1062
+
1063
+
1064
+ ## preprocesses the filtration values:
1065
+ filtrations_values = np.concatenate(self._get_filtration_values(degrees, inf_to_nan=True), axis=1)
1066
+ # removes duplicate + sort (nan at the end)
1067
+ filtrations_values = [np.unique(filtration) for filtration in filtrations_values]
1068
+ # removes nan
1069
+ filtrations_values = [filtration[:-1] if np.isnan(filtration[-1]) else filtration for filtration in filtrations_values]
1070
+
1071
+ return mpg.compute_grid(filtrations_values, resolution=resolution,strategy=grid_strategy,drop_quantiles=drop_quantiles)
1072
+
1073
+
1074
+
1075
+ 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):
1076
+ """
1077
+ Fit the filtration of the simplextree to a grid.
1078
+
1079
+ :param filtration_grid: The grid on which to squeeze. An example of grid can be given by the `get_filtration_grid` method.
1080
+ :type filtration_grid: list[list[float]]
1081
+ :param coordinate_values: If true, the filtrations values of the simplices will be set to the coordinate of the filtration grid.
1082
+ :type coordinate_values: bool
1083
+ """
1084
+ if not force and self._is_squeezed:
1085
+ raise Exception("SimplexTree already squeezed, use `force=True` if that's really what you want to do.")
1086
+ #TODO : multi-critical
1087
+ if filtration_grid is None:
1088
+ filtration_grid = self.get_filtration_grid(grid_strategy=grid_strategy, **filtration_grid_kwargs)
1089
+ cdef vector[vector[double]] c_filtration_grid = filtration_grid
1090
+ 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()}"
1091
+ cdef intptr_t ptr = self.thisptr
1092
+ if coordinate_values and inplace:
1093
+ self.filtration_grid = c_filtration_grid
1094
+ if inplace or not coordinate_values:
1095
+ self.get_ptr().squeeze_filtration_inplace(c_filtration_grid, coordinate_values)
1096
+ else:
1097
+ out = SimplexTreeMulti_Fi32(num_parameters=self.num_parameters)
1098
+ self.get_ptr().squeeze_filtration(out.thisptr, c_filtration_grid)
1099
+ out.filtration_grid = c_filtration_grid
1100
+ return out
1101
+ return self
1102
+
1103
+ @property
1104
+ def _is_squeezed(self)->bool:
1105
+ return self.num_vertices > 0 and len(self.filtration_grid)>0 and len(self.filtration_grid[0]) > 0
1106
+
1107
+ @property
1108
+ def dtype(self)->type:
1109
+ return np.int32
1110
+ def filtration_bounds(self, degrees:Iterable[int]|None=None, q:float|tuple=0, split_dimension:bool=False)->np.ndarray:
1111
+ """
1112
+ Returns the filtrations bounds of the finite filtration values.
1113
+ """
1114
+ try:
1115
+ a,b =q
1116
+ except:
1117
+ a,b,=q,q
1118
+ degrees = range(self.dimension+1) if degrees is None else degrees
1119
+ filtrations_values = self._get_filtration_values(degrees, inf_to_nan=True) ## degree, parameter, pt
1120
+ boxes = np.array([np.nanquantile(filtration, [a, 1-b], axis=1) for filtration in filtrations_values],dtype=float)
1121
+ if split_dimension: return boxes
1122
+ return np.asarray([np.nanmin(boxes, axis=(0,1)), np.nanmax(boxes, axis=(0,1))]) # box, birth/death, filtration
1123
+
1124
+
1125
+
1126
+
1127
+ def fill_lowerstar(self, F, int parameter)->SimplexTreeMulti_Fi32:
1128
+ """ Fills the `dimension`th filtration by the lower-star filtration defined by F.
1129
+
1130
+ Parameters
1131
+ ----------
1132
+
1133
+ F:1d array
1134
+ The density over the vertices, that induces a lowerstar filtration.
1135
+ parameter:int
1136
+ Which filtration parameter to fill. /!\ python starts at 0.
1137
+
1138
+ Returns
1139
+ -------
1140
+
1141
+ self:SimplexTreeMulti
1142
+ """
1143
+ # for s, sf in self.get_simplices():
1144
+ # self.assign_filtration(s, [f if i != dimension else np.max(np.array(F)[s]) for i,f in enumerate(sf)])
1145
+ # cdef int c_parameter = parameter
1146
+ cdef Fi32 c_F = _py21c_i32(np.asarray(F,dtype=np.int32))
1147
+ with nogil:
1148
+ self.get_ptr().fill_lowerstar(c_F, parameter)
1149
+ return self
1150
+
1151
+
1152
+ def project_on_line(self, parameter:int=0, basepoint:None|list|np.ndarray= None, direction:None|list|np.ndarray= None)->SimplexTree:
1153
+ """Converts an multi simplextree to a gudhi simplextree.
1154
+
1155
+ Parameters
1156
+ ----------
1157
+
1158
+ parameter:int = 0
1159
+ The parameter to keep. WARNING will crash if the multi simplextree is not well filled.
1160
+ basepoint:None
1161
+ Instead of keeping a single parameter, will consider the filtration defined by the diagonal line crossing the basepoint.
1162
+
1163
+ WARNING
1164
+ -------
1165
+
1166
+ There are no safeguard yet, it WILL crash if asking for a parameter that is not filled.
1167
+
1168
+ Returns
1169
+ -------
1170
+
1171
+ A SimplexTree with chosen 1D filtration.
1172
+ """
1173
+ # FIXME : deal with multicritical filtrations
1174
+ import gudhi as gd
1175
+ new_simplextree = gd.SimplexTree()
1176
+ assert parameter < self.get_ptr().get_number_of_parameters()
1177
+ cdef int c_parameter = parameter
1178
+ cdef intptr_t old_ptr = self.thisptr
1179
+ cdef intptr_t new_ptr = new_simplextree.thisptr
1180
+ if basepoint is None:
1181
+ basepoint = np.array([np.inf]*self.get_ptr().get_number_of_parameters())
1182
+ basepoint[parameter] = 0
1183
+ if direction is None:
1184
+ direction = np.array([0]*self.get_ptr().get_number_of_parameters())
1185
+ direction[parameter] = 1
1186
+ cdef Finitely_critical_multi_filtration[double] c_basepoint = _py21c_f64(basepoint)
1187
+ cdef Finitely_critical_multi_filtration[double] c_direction = _py21c_f64(direction)
1188
+ cdef Line[double] c_line = Line[double](c_basepoint, c_direction)
1189
+ with nogil:
1190
+ self.get_ptr().to_std(new_ptr, c_line, c_parameter)
1191
+ return new_simplextree
1192
+
1193
+ def linear_projections(self, linear_forms:np.ndarray)->Iterable[SimplexTree]:
1194
+ """
1195
+ Compute the 1-parameter projections, w.r.t. given the linear forms, of this simplextree.
1196
+
1197
+ Input
1198
+ -----
1199
+
1200
+ Array of shape (num_linear_forms, num_parameters)
1201
+
1202
+ Output
1203
+ ------
1204
+
1205
+ List of projected (gudhi) simplextrees.
1206
+ """
1207
+ cdef Py_ssize_t num_projections = linear_forms.shape[0]
1208
+ cdef Py_ssize_t num_parameters = linear_forms.shape[1]
1209
+ if num_projections == 0: return []
1210
+ cdef vector[vector[double]] c_linear_forms = linear_forms
1211
+ assert num_parameters==self.num_parameters, f"The linear forms has to have the same number of parameter as the simplextree ({self.num_parameters})."
1212
+
1213
+ # Gudhi copies are faster than inserting simplices one by one
1214
+ import gudhi as gd
1215
+ # flattened_simplextree = gd.SimplexTree()
1216
+ # cdef intptr_t multi_prt = self.thisptr
1217
+ # cdef intptr_t flattened_ptr = flattened_simplextree.thisptr
1218
+ # with nogil:
1219
+ # # flatten_from_ptr(multi_prt, flattened_ptr, num_parameters)
1220
+ # self.get_ptr().to_std(flattened_ptr, num_parameters)
1221
+ flattened_simplextree = self.project_on_line()
1222
+ out = [flattened_simplextree] + [gd.SimplexTree(flattened_simplextree) for _ in range(num_projections-1)]
1223
+
1224
+ # Fills the 1-parameter simplextrees.
1225
+ cdef vector[intptr_t] out_ptrs = [st.thisptr for st in out]
1226
+ with nogil:
1227
+ for i in range(num_projections):
1228
+ self.get_ptr().to_std_linear_projection(out_ptrs[i], c_linear_forms[i])
1229
+ return out
1230
+
1231
+
1232
+ def set_num_parameter(self, num:int):
1233
+ """
1234
+ Sets the numbers of parameters.
1235
+ WARNING : it will resize all the filtrations to this size.
1236
+ """
1237
+ self.get_ptr().resize_all_filtrations(num)
1238
+ self.get_ptr().set_number_of_parameters(num)
1239
+ return
1240
+
1241
+ def __eq__(self, other:SimplexTreeMulti_Fi32):
1242
+ """Test for structural equality
1243
+ :returns: True if the 2 simplex trees are equal, False otherwise.
1244
+ :rtype: bool
1245
+ """
1246
+ return dereference(self.get_ptr()) == dereference(other.get_ptr())
1247
+
1248
+
1249
+
1250
+
1251
+
1252
+
1253
+ def _simplextree_multify_Fi32(simplextree:SimplexTree, int num_parameters, default_values=[])->SimplexTreeMulti_Fi32:
1254
+ """Converts a gudhi simplextree to a multi simplextree.
1255
+ Parameters
1256
+ ----------
1257
+
1258
+ parameters:int = 2
1259
+ The number of filtrations
1260
+
1261
+ Returns
1262
+ -------
1263
+
1264
+ A multi simplextree, with first filtration value being the one from the original simplextree.
1265
+ """
1266
+ if isinstance(simplextree, SimplexTreeMulti_Fi32):
1267
+ return simplextree
1268
+ st = SimplexTreeMulti_Fi32(num_parameters=num_parameters)
1269
+ cdef intptr_t old_ptr = simplextree.thisptr
1270
+ cdef Fi32 c_default_values= _py21c_i32([default_values])
1271
+ with nogil:
1272
+ st.get_ptr().from_std(old_ptr, num_parameters, c_default_values)
1273
+ return st
1274
+
1275
+ def _safe_simplextree_multify_Fi32(simplextree:SimplexTree,int num_parameters=2, cnp.ndarray default_values=np.array(-np.inf))->SimplexTreeMulti_Fi32:
1276
+ if isinstance(simplextree, SimplexTreeMulti_Fi32):
1277
+ return simplextree
1278
+ simplices = [[] for _ in range(simplextree.dimension()+1)]
1279
+ filtration_values = [[] for _ in range(simplextree.dimension()+1)]
1280
+ st_multi = SimplexTreeMulti_Fi32(num_parameters=1)
1281
+ if num_parameters > 1:
1282
+ st_multi.set_num_parameter(num_parameters)
1283
+ if default_values.squeeze().ndim == 0:
1284
+ default_values = np.zeros(num_parameters-1) + default_values
1285
+
1286
+ # TODO : Optimize with Python.h
1287
+ for s,f in simplextree.get_simplices():
1288
+ filtration_values[len(s)-1].append(np.concatenate([[f],default_values]))
1289
+ simplices[len(s)-1].append(s)
1290
+ for batch_simplices, batch_filtrations in zip(simplices,filtration_values):
1291
+ st_multi.insert_batch(np.asarray(batch_simplices, dtype=np.int32).T, np.asarray(batch_filtrations, dtype=np.int32))
1292
+ return st_multi
1293
+
1294
+
1295
+
1296
+ ctypedef KCriticalFiltration[int32_t] KFi32
1297
+
1298
+
1299
+
1300
+ # SimplexTree python interface
1301
+ cdef class SimplexTreeMulti_KFi32:
1302
+ """The simplex tree is an efficient and flexible data structure for
1303
+ representing general (filtered) simplicial complexes. The data structure
1304
+ is described in Jean-Daniel Boissonnat and Clément Maria. The Simplex
1305
+ Tree: An Efficient Data Structure for General Simplicial Complexes.
1306
+ Algorithmica, pages 1–22, 2014.
1307
+
1308
+ This class is a multi-filtered, with keys, and non contiguous vertices version
1309
+ of the simplex tree.
1310
+ """
1311
+ cdef public intptr_t thisptr
1312
+
1313
+ cdef public vector[vector[double]] filtration_grid
1314
+ cdef public bool _is_function_simplextree
1315
+ # Get the pointer casted as it should be
1316
+ cdef Simplex_tree_multi_interface[KFi32, int32_t]* get_ptr(self) noexcept nogil:
1317
+ return <Simplex_tree_multi_interface[KFi32, int32_t]*>(self.thisptr)
1318
+
1319
+ # cdef Simplex_tree_persistence_interface * pcohptr
1320
+ # Fake constructor that does nothing but documenting the constructor
1321
+ def __init__(self, other = None, num_parameters:int=2,default_values=[], safe_conversion=False):
1322
+ """SimplexTreeMulti constructor.
1323
+
1324
+ :param other: If `other` is `None` (default value), an empty `SimplexTreeMulti` is created.
1325
+ If `other` is a `SimplexTree`, the `SimplexTreeMulti` is constructed from a deep copy of `other`.
1326
+ If `other` is a `SimplexTreeMulti`, the `SimplexTreeMulti` is constructed from a deep copy of `other`.
1327
+ :type other: SimplexTree or SimplexTreeMulti (Optional)
1328
+ :param num_parameters: The number of parameter of the multi-parameter filtration.
1329
+ :type num_parameters: int
1330
+ :returns: An empty or a copy simplex tree.
1331
+ :rtype: SimplexTreeMulti
1332
+
1333
+ :raises TypeError: In case `other` is neither `None`, nor a `SimplexTree`, nor a `SimplexTreeMulti`.
1334
+ """
1335
+
1336
+ @staticmethod
1337
+ cdef int32_t T_minus_inf():
1338
+ return <int32_t>(np.iinfo(np.int32).min)
1339
+ @staticmethod
1340
+ cdef int32_t T_inf():
1341
+ return <int32_t>(np.iinfo(np.int32).max)
1342
+ @staticmethod
1343
+ def is_kcritical()->bool:
1344
+ return True
1345
+ # The real cython constructor
1346
+ def __cinit__(self, other = None, int num_parameters=2,
1347
+ default_values=np.asarray([SimplexTreeMulti_KFi32.T_minus_inf()]), # I'm not sure why `[]` does not work. Cython bug ?
1348
+ bool safe_conversion=False,
1349
+ ): #TODO doc
1350
+ cdef KFi32 c_default_values = _py2kc_i32([default_values])
1351
+ cdef intptr_t other_ptr
1352
+ if other is not None:
1353
+ if isinstance(other, SimplexTreeMulti_KFi32):
1354
+ other_ptr = other.thisptr
1355
+ self.thisptr = <intptr_t>(new Simplex_tree_multi_interface[KFi32, int32_t](dereference(<Simplex_tree_multi_interface[KFi32, int32_t]*>other_ptr))) ## prevents calling destructor of other
1356
+ num_parameters = other.num_parameters
1357
+ self.filtration_grid = other.filtration_grid
1358
+ elif isinstance(other, SimplexTree): # Constructs a SimplexTreeMulti from a SimplexTree
1359
+ self.thisptr = <intptr_t>(new Simplex_tree_multi_interface[KFi32, int32_t]())
1360
+ if safe_conversion or SAFE_CONVERSION:
1361
+ new_st_multi = _safe_simplextree_multify_KFi32(other, num_parameters = num_parameters, default_values=np.asarray(default_values))
1362
+ self.thisptr, new_st_multi.thisptr = new_st_multi.thisptr, self.thisptr
1363
+ else:
1364
+ other_ptr = other.thisptr
1365
+ with nogil:
1366
+ self.get_ptr().from_std(other_ptr, num_parameters, c_default_values)
1367
+ else:
1368
+ raise TypeError("`other` argument requires to be of type `SimplexTree`, `SimplexTreeMulti`, or `None`.")
1369
+ else:
1370
+ self.thisptr = <intptr_t>(new Simplex_tree_multi_interface[KFi32, int32_t]())
1371
+ self.get_ptr().set_number_of_parameters(num_parameters)
1372
+ self._is_function_simplextree = False
1373
+ self.filtration_grid=[[]*num_parameters]
1374
+
1375
+ def __dealloc__(self):
1376
+ cdef Simplex_tree_multi_interface[KFi32,int32_t]* ptr = self.get_ptr()
1377
+ if ptr != NULL:
1378
+ del ptr
1379
+ # TODO : is that enough ??
1380
+
1381
+
1382
+
1383
+
1384
+ def copy(self)->SimplexTreeMulti_KFi32:
1385
+ """
1386
+ :returns: A simplex tree that is a deep copy of itself.
1387
+ :rtype: SimplexTreeMulti
1388
+
1389
+ :note: The persistence information is not copied. If you need it in the clone, you have to call
1390
+ :func:`compute_persistence` on it even if you had already computed it in the original.
1391
+ """
1392
+ stree = SimplexTreeMulti_KFi32(self,num_parameters=self.num_parameters)
1393
+ return stree
1394
+
1395
+ def __deepcopy__(self):
1396
+ return self.copy()
1397
+
1398
+ def filtration(self, simplex:list|np.ndarray)->np.ndarray:
1399
+ """This function returns the filtration value for a given N-simplex in
1400
+ this simplicial complex, or +infinity if it is not in the complex.
1401
+ :param simplex: The N-simplex, represented by a list of vertex.
1402
+ :type simplex: list of int
1403
+ :returns: The simplicial complex multi-critical filtration value.
1404
+ :rtype: numpy array of shape (-1, num_parameters)
1405
+ """
1406
+ return self[simplex]
1407
+
1408
+ def assign_filtration(self, simplex:list|np.ndarray, filtration:list|np.ndarray)->SimplexTreeMulti_KFi32:
1409
+ """This function assigns a new multi-critical filtration value to a
1410
+ given N-simplex.
1411
+
1412
+ :param simplex: The N-simplex, represented by a list of vertex.
1413
+ :type simplex: list of int
1414
+ :param filtration: The new filtration(s) value(s), concatenated.
1415
+ :type filtration: list[float] or np.ndarray[float, ndim=1]
1416
+
1417
+ .. note::
1418
+ Beware that after this operation, the structure may not be a valid
1419
+ filtration anymore, a simplex could have a lower filtration value
1420
+ than one of its faces. Callers are responsible for fixing this
1421
+ (with more :meth:`assign_filtration` or
1422
+ :meth:`make_filtration_non_decreasing` for instance) before calling
1423
+ any function that relies on the filtration property, like
1424
+ :meth:`persistence`.
1425
+ """
1426
+ assert len(filtration)>0 and len(filtration) % self.get_ptr().get_number_of_parameters() == 0
1427
+ # self.get_ptr().assign_simplex_filtration(simplex, KFi32(<python_filtration_type>filtration))
1428
+ filtration = np.asarray(filtration, dtype=np.int32)[None,:]
1429
+ self.get_ptr().assign_simplex_filtration(simplex, _py2kc_i32(filtration))
1430
+ return self
1431
+
1432
+ def __getitem__(self, simplex)->np.ndarray:
1433
+ cdef vector[int] csimplex = simplex
1434
+ cdef KFi32* f_ptr = self.get_ptr().simplex_filtration(csimplex)
1435
+ return _ff2kcview_i32(f_ptr)
1436
+
1437
+
1438
+ @property
1439
+ def num_vertices(self)->int:
1440
+ """This function returns the number of vertices of the simplicial
1441
+ complex.
1442
+
1443
+ :returns: The simplicial complex number of vertices.
1444
+ :rtype: int
1445
+ """
1446
+ return self.get_ptr().num_vertices()
1447
+
1448
+ @property
1449
+ def num_simplices(self)->int:
1450
+ """This function returns the number of simplices of the simplicial
1451
+ complex.
1452
+
1453
+ :returns: the simplicial complex number of simplices.
1454
+ :rtype: int
1455
+ """
1456
+ return self.get_ptr().num_simplices()
1457
+
1458
+ @property
1459
+ def dimension(self)->int:
1460
+ """This function returns the dimension of the simplicial complex.
1461
+
1462
+ :returns: the simplicial complex dimension.
1463
+ :rtype: int
1464
+
1465
+ .. note::
1466
+
1467
+ This function is not constant time because it can recompute
1468
+ dimension if required (can be triggered by
1469
+ :func:`remove_maximal_simplex`
1470
+ or
1471
+ :func:`prune_above_filtration`
1472
+ methods).
1473
+ """
1474
+ return self.get_ptr().dimension()
1475
+ def upper_bound_dimension(self)->int:
1476
+ """This function returns a valid dimension upper bound of the
1477
+ simplicial complex.
1478
+
1479
+ :returns: an upper bound on the dimension of the simplicial complex.
1480
+ :rtype: int
1481
+ """
1482
+ return self.get_ptr().upper_bound_dimension()
1483
+
1484
+ def __iter__(self):
1485
+ for stuff in self.get_simplices():
1486
+ yield stuff
1487
+
1488
+ def set_dimension(self, int dimension)->None:
1489
+ """This function sets the dimension of the simplicial complex.
1490
+
1491
+ :param dimension: The new dimension value.
1492
+ :type dimension: int
1493
+
1494
+ .. note::
1495
+
1496
+ This function must be used with caution because it disables
1497
+ dimension recomputation when required
1498
+ (this recomputation can be triggered by
1499
+ :func:`remove_maximal_simplex`
1500
+ or
1501
+ :func:`prune_above_filtration`
1502
+ ).
1503
+ """
1504
+ self.get_ptr().set_dimension(dimension)
1505
+
1506
+ def __contains__(self, simplex):
1507
+ """This function returns if the N-simplex was found in the simplicial
1508
+ complex or not.
1509
+
1510
+ :param simplex: The N-simplex to find, represented by a list of vertex.
1511
+ :type simplex: list of int
1512
+ :returns: true if the simplex was found, false otherwise.
1513
+ :rtype: bool
1514
+ """
1515
+ if len(simplex) == 0:
1516
+ return False
1517
+ if isinstance(simplex[0], Iterable):
1518
+ s,f = simplex
1519
+ if not self.get_ptr().find_simplex(simplex):
1520
+ return False
1521
+ current_f = np.asarray(self[s])
1522
+ return np.all(np.asarray(f)>=current_f)
1523
+
1524
+ return self.get_ptr().find_simplex(simplex)
1525
+
1526
+ def insert(self, vector[int] simplex, filtration:list|np.ndarray|None=None)->bool:
1527
+ """This function inserts the given N-simplex and its subfaces with the
1528
+ given filtration value (default value is '0.0'). If some of those
1529
+ simplices are already present with a higher filtration value, their
1530
+ filtration value is lowered.
1531
+
1532
+ :param simplex: The N-simplex to insert, represented by a list of
1533
+ vertex.
1534
+ :type simplex: list of int
1535
+ :param filtration: The filtration value of the simplex.
1536
+ :type filtration: float
1537
+ :returns: true if the simplex was not yet in the complex, false
1538
+ otherwise (whatever its original filtration value).
1539
+ :rtype: bool
1540
+ """
1541
+ # TODO C++, to be compatible with insert_batch and multicritical filtrations
1542
+ num_parameters = self.get_ptr().get_number_of_parameters()
1543
+ assert filtration is None or len(filtration) % num_parameters == 0, f"Invalid number \
1544
+ of parameters. Should be {num_parameters}, got {len(filtration)}"
1545
+ if filtration is None:
1546
+ filtration = np.array([-np.inf]*num_parameters, dtype = float)
1547
+
1548
+ from itertools import chain, combinations
1549
+
1550
+ def powerset(iterable):
1551
+ s = tuple(iterable)
1552
+ return chain.from_iterable(combinations(s, r) for r in range(1,len(s)+1)) # skip the empty splx
1553
+ cdef KFi32* current_filtration_ptr
1554
+ cdef vector[vector[int]] simplices_filtration_to_insert = powerset(simplex) # TODO : optimize
1555
+
1556
+ if self.get_ptr().find_simplex(simplex):
1557
+ for i in range(simplices_filtration_to_insert.size()):
1558
+ current_filtration_ptr = self.get_ptr().simplex_filtration(simplices_filtration_to_insert[i])
1559
+ dereference(current_filtration_ptr).add_point(_py21c_i32(filtration))
1560
+ return True ## TODO : we may want to return false if this birth wasn't necessary
1561
+ cdef KCriticalFiltration[int32_t] cfiltration = _py2kc_i32(np.asarray(filtration, dtype = np.int32)[None,:])
1562
+ return self.get_ptr().insert(simplex,cfiltration)
1563
+
1564
+
1565
+
1566
+ def get_simplices(self)->Iterable[tuple[np.ndarray, np.ndarray]]:
1567
+ """This function returns a generator with simplices and their given
1568
+ filtration values.
1569
+
1570
+ :returns: The simplices.
1571
+ :rtype: generator with tuples(simplex, filtration)
1572
+ """
1573
+ cdef Simplex_tree_multi_simplices_iterator[KFi32] it = self.get_ptr().get_simplices_iterator_begin()
1574
+ cdef Simplex_tree_multi_simplices_iterator[KFi32] end = self.get_ptr().get_simplices_iterator_end()
1575
+ cdef Simplex_tree_multi_simplex_handle[KFi32] sh = dereference(it)
1576
+ cdef int num_parameters = self.get_ptr().get_number_of_parameters()
1577
+ while it != end:
1578
+ pair_sf = <pair[simplex_type, KFi32*]> self.get_ptr().get_simplex_and_filtration(dereference(it))
1579
+ yield (
1580
+ np.asarray(pair_sf.first, dtype=int),
1581
+ _ff2kcview_i32(pair_sf.second)
1582
+ )
1583
+ preincrement(it)
1584
+
1585
+
1586
+
1587
+ def persistence_approximation(self, **kwargs):
1588
+ from multipers.multiparameter_module_approximation import module_approximation
1589
+ return module_approximation(self, **kwargs)
1590
+
1591
+
1592
+ def get_skeleton(self, dimension)->Iterable[tuple[np.ndarray,np.ndarray]]:
1593
+ """This function returns a generator with the (simplices of the) skeleton of a maximum given dimension.
1594
+
1595
+ :param dimension: The skeleton dimension value.
1596
+ :type dimension: int
1597
+ :returns: The (simplices of the) skeleton of a maximum dimension.
1598
+ :rtype: generator with tuples(simplex, filtration)
1599
+ """
1600
+ cdef Simplex_tree_multi_skeleton_iterator[KFi32] it = self.get_ptr().get_skeleton_iterator_begin(dimension)
1601
+ cdef Simplex_tree_multi_skeleton_iterator[KFi32] end = self.get_ptr().get_skeleton_iterator_end(dimension)
1602
+ cdef int num_parameters = self.get_ptr().get_number_of_parameters()
1603
+ while it != end:
1604
+ # yield self.get_ptr().get_simplex_and_filtration(dereference(it))
1605
+ pair = self.get_ptr().get_simplex_and_filtration(dereference(it))
1606
+ yield (np.asarray(pair.first, dtype=int),
1607
+ _ff2kcview_i32(pair.second)
1608
+ )
1609
+ preincrement(it)
1610
+
1611
+ # def get_star(self, simplex):
1612
+ # """This function returns the star of a given N-simplex.
1613
+
1614
+ # :param simplex: The N-simplex, represented by a list of vertex.
1615
+ # :type simplex: list of int
1616
+ # :returns: The (simplices of the) star of a simplex.
1617
+ # :rtype: list of tuples(simplex, filtration)
1618
+ # """
1619
+ # cdef simplex_type csimplex = simplex
1620
+ # cdef int num_parameters = self.num_parameters
1621
+ # # for i in simplex:
1622
+ # # csimplex.push_back(i)
1623
+ # cdef vector[simplex_filtration_type] star \
1624
+ # = self.get_ptr().get_star(csimplex)
1625
+ # ct = []
1626
+
1627
+ # for filtered_simplex in star:
1628
+ # v = []
1629
+ # for vertex in filtered_simplex.first:
1630
+ # v.append(vertex)
1631
+ # ct.append((v, np.asarray(<int32_t[:num_parameters]>filtered_simplex.second)))
1632
+ # return ct
1633
+
1634
+ # def get_cofaces(self, simplex, codimension):
1635
+ # """This function returns the cofaces of a given N-simplex with a
1636
+ # given codimension.
1637
+
1638
+ # :param simplex: The N-simplex, represented by a list of vertex.
1639
+ # :type simplex: list of int
1640
+ # :param codimension: The codimension. If codimension = 0, all cofaces
1641
+ # are returned (equivalent of get_star function)
1642
+ # :type codimension: int
1643
+ # :returns: The (simplices of the) cofaces of a simplex
1644
+ # :rtype: list of tuples(simplex, filtration)
1645
+ # """
1646
+ # cdef vector[int] csimplex = simplex
1647
+ # cdef int num_parameters = self.num_parameters
1648
+ # # for i in simplex:
1649
+ # # csimplex.push_back(i)
1650
+ # cdef vector[simplex_filtration_type] cofaces \
1651
+ # = self.get_ptr().get_cofaces(csimplex, <int>codimension)
1652
+ # ct = []
1653
+ # for filtered_simplex in cofaces:
1654
+ # v = []
1655
+ # for vertex in filtered_simplex.first:
1656
+ # v.append(vertex)
1657
+ # ct.append((v, np.asarray(<int32_t[:num_parameters]>filtered_simplex.second)))
1658
+ # return ct
1659
+
1660
+ def get_boundaries(self, simplex)->Iterable[tuple[np.ndarray, np.ndarray]]:
1661
+ """This function returns a generator with the boundaries of a given N-simplex.
1662
+ If you do not need the filtration values, the boundary can also be obtained as
1663
+ :code:`itertools.combinations(simplex,len(simplex)-1)`.
1664
+
1665
+ :param simplex: The N-simplex, represented by a list of vertex.
1666
+ :type simplex: list of int.
1667
+ :returns: The (simplices of the) boundary of a simplex
1668
+ :rtype: generator with tuples(simplex, filtration)
1669
+ """
1670
+ cdef pair[Simplex_tree_multi_boundary_iterator[KFi32], Simplex_tree_multi_boundary_iterator[KFi32]] it = self.get_ptr().get_boundary_iterators(simplex)
1671
+
1672
+ cdef int num_parameters = self.get_ptr().get_number_of_parameters()
1673
+ while it.first != it.second:
1674
+ # yield self.get_ptr().get_simplex_and_filtration(dereference(it))
1675
+ pair = self.get_ptr().get_simplex_and_filtration(dereference(it.first))
1676
+ yield (np.asarray(pair.first, dtype=int),
1677
+ _ff2kcview_i32(pair.second)
1678
+ )
1679
+ preincrement(it.first)
1680
+ def remove_maximal_simplex(self, simplex)->SimplexTreeMulti_KFi32:
1681
+ """This function removes a given maximal N-simplex from the simplicial
1682
+ complex.
1683
+
1684
+ :param simplex: The N-simplex, represented by a list of vertex.
1685
+ :type simplex: list of int
1686
+
1687
+ .. note::
1688
+
1689
+ The dimension of the simplicial complex may be lower after calling
1690
+ remove_maximal_simplex than it was before. However,
1691
+ :func:`upper_bound_dimension`
1692
+ method will return the old value, which
1693
+ remains a valid upper bound. If you care, you can call
1694
+ :func:`dimension`
1695
+ to recompute the exact dimension.
1696
+ """
1697
+ self.get_ptr().remove_maximal_simplex(simplex)
1698
+ return self
1699
+
1700
+ # def prune_above_filtration(self, filtration)->bool:
1701
+ # """Prune above filtration value given as parameter.
1702
+
1703
+ # :param filtration: Maximum threshold value.
1704
+ # :type filtration: float
1705
+ # :returns: The filtration modification information.
1706
+ # :rtype: bool
1707
+
1708
+
1709
+ # .. note::
1710
+
1711
+ # Note that the dimension of the simplicial complex may be lower
1712
+ # after calling
1713
+ # :func:`prune_above_filtration`
1714
+ # than it was before. However,
1715
+ # :func:`upper_bound_dimension`
1716
+ # will return the old value, which remains a
1717
+ # valid upper bound. If you care, you can call
1718
+ # :func:`dimension`
1719
+ # method to recompute the exact dimension.
1720
+ # """
1721
+ # return self.get_ptr().prune_above_filtration(filtration)
1722
+ def prune_above_dimension(self, int dimension):
1723
+ """Remove all simplices of dimension greater than a given value.
1724
+
1725
+ :param dimension: Maximum dimension value.
1726
+ :type dimension: int
1727
+ :returns: The modification information.
1728
+ :rtype: bool
1729
+ """
1730
+ return self.get_ptr().prune_above_dimension(dimension)
1731
+
1732
+
1733
+ def reset_filtration(self, filtration, min_dim = 0)->SimplexTreeMulti_KFi32:
1734
+ """This function resets the filtration value of all the simplices of dimension at least min_dim. Resets all the
1735
+ simplex tree when `min_dim = 0`.
1736
+ `reset_filtration` may break the filtration property with `min_dim > 0`, and it is the user's responsibility to
1737
+ make it a valid filtration (using a large enough `filt_value`, or calling `make_filtration_non_decreasing`
1738
+ afterwards for instance).
1739
+
1740
+ :param filtration: New threshold value.
1741
+ :type filtration: float.
1742
+ :param min_dim: The minimal dimension. Default value is 0.
1743
+ :type min_dim: int.
1744
+ """
1745
+ cdef KCriticalFiltration[int32_t] cfiltration = _py2kc_i32(np.asarray(filtration, dtype = np.int32)[None,:])
1746
+ self.get_ptr().reset_filtration(cfiltration, min_dim)
1747
+ return self
1748
+
1749
+
1750
+ @property
1751
+ def num_parameters(self)->int:
1752
+ return self.get_ptr().get_number_of_parameters()
1753
+ def get_simplices_of_dimension(self, dim:int)->np.ndarray:
1754
+ return np.asarray(self.get_ptr().get_simplices_of_dimension(dim), dtype=int)
1755
+ def key(self, simplex:list|np.ndarray):
1756
+ return self.get_ptr().get_key(simplex)
1757
+ def set_keys_to_enumerate(self)->None:
1758
+ self.get_ptr().set_keys_to_enumerate()
1759
+ return
1760
+ def set_key(self,simplex:list|np.ndarray, key:int)->None:
1761
+ self.get_ptr().set_key(simplex, key)
1762
+ return
1763
+
1764
+
1765
+
1766
+ def _to_scc(self,filtration_dtype=np.int32, bool flattened=False):
1767
+ """
1768
+ Turns a simplextree into a (simplicial) module presentation.
1769
+ """
1770
+ cdef bool is_function_st = self._is_function_simplextree
1771
+
1772
+ cdef pair[vector[vector[int32_t]], boundary_matrix] out
1773
+ if flattened:
1774
+ # out = simplextree_to_ordered_bf(cptr)
1775
+ out = self.get_ptr().simplextree_to_ordered_bf()
1776
+ return np.asarray(out.first,dtype=filtration_dtype), tuple(out.second)
1777
+ if is_function_st:
1778
+ raise Exception("Kcritical cannot be a function simplextree ?? TODO: Fixme")
1779
+ else:
1780
+ blocks = self.get_ptr().kcritical_simplextree_to_scc()
1781
+ # reduces the space in memory
1782
+ if is_function_st:
1783
+ blocks = [(tuple(f), tuple(b)) for f,b in blocks[::-1]]
1784
+ else:
1785
+ blocks = [(tuple(f), tuple(b)) for f,b in blocks[::-1]] ## presentation is on the other order
1786
+ return blocks # +[(np.empty(0,dtype=filtration_dtype),[])] ## TODO : investigate
1787
+
1788
+ def to_scc_kcritical(self,
1789
+ path:os.PathLike|str,
1790
+ bool rivet_compatible=False,
1791
+ bool strip_comments=False,
1792
+ bool ignore_last_generators=False,
1793
+ bool overwrite=False,
1794
+ bool reverse_block=False,
1795
+ ):
1796
+ """
1797
+ TODO: function-simplextree, from squeezed
1798
+ """
1799
+ from os.path import exists
1800
+ from os import remove
1801
+ if exists(path):
1802
+ if not(overwrite):
1803
+ raise Exception(f"The file {path} already exists. Use the `overwrite` flag if you want to overwrite.")
1804
+ remove(path)
1805
+ # blocks = simplextree2scc(self)
1806
+ blocks = self._to_scc()
1807
+ from multipers.io import scc2disk
1808
+ scc2disk(blocks,
1809
+ path=path,
1810
+ num_parameters=self.num_parameters,
1811
+ reverse_block=reverse_block,
1812
+ rivet_compatible=rivet_compatible,
1813
+ ignore_last_generators=ignore_last_generators,
1814
+ strip_comments=strip_comments,
1815
+ )
1816
+
1817
+ def to_scc_function_st(self,
1818
+ path="scc_dataset.scc",
1819
+ bool rivet_compatible=False,
1820
+ bool strip_comments=False,
1821
+ bool ignore_last_generators=False,
1822
+ bool overwrite=False,
1823
+ bool reverse_block=True,
1824
+ ):
1825
+ from multipers.io import scc2disk
1826
+ from warnings import warn
1827
+ warn("This function is not tested yet.")
1828
+ from os.path import exists
1829
+ from os import remove
1830
+ if exists(path):
1831
+ if not(overwrite):
1832
+ raise Exception(f"The file {path} already exists. Use the `overwrite` flag if you want to overwrite.")
1833
+ remove(path)
1834
+ stuff = self._to_scc(self)
1835
+ if reverse_block: stuff.reverse()
1836
+ cdef int num_parameters = self.num_parameters
1837
+ with open(path, "w") as f:
1838
+ f.write("scc2020\n") if not rivet_compatible else f.write("firep\n")
1839
+ if not strip_comments and not rivet_compatible: f.write("# Number of parameters\n")
1840
+ num_parameters = self.num_parameters
1841
+ if rivet_compatible:
1842
+ assert num_parameters == 2
1843
+ f.write("Filtration 1\n")
1844
+ f.write("Filtration 2\n")
1845
+ else:
1846
+ f.write(f"{num_parameters}\n")
1847
+
1848
+ if not strip_comments: f.write("# Sizes of generating sets\n")
1849
+ for block in stuff: f.write(f"{len(block[1])} ")
1850
+ f.write("\n")
1851
+
1852
+ for i,block in enumerate(stuff):
1853
+ if (rivet_compatible or ignore_last_generators) and i == len(stuff)-1: continue
1854
+ if not strip_comments: f.write(f"# Block of dimension {len(stuff)-i}\n")
1855
+ for filtration, boundary in zip(*block):
1856
+ k = len(filtration)
1857
+ for j in range(k):
1858
+ if filtration[j] != np.inf:
1859
+ line = f"{filtration[j]} {k} ; " + " ".join([str(x) for x in boundary]) +"\n"
1860
+ f.write(line)
1861
+ def to_scc(self,**kwargs):
1862
+ """
1863
+ Returns an scc representation of the simplextree.
1864
+ """
1865
+ if self._is_function_simplextree:
1866
+ return self.to_scc_function_st(**kwargs)
1867
+ else:
1868
+ return self.to_scc_kcritical(**kwargs)
1869
+
1870
+ 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:
1871
+ """ Create a file that can be imported by rivet, representing the filtration of the simplextree.
1872
+
1873
+ Parameters
1874
+ ----------
1875
+
1876
+ path:str
1877
+ path of the file.
1878
+ degree:int
1879
+ The homological degree to ask rivet to compute.
1880
+ progress:bool = True
1881
+ Shows the progress bar.
1882
+ overwrite:bool = False
1883
+ If true, will overwrite the previous file if it already exists.
1884
+ """
1885
+ ...
1886
+ from os.path import exists
1887
+ from os import remove
1888
+ if exists(path):
1889
+ if not(overwrite):
1890
+ print(f"The file {path} already exists. Use the `overwrite` flag if you want to overwrite.")
1891
+ return
1892
+ remove(path)
1893
+ file = open(path, "a")
1894
+ file.write("# This file was generated by multipers.\n")
1895
+ file.write("--datatype bifiltration\n")
1896
+ file.write(f"--homology {degree}\n") if degree is not None else None
1897
+ file.write(f"-x {xbins}\n") if xbins is not None else None
1898
+ file.write(f"-y {ybins}\n") if ybins is not None else None
1899
+ file.write("--xlabel time of appearance\n")
1900
+ file.write("--ylabel density\n\n")
1901
+ from tqdm import tqdm
1902
+ with tqdm(total=self.num_simplices, position=0, disable = not(progress), desc="Writing simplex to file") as bar:
1903
+ for dim in range(0,self.dimension+1): # Not sure if dimension sort is necessary for rivet. Check ?
1904
+ file.write(f"# block of dimension {dim}\n")
1905
+ for s,F in self.get_skeleton(dim):
1906
+ if len(s) != dim+1: continue
1907
+ for i in s:
1908
+ file.write(str(i) + " ")
1909
+ file.write("; ")
1910
+ for f in F:
1911
+ file.write(str(f) + " ")
1912
+ file.write("\n")
1913
+ bar.update(1)
1914
+ file.close()
1915
+ return
1916
+
1917
+
1918
+
1919
+ def _get_filtration_values(self, vector[int] degrees, bool inf_to_nan:bool=False, bool return_raw = False)->Iterable[np.ndarray]:
1920
+ # cdef vector[int] c_degrees = degrees
1921
+ # out = get_filtration_values_from_ptr[KFi32](ptr, degrees)
1922
+ cdef intptr_t ptr = self.thisptr
1923
+ cdef vector[vector[vector[int32_t]]] out
1924
+ with nogil:
1925
+ out = self.get_ptr().get_filtration_values(degrees)
1926
+ filtrations_values = [np.asarray(filtration) for filtration in out]
1927
+ # Removes infs
1928
+ if inf_to_nan:
1929
+ for i,f in enumerate(filtrations_values):
1930
+ filtrations_values[i][f == np.inf] = np.nan
1931
+ filtrations_values[i][f == - np.inf] = np.nan
1932
+ return filtrations_values
1933
+
1934
+
1935
+ 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]:
1936
+ """
1937
+ Returns a grid over the n-filtration, from the simplextree. Usefull for grid_squeeze. TODO : multicritical
1938
+
1939
+ Parameters
1940
+ ----------
1941
+
1942
+ resolution: list[int]
1943
+ resolution of the grid, for each parameter
1944
+ box=None : pair[list[float]]
1945
+ Grid bounds. format : [low bound, high bound]
1946
+ If None is given, will use the filtration bounds of the simplextree.
1947
+ grid_strategy="regular" : string
1948
+ Either "regular", "quantile", or "exact".
1949
+ Returns
1950
+ -------
1951
+
1952
+ List of filtration values, for each parameter, defining the grid.
1953
+ """
1954
+ if degrees is None:
1955
+ degrees = range(self.dimension+1)
1956
+
1957
+
1958
+ ## preprocesses the filtration values:
1959
+ filtrations_values = np.concatenate(self._get_filtration_values(degrees, inf_to_nan=True), axis=1)
1960
+ # removes duplicate + sort (nan at the end)
1961
+ filtrations_values = [np.unique(filtration) for filtration in filtrations_values]
1962
+ # removes nan
1963
+ filtrations_values = [filtration[:-1] if np.isnan(filtration[-1]) else filtration for filtration in filtrations_values]
1964
+
1965
+ return mpg.compute_grid(filtrations_values, resolution=resolution,strategy=grid_strategy,drop_quantiles=drop_quantiles)
1966
+
1967
+
1968
+
1969
+ 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):
1970
+ """
1971
+ Fit the filtration of the simplextree to a grid.
1972
+
1973
+ :param filtration_grid: The grid on which to squeeze. An example of grid can be given by the `get_filtration_grid` method.
1974
+ :type filtration_grid: list[list[float]]
1975
+ :param coordinate_values: If true, the filtrations values of the simplices will be set to the coordinate of the filtration grid.
1976
+ :type coordinate_values: bool
1977
+ """
1978
+ if not force and self._is_squeezed:
1979
+ raise Exception("SimplexTree already squeezed, use `force=True` if that's really what you want to do.")
1980
+ #TODO : multi-critical
1981
+ if filtration_grid is None:
1982
+ filtration_grid = self.get_filtration_grid(grid_strategy=grid_strategy, **filtration_grid_kwargs)
1983
+ cdef vector[vector[double]] c_filtration_grid = filtration_grid
1984
+ 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()}"
1985
+ cdef intptr_t ptr = self.thisptr
1986
+ if coordinate_values and inplace:
1987
+ self.filtration_grid = c_filtration_grid
1988
+ if inplace or not coordinate_values:
1989
+ self.get_ptr().squeeze_filtration_inplace(c_filtration_grid, coordinate_values)
1990
+ else:
1991
+ out = SimplexTreeMulti_KFi32(num_parameters=self.num_parameters)
1992
+ self.get_ptr().squeeze_filtration(out.thisptr, c_filtration_grid)
1993
+ out.filtration_grid = c_filtration_grid
1994
+ return out
1995
+ return self
1996
+
1997
+ @property
1998
+ def _is_squeezed(self)->bool:
1999
+ return self.num_vertices > 0 and len(self.filtration_grid)>0 and len(self.filtration_grid[0]) > 0
2000
+
2001
+ @property
2002
+ def dtype(self)->type:
2003
+ return np.int32
2004
+ def filtration_bounds(self, degrees:Iterable[int]|None=None, q:float|tuple=0, split_dimension:bool=False)->np.ndarray:
2005
+ """
2006
+ Returns the filtrations bounds of the finite filtration values.
2007
+ """
2008
+ try:
2009
+ a,b =q
2010
+ except:
2011
+ a,b,=q,q
2012
+ degrees = range(self.dimension+1) if degrees is None else degrees
2013
+ filtrations_values = self._get_filtration_values(degrees, inf_to_nan=True) ## degree, parameter, pt
2014
+ boxes = np.array([np.nanquantile(filtration, [a, 1-b], axis=1) for filtration in filtrations_values],dtype=float)
2015
+ if split_dimension: return boxes
2016
+ return np.asarray([np.nanmin(boxes, axis=(0,1)), np.nanmax(boxes, axis=(0,1))]) # box, birth/death, filtration
2017
+
2018
+
2019
+
2020
+
2021
+ def fill_lowerstar(self, F, int parameter)->SimplexTreeMulti_KFi32:
2022
+ """ Fills the `dimension`th filtration by the lower-star filtration defined by F.
2023
+
2024
+ Parameters
2025
+ ----------
2026
+
2027
+ F:1d array
2028
+ The density over the vertices, that induces a lowerstar filtration.
2029
+ parameter:int
2030
+ Which filtration parameter to fill. /!\ python starts at 0.
2031
+
2032
+ Returns
2033
+ -------
2034
+
2035
+ self:SimplexTreeMulti
2036
+ """
2037
+ # for s, sf in self.get_simplices():
2038
+ # self.assign_filtration(s, [f if i != dimension else np.max(np.array(F)[s]) for i,f in enumerate(sf)])
2039
+ # cdef int c_parameter = parameter
2040
+ cdef KFi32 c_F = _py2kc_i32(np.asarray(F,dtype=np.int32)[None,:])
2041
+ with nogil:
2042
+ self.get_ptr().fill_lowerstar(c_F, parameter)
2043
+ return self
2044
+
2045
+
2046
+ def project_on_line(self, parameter:int=0, basepoint:None|list|np.ndarray= None, direction:None|list|np.ndarray= None)->SimplexTree:
2047
+ """Converts an multi simplextree to a gudhi simplextree.
2048
+
2049
+ Parameters
2050
+ ----------
2051
+
2052
+ parameter:int = 0
2053
+ The parameter to keep. WARNING will crash if the multi simplextree is not well filled.
2054
+ basepoint:None
2055
+ Instead of keeping a single parameter, will consider the filtration defined by the diagonal line crossing the basepoint.
2056
+
2057
+ WARNING
2058
+ -------
2059
+
2060
+ There are no safeguard yet, it WILL crash if asking for a parameter that is not filled.
2061
+
2062
+ Returns
2063
+ -------
2064
+
2065
+ A SimplexTree with chosen 1D filtration.
2066
+ """
2067
+ # FIXME : deal with multicritical filtrations
2068
+ import gudhi as gd
2069
+ new_simplextree = gd.SimplexTree()
2070
+ assert parameter < self.get_ptr().get_number_of_parameters()
2071
+ cdef int c_parameter = parameter
2072
+ cdef intptr_t old_ptr = self.thisptr
2073
+ cdef intptr_t new_ptr = new_simplextree.thisptr
2074
+ if basepoint is None:
2075
+ basepoint = np.array([np.inf]*self.get_ptr().get_number_of_parameters())
2076
+ basepoint[parameter] = 0
2077
+ if direction is None:
2078
+ direction = np.array([0]*self.get_ptr().get_number_of_parameters())
2079
+ direction[parameter] = 1
2080
+ cdef Finitely_critical_multi_filtration[double] c_basepoint = _py21c_f64(basepoint)
2081
+ cdef Finitely_critical_multi_filtration[double] c_direction = _py21c_f64(direction)
2082
+ cdef Line[double] c_line = Line[double](c_basepoint, c_direction)
2083
+ with nogil:
2084
+ self.get_ptr().to_std(new_ptr, c_line, c_parameter)
2085
+ return new_simplextree
2086
+
2087
+ def linear_projections(self, linear_forms:np.ndarray)->Iterable[SimplexTree]:
2088
+ """
2089
+ Compute the 1-parameter projections, w.r.t. given the linear forms, of this simplextree.
2090
+
2091
+ Input
2092
+ -----
2093
+
2094
+ Array of shape (num_linear_forms, num_parameters)
2095
+
2096
+ Output
2097
+ ------
2098
+
2099
+ List of projected (gudhi) simplextrees.
2100
+ """
2101
+ cdef Py_ssize_t num_projections = linear_forms.shape[0]
2102
+ cdef Py_ssize_t num_parameters = linear_forms.shape[1]
2103
+ if num_projections == 0: return []
2104
+ cdef vector[vector[double]] c_linear_forms = linear_forms
2105
+ assert num_parameters==self.num_parameters, f"The linear forms has to have the same number of parameter as the simplextree ({self.num_parameters})."
2106
+
2107
+ # Gudhi copies are faster than inserting simplices one by one
2108
+ import gudhi as gd
2109
+ # flattened_simplextree = gd.SimplexTree()
2110
+ # cdef intptr_t multi_prt = self.thisptr
2111
+ # cdef intptr_t flattened_ptr = flattened_simplextree.thisptr
2112
+ # with nogil:
2113
+ # # flatten_from_ptr(multi_prt, flattened_ptr, num_parameters)
2114
+ # self.get_ptr().to_std(flattened_ptr, num_parameters)
2115
+ flattened_simplextree = self.project_on_line()
2116
+ out = [flattened_simplextree] + [gd.SimplexTree(flattened_simplextree) for _ in range(num_projections-1)]
2117
+
2118
+ # Fills the 1-parameter simplextrees.
2119
+ cdef vector[intptr_t] out_ptrs = [st.thisptr for st in out]
2120
+ with nogil:
2121
+ for i in range(num_projections):
2122
+ self.get_ptr().to_std_linear_projection(out_ptrs[i], c_linear_forms[i])
2123
+ return out
2124
+
2125
+
2126
+ def set_num_parameter(self, num:int):
2127
+ """
2128
+ Sets the numbers of parameters.
2129
+ WARNING : it will resize all the filtrations to this size.
2130
+ """
2131
+ self.get_ptr().resize_all_filtrations(num)
2132
+ self.get_ptr().set_number_of_parameters(num)
2133
+ return
2134
+
2135
+ def __eq__(self, other:SimplexTreeMulti_KFi32):
2136
+ """Test for structural equality
2137
+ :returns: True if the 2 simplex trees are equal, False otherwise.
2138
+ :rtype: bool
2139
+ """
2140
+ return dereference(self.get_ptr()) == dereference(other.get_ptr())
2141
+
2142
+
2143
+
2144
+
2145
+
2146
+
2147
+ def _simplextree_multify_KFi32(simplextree:SimplexTree, int num_parameters, default_values=[])->SimplexTreeMulti_KFi32:
2148
+ """Converts a gudhi simplextree to a multi simplextree.
2149
+ Parameters
2150
+ ----------
2151
+
2152
+ parameters:int = 2
2153
+ The number of filtrations
2154
+
2155
+ Returns
2156
+ -------
2157
+
2158
+ A multi simplextree, with first filtration value being the one from the original simplextree.
2159
+ """
2160
+ if isinstance(simplextree, SimplexTreeMulti_KFi32):
2161
+ return simplextree
2162
+ st = SimplexTreeMulti_KFi32(num_parameters=num_parameters)
2163
+ cdef intptr_t old_ptr = simplextree.thisptr
2164
+ cdef KFi32 c_default_values= _py2kc_i32([default_values])
2165
+ with nogil:
2166
+ st.get_ptr().from_std(old_ptr, num_parameters, c_default_values)
2167
+ return st
2168
+
2169
+ def _safe_simplextree_multify_KFi32(simplextree:SimplexTree,int num_parameters=2, cnp.ndarray default_values=np.array(-np.inf))->SimplexTreeMulti_KFi32:
2170
+ if isinstance(simplextree, SimplexTreeMulti_KFi32):
2171
+ return simplextree
2172
+ simplices = [[] for _ in range(simplextree.dimension()+1)]
2173
+ filtration_values = [[] for _ in range(simplextree.dimension()+1)]
2174
+ st_multi = SimplexTreeMulti_KFi32(num_parameters=1)
2175
+ if num_parameters > 1:
2176
+ st_multi.set_num_parameter(num_parameters)
2177
+ if default_values.squeeze().ndim == 0:
2178
+ default_values = np.zeros(num_parameters-1) + default_values
2179
+
2180
+ # TODO : Optimize with Python.h
2181
+ for s,f in simplextree.get_simplices():
2182
+ filtration_values[len(s)-1].append(np.concatenate([[f],default_values]))
2183
+ simplices[len(s)-1].append(s)
2184
+ for batch_simplices, batch_filtrations in zip(simplices,filtration_values):
2185
+ st_multi.insert_batch(np.asarray(batch_simplices, dtype=np.int32).T, np.asarray(batch_filtrations, dtype=np.int32))
2186
+ return st_multi
2187
+
2188
+
2189
+
2190
+ ctypedef Finitely_critical_multi_filtration[double] Ff64
2191
+
2192
+
2193
+
2194
+ # SimplexTree python interface
2195
+ cdef class SimplexTreeMulti_Ff64:
2196
+ """The simplex tree is an efficient and flexible data structure for
2197
+ representing general (filtered) simplicial complexes. The data structure
2198
+ is described in Jean-Daniel Boissonnat and Clément Maria. The Simplex
2199
+ Tree: An Efficient Data Structure for General Simplicial Complexes.
2200
+ Algorithmica, pages 1–22, 2014.
2201
+
2202
+ This class is a multi-filtered, with keys, and non contiguous vertices version
2203
+ of the simplex tree.
2204
+ """
2205
+ cdef public intptr_t thisptr
2206
+
2207
+ cdef public vector[vector[double]] filtration_grid
2208
+ cdef public bool _is_function_simplextree
2209
+ # Get the pointer casted as it should be
2210
+ cdef Simplex_tree_multi_interface[Ff64, double]* get_ptr(self) noexcept nogil:
2211
+ return <Simplex_tree_multi_interface[Ff64, double]*>(self.thisptr)
2212
+
2213
+ # cdef Simplex_tree_persistence_interface * pcohptr
2214
+ # Fake constructor that does nothing but documenting the constructor
2215
+ def __init__(self, other = None, num_parameters:int=2,default_values=[], safe_conversion=False):
2216
+ """SimplexTreeMulti constructor.
2217
+
2218
+ :param other: If `other` is `None` (default value), an empty `SimplexTreeMulti` is created.
2219
+ If `other` is a `SimplexTree`, the `SimplexTreeMulti` is constructed from a deep copy of `other`.
2220
+ If `other` is a `SimplexTreeMulti`, the `SimplexTreeMulti` is constructed from a deep copy of `other`.
2221
+ :type other: SimplexTree or SimplexTreeMulti (Optional)
2222
+ :param num_parameters: The number of parameter of the multi-parameter filtration.
2223
+ :type num_parameters: int
2224
+ :returns: An empty or a copy simplex tree.
2225
+ :rtype: SimplexTreeMulti
2226
+
2227
+ :raises TypeError: In case `other` is neither `None`, nor a `SimplexTree`, nor a `SimplexTreeMulti`.
2228
+ """
2229
+
2230
+ @staticmethod
2231
+ cdef double T_minus_inf():
2232
+ return <double>(-np.inf)
2233
+ @staticmethod
2234
+ cdef double T_inf():
2235
+ return <double>(np.inf)
2236
+ @staticmethod
2237
+ def is_kcritical()->bool:
2238
+ return False
2239
+ # The real cython constructor
2240
+ def __cinit__(self, other = None, int num_parameters=2,
2241
+ default_values=np.asarray([SimplexTreeMulti_Ff64.T_minus_inf()]), # I'm not sure why `[]` does not work. Cython bug ?
2242
+ bool safe_conversion=False,
2243
+ ): #TODO doc
2244
+ cdef Ff64 c_default_values = _py21c_f64(default_values)
2245
+ cdef intptr_t other_ptr
2246
+ if other is not None:
2247
+ if isinstance(other, SimplexTreeMulti_Ff64):
2248
+ other_ptr = other.thisptr
2249
+ self.thisptr = <intptr_t>(new Simplex_tree_multi_interface[Ff64, double](dereference(<Simplex_tree_multi_interface[Ff64, double]*>other_ptr))) ## prevents calling destructor of other
2250
+ num_parameters = other.num_parameters
2251
+ self.filtration_grid = other.filtration_grid
2252
+ elif isinstance(other, SimplexTree): # Constructs a SimplexTreeMulti from a SimplexTree
2253
+ self.thisptr = <intptr_t>(new Simplex_tree_multi_interface[Ff64, double]())
2254
+ if safe_conversion or SAFE_CONVERSION:
2255
+ new_st_multi = _safe_simplextree_multify_Ff64(other, num_parameters = num_parameters, default_values=np.asarray(default_values))
2256
+ self.thisptr, new_st_multi.thisptr = new_st_multi.thisptr, self.thisptr
2257
+ else:
2258
+ other_ptr = other.thisptr
2259
+ with nogil:
2260
+ self.get_ptr().from_std(other_ptr, num_parameters, c_default_values)
2261
+ else:
2262
+ raise TypeError("`other` argument requires to be of type `SimplexTree`, `SimplexTreeMulti`, or `None`.")
2263
+ else:
2264
+ self.thisptr = <intptr_t>(new Simplex_tree_multi_interface[Ff64, double]())
2265
+ self.get_ptr().set_number_of_parameters(num_parameters)
2266
+ self._is_function_simplextree = False
2267
+ self.filtration_grid=[[]*num_parameters]
2268
+
2269
+ def __dealloc__(self):
2270
+ cdef Simplex_tree_multi_interface[Ff64,double]* ptr = self.get_ptr()
2271
+ if ptr != NULL:
2272
+ del ptr
2273
+ # TODO : is that enough ??
2274
+
2275
+
2276
+
2277
+
2278
+ def copy(self)->SimplexTreeMulti_Ff64:
2279
+ """
2280
+ :returns: A simplex tree that is a deep copy of itself.
2281
+ :rtype: SimplexTreeMulti
2282
+
2283
+ :note: The persistence information is not copied. If you need it in the clone, you have to call
2284
+ :func:`compute_persistence` on it even if you had already computed it in the original.
2285
+ """
2286
+ stree = SimplexTreeMulti_Ff64(self,num_parameters=self.num_parameters)
2287
+ return stree
2288
+
2289
+ def __deepcopy__(self):
2290
+ return self.copy()
2291
+
2292
+ def filtration(self, simplex:list|np.ndarray)->np.ndarray:
2293
+ """This function returns the filtration value for a given N-simplex in
2294
+ this simplicial complex, or +infinity if it is not in the complex.
2295
+ :param simplex: The N-simplex, represented by a list of vertex.
2296
+ :type simplex: list of int
2297
+ :returns: The simplicial complex multi-critical filtration value.
2298
+ :rtype: numpy array of shape (-1, num_parameters)
2299
+ """
2300
+ return self[simplex]
2301
+
2302
+ def assign_filtration(self, simplex:list|np.ndarray, filtration:list|np.ndarray)->SimplexTreeMulti_Ff64:
2303
+ """This function assigns a new multi-critical filtration value to a
2304
+ given N-simplex.
2305
+
2306
+ :param simplex: The N-simplex, represented by a list of vertex.
2307
+ :type simplex: list of int
2308
+ :param filtration: The new filtration(s) value(s), concatenated.
2309
+ :type filtration: list[float] or np.ndarray[float, ndim=1]
2310
+
2311
+ .. note::
2312
+ Beware that after this operation, the structure may not be a valid
2313
+ filtration anymore, a simplex could have a lower filtration value
2314
+ than one of its faces. Callers are responsible for fixing this
2315
+ (with more :meth:`assign_filtration` or
2316
+ :meth:`make_filtration_non_decreasing` for instance) before calling
2317
+ any function that relies on the filtration property, like
2318
+ :meth:`persistence`.
2319
+ """
2320
+ assert len(filtration)>0 and len(filtration) % self.get_ptr().get_number_of_parameters() == 0
2321
+ # self.get_ptr().assign_simplex_filtration(simplex, Ff64(<python_filtration_type>filtration))
2322
+ filtration = np.asarray(filtration, dtype=np.float64)
2323
+ self.get_ptr().assign_simplex_filtration(simplex, _py21c_f64(filtration))
2324
+ return self
2325
+
2326
+ def __getitem__(self, simplex)->np.ndarray:
2327
+ cdef vector[int] csimplex = simplex
2328
+ cdef Ff64* f_ptr = self.get_ptr().simplex_filtration(csimplex)
2329
+ return _ff21cview_f64(f_ptr)
2330
+
2331
+
2332
+ @property
2333
+ def num_vertices(self)->int:
2334
+ """This function returns the number of vertices of the simplicial
2335
+ complex.
2336
+
2337
+ :returns: The simplicial complex number of vertices.
2338
+ :rtype: int
2339
+ """
2340
+ return self.get_ptr().num_vertices()
2341
+
2342
+ @property
2343
+ def num_simplices(self)->int:
2344
+ """This function returns the number of simplices of the simplicial
2345
+ complex.
2346
+
2347
+ :returns: the simplicial complex number of simplices.
2348
+ :rtype: int
2349
+ """
2350
+ return self.get_ptr().num_simplices()
2351
+
2352
+ @property
2353
+ def dimension(self)->int:
2354
+ """This function returns the dimension of the simplicial complex.
2355
+
2356
+ :returns: the simplicial complex dimension.
2357
+ :rtype: int
2358
+
2359
+ .. note::
2360
+
2361
+ This function is not constant time because it can recompute
2362
+ dimension if required (can be triggered by
2363
+ :func:`remove_maximal_simplex`
2364
+ or
2365
+ :func:`prune_above_filtration`
2366
+ methods).
2367
+ """
2368
+ return self.get_ptr().dimension()
2369
+ def upper_bound_dimension(self)->int:
2370
+ """This function returns a valid dimension upper bound of the
2371
+ simplicial complex.
2372
+
2373
+ :returns: an upper bound on the dimension of the simplicial complex.
2374
+ :rtype: int
2375
+ """
2376
+ return self.get_ptr().upper_bound_dimension()
2377
+
2378
+ def __iter__(self):
2379
+ for stuff in self.get_simplices():
2380
+ yield stuff
2381
+
2382
+ def set_dimension(self, int dimension)->None:
2383
+ """This function sets the dimension of the simplicial complex.
2384
+
2385
+ :param dimension: The new dimension value.
2386
+ :type dimension: int
2387
+
2388
+ .. note::
2389
+
2390
+ This function must be used with caution because it disables
2391
+ dimension recomputation when required
2392
+ (this recomputation can be triggered by
2393
+ :func:`remove_maximal_simplex`
2394
+ or
2395
+ :func:`prune_above_filtration`
2396
+ ).
2397
+ """
2398
+ self.get_ptr().set_dimension(dimension)
2399
+
2400
+ def __contains__(self, simplex):
2401
+ """This function returns if the N-simplex was found in the simplicial
2402
+ complex or not.
2403
+
2404
+ :param simplex: The N-simplex to find, represented by a list of vertex.
2405
+ :type simplex: list of int
2406
+ :returns: true if the simplex was found, false otherwise.
2407
+ :rtype: bool
2408
+ """
2409
+ if len(simplex) == 0:
2410
+ return False
2411
+ if isinstance(simplex[0], Iterable):
2412
+ s,f = simplex
2413
+ if not self.get_ptr().find_simplex(simplex):
2414
+ return False
2415
+ current_f = np.asarray(self[s])
2416
+ return np.all(np.asarray(f)>=current_f)
2417
+
2418
+ return self.get_ptr().find_simplex(simplex)
2419
+
2420
+ def insert(self, vector[int] simplex, filtration:list|np.ndarray|None=None)->bool:
2421
+ """This function inserts the given N-simplex and its subfaces with the
2422
+ given filtration value (default value is '0.0'). If some of those
2423
+ simplices are already present with a higher filtration value, their
2424
+ filtration value is lowered.
2425
+
2426
+ :param simplex: The N-simplex to insert, represented by a list of
2427
+ vertex.
2428
+ :type simplex: list of int
2429
+ :param filtration: The filtration value of the simplex.
2430
+ :type filtration: float
2431
+ :returns: true if the simplex was not yet in the complex, false
2432
+ otherwise (whatever its original filtration value).
2433
+ :rtype: bool
2434
+ """
2435
+ # TODO C++, to be compatible with insert_batch and multicritical filtrations
2436
+ num_parameters = self.get_ptr().get_number_of_parameters()
2437
+ assert filtration is None or len(filtration) % num_parameters == 0, f"Invalid number \
2438
+ of parameters. Should be {num_parameters}, got {len(filtration)}"
2439
+ if filtration is None:
2440
+ filtration = np.array([-np.inf]*num_parameters, dtype = float)
2441
+
2442
+ cdef Finitely_critical_multi_filtration[double] cfiltration = _py21c_f64(np.asarray(filtration, dtype = np.float64))
2443
+ return self.get_ptr().insert(simplex,cfiltration)
2444
+
2445
+ @cython.boundscheck(False)
2446
+ @cython.wraparound(False)
2447
+ def insert_batch(self, some_int[:,:] vertex_array, some_float[:,:] filtrations)->SimplexTreeMulti_Ff64 :
2448
+ """Inserts k-simplices given by a sparse array in a format similar
2449
+ to `torch.sparse <https://pytorch.org/docs/stable/sparse.html>`_.
2450
+ The n-th simplex has vertices `vertex_array[0,n]`, ...,
2451
+ `vertex_array[k,n]` and filtration value `filtrations[n,num_parameters]`.
2452
+ /!\ Only compatible with 1-critical filtrations. If a simplex is repeated,
2453
+ only one filtration value will be taken into account.
2454
+
2455
+ :param vertex_array: the k-simplices to insert.
2456
+ :type vertex_array: numpy.array of shape (k+1,n)
2457
+ :param filtrations: the filtration values.
2458
+ :type filtrations: numpy.array of shape (n,num_parameters)
2459
+ """
2460
+ # TODO : multi-critical
2461
+ # cdef vector[int] vertices = np.unique(vertex_array)
2462
+ cdef Py_ssize_t k = vertex_array.shape[0]
2463
+ cdef Py_ssize_t n = vertex_array.shape[1]
2464
+ cdef int num_parameters = self.get_ptr().get_number_of_parameters()
2465
+ cdef bool empty_filtration = (filtrations.size == 0)
2466
+ if not empty_filtration :
2467
+ assert filtrations.shape[0] == n, f"inconsistent sizes for vertex_array and filtrations\
2468
+ Filtrations should be of shape ({n},{self.num_parameters})"
2469
+ assert filtrations.shape[1] == num_parameters, f"Inconsistent number of parameters.\
2470
+ Filtrations should be of shape ({n},{self.num_parameters})"
2471
+ cdef Py_ssize_t i
2472
+ cdef Py_ssize_t j
2473
+ cdef vector[int] v
2474
+ cdef Finitely_critical_multi_filtration[double] w
2475
+ if empty_filtration:
2476
+ w = Finitely_critical_multi_filtration[double](num_parameters) # at -inf by default
2477
+ with nogil:
2478
+ for i in range(n):
2479
+ # vertex
2480
+ for j in range(k):
2481
+ v.push_back(vertex_array[j, i])
2482
+ #filtration
2483
+ if not empty_filtration:
2484
+ for j in range(num_parameters):
2485
+ w.push_back(<double>filtrations[i,j])
2486
+ self.get_ptr().insert(v, w)
2487
+ v.clear()
2488
+ if not empty_filtration:
2489
+ w.clear()
2490
+ #repair filtration if necessary
2491
+ if empty_filtration:
2492
+ self.make_filtration_non_decreasing()
2493
+ return self
2494
+
2495
+ def lower_star_multi_filtration_update(self, nodes_filtrations):
2496
+ """
2497
+ Updates the multi filtration of the simplextree to the lower-star
2498
+ filtration defined on the vertices, by `node_filtrations`.
2499
+ """
2500
+ cdef Py_ssize_t num_vertices = nodes_filtrations.shape[0]
2501
+ cdef Py_ssize_t num_parameters = nodes_filtrations.shape[1]
2502
+ 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=})."
2503
+
2504
+ cdef Simplex_tree_multi_simplices_iterator[Ff64] it = self.get_ptr().get_simplices_iterator_begin()
2505
+ cdef Simplex_tree_multi_simplices_iterator[Ff64] end = self.get_ptr().get_simplices_iterator_end()
2506
+ cdef Py_ssize_t node_idx = 0
2507
+ cdef double[:,:] F = nodes_filtrations
2508
+ cdef double minus_inf = -np.inf
2509
+ with nogil:
2510
+ while it != end:
2511
+ pair_sf = <pair[simplex_type, Ff64*]> self.get_ptr().get_simplex_and_filtration(dereference(it))
2512
+ if pair_sf.first.size() == 1: # dimension == 0
2513
+ for i in range(num_parameters):
2514
+ dereference(pair_sf.second)[i] = F[node_idx,i]
2515
+ node_idx += 1
2516
+ # with gil:
2517
+ # print(pair_sf.first, node_idx,i, F[node_idx,i])
2518
+ else:
2519
+ for i in range(num_parameters):
2520
+ dereference(pair_sf.second)[i] = minus_inf
2521
+ preincrement(it)
2522
+ self.make_filtration_non_decreasing()
2523
+ return self
2524
+
2525
+
2526
+ def assign_all(self, filtration_values)-> SimplexTreeMulti_Ff64:
2527
+ """
2528
+ Updates the filtration values of all of the simplices, with `filtration_values`
2529
+ with order given by the simplextree iterator, e.g. self.get_simplices().
2530
+ """
2531
+ cdef Py_ssize_t num_simplices = filtration_values.shape[0]
2532
+ cdef Py_ssize_t num_parameters = filtration_values.shape[1]
2533
+
2534
+ assert num_simplices == self.num_simplices, f"Number of filtration values {filtration_values.shape[0]} is not the number of simplices {self.num_simplices}"
2535
+ assert num_parameters == self.num_parameters, f"Number of parameter do not coincide {filtration_values.shape[1]} vs {self.num_parameters}"
2536
+ cdef Simplex_tree_multi_simplices_iterator[Ff64] it = self.get_ptr().get_simplices_iterator_begin()
2537
+ cdef Simplex_tree_multi_simplices_iterator[Ff64] end = self.get_ptr().get_simplices_iterator_end()
2538
+ cdef Simplex_tree_multi_simplex_handle[Ff64] sh = dereference(it)
2539
+ cdef int counter =0
2540
+ # cdef cnp.ndarray[double,ndim=1] current_filtration
2541
+ cdef double[:,:] F = filtration_values
2542
+ with nogil:
2543
+ while it != end:
2544
+ pair_sf =<pair[simplex_type, Ff64*]> self.get_ptr().get_simplex_and_filtration(dereference(it))
2545
+
2546
+ for i in range(num_parameters):
2547
+ dereference(pair_sf.second)[i] = F[counter,i]
2548
+ # current_filtration= F[counter]
2549
+ counter += 1
2550
+ # yield SimplexTreeMulti._pair_simplex_filtration_to_python(out)
2551
+ preincrement(it)
2552
+
2553
+
2554
+
2555
+ @cython.boundscheck(False)
2556
+ @cython.wraparound(False)
2557
+ def assign_batch_filtration(self, some_int[:,:] vertex_array, some_float[:,:] filtrations, bool propagate=True)->SimplexTreeMulti_Ff64:
2558
+ """Assign k-simplices given by a sparse array in a format similar
2559
+ to `torch.sparse <https://pytorch.org/docs/stable/sparse.html>`_.
2560
+ The n-th simplex has vertices `vertex_array[0,n]`, ...,
2561
+ `vertex_array[k,n]` and filtration value `filtrations[n,num_parameters]`.
2562
+ /!\ Only compatible with 1-critical filtrations. If a simplex is repeated,
2563
+ only one filtration value will be taken into account.
2564
+
2565
+ :param vertex_array: the k-simplices to assign.
2566
+ :type vertex_array: numpy.array of shape (k+1,n)
2567
+ :param filtrations: the filtration values.
2568
+ :type filtrations: numpy.array of shape (n,num_parameters)
2569
+ """
2570
+ cdef Py_ssize_t k = vertex_array.shape[0]
2571
+ cdef Py_ssize_t n = vertex_array.shape[1]
2572
+ assert filtrations.shape[0] == n, 'inconsistent sizes for vertex_array and filtrations'
2573
+ assert filtrations.shape[1] == self.num_parameters, "wrong number of parameters"
2574
+ cdef Py_ssize_t i
2575
+ cdef Py_ssize_t j
2576
+ cdef vector[int] v
2577
+ cdef Finitely_critical_multi_filtration[double] w
2578
+ cdef int n_parameters = self.num_parameters
2579
+ with nogil:
2580
+ for i in range(n):
2581
+ for j in range(k):
2582
+ v.push_back(vertex_array[j, i])
2583
+ for j in range(n_parameters):
2584
+ w.push_back(<double>filtrations[i,j])
2585
+ self.get_ptr().assign_simplex_filtration(v, w)
2586
+ v.clear()
2587
+ w.clear()
2588
+ if propagate: self.make_filtration_non_decreasing()
2589
+ return self
2590
+
2591
+ @cython.boundscheck(False)
2592
+ @cython.wraparound(False)
2593
+ def euler_characteristic(self, dtype = np.float64):
2594
+ """This function returns a generator with simplices and their given
2595
+ filtration values.
2596
+
2597
+ :returns: The simplices.
2598
+ :rtype: generator with tuples(simplex, filtration)
2599
+ """
2600
+ cdef Simplex_tree_multi_simplices_iterator[Ff64] it = self.get_ptr().get_simplices_iterator_begin()
2601
+ cdef Simplex_tree_multi_simplices_iterator[Ff64] end = self.get_ptr().get_simplices_iterator_end()
2602
+ cdef Simplex_tree_multi_simplex_handle[Ff64] sh = dereference(it)
2603
+ cdef int num_parameters = self.get_ptr().get_number_of_parameters()
2604
+ cdef dict[tuple,int] out = {}
2605
+ cdef int dim
2606
+ while it != end:
2607
+ pair_sf = <pair[simplex_type, Ff64*]> self.get_ptr().get_simplex_and_filtration(dereference(it))
2608
+ # TODO: optimize https://stackoverflow.com/questions/16589791/most-efficient-property-to-hash-for-numpy-array
2609
+ key = tuple(_ff21cview_f64(pair_sf.second))
2610
+ dim = pair_sf.first.size() -1 % 2
2611
+ out[key] = out.get(key,0)+(-1)**dim
2612
+ num_keys = len(out)
2613
+ new_pts = np.fromiter(out.keys(), dtype=np.dtype((dtype,num_parameters)), count=num_keys)
2614
+ new_weights = np.fromiter(out.values(), dtype=np.int32, count=num_keys)
2615
+ idx = np.nonzero(new_weights)
2616
+ new_pts = new_pts[idx]
2617
+ new_weights = new_weights[idx]
2618
+ return (new_pts, new_weights)
2619
+
2620
+
2621
+ def get_simplices(self)->Iterable[tuple[np.ndarray, np.ndarray]]:
2622
+ """This function returns a generator with simplices and their given
2623
+ filtration values.
2624
+
2625
+ :returns: The simplices.
2626
+ :rtype: generator with tuples(simplex, filtration)
2627
+ """
2628
+ cdef Simplex_tree_multi_simplices_iterator[Ff64] it = self.get_ptr().get_simplices_iterator_begin()
2629
+ cdef Simplex_tree_multi_simplices_iterator[Ff64] end = self.get_ptr().get_simplices_iterator_end()
2630
+ cdef Simplex_tree_multi_simplex_handle[Ff64] sh = dereference(it)
2631
+ cdef int num_parameters = self.get_ptr().get_number_of_parameters()
2632
+ while it != end:
2633
+ pair_sf = <pair[simplex_type, Ff64*]> self.get_ptr().get_simplex_and_filtration(dereference(it))
2634
+ yield (
2635
+ np.asarray(pair_sf.first, dtype=int),
2636
+ _ff21cview_f64(pair_sf.second)
2637
+ )
2638
+ preincrement(it)
2639
+
2640
+
2641
+
2642
+ def persistence_approximation(self, **kwargs):
2643
+ from multipers.multiparameter_module_approximation import module_approximation
2644
+ return module_approximation(self, **kwargs)
2645
+
2646
+
2647
+ def get_skeleton(self, dimension)->Iterable[tuple[np.ndarray,np.ndarray]]:
2648
+ """This function returns a generator with the (simplices of the) skeleton of a maximum given dimension.
2649
+
2650
+ :param dimension: The skeleton dimension value.
2651
+ :type dimension: int
2652
+ :returns: The (simplices of the) skeleton of a maximum dimension.
2653
+ :rtype: generator with tuples(simplex, filtration)
2654
+ """
2655
+ cdef Simplex_tree_multi_skeleton_iterator[Ff64] it = self.get_ptr().get_skeleton_iterator_begin(dimension)
2656
+ cdef Simplex_tree_multi_skeleton_iterator[Ff64] end = self.get_ptr().get_skeleton_iterator_end(dimension)
2657
+ cdef int num_parameters = self.get_ptr().get_number_of_parameters()
2658
+ while it != end:
2659
+ # yield self.get_ptr().get_simplex_and_filtration(dereference(it))
2660
+ pair = self.get_ptr().get_simplex_and_filtration(dereference(it))
2661
+ yield (np.asarray(pair.first, dtype=int),
2662
+ _ff21cview_f64(pair.second)
2663
+ )
2664
+ preincrement(it)
2665
+
2666
+ # def get_star(self, simplex):
2667
+ # """This function returns the star of a given N-simplex.
2668
+
2669
+ # :param simplex: The N-simplex, represented by a list of vertex.
2670
+ # :type simplex: list of int
2671
+ # :returns: The (simplices of the) star of a simplex.
2672
+ # :rtype: list of tuples(simplex, filtration)
2673
+ # """
2674
+ # cdef simplex_type csimplex = simplex
2675
+ # cdef int num_parameters = self.num_parameters
2676
+ # # for i in simplex:
2677
+ # # csimplex.push_back(i)
2678
+ # cdef vector[simplex_filtration_type] star \
2679
+ # = self.get_ptr().get_star(csimplex)
2680
+ # ct = []
2681
+
2682
+ # for filtered_simplex in star:
2683
+ # v = []
2684
+ # for vertex in filtered_simplex.first:
2685
+ # v.append(vertex)
2686
+ # ct.append((v, np.asarray(<double[:num_parameters]>filtered_simplex.second)))
2687
+ # return ct
2688
+
2689
+ # def get_cofaces(self, simplex, codimension):
2690
+ # """This function returns the cofaces of a given N-simplex with a
2691
+ # given codimension.
2692
+
2693
+ # :param simplex: The N-simplex, represented by a list of vertex.
2694
+ # :type simplex: list of int
2695
+ # :param codimension: The codimension. If codimension = 0, all cofaces
2696
+ # are returned (equivalent of get_star function)
2697
+ # :type codimension: int
2698
+ # :returns: The (simplices of the) cofaces of a simplex
2699
+ # :rtype: list of tuples(simplex, filtration)
2700
+ # """
2701
+ # cdef vector[int] csimplex = simplex
2702
+ # cdef int num_parameters = self.num_parameters
2703
+ # # for i in simplex:
2704
+ # # csimplex.push_back(i)
2705
+ # cdef vector[simplex_filtration_type] cofaces \
2706
+ # = self.get_ptr().get_cofaces(csimplex, <int>codimension)
2707
+ # ct = []
2708
+ # for filtered_simplex in cofaces:
2709
+ # v = []
2710
+ # for vertex in filtered_simplex.first:
2711
+ # v.append(vertex)
2712
+ # ct.append((v, np.asarray(<double[:num_parameters]>filtered_simplex.second)))
2713
+ # return ct
2714
+
2715
+ def get_boundaries(self, simplex)->Iterable[tuple[np.ndarray, np.ndarray]]:
2716
+ """This function returns a generator with the boundaries of a given N-simplex.
2717
+ If you do not need the filtration values, the boundary can also be obtained as
2718
+ :code:`itertools.combinations(simplex,len(simplex)-1)`.
2719
+
2720
+ :param simplex: The N-simplex, represented by a list of vertex.
2721
+ :type simplex: list of int.
2722
+ :returns: The (simplices of the) boundary of a simplex
2723
+ :rtype: generator with tuples(simplex, filtration)
2724
+ """
2725
+ cdef pair[Simplex_tree_multi_boundary_iterator[Ff64], Simplex_tree_multi_boundary_iterator[Ff64]] it = self.get_ptr().get_boundary_iterators(simplex)
2726
+
2727
+ cdef int num_parameters = self.get_ptr().get_number_of_parameters()
2728
+ while it.first != it.second:
2729
+ # yield self.get_ptr().get_simplex_and_filtration(dereference(it))
2730
+ pair = self.get_ptr().get_simplex_and_filtration(dereference(it.first))
2731
+ yield (np.asarray(pair.first, dtype=int),
2732
+ _ff21cview_f64(pair.second)
2733
+ )
2734
+ preincrement(it.first)
2735
+ def remove_maximal_simplex(self, simplex)->SimplexTreeMulti_Ff64:
2736
+ """This function removes a given maximal N-simplex from the simplicial
2737
+ complex.
2738
+
2739
+ :param simplex: The N-simplex, represented by a list of vertex.
2740
+ :type simplex: list of int
2741
+
2742
+ .. note::
2743
+
2744
+ The dimension of the simplicial complex may be lower after calling
2745
+ remove_maximal_simplex than it was before. However,
2746
+ :func:`upper_bound_dimension`
2747
+ method will return the old value, which
2748
+ remains a valid upper bound. If you care, you can call
2749
+ :func:`dimension`
2750
+ to recompute the exact dimension.
2751
+ """
2752
+ self.get_ptr().remove_maximal_simplex(simplex)
2753
+ return self
2754
+
2755
+ # def prune_above_filtration(self, filtration)->bool:
2756
+ # """Prune above filtration value given as parameter.
2757
+
2758
+ # :param filtration: Maximum threshold value.
2759
+ # :type filtration: float
2760
+ # :returns: The filtration modification information.
2761
+ # :rtype: bool
2762
+
2763
+
2764
+ # .. note::
2765
+
2766
+ # Note that the dimension of the simplicial complex may be lower
2767
+ # after calling
2768
+ # :func:`prune_above_filtration`
2769
+ # than it was before. However,
2770
+ # :func:`upper_bound_dimension`
2771
+ # will return the old value, which remains a
2772
+ # valid upper bound. If you care, you can call
2773
+ # :func:`dimension`
2774
+ # method to recompute the exact dimension.
2775
+ # """
2776
+ # return self.get_ptr().prune_above_filtration(filtration)
2777
+ def prune_above_dimension(self, int dimension):
2778
+ """Remove all simplices of dimension greater than a given value.
2779
+
2780
+ :param dimension: Maximum dimension value.
2781
+ :type dimension: int
2782
+ :returns: The modification information.
2783
+ :rtype: bool
2784
+ """
2785
+ return self.get_ptr().prune_above_dimension(dimension)
2786
+
2787
+ def expansion(self, int max_dim)->SimplexTreeMulti_Ff64:
2788
+ """Expands the simplex tree containing only its one skeleton
2789
+ until dimension max_dim.
2790
+
2791
+ The expanded simplicial complex until dimension :math:`d`
2792
+ attached to a graph :math:`G` is the maximal simplicial complex of
2793
+ dimension at most :math:`d` admitting the graph :math:`G` as
2794
+ :math:`1`-skeleton.
2795
+ The filtration value assigned to a simplex is the maximal filtration
2796
+ value of one of its edges.
2797
+
2798
+ The simplex tree must contain no simplex of dimension bigger than
2799
+ 1 when calling the method.
2800
+
2801
+ :param max_dim: The maximal dimension.
2802
+ :type max_dim: int
2803
+ """
2804
+ with nogil:
2805
+ self.get_ptr().expansion(max_dim)
2806
+ # This is a fix for multipersistence. FIXME expansion in c++
2807
+ self.get_ptr().make_filtration_non_decreasing()
2808
+ return self
2809
+
2810
+ def make_filtration_non_decreasing(self)->bool:
2811
+ """This function ensures that each simplex has a higher filtration
2812
+ value than its faces by increasing the filtration values.
2813
+
2814
+ :returns: True if any filtration value was modified,
2815
+ False if the filtration was already non-decreasing.
2816
+ :rtype: bool
2817
+ """
2818
+ cdef bool out
2819
+ with nogil:
2820
+ out = self.get_ptr().make_filtration_non_decreasing()
2821
+ return out
2822
+
2823
+ def reset_filtration(self, filtration, min_dim = 0)->SimplexTreeMulti_Ff64:
2824
+ """This function resets the filtration value of all the simplices of dimension at least min_dim. Resets all the
2825
+ simplex tree when `min_dim = 0`.
2826
+ `reset_filtration` may break the filtration property with `min_dim > 0`, and it is the user's responsibility to
2827
+ make it a valid filtration (using a large enough `filt_value`, or calling `make_filtration_non_decreasing`
2828
+ afterwards for instance).
2829
+
2830
+ :param filtration: New threshold value.
2831
+ :type filtration: float.
2832
+ :param min_dim: The minimal dimension. Default value is 0.
2833
+ :type min_dim: int.
2834
+ """
2835
+ cdef Finitely_critical_multi_filtration[double] cfiltration = _py21c_f64(np.asarray(filtration, dtype = np.float64))
2836
+ self.get_ptr().reset_filtration(cfiltration, min_dim)
2837
+ return self
2838
+
2839
+ def pts_to_indices(self,pts:np.ndarray, simplices_dimensions:Iterable[int]) -> tuple[np.ndarray,np.ndarray]:
2840
+ """
2841
+ Returns the indices of the simplex tree with corresponding filtrations.
2842
+
2843
+ Args:
2844
+ - st: SimplexTreeMulti on which to recover the indices.
2845
+ - pts: (num_pts, num_parameters,) array of points to recover.
2846
+ simplices_dimensions: (num_parameters,) the simplices dimension to take into account for each parameter.
2847
+
2848
+ Returns:
2849
+ - A (m, num_parameters) array containing the found indices (m <= num_pts).
2850
+ - A (m, 2) array containing the non-found indices (pt_index, parameter failing).
2851
+ """
2852
+
2853
+ # TODO detect rank or not
2854
+ cdef bool is_rank_invariant = pts.shape[1] == 2*self.num_parameters
2855
+ if is_rank_invariant:
2856
+ births = pts[:,:self.num_parameters]
2857
+ deaths = pts[:,self.num_parameters:]
2858
+ births_indices,non_recovered_births = self.pts_to_indices(births, simplices_dimensions)
2859
+ deaths_indices,non_recovered_deaths = self.pts_to_indices(deaths, simplices_dimensions)
2860
+ non_recovered_pts = np.concatenate([non_recovered_births,non_recovered_deaths], axis=0)
2861
+ pts_indices = np.concatenate([births_indices,deaths_indices], axis=1)
2862
+ return pts_indices, non_recovered_pts
2863
+
2864
+ cdef vector[vector[double]] cpts = pts
2865
+ cdef vector[int] csimplices_dimensions = simplices_dimensions
2866
+ # cdef pair[indices_type,indices_type] out
2867
+ found_indices,not_found_indices = self.get_ptr().pts_to_indices(cpts,csimplices_dimensions)
2868
+ if len(found_indices) == 0:
2869
+ found_indices = np.empty(shape=(0,self.num_parameters), dtype = np.int32)
2870
+ if len(not_found_indices) == 0:
2871
+ not_found_indices = np.empty(shape=(0,2), dtype = np.int32)
2872
+ return np.asarray(found_indices), np.asarray(not_found_indices)
2873
+ ## This function is only meant for the edge collapse interface.
2874
+ def get_edge_list(self):
2875
+ """
2876
+ in the filtration-domination's format
2877
+ """
2878
+ cdef edge_list out
2879
+ with nogil:
2880
+ out = self.get_ptr().get_edge_list()
2881
+ return out
2882
+
2883
+ 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_Ff64:
2884
+ """Edge collapse for 1-critical 2-parameter clique complex (see https://arxiv.org/abs/2211.05574).
2885
+ It uses the code from the github repository https://github.com/aj-alonso/filtration_domination .
2886
+
2887
+ Parameters
2888
+ ----------
2889
+
2890
+ max_dimension:int
2891
+ Max simplicial dimension of the complex. Unless specified, keeps the same dimension.
2892
+ num:int
2893
+ The number of collapses to do.
2894
+ strong:bool
2895
+ Whether to use strong collapses or standard collapses (slower, but may remove more edges)
2896
+ full:bool
2897
+ 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.
2898
+ progress:bool
2899
+ If true, shows the progress of the number of collapses.
2900
+
2901
+ WARNING
2902
+ -------
2903
+
2904
+ - 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.
2905
+ - This is for 1 critical simplices, with 2 parameter persistence.
2906
+ Returns
2907
+ -------
2908
+
2909
+ self:SimplexTreeMulti
2910
+ A (smaller) simplex tree that has the same homology over this bifiltration.
2911
+
2912
+ """
2913
+ # TODO : find a way to do multiple edge collapses without python conversions.
2914
+ if num == 0:
2915
+ return self
2916
+ elif num == -1:
2917
+ num=100
2918
+ full=False
2919
+ elif num == -2:
2920
+ num=100
2921
+ full=True
2922
+ assert self.num_parameters == 2, "Number of parameters has to be 2 to use edge collapses ! This is a limitation of Filtration-domination"
2923
+ if self.dimension > 1 and not ignore_warning: warn("This method ignores simplices of dimension > 1 !")
2924
+
2925
+ max_dimension = self.dimension if max_dimension <=0 else max_dimension
2926
+
2927
+ # Retrieves the edge list, and send it to filration_domination
2928
+ edges = self.get_edge_list()
2929
+ from multipers.multiparameter_edge_collapse import _collapse_edge_list
2930
+ edges = _collapse_edge_list(edges, num=num, full=full, strong=strong, progress=progress)
2931
+ # Retrieves the collapsed simplicial complex
2932
+ self._reconstruct_from_edge_list(edges, swap=True, expand_dimension=max_dimension)
2933
+ return self
2934
+
2935
+ @cython.inline
2936
+ cdef _reconstruct_from_edge_list(self,edge_list edges, bool swap=True, int expand_dimension=0):
2937
+ """
2938
+ Generates a 1-dimensional copy of self, with the edges given as input. Useful for edge collapses
2939
+
2940
+ Input
2941
+ -----
2942
+
2943
+ - edges : Iterable[(int,int),(float,float)] ## This is the format of the rust library filtration-domination
2944
+ - swap : bool
2945
+ If true, will swap self and the collapsed simplextrees.
2946
+ - expand_dim : int
2947
+ expands back the simplextree to this dimension
2948
+ Ouput
2949
+ -----
2950
+
2951
+ The reduced SimplexTreeMulti having only these edges.
2952
+ """
2953
+ reduced_tree = SimplexTreeMulti_Ff64(num_parameters=self.num_parameters)
2954
+
2955
+ ## Adds vertices back, with good filtration
2956
+ if self.num_vertices > 0:
2957
+ vertices = np.fromiter((splx[0] for splx, f in self.get_skeleton(0)), dtype=np.int32)[None,:]
2958
+ vertices_filtration = np.fromiter((f for splx, f in self.get_skeleton(0)), dtype=np.dtype((np.float64,2)))
2959
+ reduced_tree.insert_batch(vertices, vertices_filtration)
2960
+
2961
+ ## Adds edges again
2962
+ if self.num_simplices - self.num_vertices > 0:
2963
+ edges_filtration = np.fromiter(((e.second.first, e.second.second) for e in edges), dtype=np.dtype((np.float64,2)))
2964
+ edges_idx = np.fromiter(((e.first.first, e.first.second) for e in edges), dtype=np.dtype((np.int32,2))).T
2965
+ reduced_tree.insert_batch(edges_idx, edges_filtration)
2966
+ if swap:
2967
+ # Swaps the simplextrees pointers
2968
+ self.thisptr, reduced_tree.thisptr = reduced_tree.thisptr, self.thisptr # Swaps self and reduced tree (self is a local variable)
2969
+ if expand_dimension > 0:
2970
+ self.expansion(expand_dimension) # Expands back the simplextree to the original dimension.
2971
+ return self if swap else reduced_tree
2972
+
2973
+ @property
2974
+ def num_parameters(self)->int:
2975
+ return self.get_ptr().get_number_of_parameters()
2976
+ def get_simplices_of_dimension(self, dim:int)->np.ndarray:
2977
+ return np.asarray(self.get_ptr().get_simplices_of_dimension(dim), dtype=int)
2978
+ def key(self, simplex:list|np.ndarray):
2979
+ return self.get_ptr().get_key(simplex)
2980
+ def set_keys_to_enumerate(self)->None:
2981
+ self.get_ptr().set_keys_to_enumerate()
2982
+ return
2983
+ def set_key(self,simplex:list|np.ndarray, key:int)->None:
2984
+ self.get_ptr().set_key(simplex, key)
2985
+ return
2986
+
2987
+
2988
+
2989
+ def _to_scc(self,filtration_dtype=np.float64, bool flattened=False):
2990
+ """
2991
+ Turns a simplextree into a (simplicial) module presentation.
2992
+ """
2993
+ cdef bool is_function_st = self._is_function_simplextree
2994
+
2995
+ cdef pair[vector[vector[double]], boundary_matrix] out
2996
+ if flattened:
2997
+ # out = simplextree_to_ordered_bf(cptr)
2998
+ out = self.get_ptr().simplextree_to_ordered_bf()
2999
+ return np.asarray(out.first,dtype=filtration_dtype), tuple(out.second)
3000
+ if is_function_st:
3001
+ blocks = self.get_ptr().function_simplextree_to_scc()
3002
+ else:
3003
+ blocks = self.get_ptr().simplextree_to_scc()
3004
+ # reduces the space in memory
3005
+ if is_function_st:
3006
+ blocks = [(tuple(f), tuple(b)) for f,b in blocks[::-1]]
3007
+ else:
3008
+ blocks = [(np.asarray(f,dtype=filtration_dtype), tuple(b)) for f,b in blocks[::-1]] ## presentation is on the other order
3009
+ return blocks # +[(np.empty(0,dtype=filtration_dtype),[])] ## TODO : investigate
3010
+
3011
+ def to_scc_kcritical(self,
3012
+ path:os.PathLike|str,
3013
+ bool rivet_compatible=False,
3014
+ bool strip_comments=False,
3015
+ bool ignore_last_generators=False,
3016
+ bool overwrite=False,
3017
+ bool reverse_block=False,
3018
+ ):
3019
+ """
3020
+ TODO: function-simplextree, from squeezed
3021
+ """
3022
+ from os.path import exists
3023
+ from os import remove
3024
+ if exists(path):
3025
+ if not(overwrite):
3026
+ raise Exception(f"The file {path} already exists. Use the `overwrite` flag if you want to overwrite.")
3027
+ remove(path)
3028
+ # blocks = simplextree2scc(self)
3029
+ blocks = self._to_scc()
3030
+ from multipers.io import scc2disk
3031
+ scc2disk(blocks,
3032
+ path=path,
3033
+ num_parameters=self.num_parameters,
3034
+ reverse_block=reverse_block,
3035
+ rivet_compatible=rivet_compatible,
3036
+ ignore_last_generators=ignore_last_generators,
3037
+ strip_comments=strip_comments,
3038
+ )
3039
+
3040
+ def to_scc_function_st(self,
3041
+ path="scc_dataset.scc",
3042
+ bool rivet_compatible=False,
3043
+ bool strip_comments=False,
3044
+ bool ignore_last_generators=False,
3045
+ bool overwrite=False,
3046
+ bool reverse_block=True,
3047
+ ):
3048
+ from multipers.io import scc2disk
3049
+ from warnings import warn
3050
+ warn("This function is not tested yet.")
3051
+ from os.path import exists
3052
+ from os import remove
3053
+ if exists(path):
3054
+ if not(overwrite):
3055
+ raise Exception(f"The file {path} already exists. Use the `overwrite` flag if you want to overwrite.")
3056
+ remove(path)
3057
+ stuff = self._to_scc(self)
3058
+ if reverse_block: stuff.reverse()
3059
+ cdef int num_parameters = self.num_parameters
3060
+ with open(path, "w") as f:
3061
+ f.write("scc2020\n") if not rivet_compatible else f.write("firep\n")
3062
+ if not strip_comments and not rivet_compatible: f.write("# Number of parameters\n")
3063
+ num_parameters = self.num_parameters
3064
+ if rivet_compatible:
3065
+ assert num_parameters == 2
3066
+ f.write("Filtration 1\n")
3067
+ f.write("Filtration 2\n")
3068
+ else:
3069
+ f.write(f"{num_parameters}\n")
3070
+
3071
+ if not strip_comments: f.write("# Sizes of generating sets\n")
3072
+ for block in stuff: f.write(f"{len(block[1])} ")
3073
+ f.write("\n")
3074
+
3075
+ for i,block in enumerate(stuff):
3076
+ if (rivet_compatible or ignore_last_generators) and i == len(stuff)-1: continue
3077
+ if not strip_comments: f.write(f"# Block of dimension {len(stuff)-i}\n")
3078
+ for filtration, boundary in zip(*block):
3079
+ k = len(filtration)
3080
+ for j in range(k):
3081
+ if filtration[j] != np.inf:
3082
+ line = f"{filtration[j]} {k} ; " + " ".join([str(x) for x in boundary]) +"\n"
3083
+ f.write(line)
3084
+ def to_scc(self,**kwargs):
3085
+ """
3086
+ Returns an scc representation of the simplextree.
3087
+ """
3088
+ if self._is_function_simplextree:
3089
+ return self.to_scc_function_st(**kwargs)
3090
+ else:
3091
+ return self.to_scc_kcritical(**kwargs)
3092
+
3093
+ 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:
3094
+ """ Create a file that can be imported by rivet, representing the filtration of the simplextree.
3095
+
3096
+ Parameters
3097
+ ----------
3098
+
3099
+ path:str
3100
+ path of the file.
3101
+ degree:int
3102
+ The homological degree to ask rivet to compute.
3103
+ progress:bool = True
3104
+ Shows the progress bar.
3105
+ overwrite:bool = False
3106
+ If true, will overwrite the previous file if it already exists.
3107
+ """
3108
+ ...
3109
+ from os.path import exists
3110
+ from os import remove
3111
+ if exists(path):
3112
+ if not(overwrite):
3113
+ print(f"The file {path} already exists. Use the `overwrite` flag if you want to overwrite.")
3114
+ return
3115
+ remove(path)
3116
+ file = open(path, "a")
3117
+ file.write("# This file was generated by multipers.\n")
3118
+ file.write("--datatype bifiltration\n")
3119
+ file.write(f"--homology {degree}\n") if degree is not None else None
3120
+ file.write(f"-x {xbins}\n") if xbins is not None else None
3121
+ file.write(f"-y {ybins}\n") if ybins is not None else None
3122
+ file.write("--xlabel time of appearance\n")
3123
+ file.write("--ylabel density\n\n")
3124
+ from tqdm import tqdm
3125
+ with tqdm(total=self.num_simplices, position=0, disable = not(progress), desc="Writing simplex to file") as bar:
3126
+ for dim in range(0,self.dimension+1): # Not sure if dimension sort is necessary for rivet. Check ?
3127
+ file.write(f"# block of dimension {dim}\n")
3128
+ for s,F in self.get_skeleton(dim):
3129
+ if len(s) != dim+1: continue
3130
+ for i in s:
3131
+ file.write(str(i) + " ")
3132
+ file.write("; ")
3133
+ for f in F:
3134
+ file.write(str(f) + " ")
3135
+ file.write("\n")
3136
+ bar.update(1)
3137
+ file.close()
3138
+ return
3139
+
3140
+
3141
+
3142
+ def _get_filtration_values(self, vector[int] degrees, bool inf_to_nan:bool=False, bool return_raw = False)->Iterable[np.ndarray]:
3143
+ # cdef vector[int] c_degrees = degrees
3144
+ # out = get_filtration_values_from_ptr[Ff64](ptr, degrees)
3145
+ cdef intptr_t ptr = self.thisptr
3146
+ cdef vector[vector[vector[double]]] out
3147
+ with nogil:
3148
+ out = self.get_ptr().get_filtration_values(degrees)
3149
+ filtrations_values = [np.asarray(filtration) for filtration in out]
3150
+ # Removes infs
3151
+ if inf_to_nan:
3152
+ for i,f in enumerate(filtrations_values):
3153
+ filtrations_values[i][f == np.inf] = np.nan
3154
+ filtrations_values[i][f == - np.inf] = np.nan
3155
+ return filtrations_values
3156
+
3157
+
3158
+ 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]:
3159
+ """
3160
+ Returns a grid over the n-filtration, from the simplextree. Usefull for grid_squeeze. TODO : multicritical
3161
+
3162
+ Parameters
3163
+ ----------
3164
+
3165
+ resolution: list[int]
3166
+ resolution of the grid, for each parameter
3167
+ box=None : pair[list[float]]
3168
+ Grid bounds. format : [low bound, high bound]
3169
+ If None is given, will use the filtration bounds of the simplextree.
3170
+ grid_strategy="regular" : string
3171
+ Either "regular", "quantile", or "exact".
3172
+ Returns
3173
+ -------
3174
+
3175
+ List of filtration values, for each parameter, defining the grid.
3176
+ """
3177
+ if degrees is None:
3178
+ degrees = range(self.dimension+1)
3179
+
3180
+
3181
+ ## preprocesses the filtration values:
3182
+ filtrations_values = np.concatenate(self._get_filtration_values(degrees, inf_to_nan=True), axis=1)
3183
+ # removes duplicate + sort (nan at the end)
3184
+ filtrations_values = [np.unique(filtration) for filtration in filtrations_values]
3185
+ # removes nan
3186
+ filtrations_values = [filtration[:-1] if np.isnan(filtration[-1]) else filtration for filtration in filtrations_values]
3187
+
3188
+ return mpg.compute_grid(filtrations_values, resolution=resolution,strategy=grid_strategy,drop_quantiles=drop_quantiles)
3189
+
3190
+
3191
+
3192
+ 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):
3193
+ """
3194
+ Fit the filtration of the simplextree to a grid.
3195
+
3196
+ :param filtration_grid: The grid on which to squeeze. An example of grid can be given by the `get_filtration_grid` method.
3197
+ :type filtration_grid: list[list[float]]
3198
+ :param coordinate_values: If true, the filtrations values of the simplices will be set to the coordinate of the filtration grid.
3199
+ :type coordinate_values: bool
3200
+ """
3201
+ if not force and self._is_squeezed:
3202
+ raise Exception("SimplexTree already squeezed, use `force=True` if that's really what you want to do.")
3203
+ #TODO : multi-critical
3204
+ if filtration_grid is None:
3205
+ filtration_grid = self.get_filtration_grid(grid_strategy=grid_strategy, **filtration_grid_kwargs)
3206
+ cdef vector[vector[double]] c_filtration_grid = filtration_grid
3207
+ 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()}"
3208
+ cdef intptr_t ptr = self.thisptr
3209
+ if coordinate_values and inplace:
3210
+ self.filtration_grid = c_filtration_grid
3211
+ if inplace or not coordinate_values:
3212
+ self.get_ptr().squeeze_filtration_inplace(c_filtration_grid, coordinate_values)
3213
+ else:
3214
+ out = SimplexTreeMulti_Fi32(num_parameters=self.num_parameters)
3215
+ self.get_ptr().squeeze_filtration(out.thisptr, c_filtration_grid)
3216
+ out.filtration_grid = c_filtration_grid
3217
+ return out
3218
+ return self
3219
+
3220
+ @property
3221
+ def _is_squeezed(self)->bool:
3222
+ return self.num_vertices > 0 and len(self.filtration_grid)>0 and len(self.filtration_grid[0]) > 0
3223
+
3224
+ @property
3225
+ def dtype(self)->type:
3226
+ return np.float64
3227
+ def filtration_bounds(self, degrees:Iterable[int]|None=None, q:float|tuple=0, split_dimension:bool=False)->np.ndarray:
3228
+ """
3229
+ Returns the filtrations bounds of the finite filtration values.
3230
+ """
3231
+ try:
3232
+ a,b =q
3233
+ except:
3234
+ a,b,=q,q
3235
+ degrees = range(self.dimension+1) if degrees is None else degrees
3236
+ filtrations_values = self._get_filtration_values(degrees, inf_to_nan=True) ## degree, parameter, pt
3237
+ boxes = np.array([np.nanquantile(filtration, [a, 1-b], axis=1) for filtration in filtrations_values],dtype=float)
3238
+ if split_dimension: return boxes
3239
+ return np.asarray([np.nanmin(boxes, axis=(0,1)), np.nanmax(boxes, axis=(0,1))]) # box, birth/death, filtration
3240
+
3241
+
3242
+
3243
+
3244
+ def fill_lowerstar(self, F, int parameter)->SimplexTreeMulti_Ff64:
3245
+ """ Fills the `dimension`th filtration by the lower-star filtration defined by F.
3246
+
3247
+ Parameters
3248
+ ----------
3249
+
3250
+ F:1d array
3251
+ The density over the vertices, that induces a lowerstar filtration.
3252
+ parameter:int
3253
+ Which filtration parameter to fill. /!\ python starts at 0.
3254
+
3255
+ Returns
3256
+ -------
3257
+
3258
+ self:SimplexTreeMulti
3259
+ """
3260
+ # for s, sf in self.get_simplices():
3261
+ # self.assign_filtration(s, [f if i != dimension else np.max(np.array(F)[s]) for i,f in enumerate(sf)])
3262
+ # cdef int c_parameter = parameter
3263
+ cdef Ff64 c_F = _py21c_f64(np.asarray(F,dtype=np.float64))
3264
+ with nogil:
3265
+ self.get_ptr().fill_lowerstar(c_F, parameter)
3266
+ return self
3267
+
3268
+
3269
+ def project_on_line(self, parameter:int=0, basepoint:None|list|np.ndarray= None, direction:None|list|np.ndarray= None)->SimplexTree:
3270
+ """Converts an multi simplextree to a gudhi simplextree.
3271
+
3272
+ Parameters
3273
+ ----------
3274
+
3275
+ parameter:int = 0
3276
+ The parameter to keep. WARNING will crash if the multi simplextree is not well filled.
3277
+ basepoint:None
3278
+ Instead of keeping a single parameter, will consider the filtration defined by the diagonal line crossing the basepoint.
3279
+
3280
+ WARNING
3281
+ -------
3282
+
3283
+ There are no safeguard yet, it WILL crash if asking for a parameter that is not filled.
3284
+
3285
+ Returns
3286
+ -------
3287
+
3288
+ A SimplexTree with chosen 1D filtration.
3289
+ """
3290
+ # FIXME : deal with multicritical filtrations
3291
+ import gudhi as gd
3292
+ new_simplextree = gd.SimplexTree()
3293
+ assert parameter < self.get_ptr().get_number_of_parameters()
3294
+ cdef int c_parameter = parameter
3295
+ cdef intptr_t old_ptr = self.thisptr
3296
+ cdef intptr_t new_ptr = new_simplextree.thisptr
3297
+ if basepoint is None:
3298
+ basepoint = np.array([np.inf]*self.get_ptr().get_number_of_parameters())
3299
+ basepoint[parameter] = 0
3300
+ if direction is None:
3301
+ direction = np.array([0]*self.get_ptr().get_number_of_parameters())
3302
+ direction[parameter] = 1
3303
+ cdef Finitely_critical_multi_filtration[double] c_basepoint = _py21c_f64(basepoint)
3304
+ cdef Finitely_critical_multi_filtration[double] c_direction = _py21c_f64(direction)
3305
+ cdef Line[double] c_line = Line[double](c_basepoint, c_direction)
3306
+ with nogil:
3307
+ self.get_ptr().to_std(new_ptr, c_line, c_parameter)
3308
+ return new_simplextree
3309
+
3310
+ def linear_projections(self, linear_forms:np.ndarray)->Iterable[SimplexTree]:
3311
+ """
3312
+ Compute the 1-parameter projections, w.r.t. given the linear forms, of this simplextree.
3313
+
3314
+ Input
3315
+ -----
3316
+
3317
+ Array of shape (num_linear_forms, num_parameters)
3318
+
3319
+ Output
3320
+ ------
3321
+
3322
+ List of projected (gudhi) simplextrees.
3323
+ """
3324
+ cdef Py_ssize_t num_projections = linear_forms.shape[0]
3325
+ cdef Py_ssize_t num_parameters = linear_forms.shape[1]
3326
+ if num_projections == 0: return []
3327
+ cdef vector[vector[double]] c_linear_forms = linear_forms
3328
+ assert num_parameters==self.num_parameters, f"The linear forms has to have the same number of parameter as the simplextree ({self.num_parameters})."
3329
+
3330
+ # Gudhi copies are faster than inserting simplices one by one
3331
+ import gudhi as gd
3332
+ # flattened_simplextree = gd.SimplexTree()
3333
+ # cdef intptr_t multi_prt = self.thisptr
3334
+ # cdef intptr_t flattened_ptr = flattened_simplextree.thisptr
3335
+ # with nogil:
3336
+ # # flatten_from_ptr(multi_prt, flattened_ptr, num_parameters)
3337
+ # self.get_ptr().to_std(flattened_ptr, num_parameters)
3338
+ flattened_simplextree = self.project_on_line()
3339
+ out = [flattened_simplextree] + [gd.SimplexTree(flattened_simplextree) for _ in range(num_projections-1)]
3340
+
3341
+ # Fills the 1-parameter simplextrees.
3342
+ cdef vector[intptr_t] out_ptrs = [st.thisptr for st in out]
3343
+ with nogil:
3344
+ for i in range(num_projections):
3345
+ self.get_ptr().to_std_linear_projection(out_ptrs[i], c_linear_forms[i])
3346
+ return out
3347
+
3348
+
3349
+ def set_num_parameter(self, num:int):
3350
+ """
3351
+ Sets the numbers of parameters.
3352
+ WARNING : it will resize all the filtrations to this size.
3353
+ """
3354
+ self.get_ptr().resize_all_filtrations(num)
3355
+ self.get_ptr().set_number_of_parameters(num)
3356
+ return
3357
+
3358
+ def __eq__(self, other:SimplexTreeMulti_Ff64):
3359
+ """Test for structural equality
3360
+ :returns: True if the 2 simplex trees are equal, False otherwise.
3361
+ :rtype: bool
3362
+ """
3363
+ return dereference(self.get_ptr()) == dereference(other.get_ptr())
3364
+
3365
+
3366
+
3367
+
3368
+
3369
+
3370
+ def _simplextree_multify_Ff64(simplextree:SimplexTree, int num_parameters, default_values=[])->SimplexTreeMulti_Ff64:
3371
+ """Converts a gudhi simplextree to a multi simplextree.
3372
+ Parameters
3373
+ ----------
3374
+
3375
+ parameters:int = 2
3376
+ The number of filtrations
3377
+
3378
+ Returns
3379
+ -------
3380
+
3381
+ A multi simplextree, with first filtration value being the one from the original simplextree.
3382
+ """
3383
+ if isinstance(simplextree, SimplexTreeMulti_Ff64):
3384
+ return simplextree
3385
+ st = SimplexTreeMulti_Ff64(num_parameters=num_parameters)
3386
+ cdef intptr_t old_ptr = simplextree.thisptr
3387
+ cdef Ff64 c_default_values= _py21c_f64([default_values])
3388
+ with nogil:
3389
+ st.get_ptr().from_std(old_ptr, num_parameters, c_default_values)
3390
+ return st
3391
+
3392
+ def _safe_simplextree_multify_Ff64(simplextree:SimplexTree,int num_parameters=2, cnp.ndarray default_values=np.array(-np.inf))->SimplexTreeMulti_Ff64:
3393
+ if isinstance(simplextree, SimplexTreeMulti_Ff64):
3394
+ return simplextree
3395
+ simplices = [[] for _ in range(simplextree.dimension()+1)]
3396
+ filtration_values = [[] for _ in range(simplextree.dimension()+1)]
3397
+ st_multi = SimplexTreeMulti_Ff64(num_parameters=1)
3398
+ if num_parameters > 1:
3399
+ st_multi.set_num_parameter(num_parameters)
3400
+ if default_values.squeeze().ndim == 0:
3401
+ default_values = np.zeros(num_parameters-1) + default_values
3402
+
3403
+ # TODO : Optimize with Python.h
3404
+ for s,f in simplextree.get_simplices():
3405
+ filtration_values[len(s)-1].append(np.concatenate([[f],default_values]))
3406
+ simplices[len(s)-1].append(s)
3407
+ for batch_simplices, batch_filtrations in zip(simplices,filtration_values):
3408
+ st_multi.insert_batch(np.asarray(batch_simplices, dtype=np.int32).T, np.asarray(batch_filtrations, dtype=np.float64))
3409
+ return st_multi
3410
+
3411
+
3412
+
3413
+ ctypedef KCriticalFiltration[double] KFf64
3414
+
3415
+
3416
+
3417
+ # SimplexTree python interface
3418
+ cdef class SimplexTreeMulti_KFf64:
3419
+ """The simplex tree is an efficient and flexible data structure for
3420
+ representing general (filtered) simplicial complexes. The data structure
3421
+ is described in Jean-Daniel Boissonnat and Clément Maria. The Simplex
3422
+ Tree: An Efficient Data Structure for General Simplicial Complexes.
3423
+ Algorithmica, pages 1–22, 2014.
3424
+
3425
+ This class is a multi-filtered, with keys, and non contiguous vertices version
3426
+ of the simplex tree.
3427
+ """
3428
+ cdef public intptr_t thisptr
3429
+
3430
+ cdef public vector[vector[double]] filtration_grid
3431
+ cdef public bool _is_function_simplextree
3432
+ # Get the pointer casted as it should be
3433
+ cdef Simplex_tree_multi_interface[KFf64, double]* get_ptr(self) noexcept nogil:
3434
+ return <Simplex_tree_multi_interface[KFf64, double]*>(self.thisptr)
3435
+
3436
+ # cdef Simplex_tree_persistence_interface * pcohptr
3437
+ # Fake constructor that does nothing but documenting the constructor
3438
+ def __init__(self, other = None, num_parameters:int=2,default_values=[], safe_conversion=False):
3439
+ """SimplexTreeMulti constructor.
3440
+
3441
+ :param other: If `other` is `None` (default value), an empty `SimplexTreeMulti` is created.
3442
+ If `other` is a `SimplexTree`, the `SimplexTreeMulti` is constructed from a deep copy of `other`.
3443
+ If `other` is a `SimplexTreeMulti`, the `SimplexTreeMulti` is constructed from a deep copy of `other`.
3444
+ :type other: SimplexTree or SimplexTreeMulti (Optional)
3445
+ :param num_parameters: The number of parameter of the multi-parameter filtration.
3446
+ :type num_parameters: int
3447
+ :returns: An empty or a copy simplex tree.
3448
+ :rtype: SimplexTreeMulti
3449
+
3450
+ :raises TypeError: In case `other` is neither `None`, nor a `SimplexTree`, nor a `SimplexTreeMulti`.
3451
+ """
3452
+
3453
+ @staticmethod
3454
+ cdef double T_minus_inf():
3455
+ return <double>(-np.inf)
3456
+ @staticmethod
3457
+ cdef double T_inf():
3458
+ return <double>(np.inf)
3459
+ @staticmethod
3460
+ def is_kcritical()->bool:
3461
+ return True
3462
+ # The real cython constructor
3463
+ def __cinit__(self, other = None, int num_parameters=2,
3464
+ default_values=np.asarray([SimplexTreeMulti_KFf64.T_minus_inf()]), # I'm not sure why `[]` does not work. Cython bug ?
3465
+ bool safe_conversion=False,
3466
+ ): #TODO doc
3467
+ cdef KFf64 c_default_values = _py2kc_f64([default_values])
3468
+ cdef intptr_t other_ptr
3469
+ if other is not None:
3470
+ if isinstance(other, SimplexTreeMulti_KFf64):
3471
+ other_ptr = other.thisptr
3472
+ self.thisptr = <intptr_t>(new Simplex_tree_multi_interface[KFf64, double](dereference(<Simplex_tree_multi_interface[KFf64, double]*>other_ptr))) ## prevents calling destructor of other
3473
+ num_parameters = other.num_parameters
3474
+ self.filtration_grid = other.filtration_grid
3475
+ elif isinstance(other, SimplexTree): # Constructs a SimplexTreeMulti from a SimplexTree
3476
+ self.thisptr = <intptr_t>(new Simplex_tree_multi_interface[KFf64, double]())
3477
+ if safe_conversion or SAFE_CONVERSION:
3478
+ new_st_multi = _safe_simplextree_multify_KFf64(other, num_parameters = num_parameters, default_values=np.asarray(default_values))
3479
+ self.thisptr, new_st_multi.thisptr = new_st_multi.thisptr, self.thisptr
3480
+ else:
3481
+ other_ptr = other.thisptr
3482
+ with nogil:
3483
+ self.get_ptr().from_std(other_ptr, num_parameters, c_default_values)
3484
+ else:
3485
+ raise TypeError("`other` argument requires to be of type `SimplexTree`, `SimplexTreeMulti`, or `None`.")
3486
+ else:
3487
+ self.thisptr = <intptr_t>(new Simplex_tree_multi_interface[KFf64, double]())
3488
+ self.get_ptr().set_number_of_parameters(num_parameters)
3489
+ self._is_function_simplextree = False
3490
+ self.filtration_grid=[[]*num_parameters]
3491
+
3492
+ def __dealloc__(self):
3493
+ cdef Simplex_tree_multi_interface[KFf64,double]* ptr = self.get_ptr()
3494
+ if ptr != NULL:
3495
+ del ptr
3496
+ # TODO : is that enough ??
3497
+
3498
+
3499
+
3500
+
3501
+ def copy(self)->SimplexTreeMulti_KFf64:
3502
+ """
3503
+ :returns: A simplex tree that is a deep copy of itself.
3504
+ :rtype: SimplexTreeMulti
3505
+
3506
+ :note: The persistence information is not copied. If you need it in the clone, you have to call
3507
+ :func:`compute_persistence` on it even if you had already computed it in the original.
3508
+ """
3509
+ stree = SimplexTreeMulti_KFf64(self,num_parameters=self.num_parameters)
3510
+ return stree
3511
+
3512
+ def __deepcopy__(self):
3513
+ return self.copy()
3514
+
3515
+ def filtration(self, simplex:list|np.ndarray)->np.ndarray:
3516
+ """This function returns the filtration value for a given N-simplex in
3517
+ this simplicial complex, or +infinity if it is not in the complex.
3518
+ :param simplex: The N-simplex, represented by a list of vertex.
3519
+ :type simplex: list of int
3520
+ :returns: The simplicial complex multi-critical filtration value.
3521
+ :rtype: numpy array of shape (-1, num_parameters)
3522
+ """
3523
+ return self[simplex]
3524
+
3525
+ def assign_filtration(self, simplex:list|np.ndarray, filtration:list|np.ndarray)->SimplexTreeMulti_KFf64:
3526
+ """This function assigns a new multi-critical filtration value to a
3527
+ given N-simplex.
3528
+
3529
+ :param simplex: The N-simplex, represented by a list of vertex.
3530
+ :type simplex: list of int
3531
+ :param filtration: The new filtration(s) value(s), concatenated.
3532
+ :type filtration: list[float] or np.ndarray[float, ndim=1]
3533
+
3534
+ .. note::
3535
+ Beware that after this operation, the structure may not be a valid
3536
+ filtration anymore, a simplex could have a lower filtration value
3537
+ than one of its faces. Callers are responsible for fixing this
3538
+ (with more :meth:`assign_filtration` or
3539
+ :meth:`make_filtration_non_decreasing` for instance) before calling
3540
+ any function that relies on the filtration property, like
3541
+ :meth:`persistence`.
3542
+ """
3543
+ assert len(filtration)>0 and len(filtration) % self.get_ptr().get_number_of_parameters() == 0
3544
+ # self.get_ptr().assign_simplex_filtration(simplex, KFf64(<python_filtration_type>filtration))
3545
+ filtration = np.asarray(filtration, dtype=np.float64)[None,:]
3546
+ self.get_ptr().assign_simplex_filtration(simplex, _py2kc_f64(filtration))
3547
+ return self
3548
+
3549
+ def __getitem__(self, simplex)->np.ndarray:
3550
+ cdef vector[int] csimplex = simplex
3551
+ cdef KFf64* f_ptr = self.get_ptr().simplex_filtration(csimplex)
3552
+ return _ff2kcview_f64(f_ptr)
3553
+
3554
+
3555
+ @property
3556
+ def num_vertices(self)->int:
3557
+ """This function returns the number of vertices of the simplicial
3558
+ complex.
3559
+
3560
+ :returns: The simplicial complex number of vertices.
3561
+ :rtype: int
3562
+ """
3563
+ return self.get_ptr().num_vertices()
3564
+
3565
+ @property
3566
+ def num_simplices(self)->int:
3567
+ """This function returns the number of simplices of the simplicial
3568
+ complex.
3569
+
3570
+ :returns: the simplicial complex number of simplices.
3571
+ :rtype: int
3572
+ """
3573
+ return self.get_ptr().num_simplices()
3574
+
3575
+ @property
3576
+ def dimension(self)->int:
3577
+ """This function returns the dimension of the simplicial complex.
3578
+
3579
+ :returns: the simplicial complex dimension.
3580
+ :rtype: int
3581
+
3582
+ .. note::
3583
+
3584
+ This function is not constant time because it can recompute
3585
+ dimension if required (can be triggered by
3586
+ :func:`remove_maximal_simplex`
3587
+ or
3588
+ :func:`prune_above_filtration`
3589
+ methods).
3590
+ """
3591
+ return self.get_ptr().dimension()
3592
+ def upper_bound_dimension(self)->int:
3593
+ """This function returns a valid dimension upper bound of the
3594
+ simplicial complex.
3595
+
3596
+ :returns: an upper bound on the dimension of the simplicial complex.
3597
+ :rtype: int
3598
+ """
3599
+ return self.get_ptr().upper_bound_dimension()
3600
+
3601
+ def __iter__(self):
3602
+ for stuff in self.get_simplices():
3603
+ yield stuff
3604
+
3605
+ def set_dimension(self, int dimension)->None:
3606
+ """This function sets the dimension of the simplicial complex.
3607
+
3608
+ :param dimension: The new dimension value.
3609
+ :type dimension: int
3610
+
3611
+ .. note::
3612
+
3613
+ This function must be used with caution because it disables
3614
+ dimension recomputation when required
3615
+ (this recomputation can be triggered by
3616
+ :func:`remove_maximal_simplex`
3617
+ or
3618
+ :func:`prune_above_filtration`
3619
+ ).
3620
+ """
3621
+ self.get_ptr().set_dimension(dimension)
3622
+
3623
+ def __contains__(self, simplex):
3624
+ """This function returns if the N-simplex was found in the simplicial
3625
+ complex or not.
3626
+
3627
+ :param simplex: The N-simplex to find, represented by a list of vertex.
3628
+ :type simplex: list of int
3629
+ :returns: true if the simplex was found, false otherwise.
3630
+ :rtype: bool
3631
+ """
3632
+ if len(simplex) == 0:
3633
+ return False
3634
+ if isinstance(simplex[0], Iterable):
3635
+ s,f = simplex
3636
+ if not self.get_ptr().find_simplex(simplex):
3637
+ return False
3638
+ current_f = np.asarray(self[s])
3639
+ return np.all(np.asarray(f)>=current_f)
3640
+
3641
+ return self.get_ptr().find_simplex(simplex)
3642
+
3643
+ def insert(self, vector[int] simplex, filtration:list|np.ndarray|None=None)->bool:
3644
+ """This function inserts the given N-simplex and its subfaces with the
3645
+ given filtration value (default value is '0.0'). If some of those
3646
+ simplices are already present with a higher filtration value, their
3647
+ filtration value is lowered.
3648
+
3649
+ :param simplex: The N-simplex to insert, represented by a list of
3650
+ vertex.
3651
+ :type simplex: list of int
3652
+ :param filtration: The filtration value of the simplex.
3653
+ :type filtration: float
3654
+ :returns: true if the simplex was not yet in the complex, false
3655
+ otherwise (whatever its original filtration value).
3656
+ :rtype: bool
3657
+ """
3658
+ # TODO C++, to be compatible with insert_batch and multicritical filtrations
3659
+ num_parameters = self.get_ptr().get_number_of_parameters()
3660
+ assert filtration is None or len(filtration) % num_parameters == 0, f"Invalid number \
3661
+ of parameters. Should be {num_parameters}, got {len(filtration)}"
3662
+ if filtration is None:
3663
+ filtration = np.array([-np.inf]*num_parameters, dtype = float)
3664
+
3665
+ from itertools import chain, combinations
3666
+
3667
+ def powerset(iterable):
3668
+ s = tuple(iterable)
3669
+ return chain.from_iterable(combinations(s, r) for r in range(1,len(s)+1)) # skip the empty splx
3670
+ cdef KFf64* current_filtration_ptr
3671
+ cdef vector[vector[int]] simplices_filtration_to_insert = powerset(simplex) # TODO : optimize
3672
+
3673
+ if self.get_ptr().find_simplex(simplex):
3674
+ for i in range(simplices_filtration_to_insert.size()):
3675
+ current_filtration_ptr = self.get_ptr().simplex_filtration(simplices_filtration_to_insert[i])
3676
+ dereference(current_filtration_ptr).add_point(_py21c_f64(filtration))
3677
+ return True ## TODO : we may want to return false if this birth wasn't necessary
3678
+ cdef KCriticalFiltration[double] cfiltration = _py2kc_f64(np.asarray(filtration, dtype = np.float64)[None,:])
3679
+ return self.get_ptr().insert(simplex,cfiltration)
3680
+
3681
+
3682
+
3683
+ def get_simplices(self)->Iterable[tuple[np.ndarray, np.ndarray]]:
3684
+ """This function returns a generator with simplices and their given
3685
+ filtration values.
3686
+
3687
+ :returns: The simplices.
3688
+ :rtype: generator with tuples(simplex, filtration)
3689
+ """
3690
+ cdef Simplex_tree_multi_simplices_iterator[KFf64] it = self.get_ptr().get_simplices_iterator_begin()
3691
+ cdef Simplex_tree_multi_simplices_iterator[KFf64] end = self.get_ptr().get_simplices_iterator_end()
3692
+ cdef Simplex_tree_multi_simplex_handle[KFf64] sh = dereference(it)
3693
+ cdef int num_parameters = self.get_ptr().get_number_of_parameters()
3694
+ while it != end:
3695
+ pair_sf = <pair[simplex_type, KFf64*]> self.get_ptr().get_simplex_and_filtration(dereference(it))
3696
+ yield (
3697
+ np.asarray(pair_sf.first, dtype=int),
3698
+ _ff2kcview_f64(pair_sf.second)
3699
+ )
3700
+ preincrement(it)
3701
+
3702
+
3703
+
3704
+ def persistence_approximation(self, **kwargs):
3705
+ from multipers.multiparameter_module_approximation import module_approximation
3706
+ return module_approximation(self, **kwargs)
3707
+
3708
+
3709
+ def get_skeleton(self, dimension)->Iterable[tuple[np.ndarray,np.ndarray]]:
3710
+ """This function returns a generator with the (simplices of the) skeleton of a maximum given dimension.
3711
+
3712
+ :param dimension: The skeleton dimension value.
3713
+ :type dimension: int
3714
+ :returns: The (simplices of the) skeleton of a maximum dimension.
3715
+ :rtype: generator with tuples(simplex, filtration)
3716
+ """
3717
+ cdef Simplex_tree_multi_skeleton_iterator[KFf64] it = self.get_ptr().get_skeleton_iterator_begin(dimension)
3718
+ cdef Simplex_tree_multi_skeleton_iterator[KFf64] end = self.get_ptr().get_skeleton_iterator_end(dimension)
3719
+ cdef int num_parameters = self.get_ptr().get_number_of_parameters()
3720
+ while it != end:
3721
+ # yield self.get_ptr().get_simplex_and_filtration(dereference(it))
3722
+ pair = self.get_ptr().get_simplex_and_filtration(dereference(it))
3723
+ yield (np.asarray(pair.first, dtype=int),
3724
+ _ff2kcview_f64(pair.second)
3725
+ )
3726
+ preincrement(it)
3727
+
3728
+ # def get_star(self, simplex):
3729
+ # """This function returns the star of a given N-simplex.
3730
+
3731
+ # :param simplex: The N-simplex, represented by a list of vertex.
3732
+ # :type simplex: list of int
3733
+ # :returns: The (simplices of the) star of a simplex.
3734
+ # :rtype: list of tuples(simplex, filtration)
3735
+ # """
3736
+ # cdef simplex_type csimplex = simplex
3737
+ # cdef int num_parameters = self.num_parameters
3738
+ # # for i in simplex:
3739
+ # # csimplex.push_back(i)
3740
+ # cdef vector[simplex_filtration_type] star \
3741
+ # = self.get_ptr().get_star(csimplex)
3742
+ # ct = []
3743
+
3744
+ # for filtered_simplex in star:
3745
+ # v = []
3746
+ # for vertex in filtered_simplex.first:
3747
+ # v.append(vertex)
3748
+ # ct.append((v, np.asarray(<double[:num_parameters]>filtered_simplex.second)))
3749
+ # return ct
3750
+
3751
+ # def get_cofaces(self, simplex, codimension):
3752
+ # """This function returns the cofaces of a given N-simplex with a
3753
+ # given codimension.
3754
+
3755
+ # :param simplex: The N-simplex, represented by a list of vertex.
3756
+ # :type simplex: list of int
3757
+ # :param codimension: The codimension. If codimension = 0, all cofaces
3758
+ # are returned (equivalent of get_star function)
3759
+ # :type codimension: int
3760
+ # :returns: The (simplices of the) cofaces of a simplex
3761
+ # :rtype: list of tuples(simplex, filtration)
3762
+ # """
3763
+ # cdef vector[int] csimplex = simplex
3764
+ # cdef int num_parameters = self.num_parameters
3765
+ # # for i in simplex:
3766
+ # # csimplex.push_back(i)
3767
+ # cdef vector[simplex_filtration_type] cofaces \
3768
+ # = self.get_ptr().get_cofaces(csimplex, <int>codimension)
3769
+ # ct = []
3770
+ # for filtered_simplex in cofaces:
3771
+ # v = []
3772
+ # for vertex in filtered_simplex.first:
3773
+ # v.append(vertex)
3774
+ # ct.append((v, np.asarray(<double[:num_parameters]>filtered_simplex.second)))
3775
+ # return ct
3776
+
3777
+ def get_boundaries(self, simplex)->Iterable[tuple[np.ndarray, np.ndarray]]:
3778
+ """This function returns a generator with the boundaries of a given N-simplex.
3779
+ If you do not need the filtration values, the boundary can also be obtained as
3780
+ :code:`itertools.combinations(simplex,len(simplex)-1)`.
3781
+
3782
+ :param simplex: The N-simplex, represented by a list of vertex.
3783
+ :type simplex: list of int.
3784
+ :returns: The (simplices of the) boundary of a simplex
3785
+ :rtype: generator with tuples(simplex, filtration)
3786
+ """
3787
+ cdef pair[Simplex_tree_multi_boundary_iterator[KFf64], Simplex_tree_multi_boundary_iterator[KFf64]] it = self.get_ptr().get_boundary_iterators(simplex)
3788
+
3789
+ cdef int num_parameters = self.get_ptr().get_number_of_parameters()
3790
+ while it.first != it.second:
3791
+ # yield self.get_ptr().get_simplex_and_filtration(dereference(it))
3792
+ pair = self.get_ptr().get_simplex_and_filtration(dereference(it.first))
3793
+ yield (np.asarray(pair.first, dtype=int),
3794
+ _ff2kcview_f64(pair.second)
3795
+ )
3796
+ preincrement(it.first)
3797
+ def remove_maximal_simplex(self, simplex)->SimplexTreeMulti_KFf64:
3798
+ """This function removes a given maximal N-simplex from the simplicial
3799
+ complex.
3800
+
3801
+ :param simplex: The N-simplex, represented by a list of vertex.
3802
+ :type simplex: list of int
3803
+
3804
+ .. note::
3805
+
3806
+ The dimension of the simplicial complex may be lower after calling
3807
+ remove_maximal_simplex than it was before. However,
3808
+ :func:`upper_bound_dimension`
3809
+ method will return the old value, which
3810
+ remains a valid upper bound. If you care, you can call
3811
+ :func:`dimension`
3812
+ to recompute the exact dimension.
3813
+ """
3814
+ self.get_ptr().remove_maximal_simplex(simplex)
3815
+ return self
3816
+
3817
+ # def prune_above_filtration(self, filtration)->bool:
3818
+ # """Prune above filtration value given as parameter.
3819
+
3820
+ # :param filtration: Maximum threshold value.
3821
+ # :type filtration: float
3822
+ # :returns: The filtration modification information.
3823
+ # :rtype: bool
3824
+
3825
+
3826
+ # .. note::
3827
+
3828
+ # Note that the dimension of the simplicial complex may be lower
3829
+ # after calling
3830
+ # :func:`prune_above_filtration`
3831
+ # than it was before. However,
3832
+ # :func:`upper_bound_dimension`
3833
+ # will return the old value, which remains a
3834
+ # valid upper bound. If you care, you can call
3835
+ # :func:`dimension`
3836
+ # method to recompute the exact dimension.
3837
+ # """
3838
+ # return self.get_ptr().prune_above_filtration(filtration)
3839
+ def prune_above_dimension(self, int dimension):
3840
+ """Remove all simplices of dimension greater than a given value.
3841
+
3842
+ :param dimension: Maximum dimension value.
3843
+ :type dimension: int
3844
+ :returns: The modification information.
3845
+ :rtype: bool
3846
+ """
3847
+ return self.get_ptr().prune_above_dimension(dimension)
3848
+
3849
+
3850
+ def reset_filtration(self, filtration, min_dim = 0)->SimplexTreeMulti_KFf64:
3851
+ """This function resets the filtration value of all the simplices of dimension at least min_dim. Resets all the
3852
+ simplex tree when `min_dim = 0`.
3853
+ `reset_filtration` may break the filtration property with `min_dim > 0`, and it is the user's responsibility to
3854
+ make it a valid filtration (using a large enough `filt_value`, or calling `make_filtration_non_decreasing`
3855
+ afterwards for instance).
3856
+
3857
+ :param filtration: New threshold value.
3858
+ :type filtration: float.
3859
+ :param min_dim: The minimal dimension. Default value is 0.
3860
+ :type min_dim: int.
3861
+ """
3862
+ cdef KCriticalFiltration[double] cfiltration = _py2kc_f64(np.asarray(filtration, dtype = np.float64)[None,:])
3863
+ self.get_ptr().reset_filtration(cfiltration, min_dim)
3864
+ return self
3865
+
3866
+
3867
+ @property
3868
+ def num_parameters(self)->int:
3869
+ return self.get_ptr().get_number_of_parameters()
3870
+ def get_simplices_of_dimension(self, dim:int)->np.ndarray:
3871
+ return np.asarray(self.get_ptr().get_simplices_of_dimension(dim), dtype=int)
3872
+ def key(self, simplex:list|np.ndarray):
3873
+ return self.get_ptr().get_key(simplex)
3874
+ def set_keys_to_enumerate(self)->None:
3875
+ self.get_ptr().set_keys_to_enumerate()
3876
+ return
3877
+ def set_key(self,simplex:list|np.ndarray, key:int)->None:
3878
+ self.get_ptr().set_key(simplex, key)
3879
+ return
3880
+
3881
+
3882
+
3883
+ def _to_scc(self,filtration_dtype=np.float64, bool flattened=False):
3884
+ """
3885
+ Turns a simplextree into a (simplicial) module presentation.
3886
+ """
3887
+ cdef bool is_function_st = self._is_function_simplextree
3888
+
3889
+ cdef pair[vector[vector[double]], boundary_matrix] out
3890
+ if flattened:
3891
+ # out = simplextree_to_ordered_bf(cptr)
3892
+ out = self.get_ptr().simplextree_to_ordered_bf()
3893
+ return np.asarray(out.first,dtype=filtration_dtype), tuple(out.second)
3894
+ if is_function_st:
3895
+ raise Exception("Kcritical cannot be a function simplextree ?? TODO: Fixme")
3896
+ else:
3897
+ blocks = self.get_ptr().kcritical_simplextree_to_scc()
3898
+ # reduces the space in memory
3899
+ if is_function_st:
3900
+ blocks = [(tuple(f), tuple(b)) for f,b in blocks[::-1]]
3901
+ else:
3902
+ blocks = [(tuple(f), tuple(b)) for f,b in blocks[::-1]] ## presentation is on the other order
3903
+ return blocks # +[(np.empty(0,dtype=filtration_dtype),[])] ## TODO : investigate
3904
+
3905
+ def to_scc_kcritical(self,
3906
+ path:os.PathLike|str,
3907
+ bool rivet_compatible=False,
3908
+ bool strip_comments=False,
3909
+ bool ignore_last_generators=False,
3910
+ bool overwrite=False,
3911
+ bool reverse_block=False,
3912
+ ):
3913
+ """
3914
+ TODO: function-simplextree, from squeezed
3915
+ """
3916
+ from os.path import exists
3917
+ from os import remove
3918
+ if exists(path):
3919
+ if not(overwrite):
3920
+ raise Exception(f"The file {path} already exists. Use the `overwrite` flag if you want to overwrite.")
3921
+ remove(path)
3922
+ # blocks = simplextree2scc(self)
3923
+ blocks = self._to_scc()
3924
+ from multipers.io import scc2disk
3925
+ scc2disk(blocks,
3926
+ path=path,
3927
+ num_parameters=self.num_parameters,
3928
+ reverse_block=reverse_block,
3929
+ rivet_compatible=rivet_compatible,
3930
+ ignore_last_generators=ignore_last_generators,
3931
+ strip_comments=strip_comments,
3932
+ )
3933
+
3934
+ def to_scc_function_st(self,
3935
+ path="scc_dataset.scc",
3936
+ bool rivet_compatible=False,
3937
+ bool strip_comments=False,
3938
+ bool ignore_last_generators=False,
3939
+ bool overwrite=False,
3940
+ bool reverse_block=True,
3941
+ ):
3942
+ from multipers.io import scc2disk
3943
+ from warnings import warn
3944
+ warn("This function is not tested yet.")
3945
+ from os.path import exists
3946
+ from os import remove
3947
+ if exists(path):
3948
+ if not(overwrite):
3949
+ raise Exception(f"The file {path} already exists. Use the `overwrite` flag if you want to overwrite.")
3950
+ remove(path)
3951
+ stuff = self._to_scc(self)
3952
+ if reverse_block: stuff.reverse()
3953
+ cdef int num_parameters = self.num_parameters
3954
+ with open(path, "w") as f:
3955
+ f.write("scc2020\n") if not rivet_compatible else f.write("firep\n")
3956
+ if not strip_comments and not rivet_compatible: f.write("# Number of parameters\n")
3957
+ num_parameters = self.num_parameters
3958
+ if rivet_compatible:
3959
+ assert num_parameters == 2
3960
+ f.write("Filtration 1\n")
3961
+ f.write("Filtration 2\n")
3962
+ else:
3963
+ f.write(f"{num_parameters}\n")
3964
+
3965
+ if not strip_comments: f.write("# Sizes of generating sets\n")
3966
+ for block in stuff: f.write(f"{len(block[1])} ")
3967
+ f.write("\n")
3968
+
3969
+ for i,block in enumerate(stuff):
3970
+ if (rivet_compatible or ignore_last_generators) and i == len(stuff)-1: continue
3971
+ if not strip_comments: f.write(f"# Block of dimension {len(stuff)-i}\n")
3972
+ for filtration, boundary in zip(*block):
3973
+ k = len(filtration)
3974
+ for j in range(k):
3975
+ if filtration[j] != np.inf:
3976
+ line = f"{filtration[j]} {k} ; " + " ".join([str(x) for x in boundary]) +"\n"
3977
+ f.write(line)
3978
+ def to_scc(self,**kwargs):
3979
+ """
3980
+ Returns an scc representation of the simplextree.
3981
+ """
3982
+ if self._is_function_simplextree:
3983
+ return self.to_scc_function_st(**kwargs)
3984
+ else:
3985
+ return self.to_scc_kcritical(**kwargs)
3986
+
3987
+ 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:
3988
+ """ Create a file that can be imported by rivet, representing the filtration of the simplextree.
3989
+
3990
+ Parameters
3991
+ ----------
3992
+
3993
+ path:str
3994
+ path of the file.
3995
+ degree:int
3996
+ The homological degree to ask rivet to compute.
3997
+ progress:bool = True
3998
+ Shows the progress bar.
3999
+ overwrite:bool = False
4000
+ If true, will overwrite the previous file if it already exists.
4001
+ """
4002
+ ...
4003
+ from os.path import exists
4004
+ from os import remove
4005
+ if exists(path):
4006
+ if not(overwrite):
4007
+ print(f"The file {path} already exists. Use the `overwrite` flag if you want to overwrite.")
4008
+ return
4009
+ remove(path)
4010
+ file = open(path, "a")
4011
+ file.write("# This file was generated by multipers.\n")
4012
+ file.write("--datatype bifiltration\n")
4013
+ file.write(f"--homology {degree}\n") if degree is not None else None
4014
+ file.write(f"-x {xbins}\n") if xbins is not None else None
4015
+ file.write(f"-y {ybins}\n") if ybins is not None else None
4016
+ file.write("--xlabel time of appearance\n")
4017
+ file.write("--ylabel density\n\n")
4018
+ from tqdm import tqdm
4019
+ with tqdm(total=self.num_simplices, position=0, disable = not(progress), desc="Writing simplex to file") as bar:
4020
+ for dim in range(0,self.dimension+1): # Not sure if dimension sort is necessary for rivet. Check ?
4021
+ file.write(f"# block of dimension {dim}\n")
4022
+ for s,F in self.get_skeleton(dim):
4023
+ if len(s) != dim+1: continue
4024
+ for i in s:
4025
+ file.write(str(i) + " ")
4026
+ file.write("; ")
4027
+ for f in F:
4028
+ file.write(str(f) + " ")
4029
+ file.write("\n")
4030
+ bar.update(1)
4031
+ file.close()
4032
+ return
4033
+
4034
+
4035
+
4036
+ def _get_filtration_values(self, vector[int] degrees, bool inf_to_nan:bool=False, bool return_raw = False)->Iterable[np.ndarray]:
4037
+ # cdef vector[int] c_degrees = degrees
4038
+ # out = get_filtration_values_from_ptr[KFf64](ptr, degrees)
4039
+ cdef intptr_t ptr = self.thisptr
4040
+ cdef vector[vector[vector[double]]] out
4041
+ with nogil:
4042
+ out = self.get_ptr().get_filtration_values(degrees)
4043
+ filtrations_values = [np.asarray(filtration) for filtration in out]
4044
+ # Removes infs
4045
+ if inf_to_nan:
4046
+ for i,f in enumerate(filtrations_values):
4047
+ filtrations_values[i][f == np.inf] = np.nan
4048
+ filtrations_values[i][f == - np.inf] = np.nan
4049
+ return filtrations_values
4050
+
4051
+
4052
+ 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]:
4053
+ """
4054
+ Returns a grid over the n-filtration, from the simplextree. Usefull for grid_squeeze. TODO : multicritical
4055
+
4056
+ Parameters
4057
+ ----------
4058
+
4059
+ resolution: list[int]
4060
+ resolution of the grid, for each parameter
4061
+ box=None : pair[list[float]]
4062
+ Grid bounds. format : [low bound, high bound]
4063
+ If None is given, will use the filtration bounds of the simplextree.
4064
+ grid_strategy="regular" : string
4065
+ Either "regular", "quantile", or "exact".
4066
+ Returns
4067
+ -------
4068
+
4069
+ List of filtration values, for each parameter, defining the grid.
4070
+ """
4071
+ if degrees is None:
4072
+ degrees = range(self.dimension+1)
4073
+
4074
+
4075
+ ## preprocesses the filtration values:
4076
+ filtrations_values = np.concatenate(self._get_filtration_values(degrees, inf_to_nan=True), axis=1)
4077
+ # removes duplicate + sort (nan at the end)
4078
+ filtrations_values = [np.unique(filtration) for filtration in filtrations_values]
4079
+ # removes nan
4080
+ filtrations_values = [filtration[:-1] if np.isnan(filtration[-1]) else filtration for filtration in filtrations_values]
4081
+
4082
+ return mpg.compute_grid(filtrations_values, resolution=resolution,strategy=grid_strategy,drop_quantiles=drop_quantiles)
4083
+
4084
+
4085
+
4086
+ 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):
4087
+ """
4088
+ Fit the filtration of the simplextree to a grid.
4089
+
4090
+ :param filtration_grid: The grid on which to squeeze. An example of grid can be given by the `get_filtration_grid` method.
4091
+ :type filtration_grid: list[list[float]]
4092
+ :param coordinate_values: If true, the filtrations values of the simplices will be set to the coordinate of the filtration grid.
4093
+ :type coordinate_values: bool
4094
+ """
4095
+ if not force and self._is_squeezed:
4096
+ raise Exception("SimplexTree already squeezed, use `force=True` if that's really what you want to do.")
4097
+ #TODO : multi-critical
4098
+ if filtration_grid is None:
4099
+ filtration_grid = self.get_filtration_grid(grid_strategy=grid_strategy, **filtration_grid_kwargs)
4100
+ cdef vector[vector[double]] c_filtration_grid = filtration_grid
4101
+ 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()}"
4102
+ cdef intptr_t ptr = self.thisptr
4103
+ if coordinate_values and inplace:
4104
+ self.filtration_grid = c_filtration_grid
4105
+ if inplace or not coordinate_values:
4106
+ self.get_ptr().squeeze_filtration_inplace(c_filtration_grid, coordinate_values)
4107
+ else:
4108
+ out = SimplexTreeMulti_KFi32(num_parameters=self.num_parameters)
4109
+ self.get_ptr().squeeze_filtration(out.thisptr, c_filtration_grid)
4110
+ out.filtration_grid = c_filtration_grid
4111
+ return out
4112
+ return self
4113
+
4114
+ @property
4115
+ def _is_squeezed(self)->bool:
4116
+ return self.num_vertices > 0 and len(self.filtration_grid)>0 and len(self.filtration_grid[0]) > 0
4117
+
4118
+ @property
4119
+ def dtype(self)->type:
4120
+ return np.float64
4121
+ def filtration_bounds(self, degrees:Iterable[int]|None=None, q:float|tuple=0, split_dimension:bool=False)->np.ndarray:
4122
+ """
4123
+ Returns the filtrations bounds of the finite filtration values.
4124
+ """
4125
+ try:
4126
+ a,b =q
4127
+ except:
4128
+ a,b,=q,q
4129
+ degrees = range(self.dimension+1) if degrees is None else degrees
4130
+ filtrations_values = self._get_filtration_values(degrees, inf_to_nan=True) ## degree, parameter, pt
4131
+ boxes = np.array([np.nanquantile(filtration, [a, 1-b], axis=1) for filtration in filtrations_values],dtype=float)
4132
+ if split_dimension: return boxes
4133
+ return np.asarray([np.nanmin(boxes, axis=(0,1)), np.nanmax(boxes, axis=(0,1))]) # box, birth/death, filtration
4134
+
4135
+
4136
+
4137
+
4138
+ def fill_lowerstar(self, F, int parameter)->SimplexTreeMulti_KFf64:
4139
+ """ Fills the `dimension`th filtration by the lower-star filtration defined by F.
4140
+
4141
+ Parameters
4142
+ ----------
4143
+
4144
+ F:1d array
4145
+ The density over the vertices, that induces a lowerstar filtration.
4146
+ parameter:int
4147
+ Which filtration parameter to fill. /!\ python starts at 0.
4148
+
4149
+ Returns
4150
+ -------
4151
+
4152
+ self:SimplexTreeMulti
4153
+ """
4154
+ # for s, sf in self.get_simplices():
4155
+ # self.assign_filtration(s, [f if i != dimension else np.max(np.array(F)[s]) for i,f in enumerate(sf)])
4156
+ # cdef int c_parameter = parameter
4157
+ cdef KFf64 c_F = _py2kc_f64(np.asarray(F,dtype=np.float64)[None,:])
4158
+ with nogil:
4159
+ self.get_ptr().fill_lowerstar(c_F, parameter)
4160
+ return self
4161
+
4162
+
4163
+ def project_on_line(self, parameter:int=0, basepoint:None|list|np.ndarray= None, direction:None|list|np.ndarray= None)->SimplexTree:
4164
+ """Converts an multi simplextree to a gudhi simplextree.
4165
+
4166
+ Parameters
4167
+ ----------
4168
+
4169
+ parameter:int = 0
4170
+ The parameter to keep. WARNING will crash if the multi simplextree is not well filled.
4171
+ basepoint:None
4172
+ Instead of keeping a single parameter, will consider the filtration defined by the diagonal line crossing the basepoint.
4173
+
4174
+ WARNING
4175
+ -------
4176
+
4177
+ There are no safeguard yet, it WILL crash if asking for a parameter that is not filled.
4178
+
4179
+ Returns
4180
+ -------
4181
+
4182
+ A SimplexTree with chosen 1D filtration.
4183
+ """
4184
+ # FIXME : deal with multicritical filtrations
4185
+ import gudhi as gd
4186
+ new_simplextree = gd.SimplexTree()
4187
+ assert parameter < self.get_ptr().get_number_of_parameters()
4188
+ cdef int c_parameter = parameter
4189
+ cdef intptr_t old_ptr = self.thisptr
4190
+ cdef intptr_t new_ptr = new_simplextree.thisptr
4191
+ if basepoint is None:
4192
+ basepoint = np.array([np.inf]*self.get_ptr().get_number_of_parameters())
4193
+ basepoint[parameter] = 0
4194
+ if direction is None:
4195
+ direction = np.array([0]*self.get_ptr().get_number_of_parameters())
4196
+ direction[parameter] = 1
4197
+ cdef Finitely_critical_multi_filtration[double] c_basepoint = _py21c_f64(basepoint)
4198
+ cdef Finitely_critical_multi_filtration[double] c_direction = _py21c_f64(direction)
4199
+ cdef Line[double] c_line = Line[double](c_basepoint, c_direction)
4200
+ with nogil:
4201
+ self.get_ptr().to_std(new_ptr, c_line, c_parameter)
4202
+ return new_simplextree
4203
+
4204
+ def linear_projections(self, linear_forms:np.ndarray)->Iterable[SimplexTree]:
4205
+ """
4206
+ Compute the 1-parameter projections, w.r.t. given the linear forms, of this simplextree.
4207
+
4208
+ Input
4209
+ -----
4210
+
4211
+ Array of shape (num_linear_forms, num_parameters)
4212
+
4213
+ Output
4214
+ ------
4215
+
4216
+ List of projected (gudhi) simplextrees.
4217
+ """
4218
+ cdef Py_ssize_t num_projections = linear_forms.shape[0]
4219
+ cdef Py_ssize_t num_parameters = linear_forms.shape[1]
4220
+ if num_projections == 0: return []
4221
+ cdef vector[vector[double]] c_linear_forms = linear_forms
4222
+ assert num_parameters==self.num_parameters, f"The linear forms has to have the same number of parameter as the simplextree ({self.num_parameters})."
4223
+
4224
+ # Gudhi copies are faster than inserting simplices one by one
4225
+ import gudhi as gd
4226
+ # flattened_simplextree = gd.SimplexTree()
4227
+ # cdef intptr_t multi_prt = self.thisptr
4228
+ # cdef intptr_t flattened_ptr = flattened_simplextree.thisptr
4229
+ # with nogil:
4230
+ # # flatten_from_ptr(multi_prt, flattened_ptr, num_parameters)
4231
+ # self.get_ptr().to_std(flattened_ptr, num_parameters)
4232
+ flattened_simplextree = self.project_on_line()
4233
+ out = [flattened_simplextree] + [gd.SimplexTree(flattened_simplextree) for _ in range(num_projections-1)]
4234
+
4235
+ # Fills the 1-parameter simplextrees.
4236
+ cdef vector[intptr_t] out_ptrs = [st.thisptr for st in out]
4237
+ with nogil:
4238
+ for i in range(num_projections):
4239
+ self.get_ptr().to_std_linear_projection(out_ptrs[i], c_linear_forms[i])
4240
+ return out
4241
+
4242
+
4243
+ def set_num_parameter(self, num:int):
4244
+ """
4245
+ Sets the numbers of parameters.
4246
+ WARNING : it will resize all the filtrations to this size.
4247
+ """
4248
+ self.get_ptr().resize_all_filtrations(num)
4249
+ self.get_ptr().set_number_of_parameters(num)
4250
+ return
4251
+
4252
+ def __eq__(self, other:SimplexTreeMulti_KFf64):
4253
+ """Test for structural equality
4254
+ :returns: True if the 2 simplex trees are equal, False otherwise.
4255
+ :rtype: bool
4256
+ """
4257
+ return dereference(self.get_ptr()) == dereference(other.get_ptr())
4258
+
4259
+
4260
+
4261
+
4262
+
4263
+
4264
+ def _simplextree_multify_KFf64(simplextree:SimplexTree, int num_parameters, default_values=[])->SimplexTreeMulti_KFf64:
4265
+ """Converts a gudhi simplextree to a multi simplextree.
4266
+ Parameters
4267
+ ----------
4268
+
4269
+ parameters:int = 2
4270
+ The number of filtrations
4271
+
4272
+ Returns
4273
+ -------
4274
+
4275
+ A multi simplextree, with first filtration value being the one from the original simplextree.
4276
+ """
4277
+ if isinstance(simplextree, SimplexTreeMulti_KFf64):
4278
+ return simplextree
4279
+ st = SimplexTreeMulti_KFf64(num_parameters=num_parameters)
4280
+ cdef intptr_t old_ptr = simplextree.thisptr
4281
+ cdef KFf64 c_default_values= _py2kc_f64([default_values])
4282
+ with nogil:
4283
+ st.get_ptr().from_std(old_ptr, num_parameters, c_default_values)
4284
+ return st
4285
+
4286
+ def _safe_simplextree_multify_KFf64(simplextree:SimplexTree,int num_parameters=2, cnp.ndarray default_values=np.array(-np.inf))->SimplexTreeMulti_KFf64:
4287
+ if isinstance(simplextree, SimplexTreeMulti_KFf64):
4288
+ return simplextree
4289
+ simplices = [[] for _ in range(simplextree.dimension()+1)]
4290
+ filtration_values = [[] for _ in range(simplextree.dimension()+1)]
4291
+ st_multi = SimplexTreeMulti_KFf64(num_parameters=1)
4292
+ if num_parameters > 1:
4293
+ st_multi.set_num_parameter(num_parameters)
4294
+ if default_values.squeeze().ndim == 0:
4295
+ default_values = np.zeros(num_parameters-1) + default_values
4296
+
4297
+ # TODO : Optimize with Python.h
4298
+ for s,f in simplextree.get_simplices():
4299
+ filtration_values[len(s)-1].append(np.concatenate([[f],default_values]))
4300
+ simplices[len(s)-1].append(s)
4301
+ for batch_simplices, batch_filtrations in zip(simplices,filtration_values):
4302
+ st_multi.insert_batch(np.asarray(batch_simplices, dtype=np.int32).T, np.asarray(batch_filtrations, dtype=np.float64))
4303
+ return st_multi
4304
+
4305
+ global available_simplextrees, SimplexTreeMulti_type
4306
+ available_simplextrees = tuple((
4307
+ SimplexTreeMulti_Fi32,
4308
+ SimplexTreeMulti_KFi32,
4309
+ SimplexTreeMulti_Ff64,
4310
+ SimplexTreeMulti_KFf64,
4311
+ ))
4312
+ SimplexTreeMulti_type:type= Union[
4313
+ SimplexTreeMulti_Fi32,
4314
+ SimplexTreeMulti_KFi32,
4315
+ SimplexTreeMulti_Ff64,
4316
+ SimplexTreeMulti_KFf64,
4317
+ ]
4318
+
4319
+ def is_simplextree_multi(input)->bool:
4320
+ return (False
4321
+ or isinstance(input, SimplexTreeMulti_Fi32)
4322
+ or isinstance(input, SimplexTreeMulti_KFi32)
4323
+ or isinstance(input, SimplexTreeMulti_Ff64)
4324
+ or isinstance(input, SimplexTreeMulti_KFf64)
4325
+ )
4326
+
4327
+
4328
+
4329
+
4330
+
4331
+ def SimplexTreeMulti(input=None, int num_parameters=2, dtype:type = np.float64, bool kcritical = False,**kwargs) -> SimplexTreeMulti_type:
4332
+ """SimplexTreeMulti constructor.
4333
+
4334
+ :param other: If `other` is `None` (default value), an empty `SimplexTreeMulti` is created.
4335
+ If `other` is a `SimplexTree`, the `SimplexTreeMulti` is constructed from a deep copy of `other`.
4336
+ If `other` is a `SimplexTreeMulti`, the `SimplexTreeMulti` is constructed from a deep copy of `other`.
4337
+ :type other: SimplexTree or SimplexTreeMulti (Optional)
4338
+ :param num_parameters: The number of parameter of the multi-parameter filtration.
4339
+ :type num_parameters: int
4340
+ :returns: An empty or a copy simplex tree.
4341
+ :rtype: SimplexTreeMulti
4342
+
4343
+ :raises TypeError: In case `other` is neither `None`, nor a `SimplexTree`, nor a `SimplexTreeMulti`.
4344
+ """
4345
+ cdef dict[tuple[type, bool], type] _st_map = {
4346
+ (np.dtype(np.int32),False): SimplexTreeMulti_Fi32,
4347
+ (np.dtype(np.int32),True): SimplexTreeMulti_KFi32,
4348
+ (np.dtype(np.float64),False): SimplexTreeMulti_Ff64,
4349
+ (np.dtype(np.float64),True): SimplexTreeMulti_KFf64,
4350
+ }
4351
+ # TODO : check that + kcriticality
4352
+ # if input is not None:
4353
+ # assert input.dtype is dtype, "SimplexTree conversions are not yet implemented"
4354
+
4355
+ return _st_map[(np.dtype(dtype), kcritical)](input, num_parameters=num_parameters, **kwargs)
4356
+
4357
+
4358
+
4359
+
4360
+ ## SIGNED MEASURES STUFF
4361
+ from multipers.grids import sms_in_grid
4362
+
4363
+ ctypedef int32_t indices_type # uint fails for some reason
4364
+ python_indices_type=np.int32
4365
+
4366
+ ctypedef int32_t tensor_dtype
4367
+ python_tensor_dtype = np.int32
4368
+
4369
+
4370
+ ctypedef pair[vector[vector[indices_type]], vector[tensor_dtype]] signed_measure_type
4371
+
4372
+ cdef extern from "multi_parameter_rank_invariant/hilbert_function.h" namespace "Gudhi::multiparameter::hilbert_function":
4373
+ signed_measure_type get_hilbert_signed_measure(Simplex_tree_multi_interface[Fi32, int32_t]&, tensor_dtype* , const vector[indices_type], const vector[indices_type], bool, indices_type, bool, bool) except + nogil
4374
+ signed_measure_type get_hilbert_signed_measure(Simplex_tree_multi_interface[KFi32, int32_t]&, tensor_dtype* , const vector[indices_type], const vector[indices_type], bool, indices_type, bool, bool) except + nogil
4375
+ signed_measure_type get_hilbert_signed_measure(Simplex_tree_multi_interface[Ff64, double]&, tensor_dtype* , const vector[indices_type], const vector[indices_type], bool, indices_type, bool, bool) except + nogil
4376
+ signed_measure_type get_hilbert_signed_measure(Simplex_tree_multi_interface[KFf64, double]&, tensor_dtype* , const vector[indices_type], const vector[indices_type], bool, indices_type, bool, bool) except + nogil
4377
+
4378
+
4379
+
4380
+
4381
+ ## Aligns python/cpp
4382
+ 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):
4383
+ cdef intptr_t simplextree_ptr = simplextree.thisptr
4384
+ if False:
4385
+ pass
4386
+ elif isinstance(simplextree, SimplexTreeMulti_Fi32):
4387
+ with nogil:
4388
+ return get_hilbert_signed_measure(dereference(<Simplex_tree_multi_interface[Fi32, int32_t]*>simplextree_ptr), container_ptr, c_grid_shape, degrees, zero_pad, n_jobs, verbose, expand_collapse)
4389
+ elif isinstance(simplextree, SimplexTreeMulti_KFi32):
4390
+ with nogil:
4391
+ return get_hilbert_signed_measure(dereference(<Simplex_tree_multi_interface[KFi32, int32_t]*>simplextree_ptr), container_ptr, c_grid_shape, degrees, zero_pad, n_jobs, verbose, expand_collapse)
4392
+ elif isinstance(simplextree, SimplexTreeMulti_Ff64):
4393
+ with nogil:
4394
+ return get_hilbert_signed_measure(dereference(<Simplex_tree_multi_interface[Ff64, double]*>simplextree_ptr), container_ptr, c_grid_shape, degrees, zero_pad, n_jobs, verbose, expand_collapse)
4395
+ elif isinstance(simplextree, SimplexTreeMulti_KFf64):
4396
+ with nogil:
4397
+ return get_hilbert_signed_measure(dereference(<Simplex_tree_multi_interface[KFf64, double]*>simplextree_ptr), container_ptr, c_grid_shape, degrees, zero_pad, n_jobs, verbose, expand_collapse)
4398
+ else:
4399
+ raise ValueError("Input {simplextree} not supported.")
4400
+
4401
+ def _hilbert_signed_measure(simplextree,
4402
+ vector[indices_type] degrees,
4403
+ mass_default=None,
4404
+ plot=False,
4405
+ indices_type n_jobs=0,
4406
+ bool verbose=False,
4407
+ bool expand_collapse=False,
4408
+ grid_conversion = None):
4409
+ """
4410
+ Computes the signed measures given by the decomposition of the hilbert function.
4411
+
4412
+ Input
4413
+ -----
4414
+
4415
+ - simplextree:SimplexTreeMulti, the multifiltered simplicial complex
4416
+ - degrees:array-like of ints, the degrees to compute
4417
+ - 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.
4418
+ - plot:bool, plots the computed measures if true.
4419
+ - n_jobs:int, number of jobs. Defaults to #cpu, but when doing parallel computations of signed measures, we recommend setting this to 1.
4420
+ - verbose:bool, prints c++ logs.
4421
+
4422
+ Output
4423
+ ------
4424
+
4425
+ `[signed_measure_of_degree for degree in degrees]`
4426
+ with `signed_measure_of_degree` of the form `(dirac location, dirac weights)`.
4427
+ """
4428
+
4429
+ assert simplextree._is_squeezed, "Squeeze grid first."
4430
+ cdef bool zero_pad = mass_default is not None
4431
+ # assert simplextree.num_parameters == 2
4432
+ grid_shape = np.array([len(f) for f in simplextree.filtration_grid])
4433
+ if mass_default is None:
4434
+ mass_default = mass_default
4435
+ else:
4436
+ mass_default = np.asarray(mass_default)
4437
+ assert mass_default.ndim == 1 and mass_default.shape[0] == simplextree.num_parameters
4438
+ if zero_pad:
4439
+ for i, _ in enumerate(grid_shape):
4440
+ grid_shape[i] += 1 # adds a 0
4441
+ if grid_conversion is not None:
4442
+ grid_conversion = tuple(np.concatenate([f, [mass_default[i]]]) for i,f in enumerate(grid_conversion))
4443
+ assert len(grid_shape) == simplextree.num_parameters, "Grid shape size has to be the number of parameters."
4444
+ grid_shape_with_degree = np.asarray(np.concatenate([[len(degrees)], grid_shape]), dtype=python_indices_type)
4445
+ container_array = np.ascontiguousarray(np.zeros(grid_shape_with_degree, dtype=python_tensor_dtype).flatten())
4446
+ 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[])"
4447
+ cdef intptr_t simplextree_ptr = simplextree.thisptr
4448
+ cdef vector[indices_type] c_grid_shape = grid_shape_with_degree
4449
+ cdef tensor_dtype[::1] container = container_array
4450
+ cdef tensor_dtype* container_ptr = &container[0]
4451
+ cdef signed_measure_type out = _hilbert_sm_from_simplextree(simplextree, container_ptr, c_grid_shape, degrees, zero_pad, n_jobs, verbose, expand_collapse)
4452
+ pts, weights = np.asarray(out.first, dtype=int).reshape(-1, simplextree.num_parameters+1), np.asarray(out.second, dtype=int)
4453
+ slices = np.concatenate([np.searchsorted(pts[:,0], np.arange(degrees.size())), [pts.shape[0]] ])
4454
+ sms = [
4455
+ (pts[slices[i]:slices[i+1],1:],weights[slices[i]:slices[i+1]])
4456
+ for i in range(slices.shape[0]-1)
4457
+ ]
4458
+ if grid_conversion is not None:
4459
+ sms = sms_in_grid(sms,grid_conversion)
4460
+
4461
+ if plot:
4462
+ from multipers.plots import plot_signed_measures
4463
+ plot_signed_measures(sms)
4464
+ return sms
4465
+
4466
+
4467
+ #### EULER CHAR
4468
+
4469
+ cdef extern from "multi_parameter_rank_invariant/euler_characteristic.h" namespace "Gudhi::multiparameter::euler_characteristic":
4470
+ # void get_euler_surface_python(const intptr_t, tensor_dtype*, const vector[indices_type], bool, bool, bool) except + nogil
4471
+ signed_measure_type get_euler_signed_measure(Simplex_tree_multi_interface[Fi32, int32_t]&, tensor_dtype* , const vector[indices_type], bool, bool) except + nogil
4472
+ signed_measure_type get_euler_signed_measure(Simplex_tree_multi_interface[KFi32, int32_t]&, tensor_dtype* , const vector[indices_type], bool, bool) except + nogil
4473
+ signed_measure_type get_euler_signed_measure(Simplex_tree_multi_interface[Ff64, double]&, tensor_dtype* , const vector[indices_type], bool, bool) except + nogil
4474
+ signed_measure_type get_euler_signed_measure(Simplex_tree_multi_interface[KFf64, double]&, tensor_dtype* , const vector[indices_type], bool, bool) except + nogil
4475
+
4476
+
4477
+ ## Aligns python/cpp
4478
+ 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):
4479
+ cdef intptr_t simplextree_ptr = simplextree.thisptr
4480
+ if False:
4481
+ pass
4482
+ elif isinstance(simplextree, SimplexTreeMulti_Fi32):
4483
+ with nogil:
4484
+ return get_euler_signed_measure(dereference(<Simplex_tree_multi_interface[Fi32, int32_t]*>simplextree_ptr), container_ptr, c_grid_shape, zero_pad, verbose)
4485
+ elif isinstance(simplextree, SimplexTreeMulti_Ff64):
4486
+ with nogil:
4487
+ return get_euler_signed_measure(dereference(<Simplex_tree_multi_interface[Ff64, double]*>simplextree_ptr), container_ptr, c_grid_shape, zero_pad, verbose)
4488
+ else:
4489
+ raise ValueError("Input {simplextree} not supported.")
4490
+
4491
+
4492
+ def _euler_signed_measure(simplextree, mass_default=None, bool verbose=False, bool plot=False, grid_conversion=None):
4493
+ """
4494
+ Computes the signed measures given by the decomposition of the hilbert function.
4495
+
4496
+ Input
4497
+ -----
4498
+
4499
+ - simplextree:SimplexTreeMulti, the multifiltered simplicial complex
4500
+ - 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.
4501
+ - plot:bool, plots the computed measures if true.
4502
+ - n_jobs:int, number of jobs. Defaults to #cpu, but when doing parallel computations of signed measures, we recommend setting this to 1.
4503
+ - verbose:bool, prints c++ logs.
4504
+
4505
+ Output
4506
+ ------
4507
+
4508
+ `[signed_measure_of_degree for degree in degrees]`
4509
+ with `signed_measure_of_degree` of the form `(dirac location, dirac weights)`.
4510
+ """
4511
+ assert len(simplextree.filtration_grid[0]) > 0, "Squeeze grid first."
4512
+ cdef bool zero_pad = mass_default is not None
4513
+ # assert simplextree.num_parameters == 2
4514
+ grid_shape = np.array([len(f) for f in simplextree.filtration_grid])
4515
+
4516
+ if mass_default is None:
4517
+ mass_default = mass_default
4518
+ else:
4519
+ mass_default = np.asarray(mass_default)
4520
+ assert mass_default.ndim == 1 and mass_default.shape[0] == simplextree.num_parameters
4521
+ if zero_pad:
4522
+ for i, _ in enumerate(grid_shape):
4523
+ grid_shape[i] += 1 # adds a 0
4524
+ if grid_conversion is not None:
4525
+ grid_conversion = tuple(np.concatenate([f, [mass_default[i]]]) for i,f in enumerate(grid_conversion))
4526
+ assert len(grid_shape) == simplextree.num_parameters, "Grid shape size has to be the number of parameters."
4527
+ container_array = np.ascontiguousarray(np.zeros(grid_shape, dtype=python_tensor_dtype).flatten())
4528
+ 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[])"
4529
+ cdef intptr_t simplextree_ptr = simplextree.thisptr
4530
+ cdef vector[indices_type] c_grid_shape = grid_shape
4531
+ cdef tensor_dtype[::1] container = container_array
4532
+ cdef tensor_dtype* container_ptr = &container[0]
4533
+ cdef signed_measure_type out
4534
+
4535
+ out = _euler_sm_from_simplextree(simplextree, container_ptr, c_grid_shape, zero_pad, verbose)
4536
+
4537
+ pts, weights = np.asarray(out.first, dtype=int).reshape(-1, simplextree.num_parameters), np.asarray(out.second, dtype=int)
4538
+ # return pts, weights
4539
+ sm = (pts,weights)
4540
+
4541
+ if grid_conversion is not None:
4542
+ sm, = sms_in_grid([sm], grid_conversion)
4543
+ if plot:
4544
+ from multipers.plots import plot_signed_measures
4545
+ plot_signed_measures([sm])
4546
+ return sm
4547
+
4548
+
4549
+
4550
+
4551
+
4552
+
4553
+
4554
+
4555
+
4556
+ ## Rank invariant
4557
+
4558
+ cdef extern from "multi_parameter_rank_invariant/rank_invariant.h" namespace "Gudhi::multiparameter::rank_invariant":
4559
+ void compute_rank_invariant_python(Simplex_tree_multi_interface[Fi32, int32_t]&, tensor_dtype* , const vector[indices_type], const vector[indices_type], indices_type, bool) except + nogil
4560
+ void compute_rank_invariant_python(Simplex_tree_multi_interface[KFi32, int32_t]&, tensor_dtype* , const vector[indices_type], const vector[indices_type], indices_type, bool) except + nogil
4561
+ void compute_rank_invariant_python(Simplex_tree_multi_interface[Ff64, double]&, tensor_dtype* , const vector[indices_type], const vector[indices_type], indices_type, bool) except + nogil
4562
+ void compute_rank_invariant_python(Simplex_tree_multi_interface[KFf64, double]&, tensor_dtype* , const vector[indices_type], const vector[indices_type], indices_type, bool) except + nogil
4563
+
4564
+
4565
+ ## Aligns python/cpp
4566
+ 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):
4567
+ cdef intptr_t simplextree_ptr = simplextree.thisptr
4568
+ if False:
4569
+ pass
4570
+ elif isinstance(simplextree, SimplexTreeMulti_Fi32):
4571
+ with nogil:
4572
+ compute_rank_invariant_python(dereference(<Simplex_tree_multi_interface[Fi32, int32_t]*>simplextree_ptr), container_ptr, c_grid_shape, degrees, n_jobs, expand_collapse)
4573
+ elif isinstance(simplextree, SimplexTreeMulti_KFi32):
4574
+ with nogil:
4575
+ compute_rank_invariant_python(dereference(<Simplex_tree_multi_interface[KFi32, int32_t]*>simplextree_ptr), container_ptr, c_grid_shape, degrees, n_jobs, expand_collapse)
4576
+ elif isinstance(simplextree, SimplexTreeMulti_Ff64):
4577
+ with nogil:
4578
+ compute_rank_invariant_python(dereference(<Simplex_tree_multi_interface[Ff64, double]*>simplextree_ptr), container_ptr, c_grid_shape, degrees, n_jobs, expand_collapse)
4579
+ elif isinstance(simplextree, SimplexTreeMulti_KFf64):
4580
+ with nogil:
4581
+ compute_rank_invariant_python(dereference(<Simplex_tree_multi_interface[KFf64, double]*>simplextree_ptr), container_ptr, c_grid_shape, degrees, n_jobs, expand_collapse)
4582
+ else:
4583
+ raise ValueError("Input {simplextree} not supported.")
4584
+
4585
+
4586
+ 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):
4587
+ """
4588
+ Computes the signed measures given by the decomposition of the hilbert function.
4589
+
4590
+ Input
4591
+ -----
4592
+
4593
+ - simplextree:SimplexTreeMulti, the multifiltered simplicial complex
4594
+ - degrees:array-like of ints, the degrees to compute
4595
+ - 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.
4596
+ - plot:bool, plots the computed measures if true.
4597
+ - n_jobs:int, number of jobs. Defaults to #cpu, but when doing parallel computations of signed measures, we recommend setting this to 1.
4598
+ - verbose:bool, prints c++ logs.
4599
+
4600
+ Output
4601
+ ------
4602
+
4603
+ `[signed_measure_of_degree for degree in degrees]`
4604
+ with `signed_measure_of_degree` of the form `(dirac location, dirac weights)`.
4605
+ """
4606
+ assert simplextree._is_squeezed, "Squeeze grid first."
4607
+ cdef bool zero_pad = mass_default is not None
4608
+ grid_conversion = [np.asarray(f) for f in simplextree.filtration_grid]
4609
+ # assert simplextree.num_parameters == 2
4610
+ grid_shape = np.array([len(f) for f in grid_conversion])
4611
+
4612
+ if mass_default is None:
4613
+ mass_default = mass_default
4614
+ else:
4615
+ mass_default = np.asarray(mass_default)
4616
+ 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,)"
4617
+ if zero_pad:
4618
+ for i, _ in enumerate(grid_shape):
4619
+ grid_shape[i] += 1 # adds a 0
4620
+ grid_conversion = tuple(np.concatenate([f, [mass_default[i]]]) for i,f in enumerate(grid_conversion))
4621
+
4622
+ assert len(grid_shape) == simplextree.num_parameters, "Grid shape size has to be the number of parameters."
4623
+ grid_shape_with_degree = np.asarray(np.concatenate([[len(degrees)], grid_shape, grid_shape]), dtype=python_indices_type)
4624
+ container_array = np.ascontiguousarray(np.zeros(grid_shape_with_degree, dtype=python_tensor_dtype).flatten())
4625
+ 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[])"
4626
+ cdef intptr_t simplextree_ptr = simplextree.thisptr
4627
+ cdef vector[indices_type] c_grid_shape = grid_shape_with_degree
4628
+ cdef tensor_dtype[::1] container = container_array
4629
+ cdef tensor_dtype* container_ptr = &container[0]
4630
+ _rank_sm_from_simplextree(simplextree, container_ptr,c_grid_shape,degrees, n_jobs, expand_collapse)
4631
+ rank = container_array.reshape(grid_shape_with_degree)
4632
+ rank = tuple(rank_decomposition_by_rectangles(rank_of_degree) for rank_of_degree in rank)
4633
+ out = []
4634
+ cdef int num_parameters = simplextree.num_parameters
4635
+ for rank_decomposition in rank:
4636
+ (coords, weights) = sparsify(np.ascontiguousarray(rank_decomposition))
4637
+ births = coords[:,:num_parameters]
4638
+ deaths = coords[:,num_parameters:]
4639
+ correct_indices = np.all(births<=deaths, axis=1) # TODO : correct this
4640
+ coords = coords[correct_indices]
4641
+ weights = weights[correct_indices]
4642
+ if len(correct_indices) == 0:
4643
+ pts, weights = np.empty((0, 2*num_parameters)), np.empty((0))
4644
+ else:
4645
+ pts = np.empty(shape=coords.shape, dtype=grid_conversion[0].dtype)
4646
+ for i in range(pts.shape[1]):
4647
+ pts[:,i] = grid_conversion[i % num_parameters][coords[:,i]]
4648
+ rank_decomposition = (pts,weights)
4649
+ out.append(rank_decomposition)
4650
+
4651
+ if plot:
4652
+ from multipers.plots import plot_signed_measures
4653
+ plot_signed_measures(out)
4654
+ return out
4655
+