multipers 2.3.2b1__cp311-cp311-win_amd64.whl → 2.3.3__cp311-cp311-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 +22 -8
- multipers/array_api/__init__.py +25 -2
- multipers/array_api/numpy.py +70 -0
- multipers/array_api/torch.py +82 -0
- multipers/filtrations/density.py +11 -52
- multipers/filtrations/filtrations.py +21 -8
- multipers/function_rips.cp311-win_amd64.pyd +0 -0
- multipers/grids.cp311-win_amd64.pyd +0 -0
- multipers/grids.pyx +91 -43
- multipers/gudhi/gudhi/Multi_critical_filtration.h +1 -1
- multipers/io.cp311-win_amd64.pyd +0 -0
- multipers/ml/mma.py +1 -1
- multipers/ml/signed_measures.py +106 -26
- multipers/mma_structures.cp311-win_amd64.pyd +0 -0
- multipers/mma_structures.pyx +2 -2
- multipers/mma_structures.pyx.tp +1 -1
- multipers/multiparameter_module_approximation.cp311-win_amd64.pyd +0 -0
- multipers/multiparameter_module_approximation.pyx +2 -1
- multipers/plots.py +164 -37
- multipers/point_measure.cp311-win_amd64.pyd +0 -0
- multipers/point_measure.pyx +71 -2
- multipers/simplex_tree_multi.cp311-win_amd64.pyd +0 -0
- multipers/simplex_tree_multi.pxd +2 -2
- multipers/simplex_tree_multi.pyx +236 -36
- multipers/simplex_tree_multi.pyx.tp +18 -7
- multipers/slicer.cp311-win_amd64.pyd +0 -0
- multipers/slicer.pyx +63 -59
- multipers/slicer.pyx.tp +8 -4
- 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.2b1.dist-info → multipers-2.3.3.dist-info}/METADATA +6 -1
- {multipers-2.3.2b1.dist-info → multipers-2.3.3.dist-info}/RECORD +37 -37
- {multipers-2.3.2b1.dist-info → multipers-2.3.3.dist-info}/WHEEL +1 -1
- {multipers-2.3.2b1.dist-info → multipers-2.3.3.dist-info}/licenses/LICENSE +0 -0
- {multipers-2.3.2b1.dist-info → multipers-2.3.3.dist-info}/top_level.txt +0 -0
|
@@ -3,7 +3,6 @@ from typing import Optional, Union
|
|
|
3
3
|
|
|
4
4
|
import numpy as np
|
|
5
5
|
|
|
6
|
-
import multipers as mp
|
|
7
6
|
from multipers.grids import compute_grid, sms_in_grid
|
|
8
7
|
from multipers.plots import plot_signed_measures
|
|
9
8
|
from multipers.point_measure import clean_sms, zero_out_sms
|
|
@@ -31,7 +30,7 @@ def signed_measure(
|
|
|
31
30
|
verbose: bool = False,
|
|
32
31
|
n_jobs: int = -1,
|
|
33
32
|
expand_collapse: bool = False,
|
|
34
|
-
backend: Optional[str] = None,
|
|
33
|
+
backend: Optional[str] = None, # deprecated
|
|
35
34
|
grid: Optional[Iterable] = None,
|
|
36
35
|
coordinate_measure: bool = False,
|
|
37
36
|
num_collapses: int = 0, # TODO : deprecate
|
|
@@ -99,12 +98,21 @@ def signed_measure(
|
|
|
99
98
|
- Rank: Same as Hilbert.
|
|
100
99
|
"""
|
|
101
100
|
if backend is not None:
|
|
102
|
-
raise ValueError(
|
|
103
|
-
|
|
104
|
-
|
|
101
|
+
raise ValueError(
|
|
102
|
+
"backend is deprecated. reduce the complex before this function."
|
|
103
|
+
)
|
|
104
|
+
if num_collapses > 0:
|
|
105
|
+
raise ValueError(
|
|
106
|
+
"num_collapses is deprecated. reduce the complex before this function."
|
|
107
|
+
)
|
|
105
108
|
## TODO : add timings in verbose
|
|
106
109
|
if len(filtered_complex) == 0:
|
|
107
|
-
return [
|
|
110
|
+
return [
|
|
111
|
+
(
|
|
112
|
+
np.empty((0, 2), dtype=filtered_complex.dtype),
|
|
113
|
+
np.empty(shape=(0,), dtype=int),
|
|
114
|
+
)
|
|
115
|
+
]
|
|
108
116
|
if grid_conversion is not None:
|
|
109
117
|
grid = tuple(f for f in grid_conversion)
|
|
110
118
|
raise DeprecationWarning(
|
|
@@ -134,6 +142,8 @@ def signed_measure(
|
|
|
134
142
|
"rank",
|
|
135
143
|
"euler_characteristic",
|
|
136
144
|
"hilbert_function",
|
|
145
|
+
"rectangle",
|
|
146
|
+
"hook",
|
|
137
147
|
]
|
|
138
148
|
|
|
139
149
|
assert (
|
|
@@ -256,7 +266,7 @@ def signed_measure(
|
|
|
256
266
|
# if verbose:
|
|
257
267
|
# print("Done.")
|
|
258
268
|
else: # No backend
|
|
259
|
-
if invariant is not None and "rank" in invariant:
|
|
269
|
+
if invariant is not None and ("rank" in invariant or "hook" in invariant or "rectangle" in invariant):
|
|
260
270
|
degrees = np.asarray(degrees, dtype=int)
|
|
261
271
|
if verbose:
|
|
262
272
|
print("Computing rank...", end="")
|
|
@@ -315,7 +325,7 @@ def signed_measure(
|
|
|
315
325
|
if verbose:
|
|
316
326
|
print("Input is a simplextree.")
|
|
317
327
|
## we still have a simplextree here
|
|
318
|
-
if invariant in ["rank_invariant", "rank"]:
|
|
328
|
+
if invariant in ["rank_invariant", "rank", "hook", "rectangle"]:
|
|
319
329
|
if verbose:
|
|
320
330
|
print("Computing rank invariant...", end="")
|
|
321
331
|
assert (
|
|
@@ -403,6 +413,10 @@ def signed_measure(
|
|
|
403
413
|
sms = zero_out_sms(sms, mass_default=mass_default)
|
|
404
414
|
if verbose:
|
|
405
415
|
print("Done.")
|
|
416
|
+
|
|
417
|
+
if invariant == "hook":
|
|
418
|
+
from multipers.point_measure import rectangle_to_hook_minimal_signed_barcode
|
|
419
|
+
sms = [rectangle_to_hook_minimal_signed_barcode(pts,w) for pts,w in sms]
|
|
406
420
|
if plot:
|
|
407
421
|
plot_signed_measures(sms)
|
|
408
422
|
return sms
|
multipers/array_api/__init__.py
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import multipers.array_api.numpy as npapi
|
|
2
|
+
|
|
3
3
|
|
|
4
|
+
def api_from_tensor(x, *, verbose: bool = False, strict=False):
|
|
5
|
+
if strict:
|
|
6
|
+
if npapi.is_tensor(x):
|
|
7
|
+
return npapi
|
|
8
|
+
import multipers.array_api.torch as torchapi
|
|
9
|
+
|
|
10
|
+
if torchapi.is_tensor(x):
|
|
11
|
+
return torchapi
|
|
12
|
+
raise ValueError(f"Unsupported (strict) type {type(x)=}")
|
|
4
13
|
if npapi.is_promotable(x):
|
|
5
14
|
if verbose:
|
|
6
15
|
print("using numpy backend")
|
|
@@ -37,3 +46,17 @@ def api_from_tensors(*args):
|
|
|
37
46
|
if is_torch:
|
|
38
47
|
return torchapi
|
|
39
48
|
raise ValueError(f"Incompatible types got {[type(x) for x in args]=}.")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def to_numpy(x):
|
|
52
|
+
api = api_from_tensor(x)
|
|
53
|
+
return api.asnumpy(x)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def check_keops():
|
|
57
|
+
import os
|
|
58
|
+
|
|
59
|
+
if os.name == "nt":
|
|
60
|
+
# see https://github.com/getkeops/keops/pull/421
|
|
61
|
+
return False
|
|
62
|
+
return npapi.check_keops()
|
multipers/array_api/numpy.py
CHANGED
|
@@ -16,6 +16,72 @@ no_grad = nullcontext
|
|
|
16
16
|
zeros = _np.zeros
|
|
17
17
|
min = _np.min
|
|
18
18
|
max = _np.max
|
|
19
|
+
repeat_interleave = _np.repeat
|
|
20
|
+
cdist = cdist # type: ignore[no-redef]
|
|
21
|
+
unique = _np.unique
|
|
22
|
+
inf = _np.inf
|
|
23
|
+
searchsorted = _np.searchsorted
|
|
24
|
+
LazyTensor = None
|
|
25
|
+
|
|
26
|
+
# Test keops
|
|
27
|
+
_is_keops_available = None
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def check_keops():
|
|
31
|
+
global _is_keops_available, LazyTensor
|
|
32
|
+
if _is_keops_available is not None:
|
|
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
|
+
try:
|
|
45
|
+
_is_keops_available = _np.allclose(my_conv(x, y).flatten(), expected_res)
|
|
46
|
+
LazyTensor = LT
|
|
47
|
+
except:
|
|
48
|
+
from warnings import warn
|
|
49
|
+
|
|
50
|
+
warn("Could not initialize keops (numpy). using workarounds")
|
|
51
|
+
_is_keops_available = False
|
|
52
|
+
|
|
53
|
+
return _is_keops_available
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def from_numpy(x):
|
|
57
|
+
return _np.asarray(x)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def ascontiguous(x):
|
|
61
|
+
return _np.ascontiguousarray(x)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def sort(x, axis=-1):
|
|
65
|
+
return _np.sort(x, axis=axis)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def device(x): # type: ignore[no-unused-arg]
|
|
69
|
+
return None
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# type: ignore[no-unused-arg]
|
|
73
|
+
def linspace(low, high, r, device=None, dtype=None):
|
|
74
|
+
return _np.linspace(low, high, r, dtype=dtype)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def cartesian_product(*arrays, dtype=None):
|
|
78
|
+
mesh = _np.meshgrid(*arrays, indexing="ij")
|
|
79
|
+
coordinates = _np.stack(mesh, axis=-1).reshape(-1, len(arrays)).astype(dtype)
|
|
80
|
+
return coordinates
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def quantile_closest(x, q, axis=None):
|
|
84
|
+
return _np.quantile(x, q, axis=axis, method="closest_observation")
|
|
19
85
|
|
|
20
86
|
|
|
21
87
|
def minvalues(x: _np.ndarray, **kwargs):
|
|
@@ -26,6 +92,10 @@ def maxvalues(x: _np.ndarray, **kwargs):
|
|
|
26
92
|
return _np.max(x, **kwargs)
|
|
27
93
|
|
|
28
94
|
|
|
95
|
+
def is_tensor(x):
|
|
96
|
+
return isinstance(x, _np.ndarray)
|
|
97
|
+
|
|
98
|
+
|
|
29
99
|
def is_promotable(x):
|
|
30
100
|
return isinstance(x, _np.ndarray | list | tuple)
|
|
31
101
|
|
multipers/array_api/torch.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import numpy as _np
|
|
1
2
|
import torch as _t
|
|
2
3
|
|
|
3
4
|
backend = _t
|
|
@@ -13,6 +14,83 @@ cdist = _t.cdist
|
|
|
13
14
|
zeros = _t.zeros
|
|
14
15
|
min = _t.min
|
|
15
16
|
max = _t.max
|
|
17
|
+
repeat_interleave = _t.repeat_interleave
|
|
18
|
+
linspace = _t.linspace
|
|
19
|
+
cartesian_product = _t.cartesian_prod
|
|
20
|
+
inf = _t.inf
|
|
21
|
+
searchsorted = _t.searchsorted
|
|
22
|
+
LazyTensor = None
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
_is_keops_available = None
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def check_keops():
|
|
29
|
+
global _is_keops_available, LazyTensor
|
|
30
|
+
if _is_keops_available is not None:
|
|
31
|
+
return _is_keops_available
|
|
32
|
+
try:
|
|
33
|
+
import pykeops.torch as pknp
|
|
34
|
+
from pykeops.torch import LazyTensor as LT
|
|
35
|
+
|
|
36
|
+
formula = "SqNorm2(x - y)"
|
|
37
|
+
var = ["x = Vi(3)", "y = Vj(3)"]
|
|
38
|
+
expected_res = _t.tensor([63.0, 90.0])
|
|
39
|
+
x = _t.arange(1, 10, dtype=_t.float32).view(-1, 3)
|
|
40
|
+
y = _t.arange(3, 9, dtype=_t.float32).view(-1, 3)
|
|
41
|
+
|
|
42
|
+
my_conv = pknp.Genred(formula, var)
|
|
43
|
+
_is_keops_available = _t.allclose(
|
|
44
|
+
my_conv(x, y).view(-1), expected_res.type(_t.float32)
|
|
45
|
+
)
|
|
46
|
+
LazyTensor = LT
|
|
47
|
+
|
|
48
|
+
except:
|
|
49
|
+
from warnings import warn
|
|
50
|
+
|
|
51
|
+
warn("Could not initialize keops (torch). using workarounds")
|
|
52
|
+
|
|
53
|
+
_is_keops_available = False
|
|
54
|
+
|
|
55
|
+
return _is_keops_available
|
|
56
|
+
check_keops()
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def from_numpy(x):
|
|
60
|
+
return _t.from_numpy(x)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def ascontiguous(x):
|
|
64
|
+
return _t.as_tensor(x).contiguous()
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def device(x):
|
|
68
|
+
return x.device
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def sort(x, axis=-1):
|
|
72
|
+
return _t.sort(x, dim=axis).values
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
# in our context, this allows to get a correct gradient.
|
|
76
|
+
def unique(x, assume_sorted=False, _mean=True):
|
|
77
|
+
if not x.requires_grad:
|
|
78
|
+
return x.unique(sorted=assume_sorted)
|
|
79
|
+
if x.ndim != 1:
|
|
80
|
+
raise ValueError(f"Got ndim!=1. {x=}")
|
|
81
|
+
if not assume_sorted:
|
|
82
|
+
x = x.sort().values
|
|
83
|
+
_, c = _t.unique(x, sorted=True, return_counts=True)
|
|
84
|
+
if _mean:
|
|
85
|
+
x = _t.segment_reduce(data=x, reduce="mean", lengths=c, unsafe=True, axis=0)
|
|
86
|
+
else:
|
|
87
|
+
c = _np.concatenate([[0], _np.cumsum(c[:-1])])
|
|
88
|
+
x = x[c]
|
|
89
|
+
return x
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def quantile_closest(x, q, axis=None):
|
|
93
|
+
return _t.quantile(x, q, dim=axis, interpolation="nearest")
|
|
16
94
|
|
|
17
95
|
|
|
18
96
|
def minvalues(x: _t.Tensor, **kwargs):
|
|
@@ -27,6 +105,10 @@ def asnumpy(x):
|
|
|
27
105
|
return x.detach().numpy()
|
|
28
106
|
|
|
29
107
|
|
|
108
|
+
def is_tensor(x):
|
|
109
|
+
return isinstance(x, _t.Tensor)
|
|
110
|
+
|
|
111
|
+
|
|
30
112
|
def is_promotable(x):
|
|
31
113
|
return isinstance(x, _t.Tensor)
|
|
32
114
|
|
multipers/filtrations/density.py
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
from collections.abc import Callable, Iterable
|
|
2
2
|
from typing import Any, Literal, Union
|
|
3
|
+
|
|
3
4
|
import numpy as np
|
|
4
5
|
|
|
6
|
+
from multipers.array_api import api_from_tensor, api_from_tensors
|
|
5
7
|
|
|
6
|
-
from multipers.array_api import api_from_tensor
|
|
7
8
|
global available_kernels
|
|
8
9
|
available_kernels = Union[
|
|
9
10
|
Literal[
|
|
@@ -176,23 +177,24 @@ def _pts_convolution_pykeops(
|
|
|
176
177
|
Pykeops convolution
|
|
177
178
|
"""
|
|
178
179
|
if isinstance(pts, np.ndarray):
|
|
179
|
-
_asarray_weights = lambda x
|
|
180
|
+
_asarray_weights = lambda x: np.asarray(x, dtype=pts.dtype)
|
|
180
181
|
_asarray_grid = _asarray_weights
|
|
181
182
|
else:
|
|
182
183
|
import torch
|
|
183
|
-
|
|
184
|
-
|
|
184
|
+
|
|
185
|
+
_asarray_weights = lambda x: torch.from_numpy(x).type(pts.dtype)
|
|
186
|
+
_asarray_grid = lambda x: x.type(pts.dtype)
|
|
185
187
|
kde = KDE(kernel=kernel, bandwidth=bandwidth, **more_kde_args)
|
|
186
|
-
return kde.fit(
|
|
187
|
-
|
|
188
|
-
)
|
|
188
|
+
return kde.fit(pts, sample_weights=_asarray_weights(pts_weights)).score_samples(
|
|
189
|
+
_asarray_grid(grid_iterator)
|
|
190
|
+
)
|
|
189
191
|
|
|
190
192
|
|
|
191
193
|
def gaussian_kernel(x_i, y_j, bandwidth):
|
|
192
194
|
D = x_i.shape[-1]
|
|
193
195
|
exponent = -(((x_i - y_j) / bandwidth) ** 2).sum(dim=-1) / 2
|
|
194
196
|
# float is necessary for some reason (pykeops fails)
|
|
195
|
-
kernel = (exponent).exp() / float((bandwidth*np.sqrt(2 * np.pi))**D)
|
|
197
|
+
kernel = (exponent).exp() / float((bandwidth * np.sqrt(2 * np.pi)) ** D)
|
|
196
198
|
return kernel
|
|
197
199
|
|
|
198
200
|
|
|
@@ -359,49 +361,6 @@ class KDE:
|
|
|
359
361
|
)
|
|
360
362
|
|
|
361
363
|
|
|
362
|
-
def batch_signed_measure_convolutions(
|
|
363
|
-
signed_measures, # array of shape (num_data,num_pts,D)
|
|
364
|
-
x, # array of shape (num_x, D) or (num_data, num_x, D)
|
|
365
|
-
bandwidth, # either float or matrix if multivariate kernel
|
|
366
|
-
kernel: available_kernels,
|
|
367
|
-
):
|
|
368
|
-
"""
|
|
369
|
-
Input
|
|
370
|
-
-----
|
|
371
|
-
- signed_measures: unragged, of shape (num_data, num_pts, D+1)
|
|
372
|
-
where last coord is weights, (0 for dummy points)
|
|
373
|
-
- x : the points to convolve (num_x,D)
|
|
374
|
-
- bandwidth : the bandwidths or covariance matrix inverse or ... of the kernel
|
|
375
|
-
- kernel : "gaussian", "multivariate_gaussian", "exponential", or Callable (x_i, y_i, bandwidth)->float
|
|
376
|
-
|
|
377
|
-
Output
|
|
378
|
-
------
|
|
379
|
-
Array of shape (num_convolutions, (num_axis), num_data,
|
|
380
|
-
Array of shape (num_convolutions, (num_axis), num_data, max_x_size)
|
|
381
|
-
"""
|
|
382
|
-
if signed_measures.ndim == 2:
|
|
383
|
-
signed_measures = signed_measures[None, :, :]
|
|
384
|
-
sms = signed_measures[..., :-1]
|
|
385
|
-
weights = signed_measures[..., -1]
|
|
386
|
-
if isinstance(signed_measures, np.ndarray):
|
|
387
|
-
from pykeops.numpy import LazyTensor
|
|
388
|
-
else:
|
|
389
|
-
import torch
|
|
390
|
-
|
|
391
|
-
assert isinstance(signed_measures, torch.Tensor)
|
|
392
|
-
from pykeops.torch import LazyTensor
|
|
393
|
-
|
|
394
|
-
_sms = LazyTensor(sms[..., None, :].contiguous())
|
|
395
|
-
_x = x[..., None, :, :].contiguous()
|
|
396
|
-
|
|
397
|
-
sms_kernel = _kernel(kernel)(_sms, _x, bandwidth)
|
|
398
|
-
out = (sms_kernel * weights[..., None, None].contiguous()).sum(
|
|
399
|
-
signed_measures.ndim - 2
|
|
400
|
-
)
|
|
401
|
-
assert out.shape[-1] == 1, "Pykeops bug fixed, TODO : refix this "
|
|
402
|
-
out = out[..., 0] ## pykeops bug + ensures its a tensor
|
|
403
|
-
# assert out.shape == (x.shape[0], x.shape[1]), f"{x.shape=}, {out.shape=}"
|
|
404
|
-
return out
|
|
405
364
|
|
|
406
365
|
|
|
407
366
|
class DTM:
|
|
@@ -532,7 +491,7 @@ class KNNmean:
|
|
|
532
491
|
|
|
533
492
|
# Symbolic distance matrix:
|
|
534
493
|
if self.metric == "euclidean":
|
|
535
|
-
D_ij = ((X_i - X_j) ** 2).sum(-1) ** (1/2)
|
|
494
|
+
D_ij = ((X_i - X_j) ** 2).sum(-1) ** (1 / 2)
|
|
536
495
|
elif self.metric == "manhattan":
|
|
537
496
|
D_ij = (X_i - X_j).abs().sum(-1)
|
|
538
497
|
elif self.metric == "angular":
|
|
@@ -17,7 +17,6 @@ try:
|
|
|
17
17
|
|
|
18
18
|
from multipers.filtrations.density import KDE
|
|
19
19
|
except ImportError:
|
|
20
|
-
|
|
21
20
|
from sklearn.neighbors import KernelDensity
|
|
22
21
|
|
|
23
22
|
warn("pykeops not found. Falling back to sklearn.")
|
|
@@ -67,7 +66,9 @@ def RipsLowerstar(
|
|
|
67
66
|
function = function[:, None]
|
|
68
67
|
if function.ndim != 2:
|
|
69
68
|
raise ValueError(
|
|
70
|
-
f"
|
|
69
|
+
f"""
|
|
70
|
+
`function.ndim` should be 0 or 1 . Got {function.ndim=}.{function=}
|
|
71
|
+
"""
|
|
71
72
|
)
|
|
72
73
|
num_parameters = function.shape[1] + 1
|
|
73
74
|
st = SimplexTreeMulti(st, num_parameters=num_parameters)
|
|
@@ -75,8 +76,8 @@ def RipsLowerstar(
|
|
|
75
76
|
st.fill_lowerstar(api.asnumpy(function[:, i]), parameter=1 + i)
|
|
76
77
|
if api.has_grad(D) or api.has_grad(function):
|
|
77
78
|
from multipers.grids import compute_grid
|
|
78
|
-
|
|
79
|
-
grid = compute_grid(
|
|
79
|
+
filtration_values = [D.ravel(), *[f for f in function.T]]
|
|
80
|
+
grid = compute_grid(filtration_values)
|
|
80
81
|
st = st.grid_squeeze(grid)
|
|
81
82
|
return st
|
|
82
83
|
|
|
@@ -154,6 +155,9 @@ def DelaunayLowerstar(
|
|
|
154
155
|
verbose=verbose,
|
|
155
156
|
clear=clear,
|
|
156
157
|
)
|
|
158
|
+
if reduce_degree >= 0:
|
|
159
|
+
# Force resolution to avoid confusion with hilbert.
|
|
160
|
+
slicer = slicer.minpres(degree=reduce_degree, force=True)
|
|
157
161
|
if flagify:
|
|
158
162
|
from multipers.slicer import to_simplextree
|
|
159
163
|
|
|
@@ -192,7 +196,7 @@ def DelaunayCodensity(
|
|
|
192
196
|
), "Density estimation is either via kernels or dtm."
|
|
193
197
|
if bandwidth is not None:
|
|
194
198
|
kde = KDE(bandwidth=bandwidth, kernel=kernel, return_log=return_log)
|
|
195
|
-
f = kde.fit(points).score_samples(points)
|
|
199
|
+
f = -kde.fit(points).score_samples(points)
|
|
196
200
|
elif dtm_mass is not None:
|
|
197
201
|
f = DTM(masses=[dtm_mass]).fit(points).score_samples(points)[0]
|
|
198
202
|
else:
|
|
@@ -287,11 +291,17 @@ def CoreDelaunay(
|
|
|
287
291
|
"safe",
|
|
288
292
|
"exact",
|
|
289
293
|
"fast",
|
|
290
|
-
], f"
|
|
294
|
+
], f"""
|
|
295
|
+
The parameter precision must be one of ['safe', 'exact', 'fast'],
|
|
296
|
+
got {precision}.
|
|
297
|
+
"""
|
|
291
298
|
|
|
292
299
|
if verbose:
|
|
293
300
|
print(
|
|
294
|
-
f"Computing the Delaunay Core Bifiltration
|
|
301
|
+
f"""Computing the Delaunay Core Bifiltration
|
|
302
|
+
of {len(points)} points in dimension {points.shape[1]}
|
|
303
|
+
with parameters:
|
|
304
|
+
"""
|
|
295
305
|
)
|
|
296
306
|
print(f"\tbeta = {beta}")
|
|
297
307
|
print(f"\tks = {ks}")
|
|
@@ -333,7 +343,10 @@ def CoreDelaunay(
|
|
|
333
343
|
num_simplices = len(vertex_array)
|
|
334
344
|
if verbose:
|
|
335
345
|
print(
|
|
336
|
-
f"
|
|
346
|
+
f"""
|
|
347
|
+
Inserting {num_simplices} simplices of dimension {dim}
|
|
348
|
+
({num_simplices * len(ks)} birth values)...
|
|
349
|
+
"""
|
|
337
350
|
)
|
|
338
351
|
max_knn_distances = np.max(knn_distances[vertex_array], axis=1)
|
|
339
352
|
critical_radii = np.maximum(alphas[:, None], beta * max_knn_distances)
|
|
Binary file
|
|
Binary file
|