multipers 1.0__cp311-cp311-manylinux_2_34_x86_64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of multipers might be problematic. Click here for more details.

Files changed (56) hide show
  1. multipers/__init__.py +4 -0
  2. multipers/_old_rank_invariant.pyx +328 -0
  3. multipers/_signed_measure_meta.py +72 -0
  4. multipers/data/MOL2.py +350 -0
  5. multipers/data/UCR.py +18 -0
  6. multipers/data/__init__.py +1 -0
  7. multipers/data/graphs.py +272 -0
  8. multipers/data/immuno_regions.py +27 -0
  9. multipers/data/minimal_presentation_to_st_bf.py +0 -0
  10. multipers/data/pytorch2simplextree.py +91 -0
  11. multipers/data/shape3d.py +101 -0
  12. multipers/data/synthetic.py +68 -0
  13. multipers/distances.py +100 -0
  14. multipers/euler_characteristic.cpython-311-x86_64-linux-gnu.so +0 -0
  15. multipers/euler_characteristic.pyx +132 -0
  16. multipers/function_rips.cpython-311-x86_64-linux-gnu.so +0 -0
  17. multipers/function_rips.pyx +101 -0
  18. multipers/hilbert_function.cpython-311-x86_64-linux-gnu.so +0 -0
  19. multipers/hilbert_function.pyi +46 -0
  20. multipers/hilbert_function.pyx +145 -0
  21. multipers/ml/__init__.py +0 -0
  22. multipers/ml/accuracies.py +61 -0
  23. multipers/ml/convolutions.py +384 -0
  24. multipers/ml/invariants_with_persistable.py +79 -0
  25. multipers/ml/kernels.py +128 -0
  26. multipers/ml/mma.py +422 -0
  27. multipers/ml/one.py +472 -0
  28. multipers/ml/point_clouds.py +191 -0
  29. multipers/ml/signed_betti.py +50 -0
  30. multipers/ml/signed_measures.py +1046 -0
  31. multipers/ml/sliced_wasserstein.py +313 -0
  32. multipers/ml/tools.py +99 -0
  33. multipers/multiparameter_edge_collapse.py +29 -0
  34. multipers/multiparameter_module_approximation.cpython-311-x86_64-linux-gnu.so +0 -0
  35. multipers/multiparameter_module_approximation.pxd +147 -0
  36. multipers/multiparameter_module_approximation.pyi +439 -0
  37. multipers/multiparameter_module_approximation.pyx +931 -0
  38. multipers/pickle.py +53 -0
  39. multipers/plots.py +207 -0
  40. multipers/point_measure_integration.cpython-311-x86_64-linux-gnu.so +0 -0
  41. multipers/point_measure_integration.pyx +59 -0
  42. multipers/rank_invariant.cpython-311-x86_64-linux-gnu.so +0 -0
  43. multipers/rank_invariant.pyx +154 -0
  44. multipers/simplex_tree_multi.cpython-311-x86_64-linux-gnu.so +0 -0
  45. multipers/simplex_tree_multi.pxd +121 -0
  46. multipers/simplex_tree_multi.pyi +715 -0
  47. multipers/simplex_tree_multi.pyx +1284 -0
  48. multipers/tensor.pxd +13 -0
  49. multipers/test.pyx +44 -0
  50. multipers-1.0.dist-info/LICENSE +21 -0
  51. multipers-1.0.dist-info/METADATA +9 -0
  52. multipers-1.0.dist-info/RECORD +56 -0
  53. multipers-1.0.dist-info/WHEEL +5 -0
  54. multipers-1.0.dist-info/top_level.txt +1 -0
  55. multipers.libs/libtbb-5d1cde94.so.12.10 +0 -0
  56. multipers.libs/libtbbmalloc-5e0a3d4c.so.2.10 +0 -0
multipers/pickle.py ADDED
@@ -0,0 +1,53 @@
1
+ import numpy as np
2
+
3
+ def save_with_axis(path:str, signed_measures):
4
+ np.savez(path,
5
+ **{f"{i}_{axis}_{degree}":np.c_[sm_of_degree[0],sm_of_degree[1][:,np.newaxis]] for i,sm in enumerate(signed_measures) for axis,sm_of_axis in enumerate(sm) for degree,sm_of_degree in enumerate(sm_of_axis)},
6
+ )
7
+
8
+ def save_without_axis(path:str, signed_measures):
9
+ np.savez(path,
10
+ **{f"{i}_{degree}":np.c_[sm_of_degree[0],sm_of_degree[1][:,np.newaxis]] for i,sm in enumerate(signed_measures) for degree,sm_of_degree in enumerate(sm)},
11
+ )
12
+
13
+ def get_sm_with_axis(sms,idx,axis,degree):
14
+ sm = sms[f"{idx}_{axis}_{degree}"]
15
+ return (sm[:,:-1],sm[:,-1])
16
+ def get_sm_without_axis(sms,idx,degree):
17
+ sm = sms[f"{idx}_{degree}"]
18
+ return (sm[:,:-1],sm[:,-1])
19
+
20
+
21
+ def load_without_axis(sms):
22
+ indices = np.array([[int(i) for i in key.split('_')] for key in sms.keys()], dtype=int)
23
+ num_data,num_degrees = indices.max(axis=0)+1
24
+ signed_measures_reconstructed = [[get_sm_without_axis(sms,idx,degree) for degree in range(num_degrees)] for idx in range(num_data)]
25
+ return signed_measures_reconstructed
26
+ # test : np.all([np.array_equal(a[0],b[0]) and np.array_equal(a[1],b[1]) and len(a) == len(b) == 2 for x,y in zip(signed_measures_reconstructed,signed_measures_reconstructed) for a,b in zip(x,y)])
27
+
28
+ def load_with_axis(sms):
29
+ indices = np.array([[int(i) for i in key.split('_')] for key in sms.keys()], dtype=int)
30
+ num_data,num_axis,num_degrees = indices.max(axis=0)+1
31
+ signed_measures_reconstructed = [[[get_sm_with_axis(sms,idx,axis,degree) for degree in range(num_degrees)] for axis in range(num_axis)] for idx in range(num_data)]
32
+ return signed_measures_reconstructed
33
+
34
+ def save(path:str, signed_measures):
35
+ if isinstance(signed_measures[0][0], tuple):
36
+ save_without_axis(path=path,signed_measures=signed_measures)
37
+ else:
38
+ save_with_axis(path=path,signed_measures=signed_measures)
39
+
40
+ def load(path:str):
41
+ sms = np.load(path)
42
+ item=None
43
+ for i in sms.keys():
44
+ item=i
45
+ break
46
+ n = len(item.split('_'))
47
+ match n:
48
+ case 2:
49
+ return load_without_axis(sms)
50
+ case 3:
51
+ return load_with_axis(sms)
52
+ case _:
53
+ raise Exception("Invalid Signed Measure !")
multipers/plots.py ADDED
@@ -0,0 +1,207 @@
1
+ import matplotlib.pyplot as plt
2
+ import matplotlib
3
+ import numpy as np
4
+
5
+ def _plot_rectangle(rectangle:np.ndarray, weight, **plt_kwargs):
6
+ rectangle = np.asarray(rectangle)
7
+ x_axis=rectangle[[0,2]]
8
+ y_axis=rectangle[[1,3]]
9
+ color = "blue" if weight > 0 else "red"
10
+ plt.plot(x_axis, y_axis, c=color, **plt_kwargs)
11
+
12
+
13
+ def _plot_signed_measure_2(pts, weights,temp_alpha=.7, **plt_kwargs):
14
+ import matplotlib.colors
15
+ weights = np.asarray(weights)
16
+ color_weights = np.array(weights, dtype=float)
17
+ neg_idx = weights<0
18
+ pos_idx = weights>0
19
+ if np.any(neg_idx):
20
+ current_weights = -weights[neg_idx]
21
+ min_weight = np.max(current_weights)
22
+ color_weights[neg_idx] /= min_weight
23
+ color_weights[neg_idx] -= 1
24
+ else:
25
+ min_weight=0
26
+
27
+ if np.any(pos_idx):
28
+ current_weights = weights[pos_idx]
29
+ max_weight = np.max(current_weights)
30
+ color_weights[pos_idx] /= max_weight
31
+ color_weights[pos_idx] += 1
32
+ else:
33
+ max_weight=1
34
+
35
+ bordeaux = np.array([0.70567316, 0.01555616, 0.15023281, 1 ])
36
+ light_bordeaux = np.array([0.70567316, 0.01555616, 0.15023281, temp_alpha ])
37
+ bleu = np.array([0.2298057 , 0.29871797, 0.75368315, 1 ])
38
+ light_bleu = np.array([0.2298057 , 0.29871797, 0.75368315, temp_alpha ])
39
+ norm = plt.Normalize(-2,2)
40
+ cmap = matplotlib.colors.LinearSegmentedColormap.from_list("", [bordeaux,light_bordeaux,"white",light_bleu,bleu])
41
+ plt.scatter(pts[:,0],pts[:,1], c=color_weights, cmap=cmap, norm=norm, **plt_kwargs)
42
+ plt.scatter([],[],color=bleu,label="positive mass", **plt_kwargs)
43
+ plt.scatter([],[],color=bordeaux,label="negative mass", **plt_kwargs)
44
+ plt.legend()
45
+
46
+ def _plot_signed_measure_4(pts, weights, x_smoothing:float=1, area_alpha:bool=True):
47
+ # compute the maximal rectangle area
48
+ alpha_rescaling = 0;
49
+ for rectangle, weight in zip(pts, weights):
50
+ if rectangle[2] > x_smoothing*rectangle[0]:
51
+ alpha_rescaling = max(alpha_rescaling, (rectangle[2]/x_smoothing-rectangle[0])*(rectangle[3]-rectangle[1]))
52
+ # draw the rectangles
53
+ for rectangle, weight in zip(pts, weights):
54
+ # draw only the rectangles that have not been reduced to the empty set
55
+ if rectangle[2] > x_smoothing*rectangle[0]:
56
+ # make the alpha channel proportional to the rectangle's area
57
+ if area_alpha:
58
+ _plot_rectangle(rectangle=[rectangle[0], rectangle[1], rectangle[2]/x_smoothing, rectangle[3]], weight=weight, alpha=(rectangle[2]/x_smoothing-rectangle[0])*(rectangle[3]-rectangle[1])/alpha_rescaling)
59
+ else:
60
+ _plot_rectangle(rectangle=[rectangle[0], rectangle[1], rectangle[2]/x_smoothing, rectangle[3]], weight=weight, alpha=1)
61
+
62
+ def plot_signed_measure(signed_measure, ax=None, **plt_kwargs):
63
+ if ax is None:
64
+ ax = plt.gca()
65
+ else:
66
+ plt.sca(ax)
67
+ pts, weights = signed_measure
68
+ num_parameters = pts.shape[1]
69
+
70
+ if isinstance(pts,np.ndarray):
71
+ pass
72
+ else:
73
+ import torch
74
+ if isinstance(pts,torch.Tensor):
75
+ pts = pts.detach().numpy()
76
+ else:
77
+ raise Exception("Invalid measure type.")
78
+
79
+ assert num_parameters in (2,4)
80
+ if num_parameters == 2:
81
+ _plot_signed_measure_2(pts=pts,weights=weights, **plt_kwargs)
82
+ else:
83
+ _plot_signed_measure_4(pts=pts,weights=weights, **plt_kwargs)
84
+
85
+ def plot_signed_measures(signed_measures, size=4):
86
+ num_degrees = len(signed_measures)
87
+ fig, axes = plt.subplots(nrows=1,ncols=num_degrees, figsize=(num_degrees*size,size))
88
+ if num_degrees == 1:
89
+ axes = [axes]
90
+ for ax, signed_measure in zip(axes,signed_measures):
91
+ plot_signed_measure(signed_measure=signed_measure,ax=ax)
92
+ plt.tight_layout()
93
+
94
+
95
+ def plot_surface(grid, hf, fig=None, ax=None,cmap=None, **plt_args):
96
+ import matplotlib
97
+ if ax is None:
98
+ ax = plt.gca()
99
+ else:
100
+ plt.sca(ax)
101
+ if hf.ndim == 3 and hf.shape[0] == 1: hf=hf[0]
102
+ assert hf.ndim == 2, "Can only plot a 2d surface"
103
+ fig = plt.gcf() if fig is None else fig
104
+ cmap = matplotlib.colormaps["gray_r"] if cmap is None else matplotlib.colormaps[cmap]
105
+ bounds = np.arange(0,11,1, dtype=int)
106
+ norm = matplotlib.colors.BoundaryNorm(bounds, cmap.N, extend='max')
107
+ im = ax.pcolormesh(grid[0], grid[1], hf.T,cmap=cmap,norm=norm, **plt_args)
108
+ cbar=fig.colorbar(matplotlib.cm.ScalarMappable(cmap=cmap, norm=norm), spacing="proportional", ax=ax)
109
+ cbar.set_ticks(ticks=bounds, labels=bounds)
110
+
111
+ def plot_surfaces(HF, size=4, **plt_args):
112
+ grid, hf = HF
113
+ assert hf.ndim == 3, f"Found hf.shape = {hf.shape}, expected ndim = 3 : degree, 2-parameter surface."
114
+ num_degrees = hf.shape[0]
115
+ fig, axes = plt.subplots(nrows=1,ncols=num_degrees, figsize=(num_degrees*size,size))
116
+ if num_degrees == 1:
117
+ axes = [axes]
118
+ for ax, hf_of_degree in zip(axes,hf):
119
+ plot_surface(grid=grid,hf=hf_of_degree, fig=fig,ax=ax,**plt_args)
120
+ plt.tight_layout()
121
+
122
+
123
+
124
+
125
+
126
+
127
+
128
+
129
+
130
+ def _rectangle(x,y,color, alpha):
131
+ """
132
+ Defines a rectangle patch in the format {z | x  ≤ z ≤ y} with color and alpha
133
+ """
134
+ from matplotlib.patches import Rectangle as RectanglePatch
135
+ return RectanglePatch(x, max(y[0]-x[0],0),max(y[1]-x[1],0), color=color, alpha=alpha)
136
+
137
+ def _d_inf(a,b):
138
+ if type(a) != np.ndarray or type(b) != np.ndarray:
139
+ a = np.array(a)
140
+ b = np.array(b)
141
+ return np.min(np.abs(b-a))
142
+
143
+
144
+
145
+ def plot2d_PyModule(corners, box = [],*,dimension=-1, separated=False, min_persistence = 0, alpha=1, verbose = False, save=False, dpi=200, shapely = True, xlabel=None, ylabel=None, cmap=None):
146
+ import matplotlib
147
+ try:
148
+ from shapely.geometry import Polygon as _Polygon
149
+ from shapely.geometry import box as _rectangle_box
150
+ from shapely import union_all
151
+ shapely=True and shapely
152
+ except:
153
+ from warnings import warn
154
+ shapely=False
155
+ warn("Shapely not installed. Fallbacking to matplotlib. The plots may be inacurate.")
156
+ cmap = matplotlib.colormaps["Spectral"] if cmap is None else matplotlib.colormaps[cmap]
157
+ if not(separated):
158
+ # fig, ax = plt.subplots()
159
+ ax = plt.gca()
160
+ ax.set(xlim=[box[0][0],box[1][0]],ylim=[box[0][1],box[1][1]])
161
+ n_summands = len(corners)
162
+ for i in range(n_summands):
163
+ trivial_summand = True
164
+ list_of_rect = []
165
+ for birth in corners[i][0]:
166
+ for death in corners[i][1]:
167
+ death[0] = min(death[0],box[1][0])
168
+ death[1] = min(death[1],box[1][1])
169
+ if death[1]>birth[1] and death[0]>birth[0]:
170
+ if trivial_summand and _d_inf(birth,death)>min_persistence:
171
+ trivial_summand = False
172
+ if shapely:
173
+ list_of_rect.append(_rectangle_box(birth[0], birth[1], death[0],death[1]))
174
+ else:
175
+ list_of_rect.append(_rectangle(birth,death,cmap(i/n_summands),alpha))
176
+ if not(trivial_summand):
177
+ if separated:
178
+ fig,ax= plt.subplots()
179
+ ax.set(xlim=[box[0][0],box[1][0]],ylim=[box[0][1],box[1][1]])
180
+ if shapely:
181
+ summand_shape = union_all(list_of_rect)
182
+ if type(summand_shape) is _Polygon:
183
+ xs,ys=summand_shape.exterior.xy
184
+ ax.fill(xs,ys,alpha=alpha, fc=cmap(i/n_summands), ec='None')
185
+ else:
186
+ for polygon in summand_shape.geoms:
187
+ xs,ys=polygon.exterior.xy
188
+ ax.fill(xs,ys,alpha=alpha, fc=cmap(i/n_summands), ec='None')
189
+ else:
190
+ for rectangle in list_of_rect:
191
+ ax.add_patch(rectangle)
192
+ if separated:
193
+ if xlabel:
194
+ plt.xlabel(xlabel)
195
+ if ylabel:
196
+ plt.ylabel(ylabel)
197
+ if dimension>=0:
198
+ plt.title(rf"$H_{dimension}$ $2$-persistence")
199
+ if not(separated):
200
+ if xlabel != None:
201
+ plt.xlabel(xlabel)
202
+ if ylabel != None:
203
+ plt.ylabel(ylabel)
204
+ if dimension>=0:
205
+ plt.title(rf"$H_{dimension}$ $2$-persistence")
206
+ return
207
+
@@ -0,0 +1,59 @@
1
+ # cimport multipers.tensor as mt
2
+ from libc.stdint cimport intptr_t, uint16_t, uint32_t, int32_t, int64_t
3
+ from libcpp.vector cimport vector
4
+ from libcpp cimport bool, int, float
5
+ import numpy as np
6
+ cimport numpy as cnp
7
+ cnp.import_array()
8
+
9
+ ctypedef fused some_int:
10
+ int32_t
11
+ int64_t
12
+ int
13
+
14
+ ctypedef fused some_float:
15
+ float
16
+ double
17
+
18
+
19
+ ## TODO : optimize. The filtrations is a list of filtrations, but thats not easily convertible to a cython type without copies
20
+ import cython
21
+ @cython.boundscheck(False)
22
+ @cython.wraparound(False)
23
+ def integrate_measure(
24
+ some_float[:,:] pts,
25
+ some_int[:] weights,
26
+ filtrations
27
+ ):
28
+ resolution = np.asarray([len(f) for f in filtrations])
29
+ cdef int num_pts = pts.shape[0]
30
+ cdef int num_parameters = pts.shape[1]
31
+ assert weights.shape[0] == num_pts
32
+ out = np.zeros(shape=resolution, dtype=np.int32) ## dim cannot be known at compiletime
33
+ # cdef some_float[:] filtration_of_parameter
34
+ # cdef cnp.ndarray indices = np.zeros(shape=num_parameters, dtype=int)
35
+ for i in range(num_pts): ## this is slow...
36
+ # for parameter in range(num_parameters):
37
+ # indices[parameter] = np.asarray(filtrations[parameter]) >= pts[i,parameter]
38
+ indices = (
39
+ # np.asarray(
40
+ # <some_float[:]>(&filtrations[parameter][0])
41
+ # ) ## I'm not sure why it doesn't work
42
+ np.asarray(filtrations[parameter])
43
+ >=
44
+ pts[i,parameter] for parameter in range(num_parameters)
45
+ ) ## iterator so that below there is no copy
46
+
47
+ out[np.ix_(*indices)] += weights[i] # This is slow...
48
+ return out
49
+
50
+ ## for benchmark purposes
51
+ def integrate_measure_python(pts, weights, filtrations):
52
+ resolution = tuple([len(f) for f in filtrations])
53
+ out = np.zeros(shape=resolution, dtype=pts.dtype)
54
+ num_pts = pts.shape[0]
55
+ num_parameters = pts.shape[1]
56
+ for i in range(num_pts): #this is slow.
57
+ indices = (filtrations[parameter]>=pts[i][parameter] for parameter in range(num_parameters))
58
+ out[np.ix_(*indices)] += weights[i]
59
+ return out
@@ -0,0 +1,154 @@
1
+ # cimport multipers.tensor as mt
2
+ from libc.stdint cimport intptr_t, uint16_t, uint32_t, int32_t
3
+ from libcpp.vector cimport vector
4
+ from libcpp cimport bool, int, float
5
+ from libcpp.utility cimport pair
6
+ from typing import Optional,Iterable,Callable
7
+
8
+ import numpy as np
9
+ cimport numpy as cnp
10
+ cnp.import_array()
11
+
12
+ ctypedef float value_type
13
+ python_value_type=np.float32
14
+
15
+ ctypedef int32_t indices_type # uint fails for some reason
16
+ python_indices_type=np.int32
17
+
18
+ ctypedef int32_t tensor_dtype
19
+ python_tensor_dtype = np.int32
20
+
21
+
22
+ ctypedef pair[vector[vector[indices_type]], vector[tensor_dtype]] signed_measure_type
23
+
24
+ cdef extern from "multi_parameter_rank_invariant/rank_invariant.h" namespace "Gudhi::multiparameter::rank_invariant":
25
+ void compute_rank_invariant_python(const intptr_t, tensor_dtype* , const vector[indices_type], const vector[indices_type], indices_type, bool) except + nogil
26
+
27
+
28
+ def rank_invariant(simplextree, vector[indices_type] degrees, mass_default=None, plot=False, indices_type n_jobs=0, bool verbose=False, bool expand_collapse=False):
29
+ """
30
+ Computes the signed measures given by the decomposition of the hilbert function.
31
+
32
+ Input
33
+ -----
34
+ - simplextree:SimplexTreeMulti, the multifiltered simplicial complex
35
+ - degrees:array-like of ints, the degrees to compute
36
+ - mass_default: Either None, or 'auto' or 'inf', or array-like of floats. Where to put the default mass to get a zero-mass measure.
37
+ - plot:bool, plots the computed measures if true.
38
+ - n_jobs:int, number of jobs. Defaults to #cpu, but when doing parallel computations of signed measures, we recommend setting this to 1.
39
+ - verbose:bool, prints c++ logs.
40
+
41
+ Output
42
+ ------
43
+ `[signed_measure_of_degree for degree in degrees]`
44
+ with `signed_measure_of_degree` of the form `(dirac location, dirac weights)`.
45
+ """
46
+ assert simplextree._is_squeezed, "Squeeze grid first."
47
+ cdef bool zero_pad = mass_default is not None
48
+ grid_conversion = [np.asarray(f) for f in simplextree.filtration_grid]
49
+ # assert simplextree.num_parameters == 2
50
+ grid_shape = np.array([len(f) for f in grid_conversion])
51
+
52
+ if mass_default is None:
53
+ mass_default = mass_default
54
+ else:
55
+ mass_default = np.asarray(mass_default)
56
+ assert mass_default.ndim == 1 and mass_default.shape[0] == simplextree.num_parameters
57
+ if zero_pad:
58
+ for i, _ in enumerate(grid_shape):
59
+ grid_shape[i] += 1 # adds a 0
60
+ for i,f in enumerate(grid_conversion):
61
+ grid_conversion[i] = np.concatenate([f, [mass_default[i]]])
62
+
63
+ assert len(grid_shape) == simplextree.num_parameters, "Grid shape size has to be the number of parameters."
64
+ grid_shape_with_degree = np.asarray(np.concatenate([[len(degrees)], grid_shape, grid_shape]), dtype=python_indices_type)
65
+ container_array = np.ascontiguousarray(np.zeros(grid_shape_with_degree, dtype=python_tensor_dtype).flatten())
66
+ assert len(container_array) < np.iinfo(python_indices_type).max, "Too large container. Raise an issue on github if you encounter this issue. (Due to tensor's operator[])"
67
+ cdef intptr_t simplextree_ptr = simplextree.thisptr
68
+ cdef vector[indices_type] c_grid_shape = grid_shape_with_degree
69
+ cdef tensor_dtype[::1] container = container_array
70
+ cdef tensor_dtype* container_ptr = &container[0]
71
+ with nogil:
72
+ compute_rank_invariant_python(simplextree_ptr, container_ptr,c_grid_shape,degrees, n_jobs, expand_collapse)
73
+ container_array = container_array.reshape(grid_shape_with_degree)
74
+ if plot:
75
+ from multipers.plots import plot_surfaces
76
+ plot_surfaces((grid_conversion, container_array))
77
+ return (grid_conversion, container_array)
78
+
79
+
80
+ def signed_measure(simplextree, vector[indices_type] degrees, mass_default=None, plot=False, indices_type n_jobs=0, bool verbose=False, bool expand_collapse=False):
81
+ """
82
+ Computes the signed measures given by the decomposition of the hilbert function.
83
+
84
+ Input
85
+ -----
86
+ - simplextree:SimplexTreeMulti, the multifiltered simplicial complex
87
+ - degrees:array-like of ints, the degrees to compute
88
+ - mass_default: Either None, or 'auto' or 'inf', or array-like of floats. Where to put the default mass to get a zero-mass measure.
89
+ - plot:bool, plots the computed measures if true.
90
+ - n_jobs:int, number of jobs. Defaults to #cpu, but when doing parallel computations of signed measures, we recommend setting this to 1.
91
+ - verbose:bool, prints c++ logs.
92
+
93
+ Output
94
+ ------
95
+ `[signed_measure_of_degree for degree in degrees]`
96
+ with `signed_measure_of_degree` of the form `(dirac location, dirac weights)`.
97
+ """
98
+ assert simplextree._is_squeezed > 0, "Squeeze grid first."
99
+ cdef bool zero_pad = mass_default is not None
100
+ grid_conversion = [np.asarray(f) for f in simplextree.filtration_grid]
101
+ # assert simplextree.num_parameters == 2
102
+ grid_shape = np.array([len(f) for f in grid_conversion])
103
+
104
+ if mass_default is None:
105
+ mass_default = mass_default
106
+ else:
107
+ mass_default = np.asarray(mass_default)
108
+ assert mass_default.ndim == 1 and mass_default.shape[0] == simplextree.num_parameters, "Mass default has to be an array like of shape (num_parameters,)"
109
+ if zero_pad:
110
+ for i, _ in enumerate(grid_shape):
111
+ grid_shape[i] += 1 # adds a 0
112
+ for i,f in enumerate(grid_conversion):
113
+ grid_conversion[i] = np.concatenate([f, [mass_default[i]]])
114
+
115
+ assert len(grid_shape) == simplextree.num_parameters, "Grid shape size has to be the number of parameters."
116
+ grid_shape_with_degree = np.asarray(np.concatenate([[len(degrees)], grid_shape, grid_shape]), dtype=python_indices_type)
117
+ container_array = np.ascontiguousarray(np.zeros(grid_shape_with_degree, dtype=python_tensor_dtype).flatten())
118
+ assert len(container_array) < np.iinfo(python_indices_type).max, "Too large container. Raise an issue on github if you encounter this issue. (Due to tensor's operator[])"
119
+ cdef intptr_t simplextree_ptr = simplextree.thisptr
120
+ cdef vector[indices_type] c_grid_shape = grid_shape_with_degree
121
+ cdef tensor_dtype[::1] container = container_array
122
+ cdef tensor_dtype* container_ptr = &container[0]
123
+ with nogil:
124
+ compute_rank_invariant_python(simplextree_ptr, container_ptr,c_grid_shape,degrees, n_jobs, expand_collapse)
125
+ rank = container_array.reshape(grid_shape_with_degree)
126
+ from multipers.ml.signed_betti import rank_decomposition_by_rectangles
127
+ from torch import Tensor
128
+ rank = [rank_decomposition_by_rectangles(rank_of_degree) for rank_of_degree in rank]
129
+ out = []
130
+ cdef int num_parameters = simplextree.num_parameters
131
+ for rank_decomposition in rank:
132
+ rank_decomposition = Tensor(np.ascontiguousarray(rank_decomposition)).to_sparse()
133
+ def _is_trivial(rectangle:np.ndarray):
134
+ birth=rectangle[:num_parameters]
135
+ death=rectangle[num_parameters:]
136
+ return np.all(birth<=death) and not np.array_equal(birth,death)
137
+ coords = np.asarray(rank_decomposition.indices().T, dtype=int)
138
+ weights = np.asarray(rank_decomposition.values(), dtype=int)
139
+ correct_indices = np.asarray([_is_trivial(rectangle) for rectangle in coords])
140
+ coords = coords[correct_indices]
141
+ weights = weights[correct_indices]
142
+ if len(correct_indices) == 0:
143
+ pts, weights = np.empty((0, 2*num_parameters)), np.empty((0))
144
+ else:
145
+ pts = np.empty(shape=coords.shape, dtype=grid_conversion[0].dtype)
146
+ for i in range(pts.shape[1]):
147
+ pts[:,i] = grid_conversion[i % num_parameters][coords[:,i]]
148
+ rank_decomposition = (pts,weights)
149
+ out.append(rank_decomposition)
150
+
151
+ if plot:
152
+ from multipers.plots import plot_signed_measures
153
+ plot_signed_measures(out)
154
+ return out
@@ -0,0 +1,121 @@
1
+ # This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT.
2
+ # See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details.
3
+ # Author(s): Vincent Rouvreau
4
+ #
5
+ # Copyright (C) 2016 Inria
6
+ #
7
+ # Modification(s):
8
+ # - 2022 David Loiseaux, Hannah Schreiber: adapt for multipersistence.
9
+ # - YYYY/MM Author: Description of the modification
10
+
11
+ from cython cimport numeric
12
+ from libcpp.vector cimport vector
13
+ from libcpp.utility cimport pair
14
+ from libcpp cimport bool
15
+ from libcpp.string cimport string
16
+
17
+ __author__ = "Vincent Rouvreau"
18
+ __copyright__ = "Copyright (C) 2016 Inria"
19
+ __license__ = "MIT"
20
+
21
+ ctypedef int dimension_type
22
+ ctypedef float value_type
23
+ ctypedef Finitely_critical_multi_filtration filtration_type
24
+ ctypedef vector[value_type] python_filtration_type ## TODO: move constructor for C++ filtration type ?
25
+ ctypedef vector[int] simplex_type
26
+ ctypedef vector[simplex_type] simplex_list
27
+ ctypedef vector[pair[pair[int,int], pair[double, double]]] edge_list
28
+ ctypedef vector[int] euler_char_list
29
+ ctypedef pair[simplex_type, value_type*] simplex_filtration_type
30
+
31
+
32
+
33
+ cdef extern from "gudhi/Simplex_tree/multi_filtrations/Finitely_critical_filtrations.h" namespace "Gudhi::multiparameter::multi_filtrations":
34
+ cdef cppclass Finitely_critical_multi_filtration "Gudhi::multiparameter::multi_filtrations::Finitely_critical_multi_filtration<Gudhi::multiparameter::Simplex_tree_options_multidimensional_filtration::value_type>":
35
+ Finitely_critical_multi_filtration() except + nogil
36
+ Finitely_critical_multi_filtration(vector[value_type]) except +
37
+ Finitely_critical_multi_filtration& operator=(const Finitely_critical_multi_filtration&)
38
+ filtration_type& get_vector() nogil
39
+ int size() nogil
40
+ void push_back(const value_type&) nogil
41
+ void clear() nogil
42
+ value_type& operator[](int)
43
+
44
+
45
+
46
+ cdef extern from "Simplex_tree_multi_interface.h" namespace "Gudhi::multiparameter":
47
+ cdef cppclass Simplex_tree_options_multidimensional_filtration:
48
+ pass
49
+
50
+ cdef cppclass Simplex_tree_multi_simplex_handle "Gudhi::multiparameter::Simplex_tree_multi_interface<Gudhi::multiparameter::Simplex_tree_options_multidimensional_filtration>::Simplex_handle":
51
+ pass
52
+
53
+ cdef cppclass Simplex_tree_multi_simplices_iterator "Gudhi::multiparameter::Simplex_tree_multi_interface<Gudhi::multiparameter::Simplex_tree_options_multidimensional_filtration>::Complex_simplex_iterator":
54
+ Simplex_tree_multi_simplices_iterator() nogil
55
+ Simplex_tree_multi_simplex_handle& operator*() nogil
56
+ Simplex_tree_multi_simplices_iterator operator++() nogil
57
+ bint operator!=(Simplex_tree_multi_simplices_iterator) nogil
58
+
59
+ cdef cppclass Simplex_tree_multi_skeleton_iterator "Gudhi::multiparameter::Simplex_tree_multi_interface<Gudhi::multiparameter::Simplex_tree_options_multidimensional_filtration>::Skeleton_simplex_iterator":
60
+ Simplex_tree_multi_skeleton_iterator() nogil
61
+ Simplex_tree_multi_simplex_handle& operator*() nogil
62
+ Simplex_tree_multi_skeleton_iterator operator++() nogil
63
+ bint operator!=(Simplex_tree_multi_skeleton_iterator) nogil
64
+
65
+ cdef cppclass Simplex_tree_multi_boundary_iterator "Gudhi::multiparameter::Simplex_tree_multi_interface<Gudhi::multiparameter::Simplex_tree_options_multidimensional_filtration>::Boundary_simplex_iterator":
66
+ Simplex_tree_multi_boundary_iterator() nogil
67
+ Simplex_tree_multi_simplex_handle& operator*() nogil
68
+ Simplex_tree_multi_boundary_iterator operator++() nogil
69
+ bint operator!=(Simplex_tree_multi_boundary_iterator) nogil
70
+
71
+
72
+ cdef cppclass Simplex_tree_multi_interface "Gudhi::multiparameter::Simplex_tree_multi_interface<Gudhi::multiparameter::Simplex_tree_options_multidimensional_filtration>":
73
+ Simplex_tree_multi_interface() nogil
74
+ Simplex_tree_multi_interface(Simplex_tree_multi_interface&) nogil
75
+ value_type* simplex_filtration(const vector[int]& simplex) nogil
76
+ void assign_simplex_filtration(vector[int]& simplex, const filtration_type& filtration) noexcept nogil
77
+ void initialize_filtration() nogil
78
+ int num_vertices() nogil
79
+ int num_simplices() nogil
80
+ void set_dimension(int dimension) nogil
81
+ dimension_type dimension() nogil
82
+ dimension_type upper_bound_dimension() nogil
83
+ bool find_simplex(vector[int]& simplex) nogil
84
+ bool insert(vector[int]& simplex, filtration_type& filtration) noexcept nogil
85
+ vector[simplex_filtration_type] get_star(const vector[int]& simplex) nogil
86
+ vector[simplex_filtration_type] get_cofaces(const vector[int]& simplex, int dimension) nogil
87
+ void expansion(int max_dim) except + nogil
88
+ void remove_maximal_simplex(simplex_type simplex) nogil
89
+ # bool prune_above_filtration(filtration_type filtration) nogil
90
+ bool prune_above_dimension(int dimension) nogil
91
+ bool make_filtration_non_decreasing() except + nogil
92
+ # void compute_extended_filtration() nogil
93
+ # Simplex_tree_multi_interface* collapse_edges(int nb_collapse_iteration) except + nogil
94
+ void reset_filtration(const filtration_type& filtration, int dimension) nogil
95
+ bint operator==(Simplex_tree_multi_interface) nogil
96
+ # Iterators over Simplex tree
97
+ simplex_filtration_type get_simplex_and_filtration(Simplex_tree_multi_simplex_handle f_simplex) nogil
98
+ Simplex_tree_multi_simplices_iterator get_simplices_iterator_begin() nogil
99
+ Simplex_tree_multi_simplices_iterator get_simplices_iterator_end() nogil
100
+ vector[Simplex_tree_multi_simplex_handle].const_iterator get_filtration_iterator_begin() nogil
101
+ vector[Simplex_tree_multi_simplex_handle].const_iterator get_filtration_iterator_end() nogil
102
+ Simplex_tree_multi_skeleton_iterator get_skeleton_iterator_begin(int dimension) nogil
103
+ Simplex_tree_multi_skeleton_iterator get_skeleton_iterator_end(int dimension) nogil
104
+ pair[Simplex_tree_multi_boundary_iterator, Simplex_tree_multi_boundary_iterator] get_boundary_iterators(vector[int] simplex) except + nogil
105
+ # Expansion with blockers
106
+ ctypedef bool (*blocker_func_t)(vector[int], void *user_data)
107
+ void expansion_with_blockers_callback(int dimension, blocker_func_t user_func, void *user_data)
108
+
109
+ ## MULTIPERS STUFF
110
+ void set_keys_to_enumerate() nogil const
111
+ int get_key(const simplex_type) nogil
112
+ void set_key(simplex_type, int) nogil
113
+ void fill_lowerstar(const vector[value_type]&, int) nogil
114
+ simplex_list get_simplices_of_dimension(int) nogil
115
+ edge_list get_edge_list() nogil
116
+ # euler_char_list euler_char(const vector[filtration_type]&) nogil
117
+ void resize_all_filtrations(int) nogil
118
+ void set_number_of_parameters(int) nogil
119
+ int get_number_of_parameters() nogil
120
+
121
+