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
multipers/plots.py CHANGED
@@ -1,13 +1,25 @@
1
1
  from typing import Optional
2
+ from warnings import warn
2
3
 
4
+ import matplotlib.colors as mcolors
3
5
  import matplotlib.pyplot as plt
4
6
  import numpy as np
7
+ from matplotlib.colors import ListedColormap
8
+ from numpy.typing import ArrayLike
9
+
10
+ from multipers.array_api import to_numpy
11
+
12
+ _custom_colors = [
13
+ "#03045e",
14
+ "#0077b6",
15
+ "#00b4d8",
16
+ "#90e0ef",
17
+ ]
18
+ _cmap_ = ListedColormap(_custom_colors)
19
+ _cmap = mcolors.LinearSegmentedColormap.from_list(
20
+ "continuous_cmap", _cmap_.colors, N=256
21
+ )
5
22
 
6
- try:
7
- import torch
8
- istensor = torch.is_tensor
9
- except ImportError:
10
- istensor = lambda x: False
11
23
 
12
24
  def _plot_rectangle(rectangle: np.ndarray, weight, **plt_kwargs):
13
25
  rectangle = np.asarray(rectangle)
@@ -69,10 +81,11 @@ def _plot_signed_measure_4(
69
81
  **plt_kwargs, # ignored ftm
70
82
  ):
71
83
  # compute the maximal rectangle area
72
- pts = np.clip(pts, a_min=-np.inf, a_max=np.array((*threshold, *threshold))[None, :])
84
+ pts = np.clip(pts, a_min=-np.inf,
85
+ a_max=np.array((*threshold, *threshold))[None, :])
73
86
  alpha_rescaling = 0
74
87
  for rectangle, weight in zip(pts, weights):
75
- if rectangle[2] > x_smoothing * rectangle[0]:
88
+ if rectangle[2] >= x_smoothing * rectangle[0]:
76
89
  alpha_rescaling = max(
77
90
  alpha_rescaling,
78
91
  (rectangle[2] / x_smoothing - rectangle[0])
@@ -81,7 +94,7 @@ def _plot_signed_measure_4(
81
94
  # draw the rectangles
82
95
  for rectangle, weight in zip(pts, weights):
83
96
  # draw only the rectangles that have not been reduced to the empty set
84
- if rectangle[2] > x_smoothing * rectangle[0]:
97
+ if rectangle[2] >= x_smoothing * rectangle[0]:
85
98
  # make the alpha channel proportional to the rectangle's area
86
99
  if area_alpha:
87
100
  _plot_rectangle(
@@ -121,11 +134,8 @@ def plot_signed_measure(signed_measure, threshold=None, ax=None, **plt_kwargs):
121
134
  else:
122
135
  plt.sca(ax)
123
136
  pts, weights = signed_measure
124
- if istensor(pts):
125
- pts = pts.detach().numpy()
126
- if istensor(weights):
127
- weights = weights.detach().numpy()
128
- pts = np.asarray(pts)
137
+ pts = to_numpy(pts)
138
+ weights = to_numpy(weights)
129
139
  num_pts = pts.shape[0]
130
140
  num_parameters = pts.shape[1]
131
141
  if threshold is None:
@@ -137,16 +147,9 @@ def plot_signed_measure(signed_measure, threshold=None, ax=None, **plt_kwargs):
137
147
  else:
138
148
  pts_ = pts
139
149
  threshold = np.max(np.ma.masked_invalid(pts_), axis=0)
140
- threshold = np.max([threshold, [plt.gca().get_xlim()[1], plt.gca().get_ylim()[1]]], axis=0)
141
- if isinstance(pts, np.ndarray):
142
- pass
143
- else:
144
- import torch
145
-
146
- if isinstance(pts, torch.Tensor):
147
- pts = pts.detach().numpy()
148
- else:
149
- raise Exception("Invalid measure type.")
150
+ threshold = np.max(
151
+ [threshold, [plt.gca().get_xlim()[1], plt.gca().get_ylim()[1]]], axis=0
152
+ )
150
153
 
151
154
  assert num_parameters in (2, 4)
152
155
  if num_parameters == 2:
@@ -168,7 +171,8 @@ def plot_signed_measures(signed_measures, threshold=None, size=4):
168
171
  nrows=1, ncols=num_degrees, figsize=(num_degrees * size, size)
169
172
  )
170
173
  for ax, signed_measure in zip(axes, signed_measures):
171
- plot_signed_measure(signed_measure=signed_measure, ax=ax, threshold=threshold)
174
+ plot_signed_measure(signed_measure=signed_measure,
175
+ ax=ax, threshold=threshold)
172
176
  plt.tight_layout()
173
177
 
174
178
 
@@ -178,8 +182,9 @@ def plot_surface(
178
182
  fig=None,
179
183
  ax=None,
180
184
  cmap: Optional[str] = None,
181
- discrete_surface=False,
182
- has_negative_values=False,
185
+ discrete_surface: bool = False,
186
+ has_negative_values: bool = False,
187
+ contour: bool = True,
183
188
  **plt_args,
184
189
  ):
185
190
  import matplotlib
@@ -196,14 +201,18 @@ def plot_surface(
196
201
  if discrete_surface:
197
202
  cmap = matplotlib.colormaps["gray_r"]
198
203
  else:
199
- cmap = matplotlib.colormaps["plasma"]
204
+ cmap = _cmap
205
+ if discrete_surface or not contour:
206
+ # for shading="flat"
207
+ grid = [np.concatenate([g, [g[-1]*1.1 - .1*g[0]]]) for g in grid]
200
208
  if discrete_surface:
201
209
  if has_negative_values:
202
210
  bounds = np.arange(-5, 6, 1, dtype=int)
203
211
  else:
204
212
  bounds = np.arange(0, 11, 1, dtype=int)
205
213
  norm = matplotlib.colors.BoundaryNorm(bounds, cmap.N, extend="max")
206
- im = ax.pcolormesh(grid[0], grid[1], hf.T, cmap=cmap, norm=norm, **plt_args)
214
+ im = ax.pcolormesh(grid[0], grid[1], hf.T, cmap=cmap,
215
+ norm=norm, shading="flat", **plt_args)
207
216
  cbar = fig.colorbar(
208
217
  matplotlib.cm.ScalarMappable(cmap=cmap, norm=norm),
209
218
  spacing="proportional",
@@ -211,7 +220,14 @@ def plot_surface(
211
220
  )
212
221
  cbar.set_ticks(ticks=bounds, labels=bounds)
213
222
  return im
214
- im = ax.pcolormesh(grid[0], grid[1], hf.T, cmap=cmap, **plt_args)
223
+
224
+ if contour:
225
+ levels = plt_args.pop("levels", 50)
226
+ im = ax.contourf(grid[0], grid[1], hf.T,
227
+ cmap=cmap, levels=levels, **plt_args)
228
+ else:
229
+ im = ax.pcolormesh(grid[0], grid[1], hf.T,
230
+ cmap=cmap, shading="flat", **plt_args)
215
231
  return im
216
232
 
217
233
 
@@ -255,7 +271,7 @@ def plot2d_PyModule(
255
271
  dimension=-1,
256
272
  separated=False,
257
273
  min_persistence=0,
258
- alpha=.8,
274
+ alpha=None,
259
275
  verbose=False,
260
276
  save=False,
261
277
  dpi=200,
@@ -273,12 +289,14 @@ def plot2d_PyModule(
273
289
 
274
290
  shapely = True and shapely
275
291
  except ImportError:
276
- from warnings import warn
277
-
278
292
  shapely = False
279
293
  warn(
280
294
  "Shapely not installed. Fallbacking to matplotlib. The plots may be inacurate."
281
295
  )
296
+ if alpha is None:
297
+ alpha = 0.8 if shapely else 1
298
+ if not shapely and alpha != 1:
299
+ warn("Opacity without shapely will lead to incorect plots.")
282
300
  cmap = (
283
301
  matplotlib.colormaps["Spectral"] if cmap is None else matplotlib.colormaps[cmap]
284
302
  )
@@ -304,25 +322,30 @@ def plot2d_PyModule(
304
322
  trivial_summand = False
305
323
  if shapely:
306
324
  list_of_rect.append(
307
- _rectangle_box(birth[0], birth[1], death[0], death[1])
325
+ _rectangle_box(
326
+ birth[0], birth[1], death[0], death[1])
308
327
  )
309
328
  else:
310
329
  list_of_rect.append(
311
- _rectangle(birth, death, cmap(i / n_summands), alpha)
330
+ _rectangle(birth, death, cmap(
331
+ i / n_summands), alpha)
312
332
  )
313
333
  if not (trivial_summand):
314
334
  if separated:
315
335
  fig, ax = plt.subplots()
316
- ax.set(xlim=[box[0][0], box[1][0]], ylim=[box[0][1], box[1][1]])
336
+ ax.set(xlim=[box[0][0], box[1][0]],
337
+ ylim=[box[0][1], box[1][1]])
317
338
  if shapely:
318
339
  summand_shape = union_all(list_of_rect)
319
340
  if type(summand_shape) is _Polygon:
320
341
  xs, ys = summand_shape.exterior.xy
321
- ax.fill(xs, ys, alpha=alpha, fc=cmap(i / n_summands), ec="None")
342
+ ax.fill(xs, ys, alpha=alpha, fc=cmap(
343
+ i / n_summands), ec="None")
322
344
  else:
323
345
  for polygon in summand_shape.geoms:
324
346
  xs, ys = polygon.exterior.xy
325
- ax.fill(xs, ys, alpha=alpha, fc=cmap(i / n_summands), ec="None")
347
+ ax.fill(xs, ys, alpha=alpha, fc=cmap(
348
+ i / n_summands), ec="None")
326
349
  else:
327
350
  for rectangle in list_of_rect:
328
351
  ax.add_patch(rectangle)
@@ -341,3 +364,107 @@ def plot2d_PyModule(
341
364
  if dimension >= 0:
342
365
  plt.title(rf"$H_{dimension}$ $2$-persistence")
343
366
  return
367
+
368
+
369
+ def plot_simplicial_complex(
370
+ st, pts: ArrayLike, x: float, y: float, mma=None, degree=None
371
+ ):
372
+ """
373
+ Scatters the points, with the simplices in the filtration at coordinates (x,y).
374
+ if an mma module is given, plots it in a second axis
375
+ """
376
+ if mma is not None:
377
+ fig, (a, b) = plt.subplots(ncols=2, figsize=(15, 5))
378
+ plt.sca(a)
379
+ plot_simplicial_complex(st, pts, x, y)
380
+ plt.sca(b)
381
+ mma.plot(degree=degree)
382
+ box = mma.get_box()
383
+ a, b, c, d = box.ravel()
384
+ mma.plot(degree=1, min_persistence=0.01)
385
+ plt.vlines(x, b, d, color="k", linestyle="--")
386
+ plt.hlines(y, a, c, color="k", linestyle="--")
387
+ plt.scatter([x], [y], c="r", zorder=10)
388
+ plt.text(x + 0.01 * (b - a), y + 0.01 * (d - c), f"({x},{y})")
389
+ return
390
+
391
+ pts = np.asarray(pts)
392
+ values = np.array([-f[1] for s, f in st.get_skeleton(0)])
393
+ qs = np.quantile(values, np.linspace(0, 1, 100))
394
+
395
+ def color_idx(d):
396
+ return np.searchsorted(qs, d) / 100
397
+
398
+ from matplotlib.pyplot import get_cmap
399
+
400
+ def color(d):
401
+ return get_cmap("viridis")([0, color_idx(d), 1])[1]
402
+
403
+ cols_pc = np.asarray([color(v) for v in values])
404
+ ax = plt.gca()
405
+ for s, f in st: # simplexe, filtration
406
+ density = -f[1]
407
+ if len(s) <= 1 or f[0] > x or density < -y: # simplexe = point
408
+ continue
409
+ if len(s) == 2: # simplexe = segment
410
+ xx = np.array([pts[a, 0] for a in s])
411
+ yy = np.array([pts[a, 1] for a in s])
412
+ plt.plot(xx, yy, c=color(density), alpha=1,
413
+ zorder=10 * density, lw=1.5)
414
+ if len(s) == 3: # simplexe = triangle
415
+ xx = np.array([pts[a, 0] for a in s])
416
+ yy = np.array([pts[a, 1] for a in s])
417
+ _c = color(density)
418
+ ax.fill(xx, yy, c=_c, alpha=0.3, zorder=0)
419
+ out = plt.scatter(pts[:, 0], pts[:, 1], c=cols_pc, zorder=10, s=10)
420
+ ax.set_aspect(1)
421
+ return out
422
+
423
+
424
+ def plot_point_cloud(
425
+ pts,
426
+ function,
427
+ x,
428
+ y,
429
+ mma=None,
430
+ degree=None,
431
+ ball_alpha=0.3,
432
+ point_cmap="viridis",
433
+ color_bias=1,
434
+ ball_color=None,
435
+ point_size=20,
436
+ ):
437
+ if mma is not None:
438
+ fig, (a, b) = plt.subplots(ncols=2, figsize=(15, 5))
439
+ plt.sca(a)
440
+ plot_point_cloud(pts, function, x, y)
441
+ plt.sca(b)
442
+ mma.plot(degree=degree)
443
+ box = mma.get_box()
444
+ a, b, c, d = box.ravel()
445
+ mma.plot(degree=1, min_persistence=0.01)
446
+ plt.vlines(x, b, d, color="k", linestyle="--")
447
+ plt.hlines(y, a, c, color="k", linestyle="--")
448
+ plt.scatter([x], [y], c="r", zorder=10)
449
+ plt.text(x + 0.01 * (b - a), y + 0.01 * (d - c), f"({x},{y})")
450
+ return
451
+ values = -function
452
+ qs = np.quantile(values, np.linspace(0, 1, 100))
453
+
454
+ def color_idx(d):
455
+ return np.searchsorted(qs, d * color_bias) / 100
456
+
457
+ from matplotlib.collections import PatchCollection
458
+ from matplotlib.pyplot import get_cmap
459
+
460
+ def color(d):
461
+ return get_cmap(point_cmap)([0, color_idx(d), 1])[1]
462
+
463
+ _colors = np.array([color(v) for v in values])
464
+ ax = plt.gca()
465
+ idx = function <= y
466
+ circles = [plt.Circle(pt, x) for pt, c in zip(pts[idx], function)]
467
+ pc = PatchCollection(circles, alpha=ball_alpha, color=ball_color)
468
+ ax.add_collection(pc)
469
+ plt.scatter(*pts.T, c=_colors, s=point_size)
470
+ ax.set_aspect(1)
Binary file
@@ -11,6 +11,7 @@ from collections import defaultdict
11
11
  cnp.import_array()
12
12
  from scipy import sparse
13
13
 
14
+ from multipers.array_api import api_from_tensor
14
15
 
15
16
  import multipers.grids as mpg
16
17
 
@@ -31,7 +32,6 @@ import cython
31
32
  cimport cython
32
33
 
33
34
 
34
-
35
35
  # from scipy.sparse import coo_array
36
36
  # from scipy.ndimage import convolve1d
37
37
 
@@ -169,7 +169,7 @@ def sparsify(x):
169
169
 
170
170
  @cython.boundscheck(False)
171
171
  @cython.wraparound(False)
172
- def clean_signed_measure(some_float[:,:] pts, some_int[:] weights, dtype = np.float32):
172
+ def clean_signed_measure_old(some_float[:,:] pts, some_int[:] weights, dtype = np.float32):
173
173
  """
174
174
  Sum the diracs at the same locations. i.e.,
175
175
  returns the minimal sized measure to represent the input.
@@ -190,6 +190,14 @@ def clean_signed_measure(some_float[:,:] pts, some_int[:] weights, dtype = np.fl
190
190
  new_weights = new_weights[idx]
191
191
  return (new_pts, new_weights)
192
192
 
193
+ def clean_signed_measure(pts, w, dtype=np.int32):
194
+ api = api_from_tensor(pts)
195
+ _, idx, inv = np.unique(api.asnumpy(pts), return_index=True, return_inverse=True, axis=0)
196
+ new_w = np.bincount(inv, weights=w).astype(w.dtype)
197
+ pts, w = pts[idx], new_w
198
+ idx = w!=0
199
+ return pts[idx],w[idx]
200
+
193
201
  def clean_sms(sms):
194
202
  """
195
203
  Sum the diracs at the same locations. i.e.,
@@ -324,3 +332,64 @@ def estimate_rank_from_rank_sm(sm:tuple, a, b)->np.int64:
324
332
  return w[idx].sum()
325
333
 
326
334
 
335
+ def rectangle_to_hook_minimal_signed_barcode(pts,w,):
336
+ if pts.shape[1] != 4:
337
+ raise NotImplementedError(
338
+ "Only works for 2-parameter persistence modules for the moment."
339
+ )
340
+ api = api_from_tensor(pts)
341
+ pts = api.astensor(pts)
342
+ w = np.asarray(w)
343
+ ## [a,b], [a, a0b1], [a,b0a1], proj, V,H
344
+
345
+ projectives_idx = (pts[:,3] == np.inf) * (pts[:,2] == np.inf)
346
+ pts_proj = pts[projectives_idx]
347
+ w_proj = w[projectives_idx]
348
+ pts = pts[~projectives_idx]
349
+ w = w[~projectives_idx]
350
+ # print("projectives:", pts_proj)
351
+
352
+ vert_blocks_idx = pts[:,3] == np.inf
353
+ pts_V = pts[vert_blocks_idx]
354
+ w_V = w[vert_blocks_idx]
355
+ pts_V[:,3] = pts_V[:,1]
356
+ pts = pts[~vert_blocks_idx]
357
+ w = w[~vert_blocks_idx]
358
+ # print("vertical:", pts_V)
359
+
360
+ h_idx = pts[:,2] == np.inf
361
+ pts_H = pts[h_idx]
362
+ w_H = w[h_idx]
363
+ pts_H[:,2] = pts_H[:,0]
364
+ pts = pts[~h_idx]
365
+ w = w[~h_idx]
366
+ # print("horizontal:", pts_H)
367
+
368
+
369
+ new_w = api.cat([-w, w, w, w_proj,w_V,w_H])
370
+
371
+ pts_b0a1 = api.tensor(pts)
372
+ pts_b0a1[:,3] = pts[:,1]
373
+ pts_a0b1 = api.tensor(pts)
374
+ pts_a0b1[:,2] = pts[:,0]
375
+
376
+ new_pts = api.cat([
377
+ pts,
378
+ pts_b0a1,
379
+ pts_a0b1,
380
+ pts_proj,
381
+ pts_V,
382
+ pts_H
383
+ ], axis=0)
384
+ # pts,w = new_pts,new_w
385
+ pts,w = clean_signed_measure(new_pts,new_w)
386
+
387
+ # Everything infinite is handled separately anyway
388
+ # inf0 = pts[:,2] == np.inf
389
+ # inf1 = pts[:,3] == np.inf
390
+ # pts[inf0,3] = pts[inf0,1]
391
+ # pts[inf1,2] = pts[inf1,0]
392
+ # pts,w = clean_signed_measure(pts,w)
393
+
394
+ return pts,w
395
+
@@ -117,8 +117,8 @@ cdef extern from "Simplex_tree_multi_interface.h" namespace "Gudhi::multiparamet
117
117
  void to_std(intptr_t, Line[double],int ) nogil
118
118
  void to_std_linear_projection(intptr_t, vector[double]) nogil
119
119
  void squeeze_filtration_inplace(vector[vector[double]] &, bool) nogil
120
- void squeeze_filtration(intptr_t, vector[vector[double]] &) nogil
121
- void unsqueeze_filtration(intptr_t, vector[vector[double]] &) nogil
120
+ void squeeze_filtration(intptr_t, vector[vector[double]] &) except + nogil
121
+ void unsqueeze_filtration(intptr_t, vector[vector[double]] &) except + nogil
122
122
  vector[vector[vector[value_type]]] get_filtration_values(const vector[int]&) nogil
123
123
 
124
124