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.

Files changed (63) hide show
  1. multipers/.dylibs/libtbb.12.12.dylib +0 -0
  2. multipers/.dylibs/libtbbmalloc.2.12.dylib +0 -0
  3. multipers/__init__.py +5 -0
  4. multipers/_old_rank_invariant.pyx +328 -0
  5. multipers/_signed_measure_meta.py +193 -0
  6. multipers/data/MOL2.py +350 -0
  7. multipers/data/UCR.py +18 -0
  8. multipers/data/__init__.py +1 -0
  9. multipers/data/graphs.py +466 -0
  10. multipers/data/immuno_regions.py +27 -0
  11. multipers/data/minimal_presentation_to_st_bf.py +0 -0
  12. multipers/data/pytorch2simplextree.py +91 -0
  13. multipers/data/shape3d.py +101 -0
  14. multipers/data/synthetic.py +68 -0
  15. multipers/distances.py +172 -0
  16. multipers/euler_characteristic.cpython-312-darwin.so +0 -0
  17. multipers/euler_characteristic.pyx +137 -0
  18. multipers/function_rips.cpython-312-darwin.so +0 -0
  19. multipers/function_rips.pyx +102 -0
  20. multipers/hilbert_function.cpython-312-darwin.so +0 -0
  21. multipers/hilbert_function.pyi +46 -0
  22. multipers/hilbert_function.pyx +151 -0
  23. multipers/io.cpython-312-darwin.so +0 -0
  24. multipers/io.pyx +176 -0
  25. multipers/ml/__init__.py +0 -0
  26. multipers/ml/accuracies.py +61 -0
  27. multipers/ml/convolutions.py +510 -0
  28. multipers/ml/invariants_with_persistable.py +79 -0
  29. multipers/ml/kernels.py +128 -0
  30. multipers/ml/mma.py +657 -0
  31. multipers/ml/one.py +472 -0
  32. multipers/ml/point_clouds.py +191 -0
  33. multipers/ml/signed_betti.py +50 -0
  34. multipers/ml/signed_measures.py +1479 -0
  35. multipers/ml/sliced_wasserstein.py +313 -0
  36. multipers/ml/tools.py +116 -0
  37. multipers/mma_structures.cpython-312-darwin.so +0 -0
  38. multipers/mma_structures.pxd +155 -0
  39. multipers/mma_structures.pyx +651 -0
  40. multipers/multiparameter_edge_collapse.py +29 -0
  41. multipers/multiparameter_module_approximation.cpython-312-darwin.so +0 -0
  42. multipers/multiparameter_module_approximation.pyi +439 -0
  43. multipers/multiparameter_module_approximation.pyx +311 -0
  44. multipers/pickle.py +53 -0
  45. multipers/plots.py +292 -0
  46. multipers/point_measure_integration.cpython-312-darwin.so +0 -0
  47. multipers/point_measure_integration.pyx +59 -0
  48. multipers/rank_invariant.cpython-312-darwin.so +0 -0
  49. multipers/rank_invariant.pyx +154 -0
  50. multipers/simplex_tree_multi.cpython-312-darwin.so +0 -0
  51. multipers/simplex_tree_multi.pxd +121 -0
  52. multipers/simplex_tree_multi.pyi +715 -0
  53. multipers/simplex_tree_multi.pyx +1417 -0
  54. multipers/slicer.cpython-312-darwin.so +0 -0
  55. multipers/slicer.pxd +94 -0
  56. multipers/slicer.pyx +276 -0
  57. multipers/tensor.pxd +13 -0
  58. multipers/test.pyx +44 -0
  59. multipers-1.1.3.dist-info/LICENSE +21 -0
  60. multipers-1.1.3.dist-info/METADATA +22 -0
  61. multipers-1.1.3.dist-info/RECORD +63 -0
  62. multipers-1.1.3.dist-info/WHEEL +5 -0
  63. multipers-1.1.3.dist-info/top_level.txt +1 -0
@@ -0,0 +1,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
+