multipers 2.3.0__cp312-cp312-win_amd64.whl → 2.3.2__cp312-cp312-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 (54) 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/{ml/convolutions.py → filtrations/density.py} +67 -13
  7. multipers/filtrations/filtrations.py +76 -17
  8. multipers/function_rips.cp312-win_amd64.pyd +0 -0
  9. multipers/grids.cp312-win_amd64.pyd +0 -0
  10. multipers/grids.pyx +144 -61
  11. multipers/gudhi/Simplex_tree_multi_interface.h +36 -1
  12. multipers/gudhi/gudhi/Multi_persistence/Box.h +3 -0
  13. multipers/gudhi/gudhi/One_critical_filtration.h +18 -9
  14. multipers/gudhi/mma_interface_h0.h +1 -1
  15. multipers/gudhi/mma_interface_matrix.h +10 -1
  16. multipers/gudhi/naive_merge_tree.h +1 -1
  17. multipers/gudhi/truc.h +555 -42
  18. multipers/io.cp312-win_amd64.pyd +0 -0
  19. multipers/io.pyx +26 -93
  20. multipers/ml/mma.py +4 -4
  21. multipers/ml/point_clouds.py +2 -2
  22. multipers/ml/signed_measures.py +63 -65
  23. multipers/mma_structures.cp312-win_amd64.pyd +0 -0
  24. multipers/mma_structures.pxd +2 -1
  25. multipers/mma_structures.pyx +56 -16
  26. multipers/mma_structures.pyx.tp +14 -5
  27. multipers/multiparameter_module_approximation/approximation.h +48 -14
  28. multipers/multiparameter_module_approximation.cp312-win_amd64.pyd +0 -0
  29. multipers/multiparameter_module_approximation.pyx +27 -8
  30. multipers/plots.py +2 -1
  31. multipers/point_measure.cp312-win_amd64.pyd +0 -0
  32. multipers/point_measure.pyx +6 -2
  33. multipers/simplex_tree_multi.cp312-win_amd64.pyd +0 -0
  34. multipers/simplex_tree_multi.pxd +1 -0
  35. multipers/simplex_tree_multi.pyx +632 -146
  36. multipers/simplex_tree_multi.pyx.tp +92 -24
  37. multipers/slicer.cp312-win_amd64.pyd +0 -0
  38. multipers/slicer.pxd +779 -177
  39. multipers/slicer.pxd.tp +24 -5
  40. multipers/slicer.pyx +5657 -1427
  41. multipers/slicer.pyx.tp +211 -48
  42. multipers/tbb12.dll +0 -0
  43. multipers/tbbbind_2_5.dll +0 -0
  44. multipers/tbbmalloc.dll +0 -0
  45. multipers/tbbmalloc_proxy.dll +0 -0
  46. multipers/tensor/tensor.h +1 -1
  47. multipers/tests/__init__.py +9 -4
  48. multipers/torch/diff_grids.py +30 -7
  49. multipers/torch/rips_density.py +1 -1
  50. {multipers-2.3.0.dist-info → multipers-2.3.2.dist-info}/METADATA +4 -25
  51. {multipers-2.3.0.dist-info → multipers-2.3.2.dist-info}/RECORD +54 -51
  52. {multipers-2.3.0.dist-info → multipers-2.3.2.dist-info}/WHEEL +1 -1
  53. {multipers-2.3.0.dist-info → multipers-2.3.2.dist-info/licenses}/LICENSE +0 -0
  54. {multipers-2.3.0.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,15 +82,15 @@ 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)
89
90
 
90
91
  # cdef Simplex_tree_persistence_interface * pcohptr
91
92
  # Fake constructor that does nothing but documenting the constructor
92
- def __init__(self, other = None, num_parameters:int=2,default_values=[], safe_conversion=False):
93
+ def __init__(self, other = None, num_parameters:int=-1,default_values=[], safe_conversion=False):
93
94
  """SimplexTreeMulti constructor.
94
95
 
95
96
  :param other: If `other` is `None` (default value), an empty `SimplexTreeMulti` is created.
@@ -114,7 +115,7 @@ cdef class SimplexTreeMulti_KFi32:
114
115
  def is_kcritical(self)->bool:
115
116
  return True
116
117
  # The real cython constructor
117
- def __cinit__(self, other = None, int num_parameters=2,
118
+ def __cinit__(self, other = None, int num_parameters=-1,
118
119
  default_values=np.asarray([SimplexTreeMulti_KFi32.T_minus_inf()]), # I'm not sure why `[]` does not work. Cython bug ?
119
120
  bool safe_conversion=False,
120
121
  ): #TODO doc
@@ -127,10 +128,13 @@ cdef class SimplexTreeMulti_KFi32:
127
128
  if isinstance(other, SimplexTreeMulti_KFi32):
128
129
  other_ptr = other.thisptr
129
130
  self.thisptr = <intptr_t>(new Simplex_tree_multi_interface[KFi32, int32_t](dereference(<Simplex_tree_multi_interface[KFi32, int32_t]*>other_ptr))) ## prevents calling destructor of other
130
- num_parameters = other.num_parameters
131
+ if num_parameters <=0:
132
+ num_parameters = other.num_parameters
131
133
  self.filtration_grid = other.filtration_grid
132
134
  elif isinstance(other, SimplexTree): # Constructs a SimplexTreeMulti from a SimplexTree
133
135
  self.thisptr = <intptr_t>(new Simplex_tree_multi_interface[KFi32, int32_t]())
136
+ if num_parameters <= 0:
137
+ num_parameters = 1
134
138
  if safe_conversion or SAFE_CONVERSION:
135
139
  new_st_multi = _safe_simplextree_multify_KFi32(other, num_parameters = num_parameters, default_values=np.asarray(default_values))
136
140
  self.thisptr, new_st_multi.thisptr = new_st_multi.thisptr, self.thisptr
@@ -145,10 +149,12 @@ cdef class SimplexTreeMulti_KFi32:
145
149
  else:
146
150
  raise TypeError("`other` argument requires to be of type `SimplexTree`, `SimplexTreeMulti`, or `None`.")
147
151
  else:
152
+ if num_parameters <=0:
153
+ num_parameters = 2 # I don't know how dangerous this is, but this is mostly used.
148
154
  self.thisptr = <intptr_t>(new Simplex_tree_multi_interface[KFi32, int32_t]())
149
- self.get_ptr().set_number_of_parameters(num_parameters)
155
+ self.set_num_parameter(num_parameters)
150
156
  self._is_function_simplextree = False
151
- self.filtration_grid=[[]*num_parameters]
157
+ self.filtration_grid=[]
152
158
 
153
159
  def __dealloc__(self):
154
160
  cdef Simplex_tree_multi_interface[KFi32,int32_t]* ptr = self.get_ptr()
@@ -158,6 +164,9 @@ cdef class SimplexTreeMulti_KFi32:
158
164
 
159
165
  def __repr__(self):
160
166
  return f"SimplexTreeMulti[dtype={np.dtype(self.dtype).name},num_param={self.num_parameters},kcritical={self.is_kcritical},is_squeezed={self.is_squeezed},max_dim={self.dimension}]"
167
+ def __len__(self):
168
+ return self.num_simplices
169
+
161
170
  def __getstate__(self):
162
171
  """:returns: Serialized (or flattened) SimplexTree data structure in order to pickle SimplexTree.
163
172
  :rtype: numpy.array of shape (n,)
@@ -813,11 +822,26 @@ cdef class SimplexTreeMulti_KFi32:
813
822
  out = self.get_ptr().get_filtration_values(degrees)
814
823
  filtrations_values = [np.asarray(filtration) for filtration in out]
815
824
  # Removes infs
816
- if inf_to_nan:
825
+ if inf_to_nan and np.dtype(filtrations_values[0].dtype).kind == 'f':
817
826
  for i,f in enumerate(filtrations_values):
818
827
  filtrations_values[i][f == np.inf] = np.nan
819
828
  filtrations_values[i][f == - np.inf] = np.nan
820
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
+
821
845
 
822
846
 
823
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]:
@@ -853,8 +877,16 @@ cdef class SimplexTreeMulti_KFi32:
853
877
  return compute_grid(filtrations_values, resolution=resolution,strategy=grid_strategy,drop_quantiles=drop_quantiles)
854
878
 
855
879
 
856
-
857
- 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:
858
890
  """
859
891
  Fit the filtration of the simplextree to a grid.
860
892
 
@@ -865,26 +897,50 @@ cdef class SimplexTreeMulti_KFi32:
865
897
  """
866
898
  if not force and self.is_squeezed:
867
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
+
868
911
  #TODO : multi-critical
869
912
  if filtration_grid is None:
870
- filtration_grid = self.get_filtration_grid(grid_strategy=grid_strategy, **filtration_grid_kwargs)
871
- cdef vector[vector[double]] c_filtration_grid = filtration_grid
872
- 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()}"
873
- 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
874
920
  if coordinate_values and inplace:
875
- self.filtration_grid = c_filtration_grid
921
+ self.filtration_grid = filtration_grid
876
922
  if inplace or not coordinate_values:
877
923
  self.get_ptr().squeeze_filtration_inplace(c_filtration_grid, coordinate_values)
878
924
  else:
879
925
  out = SimplexTreeMulti_KFi32(num_parameters=self.num_parameters)
880
926
  self.get_ptr().squeeze_filtration(out.thisptr, c_filtration_grid)
881
- out.filtration_grid = c_filtration_grid
927
+ out.filtration_grid = filtration_grid
882
928
  return out
883
929
  return self
884
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
+
885
941
  @property
886
942
  def is_squeezed(self)->bool:
887
- 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
888
944
 
889
945
  @property
890
946
  def dtype(self)->type:
@@ -1177,15 +1233,15 @@ cdef class SimplexTreeMulti_Fi32:
1177
1233
  """
1178
1234
  cdef public intptr_t thisptr
1179
1235
 
1180
- cdef public vector[vector[double]] filtration_grid
1181
- cdef public bool _is_function_simplextree
1236
+ cdef public object filtration_grid
1237
+ cdef public bool _is_function_simplextree # TODO : deprecate
1182
1238
  # Get the pointer casted as it should be
1183
1239
  cdef Simplex_tree_multi_interface[Fi32, int32_t]* get_ptr(self) noexcept nogil:
1184
1240
  return <Simplex_tree_multi_interface[Fi32, int32_t]*>(self.thisptr)
1185
1241
 
1186
1242
  # cdef Simplex_tree_persistence_interface * pcohptr
1187
1243
  # Fake constructor that does nothing but documenting the constructor
1188
- def __init__(self, other = None, num_parameters:int=2,default_values=[], safe_conversion=False):
1244
+ def __init__(self, other = None, num_parameters:int=-1,default_values=[], safe_conversion=False):
1189
1245
  """SimplexTreeMulti constructor.
1190
1246
 
1191
1247
  :param other: If `other` is `None` (default value), an empty `SimplexTreeMulti` is created.
@@ -1210,7 +1266,7 @@ cdef class SimplexTreeMulti_Fi32:
1210
1266
  def is_kcritical(self)->bool:
1211
1267
  return False
1212
1268
  # The real cython constructor
1213
- def __cinit__(self, other = None, int num_parameters=2,
1269
+ def __cinit__(self, other = None, int num_parameters=-1,
1214
1270
  default_values=np.asarray([SimplexTreeMulti_Fi32.T_minus_inf()]), # I'm not sure why `[]` does not work. Cython bug ?
1215
1271
  bool safe_conversion=False,
1216
1272
  ): #TODO doc
@@ -1223,10 +1279,13 @@ cdef class SimplexTreeMulti_Fi32:
1223
1279
  if isinstance(other, SimplexTreeMulti_Fi32):
1224
1280
  other_ptr = other.thisptr
1225
1281
  self.thisptr = <intptr_t>(new Simplex_tree_multi_interface[Fi32, int32_t](dereference(<Simplex_tree_multi_interface[Fi32, int32_t]*>other_ptr))) ## prevents calling destructor of other
1226
- num_parameters = other.num_parameters
1282
+ if num_parameters <=0:
1283
+ num_parameters = other.num_parameters
1227
1284
  self.filtration_grid = other.filtration_grid
1228
1285
  elif isinstance(other, SimplexTree): # Constructs a SimplexTreeMulti from a SimplexTree
1229
1286
  self.thisptr = <intptr_t>(new Simplex_tree_multi_interface[Fi32, int32_t]())
1287
+ if num_parameters <= 0:
1288
+ num_parameters = 1
1230
1289
  if safe_conversion or SAFE_CONVERSION:
1231
1290
  new_st_multi = _safe_simplextree_multify_Fi32(other, num_parameters = num_parameters, default_values=np.asarray(default_values))
1232
1291
  self.thisptr, new_st_multi.thisptr = new_st_multi.thisptr, self.thisptr
@@ -1241,10 +1300,12 @@ cdef class SimplexTreeMulti_Fi32:
1241
1300
  else:
1242
1301
  raise TypeError("`other` argument requires to be of type `SimplexTree`, `SimplexTreeMulti`, or `None`.")
1243
1302
  else:
1303
+ if num_parameters <=0:
1304
+ num_parameters = 2 # I don't know how dangerous this is, but this is mostly used.
1244
1305
  self.thisptr = <intptr_t>(new Simplex_tree_multi_interface[Fi32, int32_t]())
1245
- self.get_ptr().set_number_of_parameters(num_parameters)
1306
+ self.set_num_parameter(num_parameters)
1246
1307
  self._is_function_simplextree = False
1247
- self.filtration_grid=[[]*num_parameters]
1308
+ self.filtration_grid=[]
1248
1309
 
1249
1310
  def __dealloc__(self):
1250
1311
  cdef Simplex_tree_multi_interface[Fi32,int32_t]* ptr = self.get_ptr()
@@ -1254,6 +1315,9 @@ cdef class SimplexTreeMulti_Fi32:
1254
1315
 
1255
1316
  def __repr__(self):
1256
1317
  return f"SimplexTreeMulti[dtype={np.dtype(self.dtype).name},num_param={self.num_parameters},kcritical={self.is_kcritical},is_squeezed={self.is_squeezed},max_dim={self.dimension}]"
1318
+ def __len__(self):
1319
+ return self.num_simplices
1320
+
1257
1321
  def __getstate__(self):
1258
1322
  """:returns: Serialized (or flattened) SimplexTree data structure in order to pickle SimplexTree.
1259
1323
  :rtype: numpy.array of shape (n,)
@@ -1918,7 +1982,16 @@ cdef class SimplexTreeMulti_Fi32:
1918
1982
  out = self.get_ptr().get_edge_list()
1919
1983
  return out
1920
1984
 
1921
- 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:
1922
1995
  """Edge collapse for 1-critical 2-parameter clique complex (see https://arxiv.org/abs/2211.05574).
1923
1996
  It uses the code from the github repository https://github.com/aj-alonso/filtration_domination .
1924
1997
 
@@ -1968,6 +2041,8 @@ cdef class SimplexTreeMulti_Fi32:
1968
2041
  edges = _collapse_edge_list(edges, num=num, full=full, strong=strong, progress=progress)
1969
2042
  # Retrieves the collapsed simplicial complex
1970
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()
1971
2046
  return self
1972
2047
 
1973
2048
  @cython.inline
@@ -2186,11 +2261,26 @@ cdef class SimplexTreeMulti_Fi32:
2186
2261
  out = self.get_ptr().get_filtration_values(degrees)
2187
2262
  filtrations_values = [np.asarray(filtration) for filtration in out]
2188
2263
  # Removes infs
2189
- if inf_to_nan:
2264
+ if inf_to_nan and np.dtype(filtrations_values[0].dtype).kind == 'f':
2190
2265
  for i,f in enumerate(filtrations_values):
2191
2266
  filtrations_values[i][f == np.inf] = np.nan
2192
2267
  filtrations_values[i][f == - np.inf] = np.nan
2193
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
+
2194
2284
 
2195
2285
 
2196
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]:
@@ -2226,8 +2316,16 @@ cdef class SimplexTreeMulti_Fi32:
2226
2316
  return compute_grid(filtrations_values, resolution=resolution,strategy=grid_strategy,drop_quantiles=drop_quantiles)
2227
2317
 
2228
2318
 
2229
-
2230
- 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:
2231
2329
  """
2232
2330
  Fit the filtration of the simplextree to a grid.
2233
2331
 
@@ -2238,26 +2336,50 @@ cdef class SimplexTreeMulti_Fi32:
2238
2336
  """
2239
2337
  if not force and self.is_squeezed:
2240
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
+
2241
2350
  #TODO : multi-critical
2242
2351
  if filtration_grid is None:
2243
- filtration_grid = self.get_filtration_grid(grid_strategy=grid_strategy, **filtration_grid_kwargs)
2244
- cdef vector[vector[double]] c_filtration_grid = filtration_grid
2245
- 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()}"
2246
- 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
2247
2359
  if coordinate_values and inplace:
2248
- self.filtration_grid = c_filtration_grid
2360
+ self.filtration_grid = filtration_grid
2249
2361
  if inplace or not coordinate_values:
2250
2362
  self.get_ptr().squeeze_filtration_inplace(c_filtration_grid, coordinate_values)
2251
2363
  else:
2252
2364
  out = SimplexTreeMulti_Fi32(num_parameters=self.num_parameters)
2253
2365
  self.get_ptr().squeeze_filtration(out.thisptr, c_filtration_grid)
2254
- out.filtration_grid = c_filtration_grid
2366
+ out.filtration_grid = filtration_grid
2255
2367
  return out
2256
2368
  return self
2257
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
+
2258
2380
  @property
2259
2381
  def is_squeezed(self)->bool:
2260
- 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
2261
2383
 
2262
2384
  @property
2263
2385
  def dtype(self)->type:
@@ -2548,15 +2670,15 @@ cdef class SimplexTreeMulti_KFi64:
2548
2670
  """
2549
2671
  cdef public intptr_t thisptr
2550
2672
 
2551
- cdef public vector[vector[double]] filtration_grid
2552
- cdef public bool _is_function_simplextree
2673
+ cdef public object filtration_grid
2674
+ cdef public bool _is_function_simplextree # TODO : deprecate
2553
2675
  # Get the pointer casted as it should be
2554
2676
  cdef Simplex_tree_multi_interface[KFi64, int64_t]* get_ptr(self) noexcept nogil:
2555
2677
  return <Simplex_tree_multi_interface[KFi64, int64_t]*>(self.thisptr)
2556
2678
 
2557
2679
  # cdef Simplex_tree_persistence_interface * pcohptr
2558
2680
  # Fake constructor that does nothing but documenting the constructor
2559
- def __init__(self, other = None, num_parameters:int=2,default_values=[], safe_conversion=False):
2681
+ def __init__(self, other = None, num_parameters:int=-1,default_values=[], safe_conversion=False):
2560
2682
  """SimplexTreeMulti constructor.
2561
2683
 
2562
2684
  :param other: If `other` is `None` (default value), an empty `SimplexTreeMulti` is created.
@@ -2581,7 +2703,7 @@ cdef class SimplexTreeMulti_KFi64:
2581
2703
  def is_kcritical(self)->bool:
2582
2704
  return True
2583
2705
  # The real cython constructor
2584
- def __cinit__(self, other = None, int num_parameters=2,
2706
+ def __cinit__(self, other = None, int num_parameters=-1,
2585
2707
  default_values=np.asarray([SimplexTreeMulti_KFi64.T_minus_inf()]), # I'm not sure why `[]` does not work. Cython bug ?
2586
2708
  bool safe_conversion=False,
2587
2709
  ): #TODO doc
@@ -2594,10 +2716,13 @@ cdef class SimplexTreeMulti_KFi64:
2594
2716
  if isinstance(other, SimplexTreeMulti_KFi64):
2595
2717
  other_ptr = other.thisptr
2596
2718
  self.thisptr = <intptr_t>(new Simplex_tree_multi_interface[KFi64, int64_t](dereference(<Simplex_tree_multi_interface[KFi64, int64_t]*>other_ptr))) ## prevents calling destructor of other
2597
- num_parameters = other.num_parameters
2719
+ if num_parameters <=0:
2720
+ num_parameters = other.num_parameters
2598
2721
  self.filtration_grid = other.filtration_grid
2599
2722
  elif isinstance(other, SimplexTree): # Constructs a SimplexTreeMulti from a SimplexTree
2600
2723
  self.thisptr = <intptr_t>(new Simplex_tree_multi_interface[KFi64, int64_t]())
2724
+ if num_parameters <= 0:
2725
+ num_parameters = 1
2601
2726
  if safe_conversion or SAFE_CONVERSION:
2602
2727
  new_st_multi = _safe_simplextree_multify_KFi64(other, num_parameters = num_parameters, default_values=np.asarray(default_values))
2603
2728
  self.thisptr, new_st_multi.thisptr = new_st_multi.thisptr, self.thisptr
@@ -2612,10 +2737,12 @@ cdef class SimplexTreeMulti_KFi64:
2612
2737
  else:
2613
2738
  raise TypeError("`other` argument requires to be of type `SimplexTree`, `SimplexTreeMulti`, or `None`.")
2614
2739
  else:
2740
+ if num_parameters <=0:
2741
+ num_parameters = 2 # I don't know how dangerous this is, but this is mostly used.
2615
2742
  self.thisptr = <intptr_t>(new Simplex_tree_multi_interface[KFi64, int64_t]())
2616
- self.get_ptr().set_number_of_parameters(num_parameters)
2743
+ self.set_num_parameter(num_parameters)
2617
2744
  self._is_function_simplextree = False
2618
- self.filtration_grid=[[]*num_parameters]
2745
+ self.filtration_grid=[]
2619
2746
 
2620
2747
  def __dealloc__(self):
2621
2748
  cdef Simplex_tree_multi_interface[KFi64,int64_t]* ptr = self.get_ptr()
@@ -2625,6 +2752,9 @@ cdef class SimplexTreeMulti_KFi64:
2625
2752
 
2626
2753
  def __repr__(self):
2627
2754
  return f"SimplexTreeMulti[dtype={np.dtype(self.dtype).name},num_param={self.num_parameters},kcritical={self.is_kcritical},is_squeezed={self.is_squeezed},max_dim={self.dimension}]"
2755
+ def __len__(self):
2756
+ return self.num_simplices
2757
+
2628
2758
  def __getstate__(self):
2629
2759
  """:returns: Serialized (or flattened) SimplexTree data structure in order to pickle SimplexTree.
2630
2760
  :rtype: numpy.array of shape (n,)
@@ -3280,11 +3410,26 @@ cdef class SimplexTreeMulti_KFi64:
3280
3410
  out = self.get_ptr().get_filtration_values(degrees)
3281
3411
  filtrations_values = [np.asarray(filtration) for filtration in out]
3282
3412
  # Removes infs
3283
- if inf_to_nan:
3413
+ if inf_to_nan and np.dtype(filtrations_values[0].dtype).kind == 'f':
3284
3414
  for i,f in enumerate(filtrations_values):
3285
3415
  filtrations_values[i][f == np.inf] = np.nan
3286
3416
  filtrations_values[i][f == - np.inf] = np.nan
3287
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
+
3288
3433
 
3289
3434
 
3290
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]:
@@ -3320,8 +3465,16 @@ cdef class SimplexTreeMulti_KFi64:
3320
3465
  return compute_grid(filtrations_values, resolution=resolution,strategy=grid_strategy,drop_quantiles=drop_quantiles)
3321
3466
 
3322
3467
 
3323
-
3324
- 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:
3325
3478
  """
3326
3479
  Fit the filtration of the simplextree to a grid.
3327
3480
 
@@ -3332,26 +3485,50 @@ cdef class SimplexTreeMulti_KFi64:
3332
3485
  """
3333
3486
  if not force and self.is_squeezed:
3334
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
+
3335
3499
  #TODO : multi-critical
3336
3500
  if filtration_grid is None:
3337
- filtration_grid = self.get_filtration_grid(grid_strategy=grid_strategy, **filtration_grid_kwargs)
3338
- cdef vector[vector[double]] c_filtration_grid = filtration_grid
3339
- 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()}"
3340
- 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
3341
3508
  if coordinate_values and inplace:
3342
- self.filtration_grid = c_filtration_grid
3509
+ self.filtration_grid = filtration_grid
3343
3510
  if inplace or not coordinate_values:
3344
3511
  self.get_ptr().squeeze_filtration_inplace(c_filtration_grid, coordinate_values)
3345
3512
  else:
3346
3513
  out = SimplexTreeMulti_KFi32(num_parameters=self.num_parameters)
3347
3514
  self.get_ptr().squeeze_filtration(out.thisptr, c_filtration_grid)
3348
- out.filtration_grid = c_filtration_grid
3515
+ out.filtration_grid = filtration_grid
3349
3516
  return out
3350
3517
  return self
3351
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
+
3352
3529
  @property
3353
3530
  def is_squeezed(self)->bool:
3354
- 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
3355
3532
 
3356
3533
  @property
3357
3534
  def dtype(self)->type:
@@ -3644,15 +3821,15 @@ cdef class SimplexTreeMulti_Fi64:
3644
3821
  """
3645
3822
  cdef public intptr_t thisptr
3646
3823
 
3647
- cdef public vector[vector[double]] filtration_grid
3648
- cdef public bool _is_function_simplextree
3824
+ cdef public object filtration_grid
3825
+ cdef public bool _is_function_simplextree # TODO : deprecate
3649
3826
  # Get the pointer casted as it should be
3650
3827
  cdef Simplex_tree_multi_interface[Fi64, int64_t]* get_ptr(self) noexcept nogil:
3651
3828
  return <Simplex_tree_multi_interface[Fi64, int64_t]*>(self.thisptr)
3652
3829
 
3653
3830
  # cdef Simplex_tree_persistence_interface * pcohptr
3654
3831
  # Fake constructor that does nothing but documenting the constructor
3655
- def __init__(self, other = None, num_parameters:int=2,default_values=[], safe_conversion=False):
3832
+ def __init__(self, other = None, num_parameters:int=-1,default_values=[], safe_conversion=False):
3656
3833
  """SimplexTreeMulti constructor.
3657
3834
 
3658
3835
  :param other: If `other` is `None` (default value), an empty `SimplexTreeMulti` is created.
@@ -3677,7 +3854,7 @@ cdef class SimplexTreeMulti_Fi64:
3677
3854
  def is_kcritical(self)->bool:
3678
3855
  return False
3679
3856
  # The real cython constructor
3680
- def __cinit__(self, other = None, int num_parameters=2,
3857
+ def __cinit__(self, other = None, int num_parameters=-1,
3681
3858
  default_values=np.asarray([SimplexTreeMulti_Fi64.T_minus_inf()]), # I'm not sure why `[]` does not work. Cython bug ?
3682
3859
  bool safe_conversion=False,
3683
3860
  ): #TODO doc
@@ -3690,10 +3867,13 @@ cdef class SimplexTreeMulti_Fi64:
3690
3867
  if isinstance(other, SimplexTreeMulti_Fi64):
3691
3868
  other_ptr = other.thisptr
3692
3869
  self.thisptr = <intptr_t>(new Simplex_tree_multi_interface[Fi64, int64_t](dereference(<Simplex_tree_multi_interface[Fi64, int64_t]*>other_ptr))) ## prevents calling destructor of other
3693
- num_parameters = other.num_parameters
3870
+ if num_parameters <=0:
3871
+ num_parameters = other.num_parameters
3694
3872
  self.filtration_grid = other.filtration_grid
3695
3873
  elif isinstance(other, SimplexTree): # Constructs a SimplexTreeMulti from a SimplexTree
3696
3874
  self.thisptr = <intptr_t>(new Simplex_tree_multi_interface[Fi64, int64_t]())
3875
+ if num_parameters <= 0:
3876
+ num_parameters = 1
3697
3877
  if safe_conversion or SAFE_CONVERSION:
3698
3878
  new_st_multi = _safe_simplextree_multify_Fi64(other, num_parameters = num_parameters, default_values=np.asarray(default_values))
3699
3879
  self.thisptr, new_st_multi.thisptr = new_st_multi.thisptr, self.thisptr
@@ -3708,10 +3888,12 @@ cdef class SimplexTreeMulti_Fi64:
3708
3888
  else:
3709
3889
  raise TypeError("`other` argument requires to be of type `SimplexTree`, `SimplexTreeMulti`, or `None`.")
3710
3890
  else:
3891
+ if num_parameters <=0:
3892
+ num_parameters = 2 # I don't know how dangerous this is, but this is mostly used.
3711
3893
  self.thisptr = <intptr_t>(new Simplex_tree_multi_interface[Fi64, int64_t]())
3712
- self.get_ptr().set_number_of_parameters(num_parameters)
3894
+ self.set_num_parameter(num_parameters)
3713
3895
  self._is_function_simplextree = False
3714
- self.filtration_grid=[[]*num_parameters]
3896
+ self.filtration_grid=[]
3715
3897
 
3716
3898
  def __dealloc__(self):
3717
3899
  cdef Simplex_tree_multi_interface[Fi64,int64_t]* ptr = self.get_ptr()
@@ -3721,6 +3903,9 @@ cdef class SimplexTreeMulti_Fi64:
3721
3903
 
3722
3904
  def __repr__(self):
3723
3905
  return f"SimplexTreeMulti[dtype={np.dtype(self.dtype).name},num_param={self.num_parameters},kcritical={self.is_kcritical},is_squeezed={self.is_squeezed},max_dim={self.dimension}]"
3906
+ def __len__(self):
3907
+ return self.num_simplices
3908
+
3724
3909
  def __getstate__(self):
3725
3910
  """:returns: Serialized (or flattened) SimplexTree data structure in order to pickle SimplexTree.
3726
3911
  :rtype: numpy.array of shape (n,)
@@ -4385,7 +4570,16 @@ cdef class SimplexTreeMulti_Fi64:
4385
4570
  out = self.get_ptr().get_edge_list()
4386
4571
  return out
4387
4572
 
4388
- 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:
4389
4583
  """Edge collapse for 1-critical 2-parameter clique complex (see https://arxiv.org/abs/2211.05574).
4390
4584
  It uses the code from the github repository https://github.com/aj-alonso/filtration_domination .
4391
4585
 
@@ -4435,6 +4629,8 @@ cdef class SimplexTreeMulti_Fi64:
4435
4629
  edges = _collapse_edge_list(edges, num=num, full=full, strong=strong, progress=progress)
4436
4630
  # Retrieves the collapsed simplicial complex
4437
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()
4438
4634
  return self
4439
4635
 
4440
4636
  @cython.inline
@@ -4653,11 +4849,26 @@ cdef class SimplexTreeMulti_Fi64:
4653
4849
  out = self.get_ptr().get_filtration_values(degrees)
4654
4850
  filtrations_values = [np.asarray(filtration) for filtration in out]
4655
4851
  # Removes infs
4656
- if inf_to_nan:
4852
+ if inf_to_nan and np.dtype(filtrations_values[0].dtype).kind == 'f':
4657
4853
  for i,f in enumerate(filtrations_values):
4658
4854
  filtrations_values[i][f == np.inf] = np.nan
4659
4855
  filtrations_values[i][f == - np.inf] = np.nan
4660
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
+
4661
4872
 
4662
4873
 
4663
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]:
@@ -4693,8 +4904,16 @@ cdef class SimplexTreeMulti_Fi64:
4693
4904
  return compute_grid(filtrations_values, resolution=resolution,strategy=grid_strategy,drop_quantiles=drop_quantiles)
4694
4905
 
4695
4906
 
4696
-
4697
- 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:
4698
4917
  """
4699
4918
  Fit the filtration of the simplextree to a grid.
4700
4919
 
@@ -4705,26 +4924,50 @@ cdef class SimplexTreeMulti_Fi64:
4705
4924
  """
4706
4925
  if not force and self.is_squeezed:
4707
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
+
4708
4938
  #TODO : multi-critical
4709
4939
  if filtration_grid is None:
4710
- filtration_grid = self.get_filtration_grid(grid_strategy=grid_strategy, **filtration_grid_kwargs)
4711
- cdef vector[vector[double]] c_filtration_grid = filtration_grid
4712
- 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()}"
4713
- 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
4714
4947
  if coordinate_values and inplace:
4715
- self.filtration_grid = c_filtration_grid
4948
+ self.filtration_grid = filtration_grid
4716
4949
  if inplace or not coordinate_values:
4717
4950
  self.get_ptr().squeeze_filtration_inplace(c_filtration_grid, coordinate_values)
4718
4951
  else:
4719
4952
  out = SimplexTreeMulti_Fi32(num_parameters=self.num_parameters)
4720
4953
  self.get_ptr().squeeze_filtration(out.thisptr, c_filtration_grid)
4721
- out.filtration_grid = c_filtration_grid
4954
+ out.filtration_grid = filtration_grid
4722
4955
  return out
4723
4956
  return self
4724
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
+
4725
4968
  @property
4726
4969
  def is_squeezed(self)->bool:
4727
- 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
4728
4971
 
4729
4972
  @property
4730
4973
  def dtype(self)->type:
@@ -5015,15 +5258,15 @@ cdef class SimplexTreeMulti_KFf32:
5015
5258
  """
5016
5259
  cdef public intptr_t thisptr
5017
5260
 
5018
- cdef public vector[vector[double]] filtration_grid
5019
- cdef public bool _is_function_simplextree
5261
+ cdef public object filtration_grid
5262
+ cdef public bool _is_function_simplextree # TODO : deprecate
5020
5263
  # Get the pointer casted as it should be
5021
5264
  cdef Simplex_tree_multi_interface[KFf32, float]* get_ptr(self) noexcept nogil:
5022
5265
  return <Simplex_tree_multi_interface[KFf32, float]*>(self.thisptr)
5023
5266
 
5024
5267
  # cdef Simplex_tree_persistence_interface * pcohptr
5025
5268
  # Fake constructor that does nothing but documenting the constructor
5026
- def __init__(self, other = None, num_parameters:int=2,default_values=[], safe_conversion=False):
5269
+ def __init__(self, other = None, num_parameters:int=-1,default_values=[], safe_conversion=False):
5027
5270
  """SimplexTreeMulti constructor.
5028
5271
 
5029
5272
  :param other: If `other` is `None` (default value), an empty `SimplexTreeMulti` is created.
@@ -5048,7 +5291,7 @@ cdef class SimplexTreeMulti_KFf32:
5048
5291
  def is_kcritical(self)->bool:
5049
5292
  return True
5050
5293
  # The real cython constructor
5051
- def __cinit__(self, other = None, int num_parameters=2,
5294
+ def __cinit__(self, other = None, int num_parameters=-1,
5052
5295
  default_values=np.asarray([SimplexTreeMulti_KFf32.T_minus_inf()]), # I'm not sure why `[]` does not work. Cython bug ?
5053
5296
  bool safe_conversion=False,
5054
5297
  ): #TODO doc
@@ -5061,10 +5304,13 @@ cdef class SimplexTreeMulti_KFf32:
5061
5304
  if isinstance(other, SimplexTreeMulti_KFf32):
5062
5305
  other_ptr = other.thisptr
5063
5306
  self.thisptr = <intptr_t>(new Simplex_tree_multi_interface[KFf32, float](dereference(<Simplex_tree_multi_interface[KFf32, float]*>other_ptr))) ## prevents calling destructor of other
5064
- num_parameters = other.num_parameters
5307
+ if num_parameters <=0:
5308
+ num_parameters = other.num_parameters
5065
5309
  self.filtration_grid = other.filtration_grid
5066
5310
  elif isinstance(other, SimplexTree): # Constructs a SimplexTreeMulti from a SimplexTree
5067
5311
  self.thisptr = <intptr_t>(new Simplex_tree_multi_interface[KFf32, float]())
5312
+ if num_parameters <= 0:
5313
+ num_parameters = 1
5068
5314
  if safe_conversion or SAFE_CONVERSION:
5069
5315
  new_st_multi = _safe_simplextree_multify_KFf32(other, num_parameters = num_parameters, default_values=np.asarray(default_values))
5070
5316
  self.thisptr, new_st_multi.thisptr = new_st_multi.thisptr, self.thisptr
@@ -5079,10 +5325,12 @@ cdef class SimplexTreeMulti_KFf32:
5079
5325
  else:
5080
5326
  raise TypeError("`other` argument requires to be of type `SimplexTree`, `SimplexTreeMulti`, or `None`.")
5081
5327
  else:
5328
+ if num_parameters <=0:
5329
+ num_parameters = 2 # I don't know how dangerous this is, but this is mostly used.
5082
5330
  self.thisptr = <intptr_t>(new Simplex_tree_multi_interface[KFf32, float]())
5083
- self.get_ptr().set_number_of_parameters(num_parameters)
5331
+ self.set_num_parameter(num_parameters)
5084
5332
  self._is_function_simplextree = False
5085
- self.filtration_grid=[[]*num_parameters]
5333
+ self.filtration_grid=[]
5086
5334
 
5087
5335
  def __dealloc__(self):
5088
5336
  cdef Simplex_tree_multi_interface[KFf32,float]* ptr = self.get_ptr()
@@ -5092,6 +5340,9 @@ cdef class SimplexTreeMulti_KFf32:
5092
5340
 
5093
5341
  def __repr__(self):
5094
5342
  return f"SimplexTreeMulti[dtype={np.dtype(self.dtype).name},num_param={self.num_parameters},kcritical={self.is_kcritical},is_squeezed={self.is_squeezed},max_dim={self.dimension}]"
5343
+ def __len__(self):
5344
+ return self.num_simplices
5345
+
5095
5346
  def __getstate__(self):
5096
5347
  """:returns: Serialized (or flattened) SimplexTree data structure in order to pickle SimplexTree.
5097
5348
  :rtype: numpy.array of shape (n,)
@@ -5747,11 +5998,26 @@ cdef class SimplexTreeMulti_KFf32:
5747
5998
  out = self.get_ptr().get_filtration_values(degrees)
5748
5999
  filtrations_values = [np.asarray(filtration) for filtration in out]
5749
6000
  # Removes infs
5750
- if inf_to_nan:
6001
+ if inf_to_nan and np.dtype(filtrations_values[0].dtype).kind == 'f':
5751
6002
  for i,f in enumerate(filtrations_values):
5752
6003
  filtrations_values[i][f == np.inf] = np.nan
5753
6004
  filtrations_values[i][f == - np.inf] = np.nan
5754
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
+
5755
6021
 
5756
6022
 
5757
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]:
@@ -5787,8 +6053,16 @@ cdef class SimplexTreeMulti_KFf32:
5787
6053
  return compute_grid(filtrations_values, resolution=resolution,strategy=grid_strategy,drop_quantiles=drop_quantiles)
5788
6054
 
5789
6055
 
5790
-
5791
- 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:
5792
6066
  """
5793
6067
  Fit the filtration of the simplextree to a grid.
5794
6068
 
@@ -5799,26 +6073,50 @@ cdef class SimplexTreeMulti_KFf32:
5799
6073
  """
5800
6074
  if not force and self.is_squeezed:
5801
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
+
5802
6087
  #TODO : multi-critical
5803
6088
  if filtration_grid is None:
5804
- filtration_grid = self.get_filtration_grid(grid_strategy=grid_strategy, **filtration_grid_kwargs)
5805
- cdef vector[vector[double]] c_filtration_grid = filtration_grid
5806
- 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()}"
5807
- 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
5808
6096
  if coordinate_values and inplace:
5809
- self.filtration_grid = c_filtration_grid
6097
+ self.filtration_grid = filtration_grid
5810
6098
  if inplace or not coordinate_values:
5811
6099
  self.get_ptr().squeeze_filtration_inplace(c_filtration_grid, coordinate_values)
5812
6100
  else:
5813
6101
  out = SimplexTreeMulti_KFi32(num_parameters=self.num_parameters)
5814
6102
  self.get_ptr().squeeze_filtration(out.thisptr, c_filtration_grid)
5815
- out.filtration_grid = c_filtration_grid
6103
+ out.filtration_grid = filtration_grid
5816
6104
  return out
5817
6105
  return self
5818
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
+
5819
6117
  @property
5820
6118
  def is_squeezed(self)->bool:
5821
- 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
5822
6120
 
5823
6121
  @property
5824
6122
  def dtype(self)->type:
@@ -6111,15 +6409,15 @@ cdef class SimplexTreeMulti_Ff32:
6111
6409
  """
6112
6410
  cdef public intptr_t thisptr
6113
6411
 
6114
- cdef public vector[vector[double]] filtration_grid
6115
- cdef public bool _is_function_simplextree
6412
+ cdef public object filtration_grid
6413
+ cdef public bool _is_function_simplextree # TODO : deprecate
6116
6414
  # Get the pointer casted as it should be
6117
6415
  cdef Simplex_tree_multi_interface[Ff32, float]* get_ptr(self) noexcept nogil:
6118
6416
  return <Simplex_tree_multi_interface[Ff32, float]*>(self.thisptr)
6119
6417
 
6120
6418
  # cdef Simplex_tree_persistence_interface * pcohptr
6121
6419
  # Fake constructor that does nothing but documenting the constructor
6122
- def __init__(self, other = None, num_parameters:int=2,default_values=[], safe_conversion=False):
6420
+ def __init__(self, other = None, num_parameters:int=-1,default_values=[], safe_conversion=False):
6123
6421
  """SimplexTreeMulti constructor.
6124
6422
 
6125
6423
  :param other: If `other` is `None` (default value), an empty `SimplexTreeMulti` is created.
@@ -6144,7 +6442,7 @@ cdef class SimplexTreeMulti_Ff32:
6144
6442
  def is_kcritical(self)->bool:
6145
6443
  return False
6146
6444
  # The real cython constructor
6147
- def __cinit__(self, other = None, int num_parameters=2,
6445
+ def __cinit__(self, other = None, int num_parameters=-1,
6148
6446
  default_values=np.asarray([SimplexTreeMulti_Ff32.T_minus_inf()]), # I'm not sure why `[]` does not work. Cython bug ?
6149
6447
  bool safe_conversion=False,
6150
6448
  ): #TODO doc
@@ -6157,10 +6455,13 @@ cdef class SimplexTreeMulti_Ff32:
6157
6455
  if isinstance(other, SimplexTreeMulti_Ff32):
6158
6456
  other_ptr = other.thisptr
6159
6457
  self.thisptr = <intptr_t>(new Simplex_tree_multi_interface[Ff32, float](dereference(<Simplex_tree_multi_interface[Ff32, float]*>other_ptr))) ## prevents calling destructor of other
6160
- num_parameters = other.num_parameters
6458
+ if num_parameters <=0:
6459
+ num_parameters = other.num_parameters
6161
6460
  self.filtration_grid = other.filtration_grid
6162
6461
  elif isinstance(other, SimplexTree): # Constructs a SimplexTreeMulti from a SimplexTree
6163
6462
  self.thisptr = <intptr_t>(new Simplex_tree_multi_interface[Ff32, float]())
6463
+ if num_parameters <= 0:
6464
+ num_parameters = 1
6164
6465
  if safe_conversion or SAFE_CONVERSION:
6165
6466
  new_st_multi = _safe_simplextree_multify_Ff32(other, num_parameters = num_parameters, default_values=np.asarray(default_values))
6166
6467
  self.thisptr, new_st_multi.thisptr = new_st_multi.thisptr, self.thisptr
@@ -6175,10 +6476,12 @@ cdef class SimplexTreeMulti_Ff32:
6175
6476
  else:
6176
6477
  raise TypeError("`other` argument requires to be of type `SimplexTree`, `SimplexTreeMulti`, or `None`.")
6177
6478
  else:
6479
+ if num_parameters <=0:
6480
+ num_parameters = 2 # I don't know how dangerous this is, but this is mostly used.
6178
6481
  self.thisptr = <intptr_t>(new Simplex_tree_multi_interface[Ff32, float]())
6179
- self.get_ptr().set_number_of_parameters(num_parameters)
6482
+ self.set_num_parameter(num_parameters)
6180
6483
  self._is_function_simplextree = False
6181
- self.filtration_grid=[[]*num_parameters]
6484
+ self.filtration_grid=[]
6182
6485
 
6183
6486
  def __dealloc__(self):
6184
6487
  cdef Simplex_tree_multi_interface[Ff32,float]* ptr = self.get_ptr()
@@ -6188,6 +6491,9 @@ cdef class SimplexTreeMulti_Ff32:
6188
6491
 
6189
6492
  def __repr__(self):
6190
6493
  return f"SimplexTreeMulti[dtype={np.dtype(self.dtype).name},num_param={self.num_parameters},kcritical={self.is_kcritical},is_squeezed={self.is_squeezed},max_dim={self.dimension}]"
6494
+ def __len__(self):
6495
+ return self.num_simplices
6496
+
6191
6497
  def __getstate__(self):
6192
6498
  """:returns: Serialized (or flattened) SimplexTree data structure in order to pickle SimplexTree.
6193
6499
  :rtype: numpy.array of shape (n,)
@@ -6852,7 +7158,16 @@ cdef class SimplexTreeMulti_Ff32:
6852
7158
  out = self.get_ptr().get_edge_list()
6853
7159
  return out
6854
7160
 
6855
- 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:
6856
7171
  """Edge collapse for 1-critical 2-parameter clique complex (see https://arxiv.org/abs/2211.05574).
6857
7172
  It uses the code from the github repository https://github.com/aj-alonso/filtration_domination .
6858
7173
 
@@ -6902,6 +7217,8 @@ cdef class SimplexTreeMulti_Ff32:
6902
7217
  edges = _collapse_edge_list(edges, num=num, full=full, strong=strong, progress=progress)
6903
7218
  # Retrieves the collapsed simplicial complex
6904
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()
6905
7222
  return self
6906
7223
 
6907
7224
  @cython.inline
@@ -7120,11 +7437,26 @@ cdef class SimplexTreeMulti_Ff32:
7120
7437
  out = self.get_ptr().get_filtration_values(degrees)
7121
7438
  filtrations_values = [np.asarray(filtration) for filtration in out]
7122
7439
  # Removes infs
7123
- if inf_to_nan:
7440
+ if inf_to_nan and np.dtype(filtrations_values[0].dtype).kind == 'f':
7124
7441
  for i,f in enumerate(filtrations_values):
7125
7442
  filtrations_values[i][f == np.inf] = np.nan
7126
7443
  filtrations_values[i][f == - np.inf] = np.nan
7127
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
+
7128
7460
 
7129
7461
 
7130
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]:
@@ -7160,8 +7492,16 @@ cdef class SimplexTreeMulti_Ff32:
7160
7492
  return compute_grid(filtrations_values, resolution=resolution,strategy=grid_strategy,drop_quantiles=drop_quantiles)
7161
7493
 
7162
7494
 
7163
-
7164
- 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:
7165
7505
  """
7166
7506
  Fit the filtration of the simplextree to a grid.
7167
7507
 
@@ -7172,26 +7512,50 @@ cdef class SimplexTreeMulti_Ff32:
7172
7512
  """
7173
7513
  if not force and self.is_squeezed:
7174
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
+
7175
7526
  #TODO : multi-critical
7176
7527
  if filtration_grid is None:
7177
- filtration_grid = self.get_filtration_grid(grid_strategy=grid_strategy, **filtration_grid_kwargs)
7178
- cdef vector[vector[double]] c_filtration_grid = filtration_grid
7179
- 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()}"
7180
- 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
7181
7535
  if coordinate_values and inplace:
7182
- self.filtration_grid = c_filtration_grid
7536
+ self.filtration_grid = filtration_grid
7183
7537
  if inplace or not coordinate_values:
7184
7538
  self.get_ptr().squeeze_filtration_inplace(c_filtration_grid, coordinate_values)
7185
7539
  else:
7186
7540
  out = SimplexTreeMulti_Fi32(num_parameters=self.num_parameters)
7187
7541
  self.get_ptr().squeeze_filtration(out.thisptr, c_filtration_grid)
7188
- out.filtration_grid = c_filtration_grid
7542
+ out.filtration_grid = filtration_grid
7189
7543
  return out
7190
7544
  return self
7191
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
+
7192
7556
  @property
7193
7557
  def is_squeezed(self)->bool:
7194
- 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
7195
7559
 
7196
7560
  @property
7197
7561
  def dtype(self)->type:
@@ -7482,15 +7846,15 @@ cdef class SimplexTreeMulti_KFf64:
7482
7846
  """
7483
7847
  cdef public intptr_t thisptr
7484
7848
 
7485
- cdef public vector[vector[double]] filtration_grid
7486
- cdef public bool _is_function_simplextree
7849
+ cdef public object filtration_grid
7850
+ cdef public bool _is_function_simplextree # TODO : deprecate
7487
7851
  # Get the pointer casted as it should be
7488
7852
  cdef Simplex_tree_multi_interface[KFf64, double]* get_ptr(self) noexcept nogil:
7489
7853
  return <Simplex_tree_multi_interface[KFf64, double]*>(self.thisptr)
7490
7854
 
7491
7855
  # cdef Simplex_tree_persistence_interface * pcohptr
7492
7856
  # Fake constructor that does nothing but documenting the constructor
7493
- def __init__(self, other = None, num_parameters:int=2,default_values=[], safe_conversion=False):
7857
+ def __init__(self, other = None, num_parameters:int=-1,default_values=[], safe_conversion=False):
7494
7858
  """SimplexTreeMulti constructor.
7495
7859
 
7496
7860
  :param other: If `other` is `None` (default value), an empty `SimplexTreeMulti` is created.
@@ -7515,7 +7879,7 @@ cdef class SimplexTreeMulti_KFf64:
7515
7879
  def is_kcritical(self)->bool:
7516
7880
  return True
7517
7881
  # The real cython constructor
7518
- def __cinit__(self, other = None, int num_parameters=2,
7882
+ def __cinit__(self, other = None, int num_parameters=-1,
7519
7883
  default_values=np.asarray([SimplexTreeMulti_KFf64.T_minus_inf()]), # I'm not sure why `[]` does not work. Cython bug ?
7520
7884
  bool safe_conversion=False,
7521
7885
  ): #TODO doc
@@ -7528,10 +7892,13 @@ cdef class SimplexTreeMulti_KFf64:
7528
7892
  if isinstance(other, SimplexTreeMulti_KFf64):
7529
7893
  other_ptr = other.thisptr
7530
7894
  self.thisptr = <intptr_t>(new Simplex_tree_multi_interface[KFf64, double](dereference(<Simplex_tree_multi_interface[KFf64, double]*>other_ptr))) ## prevents calling destructor of other
7531
- num_parameters = other.num_parameters
7895
+ if num_parameters <=0:
7896
+ num_parameters = other.num_parameters
7532
7897
  self.filtration_grid = other.filtration_grid
7533
7898
  elif isinstance(other, SimplexTree): # Constructs a SimplexTreeMulti from a SimplexTree
7534
7899
  self.thisptr = <intptr_t>(new Simplex_tree_multi_interface[KFf64, double]())
7900
+ if num_parameters <= 0:
7901
+ num_parameters = 1
7535
7902
  if safe_conversion or SAFE_CONVERSION:
7536
7903
  new_st_multi = _safe_simplextree_multify_KFf64(other, num_parameters = num_parameters, default_values=np.asarray(default_values))
7537
7904
  self.thisptr, new_st_multi.thisptr = new_st_multi.thisptr, self.thisptr
@@ -7546,10 +7913,12 @@ cdef class SimplexTreeMulti_KFf64:
7546
7913
  else:
7547
7914
  raise TypeError("`other` argument requires to be of type `SimplexTree`, `SimplexTreeMulti`, or `None`.")
7548
7915
  else:
7916
+ if num_parameters <=0:
7917
+ num_parameters = 2 # I don't know how dangerous this is, but this is mostly used.
7549
7918
  self.thisptr = <intptr_t>(new Simplex_tree_multi_interface[KFf64, double]())
7550
- self.get_ptr().set_number_of_parameters(num_parameters)
7919
+ self.set_num_parameter(num_parameters)
7551
7920
  self._is_function_simplextree = False
7552
- self.filtration_grid=[[]*num_parameters]
7921
+ self.filtration_grid=[]
7553
7922
 
7554
7923
  def __dealloc__(self):
7555
7924
  cdef Simplex_tree_multi_interface[KFf64,double]* ptr = self.get_ptr()
@@ -7559,6 +7928,9 @@ cdef class SimplexTreeMulti_KFf64:
7559
7928
 
7560
7929
  def __repr__(self):
7561
7930
  return f"SimplexTreeMulti[dtype={np.dtype(self.dtype).name},num_param={self.num_parameters},kcritical={self.is_kcritical},is_squeezed={self.is_squeezed},max_dim={self.dimension}]"
7931
+ def __len__(self):
7932
+ return self.num_simplices
7933
+
7562
7934
  def __getstate__(self):
7563
7935
  """:returns: Serialized (or flattened) SimplexTree data structure in order to pickle SimplexTree.
7564
7936
  :rtype: numpy.array of shape (n,)
@@ -8214,11 +8586,26 @@ cdef class SimplexTreeMulti_KFf64:
8214
8586
  out = self.get_ptr().get_filtration_values(degrees)
8215
8587
  filtrations_values = [np.asarray(filtration) for filtration in out]
8216
8588
  # Removes infs
8217
- if inf_to_nan:
8589
+ if inf_to_nan and np.dtype(filtrations_values[0].dtype).kind == 'f':
8218
8590
  for i,f in enumerate(filtrations_values):
8219
8591
  filtrations_values[i][f == np.inf] = np.nan
8220
8592
  filtrations_values[i][f == - np.inf] = np.nan
8221
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
+
8222
8609
 
8223
8610
 
8224
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]:
@@ -8254,8 +8641,16 @@ cdef class SimplexTreeMulti_KFf64:
8254
8641
  return compute_grid(filtrations_values, resolution=resolution,strategy=grid_strategy,drop_quantiles=drop_quantiles)
8255
8642
 
8256
8643
 
8257
-
8258
- 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:
8259
8654
  """
8260
8655
  Fit the filtration of the simplextree to a grid.
8261
8656
 
@@ -8266,26 +8661,50 @@ cdef class SimplexTreeMulti_KFf64:
8266
8661
  """
8267
8662
  if not force and self.is_squeezed:
8268
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
+
8269
8675
  #TODO : multi-critical
8270
8676
  if filtration_grid is None:
8271
- filtration_grid = self.get_filtration_grid(grid_strategy=grid_strategy, **filtration_grid_kwargs)
8272
- cdef vector[vector[double]] c_filtration_grid = filtration_grid
8273
- 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()}"
8274
- 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
8275
8684
  if coordinate_values and inplace:
8276
- self.filtration_grid = c_filtration_grid
8685
+ self.filtration_grid = filtration_grid
8277
8686
  if inplace or not coordinate_values:
8278
8687
  self.get_ptr().squeeze_filtration_inplace(c_filtration_grid, coordinate_values)
8279
8688
  else:
8280
8689
  out = SimplexTreeMulti_KFi32(num_parameters=self.num_parameters)
8281
8690
  self.get_ptr().squeeze_filtration(out.thisptr, c_filtration_grid)
8282
- out.filtration_grid = c_filtration_grid
8691
+ out.filtration_grid = filtration_grid
8283
8692
  return out
8284
8693
  return self
8285
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
+
8286
8705
  @property
8287
8706
  def is_squeezed(self)->bool:
8288
- 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
8289
8708
 
8290
8709
  @property
8291
8710
  def dtype(self)->type:
@@ -8578,15 +8997,15 @@ cdef class SimplexTreeMulti_Ff64:
8578
8997
  """
8579
8998
  cdef public intptr_t thisptr
8580
8999
 
8581
- cdef public vector[vector[double]] filtration_grid
8582
- cdef public bool _is_function_simplextree
9000
+ cdef public object filtration_grid
9001
+ cdef public bool _is_function_simplextree # TODO : deprecate
8583
9002
  # Get the pointer casted as it should be
8584
9003
  cdef Simplex_tree_multi_interface[Ff64, double]* get_ptr(self) noexcept nogil:
8585
9004
  return <Simplex_tree_multi_interface[Ff64, double]*>(self.thisptr)
8586
9005
 
8587
9006
  # cdef Simplex_tree_persistence_interface * pcohptr
8588
9007
  # Fake constructor that does nothing but documenting the constructor
8589
- def __init__(self, other = None, num_parameters:int=2,default_values=[], safe_conversion=False):
9008
+ def __init__(self, other = None, num_parameters:int=-1,default_values=[], safe_conversion=False):
8590
9009
  """SimplexTreeMulti constructor.
8591
9010
 
8592
9011
  :param other: If `other` is `None` (default value), an empty `SimplexTreeMulti` is created.
@@ -8611,7 +9030,7 @@ cdef class SimplexTreeMulti_Ff64:
8611
9030
  def is_kcritical(self)->bool:
8612
9031
  return False
8613
9032
  # The real cython constructor
8614
- def __cinit__(self, other = None, int num_parameters=2,
9033
+ def __cinit__(self, other = None, int num_parameters=-1,
8615
9034
  default_values=np.asarray([SimplexTreeMulti_Ff64.T_minus_inf()]), # I'm not sure why `[]` does not work. Cython bug ?
8616
9035
  bool safe_conversion=False,
8617
9036
  ): #TODO doc
@@ -8624,10 +9043,13 @@ cdef class SimplexTreeMulti_Ff64:
8624
9043
  if isinstance(other, SimplexTreeMulti_Ff64):
8625
9044
  other_ptr = other.thisptr
8626
9045
  self.thisptr = <intptr_t>(new Simplex_tree_multi_interface[Ff64, double](dereference(<Simplex_tree_multi_interface[Ff64, double]*>other_ptr))) ## prevents calling destructor of other
8627
- num_parameters = other.num_parameters
9046
+ if num_parameters <=0:
9047
+ num_parameters = other.num_parameters
8628
9048
  self.filtration_grid = other.filtration_grid
8629
9049
  elif isinstance(other, SimplexTree): # Constructs a SimplexTreeMulti from a SimplexTree
8630
9050
  self.thisptr = <intptr_t>(new Simplex_tree_multi_interface[Ff64, double]())
9051
+ if num_parameters <= 0:
9052
+ num_parameters = 1
8631
9053
  if safe_conversion or SAFE_CONVERSION:
8632
9054
  new_st_multi = _safe_simplextree_multify_Ff64(other, num_parameters = num_parameters, default_values=np.asarray(default_values))
8633
9055
  self.thisptr, new_st_multi.thisptr = new_st_multi.thisptr, self.thisptr
@@ -8642,10 +9064,12 @@ cdef class SimplexTreeMulti_Ff64:
8642
9064
  else:
8643
9065
  raise TypeError("`other` argument requires to be of type `SimplexTree`, `SimplexTreeMulti`, or `None`.")
8644
9066
  else:
9067
+ if num_parameters <=0:
9068
+ num_parameters = 2 # I don't know how dangerous this is, but this is mostly used.
8645
9069
  self.thisptr = <intptr_t>(new Simplex_tree_multi_interface[Ff64, double]())
8646
- self.get_ptr().set_number_of_parameters(num_parameters)
9070
+ self.set_num_parameter(num_parameters)
8647
9071
  self._is_function_simplextree = False
8648
- self.filtration_grid=[[]*num_parameters]
9072
+ self.filtration_grid=[]
8649
9073
 
8650
9074
  def __dealloc__(self):
8651
9075
  cdef Simplex_tree_multi_interface[Ff64,double]* ptr = self.get_ptr()
@@ -8655,6 +9079,9 @@ cdef class SimplexTreeMulti_Ff64:
8655
9079
 
8656
9080
  def __repr__(self):
8657
9081
  return f"SimplexTreeMulti[dtype={np.dtype(self.dtype).name},num_param={self.num_parameters},kcritical={self.is_kcritical},is_squeezed={self.is_squeezed},max_dim={self.dimension}]"
9082
+ def __len__(self):
9083
+ return self.num_simplices
9084
+
8658
9085
  def __getstate__(self):
8659
9086
  """:returns: Serialized (or flattened) SimplexTree data structure in order to pickle SimplexTree.
8660
9087
  :rtype: numpy.array of shape (n,)
@@ -9319,7 +9746,16 @@ cdef class SimplexTreeMulti_Ff64:
9319
9746
  out = self.get_ptr().get_edge_list()
9320
9747
  return out
9321
9748
 
9322
- 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:
9323
9759
  """Edge collapse for 1-critical 2-parameter clique complex (see https://arxiv.org/abs/2211.05574).
9324
9760
  It uses the code from the github repository https://github.com/aj-alonso/filtration_domination .
9325
9761
 
@@ -9369,6 +9805,8 @@ cdef class SimplexTreeMulti_Ff64:
9369
9805
  edges = _collapse_edge_list(edges, num=num, full=full, strong=strong, progress=progress)
9370
9806
  # Retrieves the collapsed simplicial complex
9371
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()
9372
9810
  return self
9373
9811
 
9374
9812
  @cython.inline
@@ -9587,11 +10025,26 @@ cdef class SimplexTreeMulti_Ff64:
9587
10025
  out = self.get_ptr().get_filtration_values(degrees)
9588
10026
  filtrations_values = [np.asarray(filtration) for filtration in out]
9589
10027
  # Removes infs
9590
- if inf_to_nan:
10028
+ if inf_to_nan and np.dtype(filtrations_values[0].dtype).kind == 'f':
9591
10029
  for i,f in enumerate(filtrations_values):
9592
10030
  filtrations_values[i][f == np.inf] = np.nan
9593
10031
  filtrations_values[i][f == - np.inf] = np.nan
9594
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
+
9595
10048
 
9596
10049
 
9597
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]:
@@ -9627,8 +10080,16 @@ cdef class SimplexTreeMulti_Ff64:
9627
10080
  return compute_grid(filtrations_values, resolution=resolution,strategy=grid_strategy,drop_quantiles=drop_quantiles)
9628
10081
 
9629
10082
 
9630
-
9631
- 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:
9632
10093
  """
9633
10094
  Fit the filtration of the simplextree to a grid.
9634
10095
 
@@ -9639,26 +10100,50 @@ cdef class SimplexTreeMulti_Ff64:
9639
10100
  """
9640
10101
  if not force and self.is_squeezed:
9641
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
+
9642
10114
  #TODO : multi-critical
9643
10115
  if filtration_grid is None:
9644
- filtration_grid = self.get_filtration_grid(grid_strategy=grid_strategy, **filtration_grid_kwargs)
9645
- cdef vector[vector[double]] c_filtration_grid = filtration_grid
9646
- 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()}"
9647
- 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
9648
10123
  if coordinate_values and inplace:
9649
- self.filtration_grid = c_filtration_grid
10124
+ self.filtration_grid = filtration_grid
9650
10125
  if inplace or not coordinate_values:
9651
10126
  self.get_ptr().squeeze_filtration_inplace(c_filtration_grid, coordinate_values)
9652
10127
  else:
9653
10128
  out = SimplexTreeMulti_Fi32(num_parameters=self.num_parameters)
9654
10129
  self.get_ptr().squeeze_filtration(out.thisptr, c_filtration_grid)
9655
- out.filtration_grid = c_filtration_grid
10130
+ out.filtration_grid = filtration_grid
9656
10131
  return out
9657
10132
  return self
9658
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
+
9659
10144
  @property
9660
10145
  def is_squeezed(self)->bool:
9661
- 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
9662
10147
 
9663
10148
  @property
9664
10149
  def dtype(self)->type:
@@ -9968,7 +10453,7 @@ def is_simplextree_multi(input)->bool:
9968
10453
 
9969
10454
 
9970
10455
 
9971
- def SimplexTreeMulti(input=None, int num_parameters=2, dtype:type = np.float64, bool kcritical = False,**kwargs) -> SimplexTreeMulti_type:
10456
+ def SimplexTreeMulti(input=None, int num_parameters=-1, dtype:type = np.float64, bool kcritical = False,**kwargs) -> SimplexTreeMulti_type:
9972
10457
  """SimplexTreeMulti constructor.
9973
10458
 
9974
10459
  :param other: If `other` is `None` (default value), an empty `SimplexTreeMulti` is created.
@@ -10179,7 +10664,8 @@ def _euler_signed_measure(simplextree, mass_default=None, bool verbose=False):
10179
10664
  `[signed_measure_of_degree for degree in degrees]`
10180
10665
  with `signed_measure_of_degree` of the form `(dirac location, dirac weights)`.
10181
10666
  """
10182
- assert len(simplextree.filtration_grid[0]) > 0, "Squeeze grid first."
10667
+ if not simplextree.is_squeezed:
10668
+ raise ValueError("Squeeze grid first.")
10183
10669
  cdef bool zero_pad = mass_default is not None
10184
10670
  # assert simplextree.num_parameters == 2
10185
10671
  grid_shape = np.array([len(f) for f in simplextree.filtration_grid])
@@ -10297,9 +10783,9 @@ def _rank_signed_measure(simplextree, vector[indices_type] degrees, mass_default
10297
10783
  else:
10298
10784
  mass_default = np.asarray(mass_default)
10299
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,)"
10300
- if zero_pad:
10301
- for i, _ in enumerate(grid_shape):
10302
- 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
10303
10789
  # grid_conversion = tuple(np.concatenate([f, [mass_default[i]]]) for i,f in enumerate(grid_conversion))
10304
10790
 
10305
10791
  assert len(grid_shape) == simplextree.num_parameters, "Grid shape size has to be the number of parameters."