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