multipers 1.1.3__cp310-cp310-macosx_11_0_universal2.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 (63) hide show
  1. multipers/.dylibs/libtbb.12.12.dylib +0 -0
  2. multipers/.dylibs/libtbbmalloc.2.12.dylib +0 -0
  3. multipers/__init__.py +5 -0
  4. multipers/_old_rank_invariant.pyx +328 -0
  5. multipers/_signed_measure_meta.py +193 -0
  6. multipers/data/MOL2.py +350 -0
  7. multipers/data/UCR.py +18 -0
  8. multipers/data/__init__.py +1 -0
  9. multipers/data/graphs.py +466 -0
  10. multipers/data/immuno_regions.py +27 -0
  11. multipers/data/minimal_presentation_to_st_bf.py +0 -0
  12. multipers/data/pytorch2simplextree.py +91 -0
  13. multipers/data/shape3d.py +101 -0
  14. multipers/data/synthetic.py +68 -0
  15. multipers/distances.py +172 -0
  16. multipers/euler_characteristic.cpython-310-darwin.so +0 -0
  17. multipers/euler_characteristic.pyx +137 -0
  18. multipers/function_rips.cpython-310-darwin.so +0 -0
  19. multipers/function_rips.pyx +102 -0
  20. multipers/hilbert_function.cpython-310-darwin.so +0 -0
  21. multipers/hilbert_function.pyi +46 -0
  22. multipers/hilbert_function.pyx +151 -0
  23. multipers/io.cpython-310-darwin.so +0 -0
  24. multipers/io.pyx +176 -0
  25. multipers/ml/__init__.py +0 -0
  26. multipers/ml/accuracies.py +61 -0
  27. multipers/ml/convolutions.py +510 -0
  28. multipers/ml/invariants_with_persistable.py +79 -0
  29. multipers/ml/kernels.py +128 -0
  30. multipers/ml/mma.py +657 -0
  31. multipers/ml/one.py +472 -0
  32. multipers/ml/point_clouds.py +191 -0
  33. multipers/ml/signed_betti.py +50 -0
  34. multipers/ml/signed_measures.py +1479 -0
  35. multipers/ml/sliced_wasserstein.py +313 -0
  36. multipers/ml/tools.py +116 -0
  37. multipers/mma_structures.cpython-310-darwin.so +0 -0
  38. multipers/mma_structures.pxd +155 -0
  39. multipers/mma_structures.pyx +651 -0
  40. multipers/multiparameter_edge_collapse.py +29 -0
  41. multipers/multiparameter_module_approximation.cpython-310-darwin.so +0 -0
  42. multipers/multiparameter_module_approximation.pyi +439 -0
  43. multipers/multiparameter_module_approximation.pyx +311 -0
  44. multipers/pickle.py +53 -0
  45. multipers/plots.py +292 -0
  46. multipers/point_measure_integration.cpython-310-darwin.so +0 -0
  47. multipers/point_measure_integration.pyx +59 -0
  48. multipers/rank_invariant.cpython-310-darwin.so +0 -0
  49. multipers/rank_invariant.pyx +154 -0
  50. multipers/simplex_tree_multi.cpython-310-darwin.so +0 -0
  51. multipers/simplex_tree_multi.pxd +121 -0
  52. multipers/simplex_tree_multi.pyi +715 -0
  53. multipers/simplex_tree_multi.pyx +1417 -0
  54. multipers/slicer.cpython-310-darwin.so +0 -0
  55. multipers/slicer.pxd +94 -0
  56. multipers/slicer.pyx +276 -0
  57. multipers/tensor.pxd +13 -0
  58. multipers/test.pyx +44 -0
  59. multipers-1.1.3.dist-info/LICENSE +21 -0
  60. multipers-1.1.3.dist-info/METADATA +22 -0
  61. multipers-1.1.3.dist-info/RECORD +63 -0
  62. multipers-1.1.3.dist-info/WHEEL +5 -0
  63. multipers-1.1.3.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1417 @@
1
+ # This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT.
2
+ # See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details.
3
+ # Author(s): Vincent Rouvreau
4
+ #
5
+ # Copyright (C) 2016 Inria
6
+ #
7
+ # Modification(s):
8
+ # - 2023 David Loiseaux : Conversions with standard simplextree, scc2020 format, edge collapses, euler characteristic, grid filtrations.
9
+ # - 2022/11 Hannah Schreiber / David Loiseaux : adapt for multipersistence.
10
+ # - YYYY/MM Author: Description of the modification
11
+
12
+
13
+
14
+ __author__ = "Vincent Rouvreau"
15
+ __copyright__ = "Copyright (C) 2016 Inria"
16
+ __license__ = "MIT"
17
+
18
+ from libc.stdint cimport intptr_t, int32_t, int64_t
19
+ from cython.operator import dereference, preincrement
20
+ from libc.stdint cimport intptr_t
21
+ from libc.stdint cimport uintptr_t, intptr_t
22
+ from libcpp.map cimport map
23
+
24
+
25
+ ctypedef fused some_int:
26
+ int32_t
27
+ int64_t
28
+ int
29
+
30
+ ctypedef fused some_float:
31
+ float
32
+ double
33
+
34
+ ctypedef vector[pair[pair[int,int],pair[value_type,value_type]]] edge_list_type
35
+
36
+ from typing import Any
37
+
38
+ cimport numpy as cnp
39
+ import numpy as np
40
+ cnp.import_array()
41
+
42
+ from multipers.simplex_tree_multi cimport *
43
+ cimport cython
44
+ from gudhi.simplex_tree import SimplexTree ## Small hack for typing
45
+ from multipers.multiparameter_module_approximation import PyModule
46
+ from multipers.io import simplextree2scc
47
+ from typing import Iterable,Literal,Optional
48
+ from tqdm import tqdm
49
+
50
+
51
+
52
+ from warnings import warn
53
+
54
+
55
+ cdef extern from "gudhi/Simplex_tree_multi_interface.h" namespace "Gudhi::multiparameter":
56
+ void multify_from_ptr(const intptr_t, const intptr_t, const unsigned int, const vector[value_type]&) except + nogil
57
+ void flatten_from_ptr(const intptr_t, const intptr_t, const unsigned int) nogil
58
+ void linear_projection_from_ptr(const intptr_t, const intptr_t, const vector[value_type]&) nogil
59
+ void flatten_diag_from_ptr(const intptr_t, const intptr_t, const vector[value_type], int) nogil
60
+ void squeeze_filtration_from_ptr(intptr_t, const vector[vector[value_type]]&, bool) except + nogil
61
+ vector[vector[vector[value_type]]] get_filtration_values_from_ptr(intptr_t, const vector[int]&) except + nogil
62
+
63
+ SAFE_CONVERSION=True #Slower but at least it works everywhere
64
+
65
+ _available_strategies = Literal["regular","regular_closest", "partition", "quantile", "precomputed"]
66
+ # cdef bool callback(vector[int] simplex, void *blocker_func):
67
+ # return (<object>blocker_func)(simplex)
68
+
69
+ # SimplexTree python interface
70
+ cdef class SimplexTreeMulti:
71
+ """The simplex tree is an efficient and flexible data structure for
72
+ representing general (filtered) simplicial complexes. The data structure
73
+ is described in Jean-Daniel Boissonnat and Clément Maria. The Simplex
74
+ Tree: An Efficient Data Structure for General Simplicial Complexes.
75
+ Algorithmica, pages 1–22, 2014.
76
+
77
+ This class is a multi-filtered, with keys, and non contiguous vertices version
78
+ of the simplex tree.
79
+ """
80
+ # unfortunately 'cdef public Simplex_tree_multi_interface* thisptr' is not possible
81
+ # Use intptr_t instead to cast the pointer
82
+ cdef public intptr_t thisptr
83
+
84
+ cdef public vector[vector[value_type]] filtration_grid
85
+ cdef public bool _is_function_simplextree
86
+ # Get the pointer casted as it should be
87
+ cdef Simplex_tree_multi_interface* get_ptr(self) noexcept nogil:
88
+ return <Simplex_tree_multi_interface*>(self.thisptr)
89
+
90
+ # cdef Simplex_tree_persistence_interface * pcohptr
91
+ # Fake constructor that does nothing but documenting the constructor
92
+ def __init__(self, other = None, num_parameters:int=2,default_values=[], safe_conversion=False):
93
+ """SimplexTreeMulti constructor.
94
+
95
+ :param other: If `other` is `None` (default value), an empty `SimplexTreeMulti` is created.
96
+ If `other` is a `SimplexTree`, the `SimplexTreeMulti` is constructed from a deep copy of `other`.
97
+ If `other` is a `SimplexTreeMulti`, the `SimplexTreeMulti` is constructed from a deep copy of `other`.
98
+ :type other: SimplexTree or SimplexTreeMulti (Optional)
99
+ :param num_parameters: The number of parameter of the multi-parameter filtration.
100
+ :type num_parameters: int
101
+ :returns: An empty or a copy simplex tree.
102
+ :rtype: SimplexTreeMulti
103
+
104
+ :raises TypeError: In case `other` is neither `None`, nor a `SimplexTree`, nor a `SimplexTreeMulti`.
105
+ """
106
+
107
+ # The real cython constructor
108
+ def __cinit__(self, other = None, int num_parameters=2,
109
+ default_values=np.asarray([-np.inf]), # I'm not sure why `[]` does not work. Cython bug ?
110
+ bool safe_conversion=False,
111
+ ): #TODO doc
112
+ cdef vector[value_type] c_default_values=default_values
113
+ cdef intptr_t other_ptr
114
+ if other is not None:
115
+ if isinstance(other, SimplexTreeMulti):
116
+ self.thisptr = _get_copy_intptr(other)
117
+ num_parameters = other.num_parameters
118
+ self.filtration_grid = other.filtration_grid
119
+ elif isinstance(other, SimplexTree): # Constructs a SimplexTreeMulti from a SimplexTree
120
+ self.thisptr = <intptr_t>(new Simplex_tree_multi_interface())
121
+ if safe_conversion or SAFE_CONVERSION:
122
+ new_st_multi = _safe_simplextree_multify(other, num_parameters = num_parameters, default_values=np.asarray(default_values))
123
+ self.thisptr, new_st_multi.thisptr = new_st_multi.thisptr, self.thisptr
124
+ else:
125
+ other_ptr = other.thisptr
126
+ with nogil:
127
+ multify_from_ptr(other_ptr, self.thisptr, num_parameters, c_default_values)
128
+ else:
129
+ raise TypeError("`other` argument requires to be of type `SimplexTree`, `SimplexTreeMulti`, or `None`.")
130
+ else:
131
+ self.thisptr = <intptr_t>(new Simplex_tree_multi_interface())
132
+ self.get_ptr().set_number_of_parameters(num_parameters)
133
+ self._is_function_simplextree = False
134
+ self.filtration_grid=[[]*num_parameters]
135
+
136
+ def __dealloc__(self):
137
+ cdef Simplex_tree_multi_interface* ptr = self.get_ptr()
138
+ if ptr != NULL:
139
+ del ptr
140
+ # if self.pcohptr != NULL:
141
+ # del self.pcohptr
142
+
143
+ def __is_defined(self):
144
+ """Returns true if SimplexTree pointer is not NULL.
145
+ """
146
+ return self.get_ptr() != NULL
147
+
148
+ # def __is_persistence_defined(self):
149
+ # """Returns true if Persistence pointer is not NULL.
150
+ # """
151
+ # return self.pcohptr != NULL
152
+
153
+ def copy(self)->SimplexTreeMulti:
154
+ """
155
+ :returns: A simplex tree that is a deep copy of itself.
156
+ :rtype: SimplexTreeMulti
157
+
158
+ :note: The persistence information is not copied. If you need it in the clone, you have to call
159
+ :func:`compute_persistence` on it even if you had already computed it in the original.
160
+ """
161
+ stree = SimplexTreeMulti(self,num_parameters=self.num_parameters)
162
+ return stree
163
+
164
+ def __deepcopy__(self):
165
+ return self.copy()
166
+
167
+ def filtration(self, simplex:list|np.ndarray)->np.ndarray:
168
+ """This function returns the filtration value for a given N-simplex in
169
+ this simplicial complex, or +infinity if it is not in the complex.
170
+
171
+ :param simplex: The N-simplex, represented by a list of vertex.
172
+ :type simplex: list of int
173
+ :returns: The simplicial complex multi-critical filtration value.
174
+ :rtype: numpy array of shape (-1, num_parameters)
175
+ """
176
+ return self[simplex]
177
+
178
+ def assign_filtration(self, simplex:list|np.ndarray, filtration:list|np.ndarray)->None:
179
+ """This function assigns a new multi-critical filtration value to a
180
+ given N-simplex.
181
+
182
+ :param simplex: The N-simplex, represented by a list of vertex.
183
+ :type simplex: list of int
184
+ :param filtration: The new filtration(s) value(s), concatenated.
185
+ :type filtration: list[float] or np.ndarray[float, ndim=1]
186
+
187
+ .. note::
188
+ Beware that after this operation, the structure may not be a valid
189
+ filtration anymore, a simplex could have a lower filtration value
190
+ than one of its faces. Callers are responsible for fixing this
191
+ (with more :meth:`assign_filtration` or
192
+ :meth:`make_filtration_non_decreasing` for instance) before calling
193
+ any function that relies on the filtration property, like
194
+ :meth:`persistence`.
195
+ """
196
+ assert len(filtration)>0 and len(filtration) % self.get_ptr().get_number_of_parameters() == 0
197
+ self.get_ptr().assign_simplex_filtration(simplex, Finitely_critical_multi_filtration(<python_filtration_type>filtration))
198
+
199
+ def __getitem__(self, simplex):
200
+ cdef vector[int] csimplex = simplex
201
+ cdef value_type[:] filtration_view = <value_type[:self.get_ptr().get_number_of_parameters()]> self.get_ptr().simplex_filtration(csimplex)
202
+ return np.asarray(filtration_view)
203
+
204
+
205
+ @property
206
+ def num_vertices(self)->int:
207
+ """This function returns the number of vertices of the simplicial
208
+ complex.
209
+
210
+ :returns: The simplicial complex number of vertices.
211
+ :rtype: int
212
+ """
213
+ return self.get_ptr().num_vertices()
214
+
215
+ @property
216
+ def num_simplices(self)->int:
217
+ """This function returns the number of simplices of the simplicial
218
+ complex.
219
+
220
+ :returns: the simplicial complex number of simplices.
221
+ :rtype: int
222
+ """
223
+ return self.get_ptr().num_simplices()
224
+
225
+ @property
226
+ def dimension(self)->int:
227
+ """This function returns the dimension of the simplicial complex.
228
+
229
+ :returns: the simplicial complex dimension.
230
+ :rtype: int
231
+
232
+ .. note::
233
+
234
+ This function is not constant time because it can recompute
235
+ dimension if required (can be triggered by
236
+ :func:`remove_maximal_simplex`
237
+ or
238
+ :func:`prune_above_filtration`
239
+ methods).
240
+ """
241
+ return self.get_ptr().dimension()
242
+ def upper_bound_dimension(self)->int:
243
+ """This function returns a valid dimension upper bound of the
244
+ simplicial complex.
245
+
246
+ :returns: an upper bound on the dimension of the simplicial complex.
247
+ :rtype: int
248
+ """
249
+ return self.get_ptr().upper_bound_dimension()
250
+
251
+ def set_dimension(self, dimension)->None:
252
+ """This function sets the dimension of the simplicial complex.
253
+
254
+ :param dimension: The new dimension value.
255
+ :type dimension: int
256
+
257
+ .. note::
258
+
259
+ This function must be used with caution because it disables
260
+ dimension recomputation when required
261
+ (this recomputation can be triggered by
262
+ :func:`remove_maximal_simplex`
263
+ or
264
+ :func:`prune_above_filtration`
265
+ ).
266
+ """
267
+ self.get_ptr().set_dimension(<int>dimension)
268
+
269
+ # def find(self, simplex)->bool:
270
+ # """This function returns if the N-simplex was found in the simplicial
271
+ # complex or not.
272
+
273
+ # :param simplex: The N-simplex to find, represented by a list of vertex.
274
+ # :type simplex: list of int
275
+ # :returns: true if the simplex was found, false otherwise.
276
+ # :rtype: bool
277
+ # """
278
+ # return self.get_ptr().find_simplex(simplex)
279
+ def __contains__(self, simplex)->bool:
280
+ """This function returns if the N-simplex was found in the simplicial
281
+ complex or not.
282
+
283
+ :param simplex: The N-simplex to find, represented by a list of vertex.
284
+ :type simplex: list of int
285
+ :returns: true if the simplex was found, false otherwise.
286
+ :rtype: bool
287
+ """
288
+ return self.get_ptr().find_simplex(simplex)
289
+
290
+ def insert(self, simplex, filtration:list|np.ndarray|None=None)->bool:
291
+ """This function inserts the given N-simplex and its subfaces with the
292
+ given filtration value (default value is '0.0'). If some of those
293
+ simplices are already present with a higher filtration value, their
294
+ filtration value is lowered.
295
+
296
+ :param simplex: The N-simplex to insert, represented by a list of
297
+ vertex.
298
+ :type simplex: list of int
299
+ :param filtration: The filtration value of the simplex.
300
+ :type filtration: float
301
+ :returns: true if the simplex was not yet in the complex, false
302
+ otherwise (whatever its original filtration value).
303
+ :rtype: bool
304
+ """
305
+ # TODO C++, to be compatible with insert_batch and multicritical filtrations
306
+ num_parameters = self.get_ptr().get_number_of_parameters()
307
+ assert filtration is None or len(filtration) % num_parameters == 0, f"Invalid number \
308
+ of parameters. Should be {num_parameters}, got {len(filtration)}"
309
+ if filtration is None:
310
+ filtration = np.array([-np.inf]*num_parameters, dtype = float)
311
+ return self.get_ptr().insert(simplex, Finitely_critical_multi_filtration(<python_filtration_type>filtration))
312
+
313
+ @cython.boundscheck(False)
314
+ @cython.wraparound(False)
315
+ def insert_batch(self, some_int[:,:] vertex_array, some_float[:,:] filtrations)->SimplexTreeMulti:
316
+ """Inserts k-simplices given by a sparse array in a format similar
317
+ to `torch.sparse <https://pytorch.org/docs/stable/sparse.html>`_.
318
+ The n-th simplex has vertices `vertex_array[0,n]`, ...,
319
+ `vertex_array[k,n]` and filtration value `filtrations[n,num_parameters]`.
320
+ /!\ Only compatible with 1-critical filtrations. If a simplex is repeated,
321
+ only one filtration value will be taken into account.
322
+
323
+ :param vertex_array: the k-simplices to insert.
324
+ :type vertex_array: numpy.array of shape (k+1,n)
325
+ :param filtrations: the filtration values.
326
+ :type filtrations: numpy.array of shape (n,num_parameters)
327
+ """
328
+ # TODO : multi-critical
329
+ # cdef vector[int] vertices = np.unique(vertex_array)
330
+ cdef Py_ssize_t k = vertex_array.shape[0]
331
+ cdef Py_ssize_t n = vertex_array.shape[1]
332
+ assert filtrations.shape[0] == n, f"inconsistent sizes for vertex_array and filtrations\
333
+ Filtrations should be of shape ({n},{self.num_parameters})"
334
+ assert filtrations.shape[1] == self.num_parameters, f"Inconsistent number of parameters.\
335
+ Filtrations should be of shape ({n},{self.num_parameters})"
336
+ cdef Py_ssize_t i
337
+ cdef Py_ssize_t j
338
+ cdef vector[int] v
339
+ cdef Finitely_critical_multi_filtration w
340
+ cdef int n_parameters = self.num_parameters
341
+ with nogil:
342
+ for i in range(n):
343
+ for j in range(k):
344
+ v.push_back(vertex_array[j, i])
345
+ for j in range(n_parameters):
346
+ w.push_back(filtrations[i,j])
347
+ self.get_ptr().insert(v, w)
348
+ v.clear()
349
+ w.clear()
350
+ return self
351
+
352
+ def assign_all(self, filtration_values)-> SimplexTreeMulti:
353
+ cdef Py_ssize_t num_simplices = filtration_values.shape[0]
354
+ cdef Py_ssize_t num_parameters = filtration_values.shape[1]
355
+
356
+ assert num_simplices == self.num_simplices, f"Number of filtration values {filtration_values.shape[0]} is not the number of simplices {self.num_simplices}"
357
+ assert num_parameters == self.num_parameters, f"Number of parameter do not coincide {filtration_values.shape[1]} vs {self.num_parameters}"
358
+ cdef Simplex_tree_multi_simplices_iterator it = self.get_ptr().get_simplices_iterator_begin()
359
+ cdef Simplex_tree_multi_simplices_iterator end = self.get_ptr().get_simplices_iterator_end()
360
+ cdef Simplex_tree_multi_simplex_handle sh = dereference(it)
361
+ cdef int counter =0
362
+ # cdef cnp.ndarray[value_type,ndim=1] current_filtration
363
+ cdef value_type[:,:] F = filtration_values
364
+ with nogil:
365
+ while it != end:
366
+ pair = self.get_ptr().get_simplex_and_filtration(dereference(it))
367
+
368
+ for i in range(num_parameters):
369
+ pair.second[i] = F[counter,i]
370
+ # current_filtration= F[counter]
371
+ counter += 1
372
+ # yield SimplexTreeMulti._pair_simplex_filtration_to_python(out)
373
+ preincrement(it)
374
+
375
+
376
+
377
+ @cython.boundscheck(False)
378
+ @cython.wraparound(False)
379
+ def assign_batch_filtration(self, some_int[:,:] vertex_array, some_float[:,:] filtrations, bool propagate=True)->SimplexTreeMulti:
380
+ """Assign k-simplices given by a sparse array in a format similar
381
+ to `torch.sparse <https://pytorch.org/docs/stable/sparse.html>`_.
382
+ The n-th simplex has vertices `vertex_array[0,n]`, ...,
383
+ `vertex_array[k,n]` and filtration value `filtrations[n,num_parameters]`.
384
+ /!\ Only compatible with 1-critical filtrations. If a simplex is repeated,
385
+ only one filtration value will be taken into account.
386
+
387
+ :param vertex_array: the k-simplices to assign.
388
+ :type vertex_array: numpy.array of shape (k+1,n)
389
+ :param filtrations: the filtration values.
390
+ :type filtrations: numpy.array of shape (n,num_parameters)
391
+ """
392
+ cdef Py_ssize_t k = vertex_array.shape[0]
393
+ cdef Py_ssize_t n = vertex_array.shape[1]
394
+ assert filtrations.shape[0] == n, 'inconsistent sizes for vertex_array and filtrations'
395
+ assert filtrations.shape[1] == self.num_parameters, "wrong number of parameters"
396
+ cdef Py_ssize_t i
397
+ cdef Py_ssize_t j
398
+ cdef vector[int] v
399
+ cdef Finitely_critical_multi_filtration w
400
+ cdef int n_parameters = self.num_parameters
401
+ with nogil:
402
+ for i in range(n):
403
+ for j in range(k):
404
+ v.push_back(vertex_array[j, i])
405
+ for j in range(n_parameters):
406
+ w.push_back(filtrations[i,j])
407
+ self.get_ptr().assign_simplex_filtration(v, w)
408
+ v.clear()
409
+ w.clear()
410
+ if propagate: self.make_filtration_non_decreasing()
411
+ return self
412
+
413
+
414
+
415
+ def get_simplices(self):
416
+ """This function returns a generator with simplices and their given
417
+ filtration values.
418
+
419
+ :returns: The simplices.
420
+ :rtype: generator with tuples(simplex, filtration)
421
+ """
422
+ cdef Simplex_tree_multi_simplices_iterator it = self.get_ptr().get_simplices_iterator_begin()
423
+ cdef Simplex_tree_multi_simplices_iterator end = self.get_ptr().get_simplices_iterator_end()
424
+ cdef Simplex_tree_multi_simplex_handle sh = dereference(it)
425
+ # cdef pair[simplex_type,Finitely_critical_multi_filtration] out_
426
+ # while it != end:
427
+ # out_ = self.get_ptr().get_simplex_and_filtration(dereference(it))
428
+ # out = (out_.first,out_.second.get_vector())
429
+ # yield out
430
+ # preincrement(it)
431
+ # cdef pair[simplex_type,filtration_type] out
432
+ cdef int num_parameters = self.get_ptr().get_number_of_parameters()
433
+ while it != end:
434
+ pair = self.get_ptr().get_simplex_and_filtration(dereference(it))
435
+
436
+ yield (np.asarray(pair.first, dtype=int),np.asarray(<value_type[:num_parameters]> pair.second))
437
+ # yield SimplexTreeMulti._pair_simplex_filtration_to_python(out)
438
+ preincrement(it)
439
+
440
+
441
+ def get_filtration(self):
442
+ """This function returns a generator with simplices and their given
443
+ filtration values sorted by increasing filtration values.
444
+
445
+ :returns: The simplices sorted by increasing filtration values.
446
+ :rtype: generator with tuples(simplex, filtration)
447
+ """
448
+ cdef vector[Simplex_tree_multi_simplex_handle].const_iterator it = self.get_ptr().get_filtration_iterator_begin()
449
+ cdef vector[Simplex_tree_multi_simplex_handle].const_iterator end = self.get_ptr().get_filtration_iterator_end()
450
+ cdef int num_parameters = self.get_ptr().get_number_of_parameters()
451
+ while it != end:
452
+ # yield self.get_ptr().get_simplex_and_filtration(dereference(it))
453
+ pair = self.get_ptr().get_simplex_and_filtration(dereference(it))
454
+ yield (np.asarray(pair.first, dtype=int),np.asarray(<value_type[:num_parameters]> pair.second))
455
+ preincrement(it)
456
+
457
+ def get_skeleton(self, dimension):
458
+ """This function returns a generator with the (simplices of the) skeleton of a maximum given dimension.
459
+
460
+ :param dimension: The skeleton dimension value.
461
+ :type dimension: int
462
+ :returns: The (simplices of the) skeleton of a maximum dimension.
463
+ :rtype: generator with tuples(simplex, filtration)
464
+ """
465
+ cdef Simplex_tree_multi_skeleton_iterator it = self.get_ptr().get_skeleton_iterator_begin(dimension)
466
+ cdef Simplex_tree_multi_skeleton_iterator end = self.get_ptr().get_skeleton_iterator_end(dimension)
467
+ cdef int num_parameters = self.get_ptr().get_number_of_parameters()
468
+ while it != end:
469
+ # yield self.get_ptr().get_simplex_and_filtration(dereference(it))
470
+ pair = self.get_ptr().get_simplex_and_filtration(dereference(it))
471
+ yield (np.asarray(pair.first, dtype=int),np.asarray(<value_type[:num_parameters]> pair.second))
472
+ preincrement(it)
473
+
474
+ def get_star(self, simplex):
475
+ """This function returns the star of a given N-simplex.
476
+
477
+ :param simplex: The N-simplex, represented by a list of vertex.
478
+ :type simplex: list of int
479
+ :returns: The (simplices of the) star of a simplex.
480
+ :rtype: list of tuples(simplex, filtration)
481
+ """
482
+ cdef simplex_type csimplex = simplex
483
+ cdef int num_parameters = self.num_parameters
484
+ # for i in simplex:
485
+ # csimplex.push_back(i)
486
+ cdef vector[simplex_filtration_type] star \
487
+ = self.get_ptr().get_star(csimplex)
488
+ ct = []
489
+
490
+ for filtered_simplex in star:
491
+ v = []
492
+ for vertex in filtered_simplex.first:
493
+ v.append(vertex)
494
+ ct.append((v, np.asarray(<value_type[:num_parameters]>filtered_simplex.second)))
495
+ return ct
496
+
497
+ def get_cofaces(self, simplex, codimension):
498
+ """This function returns the cofaces of a given N-simplex with a
499
+ given codimension.
500
+
501
+ :param simplex: The N-simplex, represented by a list of vertex.
502
+ :type simplex: list of int
503
+ :param codimension: The codimension. If codimension = 0, all cofaces
504
+ are returned (equivalent of get_star function)
505
+ :type codimension: int
506
+ :returns: The (simplices of the) cofaces of a simplex
507
+ :rtype: list of tuples(simplex, filtration)
508
+ """
509
+ cdef vector[int] csimplex = simplex
510
+ cdef int num_parameters = self.num_parameters
511
+ # for i in simplex:
512
+ # csimplex.push_back(i)
513
+ cdef vector[simplex_filtration_type] cofaces \
514
+ = self.get_ptr().get_cofaces(csimplex, <int>codimension)
515
+ ct = []
516
+ for filtered_simplex in cofaces:
517
+ v = []
518
+ for vertex in filtered_simplex.first:
519
+ v.append(vertex)
520
+ ct.append((v, np.asarray(<value_type[:num_parameters]>filtered_simplex.second)))
521
+ return ct
522
+
523
+ def get_boundaries(self, simplex):
524
+ """This function returns a generator with the boundaries of a given N-simplex.
525
+ If you do not need the filtration values, the boundary can also be obtained as
526
+ :code:`itertools.combinations(simplex,len(simplex)-1)`.
527
+
528
+ :param simplex: The N-simplex, represented by a list of vertex.
529
+ :type simplex: list of int.
530
+ :returns: The (simplices of the) boundary of a simplex
531
+ :rtype: generator with tuples(simplex, filtration)
532
+ """
533
+ cdef pair[Simplex_tree_multi_boundary_iterator, Simplex_tree_multi_boundary_iterator] it = self.get_ptr().get_boundary_iterators(simplex)
534
+
535
+ # while it.first != it.second:
536
+ # yield self.get_ptr().get_simplex_and_filtration(dereference(it.first))
537
+ # preincrement(it.first)
538
+ cdef int num_parameters = self.get_ptr().get_number_of_parameters()
539
+ while it.first != it.second:
540
+ # yield self.get_ptr().get_simplex_and_filtration(dereference(it))
541
+ pair = self.get_ptr().get_simplex_and_filtration(dereference(it.first))
542
+ yield (np.asarray(pair.first, dtype=int),np.asarray(<value_type[:num_parameters]> pair.second))
543
+ preincrement(it.first)
544
+ def remove_maximal_simplex(self, simplex):
545
+ """This function removes a given maximal N-simplex from the simplicial
546
+ complex.
547
+
548
+ :param simplex: The N-simplex, represented by a list of vertex.
549
+ :type simplex: list of int
550
+
551
+ .. note::
552
+
553
+ The dimension of the simplicial complex may be lower after calling
554
+ remove_maximal_simplex than it was before. However,
555
+ :func:`upper_bound_dimension`
556
+ method will return the old value, which
557
+ remains a valid upper bound. If you care, you can call
558
+ :func:`dimension`
559
+ to recompute the exact dimension.
560
+ """
561
+ self.get_ptr().remove_maximal_simplex(simplex)
562
+
563
+ # def prune_above_filtration(self, filtration)->bool:
564
+ # """Prune above filtration value given as parameter.
565
+
566
+ # :param filtration: Maximum threshold value.
567
+ # :type filtration: float
568
+ # :returns: The filtration modification information.
569
+ # :rtype: bool
570
+
571
+
572
+ # .. note::
573
+
574
+ # Note that the dimension of the simplicial complex may be lower
575
+ # after calling
576
+ # :func:`prune_above_filtration`
577
+ # than it was before. However,
578
+ # :func:`upper_bound_dimension`
579
+ # will return the old value, which remains a
580
+ # valid upper bound. If you care, you can call
581
+ # :func:`dimension`
582
+ # method to recompute the exact dimension.
583
+ # """
584
+ # return self.get_ptr().prune_above_filtration(filtration)
585
+ def prune_above_dimension(self, int dimension):
586
+ """Remove all simplices of dimension greater than a given value.
587
+
588
+ :param dimension: Maximum dimension value.
589
+ :type dimension: int
590
+ :returns: The modification information.
591
+ :rtype: bool
592
+ """
593
+ return self.get_ptr().prune_above_dimension(dimension)
594
+ def expansion(self, int max_dim)->SimplexTreeMulti:
595
+ """Expands the simplex tree containing only its one skeleton
596
+ until dimension max_dim.
597
+
598
+ The expanded simplicial complex until dimension :math:`d`
599
+ attached to a graph :math:`G` is the maximal simplicial complex of
600
+ dimension at most :math:`d` admitting the graph :math:`G` as
601
+ :math:`1`-skeleton.
602
+ The filtration value assigned to a simplex is the maximal filtration
603
+ value of one of its edges.
604
+
605
+ The simplex tree must contain no simplex of dimension bigger than
606
+ 1 when calling the method.
607
+
608
+ :param max_dim: The maximal dimension.
609
+ :type max_dim: int
610
+ """
611
+ with nogil:
612
+ self.get_ptr().expansion(max_dim)
613
+ # This is a fix for multipersistence. FIXME expansion in c++
614
+ self.get_ptr().make_filtration_non_decreasing()
615
+ return self
616
+
617
+ def make_filtration_non_decreasing(self)->bool:
618
+ """This function ensures that each simplex has a higher filtration
619
+ value than its faces by increasing the filtration values.
620
+
621
+ :returns: True if any filtration value was modified,
622
+ False if the filtration was already non-decreasing.
623
+ :rtype: bool
624
+ """
625
+ cdef bool out
626
+ with nogil:
627
+ out = self.get_ptr().make_filtration_non_decreasing()
628
+ return out
629
+
630
+ def reset_filtration(self, filtration, min_dim = 0):
631
+ """This function resets the filtration value of all the simplices of dimension at least min_dim. Resets all the
632
+ simplex tree when `min_dim = 0`.
633
+ `reset_filtration` may break the filtration property with `min_dim > 0`, and it is the user's responsibility to
634
+ make it a valid filtration (using a large enough `filt_value`, or calling `make_filtration_non_decreasing`
635
+ afterwards for instance).
636
+
637
+ :param filtration: New threshold value.
638
+ :type filtration: float.
639
+ :param min_dim: The minimal dimension. Default value is 0.
640
+ :type min_dim: int.
641
+ """
642
+ self.get_ptr().reset_filtration(Finitely_critical_multi_filtration(<python_filtration_type>filtration), min_dim)
643
+
644
+
645
+
646
+ # def extend_filtration(self):
647
+ # """ Extend filtration for computing extended persistence. This function only uses the filtration values at the
648
+ # 0-dimensional simplices, and computes the extended persistence diagram induced by the lower-star filtration
649
+ # computed with these values.
650
+ #
651
+ # .. note::
652
+ #
653
+ # Note that after calling this function, the filtration values are actually modified within the simplex tree.
654
+ # The function :func:`extended_persistence` retrieves the original values.
655
+ #
656
+ # .. note::
657
+ #
658
+ # Note that this code creates an extra vertex internally, so you should make sure that the simplex tree does
659
+ # not contain a vertex with the largest possible value (i.e., 4294967295).
660
+ #
661
+ # This `notebook <https://github.com/GUDHI/TDA-tutorial/blob/master/Tuto-GUDHI-extended-persistence.ipynb>`_
662
+ # explains how to compute an extension of persistence called extended persistence.
663
+ # """
664
+ # self.get_ptr().compute_extended_filtration()
665
+
666
+ # def extended_persistence(self, homology_coeff_field=11, min_persistence=0):
667
+ # """This function retrieves good values for extended persistence, and separate the diagrams into the Ordinary,
668
+ # Relative, Extended+ and Extended- subdiagrams.
669
+ #
670
+ # :param homology_coeff_field: The homology coefficient field. Must be a prime number. Default value is 11. Max is 46337.
671
+ # :type homology_coeff_field: int
672
+ # :param min_persistence: The minimum persistence value (i.e., the absolute value of the difference between the
673
+ # persistence diagram point coordinates) to take into account (strictly greater than min_persistence).
674
+ # Default value is 0.0. Sets min_persistence to -1.0 to see all values.
675
+ # :type min_persistence: float
676
+ # :returns: A list of four persistence diagrams in the format described in :func:`persistence`. The first one is
677
+ # Ordinary, the second one is Relative, the third one is Extended+ and the fourth one is Extended-.
678
+ # See https://link.springer.com/article/10.1007/s10208-008-9027-z and/or section 2.2 in
679
+ # https://link.springer.com/article/10.1007/s10208-017-9370-z for a description of these subtypes.
680
+ #
681
+ # .. note::
682
+ #
683
+ # This function should be called only if :func:`extend_filtration` has been called first!
684
+ #
685
+ # .. note::
686
+ #
687
+ # The coordinates of the persistence diagram points might be a little different than the
688
+ # original filtration values due to the internal transformation (scaling to [-2,-1]) that is
689
+ # performed on these values during the computation of extended persistence.
690
+ #
691
+ # This `notebook <https://github.com/GUDHI/TDA-tutorial/blob/master/Tuto-GUDHI-extended-persistence.ipynb>`_
692
+ # explains how to compute an extension of persistence called extended persistence.
693
+ # """
694
+ # cdef vector[pair[int, pair[value_type, value_type]]] persistence_result
695
+ # if self.pcohptr != NULL:
696
+ # del self.pcohptr
697
+ # self.pcohptr = new Simplex_tree_persistence_interface(self.get_ptr(), False)
698
+ # self.pcohptr.compute_persistence(homology_coeff_field, -1.)
699
+ # return self.pcohptr.compute_extended_persistence_subdiagrams(min_persistence)
700
+
701
+ # TODO : cython3
702
+ # def expansion_with_blocker(self, max_dim, blocker_func):
703
+ # """Expands the Simplex_tree containing only a graph. Simplices corresponding to cliques in the graph are added
704
+ # incrementally, faces before cofaces, unless the simplex has dimension larger than `max_dim` or `blocker_func`
705
+ # returns `True` for this simplex.
706
+
707
+ # The function identifies a candidate simplex whose faces are all already in the complex, inserts it with a
708
+ # filtration value corresponding to the maximum of the filtration values of the faces, then calls `blocker_func`
709
+ # with this new simplex (represented as a list of int). If `blocker_func` returns `True`, the simplex is removed,
710
+ # otherwise it is kept. The algorithm then proceeds with the next candidate.
711
+
712
+ # .. warning::
713
+ # Several candidates of the same dimension may be inserted simultaneously before calling `blocker_func`, so
714
+ # if you examine the complex in `blocker_func`, you may hit a few simplices of the same dimension that have
715
+ # not been vetted by `blocker_func` yet, or have already been rejected but not yet removed.
716
+
717
+ # :param max_dim: Expansion maximal dimension value.
718
+ # :type max_dim: int
719
+ # :param blocker_func: Blocker oracle.
720
+ # :type blocker_func: Callable[[List[int]], bool]
721
+ # """
722
+ # self.get_ptr().expansion_with_blockers_callback(max_dim, callback, <void*>blocker_func)
723
+
724
+ # def persistence(self, homology_coeff_field=11, min_persistence=0, persistence_dim_max = False):
725
+ # """This function computes and returns the persistence of the simplicial complex.
726
+ #
727
+ # :param homology_coeff_field: The homology coefficient field. Must be a
728
+ # prime number. Default value is 11. Max is 46337.
729
+ # :type homology_coeff_field: int
730
+ # :param min_persistence: The minimum persistence value to take into
731
+ # account (strictly greater than min_persistence). Default value is
732
+ # 0.0.
733
+ # Set min_persistence to -1.0 to see all values.
734
+ # :type min_persistence: float
735
+ # :param persistence_dim_max: If true, the persistent homology for the
736
+ # maximal dimension in the complex is computed. If false, it is
737
+ # ignored. Default is false.
738
+ # :type persistence_dim_max: bool
739
+ # :returns: The persistence of the simplicial complex.
740
+ # :rtype: list of pairs(dimension, pair(birth, death))
741
+ # """
742
+ # self.compute_persistence(homology_coeff_field, min_persistence, persistence_dim_max)
743
+ # return self.pcohptr.get_persistence()
744
+
745
+ def persistence_approximation(self, **kwargs)->PyModule:
746
+ """Computes an interval module approximation of a multiparameter filtration.
747
+
748
+ Parameters
749
+ ----------
750
+ max_error: positive float
751
+ Trade-off between approximation and computational complexity.
752
+ Upper bound of the module approximation, in bottleneck distance,
753
+ for interval-decomposable modules.
754
+ nlines: int
755
+ Alternative to precision.
756
+ box : pair of list of floats
757
+ Defines a rectangle on which to compute the approximation.
758
+ Format : [x,y], where x,y defines the rectangle {z : x ≤ z ≤ y}
759
+ threshold: bool
760
+ When true, computes the module restricted to the box.
761
+ max_dimension:int
762
+ Max simplextree dimension to consider.
763
+ verbose: bool
764
+ Prints C++ infos.
765
+ ignore_warning : bool
766
+ Unless set to true, prevents computing on more than 10k lines. Useful to prevent a segmentation fault due to "infinite" recursion.
767
+
768
+ Returns
769
+ -------
770
+ PyModule
771
+ An interval decomposable module approximation of the module defined by the
772
+ homology of this multi-filtration.
773
+ """
774
+ from multipers.multiparameter_module_approximation import module_approximation, PyModule
775
+ if self.num_simplices <= 0:
776
+ return PyModule()
777
+ assert self.num_parameters > 1, f"Use standard Gudhi for 1-parameter persistence."
778
+ return module_approximation(self,**kwargs)
779
+
780
+
781
+ ## This function is only meant for the edge collapse interface.
782
+ def get_edge_list(self):
783
+ cdef edge_list out
784
+ with nogil:
785
+ out = self.get_ptr().get_edge_list()
786
+ return out
787
+
788
+ def collapse_edges(self, max_dimension:int=None, num:int=1, progress:bool=False, strong:bool=True, full:bool=False, ignore_warning:bool=False)->SimplexTreeMulti:
789
+ """Edge collapse for 1-critical 2-parameter clique complex (see https://arxiv.org/abs/2211.05574).
790
+ It uses the code from the github repository https://github.com/aj-alonso/filtration_domination .
791
+
792
+ Parameters
793
+ ----------
794
+ max_dimension:int
795
+ Max simplicial dimension of the complex. Unless specified, keeps the same dimension.
796
+ num:int
797
+ The number of collapses to do.
798
+ strong:bool
799
+ Whether to use strong collapses or standard collapses (slower, but may remove more edges)
800
+ full:bool
801
+ 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.
802
+ progress:bool
803
+ If true, shows the progress of the number of collapses.
804
+
805
+ WARNING
806
+ -------
807
+ - 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.
808
+ - This is for 1 critical simplices, with 2 parameter persistence.
809
+ Returns
810
+ -------
811
+ self:SimplexTreeMulti
812
+ A (smaller) simplex tree that has the same homology over this bifiltration.
813
+
814
+ """
815
+ # TODO : find a way to do multiple edge collapses without python conversions.
816
+ if num == 0:
817
+ return self
818
+ elif num == -1:
819
+ num=100
820
+ full=False
821
+ elif num == -2:
822
+ num=100
823
+ full=True
824
+ assert self.num_parameters == 2, "Number of parameters has to be 2 to use edge collapses ! This is a limitation of Filtration-domination"
825
+ if self.dimension > 1 and not ignore_warning: warn("This method ignores simplices of dimension > 1 !")
826
+
827
+ max_dimension = self.dimension if max_dimension is None else max_dimension
828
+
829
+ # Retrieves the edge list, and send it to filration_domination
830
+ edges = self.get_edge_list()
831
+ from multipers.multiparameter_edge_collapse import _collapse_edge_list
832
+ edges = _collapse_edge_list(edges, num=num, full=full, strong=strong, progress=progress)
833
+ # Retrieves the collapsed simplicial complex
834
+ self._reconstruct_from_edge_list(edges, swap=True, expand_dimension=max_dimension)
835
+ return self
836
+
837
+ def _reconstruct_from_edge_list(self, edges, swap:bool=True, expand_dimension:int=None)->SimplexTreeMulti:
838
+ """
839
+ Generates a 1-dimensional copy of self, with the edges given as input. Useful for edge collapses
840
+
841
+ Input
842
+ -----
843
+ - edges : Iterable[(int,int),(float,float)] ## This is the format of the rust library filtration-domination
844
+ - swap : bool
845
+ If true, will swap self and the collapsed simplextrees.
846
+ - expand_dim : int
847
+ expands back the simplextree to this dimension
848
+ Ouput
849
+ -----
850
+ The reduced SimplexTreeMulti having only these edges.
851
+ """
852
+ reduced_tree = SimplexTreeMulti(num_parameters=self.num_parameters)
853
+
854
+ ## Adds vertices back, with good filtration
855
+ if self.num_vertices > 0:
856
+ vertices = np.asarray([splx for splx, f in self.get_skeleton(0)], dtype=int).T
857
+ vertices_filtration = np.asarray([f for splx, f in self.get_skeleton(0)], dtype=np.float32)
858
+ reduced_tree.insert_batch(vertices, vertices_filtration)
859
+
860
+ ## Adds edges again
861
+ if self.num_simplices - self.num_vertices > 0:
862
+ edges_filtration = np.asarray([f for e,f in edges], dtype=np.float32)
863
+ edges = np.asarray([e for e, _ in edges], dtype=int).T
864
+ reduced_tree.insert_batch(edges, edges_filtration)
865
+ if swap:
866
+ # Swaps the simplextrees pointers
867
+ self.thisptr, reduced_tree.thisptr = reduced_tree.thisptr, self.thisptr # Swaps self and reduced tree (self is a local variable)
868
+ if expand_dimension is not None:
869
+ self.expansion(expand_dimension) # Expands back the simplextree to the original dimension.
870
+ return self if swap else reduced_tree
871
+
872
+ @property
873
+ def num_parameters(self)->int:
874
+ return self.get_ptr().get_number_of_parameters()
875
+ def get_simplices_of_dimension(self, dim:int)->np.ndarray:
876
+ return np.asarray(self.get_ptr().get_simplices_of_dimension(dim), dtype=int)
877
+ def key(self, simplex:list|np.ndarray):
878
+ return self.get_ptr().get_key(simplex)
879
+ def set_keys_to_enumerate(self)->None:
880
+ self.get_ptr().set_keys_to_enumerate()
881
+ return
882
+ def set_key(self,simplex:list|np.ndarray, key:int)->None:
883
+ self.get_ptr().set_key(simplex, key)
884
+ return
885
+
886
+
887
+ def __old__to_scc(self, path="scc_dataset.txt", progress:bool=True, overwrite:bool=False, ignore_last_generators:bool=True, strip_comments:bool=False, reverse_block:bool=True, rivet_compatible=False)->None:
888
+ """ Create a file with the scc2020 standard, representing the n-filtration of the simplextree.
889
+ Link : https://bitbucket.org/mkerber/chain_complex_format/src/master/
890
+
891
+ Parameters
892
+ ----------
893
+ path:str
894
+ path of the file.
895
+ ignore_last_generators:bool = True
896
+ If false, will include the filtration values of the last free persistence module.
897
+ progress:bool = True
898
+ Shows the progress bar.
899
+ overwrite:bool = False
900
+ If true, will overwrite the previous file if it already exists.
901
+ ignore_last_generators:bool=True
902
+ If true, does not write the final generators to the file. Rivet ignores them.
903
+ reverse_block:bool=True
904
+ Some obscure programs reverse the inside-block order.
905
+ rivet_compatible:bool=False
906
+ Returns a firep (old scc2020) format instead. Only Rivet uses this.
907
+
908
+ Returns
909
+ -------
910
+ Nothing
911
+ """
912
+ ### initialize keys
913
+ self.set_keys_to_enumerate()
914
+ ### File
915
+ from os.path import exists
916
+ from os import remove
917
+ if exists(path):
918
+ if not(overwrite):
919
+ raise Exception(f"The file {path} already exists. Use the `overwrite` flag if you want to overwrite.")
920
+ remove(path)
921
+ file = open(path, "a")
922
+ file.write("scc2020\n") if not rivet_compatible else file.write("firep\n")
923
+ if not strip_comments and not rivet_compatible: file.write("# Number of parameters\n")
924
+ num_parameters = self.get_ptr().get_number_of_parameters()
925
+ if rivet_compatible:
926
+ assert num_parameters == 2
927
+ file.write("Filtration 1\n")
928
+ file.write("Filtration 2\n")
929
+ else:
930
+ file.write(f"{num_parameters}\n")
931
+ if not strip_comments: file.write("# Sizes of generating sets\n")
932
+ ## WRITES TSR VARIABLES
933
+ tsr:list= [0]*(self.dimension+1) # dimension --- 0
934
+ for splx,f in self.get_simplices():
935
+ dim = len(splx)-1
936
+ tsr[dim] += (int)(len(f) // num_parameters)
937
+ tsr.reverse()
938
+ file.write(" ".join([str(n) for n in tsr])+"\n")
939
+
940
+ ## Adds the boundaries to the dictionnary + tsr
941
+ dict_splx_to_firep_number = {}
942
+ tsr:list = [[] for _ in range(len(tsr))] # tsr stores simplices vertices, according to dimension, and the dictionnary
943
+ for dim in range(self.dimension,-1 , -1): # range(2,-1,-1):
944
+ for splx,F in self.get_skeleton(dim):
945
+ if len(splx) != dim+1: continue
946
+ for b,_ in self.get_boundaries(splx):
947
+ if not self.key(b) in dict_splx_to_firep_number:
948
+ dict_splx_to_firep_number[self.key(b)] = len(tsr[dim-1])
949
+ tsr[dim-1].append(b)
950
+
951
+ ## Adds simplices that are not borders to tsr, i.e., simplices not in the dictionnary
952
+ for splx,_ in self.get_simplices():
953
+ if not self.key(splx) in dict_splx_to_firep_number:
954
+ tsr[len(splx)-1].append(splx)
955
+ ## Writes simplices of tsr to file
956
+ dim_range = range(self.dimension,0,-1) if ignore_last_generators else range(self.dimension,-1,-1)
957
+ for dim in dim_range: # writes block by block
958
+ if not strip_comments: file.write(f"# Block of dimension {dim}\n")
959
+ if reverse_block: tsr[dim].reverse()
960
+ for splx in tsr[dim]: # for simplices of dimension
961
+ F = np.concatenate(self.filtration(splx), axis=0)
962
+ nbirth = (int)(len(F)//num_parameters)
963
+ for i in range(nbirth):
964
+ simplex_filtration = F[i*num_parameters:(i+1)*num_parameters]
965
+ file.write(" ".join([str(f) for f in simplex_filtration]))
966
+ file.write(" ;")
967
+ for b,_ in self.get_boundaries(splx):
968
+ file.write(f" {dict_splx_to_firep_number[self.key(b)]}")
969
+ file.write("\n")
970
+ file.close()
971
+ return
972
+
973
+ def to_scc_kcritical(self,
974
+ path="scc_dataset.scc",
975
+ bool rivet_compatible=False,
976
+ bool strip_comments=False,
977
+ bool ignore_last_generators=False,
978
+ bool overwrite=False,
979
+ bool reverse_block=True,
980
+ ):
981
+ """
982
+ TODO: function-simplextree, from squeezed
983
+ """
984
+ from os.path import exists
985
+ from os import remove
986
+ if exists(path):
987
+ if not(overwrite):
988
+ raise Exception(f"The file {path} already exists. Use the `overwrite` flag if you want to overwrite.")
989
+ remove(path)
990
+ stuff = simplextree2scc(self)
991
+ if reverse_block: stuff.reverse()
992
+ with open(path, "w") as f:
993
+ f.write("scc2020\n") if not rivet_compatible else f.write("firep\n")
994
+ if not strip_comments and not rivet_compatible: f.write("# Number of parameters\n")
995
+ num_parameters = self.num_parameters
996
+ if rivet_compatible:
997
+ assert num_parameters == 2
998
+ f.write("Filtration 1\n")
999
+ f.write("Filtration 2\n")
1000
+ else:
1001
+ f.write(f"{num_parameters}\n")
1002
+
1003
+ if not strip_comments: f.write("# Sizes of generating sets\n")
1004
+ for block in stuff: f.write(f"{len(block[1])} ")
1005
+ f.write("\n")
1006
+
1007
+ for i,block in enumerate(stuff):
1008
+ if (rivet_compatible or ignore_last_generators) and i == len(stuff)-1: continue
1009
+ if not strip_comments: f.write(f"# Block of dimension {len(stuff)-i}\n")
1010
+ for boundary, filtration in zip(*block):
1011
+ line = " ".join([str(x) for x in filtration]) + " ; " + " ".join([str(x) for x in boundary]) +"\n"
1012
+ f.write(line)
1013
+ def to_scc_function_st(self,
1014
+ path="scc_dataset.scc",
1015
+ bool rivet_compatible=False,
1016
+ bool strip_comments=False,
1017
+ bool ignore_last_generators=False,
1018
+ bool overwrite=False,
1019
+ bool reverse_block=True,
1020
+ ):
1021
+ from warnings import warn
1022
+ warn("This function is not tested yet.")
1023
+ from os.path import exists
1024
+ from os import remove
1025
+ if exists(path):
1026
+ if not(overwrite):
1027
+ raise Exception(f"The file {path} already exists. Use the `overwrite` flag if you want to overwrite.")
1028
+ remove(path)
1029
+ stuff = simplextree2scc(self)
1030
+ if reverse_block: stuff.reverse()
1031
+ with open(path, "w") as f:
1032
+ f.write("scc2020\n") if not rivet_compatible else f.write("firep\n")
1033
+ if not strip_comments and not rivet_compatible: f.write("# Number of parameters\n")
1034
+ num_parameters = self.num_parameters
1035
+ if rivet_compatible:
1036
+ assert num_parameters == 2
1037
+ f.write("Filtration 1\n")
1038
+ f.write("Filtration 2\n")
1039
+ else:
1040
+ f.write(f"{num_parameters}\n")
1041
+
1042
+ if not strip_comments: f.write("# Sizes of generating sets\n")
1043
+ for block in stuff: f.write(f"{len(block[1])} ")
1044
+ f.write("\n")
1045
+
1046
+ for i,block in enumerate(stuff):
1047
+ if (rivet_compatible or ignore_last_generators) and i == len(stuff)-1: continue
1048
+ if not strip_comments: f.write(f"# Block of dimension {len(stuff)-i}\n")
1049
+ for boundary, filtration in zip(*block):
1050
+ line = " ".join([str(i)+" " + str(x) for i,x in enumerate(filtration)]) + " ; " + " ".join([str(x) for x in boundary]) +"\n"
1051
+ f.write(line)
1052
+ def to_scc(self,**kwargs):
1053
+ if self._is_function_simplextree:
1054
+ return self.to_scc_function_st(**kwargs)
1055
+ else:
1056
+ return self.to_scc_kcritical(**kwargs)
1057
+
1058
+ 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:
1059
+ """ Create a file that can be imported by rivet, representing the filtration of the simplextree.
1060
+
1061
+ Parameters
1062
+ ----------
1063
+ path:str
1064
+ path of the file.
1065
+ degree:int
1066
+ The homological degree to ask rivet to compute.
1067
+ progress:bool = True
1068
+ Shows the progress bar.
1069
+ overwrite:bool = False
1070
+ If true, will overwrite the previous file if it already exists.
1071
+ Returns
1072
+ -------
1073
+ Nothing
1074
+ """
1075
+ ...
1076
+ from os.path import exists
1077
+ from os import remove
1078
+ if exists(path):
1079
+ if not(overwrite):
1080
+ print(f"The file {path} already exists. Use the `overwrite` flag if you want to overwrite.")
1081
+ return
1082
+ remove(path)
1083
+ file = open(path, "a")
1084
+ file.write("# This file was generated by multipers.\n")
1085
+ file.write("--datatype bifiltration\n")
1086
+ file.write(f"--homology {degree}\n") if degree is not None else None
1087
+ file.write(f"-x {xbins}\n") if xbins is not None else None
1088
+ file.write(f"-y {ybins}\n") if ybins is not None else None
1089
+ file.write("--xlabel time of appearance\n")
1090
+ file.write("--ylabel density\n\n")
1091
+ from tqdm import tqdm
1092
+ with tqdm(total=self.num_simplices, position=0, disable = not(progress), desc="Writing simplex to file") as bar:
1093
+ for dim in range(0,self.dimension+1): # Not sure if dimension sort is necessary for rivet. Check ?
1094
+ file.write(f"# block of dimension {dim}\n")
1095
+ for s,F in self.get_skeleton(dim):
1096
+ if len(s) != dim+1: continue
1097
+ for i in s:
1098
+ file.write(str(i) + " ")
1099
+ file.write("; ")
1100
+ for f in F:
1101
+ file.write(str(f) + " ")
1102
+ file.write("\n")
1103
+ bar.update(1)
1104
+ file.close()
1105
+ return
1106
+
1107
+
1108
+
1109
+ def _get_filtration_values(self, vector[int] degrees, bool inf_to_nan:bool=False)->Iterable[np.ndarray]:
1110
+ # cdef vector[int] c_degrees = degrees
1111
+ cdef intptr_t ptr = self.thisptr
1112
+ cdef vector[vector[vector[value_type]]] out
1113
+ with nogil:
1114
+ out = get_filtration_values_from_ptr(ptr, degrees)
1115
+ filtrations_values = [np.asarray(filtration) for filtration in out]
1116
+ # Removes infs
1117
+ if inf_to_nan:
1118
+ for i,f in enumerate(filtrations_values):
1119
+ filtrations_values[i][f == np.inf] = np.nan
1120
+ filtrations_values[i][f == - np.inf] = np.nan
1121
+ return filtrations_values
1122
+
1123
+ @staticmethod
1124
+ def _reduce_grid(filtrations_values,resolutions=None, strategy:_available_strategies="exact", bool unique=True, some_float _q_factor=1., drop_quantiles=[0,0]):
1125
+ num_parameters = len(filtrations_values)
1126
+ if resolutions is None and strategy not in ["exact", "precomputed"]:
1127
+ raise ValueError("Resolutions must be provided for this strategy.")
1128
+ elif resolutions is not None:
1129
+ try:
1130
+ int(resolutions)
1131
+ resolutions = [resolutions]*num_parameters
1132
+ except:
1133
+ pass
1134
+ try:
1135
+ a,b=drop_quantiles
1136
+ except:
1137
+ a,b=drop_quantiles,drop_quantiles
1138
+
1139
+ if a != 0 or b != 0:
1140
+ boxes = np.asarray([np.quantile(filtration, [a, b], axis=1, method='closest_observation') for filtration in filtrations_values])
1141
+ min_filtration, max_filtration = np.min(boxes, axis=(0,1)), np.max(boxes, axis=(0,1)) # box, birth/death, filtration
1142
+ filtrations_values = [
1143
+ filtration[(m<filtration) * (filtration <M)]
1144
+ for filtration, m,M in zip(filtrations_values, min_filtration, max_filtration)
1145
+ ]
1146
+
1147
+ ## match doesn't work with cython BUG
1148
+ if strategy == "exact":
1149
+ to_unique = lambda f : np.unique(f) if isinstance(f,np.ndarray) else f.unique()
1150
+ F=[to_unique(f) for f in filtrations_values]
1151
+ elif strategy == "quantile":
1152
+ F = [f.unique() for f in filtrations_values]
1153
+ max_resolution = [min(len(f),r) for f,r in zip(F,resolutions)]
1154
+ F = [np.quantile(f, q=np.linspace(0,1,num=int(r*_q_factor)), axis=0, method='closest_observation') for f,r in zip(F, resolutions)]
1155
+ if unique:
1156
+ F = [np.unique(f) for f in F]
1157
+ if np.all(np.asarray(max_resolution) > np.asarray([len(f) for f in F])):
1158
+ return SimplexTreeMulti._reduce_grid(filtrations_values=filtrations_values, resolutions=resolutions, strategy="quantile",_q_factor=1.5*_q_factor)
1159
+ elif strategy == "regular":
1160
+ F = [np.linspace(f.min(),f.max(),num=r) for f,r in zip(filtrations_values, resolutions)]
1161
+ elif strategy == "regular_closest":
1162
+ F = [_todo_regular_closest(f,r, unique) for f,r in zip(filtrations_values, resolutions)]
1163
+ elif strategy == "torch_regular_closest":
1164
+ F = [_torch_regular_closest(f,r, unique) for f,r in zip(filtrations_values, resolutions)]
1165
+ elif strategy == "partition":
1166
+ F = [_todo_partition(f,r, unique) for f,r in zip(filtrations_values, resolutions)]
1167
+ elif strategy == "precomputed":
1168
+ F=filtrations_values
1169
+ else:
1170
+ raise Exception("Invalid strategy. Pick either regular, regular_closest, partition, quantile, precomputed or exact.")
1171
+
1172
+ return F
1173
+
1174
+ 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]:
1175
+ """
1176
+ Returns a grid over the n-filtration, from the simplextree. Usefull for grid_squeeze. TODO : multicritical
1177
+
1178
+ Parameters
1179
+ ----------
1180
+ resolution: list[int]
1181
+ resolution of the grid, for each parameter
1182
+ box=None : pair[list[float]]
1183
+ Grid bounds. format : [low bound, high bound]
1184
+ If None is given, will use the filtration bounds of the simplextree.
1185
+ grid_strategy="regular" : string
1186
+ Either "regular", "quantile", or "exact".
1187
+ Returns
1188
+ -------
1189
+ List of filtration values, for each parameter, defining the grid.
1190
+ """
1191
+ if degrees is None:
1192
+ degrees = range(self.dimension+1)
1193
+
1194
+
1195
+ ## preprocesses the filtration values:
1196
+ filtrations_values = np.concatenate(self._get_filtration_values(degrees, inf_to_nan=True), axis=1)
1197
+ # removes duplicate + sort (nan at the end)
1198
+ filtrations_values = [np.unique(filtration) for filtration in filtrations_values]
1199
+ # removes nan
1200
+ filtrations_values = [filtration[:-1] if np.isnan(filtration[-1]) else filtration for filtration in filtrations_values]
1201
+
1202
+ return self._reduce_grid(filtrations_values=filtrations_values, resolutions=resolution,strategy=grid_strategy,drop_quantiles=drop_quantiles)
1203
+
1204
+
1205
+
1206
+ def grid_squeeze(self, filtration_grid:np.ndarray|list|None=None, bool coordinate_values=True, force=False, grid_strategy:_available_strategies = "exact", **filtration_grid_kwargs)->SimplexTreeMulti:
1207
+ """
1208
+ Fit the filtration of the simplextree to a grid.
1209
+
1210
+ :param filtration_grid: The grid on which to squeeze. An example of grid can be given by the `get_filtration_grid` method.
1211
+ :type filtration_grid: list[list[float]]
1212
+ :param coordinate_values: If true, the filtrations values of the simplices will be set to the coordinate of the filtration grid.
1213
+ :type coordinate_values: bool
1214
+ """
1215
+ if not force and self._is_squeezed:
1216
+ raise Exception("SimplexTree already squeezed, use `force=True` if that's really what you want to do.")
1217
+ #TODO : multi-critical
1218
+ if filtration_grid is None:
1219
+ filtration_grid = self.get_filtration_grid(grid_strategy=grid_strategy, **filtration_grid_kwargs)
1220
+ cdef vector[vector[value_type]] c_filtration_grid = filtration_grid
1221
+ cdef intptr_t ptr = self.thisptr
1222
+ if coordinate_values:
1223
+ self.filtration_grid = filtration_grid
1224
+ with nogil:
1225
+ squeeze_filtration_from_ptr(ptr, c_filtration_grid, coordinate_values)
1226
+ return self
1227
+
1228
+ @property
1229
+ def _is_squeezed(self)->bool:
1230
+ return self.num_vertices > 0 and len(self.filtration_grid[0]) > 0
1231
+
1232
+ def filtration_bounds(self, degrees:Iterable[int]|None=None, q:float|tuple=0, split_dimension:bool=False)->np.ndarray:
1233
+ """
1234
+ Returns the filtrations bounds of the finite filtration values.
1235
+ """
1236
+ try:
1237
+ a,b =q
1238
+ except:
1239
+ a,b,=q,q
1240
+ degrees = range(self.dimension+1) if degrees is None else degrees
1241
+ filtrations_values = self._get_filtration_values(degrees, inf_to_nan=True) ## degree, parameter, pt
1242
+ boxes = np.array([np.nanquantile(filtration, [a, 1-b], axis=1) for filtration in filtrations_values],dtype=float)
1243
+ if split_dimension: return boxes
1244
+ return np.asarray([np.nanmin(boxes, axis=(0,1)), np.nanmax(boxes, axis=(0,1))]) # box, birth/death, filtration
1245
+
1246
+
1247
+
1248
+
1249
+ def fill_lowerstar(self, vector[value_type] F, int parameter)->SimplexTreeMulti:
1250
+ """ Fills the `dimension`th filtration by the lower-star filtration defined by F.
1251
+
1252
+ Parameters
1253
+ ----------
1254
+ F:1d array
1255
+ The density over the vertices, that induces a lowerstar filtration.
1256
+ parameter:int
1257
+ Which filtration parameter to fill. /!\ python starts at 0.
1258
+
1259
+ Returns
1260
+ -------
1261
+ self:SimplexTreeMulti
1262
+ """
1263
+ # for s, sf in self.get_simplices():
1264
+ # self.assign_filtration(s, [f if i != dimension else np.max(np.array(F)[s]) for i,f in enumerate(sf)])
1265
+ # cdef int c_parameter = parameter
1266
+ # cdef vector[value_type] c_F = np.asarray(F, dtype=np.float32)
1267
+ with nogil:
1268
+ self.get_ptr().fill_lowerstar(F, parameter)
1269
+ return self
1270
+
1271
+
1272
+ def project_on_line(self, parameter:int=0, basepoint:None|list|np.ndarray= None)->SimplexTree:
1273
+ """Converts an multi simplextree to a gudhi simplextree.
1274
+ Parameters
1275
+ ----------
1276
+ parameter:int = 0
1277
+ The parameter to keep. WARNING will crash if the multi simplextree is not well filled.
1278
+ basepoint:None
1279
+ Instead of keeping a single parameter, will consider the filtration defined by the diagonal line crossing the basepoint.
1280
+ WARNING
1281
+ -------
1282
+ There are no safeguard yet, it WILL crash if asking for a parameter that is not filled.
1283
+ Returns
1284
+ -------
1285
+ A SimplexTree with chosen 1D filtration.
1286
+ """
1287
+ # FIXME : deal with multicritical filtrations
1288
+ import gudhi as gd
1289
+ new_simplextree = gd.SimplexTree()
1290
+ assert parameter < self.get_ptr().get_number_of_parameters()
1291
+ cdef int c_parameter = parameter
1292
+ cdef intptr_t old_ptr = self.thisptr
1293
+ cdef intptr_t new_ptr = new_simplextree.thisptr
1294
+ cdef vector[value_type] c_basepoint = [] if basepoint is None else basepoint
1295
+ if basepoint is None:
1296
+ with nogil:
1297
+ flatten_from_ptr(old_ptr, new_ptr, c_parameter)
1298
+ else:
1299
+ with nogil:
1300
+ flatten_diag_from_ptr(old_ptr, new_ptr, c_basepoint, c_parameter)
1301
+ return new_simplextree
1302
+
1303
+ def linear_projections(self, linear_forms:np.ndarray)->Iterable[SimplexTree]:
1304
+ """
1305
+ Compute the 1-parameter projections, w.r.t. given the linear forms, of this simplextree.
1306
+
1307
+ Input
1308
+ -----
1309
+ - Array of shape (num_linear_forms, num_parameters)
1310
+
1311
+ Output
1312
+ ------
1313
+ - List of projected (gudhi) simplextrees.
1314
+ """
1315
+ cdef Py_ssize_t num_projections = linear_forms.shape[0]
1316
+ cdef Py_ssize_t num_parameters = linear_forms.shape[1]
1317
+ if num_projections == 0: return []
1318
+ cdef vector[vector[value_type]] c_linear_forms = linear_forms
1319
+ assert num_parameters==self.num_parameters, f"The linear forms has to have the same number of parameter as the simplextree ({self.num_parameters})."
1320
+
1321
+ # Gudhi copies are faster than inserting simplices one by one
1322
+ import gudhi as gd
1323
+ flattened_simplextree = gd.SimplexTree()
1324
+ cdef intptr_t multi_prt = self.thisptr
1325
+ cdef intptr_t flattened_ptr = flattened_simplextree.thisptr
1326
+ with nogil:
1327
+ flatten_from_ptr(multi_prt, flattened_ptr, num_parameters)
1328
+ out = [flattened_simplextree] + [gd.SimplexTree(flattened_simplextree) for _ in range(num_projections-1)]
1329
+
1330
+ # Fills the 1-parameter simplextrees.
1331
+ cdef vector[intptr_t] out_ptrs = [st.thisptr for st in out]
1332
+ with nogil:
1333
+ for i in range(num_projections):
1334
+ linear_projection_from_ptr(out_ptrs[i], multi_prt, c_linear_forms[i])
1335
+ return out
1336
+
1337
+
1338
+ def set_num_parameter(self, num:int):
1339
+ """
1340
+ Sets the numbers of parameters.
1341
+ WARNING : it will resize all the filtrations to this size.
1342
+ """
1343
+ self.get_ptr().resize_all_filtrations(num)
1344
+ self.get_ptr().set_number_of_parameters(num)
1345
+ return
1346
+
1347
+ def __eq__(self, other:SimplexTreeMulti):
1348
+ """Test for structural equality
1349
+ :returns: True if the 2 simplex trees are equal, False otherwise.
1350
+ :rtype: bool
1351
+ """
1352
+ return dereference(self.get_ptr()) == dereference(other.get_ptr())
1353
+
1354
+ cdef intptr_t _get_copy_intptr(SimplexTreeMulti stree) nogil:
1355
+ return <intptr_t>(new Simplex_tree_multi_interface(dereference(stree.get_ptr())))
1356
+
1357
+
1358
+ def _todo_regular_closest(cnp.ndarray[some_float,ndim=1] f, int r, bool unique):
1359
+ f_regular = np.linspace(f.min(),f.max(),num=r)
1360
+ f_regular_closest = np.asarray([f[np.argmin(np.abs(f-x))] for x in f_regular])
1361
+ if unique: f_regular_closest = np.unique(f_regular_closest)
1362
+ return f_regular_closest
1363
+
1364
+ def _torch_regular_closest(f, int r, bool unique=True):
1365
+ import torch
1366
+ f_regular = torch.linspace(f.min(),f.max(), r)
1367
+ f_regular_closest =torch.tensor([f[(f-x).abs().argmin()] for x in f_regular])
1368
+ if unique: f_regular_closest = f_regular_closest.unique()
1369
+ return f_regular_closest
1370
+
1371
+ def _todo_partition(cnp.ndarray[some_float,ndim=1] data,int resolution, bool unique):
1372
+ if data.shape[0] < resolution: resolution=data.shape[0]
1373
+ k = data.shape[0] // resolution
1374
+ partitions = np.partition(data, k)
1375
+ f = partitions[[i*k for i in range(resolution)]]
1376
+ if unique: f= np.unique(f)
1377
+ return f
1378
+
1379
+
1380
+
1381
+ def _simplextree_multify(simplextree:SimplexTree, int num_parameters, default_values=[])->SimplexTreeMulti:
1382
+ """Converts a gudhi simplextree to a multi simplextree.
1383
+ Parameters
1384
+ ----------
1385
+ parameters:int = 2
1386
+ The number of filtrations
1387
+ Returns
1388
+ -------
1389
+ A multi simplextree, with first filtration value being the one from the original simplextree.
1390
+ """
1391
+ if isinstance(simplextree, SimplexTreeMulti):
1392
+ return simplextree
1393
+ st = SimplexTreeMulti(num_parameters=num_parameters)
1394
+ cdef intptr_t old_ptr = simplextree.thisptr
1395
+ cdef intptr_t new_ptr = st.thisptr
1396
+ cdef vector[value_type] c_default_values=default_values
1397
+ with nogil:
1398
+ multify_from_ptr(old_ptr, new_ptr, num_parameters, c_default_values)
1399
+ return st
1400
+
1401
+ def _safe_simplextree_multify(simplextree:SimplexTree,int num_parameters=2, cnp.ndarray default_values=np.array(-np.inf))->SimplexTreeMulti:
1402
+ if isinstance(simplextree, SimplexTreeMulti):
1403
+ return simplextree
1404
+ simplices = [[] for _ in range(simplextree.dimension()+1)]
1405
+ filtration_values = [[] for _ in range(simplextree.dimension()+1)]
1406
+ st_multi = SimplexTreeMulti(num_parameters=1)
1407
+ if num_parameters > 1:
1408
+ st_multi.set_num_parameter(num_parameters)
1409
+ if default_values.squeeze().ndim == 0:
1410
+ default_values = np.zeros(num_parameters-1) + default_values
1411
+
1412
+ for s,f in simplextree.get_simplices():
1413
+ filtration_values[len(s)-1].append(np.concatenate([[f],default_values]))
1414
+ simplices[len(s)-1].append(s)
1415
+ for batch_simplices, batch_filtrations in zip(simplices,filtration_values):
1416
+ st_multi.insert_batch(np.asarray(batch_simplices).T, np.asarray(batch_filtrations))
1417
+ return st_multi