multipers 2.3.3b7__cp313-cp313-win_amd64.whl → 2.3.5b1__cp313-cp313-win_amd64.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/_signed_measure_meta.py +50 -84
- multipers/array_api/numpy.py +12 -10
- multipers/array_api/torch.py +6 -3
- multipers/filtrations/filtrations.py +4 -2
- multipers/function_rips.cp313-win_amd64.pyd +0 -0
- multipers/grids.cp313-win_amd64.pyd +0 -0
- multipers/grids.pyx +51 -30
- multipers/gudhi/gudhi/Multi_critical_filtration.h +1 -1
- multipers/io.cp313-win_amd64.pyd +0 -0
- multipers/mma_structures.cp313-win_amd64.pyd +0 -0
- multipers/multi_parameter_rank_invariant/function_rips.h +1 -1
- multipers/multiparameter_module_approximation/approximation.h +8 -2
- multipers/multiparameter_module_approximation.cp313-win_amd64.pyd +0 -0
- multipers/plots.py +27 -12
- multipers/point_measure.cp313-win_amd64.pyd +0 -0
- multipers/point_measure.pyx +1 -1
- multipers/simplex_tree_multi.cp313-win_amd64.pyd +0 -0
- multipers/simplex_tree_multi.pxd +2 -2
- multipers/simplex_tree_multi.pyx +228 -56
- multipers/simplex_tree_multi.pyx.tp +23 -9
- multipers/slicer.cp313-win_amd64.pyd +0 -0
- multipers/slicer.pxd +40 -40
- multipers/slicer.pxd.tp +1 -1
- multipers/slicer.pyx +2054 -1139
- multipers/slicer.pyx.tp +100 -43
- multipers/tbb12.dll +0 -0
- multipers/tbbbind_2_5.dll +0 -0
- multipers/tbbmalloc.dll +0 -0
- multipers/tbbmalloc_proxy.dll +0 -0
- {multipers-2.3.3b7.dist-info → multipers-2.3.5b1.dist-info}/METADATA +2 -2
- {multipers-2.3.3b7.dist-info → multipers-2.3.5b1.dist-info}/RECORD +34 -34
- {multipers-2.3.3b7.dist-info → multipers-2.3.5b1.dist-info}/WHEEL +0 -0
- {multipers-2.3.3b7.dist-info → multipers-2.3.5b1.dist-info}/licenses/LICENSE +0 -0
- {multipers-2.3.3b7.dist-info → multipers-2.3.5b1.dist-info}/top_level.txt +0 -0
|
@@ -2,6 +2,7 @@ from collections.abc import Iterable, Sequence
|
|
|
2
2
|
from typing import Optional, Union
|
|
3
3
|
|
|
4
4
|
import numpy as np
|
|
5
|
+
import time
|
|
5
6
|
|
|
6
7
|
from multipers.grids import compute_grid, sms_in_grid
|
|
7
8
|
from multipers.plots import plot_signed_measures
|
|
@@ -176,10 +177,12 @@ def signed_measure(
|
|
|
176
177
|
|
|
177
178
|
if not filtered_complex.is_squeezed:
|
|
178
179
|
if verbose:
|
|
179
|
-
print("Coarsening complex...", end="")
|
|
180
|
+
print("Coarsening complex...", end="",flush=True)
|
|
181
|
+
t0 = time.time()
|
|
180
182
|
filtered_complex_ = filtered_complex.grid_squeeze(grid)
|
|
183
|
+
|
|
181
184
|
if verbose:
|
|
182
|
-
print("Done.")
|
|
185
|
+
print(f"Done. ({time.time() - t0:.3f}s)")
|
|
183
186
|
else:
|
|
184
187
|
filtered_complex_ = filtered_complex.copy()
|
|
185
188
|
|
|
@@ -190,26 +193,18 @@ def signed_measure(
|
|
|
190
193
|
else:
|
|
191
194
|
max_degree = np.max(degrees) + 1
|
|
192
195
|
if verbose:
|
|
193
|
-
print(f"Pruning simplicies up to {max_degree}...", end="")
|
|
196
|
+
print(f"Pruning simplicies up to {max_degree}...", end="", flush=True)
|
|
197
|
+
t0 = time.time()
|
|
194
198
|
if filtered_complex_.dimension > max_degree:
|
|
195
199
|
filtered_complex_.prune_above_dimension(max_degree)
|
|
196
200
|
if verbose:
|
|
197
|
-
print("Done.")
|
|
201
|
+
print(f"Done. ({time.time() - t0:.3f}s)")
|
|
198
202
|
|
|
199
203
|
num_parameters = filtered_complex.num_parameters
|
|
200
204
|
assert num_parameters == len(
|
|
201
205
|
grid
|
|
202
206
|
), f"Number of parameter do not coincide. Got (grid) {len(grid)} and (filtered complex) {num_parameters}."
|
|
203
207
|
|
|
204
|
-
# if is_simplextree_multi(filtered_complex_):
|
|
205
|
-
# # if num_collapses != 0:
|
|
206
|
-
# # if verbose:
|
|
207
|
-
# # print("Collapsing edges...", end="")
|
|
208
|
-
# # filtered_complex_.collapse_edges(num_collapses)
|
|
209
|
-
# # if verbose:
|
|
210
|
-
# # print("Done.")
|
|
211
|
-
# # if backend is not None:
|
|
212
|
-
# # filtered_complex_ = mp.Slicer(filtered_complex_, vineyard=vineyard)
|
|
213
208
|
|
|
214
209
|
fix_mass_default = mass_default is not None
|
|
215
210
|
if is_slicer(filtered_complex_):
|
|
@@ -217,59 +212,14 @@ def signed_measure(
|
|
|
217
212
|
print("Input is a slicer.")
|
|
218
213
|
if backend is not None and not filtered_complex_.is_minpres:
|
|
219
214
|
raise ValueError("giving a backend to this function is deprecated")
|
|
220
|
-
# from multipers.slicer import minimal_presentation
|
|
221
|
-
#
|
|
222
|
-
# assert (
|
|
223
|
-
# invariant != "euler"
|
|
224
|
-
# ), "Euler Characteristic cannot be speed up by a backend"
|
|
225
|
-
# # This returns a list of reduced complexes
|
|
226
|
-
# if verbose:
|
|
227
|
-
# print("Reducing complex...", end="")
|
|
228
|
-
# reduced_complex = minimal_presentation(
|
|
229
|
-
# filtered_complex_,
|
|
230
|
-
# degrees=degrees,
|
|
231
|
-
# backend=backend,
|
|
232
|
-
# vineyard=vineyard,
|
|
233
|
-
# verbose=verbose,
|
|
234
|
-
# )
|
|
235
|
-
# if verbose:
|
|
236
|
-
# print("Done.")
|
|
237
|
-
# if invariant is not None and "rank" in invariant:
|
|
238
|
-
# if verbose:
|
|
239
|
-
# print("Computing rank...", end="")
|
|
240
|
-
# sms = [
|
|
241
|
-
# _rank_from_slicer(
|
|
242
|
-
# s,
|
|
243
|
-
# degrees=[d],
|
|
244
|
-
# n_jobs=n_jobs,
|
|
245
|
-
# # grid_shape=tuple(len(g) for g in grid),
|
|
246
|
-
# zero_pad=fix_mass_default,
|
|
247
|
-
# ignore_inf=ignore_infinite_filtration_values,
|
|
248
|
-
# )[0]
|
|
249
|
-
# for s, d in zip(reduced_complex, degrees)
|
|
250
|
-
# ]
|
|
251
|
-
# fix_mass_default = False
|
|
252
|
-
# if verbose:
|
|
253
|
-
# print("Done.")
|
|
254
|
-
# else:
|
|
255
|
-
# if verbose:
|
|
256
|
-
# print("Reduced slicer. Retrieving measure from it...", end="")
|
|
257
|
-
# sms = [
|
|
258
|
-
# _signed_measure_from_slicer(
|
|
259
|
-
# s,
|
|
260
|
-
# shift=(
|
|
261
|
-
# reduced_complex.minpres_degree & 1 if d is None else d & 1
|
|
262
|
-
# ),
|
|
263
|
-
# )[0]
|
|
264
|
-
# for s, d in zip(reduced_complex, degrees)
|
|
265
|
-
# ]
|
|
266
|
-
# if verbose:
|
|
267
|
-
# print("Done.")
|
|
268
215
|
else: # No backend
|
|
269
|
-
if invariant is not None and (
|
|
216
|
+
if invariant is not None and (
|
|
217
|
+
"rank" in invariant or "hook" in invariant or "rectangle" in invariant
|
|
218
|
+
):
|
|
270
219
|
degrees = np.asarray(degrees, dtype=int)
|
|
271
220
|
if verbose:
|
|
272
|
-
print("Computing rank...", end="")
|
|
221
|
+
print("Computing rank...", end="", flush=True)
|
|
222
|
+
t0 = time.time()
|
|
273
223
|
sms = _rank_from_slicer(
|
|
274
224
|
filtered_complex_,
|
|
275
225
|
degrees=degrees,
|
|
@@ -280,10 +230,11 @@ def signed_measure(
|
|
|
280
230
|
)
|
|
281
231
|
fix_mass_default = False
|
|
282
232
|
if verbose:
|
|
283
|
-
print("Done.")
|
|
233
|
+
print(f"Done. ({time.time() - t0:.3f}s)")
|
|
284
234
|
elif filtered_complex_.is_minpres:
|
|
285
235
|
if verbose:
|
|
286
|
-
print("Reduced slicer. Retrieving measure from it...", end="")
|
|
236
|
+
print("Reduced slicer. Retrieving measure from it...", end="",flush=True)
|
|
237
|
+
t0 = time.time()
|
|
287
238
|
sms = [
|
|
288
239
|
_signed_measure_from_slicer(
|
|
289
240
|
filtered_complex_,
|
|
@@ -294,21 +245,23 @@ def signed_measure(
|
|
|
294
245
|
for d in degrees
|
|
295
246
|
]
|
|
296
247
|
if verbose:
|
|
297
|
-
print("Done.")
|
|
248
|
+
print(f"Done. ({time.time() - t0:.3f}s)")
|
|
298
249
|
elif (invariant is None or "euler" in invariant) and (
|
|
299
250
|
len(degrees) == 1 and degrees[0] is None
|
|
300
251
|
):
|
|
301
252
|
if verbose:
|
|
302
|
-
print("Retrieving measure from slicer...", end="")
|
|
253
|
+
print("Retrieving measure from slicer...", end="",flush=True)
|
|
254
|
+
t0 = time.time()
|
|
303
255
|
sms = _signed_measure_from_slicer(
|
|
304
256
|
filtered_complex_,
|
|
305
257
|
shift=0, # no minpres
|
|
306
258
|
)
|
|
307
259
|
if verbose:
|
|
308
|
-
print("Done.")
|
|
260
|
+
print(f"Done. ({time.time() - t0:.3f}s)")
|
|
309
261
|
else:
|
|
310
262
|
if verbose:
|
|
311
|
-
print("Computing Hilbert function...", end="")
|
|
263
|
+
print("Computing Hilbert function...", end="", flush=True)
|
|
264
|
+
t0 = time.time()
|
|
312
265
|
sms = _hilbert_signed_measure(
|
|
313
266
|
filtered_complex_,
|
|
314
267
|
degrees=degrees,
|
|
@@ -319,7 +272,7 @@ def signed_measure(
|
|
|
319
272
|
)
|
|
320
273
|
fix_mass_default = False
|
|
321
274
|
if verbose:
|
|
322
|
-
print("Done.")
|
|
275
|
+
print(f"Done. ({time.time() - t0:.3f}s)")
|
|
323
276
|
|
|
324
277
|
elif is_simplextree_multi(filtered_complex_):
|
|
325
278
|
if verbose:
|
|
@@ -327,7 +280,9 @@ def signed_measure(
|
|
|
327
280
|
## we still have a simplextree here
|
|
328
281
|
if invariant in ["rank_invariant", "rank", "hook", "rectangle"]:
|
|
329
282
|
if verbose:
|
|
330
|
-
print("Computing rank invariant...", end="")
|
|
283
|
+
print("Computing rank invariant...", end="", flush=True)
|
|
284
|
+
t0 = time.time()
|
|
285
|
+
|
|
331
286
|
assert (
|
|
332
287
|
num_parameters == 2
|
|
333
288
|
), "Rank invariant only implemented for 2-parameter modules."
|
|
@@ -342,10 +297,11 @@ def signed_measure(
|
|
|
342
297
|
)
|
|
343
298
|
fix_mass_default = False
|
|
344
299
|
if verbose:
|
|
345
|
-
print("Done.")
|
|
300
|
+
print(f"Done. ({time.time() - t0:.3f}s)")
|
|
346
301
|
elif len(degrees) == 1 and degrees[0] is None:
|
|
347
302
|
if verbose:
|
|
348
|
-
print("Computing Euler Characteristic...", end="")
|
|
303
|
+
print("Computing Euler Characteristic...", end="", flush=True)
|
|
304
|
+
t0 = time.time()
|
|
349
305
|
assert invariant is None or invariant in [
|
|
350
306
|
"euler",
|
|
351
307
|
"euler_characteristic",
|
|
@@ -362,10 +318,11 @@ def signed_measure(
|
|
|
362
318
|
]
|
|
363
319
|
fix_mass_default = False
|
|
364
320
|
if verbose:
|
|
365
|
-
print("Done.")
|
|
321
|
+
print(f"Done. ({time.time() - t0:.3f}s)")
|
|
366
322
|
else:
|
|
367
323
|
if verbose:
|
|
368
|
-
print("Computing Hilbert Function...", end="")
|
|
324
|
+
print("Computing Hilbert Function...", end="", flush=True)
|
|
325
|
+
t0 = time.time()
|
|
369
326
|
assert invariant is None or invariant in [
|
|
370
327
|
"hilbert",
|
|
371
328
|
"hilbert_function",
|
|
@@ -384,19 +341,21 @@ def signed_measure(
|
|
|
384
341
|
)
|
|
385
342
|
fix_mass_default = False
|
|
386
343
|
if verbose:
|
|
387
|
-
print("Done.")
|
|
344
|
+
print(f"Done. ({time.time() - t0:.3f}s)")
|
|
388
345
|
else:
|
|
389
346
|
raise ValueError("Filtered complex has to be a SimplexTree or a Slicer.")
|
|
390
347
|
|
|
391
348
|
if clean:
|
|
392
349
|
if verbose:
|
|
393
|
-
print("Cleaning measure...", end="")
|
|
350
|
+
print("Cleaning measure...", end="",flush=True)
|
|
351
|
+
t0 = time.time()
|
|
394
352
|
sms = clean_sms(sms)
|
|
395
353
|
if verbose:
|
|
396
|
-
print("Done.")
|
|
354
|
+
print(f"Done. ({time.time() - t0:.3f}s)")
|
|
397
355
|
if grid is not None and not coordinate_measure:
|
|
398
356
|
if verbose:
|
|
399
|
-
print("Pushing back the measure to the grid...", end="")
|
|
357
|
+
print("Pushing back the measure to the grid...", end="", flush=True)
|
|
358
|
+
t0 = time.time()
|
|
400
359
|
sms = sms_in_grid(
|
|
401
360
|
sms,
|
|
402
361
|
grid=grid,
|
|
@@ -404,21 +363,28 @@ def signed_measure(
|
|
|
404
363
|
# num_parameters=num_parameters,
|
|
405
364
|
)
|
|
406
365
|
if verbose:
|
|
407
|
-
print("Done.")
|
|
366
|
+
print(f"Done. ({time.time() - t0:.3f}s)")
|
|
408
367
|
|
|
409
368
|
if fix_mass_default:
|
|
410
369
|
# TODO : some methods need to use this, this could be optimized
|
|
411
370
|
if verbose:
|
|
412
|
-
print("Seems that fixing mass default is necessary...", end="")
|
|
371
|
+
print("Seems that fixing mass default is necessary...", end="", flush=True)
|
|
372
|
+
t0 = time.time()
|
|
413
373
|
sms = zero_out_sms(sms, mass_default=mass_default)
|
|
414
374
|
if verbose:
|
|
415
|
-
print("Done.")
|
|
375
|
+
print(f"Done. ({time.time() - t0:.3f}s)")
|
|
416
376
|
|
|
417
377
|
if invariant == "hook":
|
|
378
|
+
if verbose:
|
|
379
|
+
print("Converting rank decomposition to hook decomposition...", end="",flush=True)
|
|
380
|
+
t0 = time.time()
|
|
418
381
|
from multipers.point_measure import rectangle_to_hook_minimal_signed_barcode
|
|
419
|
-
|
|
382
|
+
|
|
383
|
+
sms = [rectangle_to_hook_minimal_signed_barcode(pts, w) for pts, w in sms]
|
|
384
|
+
if verbose:
|
|
385
|
+
print(f"Done. ({time.time() - t0:.3f}s)")
|
|
420
386
|
if plot:
|
|
421
|
-
plot_signed_measures(sms)
|
|
387
|
+
plot_signed_measures(sms, alpha=1 if invariant == "hook" else None)
|
|
422
388
|
return sms
|
|
423
389
|
|
|
424
390
|
|
multipers/array_api/numpy.py
CHANGED
|
@@ -31,17 +31,19 @@ def check_keops():
|
|
|
31
31
|
global _is_keops_available, LazyTensor
|
|
32
32
|
if _is_keops_available is not None:
|
|
33
33
|
return _is_keops_available
|
|
34
|
-
import pykeops.numpy as pknp
|
|
35
|
-
from pykeops.numpy import LazyTensor as LT
|
|
36
|
-
|
|
37
|
-
formula = "SqNorm2(x - y)"
|
|
38
|
-
var = ["x = Vi(3)", "y = Vj(3)"]
|
|
39
|
-
expected_res = _np.array([63.0, 90.0])
|
|
40
|
-
x = _np.arange(1, 10).reshape(-1, 3).astype("float32")
|
|
41
|
-
y = _np.arange(3, 9).reshape(-1, 3).astype("float32")
|
|
42
|
-
|
|
43
|
-
my_conv = pknp.Genred(formula, var)
|
|
44
34
|
try:
|
|
35
|
+
if _is_keops_available is not None:
|
|
36
|
+
return _is_keops_available
|
|
37
|
+
import pykeops.numpy as pknp
|
|
38
|
+
from pykeops.numpy import LazyTensor as LT
|
|
39
|
+
|
|
40
|
+
formula = "SqNorm2(x - y)"
|
|
41
|
+
var = ["x = Vi(3)", "y = Vj(3)"]
|
|
42
|
+
expected_res = _np.array([63.0, 90.0])
|
|
43
|
+
x = _np.arange(1, 10).reshape(-1, 3).astype("float32")
|
|
44
|
+
y = _np.arange(3, 9).reshape(-1, 3).astype("float32")
|
|
45
|
+
|
|
46
|
+
my_conv = pknp.Genred(formula, var)
|
|
45
47
|
_is_keops_available = _np.allclose(my_conv(x, y).flatten(), expected_res)
|
|
46
48
|
LazyTensor = LT
|
|
47
49
|
except:
|
multipers/array_api/torch.py
CHANGED
|
@@ -41,7 +41,7 @@ def check_keops():
|
|
|
41
41
|
|
|
42
42
|
my_conv = pknp.Genred(formula, var)
|
|
43
43
|
_is_keops_available = _t.allclose(
|
|
44
|
-
my_conv(x, y).view(-1),
|
|
44
|
+
my_conv(x, y).view(-1), expected_res.type(_t.float32)
|
|
45
45
|
)
|
|
46
46
|
LazyTensor = LT
|
|
47
47
|
|
|
@@ -55,6 +55,9 @@ def check_keops():
|
|
|
55
55
|
return _is_keops_available
|
|
56
56
|
|
|
57
57
|
|
|
58
|
+
check_keops()
|
|
59
|
+
|
|
60
|
+
|
|
58
61
|
def from_numpy(x):
|
|
59
62
|
return _t.from_numpy(x)
|
|
60
63
|
|
|
@@ -100,8 +103,8 @@ def maxvalues(x: _t.Tensor, **kwargs):
|
|
|
100
103
|
return _t.max(x, **kwargs).values
|
|
101
104
|
|
|
102
105
|
|
|
103
|
-
def asnumpy(x):
|
|
104
|
-
return x.detach().numpy()
|
|
106
|
+
def asnumpy(x: _t.Tensor):
|
|
107
|
+
return x.cpu().detach().numpy()
|
|
105
108
|
|
|
106
109
|
|
|
107
110
|
def is_tensor(x):
|
|
@@ -76,9 +76,10 @@ def RipsLowerstar(
|
|
|
76
76
|
st.fill_lowerstar(api.asnumpy(function[:, i]), parameter=1 + i)
|
|
77
77
|
if api.has_grad(D) or api.has_grad(function):
|
|
78
78
|
from multipers.grids import compute_grid
|
|
79
|
-
|
|
80
|
-
grid = compute_grid(
|
|
79
|
+
filtration_values = [D.ravel(), *[f for f in function.T]]
|
|
80
|
+
grid = compute_grid(filtration_values)
|
|
81
81
|
st = st.grid_squeeze(grid)
|
|
82
|
+
st._clean_filtration_grid()
|
|
82
83
|
return st
|
|
83
84
|
|
|
84
85
|
|
|
@@ -239,6 +240,7 @@ def Cubical(image: ArrayLike, **slicer_kwargs):
|
|
|
239
240
|
).reshape(slice_shape)
|
|
240
241
|
slicer = from_bitmap(coord_img, **slicer_kwargs)
|
|
241
242
|
slicer.filtration_grid = grid
|
|
243
|
+
slicer._clean_filtration_grid()
|
|
242
244
|
return slicer
|
|
243
245
|
|
|
244
246
|
return from_bitmap(image, **slicer_kwargs)
|
|
Binary file
|
|
Binary file
|
multipers/grids.pyx
CHANGED
|
@@ -124,8 +124,7 @@ def compute_grid(
|
|
|
124
124
|
except TypeError:
|
|
125
125
|
pass
|
|
126
126
|
|
|
127
|
-
|
|
128
|
-
return _compute_grid_numpy(
|
|
127
|
+
grid = _compute_grid_numpy(
|
|
129
128
|
initial_grid,
|
|
130
129
|
resolution=resolution,
|
|
131
130
|
strategy = strategy,
|
|
@@ -133,9 +132,9 @@ def compute_grid(
|
|
|
133
132
|
_q_factor=_q_factor,
|
|
134
133
|
drop_quantiles=drop_quantiles,
|
|
135
134
|
dense = dense,
|
|
136
|
-
|
|
137
|
-
from multipers.torch.diff_grids import get_grid
|
|
138
|
-
grid = get_grid(strategy)(initial_grid,resolution)
|
|
135
|
+
)
|
|
136
|
+
# from multipers.torch.diff_grids import get_grid
|
|
137
|
+
# grid = get_grid(strategy)(initial_grid,resolution)
|
|
139
138
|
if dense:
|
|
140
139
|
grid = todense(grid)
|
|
141
140
|
return grid
|
|
@@ -169,7 +168,7 @@ def _compute_grid_numpy(
|
|
|
169
168
|
Iterable[array[float, ndim=1]] : the 1d-grid for each parameter.
|
|
170
169
|
"""
|
|
171
170
|
num_parameters = len(filtrations_values)
|
|
172
|
-
api = api_from_tensors(filtrations_values)
|
|
171
|
+
api = api_from_tensors(*filtrations_values)
|
|
173
172
|
try:
|
|
174
173
|
a,b=drop_quantiles
|
|
175
174
|
except:
|
|
@@ -189,7 +188,7 @@ def _compute_grid_numpy(
|
|
|
189
188
|
elif strategy == "quantile":
|
|
190
189
|
F = tuple(api.unique(f) for f in filtrations_values)
|
|
191
190
|
max_resolution = [min(len(f),r) for f,r in zip(F,resolution)]
|
|
192
|
-
F = tuple( api.quantile_closest(f, q=
|
|
191
|
+
F = tuple( api.quantile_closest(f, q=api.linspace(0,1,int(r*_q_factor)), axis=0) for f,r in zip(F, resolution) )
|
|
193
192
|
if unique:
|
|
194
193
|
F = tuple(api.unique(f) for f in F)
|
|
195
194
|
if np.all(np.asarray(max_resolution) > np.asarray([len(f) for f in F])):
|
|
@@ -203,7 +202,7 @@ def _compute_grid_numpy(
|
|
|
203
202
|
# elif strategy == "torch_regular_closest":
|
|
204
203
|
# F = tuple(_torch_regular_closest(f,r, unique) for f,r in zip(filtrations_values, resolution))
|
|
205
204
|
elif strategy == "partition":
|
|
206
|
-
F = tuple(_todo_partition(f,r, unique) for f,r in zip(filtrations_values, resolution))
|
|
205
|
+
F = tuple(_todo_partition(f,r, unique, api) for f,r in zip(filtrations_values, resolution))
|
|
207
206
|
elif strategy == "precomputed":
|
|
208
207
|
F=filtrations_values
|
|
209
208
|
else:
|
|
@@ -215,7 +214,7 @@ def _compute_grid_numpy(
|
|
|
215
214
|
def todense(grid, bool product_order=False):
|
|
216
215
|
if len(grid) == 0:
|
|
217
216
|
return np.empty(0)
|
|
218
|
-
api = api_from_tensors(grid)
|
|
217
|
+
api = api_from_tensors(*grid)
|
|
219
218
|
# if product_order:
|
|
220
219
|
# if not api.backend ==np:
|
|
221
220
|
# raise NotImplementedError("only numpy here.")
|
|
@@ -235,8 +234,10 @@ def todense(grid, bool product_order=False):
|
|
|
235
234
|
|
|
236
235
|
|
|
237
236
|
|
|
238
|
-
## TODO : optimize. Pykeops ?
|
|
239
237
|
def _todo_regular(f, int r, api):
|
|
238
|
+
if api.has_grad(f):
|
|
239
|
+
from warnings import warn
|
|
240
|
+
warn("`strategy=regular` is not differentiable. Removing grad.")
|
|
240
241
|
with api.no_grad():
|
|
241
242
|
return api.linspace(api.min(f), api.max(f), r)
|
|
242
243
|
|
|
@@ -284,8 +285,14 @@ def _todo_regular_left_old(some_float[:] f, int r, bool unique):
|
|
|
284
285
|
if unique: f_regular_closest = np.unique(f_regular_closest)
|
|
285
286
|
return f_regular_closest
|
|
286
287
|
|
|
288
|
+
def _todo_partition(x, int resolution, bool unique, api):
|
|
289
|
+
if api.has_grad(x):
|
|
290
|
+
from warnings import warn
|
|
291
|
+
warn("`strategy=partition` is not differentiable. Removing grad.")
|
|
292
|
+
out = _todo_partition_(api.asnumpy(x), resolution, unique)
|
|
293
|
+
return api.from_numpy(out)
|
|
287
294
|
|
|
288
|
-
def
|
|
295
|
+
def _todo_partition_(some_float[:] data,int resolution, bool unique):
|
|
289
296
|
if data.shape[0] < resolution: resolution=data.shape[0]
|
|
290
297
|
k = data.shape[0] // resolution
|
|
291
298
|
partitions = np.partition(data, k)
|
|
@@ -362,7 +369,7 @@ def _inf_value(array):
|
|
|
362
369
|
return torch.iinfo(dtype).max
|
|
363
370
|
raise ValueError(f"Dtype must be integer or floating like (got {dtype})")
|
|
364
371
|
|
|
365
|
-
def evaluate_in_grid(pts, grid, mass_default=None):
|
|
372
|
+
def evaluate_in_grid(pts, grid, mass_default=None, input_inf_value=None, output_inf_value=None):
|
|
366
373
|
"""
|
|
367
374
|
Input
|
|
368
375
|
-----
|
|
@@ -372,28 +379,21 @@ def evaluate_in_grid(pts, grid, mass_default=None):
|
|
|
372
379
|
assert pts.ndim == 2
|
|
373
380
|
first_filtration = grid[0]
|
|
374
381
|
dtype = first_filtration.dtype
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
else:
|
|
381
|
-
import torch
|
|
382
|
-
# assert isinstance(first_filtration, torch.Tensor), f"Invalid grid type. Got {type(grid[0])}, expected numpy or torch array."
|
|
383
|
-
if mass_default is not None:
|
|
384
|
-
grid = tuple(torch.cat([g, torch.tensor(m)[None]]) for g,m in zip(grid, mass_default))
|
|
385
|
-
def empty_like(x):
|
|
386
|
-
return torch.empty(x.shape,dtype=dtype)
|
|
382
|
+
api = api_from_tensors(*grid)
|
|
383
|
+
if mass_default is not None:
|
|
384
|
+
grid = tuple(api.cat([g, api.astensor(m)[None]]) for g,m in zip(grid, mass_default))
|
|
385
|
+
def empty_like(x):
|
|
386
|
+
return api.empty(x.shape, dtype=dtype)
|
|
387
387
|
|
|
388
388
|
coords=empty_like(pts)
|
|
389
389
|
cdef int dim = coords.shape[1]
|
|
390
|
-
pts_inf = _inf_value(pts)
|
|
391
|
-
coords_inf = _inf_value(coords)
|
|
390
|
+
pts_inf = _inf_value(pts) if input_inf_value is None else input_inf_value
|
|
391
|
+
coords_inf = _inf_value(coords) if output_inf_value is None else output_inf_value
|
|
392
392
|
idx = np.argwhere(pts == pts_inf)
|
|
393
|
-
pts[idx]
|
|
393
|
+
pts[idx[:,0],idx[:,1]] = 0
|
|
394
394
|
for i in range(dim):
|
|
395
395
|
coords[:,i] = grid[i][pts[:,i]]
|
|
396
|
-
coords[idx] = coords_inf
|
|
396
|
+
coords[idx[:,0],idx[:,1]] = coords_inf
|
|
397
397
|
return coords
|
|
398
398
|
|
|
399
399
|
def sm_in_grid(pts, weights, grid, mass_default=None):
|
|
@@ -445,8 +445,9 @@ def sms_in_grid(sms, grid, mass_default=None):
|
|
|
445
445
|
return sms
|
|
446
446
|
|
|
447
447
|
|
|
448
|
-
def _push_pts_to_line(pts, basepoint, direction=None):
|
|
449
|
-
api
|
|
448
|
+
def _push_pts_to_line(pts, basepoint, direction=None, api=None):
|
|
449
|
+
if api is None:
|
|
450
|
+
api = api_from_tensors(pts, basepoint)
|
|
450
451
|
pts = api.astensor(pts)
|
|
451
452
|
basepoint = api.astensor(basepoint)
|
|
452
453
|
num_parameters = basepoint.shape[0]
|
|
@@ -472,3 +473,23 @@ def _push_pts_to_line(pts, basepoint, direction=None):
|
|
|
472
473
|
else:
|
|
473
474
|
xs = xa
|
|
474
475
|
return xs.squeeze()
|
|
476
|
+
|
|
477
|
+
def _push_pts_to_lines(pts, basepoints, directions=None, api=None):
|
|
478
|
+
if api is None:
|
|
479
|
+
api = api_from_tensors(pts,basepoints)
|
|
480
|
+
cdef int num_lines = len(basepoints)
|
|
481
|
+
cdef int num_pts = len(pts)
|
|
482
|
+
|
|
483
|
+
pts = api.astensor(pts)
|
|
484
|
+
basepoints = api.astensor(basepoints)
|
|
485
|
+
if directions is None:
|
|
486
|
+
directions = [None]*num_lines
|
|
487
|
+
else:
|
|
488
|
+
directions = api.astensor(directions)
|
|
489
|
+
|
|
490
|
+
out = api.empty((num_lines, num_pts), dtype=pts.dtype)
|
|
491
|
+
for i in range(num_lines):
|
|
492
|
+
out[i] = _push_pts_to_line(pts, basepoints[i], directions[i], api=api)[None]
|
|
493
|
+
return out
|
|
494
|
+
|
|
495
|
+
|
multipers/io.cp313-win_amd64.pyd
CHANGED
|
Binary file
|
|
Binary file
|
|
@@ -61,7 +61,7 @@ inline get_degree_filtrations( // also return max_degree,filtration_values
|
|
|
61
61
|
// preprocess
|
|
62
62
|
filtration_lists edge_filtration_of_nodes(st.num_vertices());
|
|
63
63
|
for (auto sh : st.complex_simplex_range()) {
|
|
64
|
-
if (st.dimension() == 0)
|
|
64
|
+
if (st.dimension(sh) == 0)
|
|
65
65
|
continue;
|
|
66
66
|
value_type filtration = st.filtration(sh);
|
|
67
67
|
for (auto node : st.simplex_vertex_range(sh)) {
|
|
@@ -1477,8 +1477,14 @@ std::vector<int> inline to_grid_coord(const Gudhi::multi_filtration::One_critica
|
|
|
1477
1477
|
out[i] = grid[i].size() - 1;
|
|
1478
1478
|
else if (pt[i] <= grid[i][0]) [[unlikely]] {
|
|
1479
1479
|
out[i] = 0;
|
|
1480
|
-
} else
|
|
1481
|
-
|
|
1480
|
+
} else {
|
|
1481
|
+
auto temp = std::distance(grid[i].begin(), std::lower_bound(grid[i].begin(), grid[i].end(), pt[i]));
|
|
1482
|
+
if (std::abs(grid[i][temp] - pt[i]) < std::abs(grid[i][temp - 1] - pt[i])) {
|
|
1483
|
+
out[i] = temp;
|
|
1484
|
+
} else {
|
|
1485
|
+
out[i] = temp - 1;
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1482
1488
|
}
|
|
1483
1489
|
return out;
|
|
1484
1490
|
}
|
|
Binary file
|
multipers/plots.py
CHANGED
|
@@ -81,7 +81,8 @@ def _plot_signed_measure_4(
|
|
|
81
81
|
**plt_kwargs, # ignored ftm
|
|
82
82
|
):
|
|
83
83
|
# compute the maximal rectangle area
|
|
84
|
-
pts = np.clip(pts, a_min=-np.inf,
|
|
84
|
+
pts = np.clip(pts, a_min=-np.inf,
|
|
85
|
+
a_max=np.array((*threshold, *threshold))[None, :])
|
|
85
86
|
alpha_rescaling = 0
|
|
86
87
|
for rectangle, weight in zip(pts, weights):
|
|
87
88
|
if rectangle[2] >= x_smoothing * rectangle[0]:
|
|
@@ -161,7 +162,7 @@ def plot_signed_measure(signed_measure, threshold=None, ax=None, **plt_kwargs):
|
|
|
161
162
|
)
|
|
162
163
|
|
|
163
164
|
|
|
164
|
-
def plot_signed_measures(signed_measures, threshold=None, size=4):
|
|
165
|
+
def plot_signed_measures(signed_measures, threshold=None, size=4, alpha=None):
|
|
165
166
|
num_degrees = len(signed_measures)
|
|
166
167
|
if num_degrees <= 1:
|
|
167
168
|
axes = [plt.gca()]
|
|
@@ -170,7 +171,9 @@ def plot_signed_measures(signed_measures, threshold=None, size=4):
|
|
|
170
171
|
nrows=1, ncols=num_degrees, figsize=(num_degrees * size, size)
|
|
171
172
|
)
|
|
172
173
|
for ax, signed_measure in zip(axes, signed_measures):
|
|
173
|
-
plot_signed_measure(signed_measure=signed_measure,
|
|
174
|
+
plot_signed_measure(signed_measure=signed_measure,
|
|
175
|
+
ax=ax, threshold=threshold,
|
|
176
|
+
alpha=alpha)
|
|
174
177
|
plt.tight_layout()
|
|
175
178
|
|
|
176
179
|
|
|
@@ -200,13 +203,17 @@ def plot_surface(
|
|
|
200
203
|
cmap = matplotlib.colormaps["gray_r"]
|
|
201
204
|
else:
|
|
202
205
|
cmap = _cmap
|
|
206
|
+
if discrete_surface or not contour:
|
|
207
|
+
# for shading="flat"
|
|
208
|
+
grid = [np.concatenate([g, [g[-1]*1.1 - .1*g[0]]]) for g in grid]
|
|
203
209
|
if discrete_surface:
|
|
204
210
|
if has_negative_values:
|
|
205
211
|
bounds = np.arange(-5, 6, 1, dtype=int)
|
|
206
212
|
else:
|
|
207
213
|
bounds = np.arange(0, 11, 1, dtype=int)
|
|
208
214
|
norm = matplotlib.colors.BoundaryNorm(bounds, cmap.N, extend="max")
|
|
209
|
-
im = ax.pcolormesh(grid[0], grid[1], hf.T, cmap=cmap,
|
|
215
|
+
im = ax.pcolormesh(grid[0], grid[1], hf.T, cmap=cmap,
|
|
216
|
+
norm=norm, shading="flat", **plt_args)
|
|
210
217
|
cbar = fig.colorbar(
|
|
211
218
|
matplotlib.cm.ScalarMappable(cmap=cmap, norm=norm),
|
|
212
219
|
spacing="proportional",
|
|
@@ -217,9 +224,11 @@ def plot_surface(
|
|
|
217
224
|
|
|
218
225
|
if contour:
|
|
219
226
|
levels = plt_args.pop("levels", 50)
|
|
220
|
-
im = ax.contourf(grid[0], grid[1], hf.T,
|
|
227
|
+
im = ax.contourf(grid[0], grid[1], hf.T,
|
|
228
|
+
cmap=cmap, levels=levels, **plt_args)
|
|
221
229
|
else:
|
|
222
|
-
im = ax.pcolormesh(grid[0], grid[1], hf.T,
|
|
230
|
+
im = ax.pcolormesh(grid[0], grid[1], hf.T,
|
|
231
|
+
cmap=cmap, shading="flat", **plt_args)
|
|
223
232
|
return im
|
|
224
233
|
|
|
225
234
|
|
|
@@ -314,25 +323,30 @@ def plot2d_PyModule(
|
|
|
314
323
|
trivial_summand = False
|
|
315
324
|
if shapely:
|
|
316
325
|
list_of_rect.append(
|
|
317
|
-
_rectangle_box(
|
|
326
|
+
_rectangle_box(
|
|
327
|
+
birth[0], birth[1], death[0], death[1])
|
|
318
328
|
)
|
|
319
329
|
else:
|
|
320
330
|
list_of_rect.append(
|
|
321
|
-
_rectangle(birth, death, cmap(
|
|
331
|
+
_rectangle(birth, death, cmap(
|
|
332
|
+
i / n_summands), alpha)
|
|
322
333
|
)
|
|
323
334
|
if not (trivial_summand):
|
|
324
335
|
if separated:
|
|
325
336
|
fig, ax = plt.subplots()
|
|
326
|
-
ax.set(xlim=[box[0][0], box[1][0]],
|
|
337
|
+
ax.set(xlim=[box[0][0], box[1][0]],
|
|
338
|
+
ylim=[box[0][1], box[1][1]])
|
|
327
339
|
if shapely:
|
|
328
340
|
summand_shape = union_all(list_of_rect)
|
|
329
341
|
if type(summand_shape) is _Polygon:
|
|
330
342
|
xs, ys = summand_shape.exterior.xy
|
|
331
|
-
ax.fill(xs, ys, alpha=alpha, fc=cmap(
|
|
343
|
+
ax.fill(xs, ys, alpha=alpha, fc=cmap(
|
|
344
|
+
i / n_summands), ec="None")
|
|
332
345
|
else:
|
|
333
346
|
for polygon in summand_shape.geoms:
|
|
334
347
|
xs, ys = polygon.exterior.xy
|
|
335
|
-
ax.fill(xs, ys, alpha=alpha, fc=cmap(
|
|
348
|
+
ax.fill(xs, ys, alpha=alpha, fc=cmap(
|
|
349
|
+
i / n_summands), ec="None")
|
|
336
350
|
else:
|
|
337
351
|
for rectangle in list_of_rect:
|
|
338
352
|
ax.add_patch(rectangle)
|
|
@@ -396,7 +410,8 @@ def plot_simplicial_complex(
|
|
|
396
410
|
if len(s) == 2: # simplexe = segment
|
|
397
411
|
xx = np.array([pts[a, 0] for a in s])
|
|
398
412
|
yy = np.array([pts[a, 1] for a in s])
|
|
399
|
-
plt.plot(xx, yy, c=color(density), alpha=1,
|
|
413
|
+
plt.plot(xx, yy, c=color(density), alpha=1,
|
|
414
|
+
zorder=10 * density, lw=1.5)
|
|
400
415
|
if len(s) == 3: # simplexe = triangle
|
|
401
416
|
xx = np.array([pts[a, 0] for a in s])
|
|
402
417
|
yy = np.array([pts[a, 1] for a in s])
|
|
Binary file
|