multipers 1.0__cp311-cp311-manylinux_2_34_x86_64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of multipers might be problematic. Click here for more details.
- multipers/__init__.py +4 -0
- multipers/_old_rank_invariant.pyx +328 -0
- multipers/_signed_measure_meta.py +72 -0
- multipers/data/MOL2.py +350 -0
- multipers/data/UCR.py +18 -0
- multipers/data/__init__.py +1 -0
- multipers/data/graphs.py +272 -0
- multipers/data/immuno_regions.py +27 -0
- multipers/data/minimal_presentation_to_st_bf.py +0 -0
- multipers/data/pytorch2simplextree.py +91 -0
- multipers/data/shape3d.py +101 -0
- multipers/data/synthetic.py +68 -0
- multipers/distances.py +100 -0
- multipers/euler_characteristic.cpython-311-x86_64-linux-gnu.so +0 -0
- multipers/euler_characteristic.pyx +132 -0
- multipers/function_rips.cpython-311-x86_64-linux-gnu.so +0 -0
- multipers/function_rips.pyx +101 -0
- multipers/hilbert_function.cpython-311-x86_64-linux-gnu.so +0 -0
- multipers/hilbert_function.pyi +46 -0
- multipers/hilbert_function.pyx +145 -0
- multipers/ml/__init__.py +0 -0
- multipers/ml/accuracies.py +61 -0
- multipers/ml/convolutions.py +384 -0
- multipers/ml/invariants_with_persistable.py +79 -0
- multipers/ml/kernels.py +128 -0
- multipers/ml/mma.py +422 -0
- multipers/ml/one.py +472 -0
- multipers/ml/point_clouds.py +191 -0
- multipers/ml/signed_betti.py +50 -0
- multipers/ml/signed_measures.py +1046 -0
- multipers/ml/sliced_wasserstein.py +313 -0
- multipers/ml/tools.py +99 -0
- multipers/multiparameter_edge_collapse.py +29 -0
- multipers/multiparameter_module_approximation.cpython-311-x86_64-linux-gnu.so +0 -0
- multipers/multiparameter_module_approximation.pxd +147 -0
- multipers/multiparameter_module_approximation.pyi +439 -0
- multipers/multiparameter_module_approximation.pyx +931 -0
- multipers/pickle.py +53 -0
- multipers/plots.py +207 -0
- multipers/point_measure_integration.cpython-311-x86_64-linux-gnu.so +0 -0
- multipers/point_measure_integration.pyx +59 -0
- multipers/rank_invariant.cpython-311-x86_64-linux-gnu.so +0 -0
- multipers/rank_invariant.pyx +154 -0
- multipers/simplex_tree_multi.cpython-311-x86_64-linux-gnu.so +0 -0
- multipers/simplex_tree_multi.pxd +121 -0
- multipers/simplex_tree_multi.pyi +715 -0
- multipers/simplex_tree_multi.pyx +1284 -0
- multipers/tensor.pxd +13 -0
- multipers/test.pyx +44 -0
- multipers-1.0.dist-info/LICENSE +21 -0
- multipers-1.0.dist-info/METADATA +9 -0
- multipers-1.0.dist-info/RECORD +56 -0
- multipers-1.0.dist-info/WHEEL +5 -0
- multipers-1.0.dist-info/top_level.txt +1 -0
- multipers.libs/libtbb-5d1cde94.so.12.10 +0 -0
- multipers.libs/libtbbmalloc-5e0a3d4c.so.2.10 +0 -0
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
|
+
|
|
Binary file
|
|
@@ -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
|
|
Binary file
|
|
@@ -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
|
|
Binary file
|
|
@@ -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
|
+
|