multipers 1.1.3__cp312-cp312-macosx_11_0_universal2.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of multipers might be problematic. Click here for more details.
- multipers/.dylibs/libtbb.12.12.dylib +0 -0
- multipers/.dylibs/libtbbmalloc.2.12.dylib +0 -0
- multipers/__init__.py +5 -0
- multipers/_old_rank_invariant.pyx +328 -0
- multipers/_signed_measure_meta.py +193 -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 +172 -0
- multipers/euler_characteristic.cpython-312-darwin.so +0 -0
- multipers/euler_characteristic.pyx +137 -0
- multipers/function_rips.cpython-312-darwin.so +0 -0
- multipers/function_rips.pyx +102 -0
- multipers/hilbert_function.cpython-312-darwin.so +0 -0
- multipers/hilbert_function.pyi +46 -0
- multipers/hilbert_function.pyx +151 -0
- multipers/io.cpython-312-darwin.so +0 -0
- multipers/io.pyx +176 -0
- multipers/ml/__init__.py +0 -0
- multipers/ml/accuracies.py +61 -0
- multipers/ml/convolutions.py +510 -0
- multipers/ml/invariants_with_persistable.py +79 -0
- multipers/ml/kernels.py +128 -0
- multipers/ml/mma.py +657 -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 +1479 -0
- multipers/ml/sliced_wasserstein.py +313 -0
- multipers/ml/tools.py +116 -0
- multipers/mma_structures.cpython-312-darwin.so +0 -0
- multipers/mma_structures.pxd +155 -0
- multipers/mma_structures.pyx +651 -0
- multipers/multiparameter_edge_collapse.py +29 -0
- multipers/multiparameter_module_approximation.cpython-312-darwin.so +0 -0
- multipers/multiparameter_module_approximation.pyi +439 -0
- multipers/multiparameter_module_approximation.pyx +311 -0
- multipers/pickle.py +53 -0
- multipers/plots.py +292 -0
- multipers/point_measure_integration.cpython-312-darwin.so +0 -0
- multipers/point_measure_integration.pyx +59 -0
- multipers/rank_invariant.cpython-312-darwin.so +0 -0
- multipers/rank_invariant.pyx +154 -0
- multipers/simplex_tree_multi.cpython-312-darwin.so +0 -0
- multipers/simplex_tree_multi.pxd +121 -0
- multipers/simplex_tree_multi.pyi +715 -0
- multipers/simplex_tree_multi.pyx +1417 -0
- multipers/slicer.cpython-312-darwin.so +0 -0
- multipers/slicer.pxd +94 -0
- multipers/slicer.pyx +276 -0
- multipers/tensor.pxd +13 -0
- multipers/test.pyx +44 -0
- multipers-1.1.3.dist-info/LICENSE +21 -0
- multipers-1.1.3.dist-info/METADATA +22 -0
- multipers-1.1.3.dist-info/RECORD +63 -0
- multipers-1.1.3.dist-info/WHEEL +5 -0
- multipers-1.1.3.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,651 @@
|
|
|
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
|
+
|
|
17
|
+
###########################################################################
|
|
18
|
+
## CPP CLASSES
|
|
19
|
+
from libc.stdint cimport intptr_t
|
|
20
|
+
from libc.stdint cimport uintptr_t
|
|
21
|
+
|
|
22
|
+
###########################################################################
|
|
23
|
+
## CYTHON TYPES
|
|
24
|
+
from libcpp.vector cimport vector
|
|
25
|
+
from libcpp.utility cimport pair
|
|
26
|
+
#from libcpp.list cimport list as clist
|
|
27
|
+
from libcpp cimport bool
|
|
28
|
+
from libcpp cimport int
|
|
29
|
+
from typing import Iterable
|
|
30
|
+
from cython.operator cimport dereference
|
|
31
|
+
from libcpp.utility cimport move
|
|
32
|
+
#########################################################################
|
|
33
|
+
## Multipersistence Module Approximation Classes
|
|
34
|
+
from multipers.mma_structures cimport *
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
#########################################################################
|
|
38
|
+
## Small hack for typing
|
|
39
|
+
from gudhi import SimplexTree
|
|
40
|
+
from multipers.simplex_tree_multi import SimplexTreeMulti
|
|
41
|
+
|
|
42
|
+
cdef class PySummand:
|
|
43
|
+
"""
|
|
44
|
+
Stores a Summand of a PyModule
|
|
45
|
+
"""
|
|
46
|
+
cdef Summand sum
|
|
47
|
+
# def __cinit__(self, vector[corner_type]& births, vector[corner_type]& deaths, int dim):
|
|
48
|
+
# self.sum = Summand(births, deaths, dim)
|
|
49
|
+
|
|
50
|
+
def get_birth_list(self):
|
|
51
|
+
return np.asarray(Finitely_critical_multi_filtration.to_python(self.sum.get_birth_list()))
|
|
52
|
+
|
|
53
|
+
def get_death_list(self):
|
|
54
|
+
return np.asarray(Finitely_critical_multi_filtration.to_python(self.sum.get_death_list()))
|
|
55
|
+
@property
|
|
56
|
+
def degree(self)->int:
|
|
57
|
+
return self.sum.get_dimension()
|
|
58
|
+
|
|
59
|
+
cdef set(self, Summand& summand):
|
|
60
|
+
self.sum = summand
|
|
61
|
+
return self
|
|
62
|
+
def get_bounds(self):
|
|
63
|
+
cdef pair[Finitely_critical_multi_filtration,Finitely_critical_multi_filtration] cbounds
|
|
64
|
+
with nogil:
|
|
65
|
+
cbounds = self.sum.get_bounds().get_pair()
|
|
66
|
+
# return np.array(<value_type[:self.num_parameters]>(&cbounds.first[0])),np.array(<value_type[:self.num_parameters]>(&cbounds.second[0]))
|
|
67
|
+
return np.asarray(cbounds.first._convert_back()), np.asarray(cbounds.second._convert_back())
|
|
68
|
+
|
|
69
|
+
cdef get_summand_filtration_values(Summand summand):
|
|
70
|
+
births = np.asarray(Finitely_critical_multi_filtration.to_python(summand.get_birth_list()))
|
|
71
|
+
deaths = np.asarray(Finitely_critical_multi_filtration.to_python(summand.get_death_list()))
|
|
72
|
+
pts = np.concatenate([births,deaths],axis=0)
|
|
73
|
+
num_parameters = pts.shape[1]
|
|
74
|
+
out = [np.unique(pts[:,parameter]) for parameter in range(num_parameters)]
|
|
75
|
+
out = [f[:-1] if f[-1] == np.inf else f for f in out]
|
|
76
|
+
out = [f[1:] if f[0] == -np.inf else f for f in out]
|
|
77
|
+
return out
|
|
78
|
+
|
|
79
|
+
cdef class PyBox:
|
|
80
|
+
cdef Box[value_type] box
|
|
81
|
+
def __cinit__(self, corner_type bottomCorner, corner_type topCorner):
|
|
82
|
+
self.box = Box[value_type](bottomCorner, topCorner)
|
|
83
|
+
@property
|
|
84
|
+
def num_parameters(self):
|
|
85
|
+
cdef size_t dim = self.box.get_bottom_corner().size()
|
|
86
|
+
if dim == self.box.get_upper_corner().size(): return dim
|
|
87
|
+
else: print("Bad box definition.")
|
|
88
|
+
def contains(self, x):
|
|
89
|
+
return self.box.contains(x)
|
|
90
|
+
cdef set(self, Box[value_type]& b):
|
|
91
|
+
self.box = b
|
|
92
|
+
return self
|
|
93
|
+
|
|
94
|
+
def get(self):
|
|
95
|
+
return [self.box.get_bottom_corner().get_vector(),self.box.get_upper_corner().get_vector()]
|
|
96
|
+
def to_multipers(self):
|
|
97
|
+
#assert (self.get_dimension() == 2) "Multipers only works in dimension 2 !"
|
|
98
|
+
return np.array(self.get()).flatten(order = 'F')
|
|
99
|
+
|
|
100
|
+
cdef class PyMultiDiagramPoint:
|
|
101
|
+
cdef MultiDiagram_point point
|
|
102
|
+
cdef set(self, MultiDiagram_point &pt):
|
|
103
|
+
self.point = pt
|
|
104
|
+
return self
|
|
105
|
+
|
|
106
|
+
def get_degree(self):
|
|
107
|
+
return self.point.get_dimension()
|
|
108
|
+
def get_birth(self):
|
|
109
|
+
return self.point.get_birth()
|
|
110
|
+
def get_death(self):
|
|
111
|
+
return self.point.get_death()
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
cdef class PyMultiDiagram:
|
|
115
|
+
"""
|
|
116
|
+
Stores the diagram of a PyModule on a line
|
|
117
|
+
"""
|
|
118
|
+
cdef MultiDiagram multiDiagram
|
|
119
|
+
cdef set(self, MultiDiagram m):
|
|
120
|
+
self.multiDiagram = m
|
|
121
|
+
return self
|
|
122
|
+
def get_points(self, degree:int=-1) -> np.ndarray:
|
|
123
|
+
out = self.multiDiagram.get_points(degree)
|
|
124
|
+
if len(out) == 0 and len(self) == 0:
|
|
125
|
+
return np.empty() # TODO Retrieve good number of parameters if there is no points in diagram
|
|
126
|
+
if len(out) == 0:
|
|
127
|
+
return np.empty((0,2,self.multiDiagram.at(0).get_dimension())) # gets the number of parameters
|
|
128
|
+
return np.array(out)
|
|
129
|
+
def to_multipers(self, dimension:int):
|
|
130
|
+
return self.multiDiagram.to_multipers(dimension)
|
|
131
|
+
def __len__(self) -> int:
|
|
132
|
+
return self.multiDiagram.size()
|
|
133
|
+
def __getitem__(self,i:int) -> PyMultiDiagramPoint:
|
|
134
|
+
return PyMultiDiagramPoint().set(self.multiDiagram.at(i % self.multiDiagram.size()))
|
|
135
|
+
cdef class PyMultiDiagrams:
|
|
136
|
+
"""
|
|
137
|
+
Stores the barcodes of a PyModule on multiple lines
|
|
138
|
+
"""
|
|
139
|
+
cdef MultiDiagrams multiDiagrams
|
|
140
|
+
cdef set(self,MultiDiagrams m):
|
|
141
|
+
self.multiDiagrams = m
|
|
142
|
+
return self
|
|
143
|
+
def to_multipers(self):
|
|
144
|
+
out = self.multiDiagrams.to_multipers()
|
|
145
|
+
# return out
|
|
146
|
+
return [np.array(summand, dtype=np.float64) for summand in out]
|
|
147
|
+
def __getitem__(self,i:int):
|
|
148
|
+
if i >=0 :
|
|
149
|
+
return PyMultiDiagram().set(self.multiDiagrams.at(i))
|
|
150
|
+
else:
|
|
151
|
+
return PyMultiDiagram().set(self.multiDiagrams.at( self.multiDiagrams.size() - i))
|
|
152
|
+
def __len__(self):
|
|
153
|
+
return self.multiDiagrams.size()
|
|
154
|
+
def get_points(self, degree:int=-1):
|
|
155
|
+
return self.multiDiagrams.get_points()
|
|
156
|
+
# return np.array([x.get_points(dimension) for x in self.multiDiagrams], dtype=float)
|
|
157
|
+
# return np.array([PyMultiDiagram().set(x).get_points(dimension) for x in self.multiDiagrams])
|
|
158
|
+
cdef _get_plot_bars(self, dimension:int=-1, min_persistence:float=0):
|
|
159
|
+
return self.multiDiagrams._for_python_plot(dimension, min_persistence);
|
|
160
|
+
def plot(self, degree:int=-1, min_persistence:float=0):
|
|
161
|
+
"""
|
|
162
|
+
Plots the barcodes.
|
|
163
|
+
|
|
164
|
+
Parameters
|
|
165
|
+
----------
|
|
166
|
+
degree:int=-1
|
|
167
|
+
Only plots the bars of specified homology degree. Useful when the multidiagrams contains multiple dimenions
|
|
168
|
+
min_persistence:float=0
|
|
169
|
+
Only plot bars of length greater than this value. Useful to reduce the time to plot.
|
|
170
|
+
|
|
171
|
+
Warning
|
|
172
|
+
-------
|
|
173
|
+
If the barcodes are not thresholded, essential barcodes will not be displayed !
|
|
174
|
+
|
|
175
|
+
Returns
|
|
176
|
+
-------
|
|
177
|
+
Nothing
|
|
178
|
+
"""
|
|
179
|
+
from cycler import cycler
|
|
180
|
+
import matplotlib
|
|
181
|
+
import matplotlib.pyplot as plt
|
|
182
|
+
if len(self) == 0: return
|
|
183
|
+
_cmap = matplotlib.colormaps["Spectral"]
|
|
184
|
+
multibarcodes_, colors = self._get_plot_bars(degree, min_persistence)
|
|
185
|
+
n_summands = np.max(colors)+1 if len(colors)>0 else 1
|
|
186
|
+
|
|
187
|
+
plt.rc('axes', prop_cycle = cycler('color', [_cmap(i/n_summands) for i in colors]))
|
|
188
|
+
plt.plot(*multibarcodes_)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
cdef class PyModule:
|
|
192
|
+
"""
|
|
193
|
+
Stores a representation of a n-persistence module.
|
|
194
|
+
"""
|
|
195
|
+
cdef Module cmod
|
|
196
|
+
|
|
197
|
+
cdef set(self, Module& m):
|
|
198
|
+
self.cmod = m
|
|
199
|
+
def _set_from_ptr(self, intptr_t module_ptr):
|
|
200
|
+
self.cmod = move(dereference(<Module*>(module_ptr)))
|
|
201
|
+
def set_box(self, PyBox pybox):
|
|
202
|
+
cdef Box[value_type] cbox = pybox.box
|
|
203
|
+
with nogil:
|
|
204
|
+
self.cmod.set_box(cbox)
|
|
205
|
+
return self
|
|
206
|
+
def get_module_of_degree(self, int degree)->PyModule: # TODO : in c++ ?
|
|
207
|
+
pmodule = PyModule()
|
|
208
|
+
cdef Box[value_type] c_box = self.cmod.get_box()
|
|
209
|
+
pmodule.cmod.set_box(c_box)
|
|
210
|
+
with nogil:
|
|
211
|
+
for summand in self.cmod:
|
|
212
|
+
if summand.get_dimension() == degree:
|
|
213
|
+
pmodule.cmod.add_summand(summand)
|
|
214
|
+
return pmodule
|
|
215
|
+
def get_module_of_degrees(self, degrees:Iterable[int])->PyModule: # TODO : in c++ ?
|
|
216
|
+
pmodule = PyModule()
|
|
217
|
+
cdef Box[value_type] c_box = self.cmod.get_box()
|
|
218
|
+
pmodule.cmod.set_box(c_box)
|
|
219
|
+
cdef vector[int] cdegrees = degrees
|
|
220
|
+
with nogil:
|
|
221
|
+
for summand in self.cmod:
|
|
222
|
+
for d in cdegrees:
|
|
223
|
+
if d == summand.get_dimension():
|
|
224
|
+
pmodule.cmod.add_summand(summand)
|
|
225
|
+
return pmodule
|
|
226
|
+
def _compute_pixels(self,coordinates:np.ndarray,
|
|
227
|
+
degrees=None, box=None, value_type delta=.1,
|
|
228
|
+
value_type p=1., bool normalize=False, int n_jobs=0):
|
|
229
|
+
if degrees is not None: assert np.all(degrees[:-1] <= degrees[1:]), "Degrees have to be sorted"
|
|
230
|
+
cdef vector[int] cdegrees = np.arange(self.max_degree +1) if degrees is None else degrees
|
|
231
|
+
pybox = PyBox(*self.get_box()) if box is None else PyBox(*box)
|
|
232
|
+
cdef Box[value_type] cbox = pybox.box
|
|
233
|
+
cdef vector[vector[value_type]] ccoords = coordinates
|
|
234
|
+
cdef vector[vector[value_type]] out
|
|
235
|
+
with nogil:
|
|
236
|
+
out = self.cmod.compute_pixels(ccoords, cdegrees, cbox, delta, p, normalize, n_jobs)
|
|
237
|
+
return np.asarray(out)
|
|
238
|
+
def __len__(self)->int:
|
|
239
|
+
return self.cmod.size()
|
|
240
|
+
def get_bottom(self)->np.ndarray:
|
|
241
|
+
return np.asarray(self.cmod.get_box().get_bottom_corner().get_vector())
|
|
242
|
+
def get_top(self)->np.ndarray:
|
|
243
|
+
return np.asarray(self.cmod.get_box().get_upper_corner().get_vector())
|
|
244
|
+
def get_box(self)->np.ndarray:
|
|
245
|
+
return np.asarray([self.get_bottom(), self.get_top()])
|
|
246
|
+
@property
|
|
247
|
+
def max_degree(self)->int:
|
|
248
|
+
return self.cmod.get_dimension()
|
|
249
|
+
@property
|
|
250
|
+
def num_parameters(self)->int:
|
|
251
|
+
cdef size_t dim = self.cmod.get_box().get_bottom_corner().size()
|
|
252
|
+
assert dim == self.cmod.get_box().get_upper_corner().size(), "Bad box definition, cannot infer num_parameters."
|
|
253
|
+
return dim
|
|
254
|
+
def dump(self, path:str|None=None):
|
|
255
|
+
"""
|
|
256
|
+
Dumps the module into a pickle-able format.
|
|
257
|
+
|
|
258
|
+
Parameters
|
|
259
|
+
----------
|
|
260
|
+
path:str=None (optional) saves the pickled module in specified path
|
|
261
|
+
|
|
262
|
+
Returns
|
|
263
|
+
-------
|
|
264
|
+
list of list, encoding the module, which can be retrieved with the function `from_dump`.
|
|
265
|
+
"""
|
|
266
|
+
## TODO : optimize, but not really used.
|
|
267
|
+
return dump_cmod(self.cmod)
|
|
268
|
+
def __getstate__(self):
|
|
269
|
+
return self.dump()
|
|
270
|
+
def __setstate__(self,dump):
|
|
271
|
+
cdef Module cmod = cmod_from_dump(dump)
|
|
272
|
+
self.cmod = cmod
|
|
273
|
+
return
|
|
274
|
+
def __getitem__(self, i) -> PySummand:
|
|
275
|
+
if i == slice(None):
|
|
276
|
+
return self
|
|
277
|
+
summand = PySummand()
|
|
278
|
+
summand.set(self.cmod.at(i % self.cmod.size()))
|
|
279
|
+
return summand
|
|
280
|
+
def get_bounds(self):
|
|
281
|
+
cdef pair[Finitely_critical_multi_filtration,Finitely_critical_multi_filtration] cbounds
|
|
282
|
+
with nogil:
|
|
283
|
+
cbounds = self.cmod.get_bounds().get_pair()
|
|
284
|
+
# return np.array(<value_type[:self.num_parameters]>(&cbounds.first[0])),np.array(<value_type[:self.num_parameters]>(&cbounds.second[0]))
|
|
285
|
+
return np.asarray(cbounds.first._convert_back()), np.asarray(cbounds.second._convert_back())
|
|
286
|
+
def rescale(self,rescale_factors, int degree=-1):
|
|
287
|
+
"""
|
|
288
|
+
Rescales the fitlration values of the summands by this rescaling vector.
|
|
289
|
+
"""
|
|
290
|
+
cdef vector[value_type] crescale_factors = rescale_factors
|
|
291
|
+
with nogil:
|
|
292
|
+
self.cmod.rescale(crescale_factors,degree)
|
|
293
|
+
def translate(self,translation, int degree=-1):
|
|
294
|
+
"""
|
|
295
|
+
Translates the module in the filtration space by this vector.
|
|
296
|
+
"""
|
|
297
|
+
cdef vector[value_type] ctranslation = translation
|
|
298
|
+
with nogil:
|
|
299
|
+
self.cmod.translate(ctranslation,degree)
|
|
300
|
+
|
|
301
|
+
def get_filtration_values(self, unique=True):
|
|
302
|
+
"""
|
|
303
|
+
Retrieves all filtration values of the summands of the module.
|
|
304
|
+
|
|
305
|
+
Output format
|
|
306
|
+
-------------
|
|
307
|
+
|
|
308
|
+
list of filtration values for parameter.
|
|
309
|
+
"""
|
|
310
|
+
if len(self) ==0:
|
|
311
|
+
return np.empty((self.num_parameters,0))
|
|
312
|
+
values = [get_summand_filtration_values(summand) for summand in self.cmod]
|
|
313
|
+
values = [np.concatenate([f[parameter] for f in values], axis=0) for parameter in range(self.num_parameters)]
|
|
314
|
+
if unique:
|
|
315
|
+
return [np.unique(f) for f in values]
|
|
316
|
+
return values
|
|
317
|
+
|
|
318
|
+
def plot(self, degree:int=-1,**kwargs)->None:
|
|
319
|
+
"""Shows the module on a plot. Each color corresponds to an apprimation summand of the module, and its shape corresponds to its support.
|
|
320
|
+
Only works with 2-parameter modules.
|
|
321
|
+
|
|
322
|
+
Parameters
|
|
323
|
+
----------
|
|
324
|
+
degree = -1 : integer
|
|
325
|
+
If positive returns only the image of dimension `dimension`.
|
|
326
|
+
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.
|
|
327
|
+
If non-None, will plot the module on this specific rectangle.
|
|
328
|
+
min_persistence =0 : float
|
|
329
|
+
Only plots the summand with a persistence above this threshold.
|
|
330
|
+
separated=False : bool
|
|
331
|
+
If true, plot each summand in a different plot.
|
|
332
|
+
alpha=1 : float
|
|
333
|
+
Transparancy parameter
|
|
334
|
+
save = False : string
|
|
335
|
+
if nontrivial, will save the figure at this path
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
Returns
|
|
339
|
+
-------
|
|
340
|
+
The figure of the plot.
|
|
341
|
+
"""
|
|
342
|
+
from multipers.plots import plot2d_PyModule
|
|
343
|
+
import matplotlib.pyplot as plt
|
|
344
|
+
if (kwargs.get('box')):
|
|
345
|
+
box = kwargs.pop('box')
|
|
346
|
+
else:
|
|
347
|
+
box = [self.get_bottom(), self.get_top()]
|
|
348
|
+
if (len(box[0]) != 2):
|
|
349
|
+
print("Filtration size :", len(box[0]), " != 2")
|
|
350
|
+
return
|
|
351
|
+
num = 0
|
|
352
|
+
if(degree < 0):
|
|
353
|
+
ndim = self.cmod.get_dimension()+1
|
|
354
|
+
scale = kwargs.pop("scale", 4)
|
|
355
|
+
fig, axes = plt.subplots(1, ndim, figsize=(ndim*scale,scale))
|
|
356
|
+
for degree in range(ndim):
|
|
357
|
+
plt.sca(axes[degree]) if ndim > 1 else plt.sca(axes)
|
|
358
|
+
self.plot(degree,box=box,**kwargs)
|
|
359
|
+
return
|
|
360
|
+
corners = self.cmod.get_corners_of_dimension(degree)
|
|
361
|
+
plot2d_PyModule(corners, box=box, dimension=degree, **kwargs)
|
|
362
|
+
return
|
|
363
|
+
|
|
364
|
+
def barcode(self, basepoint, degree:int,*, threshold = False): # TODO direction vector interface
|
|
365
|
+
"""Computes the barcode of module along a lines.
|
|
366
|
+
|
|
367
|
+
Parameters
|
|
368
|
+
----------
|
|
369
|
+
basepoint : vector
|
|
370
|
+
basepoint of the lines on which to compute the barcodes, i.e. a point on the line
|
|
371
|
+
degree = -1 : integer
|
|
372
|
+
Homology degree on which to compute the bars. If negative, every dimension is computed
|
|
373
|
+
box (default) :
|
|
374
|
+
box on which to compute the barcodes if basepoints is not specified. Default is a linspace of lines crossing that box.
|
|
375
|
+
threshold = False :
|
|
376
|
+
Thre
|
|
377
|
+
|
|
378
|
+
Warning
|
|
379
|
+
-------
|
|
380
|
+
If the barcodes are not thresholded, essential barcodes will not be plot-able.
|
|
381
|
+
|
|
382
|
+
Returns
|
|
383
|
+
-------
|
|
384
|
+
PyMultiDiagrams
|
|
385
|
+
Structure that holds the barcodes. Barcodes can be retrieved with a .get_points() or a .to_multipers() or a .plot().
|
|
386
|
+
"""
|
|
387
|
+
out = PyMultiDiagram()
|
|
388
|
+
out.set(self.cmod.get_barcode(Line[value_type](basepoint), degree, threshold))
|
|
389
|
+
return out
|
|
390
|
+
def barcodes(self, degree:int, basepoints = None, num=100, box = None,threshold = False):
|
|
391
|
+
"""Computes barcodes of module along a set of lines.
|
|
392
|
+
|
|
393
|
+
Parameters
|
|
394
|
+
----------
|
|
395
|
+
basepoints = None : list of vectors
|
|
396
|
+
basepoints of the lines on which to compute the barcodes.
|
|
397
|
+
degree = -1 : integer
|
|
398
|
+
Homology degree on which to compute the bars. If negative, every dimension is computed
|
|
399
|
+
box (default) :
|
|
400
|
+
box on which to compute the barcodes if basepoints is not specified. Default is a linspace of lines crossing that box.
|
|
401
|
+
num:int=100
|
|
402
|
+
if basepoints is not specified, defines the number of lines to consider.
|
|
403
|
+
threshold = False : threshold t
|
|
404
|
+
Resolution of the image(s).
|
|
405
|
+
|
|
406
|
+
Warning
|
|
407
|
+
-------
|
|
408
|
+
If the barcodes are not thresholded, essential barcodes will not be plot-able.
|
|
409
|
+
|
|
410
|
+
Returns
|
|
411
|
+
-------
|
|
412
|
+
PyMultiDiagrams
|
|
413
|
+
Structure that holds the barcodes. Barcodes can be retrieved with a .get_points() or a .to_multipers() or a .plot().
|
|
414
|
+
"""
|
|
415
|
+
out = PyMultiDiagrams()
|
|
416
|
+
if box is None:
|
|
417
|
+
box = [self.get_bottom(), self.get_top()]
|
|
418
|
+
if (len(box[0]) != 2) and (basepoints is None):
|
|
419
|
+
raise ValueError("Basepoints has to be specified for filtration dimension >= 3 !")
|
|
420
|
+
elif basepoints is None:
|
|
421
|
+
h = box[1][1] - box[0][1]
|
|
422
|
+
basepoints = np.linspace([box[0][0] - h,box[0][1]], [box[1][0],box[0][1]], num=num)
|
|
423
|
+
else :
|
|
424
|
+
num=len(basepoints)
|
|
425
|
+
cdef vector[cfiltration_type] cbasepoints
|
|
426
|
+
for i in range(num):
|
|
427
|
+
cbasepoints.push_back(Finitely_critical_multi_filtration(basepoints[i]))
|
|
428
|
+
|
|
429
|
+
out.set(self.cmod.get_barcodes(cbasepoints, degree, threshold))
|
|
430
|
+
return out
|
|
431
|
+
|
|
432
|
+
def landscape(self, degree:int, k:int=0,box:list|np.ndarray|None=None, resolution:List=[100,100], bool plot=False):
|
|
433
|
+
"""Computes the multiparameter landscape from a PyModule. Python interface only bifiltrations.
|
|
434
|
+
|
|
435
|
+
Parameters
|
|
436
|
+
----------
|
|
437
|
+
degree : integer
|
|
438
|
+
The homology degree of the landscape.
|
|
439
|
+
k = 0 : int
|
|
440
|
+
the k-th landscape
|
|
441
|
+
resolution = [50,50] : pair of integers
|
|
442
|
+
Resolution of the image.
|
|
443
|
+
box = None : in the format [[a,b], [c,d]]
|
|
444
|
+
If nontrivial, compute the landscape of this box. Default is the PyModule box.
|
|
445
|
+
plot = True : Boolean
|
|
446
|
+
If true, plots the images;
|
|
447
|
+
Returns
|
|
448
|
+
-------
|
|
449
|
+
Numpy array
|
|
450
|
+
The landscape of the module.
|
|
451
|
+
"""
|
|
452
|
+
import matplotlib.pyplot as plt
|
|
453
|
+
if box is None:
|
|
454
|
+
box = self.get_box()
|
|
455
|
+
cdef Box[value_type] c_box = Box[value_type](box)
|
|
456
|
+
out = np.array(self.cmod.get_landscape(degree, k, c_box, resolution))
|
|
457
|
+
if plot:
|
|
458
|
+
plt.figure()
|
|
459
|
+
aspect = (box[1][0]-box[0][0]) / (box[1][1]-box[0][1])
|
|
460
|
+
extent = [box[0][0], box[1][0], box[0][1], box[1][1]]
|
|
461
|
+
plt.imshow(out.T, origin="lower", extent=extent, aspect=aspect)
|
|
462
|
+
return out
|
|
463
|
+
|
|
464
|
+
def landscapes(self, degree:int, ks:list|np.ndarray=[0],box=None, resolution:list|np.ndarray=[100,100], bool plot=False):
|
|
465
|
+
"""Computes the multiparameter landscape from a PyModule. Python interface only bifiltrations.
|
|
466
|
+
|
|
467
|
+
Parameters
|
|
468
|
+
----------
|
|
469
|
+
degree : integer
|
|
470
|
+
The homology degree of the landscape.
|
|
471
|
+
ks = 0 : list of int
|
|
472
|
+
the k-th landscape
|
|
473
|
+
resolution = [50,50] : pair of integers
|
|
474
|
+
Resolution of the image.
|
|
475
|
+
box = None : in the format [[a,b], [c,d]]
|
|
476
|
+
If nontrivial, compute the landscape of this box. Default is the PyModule box.
|
|
477
|
+
plot = True : Boolean
|
|
478
|
+
If true, plots the images;
|
|
479
|
+
Returns
|
|
480
|
+
-------
|
|
481
|
+
Numpy array
|
|
482
|
+
The landscapes of the module with parameters ks.
|
|
483
|
+
"""
|
|
484
|
+
import matplotlib.pyplot as plt
|
|
485
|
+
if box is None:
|
|
486
|
+
box = self.get_box()
|
|
487
|
+
out = np.array(self.cmod.get_landscapes(degree, ks, Box[value_type](box), resolution))
|
|
488
|
+
if plot:
|
|
489
|
+
to_plot = np.sum(out, axis=0)
|
|
490
|
+
plt.figure()
|
|
491
|
+
aspect = (box[1][0]-box[0][0]) / (box[1][1]-box[0][1])
|
|
492
|
+
extent = [box[0][0], box[1][0], box[0][1], box[1][1]]
|
|
493
|
+
plt.imshow(to_plot.T, origin="lower", extent=extent, aspect=aspect)
|
|
494
|
+
return out
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
def image(self, degrees=None, bandwidth:float=0.1, resolution:List[int]|int=50,
|
|
498
|
+
bool normalize=False, bool plot=False,
|
|
499
|
+
bool save=False, int dpi=200, p:float=1., box=None, bool flatten=False, int n_jobs=0, **kwargs)->np.ndarray:
|
|
500
|
+
"""Computes a vectorization from a PyModule.
|
|
501
|
+
|
|
502
|
+
Parameters
|
|
503
|
+
----------
|
|
504
|
+
degrees = None : integer list
|
|
505
|
+
If given returns only the image(s) of homology degrees `degrees`.
|
|
506
|
+
bandwidth = 0.1 : float
|
|
507
|
+
Image parameter.
|
|
508
|
+
resolution = [100,100] : pair of integers
|
|
509
|
+
Resolution of the image(s).
|
|
510
|
+
normalize = True : Boolean
|
|
511
|
+
Ensures that the image belongs to [0,1].
|
|
512
|
+
plot = False : Boolean
|
|
513
|
+
If true, plots the images;
|
|
514
|
+
flatten=False :
|
|
515
|
+
If True, reshapes the output to a flattened shape.
|
|
516
|
+
|
|
517
|
+
Returns
|
|
518
|
+
-------
|
|
519
|
+
List of Numpy arrays or numpy array
|
|
520
|
+
The list of images, or the image of fixed dimension.
|
|
521
|
+
"""
|
|
522
|
+
import matplotlib.pyplot as plt
|
|
523
|
+
# box = kwargs.get("box",[self.get_bottom(),self.get_top()])
|
|
524
|
+
if box is None:
|
|
525
|
+
box = self.get_box()
|
|
526
|
+
num_parameters = self.num_parameters
|
|
527
|
+
if degrees is None:
|
|
528
|
+
degrees = np.arange(self.max_degree +1)
|
|
529
|
+
num_degrees = len(degrees)
|
|
530
|
+
try:
|
|
531
|
+
int(resolution)
|
|
532
|
+
resolution = [resolution]*num_parameters
|
|
533
|
+
except:
|
|
534
|
+
pass
|
|
535
|
+
|
|
536
|
+
xx = [np.linspace(*np.asarray(box)[:,parameter], num=res) for parameter, res in zip(range(num_parameters), resolution)]
|
|
537
|
+
mesh = np.meshgrid(*xx)
|
|
538
|
+
coordinates = np.concatenate([stuff.flatten()[:,None] for stuff in mesh], axis=1)
|
|
539
|
+
|
|
540
|
+
# if degree < 0:
|
|
541
|
+
# image_vector = np.array(self.cmod.get_vectorization(bandwidth, p, normalize, Box[value_type](box), resolution[0], resolution[1]))
|
|
542
|
+
# else:
|
|
543
|
+
# image_vector = np.array([self.cmod.get_vectorization_in_dimension(degree, bandwidth, p,normalize,Box[value_type](box), resolution[0], resolution[1])])
|
|
544
|
+
|
|
545
|
+
concatenated_images = self._compute_pixels(coordinates, degrees=degrees, box=box, delta=bandwidth, p=p, normalize=normalize,n_jobs=n_jobs)
|
|
546
|
+
if flatten:
|
|
547
|
+
image_vector = concatenated_images.reshape((len(degrees),-1))
|
|
548
|
+
if plot:
|
|
549
|
+
from warnings import warn
|
|
550
|
+
warn("Unflatten to plot.")
|
|
551
|
+
return image_vector
|
|
552
|
+
else:
|
|
553
|
+
image_vector = concatenated_images.reshape((len(degrees),*resolution))
|
|
554
|
+
if plot:
|
|
555
|
+
assert num_parameters == 2
|
|
556
|
+
i=0
|
|
557
|
+
n_plots = len(image_vector)
|
|
558
|
+
scale:float = kwargs.get("size", 4.0)
|
|
559
|
+
fig, axs = plt.subplots(1,n_plots, figsize=(n_plots*scale,scale))
|
|
560
|
+
aspect = (box[1][0]-box[0][0]) / (box[1][1]-box[0][1])
|
|
561
|
+
extent = [box[0][0], box[1][0], box[0][1], box[1][1]]
|
|
562
|
+
for image, degree, i in zip(image_vector, degrees, range(num_degrees)):
|
|
563
|
+
ax = axs if n_plots <= 1 else axs[i]
|
|
564
|
+
temp = ax.imshow(image,origin="lower",extent=extent, aspect=aspect)
|
|
565
|
+
if (kwargs.get('colorbar') or kwargs.get('cb')):
|
|
566
|
+
plt.colorbar(temp, ax = ax)
|
|
567
|
+
if degree < 0 :
|
|
568
|
+
ax.set_title(rf"$H_{i}$ $2$-persistence image")
|
|
569
|
+
if degree >= 0:
|
|
570
|
+
ax.set_title(rf"$H_{degree}$ $2$-persistence image")
|
|
571
|
+
return image_vector
|
|
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.asarray(euler, dtype=int)
|
|
599
|
+
|
|
600
|
+
cdef dump_summand(Summand& summand):
|
|
601
|
+
return (
|
|
602
|
+
np.asarray(Finitely_critical_multi_filtration.to_python(summand.get_birth_list())),
|
|
603
|
+
np.asarray(Finitely_critical_multi_filtration.to_python(summand.get_death_list())),
|
|
604
|
+
summand.get_dimension()
|
|
605
|
+
)
|
|
606
|
+
|
|
607
|
+
cdef Summand summand_from_dump(summand_dump):
|
|
608
|
+
cdef vector[Finitely_critical_multi_filtration] births = Finitely_critical_multi_filtration.from_python(summand_dump[0])
|
|
609
|
+
cdef vector[Finitely_critical_multi_filtration] deaths = Finitely_critical_multi_filtration.from_python(summand_dump[1])
|
|
610
|
+
cdef int dim = summand_dump[2]
|
|
611
|
+
return Summand(births,deaths,dim)
|
|
612
|
+
|
|
613
|
+
cdef dump_cmod(Module& mod):
|
|
614
|
+
cdef Box[value_type] cbox = mod.get_box()
|
|
615
|
+
cdef int dim = mod.get_dimension()
|
|
616
|
+
bottom_corner = cbox.get_bottom_corner().get_vector()
|
|
617
|
+
top_corner = cbox.get_upper_corner().get_vector()
|
|
618
|
+
box = np.asarray([bottom_corner,top_corner])
|
|
619
|
+
summands = tuple(dump_summand(summand) for summand in mod)
|
|
620
|
+
return box, summands
|
|
621
|
+
|
|
622
|
+
cdef Module cmod_from_dump(module_dump):
|
|
623
|
+
box = module_dump[0]
|
|
624
|
+
summands = module_dump[1]
|
|
625
|
+
cdef Module out_module = Module()
|
|
626
|
+
out_module.set_box(Box[value_type](box))
|
|
627
|
+
for i in range(len(summands)):
|
|
628
|
+
out_module.add_summand(summand_from_dump(summands[i]))
|
|
629
|
+
return out_module
|
|
630
|
+
|
|
631
|
+
|
|
632
|
+
def from_dump(dump)->PyModule:
|
|
633
|
+
"""Retrieves a PyModule from a previous dump.
|
|
634
|
+
|
|
635
|
+
Parameters
|
|
636
|
+
----------
|
|
637
|
+
dump: either the output of the dump function, or a file containing the output of a dump.
|
|
638
|
+
The dumped module to retrieve
|
|
639
|
+
|
|
640
|
+
Returns
|
|
641
|
+
-------
|
|
642
|
+
PyModule
|
|
643
|
+
The retrieved module.
|
|
644
|
+
"""
|
|
645
|
+
# TODO : optimize...
|
|
646
|
+
mod = PyModule()
|
|
647
|
+
if type(dump) is str:
|
|
648
|
+
dump = pk.load(open(dump, "rb"))
|
|
649
|
+
cdef Module cmod = cmod_from_dump(dump)
|
|
650
|
+
mod.cmod = cmod
|
|
651
|
+
return mod
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from tqdm import tqdm
|
|
2
|
+
|
|
3
|
+
def _collapse_edge_list(edges, num:int=0, full:bool=False, strong:bool=False, progress:bool=False):
|
|
4
|
+
"""
|
|
5
|
+
Given an edge list defining a 1 critical 2 parameter 1 dimensional simplicial complex, simplificates this filtered simplicial complex, using filtration-domination's edge collapser.
|
|
6
|
+
"""
|
|
7
|
+
from filtration_domination import remove_strongly_filtration_dominated, remove_filtration_dominated
|
|
8
|
+
n = len(edges)
|
|
9
|
+
if full:
|
|
10
|
+
num = 100
|
|
11
|
+
with tqdm(range(num), total=num, desc="Removing edges", disable=not(progress)) as I:
|
|
12
|
+
for i in I:
|
|
13
|
+
if strong:
|
|
14
|
+
edges = remove_strongly_filtration_dominated(edges) # nogil ?
|
|
15
|
+
else:
|
|
16
|
+
edges = remove_filtration_dominated(edges)
|
|
17
|
+
# Prevents doing useless collapses
|
|
18
|
+
if len(edges) >= n:
|
|
19
|
+
if full and strong:
|
|
20
|
+
strong = False
|
|
21
|
+
n = len(edges)
|
|
22
|
+
# n = edges.size() # len(edges)
|
|
23
|
+
else :
|
|
24
|
+
break
|
|
25
|
+
else:
|
|
26
|
+
n = len(edges)
|
|
27
|
+
# n = edges.size()
|
|
28
|
+
return edges
|
|
29
|
+
|
|
Binary file
|