multipers 2.3.1__cp311-cp311-win_amd64.whl → 2.3.2__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 (49) hide show
  1. multipers/_signed_measure_meta.py +71 -65
  2. multipers/array_api/__init__.py +39 -0
  3. multipers/array_api/numpy.py +34 -0
  4. multipers/array_api/torch.py +35 -0
  5. multipers/distances.py +6 -2
  6. multipers/filtrations/density.py +23 -12
  7. multipers/filtrations/filtrations.py +74 -15
  8. multipers/function_rips.cp311-win_amd64.pyd +0 -0
  9. multipers/grids.cp311-win_amd64.pyd +0 -0
  10. multipers/grids.pyx +144 -61
  11. multipers/gudhi/Simplex_tree_multi_interface.h +35 -0
  12. multipers/gudhi/gudhi/Multi_persistence/Box.h +3 -0
  13. multipers/gudhi/gudhi/One_critical_filtration.h +17 -9
  14. multipers/gudhi/mma_interface_matrix.h +5 -3
  15. multipers/gudhi/truc.h +488 -42
  16. multipers/io.cp311-win_amd64.pyd +0 -0
  17. multipers/io.pyx +16 -86
  18. multipers/ml/mma.py +4 -4
  19. multipers/ml/signed_measures.py +60 -62
  20. multipers/mma_structures.cp311-win_amd64.pyd +0 -0
  21. multipers/mma_structures.pxd +2 -1
  22. multipers/mma_structures.pyx +56 -12
  23. multipers/mma_structures.pyx.tp +14 -3
  24. multipers/multiparameter_module_approximation/approximation.h +45 -13
  25. multipers/multiparameter_module_approximation.cp311-win_amd64.pyd +0 -0
  26. multipers/multiparameter_module_approximation.pyx +24 -7
  27. multipers/plots.py +1 -0
  28. multipers/point_measure.cp311-win_amd64.pyd +0 -0
  29. multipers/point_measure.pyx +6 -2
  30. multipers/simplex_tree_multi.cp311-win_amd64.pyd +0 -0
  31. multipers/simplex_tree_multi.pxd +1 -0
  32. multipers/simplex_tree_multi.pyx +535 -113
  33. multipers/simplex_tree_multi.pyx.tp +79 -19
  34. multipers/slicer.cp311-win_amd64.pyd +0 -0
  35. multipers/slicer.pxd +699 -217
  36. multipers/slicer.pxd.tp +22 -6
  37. multipers/slicer.pyx +5314 -1364
  38. multipers/slicer.pyx.tp +202 -46
  39. multipers/tbb12.dll +0 -0
  40. multipers/tbbbind_2_5.dll +0 -0
  41. multipers/tbbmalloc.dll +0 -0
  42. multipers/tbbmalloc_proxy.dll +0 -0
  43. multipers/tests/__init__.py +9 -4
  44. multipers/torch/diff_grids.py +30 -7
  45. {multipers-2.3.1.dist-info → multipers-2.3.2.dist-info}/METADATA +4 -25
  46. {multipers-2.3.1.dist-info → multipers-2.3.2.dist-info}/RECORD +49 -46
  47. {multipers-2.3.1.dist-info → multipers-2.3.2.dist-info}/WHEEL +1 -1
  48. {multipers-2.3.1.dist-info → multipers-2.3.2.dist-info/licenses}/LICENSE +0 -0
  49. {multipers-2.3.1.dist-info → multipers-2.3.2.dist-info}/top_level.txt +0 -0
@@ -51,7 +51,8 @@ cimport cython
51
51
  from gudhi.simplex_tree import SimplexTree ## Small hack for typing
52
52
  from typing import Iterable,Literal,Optional
53
53
  from tqdm import tqdm
54
- from multipers.grids import Lstrategies, compute_grid
54
+ from multipers.grids import Lstrategies, compute_grid, sanitize_grid
55
+ from multipers.array_api import api_from_tensor
55
56
  from multipers.point_measure import signed_betti, rank_decomposition_by_rectangles, sparsify
56
57
 
57
58
  from warnings import warn
@@ -81,8 +82,8 @@ cdef class SimplexTreeMulti_KFi32:
81
82
  """
82
83
  cdef public intptr_t thisptr
83
84
 
84
- cdef public vector[vector[double]] filtration_grid
85
- cdef public bool _is_function_simplextree
85
+ cdef public object filtration_grid
86
+ cdef public bool _is_function_simplextree # TODO : deprecate
86
87
  # Get the pointer casted as it should be
87
88
  cdef Simplex_tree_multi_interface[KFi32, int32_t]* get_ptr(self) noexcept nogil:
88
89
  return <Simplex_tree_multi_interface[KFi32, int32_t]*>(self.thisptr)
@@ -153,7 +154,7 @@ cdef class SimplexTreeMulti_KFi32:
153
154
  self.thisptr = <intptr_t>(new Simplex_tree_multi_interface[KFi32, int32_t]())
154
155
  self.set_num_parameter(num_parameters)
155
156
  self._is_function_simplextree = False
156
- self.filtration_grid=[[]*num_parameters]
157
+ self.filtration_grid=[]
157
158
 
158
159
  def __dealloc__(self):
159
160
  cdef Simplex_tree_multi_interface[KFi32,int32_t]* ptr = self.get_ptr()
@@ -821,11 +822,26 @@ cdef class SimplexTreeMulti_KFi32:
821
822
  out = self.get_ptr().get_filtration_values(degrees)
822
823
  filtrations_values = [np.asarray(filtration) for filtration in out]
823
824
  # Removes infs
824
- if inf_to_nan:
825
+ if inf_to_nan and np.dtype(filtrations_values[0].dtype).kind == 'f':
825
826
  for i,f in enumerate(filtrations_values):
826
827
  filtrations_values[i][f == np.inf] = np.nan
827
828
  filtrations_values[i][f == - np.inf] = np.nan
828
829
  return filtrations_values
830
+ def _clean_filtration_grid(self):
831
+ """
832
+ Removes the values in filtration_grid that are not linked to any splx.
833
+ """
834
+ if not self.is_squeezed:
835
+ raise ValueError("No grid to clean.")
836
+ F = self.filtration_grid
837
+ self.filtration_grid=None
838
+ cleaned_coordinates = compute_grid(self)
839
+ new_st = self.grid_squeeze(cleaned_coordinates)
840
+
841
+ self.thisptr, new_st.thisptr = new_st.thisptr, self.thisptr
842
+ self.filtration_grid = tuple(f[g] for f,g in zip(F,cleaned_coordinates))
843
+ return self
844
+
829
845
 
830
846
 
831
847
  def get_filtration_grid(self, resolution:Iterable[int]|None=None, degrees:Iterable[int]|None=None, drop_quantiles:float|tuple=0, grid_strategy:_available_strategies="exact")->Iterable[np.ndarray]:
@@ -861,8 +877,16 @@ cdef class SimplexTreeMulti_KFi32:
861
877
  return compute_grid(filtrations_values, resolution=resolution,strategy=grid_strategy,drop_quantiles=drop_quantiles)
862
878
 
863
879
 
864
-
865
- def grid_squeeze(self, filtration_grid:np.ndarray|list|None=None, bool coordinate_values=True, force=False, grid_strategy:_available_strategies = "exact", inplace=False, **filtration_grid_kwargs):
880
+ def grid_squeeze(
881
+ self,
882
+ filtration_grid:np.ndarray|list|None=None,
883
+ bool coordinate_values=True,
884
+ bool force=False,
885
+ str strategy:_available_strategies = "exact",
886
+ grid_strategy=None,
887
+ bool inplace=False,
888
+ **filtration_grid_kwargs
889
+ )->SimplexTreeMulti_KFi32 | SimplexTreeMulti_KFi32:
866
890
  """
867
891
  Fit the filtration of the simplextree to a grid.
868
892
 
@@ -873,26 +897,50 @@ cdef class SimplexTreeMulti_KFi32:
873
897
  """
874
898
  if not force and self.is_squeezed:
875
899
  raise Exception("SimplexTree already squeezed, use `force=True` if that's really what you want to do.")
900
+
901
+ if grid_strategy is not None:
902
+ warn("`grid_strategy` is deprecated, use `strategy` instead.",DeprecationWarning)
903
+ strategy=grid_strategy
904
+
905
+ if self.is_squeezed:
906
+ warn("(copy warning) Squeezing an already squeezed slicer.")
907
+ temp = self.unsqueeze()
908
+ subgrid = compute_grid(self.filtration_grid, strategy=strategy, resolution=resolution)
909
+ return temp.grid_squeeze(subgrid, coordinates=coordinates, inplace=inplace)
910
+
876
911
  #TODO : multi-critical
877
912
  if filtration_grid is None:
878
- filtration_grid = self.get_filtration_grid(grid_strategy=grid_strategy, **filtration_grid_kwargs)
879
- cdef vector[vector[double]] c_filtration_grid = filtration_grid
880
- assert <int>c_filtration_grid.size() == self.get_ptr().get_number_of_parameters(), f"Grid has to be of size {self.num_parameters}, got {filtration_grid.size()}"
881
- cdef intptr_t ptr = self.thisptr
913
+ filtration_grid = self.get_filtration_grid(grid_strategy=strategy, **filtration_grid_kwargs)
914
+ else:
915
+ filtration_grid = sanitize_grid(filtration_grid)
916
+ if len(filtration_grid) != self.num_parameters:
917
+ raise ValueError(f"Invalid grid to squeeze onto. Got {len(filtration_grid)=} != {self.num_parameters=}.")
918
+ api = api_from_tensor(filtration_grid[0])
919
+ cdef vector[vector[double]] c_filtration_grid = tuple(api.asnumpy(f).astype(np.float64) for f in filtration_grid) # may be faster with loop on views
882
920
  if coordinate_values and inplace:
883
- self.filtration_grid = c_filtration_grid
921
+ self.filtration_grid = filtration_grid
884
922
  if inplace or not coordinate_values:
885
923
  self.get_ptr().squeeze_filtration_inplace(c_filtration_grid, coordinate_values)
886
924
  else:
887
925
  out = SimplexTreeMulti_KFi32(num_parameters=self.num_parameters)
888
926
  self.get_ptr().squeeze_filtration(out.thisptr, c_filtration_grid)
889
- out.filtration_grid = c_filtration_grid
927
+ out.filtration_grid = filtration_grid
890
928
  return out
891
929
  return self
892
930
 
931
+ def unsqueeze(self, grid=None)->SimplexTreeMulti_KFf64:
932
+ from multipers.grids import sanitize_grid
933
+ grid = self.filtration_grid if grid is None else grid
934
+
935
+ cdef vector[vector[double]] cgrid = sanitize_grid(grid, numpyfy=True)
936
+ new_slicer = SimplexTreeMulti_KFf64()
937
+ new_slicer.get_ptr().unsqueeze_filtration(self.thisptr, cgrid)
938
+
939
+ return new_slicer
940
+
893
941
  @property
894
942
  def is_squeezed(self)->bool:
895
- return self.num_vertices > 0 and len(self.filtration_grid)>0 and len(self.filtration_grid[0]) > 0
943
+ return self.num_vertices > 0 and self.filtration_grid is not None and len(self.filtration_grid)>0 and len(self.filtration_grid[0]) > 0
896
944
 
897
945
  @property
898
946
  def dtype(self)->type:
@@ -1185,8 +1233,8 @@ cdef class SimplexTreeMulti_Fi32:
1185
1233
  """
1186
1234
  cdef public intptr_t thisptr
1187
1235
 
1188
- cdef public vector[vector[double]] filtration_grid
1189
- cdef public bool _is_function_simplextree
1236
+ cdef public object filtration_grid
1237
+ cdef public bool _is_function_simplextree # TODO : deprecate
1190
1238
  # Get the pointer casted as it should be
1191
1239
  cdef Simplex_tree_multi_interface[Fi32, int32_t]* get_ptr(self) noexcept nogil:
1192
1240
  return <Simplex_tree_multi_interface[Fi32, int32_t]*>(self.thisptr)
@@ -1257,7 +1305,7 @@ cdef class SimplexTreeMulti_Fi32:
1257
1305
  self.thisptr = <intptr_t>(new Simplex_tree_multi_interface[Fi32, int32_t]())
1258
1306
  self.set_num_parameter(num_parameters)
1259
1307
  self._is_function_simplextree = False
1260
- self.filtration_grid=[[]*num_parameters]
1308
+ self.filtration_grid=[]
1261
1309
 
1262
1310
  def __dealloc__(self):
1263
1311
  cdef Simplex_tree_multi_interface[Fi32,int32_t]* ptr = self.get_ptr()
@@ -1934,7 +1982,16 @@ cdef class SimplexTreeMulti_Fi32:
1934
1982
  out = self.get_ptr().get_edge_list()
1935
1983
  return out
1936
1984
 
1937
- def collapse_edges(self, int num=1, int max_dimension = 0, bool progress=False, bool strong=True, bool full=False, bool ignore_warning=False)->SimplexTreeMulti_Fi32:
1985
+ def collapse_edges(
1986
+ self,
1987
+ int num=1,
1988
+ int max_dimension = 0,
1989
+ bool progress=False,
1990
+ bool strong=True,
1991
+ bool full=False,
1992
+ bool ignore_warning=False,
1993
+ bool auto_clean=True,
1994
+ )->SimplexTreeMulti_Fi32:
1938
1995
  """Edge collapse for 1-critical 2-parameter clique complex (see https://arxiv.org/abs/2211.05574).
1939
1996
  It uses the code from the github repository https://github.com/aj-alonso/filtration_domination .
1940
1997
 
@@ -1984,6 +2041,8 @@ cdef class SimplexTreeMulti_Fi32:
1984
2041
  edges = _collapse_edge_list(edges, num=num, full=full, strong=strong, progress=progress)
1985
2042
  # Retrieves the collapsed simplicial complex
1986
2043
  self._reconstruct_from_edge_list(edges, swap=True, expand_dimension=max_dimension)
2044
+ if self.is_squeezed and auto_clean:
2045
+ self._clean_filtration_grid()
1987
2046
  return self
1988
2047
 
1989
2048
  @cython.inline
@@ -2202,11 +2261,26 @@ cdef class SimplexTreeMulti_Fi32:
2202
2261
  out = self.get_ptr().get_filtration_values(degrees)
2203
2262
  filtrations_values = [np.asarray(filtration) for filtration in out]
2204
2263
  # Removes infs
2205
- if inf_to_nan:
2264
+ if inf_to_nan and np.dtype(filtrations_values[0].dtype).kind == 'f':
2206
2265
  for i,f in enumerate(filtrations_values):
2207
2266
  filtrations_values[i][f == np.inf] = np.nan
2208
2267
  filtrations_values[i][f == - np.inf] = np.nan
2209
2268
  return filtrations_values
2269
+ def _clean_filtration_grid(self):
2270
+ """
2271
+ Removes the values in filtration_grid that are not linked to any splx.
2272
+ """
2273
+ if not self.is_squeezed:
2274
+ raise ValueError("No grid to clean.")
2275
+ F = self.filtration_grid
2276
+ self.filtration_grid=None
2277
+ cleaned_coordinates = compute_grid(self)
2278
+ new_st = self.grid_squeeze(cleaned_coordinates)
2279
+
2280
+ self.thisptr, new_st.thisptr = new_st.thisptr, self.thisptr
2281
+ self.filtration_grid = tuple(f[g] for f,g in zip(F,cleaned_coordinates))
2282
+ return self
2283
+
2210
2284
 
2211
2285
 
2212
2286
  def get_filtration_grid(self, resolution:Iterable[int]|None=None, degrees:Iterable[int]|None=None, drop_quantiles:float|tuple=0, grid_strategy:_available_strategies="exact")->Iterable[np.ndarray]:
@@ -2242,8 +2316,16 @@ cdef class SimplexTreeMulti_Fi32:
2242
2316
  return compute_grid(filtrations_values, resolution=resolution,strategy=grid_strategy,drop_quantiles=drop_quantiles)
2243
2317
 
2244
2318
 
2245
-
2246
- def grid_squeeze(self, filtration_grid:np.ndarray|list|None=None, bool coordinate_values=True, force=False, grid_strategy:_available_strategies = "exact", inplace=False, **filtration_grid_kwargs):
2319
+ def grid_squeeze(
2320
+ self,
2321
+ filtration_grid:np.ndarray|list|None=None,
2322
+ bool coordinate_values=True,
2323
+ bool force=False,
2324
+ str strategy:_available_strategies = "exact",
2325
+ grid_strategy=None,
2326
+ bool inplace=False,
2327
+ **filtration_grid_kwargs
2328
+ )->SimplexTreeMulti_Fi32 | SimplexTreeMulti_Fi32:
2247
2329
  """
2248
2330
  Fit the filtration of the simplextree to a grid.
2249
2331
 
@@ -2254,26 +2336,50 @@ cdef class SimplexTreeMulti_Fi32:
2254
2336
  """
2255
2337
  if not force and self.is_squeezed:
2256
2338
  raise Exception("SimplexTree already squeezed, use `force=True` if that's really what you want to do.")
2339
+
2340
+ if grid_strategy is not None:
2341
+ warn("`grid_strategy` is deprecated, use `strategy` instead.",DeprecationWarning)
2342
+ strategy=grid_strategy
2343
+
2344
+ if self.is_squeezed:
2345
+ warn("(copy warning) Squeezing an already squeezed slicer.")
2346
+ temp = self.unsqueeze()
2347
+ subgrid = compute_grid(self.filtration_grid, strategy=strategy, resolution=resolution)
2348
+ return temp.grid_squeeze(subgrid, coordinates=coordinates, inplace=inplace)
2349
+
2257
2350
  #TODO : multi-critical
2258
2351
  if filtration_grid is None:
2259
- filtration_grid = self.get_filtration_grid(grid_strategy=grid_strategy, **filtration_grid_kwargs)
2260
- cdef vector[vector[double]] c_filtration_grid = filtration_grid
2261
- assert <int>c_filtration_grid.size() == self.get_ptr().get_number_of_parameters(), f"Grid has to be of size {self.num_parameters}, got {filtration_grid.size()}"
2262
- cdef intptr_t ptr = self.thisptr
2352
+ filtration_grid = self.get_filtration_grid(grid_strategy=strategy, **filtration_grid_kwargs)
2353
+ else:
2354
+ filtration_grid = sanitize_grid(filtration_grid)
2355
+ if len(filtration_grid) != self.num_parameters:
2356
+ raise ValueError(f"Invalid grid to squeeze onto. Got {len(filtration_grid)=} != {self.num_parameters=}.")
2357
+ api = api_from_tensor(filtration_grid[0])
2358
+ cdef vector[vector[double]] c_filtration_grid = tuple(api.asnumpy(f).astype(np.float64) for f in filtration_grid) # may be faster with loop on views
2263
2359
  if coordinate_values and inplace:
2264
- self.filtration_grid = c_filtration_grid
2360
+ self.filtration_grid = filtration_grid
2265
2361
  if inplace or not coordinate_values:
2266
2362
  self.get_ptr().squeeze_filtration_inplace(c_filtration_grid, coordinate_values)
2267
2363
  else:
2268
2364
  out = SimplexTreeMulti_Fi32(num_parameters=self.num_parameters)
2269
2365
  self.get_ptr().squeeze_filtration(out.thisptr, c_filtration_grid)
2270
- out.filtration_grid = c_filtration_grid
2366
+ out.filtration_grid = filtration_grid
2271
2367
  return out
2272
2368
  return self
2273
2369
 
2370
+ def unsqueeze(self, grid=None)->SimplexTreeMulti_Ff64:
2371
+ from multipers.grids import sanitize_grid
2372
+ grid = self.filtration_grid if grid is None else grid
2373
+
2374
+ cdef vector[vector[double]] cgrid = sanitize_grid(grid, numpyfy=True)
2375
+ new_slicer = SimplexTreeMulti_Ff64()
2376
+ new_slicer.get_ptr().unsqueeze_filtration(self.thisptr, cgrid)
2377
+
2378
+ return new_slicer
2379
+
2274
2380
  @property
2275
2381
  def is_squeezed(self)->bool:
2276
- return self.num_vertices > 0 and len(self.filtration_grid)>0 and len(self.filtration_grid[0]) > 0
2382
+ return self.num_vertices > 0 and self.filtration_grid is not None and len(self.filtration_grid)>0 and len(self.filtration_grid[0]) > 0
2277
2383
 
2278
2384
  @property
2279
2385
  def dtype(self)->type:
@@ -2564,8 +2670,8 @@ cdef class SimplexTreeMulti_KFi64:
2564
2670
  """
2565
2671
  cdef public intptr_t thisptr
2566
2672
 
2567
- cdef public vector[vector[double]] filtration_grid
2568
- cdef public bool _is_function_simplextree
2673
+ cdef public object filtration_grid
2674
+ cdef public bool _is_function_simplextree # TODO : deprecate
2569
2675
  # Get the pointer casted as it should be
2570
2676
  cdef Simplex_tree_multi_interface[KFi64, int64_t]* get_ptr(self) noexcept nogil:
2571
2677
  return <Simplex_tree_multi_interface[KFi64, int64_t]*>(self.thisptr)
@@ -2636,7 +2742,7 @@ cdef class SimplexTreeMulti_KFi64:
2636
2742
  self.thisptr = <intptr_t>(new Simplex_tree_multi_interface[KFi64, int64_t]())
2637
2743
  self.set_num_parameter(num_parameters)
2638
2744
  self._is_function_simplextree = False
2639
- self.filtration_grid=[[]*num_parameters]
2745
+ self.filtration_grid=[]
2640
2746
 
2641
2747
  def __dealloc__(self):
2642
2748
  cdef Simplex_tree_multi_interface[KFi64,int64_t]* ptr = self.get_ptr()
@@ -3304,11 +3410,26 @@ cdef class SimplexTreeMulti_KFi64:
3304
3410
  out = self.get_ptr().get_filtration_values(degrees)
3305
3411
  filtrations_values = [np.asarray(filtration) for filtration in out]
3306
3412
  # Removes infs
3307
- if inf_to_nan:
3413
+ if inf_to_nan and np.dtype(filtrations_values[0].dtype).kind == 'f':
3308
3414
  for i,f in enumerate(filtrations_values):
3309
3415
  filtrations_values[i][f == np.inf] = np.nan
3310
3416
  filtrations_values[i][f == - np.inf] = np.nan
3311
3417
  return filtrations_values
3418
+ def _clean_filtration_grid(self):
3419
+ """
3420
+ Removes the values in filtration_grid that are not linked to any splx.
3421
+ """
3422
+ if not self.is_squeezed:
3423
+ raise ValueError("No grid to clean.")
3424
+ F = self.filtration_grid
3425
+ self.filtration_grid=None
3426
+ cleaned_coordinates = compute_grid(self)
3427
+ new_st = self.grid_squeeze(cleaned_coordinates)
3428
+
3429
+ self.thisptr, new_st.thisptr = new_st.thisptr, self.thisptr
3430
+ self.filtration_grid = tuple(f[g] for f,g in zip(F,cleaned_coordinates))
3431
+ return self
3432
+
3312
3433
 
3313
3434
 
3314
3435
  def get_filtration_grid(self, resolution:Iterable[int]|None=None, degrees:Iterable[int]|None=None, drop_quantiles:float|tuple=0, grid_strategy:_available_strategies="exact")->Iterable[np.ndarray]:
@@ -3344,8 +3465,16 @@ cdef class SimplexTreeMulti_KFi64:
3344
3465
  return compute_grid(filtrations_values, resolution=resolution,strategy=grid_strategy,drop_quantiles=drop_quantiles)
3345
3466
 
3346
3467
 
3347
-
3348
- def grid_squeeze(self, filtration_grid:np.ndarray|list|None=None, bool coordinate_values=True, force=False, grid_strategy:_available_strategies = "exact", inplace=False, **filtration_grid_kwargs):
3468
+ def grid_squeeze(
3469
+ self,
3470
+ filtration_grid:np.ndarray|list|None=None,
3471
+ bool coordinate_values=True,
3472
+ bool force=False,
3473
+ str strategy:_available_strategies = "exact",
3474
+ grid_strategy=None,
3475
+ bool inplace=False,
3476
+ **filtration_grid_kwargs
3477
+ )->SimplexTreeMulti_KFi32 | SimplexTreeMulti_KFi64:
3349
3478
  """
3350
3479
  Fit the filtration of the simplextree to a grid.
3351
3480
 
@@ -3356,26 +3485,50 @@ cdef class SimplexTreeMulti_KFi64:
3356
3485
  """
3357
3486
  if not force and self.is_squeezed:
3358
3487
  raise Exception("SimplexTree already squeezed, use `force=True` if that's really what you want to do.")
3488
+
3489
+ if grid_strategy is not None:
3490
+ warn("`grid_strategy` is deprecated, use `strategy` instead.",DeprecationWarning)
3491
+ strategy=grid_strategy
3492
+
3493
+ if self.is_squeezed:
3494
+ warn("(copy warning) Squeezing an already squeezed slicer.")
3495
+ temp = self.unsqueeze()
3496
+ subgrid = compute_grid(self.filtration_grid, strategy=strategy, resolution=resolution)
3497
+ return temp.grid_squeeze(subgrid, coordinates=coordinates, inplace=inplace)
3498
+
3359
3499
  #TODO : multi-critical
3360
3500
  if filtration_grid is None:
3361
- filtration_grid = self.get_filtration_grid(grid_strategy=grid_strategy, **filtration_grid_kwargs)
3362
- cdef vector[vector[double]] c_filtration_grid = filtration_grid
3363
- assert <int>c_filtration_grid.size() == self.get_ptr().get_number_of_parameters(), f"Grid has to be of size {self.num_parameters}, got {filtration_grid.size()}"
3364
- cdef intptr_t ptr = self.thisptr
3501
+ filtration_grid = self.get_filtration_grid(grid_strategy=strategy, **filtration_grid_kwargs)
3502
+ else:
3503
+ filtration_grid = sanitize_grid(filtration_grid)
3504
+ if len(filtration_grid) != self.num_parameters:
3505
+ raise ValueError(f"Invalid grid to squeeze onto. Got {len(filtration_grid)=} != {self.num_parameters=}.")
3506
+ api = api_from_tensor(filtration_grid[0])
3507
+ cdef vector[vector[double]] c_filtration_grid = tuple(api.asnumpy(f).astype(np.float64) for f in filtration_grid) # may be faster with loop on views
3365
3508
  if coordinate_values and inplace:
3366
- self.filtration_grid = c_filtration_grid
3509
+ self.filtration_grid = filtration_grid
3367
3510
  if inplace or not coordinate_values:
3368
3511
  self.get_ptr().squeeze_filtration_inplace(c_filtration_grid, coordinate_values)
3369
3512
  else:
3370
3513
  out = SimplexTreeMulti_KFi32(num_parameters=self.num_parameters)
3371
3514
  self.get_ptr().squeeze_filtration(out.thisptr, c_filtration_grid)
3372
- out.filtration_grid = c_filtration_grid
3515
+ out.filtration_grid = filtration_grid
3373
3516
  return out
3374
3517
  return self
3375
3518
 
3519
+ def unsqueeze(self, grid=None)->SimplexTreeMulti_KFf64:
3520
+ from multipers.grids import sanitize_grid
3521
+ grid = self.filtration_grid if grid is None else grid
3522
+
3523
+ cdef vector[vector[double]] cgrid = sanitize_grid(grid, numpyfy=True)
3524
+ new_slicer = SimplexTreeMulti_KFf64()
3525
+ new_slicer.get_ptr().unsqueeze_filtration(self.thisptr, cgrid)
3526
+
3527
+ return new_slicer
3528
+
3376
3529
  @property
3377
3530
  def is_squeezed(self)->bool:
3378
- return self.num_vertices > 0 and len(self.filtration_grid)>0 and len(self.filtration_grid[0]) > 0
3531
+ return self.num_vertices > 0 and self.filtration_grid is not None and len(self.filtration_grid)>0 and len(self.filtration_grid[0]) > 0
3379
3532
 
3380
3533
  @property
3381
3534
  def dtype(self)->type:
@@ -3668,8 +3821,8 @@ cdef class SimplexTreeMulti_Fi64:
3668
3821
  """
3669
3822
  cdef public intptr_t thisptr
3670
3823
 
3671
- cdef public vector[vector[double]] filtration_grid
3672
- cdef public bool _is_function_simplextree
3824
+ cdef public object filtration_grid
3825
+ cdef public bool _is_function_simplextree # TODO : deprecate
3673
3826
  # Get the pointer casted as it should be
3674
3827
  cdef Simplex_tree_multi_interface[Fi64, int64_t]* get_ptr(self) noexcept nogil:
3675
3828
  return <Simplex_tree_multi_interface[Fi64, int64_t]*>(self.thisptr)
@@ -3740,7 +3893,7 @@ cdef class SimplexTreeMulti_Fi64:
3740
3893
  self.thisptr = <intptr_t>(new Simplex_tree_multi_interface[Fi64, int64_t]())
3741
3894
  self.set_num_parameter(num_parameters)
3742
3895
  self._is_function_simplextree = False
3743
- self.filtration_grid=[[]*num_parameters]
3896
+ self.filtration_grid=[]
3744
3897
 
3745
3898
  def __dealloc__(self):
3746
3899
  cdef Simplex_tree_multi_interface[Fi64,int64_t]* ptr = self.get_ptr()
@@ -4417,7 +4570,16 @@ cdef class SimplexTreeMulti_Fi64:
4417
4570
  out = self.get_ptr().get_edge_list()
4418
4571
  return out
4419
4572
 
4420
- def collapse_edges(self, int num=1, int max_dimension = 0, bool progress=False, bool strong=True, bool full=False, bool ignore_warning=False)->SimplexTreeMulti_Fi64:
4573
+ def collapse_edges(
4574
+ self,
4575
+ int num=1,
4576
+ int max_dimension = 0,
4577
+ bool progress=False,
4578
+ bool strong=True,
4579
+ bool full=False,
4580
+ bool ignore_warning=False,
4581
+ bool auto_clean=True,
4582
+ )->SimplexTreeMulti_Fi64:
4421
4583
  """Edge collapse for 1-critical 2-parameter clique complex (see https://arxiv.org/abs/2211.05574).
4422
4584
  It uses the code from the github repository https://github.com/aj-alonso/filtration_domination .
4423
4585
 
@@ -4467,6 +4629,8 @@ cdef class SimplexTreeMulti_Fi64:
4467
4629
  edges = _collapse_edge_list(edges, num=num, full=full, strong=strong, progress=progress)
4468
4630
  # Retrieves the collapsed simplicial complex
4469
4631
  self._reconstruct_from_edge_list(edges, swap=True, expand_dimension=max_dimension)
4632
+ if self.is_squeezed and auto_clean:
4633
+ self._clean_filtration_grid()
4470
4634
  return self
4471
4635
 
4472
4636
  @cython.inline
@@ -4685,11 +4849,26 @@ cdef class SimplexTreeMulti_Fi64:
4685
4849
  out = self.get_ptr().get_filtration_values(degrees)
4686
4850
  filtrations_values = [np.asarray(filtration) for filtration in out]
4687
4851
  # Removes infs
4688
- if inf_to_nan:
4852
+ if inf_to_nan and np.dtype(filtrations_values[0].dtype).kind == 'f':
4689
4853
  for i,f in enumerate(filtrations_values):
4690
4854
  filtrations_values[i][f == np.inf] = np.nan
4691
4855
  filtrations_values[i][f == - np.inf] = np.nan
4692
4856
  return filtrations_values
4857
+ def _clean_filtration_grid(self):
4858
+ """
4859
+ Removes the values in filtration_grid that are not linked to any splx.
4860
+ """
4861
+ if not self.is_squeezed:
4862
+ raise ValueError("No grid to clean.")
4863
+ F = self.filtration_grid
4864
+ self.filtration_grid=None
4865
+ cleaned_coordinates = compute_grid(self)
4866
+ new_st = self.grid_squeeze(cleaned_coordinates)
4867
+
4868
+ self.thisptr, new_st.thisptr = new_st.thisptr, self.thisptr
4869
+ self.filtration_grid = tuple(f[g] for f,g in zip(F,cleaned_coordinates))
4870
+ return self
4871
+
4693
4872
 
4694
4873
 
4695
4874
  def get_filtration_grid(self, resolution:Iterable[int]|None=None, degrees:Iterable[int]|None=None, drop_quantiles:float|tuple=0, grid_strategy:_available_strategies="exact")->Iterable[np.ndarray]:
@@ -4725,8 +4904,16 @@ cdef class SimplexTreeMulti_Fi64:
4725
4904
  return compute_grid(filtrations_values, resolution=resolution,strategy=grid_strategy,drop_quantiles=drop_quantiles)
4726
4905
 
4727
4906
 
4728
-
4729
- def grid_squeeze(self, filtration_grid:np.ndarray|list|None=None, bool coordinate_values=True, force=False, grid_strategy:_available_strategies = "exact", inplace=False, **filtration_grid_kwargs):
4907
+ def grid_squeeze(
4908
+ self,
4909
+ filtration_grid:np.ndarray|list|None=None,
4910
+ bool coordinate_values=True,
4911
+ bool force=False,
4912
+ str strategy:_available_strategies = "exact",
4913
+ grid_strategy=None,
4914
+ bool inplace=False,
4915
+ **filtration_grid_kwargs
4916
+ )->SimplexTreeMulti_Fi32 | SimplexTreeMulti_Fi64:
4730
4917
  """
4731
4918
  Fit the filtration of the simplextree to a grid.
4732
4919
 
@@ -4737,26 +4924,50 @@ cdef class SimplexTreeMulti_Fi64:
4737
4924
  """
4738
4925
  if not force and self.is_squeezed:
4739
4926
  raise Exception("SimplexTree already squeezed, use `force=True` if that's really what you want to do.")
4927
+
4928
+ if grid_strategy is not None:
4929
+ warn("`grid_strategy` is deprecated, use `strategy` instead.",DeprecationWarning)
4930
+ strategy=grid_strategy
4931
+
4932
+ if self.is_squeezed:
4933
+ warn("(copy warning) Squeezing an already squeezed slicer.")
4934
+ temp = self.unsqueeze()
4935
+ subgrid = compute_grid(self.filtration_grid, strategy=strategy, resolution=resolution)
4936
+ return temp.grid_squeeze(subgrid, coordinates=coordinates, inplace=inplace)
4937
+
4740
4938
  #TODO : multi-critical
4741
4939
  if filtration_grid is None:
4742
- filtration_grid = self.get_filtration_grid(grid_strategy=grid_strategy, **filtration_grid_kwargs)
4743
- cdef vector[vector[double]] c_filtration_grid = filtration_grid
4744
- assert <int>c_filtration_grid.size() == self.get_ptr().get_number_of_parameters(), f"Grid has to be of size {self.num_parameters}, got {filtration_grid.size()}"
4745
- cdef intptr_t ptr = self.thisptr
4940
+ filtration_grid = self.get_filtration_grid(grid_strategy=strategy, **filtration_grid_kwargs)
4941
+ else:
4942
+ filtration_grid = sanitize_grid(filtration_grid)
4943
+ if len(filtration_grid) != self.num_parameters:
4944
+ raise ValueError(f"Invalid grid to squeeze onto. Got {len(filtration_grid)=} != {self.num_parameters=}.")
4945
+ api = api_from_tensor(filtration_grid[0])
4946
+ cdef vector[vector[double]] c_filtration_grid = tuple(api.asnumpy(f).astype(np.float64) for f in filtration_grid) # may be faster with loop on views
4746
4947
  if coordinate_values and inplace:
4747
- self.filtration_grid = c_filtration_grid
4948
+ self.filtration_grid = filtration_grid
4748
4949
  if inplace or not coordinate_values:
4749
4950
  self.get_ptr().squeeze_filtration_inplace(c_filtration_grid, coordinate_values)
4750
4951
  else:
4751
4952
  out = SimplexTreeMulti_Fi32(num_parameters=self.num_parameters)
4752
4953
  self.get_ptr().squeeze_filtration(out.thisptr, c_filtration_grid)
4753
- out.filtration_grid = c_filtration_grid
4954
+ out.filtration_grid = filtration_grid
4754
4955
  return out
4755
4956
  return self
4756
4957
 
4958
+ def unsqueeze(self, grid=None)->SimplexTreeMulti_Ff64:
4959
+ from multipers.grids import sanitize_grid
4960
+ grid = self.filtration_grid if grid is None else grid
4961
+
4962
+ cdef vector[vector[double]] cgrid = sanitize_grid(grid, numpyfy=True)
4963
+ new_slicer = SimplexTreeMulti_Ff64()
4964
+ new_slicer.get_ptr().unsqueeze_filtration(self.thisptr, cgrid)
4965
+
4966
+ return new_slicer
4967
+
4757
4968
  @property
4758
4969
  def is_squeezed(self)->bool:
4759
- return self.num_vertices > 0 and len(self.filtration_grid)>0 and len(self.filtration_grid[0]) > 0
4970
+ return self.num_vertices > 0 and self.filtration_grid is not None and len(self.filtration_grid)>0 and len(self.filtration_grid[0]) > 0
4760
4971
 
4761
4972
  @property
4762
4973
  def dtype(self)->type:
@@ -5047,8 +5258,8 @@ cdef class SimplexTreeMulti_KFf32:
5047
5258
  """
5048
5259
  cdef public intptr_t thisptr
5049
5260
 
5050
- cdef public vector[vector[double]] filtration_grid
5051
- cdef public bool _is_function_simplextree
5261
+ cdef public object filtration_grid
5262
+ cdef public bool _is_function_simplextree # TODO : deprecate
5052
5263
  # Get the pointer casted as it should be
5053
5264
  cdef Simplex_tree_multi_interface[KFf32, float]* get_ptr(self) noexcept nogil:
5054
5265
  return <Simplex_tree_multi_interface[KFf32, float]*>(self.thisptr)
@@ -5119,7 +5330,7 @@ cdef class SimplexTreeMulti_KFf32:
5119
5330
  self.thisptr = <intptr_t>(new Simplex_tree_multi_interface[KFf32, float]())
5120
5331
  self.set_num_parameter(num_parameters)
5121
5332
  self._is_function_simplextree = False
5122
- self.filtration_grid=[[]*num_parameters]
5333
+ self.filtration_grid=[]
5123
5334
 
5124
5335
  def __dealloc__(self):
5125
5336
  cdef Simplex_tree_multi_interface[KFf32,float]* ptr = self.get_ptr()
@@ -5787,11 +5998,26 @@ cdef class SimplexTreeMulti_KFf32:
5787
5998
  out = self.get_ptr().get_filtration_values(degrees)
5788
5999
  filtrations_values = [np.asarray(filtration) for filtration in out]
5789
6000
  # Removes infs
5790
- if inf_to_nan:
6001
+ if inf_to_nan and np.dtype(filtrations_values[0].dtype).kind == 'f':
5791
6002
  for i,f in enumerate(filtrations_values):
5792
6003
  filtrations_values[i][f == np.inf] = np.nan
5793
6004
  filtrations_values[i][f == - np.inf] = np.nan
5794
6005
  return filtrations_values
6006
+ def _clean_filtration_grid(self):
6007
+ """
6008
+ Removes the values in filtration_grid that are not linked to any splx.
6009
+ """
6010
+ if not self.is_squeezed:
6011
+ raise ValueError("No grid to clean.")
6012
+ F = self.filtration_grid
6013
+ self.filtration_grid=None
6014
+ cleaned_coordinates = compute_grid(self)
6015
+ new_st = self.grid_squeeze(cleaned_coordinates)
6016
+
6017
+ self.thisptr, new_st.thisptr = new_st.thisptr, self.thisptr
6018
+ self.filtration_grid = tuple(f[g] for f,g in zip(F,cleaned_coordinates))
6019
+ return self
6020
+
5795
6021
 
5796
6022
 
5797
6023
  def get_filtration_grid(self, resolution:Iterable[int]|None=None, degrees:Iterable[int]|None=None, drop_quantiles:float|tuple=0, grid_strategy:_available_strategies="exact")->Iterable[np.ndarray]:
@@ -5827,8 +6053,16 @@ cdef class SimplexTreeMulti_KFf32:
5827
6053
  return compute_grid(filtrations_values, resolution=resolution,strategy=grid_strategy,drop_quantiles=drop_quantiles)
5828
6054
 
5829
6055
 
5830
-
5831
- def grid_squeeze(self, filtration_grid:np.ndarray|list|None=None, bool coordinate_values=True, force=False, grid_strategy:_available_strategies = "exact", inplace=False, **filtration_grid_kwargs):
6056
+ def grid_squeeze(
6057
+ self,
6058
+ filtration_grid:np.ndarray|list|None=None,
6059
+ bool coordinate_values=True,
6060
+ bool force=False,
6061
+ str strategy:_available_strategies = "exact",
6062
+ grid_strategy=None,
6063
+ bool inplace=False,
6064
+ **filtration_grid_kwargs
6065
+ )->SimplexTreeMulti_KFi32 | SimplexTreeMulti_KFf32:
5832
6066
  """
5833
6067
  Fit the filtration of the simplextree to a grid.
5834
6068
 
@@ -5839,26 +6073,50 @@ cdef class SimplexTreeMulti_KFf32:
5839
6073
  """
5840
6074
  if not force and self.is_squeezed:
5841
6075
  raise Exception("SimplexTree already squeezed, use `force=True` if that's really what you want to do.")
6076
+
6077
+ if grid_strategy is not None:
6078
+ warn("`grid_strategy` is deprecated, use `strategy` instead.",DeprecationWarning)
6079
+ strategy=grid_strategy
6080
+
6081
+ if self.is_squeezed:
6082
+ warn("(copy warning) Squeezing an already squeezed slicer.")
6083
+ temp = self.unsqueeze()
6084
+ subgrid = compute_grid(self.filtration_grid, strategy=strategy, resolution=resolution)
6085
+ return temp.grid_squeeze(subgrid, coordinates=coordinates, inplace=inplace)
6086
+
5842
6087
  #TODO : multi-critical
5843
6088
  if filtration_grid is None:
5844
- filtration_grid = self.get_filtration_grid(grid_strategy=grid_strategy, **filtration_grid_kwargs)
5845
- cdef vector[vector[double]] c_filtration_grid = filtration_grid
5846
- assert <int>c_filtration_grid.size() == self.get_ptr().get_number_of_parameters(), f"Grid has to be of size {self.num_parameters}, got {filtration_grid.size()}"
5847
- cdef intptr_t ptr = self.thisptr
6089
+ filtration_grid = self.get_filtration_grid(grid_strategy=strategy, **filtration_grid_kwargs)
6090
+ else:
6091
+ filtration_grid = sanitize_grid(filtration_grid)
6092
+ if len(filtration_grid) != self.num_parameters:
6093
+ raise ValueError(f"Invalid grid to squeeze onto. Got {len(filtration_grid)=} != {self.num_parameters=}.")
6094
+ api = api_from_tensor(filtration_grid[0])
6095
+ cdef vector[vector[double]] c_filtration_grid = tuple(api.asnumpy(f).astype(np.float64) for f in filtration_grid) # may be faster with loop on views
5848
6096
  if coordinate_values and inplace:
5849
- self.filtration_grid = c_filtration_grid
6097
+ self.filtration_grid = filtration_grid
5850
6098
  if inplace or not coordinate_values:
5851
6099
  self.get_ptr().squeeze_filtration_inplace(c_filtration_grid, coordinate_values)
5852
6100
  else:
5853
6101
  out = SimplexTreeMulti_KFi32(num_parameters=self.num_parameters)
5854
6102
  self.get_ptr().squeeze_filtration(out.thisptr, c_filtration_grid)
5855
- out.filtration_grid = c_filtration_grid
6103
+ out.filtration_grid = filtration_grid
5856
6104
  return out
5857
6105
  return self
5858
6106
 
6107
+ def unsqueeze(self, grid=None)->SimplexTreeMulti_KFf64:
6108
+ from multipers.grids import sanitize_grid
6109
+ grid = self.filtration_grid if grid is None else grid
6110
+
6111
+ cdef vector[vector[double]] cgrid = sanitize_grid(grid, numpyfy=True)
6112
+ new_slicer = SimplexTreeMulti_KFf64()
6113
+ new_slicer.get_ptr().unsqueeze_filtration(self.thisptr, cgrid)
6114
+
6115
+ return new_slicer
6116
+
5859
6117
  @property
5860
6118
  def is_squeezed(self)->bool:
5861
- return self.num_vertices > 0 and len(self.filtration_grid)>0 and len(self.filtration_grid[0]) > 0
6119
+ return self.num_vertices > 0 and self.filtration_grid is not None and len(self.filtration_grid)>0 and len(self.filtration_grid[0]) > 0
5862
6120
 
5863
6121
  @property
5864
6122
  def dtype(self)->type:
@@ -6151,8 +6409,8 @@ cdef class SimplexTreeMulti_Ff32:
6151
6409
  """
6152
6410
  cdef public intptr_t thisptr
6153
6411
 
6154
- cdef public vector[vector[double]] filtration_grid
6155
- cdef public bool _is_function_simplextree
6412
+ cdef public object filtration_grid
6413
+ cdef public bool _is_function_simplextree # TODO : deprecate
6156
6414
  # Get the pointer casted as it should be
6157
6415
  cdef Simplex_tree_multi_interface[Ff32, float]* get_ptr(self) noexcept nogil:
6158
6416
  return <Simplex_tree_multi_interface[Ff32, float]*>(self.thisptr)
@@ -6223,7 +6481,7 @@ cdef class SimplexTreeMulti_Ff32:
6223
6481
  self.thisptr = <intptr_t>(new Simplex_tree_multi_interface[Ff32, float]())
6224
6482
  self.set_num_parameter(num_parameters)
6225
6483
  self._is_function_simplextree = False
6226
- self.filtration_grid=[[]*num_parameters]
6484
+ self.filtration_grid=[]
6227
6485
 
6228
6486
  def __dealloc__(self):
6229
6487
  cdef Simplex_tree_multi_interface[Ff32,float]* ptr = self.get_ptr()
@@ -6900,7 +7158,16 @@ cdef class SimplexTreeMulti_Ff32:
6900
7158
  out = self.get_ptr().get_edge_list()
6901
7159
  return out
6902
7160
 
6903
- def collapse_edges(self, int num=1, int max_dimension = 0, bool progress=False, bool strong=True, bool full=False, bool ignore_warning=False)->SimplexTreeMulti_Ff32:
7161
+ def collapse_edges(
7162
+ self,
7163
+ int num=1,
7164
+ int max_dimension = 0,
7165
+ bool progress=False,
7166
+ bool strong=True,
7167
+ bool full=False,
7168
+ bool ignore_warning=False,
7169
+ bool auto_clean=True,
7170
+ )->SimplexTreeMulti_Ff32:
6904
7171
  """Edge collapse for 1-critical 2-parameter clique complex (see https://arxiv.org/abs/2211.05574).
6905
7172
  It uses the code from the github repository https://github.com/aj-alonso/filtration_domination .
6906
7173
 
@@ -6950,6 +7217,8 @@ cdef class SimplexTreeMulti_Ff32:
6950
7217
  edges = _collapse_edge_list(edges, num=num, full=full, strong=strong, progress=progress)
6951
7218
  # Retrieves the collapsed simplicial complex
6952
7219
  self._reconstruct_from_edge_list(edges, swap=True, expand_dimension=max_dimension)
7220
+ if self.is_squeezed and auto_clean:
7221
+ self._clean_filtration_grid()
6953
7222
  return self
6954
7223
 
6955
7224
  @cython.inline
@@ -7168,11 +7437,26 @@ cdef class SimplexTreeMulti_Ff32:
7168
7437
  out = self.get_ptr().get_filtration_values(degrees)
7169
7438
  filtrations_values = [np.asarray(filtration) for filtration in out]
7170
7439
  # Removes infs
7171
- if inf_to_nan:
7440
+ if inf_to_nan and np.dtype(filtrations_values[0].dtype).kind == 'f':
7172
7441
  for i,f in enumerate(filtrations_values):
7173
7442
  filtrations_values[i][f == np.inf] = np.nan
7174
7443
  filtrations_values[i][f == - np.inf] = np.nan
7175
7444
  return filtrations_values
7445
+ def _clean_filtration_grid(self):
7446
+ """
7447
+ Removes the values in filtration_grid that are not linked to any splx.
7448
+ """
7449
+ if not self.is_squeezed:
7450
+ raise ValueError("No grid to clean.")
7451
+ F = self.filtration_grid
7452
+ self.filtration_grid=None
7453
+ cleaned_coordinates = compute_grid(self)
7454
+ new_st = self.grid_squeeze(cleaned_coordinates)
7455
+
7456
+ self.thisptr, new_st.thisptr = new_st.thisptr, self.thisptr
7457
+ self.filtration_grid = tuple(f[g] for f,g in zip(F,cleaned_coordinates))
7458
+ return self
7459
+
7176
7460
 
7177
7461
 
7178
7462
  def get_filtration_grid(self, resolution:Iterable[int]|None=None, degrees:Iterable[int]|None=None, drop_quantiles:float|tuple=0, grid_strategy:_available_strategies="exact")->Iterable[np.ndarray]:
@@ -7208,8 +7492,16 @@ cdef class SimplexTreeMulti_Ff32:
7208
7492
  return compute_grid(filtrations_values, resolution=resolution,strategy=grid_strategy,drop_quantiles=drop_quantiles)
7209
7493
 
7210
7494
 
7211
-
7212
- def grid_squeeze(self, filtration_grid:np.ndarray|list|None=None, bool coordinate_values=True, force=False, grid_strategy:_available_strategies = "exact", inplace=False, **filtration_grid_kwargs):
7495
+ def grid_squeeze(
7496
+ self,
7497
+ filtration_grid:np.ndarray|list|None=None,
7498
+ bool coordinate_values=True,
7499
+ bool force=False,
7500
+ str strategy:_available_strategies = "exact",
7501
+ grid_strategy=None,
7502
+ bool inplace=False,
7503
+ **filtration_grid_kwargs
7504
+ )->SimplexTreeMulti_Fi32 | SimplexTreeMulti_Ff32:
7213
7505
  """
7214
7506
  Fit the filtration of the simplextree to a grid.
7215
7507
 
@@ -7220,26 +7512,50 @@ cdef class SimplexTreeMulti_Ff32:
7220
7512
  """
7221
7513
  if not force and self.is_squeezed:
7222
7514
  raise Exception("SimplexTree already squeezed, use `force=True` if that's really what you want to do.")
7515
+
7516
+ if grid_strategy is not None:
7517
+ warn("`grid_strategy` is deprecated, use `strategy` instead.",DeprecationWarning)
7518
+ strategy=grid_strategy
7519
+
7520
+ if self.is_squeezed:
7521
+ warn("(copy warning) Squeezing an already squeezed slicer.")
7522
+ temp = self.unsqueeze()
7523
+ subgrid = compute_grid(self.filtration_grid, strategy=strategy, resolution=resolution)
7524
+ return temp.grid_squeeze(subgrid, coordinates=coordinates, inplace=inplace)
7525
+
7223
7526
  #TODO : multi-critical
7224
7527
  if filtration_grid is None:
7225
- filtration_grid = self.get_filtration_grid(grid_strategy=grid_strategy, **filtration_grid_kwargs)
7226
- cdef vector[vector[double]] c_filtration_grid = filtration_grid
7227
- assert <int>c_filtration_grid.size() == self.get_ptr().get_number_of_parameters(), f"Grid has to be of size {self.num_parameters}, got {filtration_grid.size()}"
7228
- cdef intptr_t ptr = self.thisptr
7528
+ filtration_grid = self.get_filtration_grid(grid_strategy=strategy, **filtration_grid_kwargs)
7529
+ else:
7530
+ filtration_grid = sanitize_grid(filtration_grid)
7531
+ if len(filtration_grid) != self.num_parameters:
7532
+ raise ValueError(f"Invalid grid to squeeze onto. Got {len(filtration_grid)=} != {self.num_parameters=}.")
7533
+ api = api_from_tensor(filtration_grid[0])
7534
+ cdef vector[vector[double]] c_filtration_grid = tuple(api.asnumpy(f).astype(np.float64) for f in filtration_grid) # may be faster with loop on views
7229
7535
  if coordinate_values and inplace:
7230
- self.filtration_grid = c_filtration_grid
7536
+ self.filtration_grid = filtration_grid
7231
7537
  if inplace or not coordinate_values:
7232
7538
  self.get_ptr().squeeze_filtration_inplace(c_filtration_grid, coordinate_values)
7233
7539
  else:
7234
7540
  out = SimplexTreeMulti_Fi32(num_parameters=self.num_parameters)
7235
7541
  self.get_ptr().squeeze_filtration(out.thisptr, c_filtration_grid)
7236
- out.filtration_grid = c_filtration_grid
7542
+ out.filtration_grid = filtration_grid
7237
7543
  return out
7238
7544
  return self
7239
7545
 
7546
+ def unsqueeze(self, grid=None)->SimplexTreeMulti_Ff64:
7547
+ from multipers.grids import sanitize_grid
7548
+ grid = self.filtration_grid if grid is None else grid
7549
+
7550
+ cdef vector[vector[double]] cgrid = sanitize_grid(grid, numpyfy=True)
7551
+ new_slicer = SimplexTreeMulti_Ff64()
7552
+ new_slicer.get_ptr().unsqueeze_filtration(self.thisptr, cgrid)
7553
+
7554
+ return new_slicer
7555
+
7240
7556
  @property
7241
7557
  def is_squeezed(self)->bool:
7242
- return self.num_vertices > 0 and len(self.filtration_grid)>0 and len(self.filtration_grid[0]) > 0
7558
+ return self.num_vertices > 0 and self.filtration_grid is not None and len(self.filtration_grid)>0 and len(self.filtration_grid[0]) > 0
7243
7559
 
7244
7560
  @property
7245
7561
  def dtype(self)->type:
@@ -7530,8 +7846,8 @@ cdef class SimplexTreeMulti_KFf64:
7530
7846
  """
7531
7847
  cdef public intptr_t thisptr
7532
7848
 
7533
- cdef public vector[vector[double]] filtration_grid
7534
- cdef public bool _is_function_simplextree
7849
+ cdef public object filtration_grid
7850
+ cdef public bool _is_function_simplextree # TODO : deprecate
7535
7851
  # Get the pointer casted as it should be
7536
7852
  cdef Simplex_tree_multi_interface[KFf64, double]* get_ptr(self) noexcept nogil:
7537
7853
  return <Simplex_tree_multi_interface[KFf64, double]*>(self.thisptr)
@@ -7602,7 +7918,7 @@ cdef class SimplexTreeMulti_KFf64:
7602
7918
  self.thisptr = <intptr_t>(new Simplex_tree_multi_interface[KFf64, double]())
7603
7919
  self.set_num_parameter(num_parameters)
7604
7920
  self._is_function_simplextree = False
7605
- self.filtration_grid=[[]*num_parameters]
7921
+ self.filtration_grid=[]
7606
7922
 
7607
7923
  def __dealloc__(self):
7608
7924
  cdef Simplex_tree_multi_interface[KFf64,double]* ptr = self.get_ptr()
@@ -8270,11 +8586,26 @@ cdef class SimplexTreeMulti_KFf64:
8270
8586
  out = self.get_ptr().get_filtration_values(degrees)
8271
8587
  filtrations_values = [np.asarray(filtration) for filtration in out]
8272
8588
  # Removes infs
8273
- if inf_to_nan:
8589
+ if inf_to_nan and np.dtype(filtrations_values[0].dtype).kind == 'f':
8274
8590
  for i,f in enumerate(filtrations_values):
8275
8591
  filtrations_values[i][f == np.inf] = np.nan
8276
8592
  filtrations_values[i][f == - np.inf] = np.nan
8277
8593
  return filtrations_values
8594
+ def _clean_filtration_grid(self):
8595
+ """
8596
+ Removes the values in filtration_grid that are not linked to any splx.
8597
+ """
8598
+ if not self.is_squeezed:
8599
+ raise ValueError("No grid to clean.")
8600
+ F = self.filtration_grid
8601
+ self.filtration_grid=None
8602
+ cleaned_coordinates = compute_grid(self)
8603
+ new_st = self.grid_squeeze(cleaned_coordinates)
8604
+
8605
+ self.thisptr, new_st.thisptr = new_st.thisptr, self.thisptr
8606
+ self.filtration_grid = tuple(f[g] for f,g in zip(F,cleaned_coordinates))
8607
+ return self
8608
+
8278
8609
 
8279
8610
 
8280
8611
  def get_filtration_grid(self, resolution:Iterable[int]|None=None, degrees:Iterable[int]|None=None, drop_quantiles:float|tuple=0, grid_strategy:_available_strategies="exact")->Iterable[np.ndarray]:
@@ -8310,8 +8641,16 @@ cdef class SimplexTreeMulti_KFf64:
8310
8641
  return compute_grid(filtrations_values, resolution=resolution,strategy=grid_strategy,drop_quantiles=drop_quantiles)
8311
8642
 
8312
8643
 
8313
-
8314
- def grid_squeeze(self, filtration_grid:np.ndarray|list|None=None, bool coordinate_values=True, force=False, grid_strategy:_available_strategies = "exact", inplace=False, **filtration_grid_kwargs):
8644
+ def grid_squeeze(
8645
+ self,
8646
+ filtration_grid:np.ndarray|list|None=None,
8647
+ bool coordinate_values=True,
8648
+ bool force=False,
8649
+ str strategy:_available_strategies = "exact",
8650
+ grid_strategy=None,
8651
+ bool inplace=False,
8652
+ **filtration_grid_kwargs
8653
+ )->SimplexTreeMulti_KFi32 | SimplexTreeMulti_KFf64:
8315
8654
  """
8316
8655
  Fit the filtration of the simplextree to a grid.
8317
8656
 
@@ -8322,26 +8661,50 @@ cdef class SimplexTreeMulti_KFf64:
8322
8661
  """
8323
8662
  if not force and self.is_squeezed:
8324
8663
  raise Exception("SimplexTree already squeezed, use `force=True` if that's really what you want to do.")
8664
+
8665
+ if grid_strategy is not None:
8666
+ warn("`grid_strategy` is deprecated, use `strategy` instead.",DeprecationWarning)
8667
+ strategy=grid_strategy
8668
+
8669
+ if self.is_squeezed:
8670
+ warn("(copy warning) Squeezing an already squeezed slicer.")
8671
+ temp = self.unsqueeze()
8672
+ subgrid = compute_grid(self.filtration_grid, strategy=strategy, resolution=resolution)
8673
+ return temp.grid_squeeze(subgrid, coordinates=coordinates, inplace=inplace)
8674
+
8325
8675
  #TODO : multi-critical
8326
8676
  if filtration_grid is None:
8327
- filtration_grid = self.get_filtration_grid(grid_strategy=grid_strategy, **filtration_grid_kwargs)
8328
- cdef vector[vector[double]] c_filtration_grid = filtration_grid
8329
- assert <int>c_filtration_grid.size() == self.get_ptr().get_number_of_parameters(), f"Grid has to be of size {self.num_parameters}, got {filtration_grid.size()}"
8330
- cdef intptr_t ptr = self.thisptr
8677
+ filtration_grid = self.get_filtration_grid(grid_strategy=strategy, **filtration_grid_kwargs)
8678
+ else:
8679
+ filtration_grid = sanitize_grid(filtration_grid)
8680
+ if len(filtration_grid) != self.num_parameters:
8681
+ raise ValueError(f"Invalid grid to squeeze onto. Got {len(filtration_grid)=} != {self.num_parameters=}.")
8682
+ api = api_from_tensor(filtration_grid[0])
8683
+ cdef vector[vector[double]] c_filtration_grid = tuple(api.asnumpy(f).astype(np.float64) for f in filtration_grid) # may be faster with loop on views
8331
8684
  if coordinate_values and inplace:
8332
- self.filtration_grid = c_filtration_grid
8685
+ self.filtration_grid = filtration_grid
8333
8686
  if inplace or not coordinate_values:
8334
8687
  self.get_ptr().squeeze_filtration_inplace(c_filtration_grid, coordinate_values)
8335
8688
  else:
8336
8689
  out = SimplexTreeMulti_KFi32(num_parameters=self.num_parameters)
8337
8690
  self.get_ptr().squeeze_filtration(out.thisptr, c_filtration_grid)
8338
- out.filtration_grid = c_filtration_grid
8691
+ out.filtration_grid = filtration_grid
8339
8692
  return out
8340
8693
  return self
8341
8694
 
8695
+ def unsqueeze(self, grid=None)->SimplexTreeMulti_KFf64:
8696
+ from multipers.grids import sanitize_grid
8697
+ grid = self.filtration_grid if grid is None else grid
8698
+
8699
+ cdef vector[vector[double]] cgrid = sanitize_grid(grid, numpyfy=True)
8700
+ new_slicer = SimplexTreeMulti_KFf64()
8701
+ new_slicer.get_ptr().unsqueeze_filtration(self.thisptr, cgrid)
8702
+
8703
+ return new_slicer
8704
+
8342
8705
  @property
8343
8706
  def is_squeezed(self)->bool:
8344
- return self.num_vertices > 0 and len(self.filtration_grid)>0 and len(self.filtration_grid[0]) > 0
8707
+ return self.num_vertices > 0 and self.filtration_grid is not None and len(self.filtration_grid)>0 and len(self.filtration_grid[0]) > 0
8345
8708
 
8346
8709
  @property
8347
8710
  def dtype(self)->type:
@@ -8634,8 +8997,8 @@ cdef class SimplexTreeMulti_Ff64:
8634
8997
  """
8635
8998
  cdef public intptr_t thisptr
8636
8999
 
8637
- cdef public vector[vector[double]] filtration_grid
8638
- cdef public bool _is_function_simplextree
9000
+ cdef public object filtration_grid
9001
+ cdef public bool _is_function_simplextree # TODO : deprecate
8639
9002
  # Get the pointer casted as it should be
8640
9003
  cdef Simplex_tree_multi_interface[Ff64, double]* get_ptr(self) noexcept nogil:
8641
9004
  return <Simplex_tree_multi_interface[Ff64, double]*>(self.thisptr)
@@ -8706,7 +9069,7 @@ cdef class SimplexTreeMulti_Ff64:
8706
9069
  self.thisptr = <intptr_t>(new Simplex_tree_multi_interface[Ff64, double]())
8707
9070
  self.set_num_parameter(num_parameters)
8708
9071
  self._is_function_simplextree = False
8709
- self.filtration_grid=[[]*num_parameters]
9072
+ self.filtration_grid=[]
8710
9073
 
8711
9074
  def __dealloc__(self):
8712
9075
  cdef Simplex_tree_multi_interface[Ff64,double]* ptr = self.get_ptr()
@@ -9383,7 +9746,16 @@ cdef class SimplexTreeMulti_Ff64:
9383
9746
  out = self.get_ptr().get_edge_list()
9384
9747
  return out
9385
9748
 
9386
- def collapse_edges(self, int num=1, int max_dimension = 0, bool progress=False, bool strong=True, bool full=False, bool ignore_warning=False)->SimplexTreeMulti_Ff64:
9749
+ def collapse_edges(
9750
+ self,
9751
+ int num=1,
9752
+ int max_dimension = 0,
9753
+ bool progress=False,
9754
+ bool strong=True,
9755
+ bool full=False,
9756
+ bool ignore_warning=False,
9757
+ bool auto_clean=True,
9758
+ )->SimplexTreeMulti_Ff64:
9387
9759
  """Edge collapse for 1-critical 2-parameter clique complex (see https://arxiv.org/abs/2211.05574).
9388
9760
  It uses the code from the github repository https://github.com/aj-alonso/filtration_domination .
9389
9761
 
@@ -9433,6 +9805,8 @@ cdef class SimplexTreeMulti_Ff64:
9433
9805
  edges = _collapse_edge_list(edges, num=num, full=full, strong=strong, progress=progress)
9434
9806
  # Retrieves the collapsed simplicial complex
9435
9807
  self._reconstruct_from_edge_list(edges, swap=True, expand_dimension=max_dimension)
9808
+ if self.is_squeezed and auto_clean:
9809
+ self._clean_filtration_grid()
9436
9810
  return self
9437
9811
 
9438
9812
  @cython.inline
@@ -9651,11 +10025,26 @@ cdef class SimplexTreeMulti_Ff64:
9651
10025
  out = self.get_ptr().get_filtration_values(degrees)
9652
10026
  filtrations_values = [np.asarray(filtration) for filtration in out]
9653
10027
  # Removes infs
9654
- if inf_to_nan:
10028
+ if inf_to_nan and np.dtype(filtrations_values[0].dtype).kind == 'f':
9655
10029
  for i,f in enumerate(filtrations_values):
9656
10030
  filtrations_values[i][f == np.inf] = np.nan
9657
10031
  filtrations_values[i][f == - np.inf] = np.nan
9658
10032
  return filtrations_values
10033
+ def _clean_filtration_grid(self):
10034
+ """
10035
+ Removes the values in filtration_grid that are not linked to any splx.
10036
+ """
10037
+ if not self.is_squeezed:
10038
+ raise ValueError("No grid to clean.")
10039
+ F = self.filtration_grid
10040
+ self.filtration_grid=None
10041
+ cleaned_coordinates = compute_grid(self)
10042
+ new_st = self.grid_squeeze(cleaned_coordinates)
10043
+
10044
+ self.thisptr, new_st.thisptr = new_st.thisptr, self.thisptr
10045
+ self.filtration_grid = tuple(f[g] for f,g in zip(F,cleaned_coordinates))
10046
+ return self
10047
+
9659
10048
 
9660
10049
 
9661
10050
  def get_filtration_grid(self, resolution:Iterable[int]|None=None, degrees:Iterable[int]|None=None, drop_quantiles:float|tuple=0, grid_strategy:_available_strategies="exact")->Iterable[np.ndarray]:
@@ -9691,8 +10080,16 @@ cdef class SimplexTreeMulti_Ff64:
9691
10080
  return compute_grid(filtrations_values, resolution=resolution,strategy=grid_strategy,drop_quantiles=drop_quantiles)
9692
10081
 
9693
10082
 
9694
-
9695
- def grid_squeeze(self, filtration_grid:np.ndarray|list|None=None, bool coordinate_values=True, force=False, grid_strategy:_available_strategies = "exact", inplace=False, **filtration_grid_kwargs):
10083
+ def grid_squeeze(
10084
+ self,
10085
+ filtration_grid:np.ndarray|list|None=None,
10086
+ bool coordinate_values=True,
10087
+ bool force=False,
10088
+ str strategy:_available_strategies = "exact",
10089
+ grid_strategy=None,
10090
+ bool inplace=False,
10091
+ **filtration_grid_kwargs
10092
+ )->SimplexTreeMulti_Fi32 | SimplexTreeMulti_Ff64:
9696
10093
  """
9697
10094
  Fit the filtration of the simplextree to a grid.
9698
10095
 
@@ -9703,26 +10100,50 @@ cdef class SimplexTreeMulti_Ff64:
9703
10100
  """
9704
10101
  if not force and self.is_squeezed:
9705
10102
  raise Exception("SimplexTree already squeezed, use `force=True` if that's really what you want to do.")
10103
+
10104
+ if grid_strategy is not None:
10105
+ warn("`grid_strategy` is deprecated, use `strategy` instead.",DeprecationWarning)
10106
+ strategy=grid_strategy
10107
+
10108
+ if self.is_squeezed:
10109
+ warn("(copy warning) Squeezing an already squeezed slicer.")
10110
+ temp = self.unsqueeze()
10111
+ subgrid = compute_grid(self.filtration_grid, strategy=strategy, resolution=resolution)
10112
+ return temp.grid_squeeze(subgrid, coordinates=coordinates, inplace=inplace)
10113
+
9706
10114
  #TODO : multi-critical
9707
10115
  if filtration_grid is None:
9708
- filtration_grid = self.get_filtration_grid(grid_strategy=grid_strategy, **filtration_grid_kwargs)
9709
- cdef vector[vector[double]] c_filtration_grid = filtration_grid
9710
- assert <int>c_filtration_grid.size() == self.get_ptr().get_number_of_parameters(), f"Grid has to be of size {self.num_parameters}, got {filtration_grid.size()}"
9711
- cdef intptr_t ptr = self.thisptr
10116
+ filtration_grid = self.get_filtration_grid(grid_strategy=strategy, **filtration_grid_kwargs)
10117
+ else:
10118
+ filtration_grid = sanitize_grid(filtration_grid)
10119
+ if len(filtration_grid) != self.num_parameters:
10120
+ raise ValueError(f"Invalid grid to squeeze onto. Got {len(filtration_grid)=} != {self.num_parameters=}.")
10121
+ api = api_from_tensor(filtration_grid[0])
10122
+ cdef vector[vector[double]] c_filtration_grid = tuple(api.asnumpy(f).astype(np.float64) for f in filtration_grid) # may be faster with loop on views
9712
10123
  if coordinate_values and inplace:
9713
- self.filtration_grid = c_filtration_grid
10124
+ self.filtration_grid = filtration_grid
9714
10125
  if inplace or not coordinate_values:
9715
10126
  self.get_ptr().squeeze_filtration_inplace(c_filtration_grid, coordinate_values)
9716
10127
  else:
9717
10128
  out = SimplexTreeMulti_Fi32(num_parameters=self.num_parameters)
9718
10129
  self.get_ptr().squeeze_filtration(out.thisptr, c_filtration_grid)
9719
- out.filtration_grid = c_filtration_grid
10130
+ out.filtration_grid = filtration_grid
9720
10131
  return out
9721
10132
  return self
9722
10133
 
10134
+ def unsqueeze(self, grid=None)->SimplexTreeMulti_Ff64:
10135
+ from multipers.grids import sanitize_grid
10136
+ grid = self.filtration_grid if grid is None else grid
10137
+
10138
+ cdef vector[vector[double]] cgrid = sanitize_grid(grid, numpyfy=True)
10139
+ new_slicer = SimplexTreeMulti_Ff64()
10140
+ new_slicer.get_ptr().unsqueeze_filtration(self.thisptr, cgrid)
10141
+
10142
+ return new_slicer
10143
+
9723
10144
  @property
9724
10145
  def is_squeezed(self)->bool:
9725
- return self.num_vertices > 0 and len(self.filtration_grid)>0 and len(self.filtration_grid[0]) > 0
10146
+ return self.num_vertices > 0 and self.filtration_grid is not None and len(self.filtration_grid)>0 and len(self.filtration_grid[0]) > 0
9726
10147
 
9727
10148
  @property
9728
10149
  def dtype(self)->type:
@@ -10243,7 +10664,8 @@ def _euler_signed_measure(simplextree, mass_default=None, bool verbose=False):
10243
10664
  `[signed_measure_of_degree for degree in degrees]`
10244
10665
  with `signed_measure_of_degree` of the form `(dirac location, dirac weights)`.
10245
10666
  """
10246
- assert len(simplextree.filtration_grid[0]) > 0, "Squeeze grid first."
10667
+ if not simplextree.is_squeezed:
10668
+ raise ValueError("Squeeze grid first.")
10247
10669
  cdef bool zero_pad = mass_default is not None
10248
10670
  # assert simplextree.num_parameters == 2
10249
10671
  grid_shape = np.array([len(f) for f in simplextree.filtration_grid])
@@ -10361,9 +10783,9 @@ def _rank_signed_measure(simplextree, vector[indices_type] degrees, mass_default
10361
10783
  else:
10362
10784
  mass_default = np.asarray(mass_default)
10363
10785
  assert mass_default.ndim == 1 and mass_default.shape[0] == simplextree.num_parameters, "Mass default has to be an array like of shape (num_parameters,)"
10364
- if zero_pad:
10365
- for i, _ in enumerate(grid_shape):
10366
- grid_shape[i] += 1 # adds a 0
10786
+ # if zero_pad:
10787
+ # for i, _ in enumerate(grid_shape):
10788
+ # grid_shape[i] += 1 # adds a 0
10367
10789
  # grid_conversion = tuple(np.concatenate([f, [mass_default[i]]]) for i,f in enumerate(grid_conversion))
10368
10790
 
10369
10791
  assert len(grid_shape) == simplextree.num_parameters, "Grid shape size has to be the number of parameters."