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