multipers 2.3.4__cp311-cp311-win_amd64.whl → 2.3.5b2__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.

Files changed (32) hide show
  1. multipers/_signed_measure_meta.py +50 -84
  2. multipers/array_api/numpy.py +7 -0
  3. multipers/array_api/torch.py +7 -2
  4. multipers/filtrations/filtrations.py +2 -0
  5. multipers/function_rips.cp311-win_amd64.pyd +0 -0
  6. multipers/grids.cp311-win_amd64.pyd +0 -0
  7. multipers/grids.pyx +57 -19
  8. multipers/io.cp311-win_amd64.pyd +0 -0
  9. multipers/mma_structures.cp311-win_amd64.pyd +0 -0
  10. multipers/multi_parameter_rank_invariant/function_rips.h +1 -1
  11. multipers/multiparameter_module_approximation/approximation.h +8 -2
  12. multipers/multiparameter_module_approximation.cp311-win_amd64.pyd +0 -0
  13. multipers/plots.py +3 -2
  14. multipers/point_measure.cp311-win_amd64.pyd +0 -0
  15. multipers/point_measure.pyx +1 -1
  16. multipers/simplex_tree_multi.cp311-win_amd64.pyd +0 -0
  17. multipers/simplex_tree_multi.pyx +88 -56
  18. multipers/simplex_tree_multi.pyx.tp +23 -7
  19. multipers/slicer.cp311-win_amd64.pyd +0 -0
  20. multipers/slicer.pxd +60 -60
  21. multipers/slicer.pxd.tp +1 -1
  22. multipers/slicer.pyx +2052 -1137
  23. multipers/slicer.pyx.tp +100 -43
  24. multipers/tbb12.dll +0 -0
  25. multipers/tbbbind_2_5.dll +0 -0
  26. multipers/tbbmalloc.dll +0 -0
  27. multipers/tbbmalloc_proxy.dll +0 -0
  28. {multipers-2.3.4.dist-info → multipers-2.3.5b2.dist-info}/METADATA +1 -1
  29. {multipers-2.3.4.dist-info → multipers-2.3.5b2.dist-info}/RECORD +32 -32
  30. {multipers-2.3.4.dist-info → multipers-2.3.5b2.dist-info}/WHEEL +0 -0
  31. {multipers-2.3.4.dist-info → multipers-2.3.5b2.dist-info}/licenses/LICENSE +0 -0
  32. {multipers-2.3.4.dist-info → multipers-2.3.5b2.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 ("rank" in invariant or "hook" in invariant or "rectangle" in invariant):
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
- sms = [rectangle_to_hook_minimal_signed_barcode(pts,w) for pts,w in sms]
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
 
@@ -22,6 +22,13 @@ unique = _np.unique
22
22
  inf = _np.inf
23
23
  searchsorted = _np.searchsorted
24
24
  LazyTensor = None
25
+ abs = _np.abs
26
+ exp = _np.exp
27
+
28
+
29
+ def relu(x):
30
+ return _np.where(x >= 0, x, 0)
31
+
25
32
 
26
33
  # Test keops
27
34
  _is_keops_available = None
@@ -20,6 +20,9 @@ cartesian_product = _t.cartesian_prod
20
20
  inf = _t.inf
21
21
  searchsorted = _t.searchsorted
22
22
  LazyTensor = None
23
+ relu = _t.relu
24
+ abs = _t.abs
25
+ exp = _t.exp
23
26
 
24
27
 
25
28
  _is_keops_available = None
@@ -53,6 +56,8 @@ def check_keops():
53
56
  _is_keops_available = False
54
57
 
55
58
  return _is_keops_available
59
+
60
+
56
61
  check_keops()
57
62
 
58
63
 
@@ -101,8 +106,8 @@ def maxvalues(x: _t.Tensor, **kwargs):
101
106
  return _t.max(x, **kwargs).values
102
107
 
103
108
 
104
- def asnumpy(x):
105
- return x.detach().numpy()
109
+ def asnumpy(x: _t.Tensor):
110
+ return x.cpu().detach().numpy()
106
111
 
107
112
 
108
113
  def is_tensor(x):
@@ -79,6 +79,7 @@ def RipsLowerstar(
79
79
  filtration_values = [D.ravel(), *[f for f in function.T]]
80
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
@@ -369,7 +369,7 @@ def _inf_value(array):
369
369
  return torch.iinfo(dtype).max
370
370
  raise ValueError(f"Dtype must be integer or floating like (got {dtype})")
371
371
 
372
- 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):
373
373
  """
374
374
  Input
375
375
  -----
@@ -379,28 +379,21 @@ def evaluate_in_grid(pts, grid, mass_default=None):
379
379
  assert pts.ndim == 2
380
380
  first_filtration = grid[0]
381
381
  dtype = first_filtration.dtype
382
- if isinstance(first_filtration, np.ndarray):
383
- if mass_default is not None:
384
- grid = tuple(np.concatenate([g, [m]]) for g,m in zip(grid, mass_default))
385
- def empty_like(x):
386
- return np.empty_like(x, dtype=dtype)
387
- else:
388
- import torch
389
- # assert isinstance(first_filtration, torch.Tensor), f"Invalid grid type. Got {type(grid[0])}, expected numpy or torch array."
390
- if mass_default is not None:
391
- grid = tuple(torch.cat([g, torch.tensor(m)[None]]) for g,m in zip(grid, mass_default))
392
- def empty_like(x):
393
- 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)
394
387
 
395
388
  coords=empty_like(pts)
396
389
  cdef int dim = coords.shape[1]
397
- pts_inf = _inf_value(pts)
398
- 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
399
392
  idx = np.argwhere(pts == pts_inf)
400
- pts[idx] == 0
393
+ pts[idx[:,0],idx[:,1]] = 0
401
394
  for i in range(dim):
402
395
  coords[:,i] = grid[i][pts[:,i]]
403
- coords[idx] = coords_inf
396
+ coords[idx[:,0],idx[:,1]] = coords_inf
404
397
  return coords
405
398
 
406
399
  def sm_in_grid(pts, weights, grid, mass_default=None):
@@ -452,8 +445,9 @@ def sms_in_grid(sms, grid, mass_default=None):
452
445
  return sms
453
446
 
454
447
 
455
- def _push_pts_to_line(pts, basepoint, direction=None):
456
- api = api_from_tensors(pts, basepoint)
448
+ def _push_pts_to_line(pts, basepoint, direction=None, api=None):
449
+ if api is None:
450
+ api = api_from_tensors(pts, basepoint)
457
451
  pts = api.astensor(pts)
458
452
  basepoint = api.astensor(basepoint)
459
453
  num_parameters = basepoint.shape[0]
@@ -479,3 +473,47 @@ def _push_pts_to_line(pts, basepoint, direction=None):
479
473
  else:
480
474
  xs = xa
481
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
+
496
+ def evaluate_mod_in_grid(mod, grid, box=None):
497
+ """Given an MMA module, pushes it into the specified grid.
498
+ Useful for e.g., make it differentiable.
499
+
500
+ Input
501
+ -----
502
+ - mod: PyModule
503
+ - grid: Iterable of 1d array, for num_parameters
504
+ Ouput
505
+ -----
506
+ torch-compatible module in the format:
507
+ (num_degrees) x (num_interval of degree) x ((num_birth, num_parameter), (num_death, num_parameters))
508
+
509
+ """
510
+ (birth_sizes, death_sizes), births, deaths = mod.to_flat_idx(grid)
511
+ births = evaluate_in_grid(births, grid)
512
+ deaths = evaluate_in_grid(deaths, grid)
513
+ diff_mod = tuple(
514
+ zip(
515
+ births.split_with_sizes(birth_sizes.tolist()),
516
+ deaths.split_with_sizes(death_sizes.tolist()),
517
+ )
518
+ )
519
+ return diff_mod
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
- out[i] = std::distance(grid[i].begin(), std::lower_bound(grid[i].begin(), grid[i].end(), pt[i]));
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
  }
multipers/plots.py CHANGED
@@ -162,7 +162,7 @@ def plot_signed_measure(signed_measure, threshold=None, ax=None, **plt_kwargs):
162
162
  )
163
163
 
164
164
 
165
- def plot_signed_measures(signed_measures, threshold=None, size=4):
165
+ def plot_signed_measures(signed_measures, threshold=None, size=4, alpha=None):
166
166
  num_degrees = len(signed_measures)
167
167
  if num_degrees <= 1:
168
168
  axes = [plt.gca()]
@@ -172,7 +172,8 @@ def plot_signed_measures(signed_measures, threshold=None, size=4):
172
172
  )
173
173
  for ax, signed_measure in zip(axes, signed_measures):
174
174
  plot_signed_measure(signed_measure=signed_measure,
175
- ax=ax, threshold=threshold)
175
+ ax=ax, threshold=threshold,
176
+ alpha=alpha)
176
177
  plt.tight_layout()
177
178
 
178
179
 
Binary file
@@ -366,7 +366,7 @@ def rectangle_to_hook_minimal_signed_barcode(pts,w,):
366
366
  # print("horizontal:", pts_H)
367
367
 
368
368
 
369
- new_w = api.cat([-w, w, w, w_proj,w_V,w_H])
369
+ new_w = np.concatenate([-w, w, w, w_proj,w_V,w_H])
370
370
 
371
371
  pts_b0a1 = api.tensor(pts)
372
372
  pts_b0a1[:,3] = pts[:,1]