multipers 2.3.0__cp312-cp312-win_amd64.whl → 2.3.2b1__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 +3 -3
  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 +25 -7
  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 +584 -142
  36. multipers/simplex_tree_multi.pyx.tp +80 -23
  37. multipers/slicer.cp312-win_amd64.pyd +0 -0
  38. multipers/slicer.pxd +799 -197
  39. multipers/slicer.pxd.tp +24 -5
  40. multipers/slicer.pyx +5654 -1427
  41. multipers/slicer.pyx.tp +208 -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.2b1.dist-info}/METADATA +4 -25
  51. {multipers-2.3.0.dist-info → multipers-2.3.2b1.dist-info}/RECORD +54 -51
  52. {multipers-2.3.0.dist-info → multipers-2.3.2b1.dist-info}/WHEEL +1 -1
  53. {multipers-2.3.0.dist-info → multipers-2.3.2b1.dist-info/licenses}/LICENSE +0 -0
  54. {multipers-2.3.0.dist-info → multipers-2.3.2b1.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
+ force=False,
885
+ strategy:_available_strategies = "exact",
886
+ grid_strategy=None,
887
+ 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,)
@@ -2186,11 +2250,26 @@ cdef class SimplexTreeMulti_Fi32:
2186
2250
  out = self.get_ptr().get_filtration_values(degrees)
2187
2251
  filtrations_values = [np.asarray(filtration) for filtration in out]
2188
2252
  # Removes infs
2189
- if inf_to_nan:
2253
+ if inf_to_nan and np.dtype(filtrations_values[0].dtype).kind == 'f':
2190
2254
  for i,f in enumerate(filtrations_values):
2191
2255
  filtrations_values[i][f == np.inf] = np.nan
2192
2256
  filtrations_values[i][f == - np.inf] = np.nan
2193
2257
  return filtrations_values
2258
+ def _clean_filtration_grid(self):
2259
+ """
2260
+ Removes the values in filtration_grid that are not linked to any splx.
2261
+ """
2262
+ if not self.is_squeezed:
2263
+ raise ValueError("No grid to clean.")
2264
+ F = self.filtration_grid
2265
+ self.filtration_grid=None
2266
+ cleaned_coordinates = compute_grid(self)
2267
+ new_st = self.grid_squeeze(cleaned_coordinates)
2268
+
2269
+ self.thisptr, new_st.thisptr = new_st.thisptr, self.thisptr
2270
+ self.filtration_grid = tuple(f[g] for f,g in zip(F,cleaned_coordinates))
2271
+ return self
2272
+
2194
2273
 
2195
2274
 
2196
2275
  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 +2305,16 @@ cdef class SimplexTreeMulti_Fi32:
2226
2305
  return compute_grid(filtrations_values, resolution=resolution,strategy=grid_strategy,drop_quantiles=drop_quantiles)
2227
2306
 
2228
2307
 
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):
2308
+ def grid_squeeze(
2309
+ self,
2310
+ filtration_grid:np.ndarray|list|None=None,
2311
+ bool coordinate_values=True,
2312
+ force=False,
2313
+ strategy:_available_strategies = "exact",
2314
+ grid_strategy=None,
2315
+ inplace=False,
2316
+ **filtration_grid_kwargs
2317
+ )->SimplexTreeMulti_Fi32 | SimplexTreeMulti_Fi32:
2231
2318
  """
2232
2319
  Fit the filtration of the simplextree to a grid.
2233
2320
 
@@ -2238,26 +2325,50 @@ cdef class SimplexTreeMulti_Fi32:
2238
2325
  """
2239
2326
  if not force and self.is_squeezed:
2240
2327
  raise Exception("SimplexTree already squeezed, use `force=True` if that's really what you want to do.")
2328
+
2329
+ if grid_strategy is not None:
2330
+ warn("`grid_strategy` is deprecated, use `strategy` instead.",DeprecationWarning)
2331
+ strategy=grid_strategy
2332
+
2333
+ if self.is_squeezed:
2334
+ warn("(copy warning) Squeezing an already squeezed slicer.")
2335
+ temp = self.unsqueeze()
2336
+ subgrid = compute_grid(self.filtration_grid, strategy=strategy, resolution=resolution)
2337
+ return temp.grid_squeeze(subgrid, coordinates=coordinates, inplace=inplace)
2338
+
2241
2339
  #TODO : multi-critical
2242
2340
  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
2341
+ filtration_grid = self.get_filtration_grid(grid_strategy=strategy, **filtration_grid_kwargs)
2342
+ else:
2343
+ filtration_grid = sanitize_grid(filtration_grid)
2344
+ if len(filtration_grid) != self.num_parameters:
2345
+ raise ValueError(f"Invalid grid to squeeze onto. Got {len(filtration_grid)=} != {self.num_parameters=}.")
2346
+ api = api_from_tensor(filtration_grid[0])
2347
+ 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
2348
  if coordinate_values and inplace:
2248
- self.filtration_grid = c_filtration_grid
2349
+ self.filtration_grid = filtration_grid
2249
2350
  if inplace or not coordinate_values:
2250
2351
  self.get_ptr().squeeze_filtration_inplace(c_filtration_grid, coordinate_values)
2251
2352
  else:
2252
2353
  out = SimplexTreeMulti_Fi32(num_parameters=self.num_parameters)
2253
2354
  self.get_ptr().squeeze_filtration(out.thisptr, c_filtration_grid)
2254
- out.filtration_grid = c_filtration_grid
2355
+ out.filtration_grid = filtration_grid
2255
2356
  return out
2256
2357
  return self
2257
2358
 
2359
+ def unsqueeze(self, grid=None)->SimplexTreeMulti_Ff64:
2360
+ from multipers.grids import sanitize_grid
2361
+ grid = self.filtration_grid if grid is None else grid
2362
+
2363
+ cdef vector[vector[double]] cgrid = sanitize_grid(grid, numpyfy=True)
2364
+ new_slicer = SimplexTreeMulti_Ff64()
2365
+ new_slicer.get_ptr().unsqueeze_filtration(self.thisptr, cgrid)
2366
+
2367
+ return new_slicer
2368
+
2258
2369
  @property
2259
2370
  def is_squeezed(self)->bool:
2260
- return self.num_vertices > 0 and len(self.filtration_grid)>0 and len(self.filtration_grid[0]) > 0
2371
+ 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
2372
 
2262
2373
  @property
2263
2374
  def dtype(self)->type:
@@ -2548,15 +2659,15 @@ cdef class SimplexTreeMulti_KFi64:
2548
2659
  """
2549
2660
  cdef public intptr_t thisptr
2550
2661
 
2551
- cdef public vector[vector[double]] filtration_grid
2552
- cdef public bool _is_function_simplextree
2662
+ cdef public object filtration_grid
2663
+ cdef public bool _is_function_simplextree # TODO : deprecate
2553
2664
  # Get the pointer casted as it should be
2554
2665
  cdef Simplex_tree_multi_interface[KFi64, int64_t]* get_ptr(self) noexcept nogil:
2555
2666
  return <Simplex_tree_multi_interface[KFi64, int64_t]*>(self.thisptr)
2556
2667
 
2557
2668
  # cdef Simplex_tree_persistence_interface * pcohptr
2558
2669
  # Fake constructor that does nothing but documenting the constructor
2559
- def __init__(self, other = None, num_parameters:int=2,default_values=[], safe_conversion=False):
2670
+ def __init__(self, other = None, num_parameters:int=-1,default_values=[], safe_conversion=False):
2560
2671
  """SimplexTreeMulti constructor.
2561
2672
 
2562
2673
  :param other: If `other` is `None` (default value), an empty `SimplexTreeMulti` is created.
@@ -2581,7 +2692,7 @@ cdef class SimplexTreeMulti_KFi64:
2581
2692
  def is_kcritical(self)->bool:
2582
2693
  return True
2583
2694
  # The real cython constructor
2584
- def __cinit__(self, other = None, int num_parameters=2,
2695
+ def __cinit__(self, other = None, int num_parameters=-1,
2585
2696
  default_values=np.asarray([SimplexTreeMulti_KFi64.T_minus_inf()]), # I'm not sure why `[]` does not work. Cython bug ?
2586
2697
  bool safe_conversion=False,
2587
2698
  ): #TODO doc
@@ -2594,10 +2705,13 @@ cdef class SimplexTreeMulti_KFi64:
2594
2705
  if isinstance(other, SimplexTreeMulti_KFi64):
2595
2706
  other_ptr = other.thisptr
2596
2707
  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
2708
+ if num_parameters <=0:
2709
+ num_parameters = other.num_parameters
2598
2710
  self.filtration_grid = other.filtration_grid
2599
2711
  elif isinstance(other, SimplexTree): # Constructs a SimplexTreeMulti from a SimplexTree
2600
2712
  self.thisptr = <intptr_t>(new Simplex_tree_multi_interface[KFi64, int64_t]())
2713
+ if num_parameters <= 0:
2714
+ num_parameters = 1
2601
2715
  if safe_conversion or SAFE_CONVERSION:
2602
2716
  new_st_multi = _safe_simplextree_multify_KFi64(other, num_parameters = num_parameters, default_values=np.asarray(default_values))
2603
2717
  self.thisptr, new_st_multi.thisptr = new_st_multi.thisptr, self.thisptr
@@ -2612,10 +2726,12 @@ cdef class SimplexTreeMulti_KFi64:
2612
2726
  else:
2613
2727
  raise TypeError("`other` argument requires to be of type `SimplexTree`, `SimplexTreeMulti`, or `None`.")
2614
2728
  else:
2729
+ if num_parameters <=0:
2730
+ num_parameters = 2 # I don't know how dangerous this is, but this is mostly used.
2615
2731
  self.thisptr = <intptr_t>(new Simplex_tree_multi_interface[KFi64, int64_t]())
2616
- self.get_ptr().set_number_of_parameters(num_parameters)
2732
+ self.set_num_parameter(num_parameters)
2617
2733
  self._is_function_simplextree = False
2618
- self.filtration_grid=[[]*num_parameters]
2734
+ self.filtration_grid=[]
2619
2735
 
2620
2736
  def __dealloc__(self):
2621
2737
  cdef Simplex_tree_multi_interface[KFi64,int64_t]* ptr = self.get_ptr()
@@ -2625,6 +2741,9 @@ cdef class SimplexTreeMulti_KFi64:
2625
2741
 
2626
2742
  def __repr__(self):
2627
2743
  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}]"
2744
+ def __len__(self):
2745
+ return self.num_simplices
2746
+
2628
2747
  def __getstate__(self):
2629
2748
  """:returns: Serialized (or flattened) SimplexTree data structure in order to pickle SimplexTree.
2630
2749
  :rtype: numpy.array of shape (n,)
@@ -3280,11 +3399,26 @@ cdef class SimplexTreeMulti_KFi64:
3280
3399
  out = self.get_ptr().get_filtration_values(degrees)
3281
3400
  filtrations_values = [np.asarray(filtration) for filtration in out]
3282
3401
  # Removes infs
3283
- if inf_to_nan:
3402
+ if inf_to_nan and np.dtype(filtrations_values[0].dtype).kind == 'f':
3284
3403
  for i,f in enumerate(filtrations_values):
3285
3404
  filtrations_values[i][f == np.inf] = np.nan
3286
3405
  filtrations_values[i][f == - np.inf] = np.nan
3287
3406
  return filtrations_values
3407
+ def _clean_filtration_grid(self):
3408
+ """
3409
+ Removes the values in filtration_grid that are not linked to any splx.
3410
+ """
3411
+ if not self.is_squeezed:
3412
+ raise ValueError("No grid to clean.")
3413
+ F = self.filtration_grid
3414
+ self.filtration_grid=None
3415
+ cleaned_coordinates = compute_grid(self)
3416
+ new_st = self.grid_squeeze(cleaned_coordinates)
3417
+
3418
+ self.thisptr, new_st.thisptr = new_st.thisptr, self.thisptr
3419
+ self.filtration_grid = tuple(f[g] for f,g in zip(F,cleaned_coordinates))
3420
+ return self
3421
+
3288
3422
 
3289
3423
 
3290
3424
  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 +3454,16 @@ cdef class SimplexTreeMulti_KFi64:
3320
3454
  return compute_grid(filtrations_values, resolution=resolution,strategy=grid_strategy,drop_quantiles=drop_quantiles)
3321
3455
 
3322
3456
 
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):
3457
+ def grid_squeeze(
3458
+ self,
3459
+ filtration_grid:np.ndarray|list|None=None,
3460
+ bool coordinate_values=True,
3461
+ force=False,
3462
+ strategy:_available_strategies = "exact",
3463
+ grid_strategy=None,
3464
+ inplace=False,
3465
+ **filtration_grid_kwargs
3466
+ )->SimplexTreeMulti_KFi32 | SimplexTreeMulti_KFi64:
3325
3467
  """
3326
3468
  Fit the filtration of the simplextree to a grid.
3327
3469
 
@@ -3332,26 +3474,50 @@ cdef class SimplexTreeMulti_KFi64:
3332
3474
  """
3333
3475
  if not force and self.is_squeezed:
3334
3476
  raise Exception("SimplexTree already squeezed, use `force=True` if that's really what you want to do.")
3477
+
3478
+ if grid_strategy is not None:
3479
+ warn("`grid_strategy` is deprecated, use `strategy` instead.",DeprecationWarning)
3480
+ strategy=grid_strategy
3481
+
3482
+ if self.is_squeezed:
3483
+ warn("(copy warning) Squeezing an already squeezed slicer.")
3484
+ temp = self.unsqueeze()
3485
+ subgrid = compute_grid(self.filtration_grid, strategy=strategy, resolution=resolution)
3486
+ return temp.grid_squeeze(subgrid, coordinates=coordinates, inplace=inplace)
3487
+
3335
3488
  #TODO : multi-critical
3336
3489
  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
3490
+ filtration_grid = self.get_filtration_grid(grid_strategy=strategy, **filtration_grid_kwargs)
3491
+ else:
3492
+ filtration_grid = sanitize_grid(filtration_grid)
3493
+ if len(filtration_grid) != self.num_parameters:
3494
+ raise ValueError(f"Invalid grid to squeeze onto. Got {len(filtration_grid)=} != {self.num_parameters=}.")
3495
+ api = api_from_tensor(filtration_grid[0])
3496
+ 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
3497
  if coordinate_values and inplace:
3342
- self.filtration_grid = c_filtration_grid
3498
+ self.filtration_grid = filtration_grid
3343
3499
  if inplace or not coordinate_values:
3344
3500
  self.get_ptr().squeeze_filtration_inplace(c_filtration_grid, coordinate_values)
3345
3501
  else:
3346
3502
  out = SimplexTreeMulti_KFi32(num_parameters=self.num_parameters)
3347
3503
  self.get_ptr().squeeze_filtration(out.thisptr, c_filtration_grid)
3348
- out.filtration_grid = c_filtration_grid
3504
+ out.filtration_grid = filtration_grid
3349
3505
  return out
3350
3506
  return self
3351
3507
 
3508
+ def unsqueeze(self, grid=None)->SimplexTreeMulti_KFf64:
3509
+ from multipers.grids import sanitize_grid
3510
+ grid = self.filtration_grid if grid is None else grid
3511
+
3512
+ cdef vector[vector[double]] cgrid = sanitize_grid(grid, numpyfy=True)
3513
+ new_slicer = SimplexTreeMulti_KFf64()
3514
+ new_slicer.get_ptr().unsqueeze_filtration(self.thisptr, cgrid)
3515
+
3516
+ return new_slicer
3517
+
3352
3518
  @property
3353
3519
  def is_squeezed(self)->bool:
3354
- return self.num_vertices > 0 and len(self.filtration_grid)>0 and len(self.filtration_grid[0]) > 0
3520
+ 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
3521
 
3356
3522
  @property
3357
3523
  def dtype(self)->type:
@@ -3644,15 +3810,15 @@ cdef class SimplexTreeMulti_Fi64:
3644
3810
  """
3645
3811
  cdef public intptr_t thisptr
3646
3812
 
3647
- cdef public vector[vector[double]] filtration_grid
3648
- cdef public bool _is_function_simplextree
3813
+ cdef public object filtration_grid
3814
+ cdef public bool _is_function_simplextree # TODO : deprecate
3649
3815
  # Get the pointer casted as it should be
3650
3816
  cdef Simplex_tree_multi_interface[Fi64, int64_t]* get_ptr(self) noexcept nogil:
3651
3817
  return <Simplex_tree_multi_interface[Fi64, int64_t]*>(self.thisptr)
3652
3818
 
3653
3819
  # cdef Simplex_tree_persistence_interface * pcohptr
3654
3820
  # Fake constructor that does nothing but documenting the constructor
3655
- def __init__(self, other = None, num_parameters:int=2,default_values=[], safe_conversion=False):
3821
+ def __init__(self, other = None, num_parameters:int=-1,default_values=[], safe_conversion=False):
3656
3822
  """SimplexTreeMulti constructor.
3657
3823
 
3658
3824
  :param other: If `other` is `None` (default value), an empty `SimplexTreeMulti` is created.
@@ -3677,7 +3843,7 @@ cdef class SimplexTreeMulti_Fi64:
3677
3843
  def is_kcritical(self)->bool:
3678
3844
  return False
3679
3845
  # The real cython constructor
3680
- def __cinit__(self, other = None, int num_parameters=2,
3846
+ def __cinit__(self, other = None, int num_parameters=-1,
3681
3847
  default_values=np.asarray([SimplexTreeMulti_Fi64.T_minus_inf()]), # I'm not sure why `[]` does not work. Cython bug ?
3682
3848
  bool safe_conversion=False,
3683
3849
  ): #TODO doc
@@ -3690,10 +3856,13 @@ cdef class SimplexTreeMulti_Fi64:
3690
3856
  if isinstance(other, SimplexTreeMulti_Fi64):
3691
3857
  other_ptr = other.thisptr
3692
3858
  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
3859
+ if num_parameters <=0:
3860
+ num_parameters = other.num_parameters
3694
3861
  self.filtration_grid = other.filtration_grid
3695
3862
  elif isinstance(other, SimplexTree): # Constructs a SimplexTreeMulti from a SimplexTree
3696
3863
  self.thisptr = <intptr_t>(new Simplex_tree_multi_interface[Fi64, int64_t]())
3864
+ if num_parameters <= 0:
3865
+ num_parameters = 1
3697
3866
  if safe_conversion or SAFE_CONVERSION:
3698
3867
  new_st_multi = _safe_simplextree_multify_Fi64(other, num_parameters = num_parameters, default_values=np.asarray(default_values))
3699
3868
  self.thisptr, new_st_multi.thisptr = new_st_multi.thisptr, self.thisptr
@@ -3708,10 +3877,12 @@ cdef class SimplexTreeMulti_Fi64:
3708
3877
  else:
3709
3878
  raise TypeError("`other` argument requires to be of type `SimplexTree`, `SimplexTreeMulti`, or `None`.")
3710
3879
  else:
3880
+ if num_parameters <=0:
3881
+ num_parameters = 2 # I don't know how dangerous this is, but this is mostly used.
3711
3882
  self.thisptr = <intptr_t>(new Simplex_tree_multi_interface[Fi64, int64_t]())
3712
- self.get_ptr().set_number_of_parameters(num_parameters)
3883
+ self.set_num_parameter(num_parameters)
3713
3884
  self._is_function_simplextree = False
3714
- self.filtration_grid=[[]*num_parameters]
3885
+ self.filtration_grid=[]
3715
3886
 
3716
3887
  def __dealloc__(self):
3717
3888
  cdef Simplex_tree_multi_interface[Fi64,int64_t]* ptr = self.get_ptr()
@@ -3721,6 +3892,9 @@ cdef class SimplexTreeMulti_Fi64:
3721
3892
 
3722
3893
  def __repr__(self):
3723
3894
  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}]"
3895
+ def __len__(self):
3896
+ return self.num_simplices
3897
+
3724
3898
  def __getstate__(self):
3725
3899
  """:returns: Serialized (or flattened) SimplexTree data structure in order to pickle SimplexTree.
3726
3900
  :rtype: numpy.array of shape (n,)
@@ -4653,11 +4827,26 @@ cdef class SimplexTreeMulti_Fi64:
4653
4827
  out = self.get_ptr().get_filtration_values(degrees)
4654
4828
  filtrations_values = [np.asarray(filtration) for filtration in out]
4655
4829
  # Removes infs
4656
- if inf_to_nan:
4830
+ if inf_to_nan and np.dtype(filtrations_values[0].dtype).kind == 'f':
4657
4831
  for i,f in enumerate(filtrations_values):
4658
4832
  filtrations_values[i][f == np.inf] = np.nan
4659
4833
  filtrations_values[i][f == - np.inf] = np.nan
4660
4834
  return filtrations_values
4835
+ def _clean_filtration_grid(self):
4836
+ """
4837
+ Removes the values in filtration_grid that are not linked to any splx.
4838
+ """
4839
+ if not self.is_squeezed:
4840
+ raise ValueError("No grid to clean.")
4841
+ F = self.filtration_grid
4842
+ self.filtration_grid=None
4843
+ cleaned_coordinates = compute_grid(self)
4844
+ new_st = self.grid_squeeze(cleaned_coordinates)
4845
+
4846
+ self.thisptr, new_st.thisptr = new_st.thisptr, self.thisptr
4847
+ self.filtration_grid = tuple(f[g] for f,g in zip(F,cleaned_coordinates))
4848
+ return self
4849
+
4661
4850
 
4662
4851
 
4663
4852
  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 +4882,16 @@ cdef class SimplexTreeMulti_Fi64:
4693
4882
  return compute_grid(filtrations_values, resolution=resolution,strategy=grid_strategy,drop_quantiles=drop_quantiles)
4694
4883
 
4695
4884
 
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):
4885
+ def grid_squeeze(
4886
+ self,
4887
+ filtration_grid:np.ndarray|list|None=None,
4888
+ bool coordinate_values=True,
4889
+ force=False,
4890
+ strategy:_available_strategies = "exact",
4891
+ grid_strategy=None,
4892
+ inplace=False,
4893
+ **filtration_grid_kwargs
4894
+ )->SimplexTreeMulti_Fi32 | SimplexTreeMulti_Fi64:
4698
4895
  """
4699
4896
  Fit the filtration of the simplextree to a grid.
4700
4897
 
@@ -4705,26 +4902,50 @@ cdef class SimplexTreeMulti_Fi64:
4705
4902
  """
4706
4903
  if not force and self.is_squeezed:
4707
4904
  raise Exception("SimplexTree already squeezed, use `force=True` if that's really what you want to do.")
4905
+
4906
+ if grid_strategy is not None:
4907
+ warn("`grid_strategy` is deprecated, use `strategy` instead.",DeprecationWarning)
4908
+ strategy=grid_strategy
4909
+
4910
+ if self.is_squeezed:
4911
+ warn("(copy warning) Squeezing an already squeezed slicer.")
4912
+ temp = self.unsqueeze()
4913
+ subgrid = compute_grid(self.filtration_grid, strategy=strategy, resolution=resolution)
4914
+ return temp.grid_squeeze(subgrid, coordinates=coordinates, inplace=inplace)
4915
+
4708
4916
  #TODO : multi-critical
4709
4917
  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
4918
+ filtration_grid = self.get_filtration_grid(grid_strategy=strategy, **filtration_grid_kwargs)
4919
+ else:
4920
+ filtration_grid = sanitize_grid(filtration_grid)
4921
+ if len(filtration_grid) != self.num_parameters:
4922
+ raise ValueError(f"Invalid grid to squeeze onto. Got {len(filtration_grid)=} != {self.num_parameters=}.")
4923
+ api = api_from_tensor(filtration_grid[0])
4924
+ 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
4925
  if coordinate_values and inplace:
4715
- self.filtration_grid = c_filtration_grid
4926
+ self.filtration_grid = filtration_grid
4716
4927
  if inplace or not coordinate_values:
4717
4928
  self.get_ptr().squeeze_filtration_inplace(c_filtration_grid, coordinate_values)
4718
4929
  else:
4719
4930
  out = SimplexTreeMulti_Fi32(num_parameters=self.num_parameters)
4720
4931
  self.get_ptr().squeeze_filtration(out.thisptr, c_filtration_grid)
4721
- out.filtration_grid = c_filtration_grid
4932
+ out.filtration_grid = filtration_grid
4722
4933
  return out
4723
4934
  return self
4724
4935
 
4936
+ def unsqueeze(self, grid=None)->SimplexTreeMulti_Ff64:
4937
+ from multipers.grids import sanitize_grid
4938
+ grid = self.filtration_grid if grid is None else grid
4939
+
4940
+ cdef vector[vector[double]] cgrid = sanitize_grid(grid, numpyfy=True)
4941
+ new_slicer = SimplexTreeMulti_Ff64()
4942
+ new_slicer.get_ptr().unsqueeze_filtration(self.thisptr, cgrid)
4943
+
4944
+ return new_slicer
4945
+
4725
4946
  @property
4726
4947
  def is_squeezed(self)->bool:
4727
- return self.num_vertices > 0 and len(self.filtration_grid)>0 and len(self.filtration_grid[0]) > 0
4948
+ 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
4949
 
4729
4950
  @property
4730
4951
  def dtype(self)->type:
@@ -5015,15 +5236,15 @@ cdef class SimplexTreeMulti_KFf32:
5015
5236
  """
5016
5237
  cdef public intptr_t thisptr
5017
5238
 
5018
- cdef public vector[vector[double]] filtration_grid
5019
- cdef public bool _is_function_simplextree
5239
+ cdef public object filtration_grid
5240
+ cdef public bool _is_function_simplextree # TODO : deprecate
5020
5241
  # Get the pointer casted as it should be
5021
5242
  cdef Simplex_tree_multi_interface[KFf32, float]* get_ptr(self) noexcept nogil:
5022
5243
  return <Simplex_tree_multi_interface[KFf32, float]*>(self.thisptr)
5023
5244
 
5024
5245
  # cdef Simplex_tree_persistence_interface * pcohptr
5025
5246
  # Fake constructor that does nothing but documenting the constructor
5026
- def __init__(self, other = None, num_parameters:int=2,default_values=[], safe_conversion=False):
5247
+ def __init__(self, other = None, num_parameters:int=-1,default_values=[], safe_conversion=False):
5027
5248
  """SimplexTreeMulti constructor.
5028
5249
 
5029
5250
  :param other: If `other` is `None` (default value), an empty `SimplexTreeMulti` is created.
@@ -5048,7 +5269,7 @@ cdef class SimplexTreeMulti_KFf32:
5048
5269
  def is_kcritical(self)->bool:
5049
5270
  return True
5050
5271
  # The real cython constructor
5051
- def __cinit__(self, other = None, int num_parameters=2,
5272
+ def __cinit__(self, other = None, int num_parameters=-1,
5052
5273
  default_values=np.asarray([SimplexTreeMulti_KFf32.T_minus_inf()]), # I'm not sure why `[]` does not work. Cython bug ?
5053
5274
  bool safe_conversion=False,
5054
5275
  ): #TODO doc
@@ -5061,10 +5282,13 @@ cdef class SimplexTreeMulti_KFf32:
5061
5282
  if isinstance(other, SimplexTreeMulti_KFf32):
5062
5283
  other_ptr = other.thisptr
5063
5284
  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
5285
+ if num_parameters <=0:
5286
+ num_parameters = other.num_parameters
5065
5287
  self.filtration_grid = other.filtration_grid
5066
5288
  elif isinstance(other, SimplexTree): # Constructs a SimplexTreeMulti from a SimplexTree
5067
5289
  self.thisptr = <intptr_t>(new Simplex_tree_multi_interface[KFf32, float]())
5290
+ if num_parameters <= 0:
5291
+ num_parameters = 1
5068
5292
  if safe_conversion or SAFE_CONVERSION:
5069
5293
  new_st_multi = _safe_simplextree_multify_KFf32(other, num_parameters = num_parameters, default_values=np.asarray(default_values))
5070
5294
  self.thisptr, new_st_multi.thisptr = new_st_multi.thisptr, self.thisptr
@@ -5079,10 +5303,12 @@ cdef class SimplexTreeMulti_KFf32:
5079
5303
  else:
5080
5304
  raise TypeError("`other` argument requires to be of type `SimplexTree`, `SimplexTreeMulti`, or `None`.")
5081
5305
  else:
5306
+ if num_parameters <=0:
5307
+ num_parameters = 2 # I don't know how dangerous this is, but this is mostly used.
5082
5308
  self.thisptr = <intptr_t>(new Simplex_tree_multi_interface[KFf32, float]())
5083
- self.get_ptr().set_number_of_parameters(num_parameters)
5309
+ self.set_num_parameter(num_parameters)
5084
5310
  self._is_function_simplextree = False
5085
- self.filtration_grid=[[]*num_parameters]
5311
+ self.filtration_grid=[]
5086
5312
 
5087
5313
  def __dealloc__(self):
5088
5314
  cdef Simplex_tree_multi_interface[KFf32,float]* ptr = self.get_ptr()
@@ -5092,6 +5318,9 @@ cdef class SimplexTreeMulti_KFf32:
5092
5318
 
5093
5319
  def __repr__(self):
5094
5320
  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}]"
5321
+ def __len__(self):
5322
+ return self.num_simplices
5323
+
5095
5324
  def __getstate__(self):
5096
5325
  """:returns: Serialized (or flattened) SimplexTree data structure in order to pickle SimplexTree.
5097
5326
  :rtype: numpy.array of shape (n,)
@@ -5747,11 +5976,26 @@ cdef class SimplexTreeMulti_KFf32:
5747
5976
  out = self.get_ptr().get_filtration_values(degrees)
5748
5977
  filtrations_values = [np.asarray(filtration) for filtration in out]
5749
5978
  # Removes infs
5750
- if inf_to_nan:
5979
+ if inf_to_nan and np.dtype(filtrations_values[0].dtype).kind == 'f':
5751
5980
  for i,f in enumerate(filtrations_values):
5752
5981
  filtrations_values[i][f == np.inf] = np.nan
5753
5982
  filtrations_values[i][f == - np.inf] = np.nan
5754
5983
  return filtrations_values
5984
+ def _clean_filtration_grid(self):
5985
+ """
5986
+ Removes the values in filtration_grid that are not linked to any splx.
5987
+ """
5988
+ if not self.is_squeezed:
5989
+ raise ValueError("No grid to clean.")
5990
+ F = self.filtration_grid
5991
+ self.filtration_grid=None
5992
+ cleaned_coordinates = compute_grid(self)
5993
+ new_st = self.grid_squeeze(cleaned_coordinates)
5994
+
5995
+ self.thisptr, new_st.thisptr = new_st.thisptr, self.thisptr
5996
+ self.filtration_grid = tuple(f[g] for f,g in zip(F,cleaned_coordinates))
5997
+ return self
5998
+
5755
5999
 
5756
6000
 
5757
6001
  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 +6031,16 @@ cdef class SimplexTreeMulti_KFf32:
5787
6031
  return compute_grid(filtrations_values, resolution=resolution,strategy=grid_strategy,drop_quantiles=drop_quantiles)
5788
6032
 
5789
6033
 
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):
6034
+ def grid_squeeze(
6035
+ self,
6036
+ filtration_grid:np.ndarray|list|None=None,
6037
+ bool coordinate_values=True,
6038
+ force=False,
6039
+ strategy:_available_strategies = "exact",
6040
+ grid_strategy=None,
6041
+ inplace=False,
6042
+ **filtration_grid_kwargs
6043
+ )->SimplexTreeMulti_KFi32 | SimplexTreeMulti_KFf32:
5792
6044
  """
5793
6045
  Fit the filtration of the simplextree to a grid.
5794
6046
 
@@ -5799,26 +6051,50 @@ cdef class SimplexTreeMulti_KFf32:
5799
6051
  """
5800
6052
  if not force and self.is_squeezed:
5801
6053
  raise Exception("SimplexTree already squeezed, use `force=True` if that's really what you want to do.")
6054
+
6055
+ if grid_strategy is not None:
6056
+ warn("`grid_strategy` is deprecated, use `strategy` instead.",DeprecationWarning)
6057
+ strategy=grid_strategy
6058
+
6059
+ if self.is_squeezed:
6060
+ warn("(copy warning) Squeezing an already squeezed slicer.")
6061
+ temp = self.unsqueeze()
6062
+ subgrid = compute_grid(self.filtration_grid, strategy=strategy, resolution=resolution)
6063
+ return temp.grid_squeeze(subgrid, coordinates=coordinates, inplace=inplace)
6064
+
5802
6065
  #TODO : multi-critical
5803
6066
  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
6067
+ filtration_grid = self.get_filtration_grid(grid_strategy=strategy, **filtration_grid_kwargs)
6068
+ else:
6069
+ filtration_grid = sanitize_grid(filtration_grid)
6070
+ if len(filtration_grid) != self.num_parameters:
6071
+ raise ValueError(f"Invalid grid to squeeze onto. Got {len(filtration_grid)=} != {self.num_parameters=}.")
6072
+ api = api_from_tensor(filtration_grid[0])
6073
+ 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
6074
  if coordinate_values and inplace:
5809
- self.filtration_grid = c_filtration_grid
6075
+ self.filtration_grid = filtration_grid
5810
6076
  if inplace or not coordinate_values:
5811
6077
  self.get_ptr().squeeze_filtration_inplace(c_filtration_grid, coordinate_values)
5812
6078
  else:
5813
6079
  out = SimplexTreeMulti_KFi32(num_parameters=self.num_parameters)
5814
6080
  self.get_ptr().squeeze_filtration(out.thisptr, c_filtration_grid)
5815
- out.filtration_grid = c_filtration_grid
6081
+ out.filtration_grid = filtration_grid
5816
6082
  return out
5817
6083
  return self
5818
6084
 
6085
+ def unsqueeze(self, grid=None)->SimplexTreeMulti_KFf64:
6086
+ from multipers.grids import sanitize_grid
6087
+ grid = self.filtration_grid if grid is None else grid
6088
+
6089
+ cdef vector[vector[double]] cgrid = sanitize_grid(grid, numpyfy=True)
6090
+ new_slicer = SimplexTreeMulti_KFf64()
6091
+ new_slicer.get_ptr().unsqueeze_filtration(self.thisptr, cgrid)
6092
+
6093
+ return new_slicer
6094
+
5819
6095
  @property
5820
6096
  def is_squeezed(self)->bool:
5821
- return self.num_vertices > 0 and len(self.filtration_grid)>0 and len(self.filtration_grid[0]) > 0
6097
+ 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
6098
 
5823
6099
  @property
5824
6100
  def dtype(self)->type:
@@ -6111,15 +6387,15 @@ cdef class SimplexTreeMulti_Ff32:
6111
6387
  """
6112
6388
  cdef public intptr_t thisptr
6113
6389
 
6114
- cdef public vector[vector[double]] filtration_grid
6115
- cdef public bool _is_function_simplextree
6390
+ cdef public object filtration_grid
6391
+ cdef public bool _is_function_simplextree # TODO : deprecate
6116
6392
  # Get the pointer casted as it should be
6117
6393
  cdef Simplex_tree_multi_interface[Ff32, float]* get_ptr(self) noexcept nogil:
6118
6394
  return <Simplex_tree_multi_interface[Ff32, float]*>(self.thisptr)
6119
6395
 
6120
6396
  # cdef Simplex_tree_persistence_interface * pcohptr
6121
6397
  # Fake constructor that does nothing but documenting the constructor
6122
- def __init__(self, other = None, num_parameters:int=2,default_values=[], safe_conversion=False):
6398
+ def __init__(self, other = None, num_parameters:int=-1,default_values=[], safe_conversion=False):
6123
6399
  """SimplexTreeMulti constructor.
6124
6400
 
6125
6401
  :param other: If `other` is `None` (default value), an empty `SimplexTreeMulti` is created.
@@ -6144,7 +6420,7 @@ cdef class SimplexTreeMulti_Ff32:
6144
6420
  def is_kcritical(self)->bool:
6145
6421
  return False
6146
6422
  # The real cython constructor
6147
- def __cinit__(self, other = None, int num_parameters=2,
6423
+ def __cinit__(self, other = None, int num_parameters=-1,
6148
6424
  default_values=np.asarray([SimplexTreeMulti_Ff32.T_minus_inf()]), # I'm not sure why `[]` does not work. Cython bug ?
6149
6425
  bool safe_conversion=False,
6150
6426
  ): #TODO doc
@@ -6157,10 +6433,13 @@ cdef class SimplexTreeMulti_Ff32:
6157
6433
  if isinstance(other, SimplexTreeMulti_Ff32):
6158
6434
  other_ptr = other.thisptr
6159
6435
  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
6436
+ if num_parameters <=0:
6437
+ num_parameters = other.num_parameters
6161
6438
  self.filtration_grid = other.filtration_grid
6162
6439
  elif isinstance(other, SimplexTree): # Constructs a SimplexTreeMulti from a SimplexTree
6163
6440
  self.thisptr = <intptr_t>(new Simplex_tree_multi_interface[Ff32, float]())
6441
+ if num_parameters <= 0:
6442
+ num_parameters = 1
6164
6443
  if safe_conversion or SAFE_CONVERSION:
6165
6444
  new_st_multi = _safe_simplextree_multify_Ff32(other, num_parameters = num_parameters, default_values=np.asarray(default_values))
6166
6445
  self.thisptr, new_st_multi.thisptr = new_st_multi.thisptr, self.thisptr
@@ -6175,10 +6454,12 @@ cdef class SimplexTreeMulti_Ff32:
6175
6454
  else:
6176
6455
  raise TypeError("`other` argument requires to be of type `SimplexTree`, `SimplexTreeMulti`, or `None`.")
6177
6456
  else:
6457
+ if num_parameters <=0:
6458
+ num_parameters = 2 # I don't know how dangerous this is, but this is mostly used.
6178
6459
  self.thisptr = <intptr_t>(new Simplex_tree_multi_interface[Ff32, float]())
6179
- self.get_ptr().set_number_of_parameters(num_parameters)
6460
+ self.set_num_parameter(num_parameters)
6180
6461
  self._is_function_simplextree = False
6181
- self.filtration_grid=[[]*num_parameters]
6462
+ self.filtration_grid=[]
6182
6463
 
6183
6464
  def __dealloc__(self):
6184
6465
  cdef Simplex_tree_multi_interface[Ff32,float]* ptr = self.get_ptr()
@@ -6188,6 +6469,9 @@ cdef class SimplexTreeMulti_Ff32:
6188
6469
 
6189
6470
  def __repr__(self):
6190
6471
  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}]"
6472
+ def __len__(self):
6473
+ return self.num_simplices
6474
+
6191
6475
  def __getstate__(self):
6192
6476
  """:returns: Serialized (or flattened) SimplexTree data structure in order to pickle SimplexTree.
6193
6477
  :rtype: numpy.array of shape (n,)
@@ -7120,11 +7404,26 @@ cdef class SimplexTreeMulti_Ff32:
7120
7404
  out = self.get_ptr().get_filtration_values(degrees)
7121
7405
  filtrations_values = [np.asarray(filtration) for filtration in out]
7122
7406
  # Removes infs
7123
- if inf_to_nan:
7407
+ if inf_to_nan and np.dtype(filtrations_values[0].dtype).kind == 'f':
7124
7408
  for i,f in enumerate(filtrations_values):
7125
7409
  filtrations_values[i][f == np.inf] = np.nan
7126
7410
  filtrations_values[i][f == - np.inf] = np.nan
7127
7411
  return filtrations_values
7412
+ def _clean_filtration_grid(self):
7413
+ """
7414
+ Removes the values in filtration_grid that are not linked to any splx.
7415
+ """
7416
+ if not self.is_squeezed:
7417
+ raise ValueError("No grid to clean.")
7418
+ F = self.filtration_grid
7419
+ self.filtration_grid=None
7420
+ cleaned_coordinates = compute_grid(self)
7421
+ new_st = self.grid_squeeze(cleaned_coordinates)
7422
+
7423
+ self.thisptr, new_st.thisptr = new_st.thisptr, self.thisptr
7424
+ self.filtration_grid = tuple(f[g] for f,g in zip(F,cleaned_coordinates))
7425
+ return self
7426
+
7128
7427
 
7129
7428
 
7130
7429
  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 +7459,16 @@ cdef class SimplexTreeMulti_Ff32:
7160
7459
  return compute_grid(filtrations_values, resolution=resolution,strategy=grid_strategy,drop_quantiles=drop_quantiles)
7161
7460
 
7162
7461
 
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):
7462
+ def grid_squeeze(
7463
+ self,
7464
+ filtration_grid:np.ndarray|list|None=None,
7465
+ bool coordinate_values=True,
7466
+ force=False,
7467
+ strategy:_available_strategies = "exact",
7468
+ grid_strategy=None,
7469
+ inplace=False,
7470
+ **filtration_grid_kwargs
7471
+ )->SimplexTreeMulti_Fi32 | SimplexTreeMulti_Ff32:
7165
7472
  """
7166
7473
  Fit the filtration of the simplextree to a grid.
7167
7474
 
@@ -7172,26 +7479,50 @@ cdef class SimplexTreeMulti_Ff32:
7172
7479
  """
7173
7480
  if not force and self.is_squeezed:
7174
7481
  raise Exception("SimplexTree already squeezed, use `force=True` if that's really what you want to do.")
7482
+
7483
+ if grid_strategy is not None:
7484
+ warn("`grid_strategy` is deprecated, use `strategy` instead.",DeprecationWarning)
7485
+ strategy=grid_strategy
7486
+
7487
+ if self.is_squeezed:
7488
+ warn("(copy warning) Squeezing an already squeezed slicer.")
7489
+ temp = self.unsqueeze()
7490
+ subgrid = compute_grid(self.filtration_grid, strategy=strategy, resolution=resolution)
7491
+ return temp.grid_squeeze(subgrid, coordinates=coordinates, inplace=inplace)
7492
+
7175
7493
  #TODO : multi-critical
7176
7494
  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
7495
+ filtration_grid = self.get_filtration_grid(grid_strategy=strategy, **filtration_grid_kwargs)
7496
+ else:
7497
+ filtration_grid = sanitize_grid(filtration_grid)
7498
+ if len(filtration_grid) != self.num_parameters:
7499
+ raise ValueError(f"Invalid grid to squeeze onto. Got {len(filtration_grid)=} != {self.num_parameters=}.")
7500
+ api = api_from_tensor(filtration_grid[0])
7501
+ 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
7502
  if coordinate_values and inplace:
7182
- self.filtration_grid = c_filtration_grid
7503
+ self.filtration_grid = filtration_grid
7183
7504
  if inplace or not coordinate_values:
7184
7505
  self.get_ptr().squeeze_filtration_inplace(c_filtration_grid, coordinate_values)
7185
7506
  else:
7186
7507
  out = SimplexTreeMulti_Fi32(num_parameters=self.num_parameters)
7187
7508
  self.get_ptr().squeeze_filtration(out.thisptr, c_filtration_grid)
7188
- out.filtration_grid = c_filtration_grid
7509
+ out.filtration_grid = filtration_grid
7189
7510
  return out
7190
7511
  return self
7191
7512
 
7513
+ def unsqueeze(self, grid=None)->SimplexTreeMulti_Ff64:
7514
+ from multipers.grids import sanitize_grid
7515
+ grid = self.filtration_grid if grid is None else grid
7516
+
7517
+ cdef vector[vector[double]] cgrid = sanitize_grid(grid, numpyfy=True)
7518
+ new_slicer = SimplexTreeMulti_Ff64()
7519
+ new_slicer.get_ptr().unsqueeze_filtration(self.thisptr, cgrid)
7520
+
7521
+ return new_slicer
7522
+
7192
7523
  @property
7193
7524
  def is_squeezed(self)->bool:
7194
- return self.num_vertices > 0 and len(self.filtration_grid)>0 and len(self.filtration_grid[0]) > 0
7525
+ 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
7526
 
7196
7527
  @property
7197
7528
  def dtype(self)->type:
@@ -7482,15 +7813,15 @@ cdef class SimplexTreeMulti_KFf64:
7482
7813
  """
7483
7814
  cdef public intptr_t thisptr
7484
7815
 
7485
- cdef public vector[vector[double]] filtration_grid
7486
- cdef public bool _is_function_simplextree
7816
+ cdef public object filtration_grid
7817
+ cdef public bool _is_function_simplextree # TODO : deprecate
7487
7818
  # Get the pointer casted as it should be
7488
7819
  cdef Simplex_tree_multi_interface[KFf64, double]* get_ptr(self) noexcept nogil:
7489
7820
  return <Simplex_tree_multi_interface[KFf64, double]*>(self.thisptr)
7490
7821
 
7491
7822
  # cdef Simplex_tree_persistence_interface * pcohptr
7492
7823
  # Fake constructor that does nothing but documenting the constructor
7493
- def __init__(self, other = None, num_parameters:int=2,default_values=[], safe_conversion=False):
7824
+ def __init__(self, other = None, num_parameters:int=-1,default_values=[], safe_conversion=False):
7494
7825
  """SimplexTreeMulti constructor.
7495
7826
 
7496
7827
  :param other: If `other` is `None` (default value), an empty `SimplexTreeMulti` is created.
@@ -7515,7 +7846,7 @@ cdef class SimplexTreeMulti_KFf64:
7515
7846
  def is_kcritical(self)->bool:
7516
7847
  return True
7517
7848
  # The real cython constructor
7518
- def __cinit__(self, other = None, int num_parameters=2,
7849
+ def __cinit__(self, other = None, int num_parameters=-1,
7519
7850
  default_values=np.asarray([SimplexTreeMulti_KFf64.T_minus_inf()]), # I'm not sure why `[]` does not work. Cython bug ?
7520
7851
  bool safe_conversion=False,
7521
7852
  ): #TODO doc
@@ -7528,10 +7859,13 @@ cdef class SimplexTreeMulti_KFf64:
7528
7859
  if isinstance(other, SimplexTreeMulti_KFf64):
7529
7860
  other_ptr = other.thisptr
7530
7861
  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
7862
+ if num_parameters <=0:
7863
+ num_parameters = other.num_parameters
7532
7864
  self.filtration_grid = other.filtration_grid
7533
7865
  elif isinstance(other, SimplexTree): # Constructs a SimplexTreeMulti from a SimplexTree
7534
7866
  self.thisptr = <intptr_t>(new Simplex_tree_multi_interface[KFf64, double]())
7867
+ if num_parameters <= 0:
7868
+ num_parameters = 1
7535
7869
  if safe_conversion or SAFE_CONVERSION:
7536
7870
  new_st_multi = _safe_simplextree_multify_KFf64(other, num_parameters = num_parameters, default_values=np.asarray(default_values))
7537
7871
  self.thisptr, new_st_multi.thisptr = new_st_multi.thisptr, self.thisptr
@@ -7546,10 +7880,12 @@ cdef class SimplexTreeMulti_KFf64:
7546
7880
  else:
7547
7881
  raise TypeError("`other` argument requires to be of type `SimplexTree`, `SimplexTreeMulti`, or `None`.")
7548
7882
  else:
7883
+ if num_parameters <=0:
7884
+ num_parameters = 2 # I don't know how dangerous this is, but this is mostly used.
7549
7885
  self.thisptr = <intptr_t>(new Simplex_tree_multi_interface[KFf64, double]())
7550
- self.get_ptr().set_number_of_parameters(num_parameters)
7886
+ self.set_num_parameter(num_parameters)
7551
7887
  self._is_function_simplextree = False
7552
- self.filtration_grid=[[]*num_parameters]
7888
+ self.filtration_grid=[]
7553
7889
 
7554
7890
  def __dealloc__(self):
7555
7891
  cdef Simplex_tree_multi_interface[KFf64,double]* ptr = self.get_ptr()
@@ -7559,6 +7895,9 @@ cdef class SimplexTreeMulti_KFf64:
7559
7895
 
7560
7896
  def __repr__(self):
7561
7897
  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}]"
7898
+ def __len__(self):
7899
+ return self.num_simplices
7900
+
7562
7901
  def __getstate__(self):
7563
7902
  """:returns: Serialized (or flattened) SimplexTree data structure in order to pickle SimplexTree.
7564
7903
  :rtype: numpy.array of shape (n,)
@@ -8214,11 +8553,26 @@ cdef class SimplexTreeMulti_KFf64:
8214
8553
  out = self.get_ptr().get_filtration_values(degrees)
8215
8554
  filtrations_values = [np.asarray(filtration) for filtration in out]
8216
8555
  # Removes infs
8217
- if inf_to_nan:
8556
+ if inf_to_nan and np.dtype(filtrations_values[0].dtype).kind == 'f':
8218
8557
  for i,f in enumerate(filtrations_values):
8219
8558
  filtrations_values[i][f == np.inf] = np.nan
8220
8559
  filtrations_values[i][f == - np.inf] = np.nan
8221
8560
  return filtrations_values
8561
+ def _clean_filtration_grid(self):
8562
+ """
8563
+ Removes the values in filtration_grid that are not linked to any splx.
8564
+ """
8565
+ if not self.is_squeezed:
8566
+ raise ValueError("No grid to clean.")
8567
+ F = self.filtration_grid
8568
+ self.filtration_grid=None
8569
+ cleaned_coordinates = compute_grid(self)
8570
+ new_st = self.grid_squeeze(cleaned_coordinates)
8571
+
8572
+ self.thisptr, new_st.thisptr = new_st.thisptr, self.thisptr
8573
+ self.filtration_grid = tuple(f[g] for f,g in zip(F,cleaned_coordinates))
8574
+ return self
8575
+
8222
8576
 
8223
8577
 
8224
8578
  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 +8608,16 @@ cdef class SimplexTreeMulti_KFf64:
8254
8608
  return compute_grid(filtrations_values, resolution=resolution,strategy=grid_strategy,drop_quantiles=drop_quantiles)
8255
8609
 
8256
8610
 
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):
8611
+ def grid_squeeze(
8612
+ self,
8613
+ filtration_grid:np.ndarray|list|None=None,
8614
+ bool coordinate_values=True,
8615
+ force=False,
8616
+ strategy:_available_strategies = "exact",
8617
+ grid_strategy=None,
8618
+ inplace=False,
8619
+ **filtration_grid_kwargs
8620
+ )->SimplexTreeMulti_KFi32 | SimplexTreeMulti_KFf64:
8259
8621
  """
8260
8622
  Fit the filtration of the simplextree to a grid.
8261
8623
 
@@ -8266,26 +8628,50 @@ cdef class SimplexTreeMulti_KFf64:
8266
8628
  """
8267
8629
  if not force and self.is_squeezed:
8268
8630
  raise Exception("SimplexTree already squeezed, use `force=True` if that's really what you want to do.")
8631
+
8632
+ if grid_strategy is not None:
8633
+ warn("`grid_strategy` is deprecated, use `strategy` instead.",DeprecationWarning)
8634
+ strategy=grid_strategy
8635
+
8636
+ if self.is_squeezed:
8637
+ warn("(copy warning) Squeezing an already squeezed slicer.")
8638
+ temp = self.unsqueeze()
8639
+ subgrid = compute_grid(self.filtration_grid, strategy=strategy, resolution=resolution)
8640
+ return temp.grid_squeeze(subgrid, coordinates=coordinates, inplace=inplace)
8641
+
8269
8642
  #TODO : multi-critical
8270
8643
  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
8644
+ filtration_grid = self.get_filtration_grid(grid_strategy=strategy, **filtration_grid_kwargs)
8645
+ else:
8646
+ filtration_grid = sanitize_grid(filtration_grid)
8647
+ if len(filtration_grid) != self.num_parameters:
8648
+ raise ValueError(f"Invalid grid to squeeze onto. Got {len(filtration_grid)=} != {self.num_parameters=}.")
8649
+ api = api_from_tensor(filtration_grid[0])
8650
+ 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
8651
  if coordinate_values and inplace:
8276
- self.filtration_grid = c_filtration_grid
8652
+ self.filtration_grid = filtration_grid
8277
8653
  if inplace or not coordinate_values:
8278
8654
  self.get_ptr().squeeze_filtration_inplace(c_filtration_grid, coordinate_values)
8279
8655
  else:
8280
8656
  out = SimplexTreeMulti_KFi32(num_parameters=self.num_parameters)
8281
8657
  self.get_ptr().squeeze_filtration(out.thisptr, c_filtration_grid)
8282
- out.filtration_grid = c_filtration_grid
8658
+ out.filtration_grid = filtration_grid
8283
8659
  return out
8284
8660
  return self
8285
8661
 
8662
+ def unsqueeze(self, grid=None)->SimplexTreeMulti_KFf64:
8663
+ from multipers.grids import sanitize_grid
8664
+ grid = self.filtration_grid if grid is None else grid
8665
+
8666
+ cdef vector[vector[double]] cgrid = sanitize_grid(grid, numpyfy=True)
8667
+ new_slicer = SimplexTreeMulti_KFf64()
8668
+ new_slicer.get_ptr().unsqueeze_filtration(self.thisptr, cgrid)
8669
+
8670
+ return new_slicer
8671
+
8286
8672
  @property
8287
8673
  def is_squeezed(self)->bool:
8288
- return self.num_vertices > 0 and len(self.filtration_grid)>0 and len(self.filtration_grid[0]) > 0
8674
+ 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
8675
 
8290
8676
  @property
8291
8677
  def dtype(self)->type:
@@ -8578,15 +8964,15 @@ cdef class SimplexTreeMulti_Ff64:
8578
8964
  """
8579
8965
  cdef public intptr_t thisptr
8580
8966
 
8581
- cdef public vector[vector[double]] filtration_grid
8582
- cdef public bool _is_function_simplextree
8967
+ cdef public object filtration_grid
8968
+ cdef public bool _is_function_simplextree # TODO : deprecate
8583
8969
  # Get the pointer casted as it should be
8584
8970
  cdef Simplex_tree_multi_interface[Ff64, double]* get_ptr(self) noexcept nogil:
8585
8971
  return <Simplex_tree_multi_interface[Ff64, double]*>(self.thisptr)
8586
8972
 
8587
8973
  # cdef Simplex_tree_persistence_interface * pcohptr
8588
8974
  # Fake constructor that does nothing but documenting the constructor
8589
- def __init__(self, other = None, num_parameters:int=2,default_values=[], safe_conversion=False):
8975
+ def __init__(self, other = None, num_parameters:int=-1,default_values=[], safe_conversion=False):
8590
8976
  """SimplexTreeMulti constructor.
8591
8977
 
8592
8978
  :param other: If `other` is `None` (default value), an empty `SimplexTreeMulti` is created.
@@ -8611,7 +8997,7 @@ cdef class SimplexTreeMulti_Ff64:
8611
8997
  def is_kcritical(self)->bool:
8612
8998
  return False
8613
8999
  # The real cython constructor
8614
- def __cinit__(self, other = None, int num_parameters=2,
9000
+ def __cinit__(self, other = None, int num_parameters=-1,
8615
9001
  default_values=np.asarray([SimplexTreeMulti_Ff64.T_minus_inf()]), # I'm not sure why `[]` does not work. Cython bug ?
8616
9002
  bool safe_conversion=False,
8617
9003
  ): #TODO doc
@@ -8624,10 +9010,13 @@ cdef class SimplexTreeMulti_Ff64:
8624
9010
  if isinstance(other, SimplexTreeMulti_Ff64):
8625
9011
  other_ptr = other.thisptr
8626
9012
  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
9013
+ if num_parameters <=0:
9014
+ num_parameters = other.num_parameters
8628
9015
  self.filtration_grid = other.filtration_grid
8629
9016
  elif isinstance(other, SimplexTree): # Constructs a SimplexTreeMulti from a SimplexTree
8630
9017
  self.thisptr = <intptr_t>(new Simplex_tree_multi_interface[Ff64, double]())
9018
+ if num_parameters <= 0:
9019
+ num_parameters = 1
8631
9020
  if safe_conversion or SAFE_CONVERSION:
8632
9021
  new_st_multi = _safe_simplextree_multify_Ff64(other, num_parameters = num_parameters, default_values=np.asarray(default_values))
8633
9022
  self.thisptr, new_st_multi.thisptr = new_st_multi.thisptr, self.thisptr
@@ -8642,10 +9031,12 @@ cdef class SimplexTreeMulti_Ff64:
8642
9031
  else:
8643
9032
  raise TypeError("`other` argument requires to be of type `SimplexTree`, `SimplexTreeMulti`, or `None`.")
8644
9033
  else:
9034
+ if num_parameters <=0:
9035
+ num_parameters = 2 # I don't know how dangerous this is, but this is mostly used.
8645
9036
  self.thisptr = <intptr_t>(new Simplex_tree_multi_interface[Ff64, double]())
8646
- self.get_ptr().set_number_of_parameters(num_parameters)
9037
+ self.set_num_parameter(num_parameters)
8647
9038
  self._is_function_simplextree = False
8648
- self.filtration_grid=[[]*num_parameters]
9039
+ self.filtration_grid=[]
8649
9040
 
8650
9041
  def __dealloc__(self):
8651
9042
  cdef Simplex_tree_multi_interface[Ff64,double]* ptr = self.get_ptr()
@@ -8655,6 +9046,9 @@ cdef class SimplexTreeMulti_Ff64:
8655
9046
 
8656
9047
  def __repr__(self):
8657
9048
  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}]"
9049
+ def __len__(self):
9050
+ return self.num_simplices
9051
+
8658
9052
  def __getstate__(self):
8659
9053
  """:returns: Serialized (or flattened) SimplexTree data structure in order to pickle SimplexTree.
8660
9054
  :rtype: numpy.array of shape (n,)
@@ -9587,11 +9981,26 @@ cdef class SimplexTreeMulti_Ff64:
9587
9981
  out = self.get_ptr().get_filtration_values(degrees)
9588
9982
  filtrations_values = [np.asarray(filtration) for filtration in out]
9589
9983
  # Removes infs
9590
- if inf_to_nan:
9984
+ if inf_to_nan and np.dtype(filtrations_values[0].dtype).kind == 'f':
9591
9985
  for i,f in enumerate(filtrations_values):
9592
9986
  filtrations_values[i][f == np.inf] = np.nan
9593
9987
  filtrations_values[i][f == - np.inf] = np.nan
9594
9988
  return filtrations_values
9989
+ def _clean_filtration_grid(self):
9990
+ """
9991
+ Removes the values in filtration_grid that are not linked to any splx.
9992
+ """
9993
+ if not self.is_squeezed:
9994
+ raise ValueError("No grid to clean.")
9995
+ F = self.filtration_grid
9996
+ self.filtration_grid=None
9997
+ cleaned_coordinates = compute_grid(self)
9998
+ new_st = self.grid_squeeze(cleaned_coordinates)
9999
+
10000
+ self.thisptr, new_st.thisptr = new_st.thisptr, self.thisptr
10001
+ self.filtration_grid = tuple(f[g] for f,g in zip(F,cleaned_coordinates))
10002
+ return self
10003
+
9595
10004
 
9596
10005
 
9597
10006
  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 +10036,16 @@ cdef class SimplexTreeMulti_Ff64:
9627
10036
  return compute_grid(filtrations_values, resolution=resolution,strategy=grid_strategy,drop_quantiles=drop_quantiles)
9628
10037
 
9629
10038
 
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):
10039
+ def grid_squeeze(
10040
+ self,
10041
+ filtration_grid:np.ndarray|list|None=None,
10042
+ bool coordinate_values=True,
10043
+ force=False,
10044
+ strategy:_available_strategies = "exact",
10045
+ grid_strategy=None,
10046
+ inplace=False,
10047
+ **filtration_grid_kwargs
10048
+ )->SimplexTreeMulti_Fi32 | SimplexTreeMulti_Ff64:
9632
10049
  """
9633
10050
  Fit the filtration of the simplextree to a grid.
9634
10051
 
@@ -9639,26 +10056,50 @@ cdef class SimplexTreeMulti_Ff64:
9639
10056
  """
9640
10057
  if not force and self.is_squeezed:
9641
10058
  raise Exception("SimplexTree already squeezed, use `force=True` if that's really what you want to do.")
10059
+
10060
+ if grid_strategy is not None:
10061
+ warn("`grid_strategy` is deprecated, use `strategy` instead.",DeprecationWarning)
10062
+ strategy=grid_strategy
10063
+
10064
+ if self.is_squeezed:
10065
+ warn("(copy warning) Squeezing an already squeezed slicer.")
10066
+ temp = self.unsqueeze()
10067
+ subgrid = compute_grid(self.filtration_grid, strategy=strategy, resolution=resolution)
10068
+ return temp.grid_squeeze(subgrid, coordinates=coordinates, inplace=inplace)
10069
+
9642
10070
  #TODO : multi-critical
9643
10071
  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
10072
+ filtration_grid = self.get_filtration_grid(grid_strategy=strategy, **filtration_grid_kwargs)
10073
+ else:
10074
+ filtration_grid = sanitize_grid(filtration_grid)
10075
+ if len(filtration_grid) != self.num_parameters:
10076
+ raise ValueError(f"Invalid grid to squeeze onto. Got {len(filtration_grid)=} != {self.num_parameters=}.")
10077
+ api = api_from_tensor(filtration_grid[0])
10078
+ 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
10079
  if coordinate_values and inplace:
9649
- self.filtration_grid = c_filtration_grid
10080
+ self.filtration_grid = filtration_grid
9650
10081
  if inplace or not coordinate_values:
9651
10082
  self.get_ptr().squeeze_filtration_inplace(c_filtration_grid, coordinate_values)
9652
10083
  else:
9653
10084
  out = SimplexTreeMulti_Fi32(num_parameters=self.num_parameters)
9654
10085
  self.get_ptr().squeeze_filtration(out.thisptr, c_filtration_grid)
9655
- out.filtration_grid = c_filtration_grid
10086
+ out.filtration_grid = filtration_grid
9656
10087
  return out
9657
10088
  return self
9658
10089
 
10090
+ def unsqueeze(self, grid=None)->SimplexTreeMulti_Ff64:
10091
+ from multipers.grids import sanitize_grid
10092
+ grid = self.filtration_grid if grid is None else grid
10093
+
10094
+ cdef vector[vector[double]] cgrid = sanitize_grid(grid, numpyfy=True)
10095
+ new_slicer = SimplexTreeMulti_Ff64()
10096
+ new_slicer.get_ptr().unsqueeze_filtration(self.thisptr, cgrid)
10097
+
10098
+ return new_slicer
10099
+
9659
10100
  @property
9660
10101
  def is_squeezed(self)->bool:
9661
- return self.num_vertices > 0 and len(self.filtration_grid)>0 and len(self.filtration_grid[0]) > 0
10102
+ 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
10103
 
9663
10104
  @property
9664
10105
  def dtype(self)->type:
@@ -9968,7 +10409,7 @@ def is_simplextree_multi(input)->bool:
9968
10409
 
9969
10410
 
9970
10411
 
9971
- def SimplexTreeMulti(input=None, int num_parameters=2, dtype:type = np.float64, bool kcritical = False,**kwargs) -> SimplexTreeMulti_type:
10412
+ def SimplexTreeMulti(input=None, int num_parameters=-1, dtype:type = np.float64, bool kcritical = False,**kwargs) -> SimplexTreeMulti_type:
9972
10413
  """SimplexTreeMulti constructor.
9973
10414
 
9974
10415
  :param other: If `other` is `None` (default value), an empty `SimplexTreeMulti` is created.
@@ -10179,7 +10620,8 @@ def _euler_signed_measure(simplextree, mass_default=None, bool verbose=False):
10179
10620
  `[signed_measure_of_degree for degree in degrees]`
10180
10621
  with `signed_measure_of_degree` of the form `(dirac location, dirac weights)`.
10181
10622
  """
10182
- assert len(simplextree.filtration_grid[0]) > 0, "Squeeze grid first."
10623
+ if not simplextree.is_squeezed:
10624
+ raise ValueError("Squeeze grid first.")
10183
10625
  cdef bool zero_pad = mass_default is not None
10184
10626
  # assert simplextree.num_parameters == 2
10185
10627
  grid_shape = np.array([len(f) for f in simplextree.filtration_grid])
@@ -10297,9 +10739,9 @@ def _rank_signed_measure(simplextree, vector[indices_type] degrees, mass_default
10297
10739
  else:
10298
10740
  mass_default = np.asarray(mass_default)
10299
10741
  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
10742
+ # if zero_pad:
10743
+ # for i, _ in enumerate(grid_shape):
10744
+ # grid_shape[i] += 1 # adds a 0
10303
10745
  # grid_conversion = tuple(np.concatenate([f, [mass_default[i]]]) for i,f in enumerate(grid_conversion))
10304
10746
 
10305
10747
  assert len(grid_shape) == simplextree.num_parameters, "Grid shape size has to be the number of parameters."