multipers 2.0.0__cp310-cp310-macosx_13_0_arm64.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 (78) hide show
  1. multipers/.dylibs/libc++.1.0.dylib +0 -0
  2. multipers/.dylibs/libtbb.12.12.dylib +0 -0
  3. multipers/.dylibs/libtbbmalloc.2.12.dylib +0 -0
  4. multipers/__init__.py +11 -0
  5. multipers/_signed_measure_meta.py +268 -0
  6. multipers/_slicer_meta.py +171 -0
  7. multipers/data/MOL2.py +350 -0
  8. multipers/data/UCR.py +18 -0
  9. multipers/data/__init__.py +1 -0
  10. multipers/data/graphs.py +466 -0
  11. multipers/data/immuno_regions.py +27 -0
  12. multipers/data/minimal_presentation_to_st_bf.py +0 -0
  13. multipers/data/pytorch2simplextree.py +91 -0
  14. multipers/data/shape3d.py +101 -0
  15. multipers/data/synthetic.py +68 -0
  16. multipers/distances.py +198 -0
  17. multipers/euler_characteristic.pyx +132 -0
  18. multipers/filtration_conversions.pxd +229 -0
  19. multipers/filtrations.pxd +225 -0
  20. multipers/function_rips.cpython-310-darwin.so +0 -0
  21. multipers/function_rips.pyx +105 -0
  22. multipers/grids.cpython-310-darwin.so +0 -0
  23. multipers/grids.pyx +281 -0
  24. multipers/hilbert_function.pyi +46 -0
  25. multipers/hilbert_function.pyx +153 -0
  26. multipers/io.cpython-310-darwin.so +0 -0
  27. multipers/io.pyx +571 -0
  28. multipers/ml/__init__.py +0 -0
  29. multipers/ml/accuracies.py +90 -0
  30. multipers/ml/convolutions.py +532 -0
  31. multipers/ml/invariants_with_persistable.py +79 -0
  32. multipers/ml/kernels.py +176 -0
  33. multipers/ml/mma.py +659 -0
  34. multipers/ml/one.py +472 -0
  35. multipers/ml/point_clouds.py +238 -0
  36. multipers/ml/signed_betti.py +50 -0
  37. multipers/ml/signed_measures.py +1542 -0
  38. multipers/ml/sliced_wasserstein.py +461 -0
  39. multipers/ml/tools.py +113 -0
  40. multipers/mma_structures.cpython-310-darwin.so +0 -0
  41. multipers/mma_structures.pxd +127 -0
  42. multipers/mma_structures.pyx +2433 -0
  43. multipers/multiparameter_edge_collapse.py +41 -0
  44. multipers/multiparameter_module_approximation.cpython-310-darwin.so +0 -0
  45. multipers/multiparameter_module_approximation.pyx +211 -0
  46. multipers/pickle.py +53 -0
  47. multipers/plots.py +326 -0
  48. multipers/point_measure_integration.cpython-310-darwin.so +0 -0
  49. multipers/point_measure_integration.pyx +139 -0
  50. multipers/rank_invariant.cpython-310-darwin.so +0 -0
  51. multipers/rank_invariant.pyx +229 -0
  52. multipers/simplex_tree_multi.cpython-310-darwin.so +0 -0
  53. multipers/simplex_tree_multi.pxd +129 -0
  54. multipers/simplex_tree_multi.pyi +715 -0
  55. multipers/simplex_tree_multi.pyx +4655 -0
  56. multipers/slicer.cpython-310-darwin.so +0 -0
  57. multipers/slicer.pxd +781 -0
  58. multipers/slicer.pyx +3393 -0
  59. multipers/tensor.pxd +13 -0
  60. multipers/test.pyx +44 -0
  61. multipers/tests/__init__.py +40 -0
  62. multipers/tests/old_test_rank_invariant.py +91 -0
  63. multipers/tests/test_diff_helper.py +74 -0
  64. multipers/tests/test_hilbert_function.py +82 -0
  65. multipers/tests/test_mma.py +51 -0
  66. multipers/tests/test_point_clouds.py +59 -0
  67. multipers/tests/test_python-cpp_conversion.py +82 -0
  68. multipers/tests/test_signed_betti.py +181 -0
  69. multipers/tests/test_simplextreemulti.py +98 -0
  70. multipers/tests/test_slicer.py +63 -0
  71. multipers/torch/__init__.py +1 -0
  72. multipers/torch/diff_grids.py +217 -0
  73. multipers/torch/rips_density.py +257 -0
  74. multipers-2.0.0.dist-info/LICENSE +21 -0
  75. multipers-2.0.0.dist-info/METADATA +29 -0
  76. multipers-2.0.0.dist-info/RECORD +78 -0
  77. multipers-2.0.0.dist-info/WHEEL +5 -0
  78. multipers-2.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,532 @@
1
+ from collections.abc import Callable
2
+ from itertools import product
3
+ from typing import Any, Iterable, Literal
4
+
5
+ import numpy as np
6
+
7
+
8
+ def convolution_signed_measures(
9
+ iterable_of_signed_measures,
10
+ filtrations,
11
+ bandwidth,
12
+ flatten: bool = True,
13
+ n_jobs: int = 1,
14
+ backend="pykeops",
15
+ kernel="gaussian",
16
+ **kwargs,
17
+ ):
18
+ """
19
+ Evaluates the convolution of the signed measures Iterable(pts, weights) with a gaussian measure of bandwidth bandwidth, on a grid given by the filtrations
20
+
21
+ Parameters
22
+ ----------
23
+
24
+ - iterable_of_signed_measures : (num_signed_measure) x [ (npts) x (num_parameters), (npts)]
25
+ - filtrations : (num_parameter) x (filtration values)
26
+ - flatten : bool
27
+ - n_jobs : int
28
+
29
+ Outputs
30
+ -------
31
+
32
+ The concatenated images, for each signed measure (num_signed_measures) x (len(f) for f in filtration_values)
33
+ """
34
+ grid_iterator = np.array(list(product(*filtrations)), dtype=float)
35
+ match backend:
36
+ case "sklearn":
37
+
38
+ def convolution_signed_measures_on_grid(
39
+ signed_measures: Iterable[tuple[np.ndarray, np.ndarray]],
40
+ ):
41
+ return np.concatenate(
42
+ [
43
+ _pts_convolution_sparse_old(
44
+ pts=pts,
45
+ pts_weights=weights,
46
+ grid_iterator=grid_iterator,
47
+ bandwidth=bandwidth,
48
+ kernel=kernel,
49
+ **kwargs,
50
+ )
51
+ for pts, weights in signed_measures
52
+ ],
53
+ axis=0,
54
+ )
55
+
56
+ case "pykeops":
57
+
58
+ def convolution_signed_measures_on_grid(
59
+ signed_measures: Iterable[tuple[np.ndarray, np.ndarray]],
60
+ ):
61
+ return np.concatenate(
62
+ [
63
+ _pts_convolution_pykeops(
64
+ pts=pts,
65
+ pts_weights=weights,
66
+ grid_iterator=grid_iterator,
67
+ bandwidth=bandwidth,
68
+ kernel=kernel,
69
+ **kwargs,
70
+ )
71
+ for pts, weights in signed_measures
72
+ ],
73
+ axis=0,
74
+ )
75
+
76
+ # compiles first once
77
+ pts, weights = iterable_of_signed_measures[0][0]
78
+ small_pts, small_weights = pts[:2], weights[:2]
79
+
80
+ _pts_convolution_pykeops(
81
+ small_pts,
82
+ small_weights,
83
+ grid_iterator=grid_iterator,
84
+ bandwidth=bandwidth,
85
+ kernel=kernel,
86
+ **kwargs,
87
+ )
88
+
89
+ if n_jobs > 1 or n_jobs == -1:
90
+ prefer = "processes" if backend == "sklearn" else "threads"
91
+ from joblib import Parallel, delayed
92
+
93
+ convolutions = Parallel(n_jobs=n_jobs, prefer=prefer)(
94
+ delayed(convolution_signed_measures_on_grid)(sms)
95
+ for sms in iterable_of_signed_measures
96
+ )
97
+ else:
98
+ convolutions = [
99
+ convolution_signed_measures_on_grid(sms)
100
+ for sms in iterable_of_signed_measures
101
+ ]
102
+ if not flatten:
103
+ out_shape = [-1] + [len(f) for f in filtrations] # Degree
104
+ convolutions = [x.reshape(out_shape) for x in convolutions]
105
+ return np.asarray(convolutions, dtype=float)
106
+
107
+
108
+ # def _test(r=1000, b=0.5, plot=True, kernel=0):
109
+ # import matplotlib.pyplot as plt
110
+ # pts, weigths = np.array([[1.,1.], [1.1,1.1]]), np.array([1,-1])
111
+ # pt_list = np.array(list(product(*[np.linspace(0,2,r)]*2)))
112
+ # img = _pts_convolution_sparse_pts(pts,weigths, pt_list,b,kernel=kernel)
113
+ # if plot:
114
+ # plt.imshow(img.reshape(r,-1).T, origin="lower")
115
+ # plt.show()
116
+
117
+
118
+ def _pts_convolution_sparse_old(
119
+ pts: np.ndarray,
120
+ pts_weights: np.ndarray,
121
+ grid_iterator,
122
+ kernel="gaussian",
123
+ bandwidth=0.1,
124
+ **more_kde_args,
125
+ ):
126
+ """
127
+ Old version of `convolution_signed_measures`. Scikitlearn's convolution is slower than the code above.
128
+ """
129
+ from sklearn.neighbors import KernelDensity
130
+
131
+ if len(pts) == 0:
132
+ # warn("Found a trivial signed measure !")
133
+ return np.zeros(len(grid_iterator))
134
+ kde = KernelDensity(
135
+ kernel=kernel, bandwidth=bandwidth, rtol=1e-4, **more_kde_args
136
+ ) # TODO : check rtol
137
+ pos_indices = pts_weights > 0
138
+ neg_indices = pts_weights < 0
139
+ img_pos = (
140
+ np.zeros(len(grid_iterator))
141
+ if pos_indices.sum() == 0
142
+ else kde.fit(
143
+ pts[pos_indices], sample_weight=pts_weights[pos_indices]
144
+ ).score_samples(grid_iterator)
145
+ )
146
+ img_neg = (
147
+ np.zeros(len(grid_iterator))
148
+ if neg_indices.sum() == 0
149
+ else kde.fit(
150
+ pts[neg_indices], sample_weight=-pts_weights[neg_indices]
151
+ ).score_samples(grid_iterator)
152
+ )
153
+ return np.exp(img_pos) - np.exp(img_neg)
154
+
155
+
156
+ def _pts_convolution_pykeops(
157
+ pts: np.ndarray,
158
+ pts_weights: np.ndarray,
159
+ grid_iterator,
160
+ kernel="gaussian",
161
+ bandwidth=0.1,
162
+ **more_kde_args,
163
+ ):
164
+ """
165
+ Pykeops convolution
166
+ """
167
+ kde = KDE(kernel=kernel, bandwidth=bandwidth, **more_kde_args)
168
+ return kde.fit(
169
+ pts, sample_weights=np.asarray(pts_weights, dtype=pts.dtype)
170
+ ).score_samples(np.asarray(grid_iterator, dtype=pts.dtype))
171
+
172
+
173
+ def gaussian_kernel(x_i, y_j, bandwidth):
174
+ exponent = -(((x_i - y_j) / bandwidth) ** 2).sum(dim=-1) / 2
175
+ # float is necessary for some reason (pykeops fails)
176
+ kernel = (exponent).exp() / (bandwidth * float(np.sqrt(2 * np.pi)))
177
+ return kernel
178
+
179
+
180
+ def multivariate_gaussian_kernel(x_i, y_j, covariance_matrix_inverse):
181
+ # 1 / \sqrt(2 \pi^dim * \Sigma.det()) * exp( -(x-y).T @ \Sigma ^{-1} @ (x-y))
182
+ # CF https://www.kernel-operations.io/keops/_auto_examples/pytorch/plot_anisotropic_kernels.html#sphx-glr-auto-examples-pytorch-plot-anisotropic-kernels-py
183
+ # and https://www.kernel-operations.io/keops/api/math-operations.html
184
+ dim = x_i.shape[-1]
185
+ z = x_i - y_j
186
+ exponent = -(z.weightedsqnorm(covariance_matrix_inverse.flatten()) / 2)
187
+ return (
188
+ float((2 * np.pi) ** (-dim / 2))
189
+ * (covariance_matrix_inverse.det().sqrt())
190
+ * exponent.exp()
191
+ )
192
+
193
+
194
+ def exponential_kernel(x_i, y_j, bandwidth):
195
+ exponent = -(((((x_i - y_j) ** 2).sum()) ** 1 / 2) / bandwidth).sum(dim=-1)
196
+ kernel = exponent.exp() / bandwidth
197
+ return kernel
198
+
199
+
200
+ def _kernel(
201
+ kernel: (
202
+ Literal["gaussian", "exponential", "multivariate_gaussian"] | Callable
203
+ ) = "gaussian",
204
+ ):
205
+ match kernel:
206
+ case "gaussian":
207
+ return gaussian_kernel
208
+ case "exponential":
209
+ return exponential_kernel
210
+ case "multivariate_gaussian":
211
+ return multivariate_gaussian_kernel
212
+ case _:
213
+ assert callable(
214
+ kernel
215
+ ), f"--------------------------\nUnknown kernel {kernel}.\n--------------------------\n Custom kernel has to be callable, (x:LazyTensor(n,1,D),y:LazyTensor(1,m,D),bandwidth:float) ---> kernel matrix"
216
+ return kernel
217
+
218
+
219
+ # TODO : multiple bandwidths at once with lazy tensors
220
+ class KDE:
221
+ """
222
+ Fast, scikit-style, and differentiable kernel density estimation, using PyKeops.
223
+ """
224
+
225
+ def __init__(
226
+ self,
227
+ bandwidth: Any = 1,
228
+ kernel: (
229
+ Literal["m_gaussian", "gaussian", "exponential"] | Callable
230
+ ) = "gaussian",
231
+ return_log=False,
232
+ ):
233
+ """
234
+ bandwidth : numeric
235
+ bandwidth for Gaussian kernel
236
+ """
237
+ self.X = None
238
+ self.bandwidth = bandwidth
239
+ self.kernel = kernel
240
+ self._kernel = None
241
+ self._backend = None
242
+ self._sample_weights = None
243
+ self.return_log = return_log
244
+
245
+ def fit(self, X, sample_weights=None, y=None):
246
+ self.X = X
247
+ self._sample_weights = sample_weights
248
+ if isinstance(X, np.ndarray):
249
+ self._backend = np
250
+ else:
251
+ import torch
252
+
253
+ if isinstance(X, torch.Tensor):
254
+ self._backend = torch
255
+ else:
256
+ raise Exception("Unsupported backend.")
257
+ match self.kernel:
258
+ case "gaussian":
259
+ self._kernel = self.gaussian_kernel
260
+ case "m_gaussian":
261
+ self._kernel = self.multivariate_gaussian_kernel
262
+ case "exponential":
263
+ self._kernel = self.exponential_kernel
264
+ case _:
265
+ assert callable(
266
+ self.kernel
267
+ ), f"""
268
+ --------------------------
269
+ Unknown kernel {self.kernel}.
270
+ --------------------------
271
+ Custom kernel has to be callable,
272
+ (x:LazyTensor(n,1,D),y:LazyTensor(1,m,D),bandwidth:float) ---> kernel matrix
273
+ """
274
+ self._kernel = self.kernel
275
+ return self
276
+
277
+ @staticmethod
278
+ def gaussian_kernel(x_i, y_j, bandwidth):
279
+ exponent = -(((x_i - y_j) / bandwidth) ** 2).sum(dim=2) / 2
280
+ # float is necessary for some reason (pykeops fails)
281
+ kernel = (exponent).exp() / (bandwidth * float(np.sqrt(2 * np.pi)))
282
+ return kernel
283
+
284
+ @staticmethod
285
+ def multivariate_gaussian_kernel(x_i, y_j, covariance_matrix_inverse):
286
+ # 1 / \sqrt(2 \pi^dim * \Sigma.det()) * exp( -(x-y).T @ \Sigma ^{-1} @ (x-y))
287
+ # CF https://www.kernel-operations.io/keops/_auto_examples/pytorch/plot_anisotropic_kernels.html#sphx-glr-auto-examples-pytorch-plot-anisotropic-kernels-py
288
+ # and https://www.kernel-operations.io/keops/api/math-operations.html
289
+ dim = x_i.shape[-1]
290
+ z = x_i - y_j
291
+ exponent = -(z.weightedsqnorm(covariance_matrix_inverse.flatten()) / 2)
292
+ return (
293
+ float((2 * np.pi) ** (-dim / 2))
294
+ * (covariance_matrix_inverse.det().sqrt())
295
+ * exponent.exp()
296
+ )
297
+
298
+ @staticmethod
299
+ def exponential_kernel(x_i, y_j, bandwidth):
300
+ exponent = -(((((x_i - y_j) ** 2).sum()) ** 1 / 2) / bandwidth).sum(dim=2)
301
+ kernel = exponent.exp() / bandwidth
302
+ return kernel
303
+
304
+ @staticmethod
305
+ def to_lazy(X, Y, x_weights):
306
+ if isinstance(X, np.ndarray):
307
+ from pykeops.numpy import LazyTensor
308
+
309
+ lazy_x = LazyTensor(
310
+ X.reshape((X.shape[0], 1, X.shape[1]))
311
+ ) # numpts, 1, dim
312
+ lazy_y = LazyTensor(
313
+ Y.reshape((1, Y.shape[0], Y.shape[1]))
314
+ ) # 1, numpts, dim
315
+ if x_weights is not None:
316
+ w = LazyTensor(x_weights[:, None], axis=0)
317
+ return lazy_x, lazy_y, w
318
+ return lazy_x, lazy_y, None
319
+ import torch
320
+
321
+ if isinstance(X, torch.Tensor):
322
+ from pykeops.torch import LazyTensor
323
+
324
+ lazy_x = LazyTensor(X.view(X.shape[0], 1, X.shape[1]))
325
+ lazy_y = LazyTensor(Y.view(1, Y.shape[0], Y.shape[1]))
326
+ if x_weights is not None:
327
+ w = LazyTensor(x_weights[:, None], axis=0)
328
+ return lazy_x, lazy_y, w
329
+ return lazy_x, lazy_y, None
330
+ raise Exception("Bad tensor type.")
331
+
332
+ def score_samples(self, Y, X=None, return_kernel=False):
333
+ """Returns the kernel density estimates of each point in `Y`.
334
+
335
+ Parameters
336
+ ----------
337
+ Y : tensor (m, d)
338
+ `m` points with `d` dimensions for which the probability density will
339
+ be calculated
340
+ X : tensor (n, d), optional
341
+ `n` points with `d` dimensions to which KDE will be fit. Provided to
342
+ allow batch calculations in `log_prob`. By default, `X` is None and
343
+ all points used to initialize KernelDensityEstimator are included.
344
+
345
+
346
+ Returns
347
+ -------
348
+ log_probs : tensor (m)
349
+ log probability densities for each of the queried points in `Y`
350
+ """
351
+ X = self.X if X is None else X
352
+ if X.shape[0] == 0:
353
+ return self._backend.zeros((Y.shape[0]))
354
+ assert Y.shape[1] == X.shape[1] and X.ndim == Y.ndim == 2
355
+ lazy_x, lazy_y, w = self.to_lazy(X, Y, x_weights=self._sample_weights)
356
+ kernel = self._kernel(lazy_x, lazy_y, self.bandwidth)
357
+ if w is not None:
358
+ kernel *= w
359
+ if return_kernel:
360
+ return kernel
361
+ density_estimation = kernel.sum(dim=0).flatten() / kernel.shape[0] # mean
362
+ return (
363
+ self._backend.log(density_estimation)
364
+ if self.return_log
365
+ else density_estimation
366
+ )
367
+
368
+
369
+ def batch_signed_measure_convolutions(
370
+ signed_measures, # array of shape (num_data,num_pts,D)
371
+ x, # array of shape (num_x, D) or (num_data, num_x, D)
372
+ bandwidth, # either float or matrix if multivariate kernel
373
+ kernel,
374
+ ):
375
+ """
376
+ Input
377
+ -----
378
+ - signed_measures: unragged, of shape (num_data, num_pts, D+1)
379
+ where last coord is weights, (0 for dummy points)
380
+ - x : the points to convolve (num_x,D)
381
+ - bandwidth : the bandwidths or covariance matrix inverse or ... of the kernel
382
+ - kernel : "gaussian", "multivariate_gaussian", "exponential", or Callable (x_i, y_i, bandwidth)->float
383
+
384
+ Output
385
+ ------
386
+ Array of shape (num_convolutions, (num_axis), num_data,
387
+ Array of shape (num_convolutions, (num_axis), num_data, max_x_size)
388
+ """
389
+ if signed_measures.ndim == 2:
390
+ signed_measures = signed_measures[None, :, :]
391
+ sms = signed_measures[..., :-1]
392
+ weights = signed_measures[..., -1]
393
+ if isinstance(signed_measures, np.ndarray):
394
+ from pykeops.numpy import LazyTensor
395
+ else:
396
+ import torch
397
+
398
+ assert isinstance(signed_measures, torch.Tensor)
399
+ from pykeops.torch import LazyTensor
400
+
401
+ _sms = LazyTensor(sms[..., None, :].contiguous())
402
+ _x = x[..., None, :, :].contiguous()
403
+
404
+ sms_kernel = _kernel(kernel)(_sms, _x, bandwidth)
405
+ out = (sms_kernel * weights[..., None, None].contiguous()).sum(
406
+ signed_measures.ndim - 2
407
+ )
408
+ assert out.shape[-1] == 1, "Pykeops bug fixed, TODO : refix this "
409
+ out = out[..., 0] ## pykeops bug + ensures its a tensor
410
+ # assert out.shape == (x.shape[0], x.shape[1]), f"{x.shape=}, {out.shape=}"
411
+ return out
412
+
413
+
414
+ class DTM:
415
+ """
416
+ Distance To Measure
417
+ """
418
+
419
+ def __init__(self, masses=[0.1], metric: str = "euclidean", **_kdtree_kwargs):
420
+ """
421
+ mass : float in [0,1]
422
+ The mass threshold
423
+ metric :
424
+ The distance between points to consider
425
+ """
426
+ self.masses = masses
427
+ self.metric = metric
428
+ self._kdtree_kwargs = _kdtree_kwargs
429
+ self._ks = None
430
+ self._kdtree = None
431
+ self._X = None
432
+ self._backend = None
433
+
434
+ def fit(self, X, sample_weights=None, y=None):
435
+ if len(self.masses) == 0:
436
+ return self
437
+ assert np.max(self.masses) <= 1, "All masses should be in (0,1]."
438
+ from sklearn.neighbors import KDTree
439
+
440
+ if not isinstance(X, np.ndarray):
441
+ import torch
442
+
443
+ assert isinstance(X, torch.Tensor), "Backend has to be numpy of torch"
444
+ _X = X.detach()
445
+ self._backend = "torch"
446
+ else:
447
+ _X = X
448
+ self._backend = "numpy"
449
+ self._ks = np.array([int(mass * X.shape[0]) + 1 for mass in self.masses])
450
+ self._kdtree = KDTree(_X, metric=self.metric, **self._kdtree_kwargs)
451
+ self._X = X
452
+ return self
453
+
454
+ def score_samples(self, Y, X=None):
455
+ """Returns the kernel density estimates of each point in `Y`.
456
+
457
+ Parameters
458
+ ----------
459
+ Y : tensor (m, d)
460
+ `m` points with `d` dimensions for which the probability density will
461
+ be calculated
462
+
463
+
464
+ Returns
465
+ -------
466
+ the DTMs of Y, for each mass in masses.
467
+ """
468
+ if len(self.masses) == 0:
469
+ return np.empty((0, len(Y)))
470
+ assert Y.ndim == 2
471
+ if self._backend == "torch":
472
+ _Y = Y.detach().numpy()
473
+ else:
474
+ _Y = Y
475
+ NN_Dist, NN = self._kdtree.query(_Y, self._ks.max(), return_distance=True)
476
+ DTMs = np.array([((NN_Dist**2)[:, :k].mean(1)) ** 0.5 for k in self._ks])
477
+ return DTMs
478
+
479
+ def score_samples_diff(self, Y):
480
+ """Returns the kernel density estimates of each point in `Y`.
481
+
482
+ Parameters
483
+ ----------
484
+ Y : tensor (m, d)
485
+ `m` points with `d` dimensions for which the probability density will
486
+ be calculated
487
+ X : tensor (n, d), optional
488
+ `n` points with `d` dimensions to which KDE will be fit. Provided to
489
+ allow batch calculations in `log_prob`. By default, `X` is None and
490
+ all points used to initialize KernelDensityEstimator are included.
491
+
492
+
493
+ Returns
494
+ -------
495
+ log_probs : tensor (m)
496
+ log probability densities for each of the queried points in `Y`
497
+ """
498
+ import torch
499
+
500
+ assert Y.ndim == 2
501
+ assert self._backend == "torch", "Use the non-diff version with numpy."
502
+ if len(self.masses) == 0:
503
+ return torch.empty(0, len(Y))
504
+ NN = self._kdtree.query(Y.detach(), self._ks.max(), return_distance=False)
505
+ DTMs = tuple(
506
+ (((self._X[NN] - Y[:, None, :]) ** 2)[:, :k].sum(dim=(1, 2)) / k) ** 0.5
507
+ for k in self._ks
508
+ ) # TODO : kdtree already computes distance, find implementation of kdtree that is pytorch differentiable
509
+ return DTMs
510
+
511
+
512
+ # def _pts_convolution_sparse(pts:np.ndarray, pts_weights:np.ndarray, filtration_grid:Iterable[np.ndarray], kernel="gaussian", bandwidth=0.1, **more_kde_args):
513
+ # """
514
+ # Old version of `convolution_signed_measures`. Scikitlearn's convolution is slower than the code above.
515
+ # """
516
+ # from sklearn.neighbors import KernelDensity
517
+ # grid_iterator = np.asarray(list(product(*filtration_grid)))
518
+ # grid_shape = [len(f) for f in filtration_grid]
519
+ # if len(pts) == 0:
520
+ # # warn("Found a trivial signed measure !")
521
+ # return np.zeros(shape=grid_shape)
522
+ # kde = KernelDensity(kernel=kernel, bandwidth=bandwidth, rtol = 1e-4, **more_kde_args) # TODO : check rtol
523
+
524
+ # pos_indices = pts_weights>0
525
+ # neg_indices = pts_weights<0
526
+ # img_pos = kde.fit(pts[pos_indices], sample_weight=pts_weights[pos_indices]).score_samples(grid_iterator).reshape(grid_shape)
527
+ # img_neg = kde.fit(pts[neg_indices], sample_weight=-pts_weights[neg_indices]).score_samples(grid_iterator).reshape(grid_shape)
528
+ # return np.exp(img_pos) - np.exp(img_neg)
529
+
530
+
531
+ # Precompiles the convolution
532
+ # _test(r=2,b=.5, plot=False)
@@ -0,0 +1,79 @@
1
+ import persistable
2
+
3
+
4
+ # requires installing ripser (pip install ripser) as well as persistable from the higher-homology branch,
5
+ # which can be done as follows:
6
+ # pip install git+https://github.com/LuisScoccola/persistable.git@higher-homology
7
+ # NOTE: only accepts as input a distance matrix
8
+ def hf_degree_rips(
9
+ distance_matrix,
10
+ min_rips_value,
11
+ max_rips_value,
12
+ max_normalized_degree,
13
+ min_normalized_degree,
14
+ grid_granularity,
15
+ max_homological_dimension,
16
+ subsample_size = None,
17
+ ):
18
+ if subsample_size == None:
19
+ p = persistable.Persistable(distance_matrix, metric="precomputed")
20
+ else:
21
+ p = persistable.Persistable(distance_matrix, metric="precomputed", subsample=subsample_size)
22
+
23
+ rips_values, normalized_degree_values, hilbert_functions, minimal_hilbert_decompositions = p._hilbert_function(
24
+ min_rips_value,
25
+ max_rips_value,
26
+ max_normalized_degree,
27
+ min_normalized_degree,
28
+ grid_granularity,
29
+ homological_dimension=max_homological_dimension,
30
+ )
31
+
32
+ return rips_values, normalized_degree_values, hilbert_functions, minimal_hilbert_decompositions
33
+
34
+
35
+
36
+ def hf_h0_degree_rips(
37
+ point_cloud,
38
+ min_rips_value,
39
+ max_rips_value,
40
+ max_normalized_degree,
41
+ min_normalized_degree,
42
+ grid_granularity,
43
+ ):
44
+ p = persistable.Persistable(point_cloud, n_neighbors="all")
45
+
46
+ rips_values, normalized_degree_values, hilbert_functions, minimal_hilbert_decompositions = p._hilbert_function(
47
+ min_rips_value,
48
+ max_rips_value,
49
+ max_normalized_degree,
50
+ min_normalized_degree,
51
+ grid_granularity,
52
+ )
53
+
54
+ return rips_values, normalized_degree_values, hilbert_functions[0], minimal_hilbert_decompositions[0]
55
+
56
+
57
+ def ri_h0_degree_rips(
58
+ point_cloud,
59
+ min_rips_value,
60
+ max_rips_value,
61
+ max_normalized_degree,
62
+ min_normalized_degree,
63
+ grid_granularity,
64
+ ):
65
+ p = persistable.Persistable(point_cloud, n_neighbors="all")
66
+
67
+ rips_values, normalized_degree_values, rank_invariant, _, _ = p._rank_invariant(
68
+ min_rips_value,
69
+ max_rips_value,
70
+ max_normalized_degree,
71
+ min_normalized_degree,
72
+ grid_granularity,
73
+ )
74
+
75
+ return rips_values, normalized_degree_values, rank_invariant
76
+
77
+
78
+
79
+