multipers 1.0__cp311-cp311-manylinux_2_34_x86_64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of multipers might be problematic. Click here for more details.
- multipers/__init__.py +4 -0
- multipers/_old_rank_invariant.pyx +328 -0
- multipers/_signed_measure_meta.py +72 -0
- multipers/data/MOL2.py +350 -0
- multipers/data/UCR.py +18 -0
- multipers/data/__init__.py +1 -0
- multipers/data/graphs.py +272 -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 +100 -0
- multipers/euler_characteristic.cpython-311-x86_64-linux-gnu.so +0 -0
- multipers/euler_characteristic.pyx +132 -0
- multipers/function_rips.cpython-311-x86_64-linux-gnu.so +0 -0
- multipers/function_rips.pyx +101 -0
- multipers/hilbert_function.cpython-311-x86_64-linux-gnu.so +0 -0
- multipers/hilbert_function.pyi +46 -0
- multipers/hilbert_function.pyx +145 -0
- multipers/ml/__init__.py +0 -0
- multipers/ml/accuracies.py +61 -0
- multipers/ml/convolutions.py +384 -0
- multipers/ml/invariants_with_persistable.py +79 -0
- multipers/ml/kernels.py +128 -0
- multipers/ml/mma.py +422 -0
- multipers/ml/one.py +472 -0
- multipers/ml/point_clouds.py +191 -0
- multipers/ml/signed_betti.py +50 -0
- multipers/ml/signed_measures.py +1046 -0
- multipers/ml/sliced_wasserstein.py +313 -0
- multipers/ml/tools.py +99 -0
- multipers/multiparameter_edge_collapse.py +29 -0
- multipers/multiparameter_module_approximation.cpython-311-x86_64-linux-gnu.so +0 -0
- multipers/multiparameter_module_approximation.pxd +147 -0
- multipers/multiparameter_module_approximation.pyi +439 -0
- multipers/multiparameter_module_approximation.pyx +931 -0
- multipers/pickle.py +53 -0
- multipers/plots.py +207 -0
- multipers/point_measure_integration.cpython-311-x86_64-linux-gnu.so +0 -0
- multipers/point_measure_integration.pyx +59 -0
- multipers/rank_invariant.cpython-311-x86_64-linux-gnu.so +0 -0
- multipers/rank_invariant.pyx +154 -0
- multipers/simplex_tree_multi.cpython-311-x86_64-linux-gnu.so +0 -0
- multipers/simplex_tree_multi.pxd +121 -0
- multipers/simplex_tree_multi.pyi +715 -0
- multipers/simplex_tree_multi.pyx +1284 -0
- multipers/tensor.pxd +13 -0
- multipers/test.pyx +44 -0
- multipers-1.0.dist-info/LICENSE +21 -0
- multipers-1.0.dist-info/METADATA +9 -0
- multipers-1.0.dist-info/RECORD +56 -0
- multipers-1.0.dist-info/WHEEL +5 -0
- multipers-1.0.dist-info/top_level.txt +1 -0
- multipers.libs/libtbb-5d1cde94.so.12.10 +0 -0
- multipers.libs/libtbbmalloc-5e0a3d4c.so.2.10 +0 -0
|
@@ -0,0 +1,931 @@
|
|
|
1
|
+
"""!
|
|
2
|
+
@package mma
|
|
3
|
+
@brief Files containing the C++ cythonized functions.
|
|
4
|
+
@author David Loiseaux
|
|
5
|
+
@copyright Copyright (c) 2022 Inria.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
# distutils: language = c++
|
|
9
|
+
|
|
10
|
+
###########################################################################
|
|
11
|
+
## PYTHON LIBRARIES
|
|
12
|
+
import gudhi as gd
|
|
13
|
+
import numpy as np
|
|
14
|
+
from typing import List
|
|
15
|
+
import pickle as pk
|
|
16
|
+
import matplotlib.pyplot as plt
|
|
17
|
+
|
|
18
|
+
###########################################################################
|
|
19
|
+
## CPP CLASSES
|
|
20
|
+
from libc.stdint cimport intptr_t
|
|
21
|
+
from libc.stdint cimport uintptr_t
|
|
22
|
+
|
|
23
|
+
###########################################################################
|
|
24
|
+
## CYTHON TYPES
|
|
25
|
+
from libcpp.vector cimport vector
|
|
26
|
+
from libcpp.utility cimport pair
|
|
27
|
+
#from libcpp.list cimport list as clist
|
|
28
|
+
from libcpp cimport bool
|
|
29
|
+
from libcpp cimport int
|
|
30
|
+
from typing import Iterable
|
|
31
|
+
|
|
32
|
+
#########################################################################
|
|
33
|
+
## Multipersistence Module Approximation Classes
|
|
34
|
+
from multipers.multiparameter_module_approximation cimport *
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
#########################################################################
|
|
39
|
+
## Small hack for typing
|
|
40
|
+
from gudhi import SimplexTree
|
|
41
|
+
from multipers.simplex_tree_multi import SimplexTreeMulti
|
|
42
|
+
# cimport numpy as cnp
|
|
43
|
+
# cnp.import_array()
|
|
44
|
+
|
|
45
|
+
###################################### MMA
|
|
46
|
+
cdef extern from "multiparameter_module_approximation/approximation.h" namespace "Gudhi::multiparameter::mma":
|
|
47
|
+
Module compute_vineyard_barcode_approximation(boundary_matrix, vector[Finitely_critical_multi_filtration] , value_type precision, Box[value_type] &, bool threshold, bool complete, bool multithread, bool verbose) nogil
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
cdef class PySummand:
|
|
51
|
+
"""
|
|
52
|
+
Stores a Summand of a PyModule
|
|
53
|
+
"""
|
|
54
|
+
cdef Summand sum
|
|
55
|
+
# def __cinit__(self, vector[corner_type]& births, vector[corner_type]& deaths, int dim):
|
|
56
|
+
# self.sum = Summand(births, deaths, dim)
|
|
57
|
+
|
|
58
|
+
def get_birth_list(self):
|
|
59
|
+
return np.asarray(Finitely_critical_multi_filtration.to_python(self.sum.get_birth_list()))
|
|
60
|
+
|
|
61
|
+
def get_death_list(self):
|
|
62
|
+
return np.asarray(Finitely_critical_multi_filtration.to_python(self.sum.get_death_list()))
|
|
63
|
+
@property
|
|
64
|
+
def degree(self)->int:
|
|
65
|
+
return self.sum.get_dimension()
|
|
66
|
+
|
|
67
|
+
cdef set(self, Summand& summand):
|
|
68
|
+
self.sum = summand
|
|
69
|
+
return self
|
|
70
|
+
def get_bounds(self):
|
|
71
|
+
cdef pair[Finitely_critical_multi_filtration,Finitely_critical_multi_filtration] cbounds
|
|
72
|
+
with nogil:
|
|
73
|
+
cbounds = self.sum.get_bounds().get_pair()
|
|
74
|
+
# return np.array(<value_type[:self.num_parameters]>(&cbounds.first[0])),np.array(<value_type[:self.num_parameters]>(&cbounds.second[0]))
|
|
75
|
+
return np.asarray(cbounds.first._convert_back()), np.asarray(cbounds.second._convert_back())
|
|
76
|
+
|
|
77
|
+
cdef get_summand_filtration_values(Summand summand):
|
|
78
|
+
births = np.asarray(Finitely_critical_multi_filtration.to_python(summand.get_birth_list()))
|
|
79
|
+
deaths = np.asarray(Finitely_critical_multi_filtration.to_python(summand.get_death_list()))
|
|
80
|
+
pts = np.concatenate([births,deaths],axis=0)
|
|
81
|
+
num_parameters = pts.shape[1]
|
|
82
|
+
out = [np.unique(pts[:,parameter]) for parameter in range(num_parameters)]
|
|
83
|
+
out = [f[:-1] if f[-1] == np.inf else f for f in out]
|
|
84
|
+
out = [f[1:] if f[0] == -np.inf else f for f in out]
|
|
85
|
+
return out
|
|
86
|
+
|
|
87
|
+
cdef class PyBox:
|
|
88
|
+
cdef Box[value_type] box
|
|
89
|
+
def __cinit__(self, corner_type bottomCorner, corner_type topCorner):
|
|
90
|
+
self.box = Box[value_type](bottomCorner, topCorner)
|
|
91
|
+
@property
|
|
92
|
+
def num_parameters(self):
|
|
93
|
+
cdef size_t dim = self.box.get_bottom_corner().size()
|
|
94
|
+
if dim == self.box.get_upper_corner().size(): return dim
|
|
95
|
+
else: print("Bad box definition.")
|
|
96
|
+
def contains(self, x):
|
|
97
|
+
return self.box.contains(x)
|
|
98
|
+
cdef set(self, Box[value_type]& b):
|
|
99
|
+
self.box = b
|
|
100
|
+
return self
|
|
101
|
+
|
|
102
|
+
def get(self):
|
|
103
|
+
return [self.box.get_bottom_corner().get_vector(),self.box.get_upper_corner().get_vector()]
|
|
104
|
+
def to_multipers(self):
|
|
105
|
+
#assert (self.get_dimension() == 2) "Multipers only works in dimension 2 !"
|
|
106
|
+
return np.array(self.get()).flatten(order = 'F')
|
|
107
|
+
|
|
108
|
+
cdef class PyMultiDiagramPoint:
|
|
109
|
+
cdef MultiDiagram_point point
|
|
110
|
+
cdef set(self, MultiDiagram_point &pt):
|
|
111
|
+
self.point = pt
|
|
112
|
+
return self
|
|
113
|
+
|
|
114
|
+
def get_degree(self):
|
|
115
|
+
return self.point.get_dimension()
|
|
116
|
+
def get_birth(self):
|
|
117
|
+
return self.point.get_birth()
|
|
118
|
+
def get_death(self):
|
|
119
|
+
return self.point.get_death()
|
|
120
|
+
|
|
121
|
+
cdef class PyMultiDiagram:
|
|
122
|
+
"""
|
|
123
|
+
Stores the diagram of a PyModule on a line
|
|
124
|
+
"""
|
|
125
|
+
cdef MultiDiagram multiDiagram
|
|
126
|
+
cdef set(self, MultiDiagram m):
|
|
127
|
+
self.multiDiagram = m
|
|
128
|
+
return self
|
|
129
|
+
def get_points(self, degree:int=-1) -> np.ndarray:
|
|
130
|
+
out = self.multiDiagram.get_points(degree)
|
|
131
|
+
if len(out) == 0 and len(self) == 0:
|
|
132
|
+
return np.empty() # TODO Retrieve good number of parameters if there is no points in diagram
|
|
133
|
+
if len(out) == 0:
|
|
134
|
+
return np.empty((0,2,self.multiDiagram.at(0).get_dimension())) # gets the number of parameters
|
|
135
|
+
return np.array(out)
|
|
136
|
+
def to_multipers(self, dimension:int):
|
|
137
|
+
return self.multiDiagram.to_multipers(dimension)
|
|
138
|
+
def __len__(self) -> int:
|
|
139
|
+
return self.multiDiagram.size()
|
|
140
|
+
def __getitem__(self,i:int) -> PyMultiDiagramPoint:
|
|
141
|
+
return PyMultiDiagramPoint().set(self.multiDiagram.at(i % self.multiDiagram.size()))
|
|
142
|
+
cdef class PyMultiDiagrams:
|
|
143
|
+
"""
|
|
144
|
+
Stores the barcodes of a PyModule on multiple lines
|
|
145
|
+
"""
|
|
146
|
+
cdef MultiDiagrams multiDiagrams
|
|
147
|
+
cdef set(self,MultiDiagrams m):
|
|
148
|
+
self.multiDiagrams = m
|
|
149
|
+
return self
|
|
150
|
+
def to_multipers(self):
|
|
151
|
+
out = self.multiDiagrams.to_multipers()
|
|
152
|
+
# return out
|
|
153
|
+
return [np.array(summand, dtype=np.float64) for summand in out]
|
|
154
|
+
def __getitem__(self,i:int):
|
|
155
|
+
if i >=0 :
|
|
156
|
+
return PyMultiDiagram().set(self.multiDiagrams.at(i))
|
|
157
|
+
else:
|
|
158
|
+
return PyMultiDiagram().set(self.multiDiagrams.at( self.multiDiagrams.size() - i))
|
|
159
|
+
def __len__(self):
|
|
160
|
+
return self.multiDiagrams.size()
|
|
161
|
+
def get_points(self, degree:int=-1):
|
|
162
|
+
return self.multiDiagrams.get_points()
|
|
163
|
+
# return np.array([x.get_points(dimension) for x in self.multiDiagrams], dtype=float)
|
|
164
|
+
# return np.array([PyMultiDiagram().set(x).get_points(dimension) for x in self.multiDiagrams])
|
|
165
|
+
cdef _get_plot_bars(self, dimension:int=-1, min_persistence:float=0):
|
|
166
|
+
return self.multiDiagrams._for_python_plot(dimension, min_persistence);
|
|
167
|
+
def plot(self, degree:int=-1, min_persistence:float=0):
|
|
168
|
+
"""
|
|
169
|
+
Plots the barcodes.
|
|
170
|
+
|
|
171
|
+
Parameters
|
|
172
|
+
----------
|
|
173
|
+
degree:int=-1
|
|
174
|
+
Only plots the bars of specified homology degree. Useful when the multidiagrams contains multiple dimenions
|
|
175
|
+
min_persistence:float=0
|
|
176
|
+
Only plot bars of length greater than this value. Useful to reduce the time to plot.
|
|
177
|
+
|
|
178
|
+
Warning
|
|
179
|
+
-------
|
|
180
|
+
If the barcodes are not thresholded, essential barcodes will not be displayed !
|
|
181
|
+
|
|
182
|
+
Returns
|
|
183
|
+
-------
|
|
184
|
+
Nothing
|
|
185
|
+
"""
|
|
186
|
+
from cycler import cycler
|
|
187
|
+
import matplotlib
|
|
188
|
+
import matplotlib.pyplot as plt
|
|
189
|
+
if len(self) == 0: return
|
|
190
|
+
_cmap = matplotlib.colormaps["Spectral"]
|
|
191
|
+
multibarcodes_, colors = self._get_plot_bars(degree, min_persistence)
|
|
192
|
+
n_summands = np.max(colors)+1 if len(colors)>0 else 1
|
|
193
|
+
|
|
194
|
+
plt.rc('axes', prop_cycle = cycler('color', [_cmap(i/n_summands) for i in colors]))
|
|
195
|
+
plt.plot(*multibarcodes_)
|
|
196
|
+
cdef class PyLine:
|
|
197
|
+
cdef Line[value_type] line
|
|
198
|
+
|
|
199
|
+
cdef class PyModule:
|
|
200
|
+
"""
|
|
201
|
+
Stores a representation of a n-persistence module.
|
|
202
|
+
"""
|
|
203
|
+
cdef Module cmod
|
|
204
|
+
|
|
205
|
+
cdef set(self, Module m):
|
|
206
|
+
self.cmod = m
|
|
207
|
+
def set_box(self, PyBox pybox):
|
|
208
|
+
cdef Box[value_type] cbox = pybox.box
|
|
209
|
+
with nogil:
|
|
210
|
+
self.cmod.set_box(cbox)
|
|
211
|
+
return self
|
|
212
|
+
def get_module_of_degree(self, int degree)->PyModule: # TODO : in c++ ?
|
|
213
|
+
pmodule = PyModule()
|
|
214
|
+
cdef Box[value_type] c_box = self.cmod.get_box()
|
|
215
|
+
pmodule.cmod.set_box(c_box)
|
|
216
|
+
with nogil:
|
|
217
|
+
for summand in self.cmod:
|
|
218
|
+
if summand.get_dimension() == degree:
|
|
219
|
+
pmodule.cmod.add_summand(summand)
|
|
220
|
+
return pmodule
|
|
221
|
+
def get_module_of_degrees(self, degrees:Iterable[int])->PyModule: # TODO : in c++ ?
|
|
222
|
+
pmodule = PyModule()
|
|
223
|
+
cdef Box[value_type] c_box = self.cmod.get_box()
|
|
224
|
+
pmodule.cmod.set_box(c_box)
|
|
225
|
+
cdef vector[int] cdegrees = degrees
|
|
226
|
+
with nogil:
|
|
227
|
+
for summand in self.cmod:
|
|
228
|
+
for d in cdegrees:
|
|
229
|
+
if d == summand.get_dimension():
|
|
230
|
+
pmodule.cmod.add_summand(summand)
|
|
231
|
+
return pmodule
|
|
232
|
+
def _compute_pixels(self,coordinates:np.ndarray,
|
|
233
|
+
degrees=None, box=None, value_type delta=.1,
|
|
234
|
+
value_type p=1., bool normalize=False, int n_jobs=0):
|
|
235
|
+
if degrees is not None: assert np.all(degrees[:-1] <= degrees[1:]), "Degrees have to be sorted"
|
|
236
|
+
cdef vector[int] cdegrees = np.arange(self.max_degree +1) if degrees is None else degrees
|
|
237
|
+
pybox = PyBox(*self.get_box()) if box is None else PyBox(*box)
|
|
238
|
+
cdef Box[value_type] cbox = pybox.box
|
|
239
|
+
cdef vector[vector[value_type]] ccoords = coordinates
|
|
240
|
+
cdef vector[vector[value_type]] out
|
|
241
|
+
with nogil:
|
|
242
|
+
out = self.cmod.compute_pixels(ccoords, cdegrees, cbox, delta, p, normalize, n_jobs)
|
|
243
|
+
return np.asarray(out)
|
|
244
|
+
def __len__(self)->int:
|
|
245
|
+
return self.cmod.size()
|
|
246
|
+
def get_bottom(self)->list:
|
|
247
|
+
return self.cmod.get_box().get_bottom_corner().get_vector()
|
|
248
|
+
def get_top(self)->list:
|
|
249
|
+
return self.cmod.get_box().get_upper_corner().get_vector()
|
|
250
|
+
def get_box(self):
|
|
251
|
+
return np.asarray([self.get_bottom(), self.get_top()])
|
|
252
|
+
@property
|
|
253
|
+
def max_degree(self)->int:
|
|
254
|
+
return self.cmod.get_dimension()
|
|
255
|
+
@property
|
|
256
|
+
def num_parameters(self)->int:
|
|
257
|
+
cdef size_t dim = self.cmod.get_box().get_bottom_corner().size()
|
|
258
|
+
assert dim == self.cmod.get_box().get_upper_corner().size(), "Bad box definition, cannot infer num_parameters."
|
|
259
|
+
return dim
|
|
260
|
+
def dump(self, path:str|None=None):
|
|
261
|
+
"""
|
|
262
|
+
Dumps the module into a pickle-able format.
|
|
263
|
+
|
|
264
|
+
Parameters
|
|
265
|
+
----------
|
|
266
|
+
path:str=None (optional) saves the pickled module in specified path
|
|
267
|
+
|
|
268
|
+
Returns
|
|
269
|
+
-------
|
|
270
|
+
list of list, encoding the module, which can be retrieved with the function `from_dump`.
|
|
271
|
+
"""
|
|
272
|
+
## TODO : optimize, but not really used.
|
|
273
|
+
return dump_cmod(self.cmod)
|
|
274
|
+
def __getstate__(self):
|
|
275
|
+
return self.dump()
|
|
276
|
+
def __setstate__(self,dump):
|
|
277
|
+
cdef Module cmod = cmod_from_dump(dump)
|
|
278
|
+
self.cmod = cmod
|
|
279
|
+
return
|
|
280
|
+
def __getitem__(self, i) -> PySummand:
|
|
281
|
+
if i == slice(None):
|
|
282
|
+
return self
|
|
283
|
+
summand = PySummand()
|
|
284
|
+
summand.set(self.cmod.at(i % self.cmod.size()))
|
|
285
|
+
return summand
|
|
286
|
+
def get_bounds(self):
|
|
287
|
+
cdef pair[Finitely_critical_multi_filtration,Finitely_critical_multi_filtration] cbounds
|
|
288
|
+
with nogil:
|
|
289
|
+
cbounds = self.cmod.get_bounds().get_pair()
|
|
290
|
+
# return np.array(<value_type[:self.num_parameters]>(&cbounds.first[0])),np.array(<value_type[:self.num_parameters]>(&cbounds.second[0]))
|
|
291
|
+
return np.asarray(cbounds.first._convert_back()), np.asarray(cbounds.second._convert_back())
|
|
292
|
+
def rescale(self,rescale_factors, int degree=-1):
|
|
293
|
+
"""
|
|
294
|
+
Rescales the fitlration values of the summands by this rescaling vector.
|
|
295
|
+
"""
|
|
296
|
+
cdef vector[value_type] crescale_factors = rescale_factors
|
|
297
|
+
with nogil:
|
|
298
|
+
self.cmod.rescale(crescale_factors,degree)
|
|
299
|
+
def translate(self,translation, int degree=-1):
|
|
300
|
+
"""
|
|
301
|
+
Translates the module in the filtration space by this vector.
|
|
302
|
+
"""
|
|
303
|
+
cdef vector[value_type] ctranslation = translation
|
|
304
|
+
with nogil:
|
|
305
|
+
self.cmod.translate(ctranslation,degree)
|
|
306
|
+
|
|
307
|
+
def get_filtration_values(self, unique=True):
|
|
308
|
+
"""
|
|
309
|
+
Retrieves all filtration values of the summands of the module.
|
|
310
|
+
|
|
311
|
+
Output format
|
|
312
|
+
-------------
|
|
313
|
+
|
|
314
|
+
list of filtration values for parameter.
|
|
315
|
+
"""
|
|
316
|
+
if len(self) ==0:
|
|
317
|
+
return np.empty((self.num_parameters,0))
|
|
318
|
+
values = [get_summand_filtration_values(summand) for summand in self.cmod]
|
|
319
|
+
values = [np.concatenate([f[parameter] for f in values], axis=0) for parameter in range(self.num_parameters)]
|
|
320
|
+
if unique:
|
|
321
|
+
return [np.unique(f) for f in values]
|
|
322
|
+
return values
|
|
323
|
+
|
|
324
|
+
def plot(self, degree:int=-1,**kwargs)->None:
|
|
325
|
+
"""Shows the module on a plot. Each color corresponds to an apprimation summand of the module, and its shape corresponds to its support.
|
|
326
|
+
Only works with 2-parameter modules.
|
|
327
|
+
|
|
328
|
+
Parameters
|
|
329
|
+
----------
|
|
330
|
+
degree = -1 : integer
|
|
331
|
+
If positive returns only the image of dimension `dimension`.
|
|
332
|
+
box=None : of the form [[b_x,b_y], [d_x,d_y]] where b,d are the bottom and top corner of the rectangle.
|
|
333
|
+
If non-None, will plot the module on this specific rectangle.
|
|
334
|
+
min_persistence =0 : float
|
|
335
|
+
Only plots the summand with a persistence above this threshold.
|
|
336
|
+
separated=False : bool
|
|
337
|
+
If true, plot each summand in a different plot.
|
|
338
|
+
alpha=1 : float
|
|
339
|
+
Transparancy parameter
|
|
340
|
+
save = False : string
|
|
341
|
+
if nontrivial, will save the figure at this path
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
Returns
|
|
345
|
+
-------
|
|
346
|
+
The figure of the plot.
|
|
347
|
+
"""
|
|
348
|
+
from multipers.plots import plot2d_PyModule
|
|
349
|
+
if (kwargs.get('box')):
|
|
350
|
+
box = kwargs.pop('box')
|
|
351
|
+
else:
|
|
352
|
+
box = [self.get_bottom(), self.get_top()]
|
|
353
|
+
if (len(box[0]) != 2):
|
|
354
|
+
print("Filtration size :", len(box[0]), " != 2")
|
|
355
|
+
return
|
|
356
|
+
num = 0
|
|
357
|
+
if(degree < 0):
|
|
358
|
+
ndim = self.cmod.get_dimension()+1
|
|
359
|
+
scale = kwargs.pop("scale", 4)
|
|
360
|
+
fig, axes = plt.subplots(1, ndim, figsize=(ndim*scale,scale))
|
|
361
|
+
for degree in range(ndim):
|
|
362
|
+
plt.sca(axes[degree]) if ndim > 1 else plt.sca(axes)
|
|
363
|
+
self.plot(degree,box=box,**kwargs)
|
|
364
|
+
return
|
|
365
|
+
corners = self.cmod.get_corners_of_dimension(degree)
|
|
366
|
+
plot2d_PyModule(corners, box=box, dimension=degree, **kwargs)
|
|
367
|
+
return
|
|
368
|
+
|
|
369
|
+
def barcode(self, basepoint, degree:int,*, threshold = False): # TODO direction vector interface
|
|
370
|
+
"""Computes the barcode of module along a lines.
|
|
371
|
+
|
|
372
|
+
Parameters
|
|
373
|
+
----------
|
|
374
|
+
basepoint : vector
|
|
375
|
+
basepoint of the lines on which to compute the barcodes, i.e. a point on the line
|
|
376
|
+
degree = -1 : integer
|
|
377
|
+
Homology degree on which to compute the bars. If negative, every dimension is computed
|
|
378
|
+
box (default) :
|
|
379
|
+
box on which to compute the barcodes if basepoints is not specified. Default is a linspace of lines crossing that box.
|
|
380
|
+
threshold = False :
|
|
381
|
+
Thre
|
|
382
|
+
|
|
383
|
+
Warning
|
|
384
|
+
-------
|
|
385
|
+
If the barcodes are not thresholded, essential barcodes will not be plot-able.
|
|
386
|
+
|
|
387
|
+
Returns
|
|
388
|
+
-------
|
|
389
|
+
PyMultiDiagrams
|
|
390
|
+
Structure that holds the barcodes. Barcodes can be retrieved with a .get_points() or a .to_multipers() or a .plot().
|
|
391
|
+
"""
|
|
392
|
+
out = PyMultiDiagram()
|
|
393
|
+
out.set(self.cmod.get_barcode(Line[value_type](basepoint), degree, threshold))
|
|
394
|
+
return out
|
|
395
|
+
def barcodes(self, degree:int, basepoints = None, num=100, box = None,threshold = False):
|
|
396
|
+
"""Computes barcodes of module along a set of lines.
|
|
397
|
+
|
|
398
|
+
Parameters
|
|
399
|
+
----------
|
|
400
|
+
basepoints = None : list of vectors
|
|
401
|
+
basepoints of the lines on which to compute the barcodes.
|
|
402
|
+
degree = -1 : integer
|
|
403
|
+
Homology degree on which to compute the bars. If negative, every dimension is computed
|
|
404
|
+
box (default) :
|
|
405
|
+
box on which to compute the barcodes if basepoints is not specified. Default is a linspace of lines crossing that box.
|
|
406
|
+
num:int=100
|
|
407
|
+
if basepoints is not specified, defines the number of lines to consider.
|
|
408
|
+
threshold = False : threshold t
|
|
409
|
+
Resolution of the image(s).
|
|
410
|
+
|
|
411
|
+
Warning
|
|
412
|
+
-------
|
|
413
|
+
If the barcodes are not thresholded, essential barcodes will not be plot-able.
|
|
414
|
+
|
|
415
|
+
Returns
|
|
416
|
+
-------
|
|
417
|
+
PyMultiDiagrams
|
|
418
|
+
Structure that holds the barcodes. Barcodes can be retrieved with a .get_points() or a .to_multipers() or a .plot().
|
|
419
|
+
"""
|
|
420
|
+
out = PyMultiDiagrams()
|
|
421
|
+
if box is None:
|
|
422
|
+
box = [self.get_bottom(), self.get_top()]
|
|
423
|
+
if (len(box[0]) != 2) and (basepoints is None):
|
|
424
|
+
raise ValueError("Basepoints has to be specified for filtration dimension >= 3 !")
|
|
425
|
+
elif basepoints is None:
|
|
426
|
+
h = box[1][1] - box[0][1]
|
|
427
|
+
basepoints = np.linspace([box[0][0] - h,box[0][1]], [box[1][0],box[0][1]], num=num)
|
|
428
|
+
else :
|
|
429
|
+
num=len(basepoints)
|
|
430
|
+
cdef vector[cfiltration_type] cbasepoints
|
|
431
|
+
for i in range(num):
|
|
432
|
+
cbasepoints.push_back(Finitely_critical_multi_filtration(basepoints[i]))
|
|
433
|
+
|
|
434
|
+
out.set(self.cmod.get_barcodes(cbasepoints, degree, threshold))
|
|
435
|
+
return out
|
|
436
|
+
def landscape(self, degree:int, k:int=0,box:list|np.ndarray|None=None, resolution:List=[100,100], bool plot=False):
|
|
437
|
+
"""Computes the multiparameter landscape from a PyModule. Python interface only bifiltrations.
|
|
438
|
+
|
|
439
|
+
Parameters
|
|
440
|
+
----------
|
|
441
|
+
degree : integer
|
|
442
|
+
The homology degree of the landscape.
|
|
443
|
+
k = 0 : int
|
|
444
|
+
the k-th landscape
|
|
445
|
+
resolution = [50,50] : pair of integers
|
|
446
|
+
Resolution of the image.
|
|
447
|
+
box = None : in the format [[a,b], [c,d]]
|
|
448
|
+
If nontrivial, compute the landscape of this box. Default is the PyModule box.
|
|
449
|
+
plot = True : Boolean
|
|
450
|
+
If true, plots the images;
|
|
451
|
+
Returns
|
|
452
|
+
-------
|
|
453
|
+
Numpy array
|
|
454
|
+
The landscape of the module.
|
|
455
|
+
"""
|
|
456
|
+
if box is None:
|
|
457
|
+
box = self.get_box()
|
|
458
|
+
cdef Box[value_type] c_box = Box[value_type](box)
|
|
459
|
+
out = np.array(self.cmod.get_landscape(degree, k, c_box, resolution))
|
|
460
|
+
if plot:
|
|
461
|
+
plt.figure()
|
|
462
|
+
aspect = (box[1][0]-box[0][0]) / (box[1][1]-box[0][1])
|
|
463
|
+
extent = [box[0][0], box[1][0], box[0][1], box[1][1]]
|
|
464
|
+
plt.imshow(out.T, origin="lower", extent=extent, aspect=aspect)
|
|
465
|
+
return out
|
|
466
|
+
def landscapes(self, degree:int, ks:list|np.ndarray=[0],box=None, resolution:list|np.ndarray=[100,100], bool plot=False):
|
|
467
|
+
"""Computes the multiparameter landscape from a PyModule. Python interface only bifiltrations.
|
|
468
|
+
|
|
469
|
+
Parameters
|
|
470
|
+
----------
|
|
471
|
+
degree : integer
|
|
472
|
+
The homology degree of the landscape.
|
|
473
|
+
ks = 0 : list of int
|
|
474
|
+
the k-th landscape
|
|
475
|
+
resolution = [50,50] : pair of integers
|
|
476
|
+
Resolution of the image.
|
|
477
|
+
box = None : in the format [[a,b], [c,d]]
|
|
478
|
+
If nontrivial, compute the landscape of this box. Default is the PyModule box.
|
|
479
|
+
plot = True : Boolean
|
|
480
|
+
If true, plots the images;
|
|
481
|
+
Returns
|
|
482
|
+
-------
|
|
483
|
+
Numpy array
|
|
484
|
+
The landscapes of the module with parameters ks.
|
|
485
|
+
"""
|
|
486
|
+
if box is None:
|
|
487
|
+
box = self.get_box()
|
|
488
|
+
out = np.array(self.cmod.get_landscapes(degree, ks, Box[value_type](box), resolution))
|
|
489
|
+
if plot:
|
|
490
|
+
to_plot = np.sum(out, axis=0)
|
|
491
|
+
plt.figure()
|
|
492
|
+
aspect = (box[1][0]-box[0][0]) / (box[1][1]-box[0][1])
|
|
493
|
+
extent = [box[0][0], box[1][0], box[0][1], box[1][1]]
|
|
494
|
+
plt.imshow(to_plot.T, origin="lower", extent=extent, aspect=aspect)
|
|
495
|
+
return out
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
def image(self, degrees:Iterable[int]=None, bandwidth:float=0.1, resolution:list|int=50, normalize:bool=False, plot:bool=False, save:bool=False, dpi:int=200,p:float=1., box=None, flatten=False, int n_jobs=0, **kwargs)->np.ndarray:
|
|
499
|
+
"""Computes a vectorization from a PyModule. Python interface only bifiltrations.
|
|
500
|
+
|
|
501
|
+
Parameters
|
|
502
|
+
----------
|
|
503
|
+
degree = -1 : integer
|
|
504
|
+
If positive returns only the image of homology degree `degree`.
|
|
505
|
+
bandwidth = 0.1 : float
|
|
506
|
+
Image parameter. TODO : different weights
|
|
507
|
+
resolution = [100,100] : pair of integers
|
|
508
|
+
Resolution of the image(s).
|
|
509
|
+
normalize = True : Boolean
|
|
510
|
+
Ensures that the image belongs to [0,1].
|
|
511
|
+
plot = False : Boolean
|
|
512
|
+
If true, plots the images;
|
|
513
|
+
flatten=False :
|
|
514
|
+
If False, reshapes the output to the expected shape.
|
|
515
|
+
|
|
516
|
+
Returns
|
|
517
|
+
-------
|
|
518
|
+
List of Numpy arrays or numpy array
|
|
519
|
+
The list of images, or the image of fixed dimension.
|
|
520
|
+
"""
|
|
521
|
+
# box = kwargs.get("box",[self.get_bottom(),self.get_top()])
|
|
522
|
+
if box is None:
|
|
523
|
+
box = self.get_box()
|
|
524
|
+
num_parameters = self.num_parameters
|
|
525
|
+
if degrees is None:
|
|
526
|
+
degrees = np.arange(self.max_degree +1)
|
|
527
|
+
num_degrees = len(degrees)
|
|
528
|
+
try:
|
|
529
|
+
int(resolution)
|
|
530
|
+
resolution = [resolution]*num_parameters
|
|
531
|
+
except:
|
|
532
|
+
pass
|
|
533
|
+
|
|
534
|
+
xx = [np.linspace(*np.asarray(box)[:,parameter], num=res) for parameter, res in zip(range(num_parameters), resolution)]
|
|
535
|
+
mesh = np.meshgrid(*xx)
|
|
536
|
+
coordinates = np.concatenate([stuff.flatten()[:,None] for stuff in mesh], axis=1)
|
|
537
|
+
|
|
538
|
+
# if degree < 0:
|
|
539
|
+
# image_vector = np.array(self.cmod.get_vectorization(bandwidth, p, normalize, Box[value_type](box), resolution[0], resolution[1]))
|
|
540
|
+
# else:
|
|
541
|
+
# image_vector = np.array([self.cmod.get_vectorization_in_dimension(degree, bandwidth, p,normalize,Box[value_type](box), resolution[0], resolution[1])])
|
|
542
|
+
|
|
543
|
+
concatenated_images = self._compute_pixels(coordinates, degrees=degrees, box=box, delta=bandwidth, p=p, normalize=normalize,n_jobs=n_jobs)
|
|
544
|
+
if flatten:
|
|
545
|
+
image_vector = concatenated_images.reshape((len(degrees),-1))
|
|
546
|
+
if plot:
|
|
547
|
+
from warnings import warn
|
|
548
|
+
warn("Unflatten to plot.")
|
|
549
|
+
return image_vector
|
|
550
|
+
else:
|
|
551
|
+
image_vector = concatenated_images.reshape((len(degrees),*resolution))
|
|
552
|
+
if plot:
|
|
553
|
+
assert num_parameters == 2
|
|
554
|
+
i=0
|
|
555
|
+
n_plots = len(image_vector)
|
|
556
|
+
scale:float = kwargs.get("size", 4.0)
|
|
557
|
+
fig, axs = plt.subplots(1,n_plots, figsize=(n_plots*scale,scale))
|
|
558
|
+
aspect = (box[1][0]-box[0][0]) / (box[1][1]-box[0][1])
|
|
559
|
+
extent = [box[0][0], box[1][0], box[0][1], box[1][1]]
|
|
560
|
+
for image, degree, i in zip(image_vector, degrees, range(num_degrees)):
|
|
561
|
+
ax = axs if n_plots <= 1 else axs[i]
|
|
562
|
+
temp = ax.imshow(image,origin="lower",extent=extent, aspect=aspect)
|
|
563
|
+
if (kwargs.get('colorbar') or kwargs.get('cb')):
|
|
564
|
+
plt.colorbar(temp, ax = ax)
|
|
565
|
+
if degree < 0 :
|
|
566
|
+
ax.set_title(rf"$H_{i}$ $2$-persistence image")
|
|
567
|
+
if degree >= 0:
|
|
568
|
+
ax.set_title(rf"$H_{degree}$ $2$-persistence image")
|
|
569
|
+
return image_vector
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
def euler_char(self, points:list|np.ndarray) -> np.ndarray:
|
|
573
|
+
""" Computes the Euler Characteristic of the filtered complex at given (multiparameter) time
|
|
574
|
+
|
|
575
|
+
Parameters
|
|
576
|
+
----------
|
|
577
|
+
points: list[float] | list[list[float]] | np.ndarray
|
|
578
|
+
List of filtration values on which to compute the euler characteristic.
|
|
579
|
+
WARNING FIXME : the points have to have the same dimension as the simplextree.
|
|
580
|
+
|
|
581
|
+
Returns
|
|
582
|
+
-------
|
|
583
|
+
The list of euler characteristic values
|
|
584
|
+
"""
|
|
585
|
+
if len(points) == 0:
|
|
586
|
+
return []
|
|
587
|
+
if type(points[0]) is float:
|
|
588
|
+
points = [points]
|
|
589
|
+
if type(points) is np.ndarray:
|
|
590
|
+
assert len(points.shape) in [1,2]
|
|
591
|
+
if len(points.shape) == 1:
|
|
592
|
+
points = [points]
|
|
593
|
+
cdef vector[Finitely_critical_multi_filtration] c_points = Finitely_critical_multi_filtration.from_python(points)
|
|
594
|
+
cdef Module c_mod = self.cmod
|
|
595
|
+
with nogil:
|
|
596
|
+
c_euler = c_mod.euler_curve(c_points)
|
|
597
|
+
euler = c_euler
|
|
598
|
+
return np.array(euler, dtype=int)
|
|
599
|
+
|
|
600
|
+
def module_approximation(
|
|
601
|
+
st:SimplexTreeMulti|None=None,
|
|
602
|
+
max_error:float|None = None,
|
|
603
|
+
box:list|np.ndarray|None = None,
|
|
604
|
+
threshold:bool = False,
|
|
605
|
+
complete:bool = True,
|
|
606
|
+
multithread:bool = False,
|
|
607
|
+
verbose:bool = False,
|
|
608
|
+
ignore_warning:bool = False,
|
|
609
|
+
nlines:int = 500,
|
|
610
|
+
max_dimension=np.inf,
|
|
611
|
+
boundary = None,
|
|
612
|
+
filtration = None,
|
|
613
|
+
return_timings:bool = False,
|
|
614
|
+
**kwargs
|
|
615
|
+
):
|
|
616
|
+
"""Computes an interval module approximation of a multiparameter filtration.
|
|
617
|
+
|
|
618
|
+
Parameters
|
|
619
|
+
----------
|
|
620
|
+
st : n-filtered Simplextree, or None if boundary and filtration are provided.
|
|
621
|
+
Defines the n-filtration on which to compute the homology.
|
|
622
|
+
max_error: positive float
|
|
623
|
+
Trade-off between approximation and computational complexity.
|
|
624
|
+
Upper bound of the module approximation, in bottleneck distance,
|
|
625
|
+
for interval-decomposable modules.
|
|
626
|
+
nlines: int
|
|
627
|
+
Alternative to precision.
|
|
628
|
+
box : pair of list of floats
|
|
629
|
+
Defines a rectangle on which to compute the approximation.
|
|
630
|
+
Format : [x,y], where x,y defines the rectangle {z : x ≤ z ≤ y}
|
|
631
|
+
threshold: bool
|
|
632
|
+
When true, intersects the module support with the box.
|
|
633
|
+
verbose: bool
|
|
634
|
+
Prints C++ infos.
|
|
635
|
+
ignore_warning : bool
|
|
636
|
+
Unless set to true, prevents computing on more than 10k lines. Useful to prevent a segmentation fault due to "infinite" recursion.
|
|
637
|
+
return_timings : bool
|
|
638
|
+
If true, will return the time to compute instead (computed in python, using perf_counter_ns).
|
|
639
|
+
Returns
|
|
640
|
+
-------
|
|
641
|
+
PyModule
|
|
642
|
+
An interval decomposable module approximation of the module defined by the
|
|
643
|
+
homology of this multi-filtration.
|
|
644
|
+
"""
|
|
645
|
+
|
|
646
|
+
if boundary is None or filtration is None:
|
|
647
|
+
boundary,filtration = simplex_tree2boundary_filtrations(st) # TODO : recomputed each time... maybe store this somewhere ?
|
|
648
|
+
if max_dimension < np.inf: # TODO : make it more efficient
|
|
649
|
+
nsplx = len(boundary)
|
|
650
|
+
for i in range(nsplx-1,-1,-1):
|
|
651
|
+
b = boundary[i]
|
|
652
|
+
dim=len(b) -1
|
|
653
|
+
if dim>max_dimension:
|
|
654
|
+
boundary.pop(i)
|
|
655
|
+
for f in filtration:
|
|
656
|
+
f.pop(i)
|
|
657
|
+
nfiltration = len(filtration)
|
|
658
|
+
if nfiltration <= 0:
|
|
659
|
+
return PyModule()
|
|
660
|
+
if nfiltration == 1 and not(st is None):
|
|
661
|
+
st = st.project_on_line(0)
|
|
662
|
+
return st.persistence()
|
|
663
|
+
|
|
664
|
+
if box is None and not(st is None):
|
|
665
|
+
m,M = st.filtration_bounds()
|
|
666
|
+
elif box is not None:
|
|
667
|
+
m,M = box
|
|
668
|
+
else:
|
|
669
|
+
m, M = np.min(filtration, axis=0), np.max(filtration, axis=0)
|
|
670
|
+
prod = 1
|
|
671
|
+
h = M[-1] - m[-1]
|
|
672
|
+
for i, [a,b] in enumerate(zip(m,M)):
|
|
673
|
+
if i == len(M)-1: continue
|
|
674
|
+
prod *= (b-a + h)
|
|
675
|
+
|
|
676
|
+
if max_error is None:
|
|
677
|
+
max_error:float = (prod/nlines)**(1/(nfiltration-1))
|
|
678
|
+
|
|
679
|
+
if box is None:
|
|
680
|
+
M = [np.max(f)+2*max_error for f in filtration]
|
|
681
|
+
m = [np.min(f)-2*max_error for f in filtration]
|
|
682
|
+
box = [m,M]
|
|
683
|
+
|
|
684
|
+
if ignore_warning and prod >= 20_000:
|
|
685
|
+
from warnings import warn
|
|
686
|
+
warn(f"Warning : the number of lines (around {np.round(prod)}) may be too high. Try to increase the precision parameter, or set `ignore_warning=True` to compute this module. Returning the trivial module.")
|
|
687
|
+
return PyModule()
|
|
688
|
+
|
|
689
|
+
approx_mod = PyModule()
|
|
690
|
+
cdef vector[Finitely_critical_multi_filtration] c_filtration = Finitely_critical_multi_filtration.from_python(filtration)
|
|
691
|
+
cdef boundary_matrix c_boundary = boundary
|
|
692
|
+
cdef value_type c_max_error = max_error
|
|
693
|
+
cdef bool c_threshold = threshold
|
|
694
|
+
cdef bool c_complete = complete
|
|
695
|
+
cdef bool c_multithread = multithread
|
|
696
|
+
cdef bool c_verbose = verbose
|
|
697
|
+
cdef Box[value_type] c_box = Box[value_type](box)
|
|
698
|
+
if return_timings:
|
|
699
|
+
from time import perf_counter_ns
|
|
700
|
+
t = perf_counter_ns()
|
|
701
|
+
with nogil:
|
|
702
|
+
c_mod = compute_vineyard_barcode_approximation(c_boundary,c_filtration,c_max_error, c_box, c_threshold, c_complete, c_multithread,c_verbose)
|
|
703
|
+
if return_timings:
|
|
704
|
+
t = perf_counter_ns() -t
|
|
705
|
+
t /= 10**9
|
|
706
|
+
return t
|
|
707
|
+
approx_mod.set(c_mod)
|
|
708
|
+
return approx_mod
|
|
709
|
+
|
|
710
|
+
|
|
711
|
+
|
|
712
|
+
def dump(self, path:str|None=None):
|
|
713
|
+
"""
|
|
714
|
+
Dumps the module into a pickle-able format.
|
|
715
|
+
|
|
716
|
+
Parameters
|
|
717
|
+
----------
|
|
718
|
+
path:str=None (optional) saves the pickled module in specified path
|
|
719
|
+
|
|
720
|
+
Returns
|
|
721
|
+
-------
|
|
722
|
+
list of list, encoding the module, which can be retrieved with the function `from_dump`.
|
|
723
|
+
"""
|
|
724
|
+
## TODO : optimize, but not really used.
|
|
725
|
+
out = [[] for _ in range(self.cmod.get_dimension()+1)]
|
|
726
|
+
out += [self.get_box()]
|
|
727
|
+
|
|
728
|
+
for summand in self.cmod:
|
|
729
|
+
out[summand.get_dimension()].append((
|
|
730
|
+
Finitely_critical_multi_filtration.to_python(summand.get_birth_list()),
|
|
731
|
+
Finitely_critical_multi_filtration.to_python(summand.get_death_list())
|
|
732
|
+
))
|
|
733
|
+
if path is None:
|
|
734
|
+
return out
|
|
735
|
+
pk.dump(out, open(path, "wb"))
|
|
736
|
+
return out
|
|
737
|
+
|
|
738
|
+
cdef dump_summand(Summand& summand):
|
|
739
|
+
return (
|
|
740
|
+
np.asarray(Finitely_critical_multi_filtration.to_python(summand.get_birth_list())),
|
|
741
|
+
np.asarray(Finitely_critical_multi_filtration.to_python(summand.get_death_list())),
|
|
742
|
+
summand.get_dimension()
|
|
743
|
+
)
|
|
744
|
+
|
|
745
|
+
cdef Summand summand_from_dump(summand_dump):
|
|
746
|
+
cdef vector[Finitely_critical_multi_filtration] births = Finitely_critical_multi_filtration.from_python(summand_dump[0])
|
|
747
|
+
cdef vector[Finitely_critical_multi_filtration] deaths = Finitely_critical_multi_filtration.from_python(summand_dump[1])
|
|
748
|
+
cdef int dim = summand_dump[2]
|
|
749
|
+
return Summand(births,deaths,dim)
|
|
750
|
+
|
|
751
|
+
cdef dump_cmod(Module& mod):
|
|
752
|
+
cbox = mod.get_box()
|
|
753
|
+
cdef int dim = mod.get_dimension()
|
|
754
|
+
bottom_corner = cbox.get_bottom_corner().get_vector()
|
|
755
|
+
top_corner = cbox.get_upper_corner().get_vector()
|
|
756
|
+
box = np.asarray([bottom_corner,top_corner])
|
|
757
|
+
summands = tuple(dump_summand(summand) for summand in mod)
|
|
758
|
+
return box, summands
|
|
759
|
+
|
|
760
|
+
cdef Module cmod_from_dump(module_dump):
|
|
761
|
+
box = module_dump[0]
|
|
762
|
+
summands = module_dump[1]
|
|
763
|
+
cdef Module out_module = Module()
|
|
764
|
+
out_module.set_box(Box[value_type](box))
|
|
765
|
+
for i in range(len(summands)):
|
|
766
|
+
out_module.add_summand(summand_from_dump(summands[i]))
|
|
767
|
+
return out_module
|
|
768
|
+
|
|
769
|
+
|
|
770
|
+
def from_dump(dump)->PyModule:
|
|
771
|
+
"""Retrieves a PyModule from a previous dump.
|
|
772
|
+
|
|
773
|
+
Parameters
|
|
774
|
+
----------
|
|
775
|
+
dump: either the output of the dump function, or a file containing the output of a dump.
|
|
776
|
+
The dumped module to retrieve
|
|
777
|
+
|
|
778
|
+
Returns
|
|
779
|
+
-------
|
|
780
|
+
PyModule
|
|
781
|
+
The retrieved module.
|
|
782
|
+
"""
|
|
783
|
+
# TODO : optimize...
|
|
784
|
+
mod = PyModule()
|
|
785
|
+
if type(dump) is str:
|
|
786
|
+
dump = pk.load(open(dump, "rb"))
|
|
787
|
+
cdef Module cmod = cmod_from_dump(dump)
|
|
788
|
+
mod.cmod = cmod
|
|
789
|
+
return mod
|
|
790
|
+
|
|
791
|
+
|
|
792
|
+
|
|
793
|
+
################################################## MMA PLOTS
|
|
794
|
+
|
|
795
|
+
|
|
796
|
+
|
|
797
|
+
|
|
798
|
+
#################################################### DATASETS for test
|
|
799
|
+
|
|
800
|
+
|
|
801
|
+
|
|
802
|
+
def test_module(**kwargs):
|
|
803
|
+
"""Generates a module from a noisy annulus.
|
|
804
|
+
|
|
805
|
+
Parameters
|
|
806
|
+
----------
|
|
807
|
+
r1 : float.
|
|
808
|
+
Lower radius of the annulus.
|
|
809
|
+
r2 : float.
|
|
810
|
+
Upper radius of the annulus.
|
|
811
|
+
n1 : int
|
|
812
|
+
Number of points in the annulus.
|
|
813
|
+
n2 : int
|
|
814
|
+
Number of points in the square.
|
|
815
|
+
dim : int
|
|
816
|
+
Dimension of the annulus.
|
|
817
|
+
center: list or array
|
|
818
|
+
center of the annulus.
|
|
819
|
+
|
|
820
|
+
Returns
|
|
821
|
+
-------
|
|
822
|
+
PyModule
|
|
823
|
+
The associated module.
|
|
824
|
+
|
|
825
|
+
"""
|
|
826
|
+
from multipers.synthetic_data import noisy_annulus
|
|
827
|
+
points = noisy_annulus(**kwargs)
|
|
828
|
+
points = np.unique(points, axis=0)
|
|
829
|
+
from sklearn.neighbors import KernelDensity
|
|
830
|
+
kde = KernelDensity(bandwidth = 1).fit(points)
|
|
831
|
+
st = gd.RipsComplex(points = points).create_simplex_tree()
|
|
832
|
+
st = gd.SimplexTreeMulti(st, parameters = 2)
|
|
833
|
+
st.collapse_edges(full=True)
|
|
834
|
+
f2 = - kde.score_samples(points)
|
|
835
|
+
st.fill_lowerstar(f2,parameter=1)
|
|
836
|
+
mod = st.persistence(**kwargs)
|
|
837
|
+
return mod
|
|
838
|
+
|
|
839
|
+
def nlines_precision_box(nlines, basepoint, scale, square = False):
|
|
840
|
+
from random import choice, shuffle
|
|
841
|
+
from sympy.ntheory import factorint
|
|
842
|
+
h = scale
|
|
843
|
+
dim = len(basepoint)
|
|
844
|
+
basepoint = np.array(basepoint)
|
|
845
|
+
if square:
|
|
846
|
+
# here we want n^dim-1 lines (n = nlines)
|
|
847
|
+
n=nlines
|
|
848
|
+
basepoint = np.array(basepoint)
|
|
849
|
+
deathpoint = basepoint.copy()
|
|
850
|
+
deathpoint+=n*h + - h/2
|
|
851
|
+
deathpoint[-1] = basepoint[-1]+h/2
|
|
852
|
+
return [basepoint,deathpoint]
|
|
853
|
+
factors = factorint(nlines)
|
|
854
|
+
prime_list=[]
|
|
855
|
+
for prime in factors:
|
|
856
|
+
for i in range(factors[prime]):
|
|
857
|
+
prime_list.append(prime)
|
|
858
|
+
while len(prime_list)<dim-1:
|
|
859
|
+
prime_list.append(1)
|
|
860
|
+
shuffle(prime_list)
|
|
861
|
+
while len(prime_list)>dim-1:
|
|
862
|
+
prime_list[choice(range(dim-1))] *= prime_list.pop()
|
|
863
|
+
deathpoint = basepoint.copy()
|
|
864
|
+
for i in range(dim-1):
|
|
865
|
+
deathpoint[i] = basepoint[i] + prime_list[i] * scale - scale/2
|
|
866
|
+
return [basepoint,deathpoint]
|
|
867
|
+
|
|
868
|
+
##################################################### MMA CONVERSIONS
|
|
869
|
+
|
|
870
|
+
|
|
871
|
+
|
|
872
|
+
|
|
873
|
+
|
|
874
|
+
cdef extern from "multiparameter_module_approximation/format_python-cpp.h" namespace "Gudhi::multiparameter::mma":
|
|
875
|
+
#list_simplicies_to_sparse_boundary_matrix
|
|
876
|
+
#vector[vector[unsigned int]] build_sparse_boundary_matrix_from_simplex_list(vector[vector[unsigned int]] list_simplices)
|
|
877
|
+
#list_simplices_ls_filtration_to_sparse_boundary_filtration
|
|
878
|
+
#pair[vector[vector[unsigned int]], vector[vector[double]]] build_boundary_matrix_from_simplex_list(vector[vector[unsigned int]] list_simplices, vector[vector[double]] filtrations, vector[unsigned int] filters_to_permute)
|
|
879
|
+
# pair[vector[vector[unsigned int]], vector[double]] simplextree_to_boundary_filtration(vector[boundary_type] &simplexList, filtration_type &filtration)
|
|
880
|
+
pair[boundary_matrix, vector[Finitely_critical_multi_filtration]] simplextree_to_boundary_filtration(uintptr_t)
|
|
881
|
+
|
|
882
|
+
# pair[vector[vector[unsigned int]], vector[double]] __old__simplextree_to_boundary_filtration(vector[boundary_type]&, filtration_type&)
|
|
883
|
+
|
|
884
|
+
# string __old__simplextree2rivet(const uintptr_t, const vector[filtration_type]&)
|
|
885
|
+
# void simplextree2rivet(const string&, const uintptr_t, const vector[filtration_type]&)
|
|
886
|
+
|
|
887
|
+
|
|
888
|
+
|
|
889
|
+
def simplex_tree2boundary_filtrations(simplextree:SimplexTreeMulti | SimplexTree):
|
|
890
|
+
"""Computes a (sparse) boundary matrix, with associated filtration. Can be used as an input of approx afterwards.
|
|
891
|
+
|
|
892
|
+
Parameters
|
|
893
|
+
----------
|
|
894
|
+
simplextree: Gudhi or mma simplextree
|
|
895
|
+
The simplextree defining the filtration to convert to boundary-filtration.
|
|
896
|
+
|
|
897
|
+
Returns
|
|
898
|
+
-------
|
|
899
|
+
B:List of lists of ints
|
|
900
|
+
The boundary matrix.
|
|
901
|
+
F: List of 1D filtration
|
|
902
|
+
The filtrations aligned with B; the i-th simplex of this simplextree has boundary B[i] and filtration(s) F[i].
|
|
903
|
+
|
|
904
|
+
"""
|
|
905
|
+
cdef intptr_t cptr
|
|
906
|
+
if isinstance(simplextree, SimplexTreeMulti):
|
|
907
|
+
cptr = simplextree.thisptr
|
|
908
|
+
elif isinstance(simplextree, SimplexTree):
|
|
909
|
+
temp_st = gd.SimplexTreeMulti(simplextree, parameters=1)
|
|
910
|
+
cptr = temp_st.thisptr
|
|
911
|
+
else:
|
|
912
|
+
raise TypeError("Has to be a simplextree")
|
|
913
|
+
cdef pair[boundary_matrix, vector[Finitely_critical_multi_filtration]] cboundary_filtration = simplextree_to_boundary_filtration(cptr)
|
|
914
|
+
boundary = cboundary_filtration.first
|
|
915
|
+
multi_filtrations = np.array(Finitely_critical_multi_filtration.to_python(cboundary_filtration.second))
|
|
916
|
+
return boundary, multi_filtrations
|
|
917
|
+
|
|
918
|
+
|
|
919
|
+
# def simplextree_to_sparse_boundary(st:SimplexTree):
|
|
920
|
+
# return build_sparse_boundary_matrix_from_simplex_list([simplex[0] for simplex in st.get_simplices()])
|
|
921
|
+
|
|
922
|
+
|
|
923
|
+
|
|
924
|
+
|
|
925
|
+
|
|
926
|
+
|
|
927
|
+
|
|
928
|
+
############################################# MMA - Matching distances
|
|
929
|
+
|
|
930
|
+
|
|
931
|
+
|