multipers 2.3.2b1__cp310-cp310-win_amd64.whl → 2.3.3__cp310-cp310-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.

Files changed (38) hide show
  1. multipers/_signed_measure_meta.py +22 -8
  2. multipers/array_api/__init__.py +25 -2
  3. multipers/array_api/numpy.py +70 -0
  4. multipers/array_api/torch.py +82 -0
  5. multipers/filtrations/density.py +11 -52
  6. multipers/filtrations/filtrations.py +21 -8
  7. multipers/function_rips.cp310-win_amd64.pyd +0 -0
  8. multipers/grids.cp310-win_amd64.pyd +0 -0
  9. multipers/grids.pyx +91 -43
  10. multipers/gudhi/gudhi/Multi_critical_filtration.h +1 -1
  11. multipers/io.cp310-win_amd64.pyd +0 -0
  12. multipers/ml/mma.py +1 -1
  13. multipers/ml/signed_measures.py +106 -26
  14. multipers/mma_structures.cp310-win_amd64.pyd +0 -0
  15. multipers/mma_structures.pyx +2 -2
  16. multipers/mma_structures.pyx.tp +1 -1
  17. multipers/multiparameter_module_approximation.cp310-win_amd64.pyd +0 -0
  18. multipers/multiparameter_module_approximation.pyx +2 -1
  19. multipers/plots.py +164 -37
  20. multipers/point_measure.cp310-win_amd64.pyd +0 -0
  21. multipers/point_measure.pyx +71 -2
  22. multipers/simplex_tree_multi.cp310-win_amd64.pyd +0 -0
  23. multipers/simplex_tree_multi.pxd +2 -2
  24. multipers/simplex_tree_multi.pyx +236 -36
  25. multipers/simplex_tree_multi.pyx.tp +18 -7
  26. multipers/slicer.cp310-win_amd64.pyd +0 -0
  27. multipers/slicer.pxd +20 -20
  28. multipers/slicer.pyx +57 -53
  29. multipers/slicer.pyx.tp +8 -4
  30. multipers/tbb12.dll +0 -0
  31. multipers/tbbbind_2_5.dll +0 -0
  32. multipers/tbbmalloc.dll +0 -0
  33. multipers/tbbmalloc_proxy.dll +0 -0
  34. {multipers-2.3.2b1.dist-info → multipers-2.3.3.dist-info}/METADATA +6 -1
  35. {multipers-2.3.2b1.dist-info → multipers-2.3.3.dist-info}/RECORD +38 -38
  36. {multipers-2.3.2b1.dist-info → multipers-2.3.3.dist-info}/WHEEL +1 -1
  37. {multipers-2.3.2b1.dist-info → multipers-2.3.3.dist-info}/licenses/LICENSE +0 -0
  38. {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, # deprecated
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("backend is deprecated. reduce the complex before this function.")
103
- if num_collapses >0:
104
- raise ValueError("num_collapses is deprecated. reduce the complex before this function.")
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 [(np.empty((0,2), dtype=filtered_complex.dtype), np.empty(shape=(0,), dtype=int))]
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
@@ -1,6 +1,15 @@
1
- def api_from_tensor(x, *, verbose: bool = False):
2
- import multipers.array_api.numpy as npapi
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()
@@ -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
 
@@ -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
 
@@ -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 : np.asarray(x, dtype=pts.dtype)
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
- _asarray_weights = lambda x : torch.from_numpy(x).type(pts.dtype)
184
- _asarray_grid = lambda x : x.type(pts.dtype)
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
- pts, sample_weights=_asarray_weights(pts_weights)
188
- ).score_samples(_asarray_grid(grid_iterator))
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"`function.ndim` should be 0 or 1 . Got {function.ndim=}.{function=}"
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([D.ravel(), *[f for f in function.T]])
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"The parameter precision must be one of ['safe', 'exact', 'fast'], got {precision}."
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 of {len(points)} points in dimension {points.shape[1]} with parameters:"
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"Inserting {num_simplices} simplices of dimension {dim} ({num_simplices * len(ks)} birth values)..."
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