multipers 2.2.3__cp311-cp311-win_amd64.whl → 2.3.1__cp311-cp311-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of multipers might be problematic. Click here for more details.

Files changed (182) hide show
  1. multipers/__init__.py +33 -31
  2. multipers/_signed_measure_meta.py +430 -430
  3. multipers/_slicer_meta.py +211 -212
  4. multipers/data/MOL2.py +458 -458
  5. multipers/data/UCR.py +18 -18
  6. multipers/data/graphs.py +466 -466
  7. multipers/data/immuno_regions.py +27 -27
  8. multipers/data/pytorch2simplextree.py +90 -90
  9. multipers/data/shape3d.py +101 -101
  10. multipers/data/synthetic.py +113 -111
  11. multipers/distances.py +198 -198
  12. multipers/filtration_conversions.pxd.tp +84 -84
  13. multipers/filtrations/__init__.py +18 -0
  14. multipers/{ml/convolutions.py → filtrations/density.py} +563 -520
  15. multipers/filtrations/filtrations.py +289 -0
  16. multipers/filtrations.pxd +224 -224
  17. multipers/function_rips.cp311-win_amd64.pyd +0 -0
  18. multipers/function_rips.pyx +105 -105
  19. multipers/grids.cp311-win_amd64.pyd +0 -0
  20. multipers/grids.pyx +350 -350
  21. multipers/gudhi/Persistence_slices_interface.h +132 -132
  22. multipers/gudhi/Simplex_tree_interface.h +239 -245
  23. multipers/gudhi/Simplex_tree_multi_interface.h +516 -561
  24. multipers/gudhi/cubical_to_boundary.h +59 -59
  25. multipers/gudhi/gudhi/Bitmap_cubical_complex.h +450 -450
  26. multipers/gudhi/gudhi/Bitmap_cubical_complex_base.h +1070 -1070
  27. multipers/gudhi/gudhi/Bitmap_cubical_complex_periodic_boundary_conditions_base.h +579 -579
  28. multipers/gudhi/gudhi/Debug_utils.h +45 -45
  29. multipers/gudhi/gudhi/Fields/Multi_field.h +484 -484
  30. multipers/gudhi/gudhi/Fields/Multi_field_operators.h +455 -455
  31. multipers/gudhi/gudhi/Fields/Multi_field_shared.h +450 -450
  32. multipers/gudhi/gudhi/Fields/Multi_field_small.h +531 -531
  33. multipers/gudhi/gudhi/Fields/Multi_field_small_operators.h +507 -507
  34. multipers/gudhi/gudhi/Fields/Multi_field_small_shared.h +531 -531
  35. multipers/gudhi/gudhi/Fields/Z2_field.h +355 -355
  36. multipers/gudhi/gudhi/Fields/Z2_field_operators.h +376 -376
  37. multipers/gudhi/gudhi/Fields/Zp_field.h +420 -420
  38. multipers/gudhi/gudhi/Fields/Zp_field_operators.h +400 -400
  39. multipers/gudhi/gudhi/Fields/Zp_field_shared.h +418 -418
  40. multipers/gudhi/gudhi/Flag_complex_edge_collapser.h +337 -337
  41. multipers/gudhi/gudhi/Matrix.h +2107 -2107
  42. multipers/gudhi/gudhi/Multi_critical_filtration.h +1038 -1038
  43. multipers/gudhi/gudhi/Multi_persistence/Box.h +171 -171
  44. multipers/gudhi/gudhi/Multi_persistence/Line.h +282 -282
  45. multipers/gudhi/gudhi/Off_reader.h +173 -173
  46. multipers/gudhi/gudhi/One_critical_filtration.h +1433 -1431
  47. multipers/gudhi/gudhi/Persistence_matrix/Base_matrix.h +769 -769
  48. multipers/gudhi/gudhi/Persistence_matrix/Base_matrix_with_column_compression.h +686 -686
  49. multipers/gudhi/gudhi/Persistence_matrix/Boundary_matrix.h +842 -842
  50. multipers/gudhi/gudhi/Persistence_matrix/Chain_matrix.h +1350 -1350
  51. multipers/gudhi/gudhi/Persistence_matrix/Id_to_index_overlay.h +1105 -1105
  52. multipers/gudhi/gudhi/Persistence_matrix/Position_to_index_overlay.h +859 -859
  53. multipers/gudhi/gudhi/Persistence_matrix/RU_matrix.h +910 -910
  54. multipers/gudhi/gudhi/Persistence_matrix/allocators/entry_constructors.h +139 -139
  55. multipers/gudhi/gudhi/Persistence_matrix/base_pairing.h +230 -230
  56. multipers/gudhi/gudhi/Persistence_matrix/base_swap.h +211 -211
  57. multipers/gudhi/gudhi/Persistence_matrix/boundary_cell_position_to_id_mapper.h +60 -60
  58. multipers/gudhi/gudhi/Persistence_matrix/boundary_face_position_to_id_mapper.h +60 -60
  59. multipers/gudhi/gudhi/Persistence_matrix/chain_pairing.h +136 -136
  60. multipers/gudhi/gudhi/Persistence_matrix/chain_rep_cycles.h +190 -190
  61. multipers/gudhi/gudhi/Persistence_matrix/chain_vine_swap.h +616 -616
  62. multipers/gudhi/gudhi/Persistence_matrix/columns/chain_column_extra_properties.h +150 -150
  63. multipers/gudhi/gudhi/Persistence_matrix/columns/column_dimension_holder.h +106 -106
  64. multipers/gudhi/gudhi/Persistence_matrix/columns/column_utilities.h +219 -219
  65. multipers/gudhi/gudhi/Persistence_matrix/columns/entry_types.h +327 -327
  66. multipers/gudhi/gudhi/Persistence_matrix/columns/heap_column.h +1140 -1140
  67. multipers/gudhi/gudhi/Persistence_matrix/columns/intrusive_list_column.h +934 -934
  68. multipers/gudhi/gudhi/Persistence_matrix/columns/intrusive_set_column.h +934 -934
  69. multipers/gudhi/gudhi/Persistence_matrix/columns/list_column.h +980 -980
  70. multipers/gudhi/gudhi/Persistence_matrix/columns/naive_vector_column.h +1092 -1092
  71. multipers/gudhi/gudhi/Persistence_matrix/columns/row_access.h +192 -192
  72. multipers/gudhi/gudhi/Persistence_matrix/columns/set_column.h +921 -921
  73. multipers/gudhi/gudhi/Persistence_matrix/columns/small_vector_column.h +1093 -1093
  74. multipers/gudhi/gudhi/Persistence_matrix/columns/unordered_set_column.h +1012 -1012
  75. multipers/gudhi/gudhi/Persistence_matrix/columns/vector_column.h +1244 -1244
  76. multipers/gudhi/gudhi/Persistence_matrix/matrix_dimension_holders.h +186 -186
  77. multipers/gudhi/gudhi/Persistence_matrix/matrix_row_access.h +164 -164
  78. multipers/gudhi/gudhi/Persistence_matrix/ru_pairing.h +156 -156
  79. multipers/gudhi/gudhi/Persistence_matrix/ru_rep_cycles.h +376 -376
  80. multipers/gudhi/gudhi/Persistence_matrix/ru_vine_swap.h +540 -540
  81. multipers/gudhi/gudhi/Persistent_cohomology/Field_Zp.h +118 -118
  82. multipers/gudhi/gudhi/Persistent_cohomology/Multi_field.h +173 -173
  83. multipers/gudhi/gudhi/Persistent_cohomology/Persistent_cohomology_column.h +128 -128
  84. multipers/gudhi/gudhi/Persistent_cohomology.h +745 -745
  85. multipers/gudhi/gudhi/Points_off_io.h +171 -171
  86. multipers/gudhi/gudhi/Simple_object_pool.h +69 -69
  87. multipers/gudhi/gudhi/Simplex_tree/Simplex_tree_iterators.h +463 -463
  88. multipers/gudhi/gudhi/Simplex_tree/Simplex_tree_node_explicit_storage.h +83 -83
  89. multipers/gudhi/gudhi/Simplex_tree/Simplex_tree_siblings.h +106 -106
  90. multipers/gudhi/gudhi/Simplex_tree/Simplex_tree_star_simplex_iterators.h +277 -277
  91. multipers/gudhi/gudhi/Simplex_tree/hooks_simplex_base.h +62 -62
  92. multipers/gudhi/gudhi/Simplex_tree/indexing_tag.h +27 -27
  93. multipers/gudhi/gudhi/Simplex_tree/serialization_utils.h +62 -62
  94. multipers/gudhi/gudhi/Simplex_tree/simplex_tree_options.h +157 -157
  95. multipers/gudhi/gudhi/Simplex_tree.h +2794 -2794
  96. multipers/gudhi/gudhi/Simplex_tree_multi.h +152 -163
  97. multipers/gudhi/gudhi/distance_functions.h +62 -62
  98. multipers/gudhi/gudhi/graph_simplicial_complex.h +104 -104
  99. multipers/gudhi/gudhi/persistence_interval.h +253 -253
  100. multipers/gudhi/gudhi/persistence_matrix_options.h +170 -170
  101. multipers/gudhi/gudhi/reader_utils.h +367 -367
  102. multipers/gudhi/mma_interface_coh.h +256 -255
  103. multipers/gudhi/mma_interface_h0.h +223 -231
  104. multipers/gudhi/mma_interface_matrix.h +291 -282
  105. multipers/gudhi/naive_merge_tree.h +536 -575
  106. multipers/gudhi/scc_io.h +310 -289
  107. multipers/gudhi/truc.h +957 -888
  108. multipers/io.cp311-win_amd64.pyd +0 -0
  109. multipers/io.pyx +714 -711
  110. multipers/ml/accuracies.py +90 -90
  111. multipers/ml/invariants_with_persistable.py +79 -79
  112. multipers/ml/kernels.py +176 -176
  113. multipers/ml/mma.py +713 -714
  114. multipers/ml/one.py +472 -472
  115. multipers/ml/point_clouds.py +352 -346
  116. multipers/ml/signed_measures.py +1589 -1589
  117. multipers/ml/sliced_wasserstein.py +461 -461
  118. multipers/ml/tools.py +113 -113
  119. multipers/mma_structures.cp311-win_amd64.pyd +0 -0
  120. multipers/mma_structures.pxd +127 -127
  121. multipers/mma_structures.pyx +4 -8
  122. multipers/mma_structures.pyx.tp +1083 -1085
  123. multipers/multi_parameter_rank_invariant/diff_helpers.h +84 -93
  124. multipers/multi_parameter_rank_invariant/euler_characteristic.h +97 -97
  125. multipers/multi_parameter_rank_invariant/function_rips.h +322 -322
  126. multipers/multi_parameter_rank_invariant/hilbert_function.h +769 -769
  127. multipers/multi_parameter_rank_invariant/persistence_slices.h +148 -148
  128. multipers/multi_parameter_rank_invariant/rank_invariant.h +369 -369
  129. multipers/multiparameter_edge_collapse.py +41 -41
  130. multipers/multiparameter_module_approximation/approximation.h +2298 -2295
  131. multipers/multiparameter_module_approximation/combinatory.h +129 -129
  132. multipers/multiparameter_module_approximation/debug.h +107 -107
  133. multipers/multiparameter_module_approximation/format_python-cpp.h +286 -286
  134. multipers/multiparameter_module_approximation/heap_column.h +238 -238
  135. multipers/multiparameter_module_approximation/images.h +79 -79
  136. multipers/multiparameter_module_approximation/list_column.h +174 -174
  137. multipers/multiparameter_module_approximation/list_column_2.h +232 -232
  138. multipers/multiparameter_module_approximation/ru_matrix.h +347 -347
  139. multipers/multiparameter_module_approximation/set_column.h +135 -135
  140. multipers/multiparameter_module_approximation/structure_higher_dim_barcode.h +36 -36
  141. multipers/multiparameter_module_approximation/unordered_set_column.h +166 -166
  142. multipers/multiparameter_module_approximation/utilities.h +403 -419
  143. multipers/multiparameter_module_approximation/vector_column.h +223 -223
  144. multipers/multiparameter_module_approximation/vector_matrix.h +331 -331
  145. multipers/multiparameter_module_approximation/vineyards.h +464 -464
  146. multipers/multiparameter_module_approximation/vineyards_trajectories.h +649 -649
  147. multipers/multiparameter_module_approximation.cp311-win_amd64.pyd +0 -0
  148. multipers/multiparameter_module_approximation.pyx +218 -217
  149. multipers/pickle.py +90 -53
  150. multipers/plots.py +342 -334
  151. multipers/point_measure.cp311-win_amd64.pyd +0 -0
  152. multipers/point_measure.pyx +322 -320
  153. multipers/simplex_tree_multi.cp311-win_amd64.pyd +0 -0
  154. multipers/simplex_tree_multi.pxd +133 -133
  155. multipers/simplex_tree_multi.pyx +115 -48
  156. multipers/simplex_tree_multi.pyx.tp +1947 -1935
  157. multipers/slicer.cp311-win_amd64.pyd +0 -0
  158. multipers/slicer.pxd +301 -120
  159. multipers/slicer.pxd.tp +218 -214
  160. multipers/slicer.pyx +1570 -507
  161. multipers/slicer.pyx.tp +931 -914
  162. multipers/tensor/tensor.h +672 -672
  163. multipers/tensor.pxd +13 -13
  164. multipers/test.pyx +44 -44
  165. multipers/tests/__init__.py +57 -57
  166. multipers/torch/diff_grids.py +217 -217
  167. multipers/torch/rips_density.py +310 -304
  168. {multipers-2.2.3.dist-info → multipers-2.3.1.dist-info}/LICENSE +21 -21
  169. {multipers-2.2.3.dist-info → multipers-2.3.1.dist-info}/METADATA +21 -11
  170. multipers-2.3.1.dist-info/RECORD +182 -0
  171. {multipers-2.2.3.dist-info → multipers-2.3.1.dist-info}/WHEEL +1 -1
  172. multipers/tests/test_diff_helper.py +0 -73
  173. multipers/tests/test_hilbert_function.py +0 -82
  174. multipers/tests/test_mma.py +0 -83
  175. multipers/tests/test_point_clouds.py +0 -49
  176. multipers/tests/test_python-cpp_conversion.py +0 -82
  177. multipers/tests/test_signed_betti.py +0 -181
  178. multipers/tests/test_signed_measure.py +0 -89
  179. multipers/tests/test_simplextreemulti.py +0 -221
  180. multipers/tests/test_slicer.py +0 -221
  181. multipers-2.2.3.dist-info/RECORD +0 -189
  182. {multipers-2.2.3.dist-info → multipers-2.3.1.dist-info}/top_level.txt +0 -0
@@ -1,430 +1,430 @@
1
- from collections.abc import Iterable, Sequence
2
- from typing import Optional, Union
3
-
4
- import numpy as np
5
-
6
- import multipers as mp
7
- from multipers.grids import compute_grid, sms_in_grid
8
- from multipers.plots import plot_signed_measures
9
- from multipers.point_measure import clean_sms, zero_out_sms
10
- from multipers.simplex_tree_multi import (
11
- SimplexTreeMulti_type,
12
- _available_strategies,
13
- is_simplextree_multi,
14
- )
15
- from multipers.slicer import (
16
- Slicer_type,
17
- _hilbert_signed_measure,
18
- _rank_from_slicer,
19
- is_slicer,
20
- )
21
-
22
-
23
- def signed_measure(
24
- filtered_complex: Union[SimplexTreeMulti_type, Slicer_type],
25
- degree: Optional[int] = None,
26
- degrees: Sequence[int | None] = [],
27
- mass_default=None,
28
- grid_strategy: _available_strategies = "exact",
29
- invariant: Optional[str] = None,
30
- plot: bool = False,
31
- verbose: bool = False,
32
- n_jobs: int = -1,
33
- expand_collapse: bool = False,
34
- backend: Optional[str] = None,
35
- thread_id: str = "",
36
- grid: Optional[Iterable] = None,
37
- coordinate_measure: bool = False,
38
- num_collapses: int = 0,
39
- clean: Optional[bool] = None,
40
- vineyard: bool = False,
41
- grid_conversion: Optional[Iterable] = None,
42
- ignore_infinite_filtration_values:bool=True,
43
- **infer_grid_kwargs,
44
- ) -> list[tuple[np.ndarray, np.ndarray]]:
45
- r"""
46
- Computes the signed measures given by the decomposition of the hilbert
47
- function or the euler characteristic, or the rank invariant.
48
-
49
- Input
50
- -----
51
- - filtered_complex: given by a simplextree or a slicer.
52
- - degree:int|None / degrees:list[int] the degrees to compute.
53
- None represents the euler characteristic.
54
- - mass_default: Either None, or 'auto' or 'inf', or array-like of floats.
55
- Where to put the default mass to get a zero-mass measure.
56
- This corresponds to zero-out the filtered complex outside of $\{ x\in \mathbb R^n \mid x\le `mass_default`\}$
57
- - invariant: The invariant to use, either "hilbert", "rank", or "euler".
58
- - plot:bool, plots the computed measures if true.
59
- - n_jobs:int, number of jobs. Defaults to #cpu.
60
- - verbose:bool, prints c++ logs.
61
- - expand_collapse: when the input is a simplextree,
62
- only expands the complex when computing 1-dimensional slices.
63
- Meant to reduce memory footprint at some computational expense.
64
- - backend:str reduces first the filtered complex using an external library `backend`,
65
- see ``backend`` in :func:`multipers.io.reduce_complex`.
66
- - grid: If given, the computations will be done on the restriction of the filtered complex to this grid.
67
- It can also be used for auto-differentiation, i.e., if the grid is a list of pytorch tensors,
68
- then the output measure will be pytorch-differentiable.
69
- - grid_strategy: If not squeezed yet, and no grid is given,
70
- the strategy to coarsen the grid; see ``strategy`` in :func:`multipers.grids.compute_grid`.
71
- - coordinate_measure: bool, if True, compute the signed measure as a coordinates given in grid.
72
- - num_collapses: int, if `filtered_complex` is a simplextree, does some collapses if possible.
73
- - clean: if True, reduces the measure. It is not necessary in general.
74
- - ignore_infinite_filtration_values: Backend optimization.
75
-
76
- Output
77
- ------
78
-
79
- `[signed_measure_of_degree for degree in degrees]`
80
- with `signed_measure_of_degree` of the form `(dirac location, dirac weights)`.
81
-
82
- Notes on computational backends
83
- -------------------------------
84
- There are several backends for each of these computations.
85
- The backend for computations used can be displayed with `verbose=True`, use it!
86
- Also note that if `backend` is given, then the input will be converted to a slicer.
87
- - Euler: is always computed by summing the weights of the simplices
88
- - Hilbert: is computed by computing persistence on slices, and a Möbius inversion,
89
- unless the detected input is a minimal presentation (i.e., `filtered_complex.is_minpres`),
90
- which in that case, doesn't need any computation.
91
- - If the input is a simplextree, this is done via a the standard Gudhi implementation,
92
- with parallel (TBB) computations of slices.
93
- - If the input is a slicer then
94
- - If the input is vineyard-capable, then slices are computed via vineyards updates.
95
- It is slower in general, but faster if single threaded.
96
- In particular, it is usually faster to use this backend if you want to compute the
97
- signed measure of multiple datasets in a parallel context.
98
- - Otherwise, slices are computed in parallel.
99
- It is usually faster to use this backend if not in a parallel context.
100
- - Rank: Same as Hilbert.
101
- """
102
- ## TODO : add timings in verbose
103
- if grid_conversion is not None:
104
- grid = tuple(f for f in grid_conversion)
105
- raise DeprecationWarning(
106
- """
107
- Parameter `grid_conversion` is deprecated. Use `grid` instead.
108
- Most of the time there is no conversion anymore.
109
- """
110
- )
111
-
112
- if degree is not None or len(degrees) == 0:
113
- degrees = list(degrees) + [degree]
114
- if None in degrees:
115
- assert (
116
- len(degrees) == 1
117
- ), f"Can only compute one invariant at the time. Got {degrees=}, {invariant=}."
118
- assert invariant is None or not (
119
- "hilbert" in invariant or "rank" in invariant
120
- ), f"Hilbert and Rank cannot compute `None` degree. got {degrees=}, {invariant=}."
121
- invariant = "euler"
122
- if clean is None:
123
- clean = True if None in degrees else False
124
-
125
- assert invariant is None or invariant in [
126
- "hilbert",
127
- "rank_invariant",
128
- "euler",
129
- "rank",
130
- "euler_characteristic",
131
- "hilbert_function",
132
- ]
133
-
134
- assert (
135
- not plot or filtered_complex.num_parameters == 2
136
- ), "Can only plot 2d measures."
137
-
138
- if grid is None:
139
- if not filtered_complex.is_squeezed:
140
- grid = compute_grid(
141
- filtered_complex, strategy=grid_strategy, **infer_grid_kwargs
142
- )
143
- else:
144
- grid = tuple(np.asarray(f) for f in filtered_complex.filtration_grid)
145
-
146
- if mass_default is None:
147
- mass_default = mass_default
148
- elif isinstance(mass_default, str):
149
- if mass_default == "auto":
150
- mass_default = np.array([1.1 * np.max(f) - 0.1 * np.min(f) for f in grid])
151
- elif mass_default == "inf":
152
- mass_default = np.array([np.inf] * filtered_complex.num_parameters)
153
- else:
154
- raise NotImplementedError
155
- else:
156
- mass_default = np.asarray(mass_default)
157
- assert (
158
- mass_default.ndim == 1
159
- and mass_default.shape[0] == filtered_complex.num_parameters
160
- )
161
-
162
- if not filtered_complex.is_squeezed:
163
- if verbose:
164
- print("Coarsening complex...", end="")
165
- filtered_complex_ = filtered_complex.grid_squeeze(grid)
166
- if verbose:
167
- print("Done.")
168
- else:
169
- filtered_complex_ = filtered_complex.copy()
170
-
171
- # assert filtered_complex_.is_squeezed
172
- if None not in degrees:
173
- if is_slicer(filtered_complex_) and filtered_complex_.is_minpres:
174
- pass
175
- else:
176
- max_degree = np.max(degrees) + 1
177
- if verbose:
178
- print(f"Pruning simplicies up to {max_degree}...", end="")
179
- if filtered_complex_.dimension > max_degree:
180
- filtered_complex_.prune_above_dimension(max_degree)
181
- if verbose:
182
- print("Done.")
183
-
184
- num_parameters = filtered_complex.num_parameters
185
- assert num_parameters == len(
186
- grid
187
- ), f"Number of parameter do not coincide. Got (grid) {len(grid)} and (filtered complex) {num_parameters}."
188
-
189
- if is_simplextree_multi(filtered_complex_):
190
- if num_collapses != 0:
191
- if verbose:
192
- print("Collapsing edges...", end="")
193
- filtered_complex_.collapse_edges(num_collapses)
194
- if verbose:
195
- print("Done.")
196
- if backend is not None:
197
- filtered_complex_ = mp.Slicer(filtered_complex_, vineyard=vineyard)
198
-
199
- fix_mass_default = mass_default is not None
200
- if is_slicer(filtered_complex_):
201
- if verbose:
202
- print("Input is a slicer.")
203
- if backend is not None and not filtered_complex_.is_minpres:
204
- from multipers.slicer import minimal_presentation
205
-
206
- assert (
207
- invariant != "euler"
208
- ), "Euler Characteristic cannot be speed up by a backend"
209
- # This returns a list of reduced complexes
210
- if verbose:
211
- print("Reducing complex...", end="")
212
- reduced_complex = minimal_presentation(
213
- filtered_complex_,
214
- degrees=degrees,
215
- backend=backend,
216
- vineyard=vineyard,
217
- verbose=verbose,
218
- )
219
- if verbose:
220
- print("Done.")
221
- if invariant is not None and "rank" in invariant:
222
- if verbose:
223
- print("Computing rank...", end="")
224
- sms = [
225
- _rank_from_slicer(
226
- s,
227
- degrees=[d],
228
- n_jobs=n_jobs,
229
- # grid_shape=tuple(len(g) for g in grid),
230
- zero_pad=fix_mass_default,
231
- ignore_inf = ignore_infinite_filtration_values,
232
- )[0]
233
- for s, d in zip(reduced_complex, degrees)
234
- ]
235
- fix_mass_default = False
236
- if verbose:
237
- print("Done.")
238
- else:
239
- if verbose:
240
- print("Reduced slicer. Retrieving measure from it...", end="")
241
- sms = [
242
- _signed_measure_from_slicer(
243
- s,
244
- shift=(
245
- reduced_complex.minpres_degree % 2 if d is None else d % 2
246
- ),
247
- )[0]
248
- for s, d in zip(reduced_complex, degrees)
249
- ]
250
- if verbose:
251
- print("Done.")
252
- else: # No backend
253
- if invariant is not None and "rank" in invariant:
254
- degrees = np.asarray(degrees, dtype=int)
255
- if verbose:
256
- print("Computing rank...", end="")
257
- sms = _rank_from_slicer(
258
- filtered_complex_,
259
- degrees=degrees,
260
- n_jobs=n_jobs,
261
- zero_pad=fix_mass_default,
262
- # grid_shape=tuple(len(g) for g in grid),
263
- ignore_inf = ignore_infinite_filtration_values,
264
- )
265
- fix_mass_default = False
266
- if verbose:
267
- print("Done.")
268
- elif filtered_complex_.is_minpres:
269
- if verbose:
270
- print("Reduced slicer. Retrieving measure from it...", end="")
271
- sms = [
272
- _signed_measure_from_slicer(
273
- filtered_complex_,
274
- shift=(
275
- filtered_complex_.minpres_degree % 2 if d is None else d % 2
276
- ),
277
- )[0]
278
- for d in degrees
279
- ]
280
- if verbose:
281
- print("Done.")
282
- elif (invariant is None or "euler" in invariant) and (
283
- len(degrees) == 1 and degrees[0] is None
284
- ):
285
- if verbose:
286
- print("Retrieving measure from slicer...", end="")
287
- sms = _signed_measure_from_slicer(
288
- filtered_complex_,
289
- shift=0, # no minpres
290
- )
291
- if verbose:
292
- print("Done.")
293
- else:
294
- if verbose:
295
- print("Computing Hilbert function...", end="")
296
- sms = _hilbert_signed_measure(
297
- filtered_complex_,
298
- degrees=degrees,
299
- zero_pad=fix_mass_default,
300
- n_jobs=n_jobs,
301
- verbose=verbose,
302
- ignore_inf = ignore_infinite_filtration_values,
303
- )
304
- fix_mass_default = False
305
- if verbose:
306
- print("Done.")
307
-
308
- elif is_simplextree_multi(filtered_complex_):
309
- if verbose:
310
- print("Input is a simplextree.")
311
- ## we still have a simplextree here
312
- if invariant in ["rank_invariant", "rank"]:
313
- if verbose:
314
- print("Computing rank invariant...", end="")
315
- assert (
316
- num_parameters == 2
317
- ), "Rank invariant only implemented for 2-parameter modules."
318
- assert not coordinate_measure, "Not implemented"
319
- from multipers.simplex_tree_multi import _rank_signed_measure as smri
320
-
321
- sms = smri(
322
- filtered_complex_,
323
- mass_default=mass_default,
324
- degrees=degrees,
325
- expand_collapse=expand_collapse,
326
- )
327
- fix_mass_default = False
328
- if verbose:
329
- print("Done.")
330
- elif len(degrees) == 1 and degrees[0] is None:
331
- if verbose:
332
- print("Computing Euler Characteristic...", end="")
333
- assert invariant is None or invariant in [
334
- "euler",
335
- "euler_characteristic",
336
- ], "Provide a degree to compute hilbert function."
337
- # assert not coordinate_measure, "Not implemented"
338
- from multipers.simplex_tree_multi import _euler_signed_measure
339
-
340
- sms = [
341
- _euler_signed_measure(
342
- filtered_complex_,
343
- mass_default=mass_default,
344
- verbose=verbose,
345
- )
346
- ]
347
- fix_mass_default = False
348
- if verbose:
349
- print("Done.")
350
- else:
351
- if verbose:
352
- print("Computing Hilbert Function...", end="")
353
- assert invariant is None or invariant in [
354
- "hilbert",
355
- "hilbert_function",
356
- ], "Found homological degrees for euler computation."
357
- from multipers.simplex_tree_multi import (
358
- _hilbert_signed_measure as hilbert_signed_measure,
359
- )
360
-
361
- sms = hilbert_signed_measure(
362
- filtered_complex_,
363
- degrees=degrees,
364
- mass_default=mass_default,
365
- verbose=verbose,
366
- n_jobs=n_jobs,
367
- expand_collapse=expand_collapse,
368
- )
369
- fix_mass_default = False
370
- if verbose:
371
- print("Done.")
372
- else:
373
- raise ValueError("Filtered complex has to be a SimplexTree or a Slicer.")
374
-
375
- if clean:
376
- if verbose:
377
- print("Cleaning measure...", end="")
378
- sms = clean_sms(sms)
379
- if verbose:
380
- print("Done.")
381
- if grid is not None and not coordinate_measure:
382
- if verbose:
383
- print("Pushing back the measure to the grid...", end="")
384
- sms = sms_in_grid(
385
- sms,
386
- grid=grid,
387
- mass_default=mass_default,
388
- num_parameters=num_parameters,
389
- )
390
- if verbose:
391
- print("Done.")
392
-
393
- if fix_mass_default:
394
- # TODO : some methods need to use this, this could be optimized
395
- if verbose:
396
- print("Seems that fixing mass default is necessary...", end="")
397
- sms = zero_out_sms(sms, mass_default=mass_default)
398
- if verbose:
399
- print("Done.")
400
- if plot:
401
- plot_signed_measures(sms)
402
- return sms
403
-
404
-
405
- def _signed_measure_from_scc(
406
- minimal_presentation,
407
- ) -> list[tuple[np.ndarray, np.ndarray]]:
408
- pts = np.concatenate([b[0] for b in minimal_presentation])
409
- weights = np.concatenate(
410
- [
411
- (1 - 2 * (i % 2)) * np.ones(len(b[0]))
412
- for i, b in enumerate(minimal_presentation)
413
- ]
414
- )
415
- sm = [(pts, weights)]
416
- return sm
417
-
418
-
419
- def _signed_measure_from_slicer(
420
- slicer: Slicer_type,
421
- shift: int = 0,
422
- ) -> list[tuple[np.ndarray, np.ndarray]]:
423
- assert not slicer.is_kcritical, "Not implemented for k-critical filtrations yet."
424
- pts = np.array(slicer.get_filtrations())
425
- dims = slicer.get_dimensions()
426
- if shift:
427
- dims += shift
428
- weights = 1 - 2 * (dims % 2)
429
- sm = [(pts, weights)]
430
- return sm
1
+ from collections.abc import Iterable, Sequence
2
+ from typing import Optional, Union
3
+
4
+ import numpy as np
5
+
6
+ import multipers as mp
7
+ from multipers.grids import compute_grid, sms_in_grid
8
+ from multipers.plots import plot_signed_measures
9
+ from multipers.point_measure import clean_sms, zero_out_sms
10
+ from multipers.simplex_tree_multi import (
11
+ SimplexTreeMulti_type,
12
+ _available_strategies,
13
+ is_simplextree_multi,
14
+ )
15
+ from multipers.slicer import (
16
+ Slicer_type,
17
+ _hilbert_signed_measure,
18
+ _rank_from_slicer,
19
+ is_slicer,
20
+ )
21
+
22
+
23
+ def signed_measure(
24
+ filtered_complex: Union[SimplexTreeMulti_type, Slicer_type],
25
+ degree: Optional[int] = None,
26
+ degrees: Sequence[int | None] = [],
27
+ mass_default=None,
28
+ grid_strategy: _available_strategies = "exact",
29
+ invariant: Optional[str] = None,
30
+ plot: bool = False,
31
+ verbose: bool = False,
32
+ n_jobs: int = -1,
33
+ expand_collapse: bool = False,
34
+ backend: Optional[str] = None,
35
+ thread_id: str = "",
36
+ grid: Optional[Iterable] = None,
37
+ coordinate_measure: bool = False,
38
+ num_collapses: int = 0,
39
+ clean: Optional[bool] = None,
40
+ vineyard: bool = False,
41
+ grid_conversion: Optional[Iterable] = None,
42
+ ignore_infinite_filtration_values: bool = True,
43
+ **infer_grid_kwargs,
44
+ ) -> list[tuple[np.ndarray, np.ndarray]]:
45
+ r"""
46
+ Computes the signed measures given by the decomposition of the hilbert
47
+ function or the euler characteristic, or the rank invariant.
48
+
49
+ Input
50
+ -----
51
+ - filtered_complex: given by a simplextree or a slicer.
52
+ - degree:int|None / degrees:list[int] the degrees to compute.
53
+ None represents the euler characteristic.
54
+ - mass_default: Either None, or 'auto' or 'inf', or array-like of floats.
55
+ Where to put the default mass to get a zero-mass measure.
56
+ This corresponds to zero-out the filtered complex outside of $\{ x\in \mathbb R^n \mid x\le `mass_default`\}$
57
+ - invariant: The invariant to use, either "hilbert", "rank", or "euler".
58
+ - plot:bool, plots the computed measures if true.
59
+ - n_jobs:int, number of jobs. Defaults to #cpu.
60
+ - verbose:bool, prints c++ logs.
61
+ - expand_collapse: when the input is a simplextree,
62
+ only expands the complex when computing 1-dimensional slices.
63
+ Meant to reduce memory footprint at some computational expense.
64
+ - backend:str reduces first the filtered complex using an external library `backend`,
65
+ see ``backend`` in :func:`multipers.io.reduce_complex`.
66
+ - grid: If given, the computations will be done on the restriction of the filtered complex to this grid.
67
+ It can also be used for auto-differentiation, i.e., if the grid is a list of pytorch tensors,
68
+ then the output measure will be pytorch-differentiable.
69
+ - grid_strategy: If not squeezed yet, and no grid is given,
70
+ the strategy to coarsen the grid; see ``strategy`` in :func:`multipers.grids.compute_grid`.
71
+ - coordinate_measure: bool, if True, compute the signed measure as a coordinates given in grid.
72
+ - num_collapses: int, if `filtered_complex` is a simplextree, does some collapses if possible.
73
+ - clean: if True, reduces the measure. It is not necessary in general.
74
+ - ignore_infinite_filtration_values: Backend optimization.
75
+
76
+ Output
77
+ ------
78
+
79
+ `[signed_measure_of_degree for degree in degrees]`
80
+ with `signed_measure_of_degree` of the form `(dirac location, dirac weights)`.
81
+
82
+ Notes on computational backends
83
+ -------------------------------
84
+ There are several backends for each of these computations.
85
+ The backend for computations used can be displayed with `verbose=True`, use it!
86
+ Also note that if `backend` is given, then the input will be converted to a slicer.
87
+ - Euler: is always computed by summing the weights of the simplices
88
+ - Hilbert: is computed by computing persistence on slices, and a Möbius inversion,
89
+ unless the detected input is a minimal presentation (i.e., `filtered_complex.is_minpres`),
90
+ which in that case, doesn't need any computation.
91
+ - If the input is a simplextree, this is done via a the standard Gudhi implementation,
92
+ with parallel (TBB) computations of slices.
93
+ - If the input is a slicer then
94
+ - If the input is vineyard-capable, then slices are computed via vineyards updates.
95
+ It is slower in general, but faster if single threaded.
96
+ In particular, it is usually faster to use this backend if you want to compute the
97
+ signed measure of multiple datasets in a parallel context.
98
+ - Otherwise, slices are computed in parallel.
99
+ It is usually faster to use this backend if not in a parallel context.
100
+ - Rank: Same as Hilbert.
101
+ """
102
+ ## TODO : add timings in verbose
103
+ if grid_conversion is not None:
104
+ grid = tuple(f for f in grid_conversion)
105
+ raise DeprecationWarning(
106
+ """
107
+ Parameter `grid_conversion` is deprecated. Use `grid` instead.
108
+ Most of the time there is no conversion anymore.
109
+ """
110
+ )
111
+
112
+ if degree is not None or len(degrees) == 0:
113
+ degrees = list(degrees) + [degree]
114
+ if None in degrees:
115
+ assert (
116
+ len(degrees) == 1
117
+ ), f"Can only compute one invariant at the time. Got {degrees=}, {invariant=}."
118
+ assert invariant is None or not (
119
+ "hilbert" in invariant or "rank" in invariant
120
+ ), f"Hilbert and Rank cannot compute `None` degree. got {degrees=}, {invariant=}."
121
+ invariant = "euler"
122
+ if clean is None:
123
+ clean = True if None in degrees else False
124
+
125
+ assert invariant is None or invariant in [
126
+ "hilbert",
127
+ "rank_invariant",
128
+ "euler",
129
+ "rank",
130
+ "euler_characteristic",
131
+ "hilbert_function",
132
+ ]
133
+
134
+ assert (
135
+ not plot or filtered_complex.num_parameters == 2
136
+ ), "Can only plot 2d measures."
137
+
138
+ if grid is None:
139
+ if not filtered_complex.is_squeezed:
140
+ grid = compute_grid(
141
+ filtered_complex, strategy=grid_strategy, **infer_grid_kwargs
142
+ )
143
+ else:
144
+ grid = tuple(np.asarray(f) for f in filtered_complex.filtration_grid)
145
+
146
+ if mass_default is None:
147
+ mass_default = mass_default
148
+ elif isinstance(mass_default, str):
149
+ if mass_default == "auto":
150
+ mass_default = np.array([1.1 * np.max(f) - 0.1 * np.min(f) for f in grid])
151
+ elif mass_default == "inf":
152
+ mass_default = np.array([np.inf] * filtered_complex.num_parameters)
153
+ else:
154
+ raise NotImplementedError
155
+ else:
156
+ mass_default = np.asarray(mass_default)
157
+ assert (
158
+ mass_default.ndim == 1
159
+ and mass_default.shape[0] == filtered_complex.num_parameters
160
+ )
161
+
162
+ if not filtered_complex.is_squeezed:
163
+ if verbose:
164
+ print("Coarsening complex...", end="")
165
+ filtered_complex_ = filtered_complex.grid_squeeze(grid)
166
+ if verbose:
167
+ print("Done.")
168
+ else:
169
+ filtered_complex_ = filtered_complex.copy()
170
+
171
+ # assert filtered_complex_.is_squeezed
172
+ if None not in degrees:
173
+ if is_slicer(filtered_complex_) and filtered_complex_.is_minpres:
174
+ pass
175
+ else:
176
+ max_degree = np.max(degrees) + 1
177
+ if verbose:
178
+ print(f"Pruning simplicies up to {max_degree}...", end="")
179
+ if filtered_complex_.dimension > max_degree:
180
+ filtered_complex_.prune_above_dimension(max_degree)
181
+ if verbose:
182
+ print("Done.")
183
+
184
+ num_parameters = filtered_complex.num_parameters
185
+ assert num_parameters == len(
186
+ grid
187
+ ), f"Number of parameter do not coincide. Got (grid) {len(grid)} and (filtered complex) {num_parameters}."
188
+
189
+ if is_simplextree_multi(filtered_complex_):
190
+ if num_collapses != 0:
191
+ if verbose:
192
+ print("Collapsing edges...", end="")
193
+ filtered_complex_.collapse_edges(num_collapses)
194
+ if verbose:
195
+ print("Done.")
196
+ if backend is not None:
197
+ filtered_complex_ = mp.Slicer(filtered_complex_, vineyard=vineyard)
198
+
199
+ fix_mass_default = mass_default is not None
200
+ if is_slicer(filtered_complex_):
201
+ if verbose:
202
+ print("Input is a slicer.")
203
+ if backend is not None and not filtered_complex_.is_minpres:
204
+ from multipers.slicer import minimal_presentation
205
+
206
+ assert (
207
+ invariant != "euler"
208
+ ), "Euler Characteristic cannot be speed up by a backend"
209
+ # This returns a list of reduced complexes
210
+ if verbose:
211
+ print("Reducing complex...", end="")
212
+ reduced_complex = minimal_presentation(
213
+ filtered_complex_,
214
+ degrees=degrees,
215
+ backend=backend,
216
+ vineyard=vineyard,
217
+ verbose=verbose,
218
+ )
219
+ if verbose:
220
+ print("Done.")
221
+ if invariant is not None and "rank" in invariant:
222
+ if verbose:
223
+ print("Computing rank...", end="")
224
+ sms = [
225
+ _rank_from_slicer(
226
+ s,
227
+ degrees=[d],
228
+ n_jobs=n_jobs,
229
+ # grid_shape=tuple(len(g) for g in grid),
230
+ zero_pad=fix_mass_default,
231
+ ignore_inf=ignore_infinite_filtration_values,
232
+ )[0]
233
+ for s, d in zip(reduced_complex, degrees)
234
+ ]
235
+ fix_mass_default = False
236
+ if verbose:
237
+ print("Done.")
238
+ else:
239
+ if verbose:
240
+ print("Reduced slicer. Retrieving measure from it...", end="")
241
+ sms = [
242
+ _signed_measure_from_slicer(
243
+ s,
244
+ shift=(
245
+ reduced_complex.minpres_degree % 2 if d is None else d % 2
246
+ ),
247
+ )[0]
248
+ for s, d in zip(reduced_complex, degrees)
249
+ ]
250
+ if verbose:
251
+ print("Done.")
252
+ else: # No backend
253
+ if invariant is not None and "rank" in invariant:
254
+ degrees = np.asarray(degrees, dtype=int)
255
+ if verbose:
256
+ print("Computing rank...", end="")
257
+ sms = _rank_from_slicer(
258
+ filtered_complex_,
259
+ degrees=degrees,
260
+ n_jobs=n_jobs,
261
+ zero_pad=fix_mass_default,
262
+ # grid_shape=tuple(len(g) for g in grid),
263
+ ignore_inf=ignore_infinite_filtration_values,
264
+ )
265
+ fix_mass_default = False
266
+ if verbose:
267
+ print("Done.")
268
+ elif filtered_complex_.is_minpres:
269
+ if verbose:
270
+ print("Reduced slicer. Retrieving measure from it...", end="")
271
+ sms = [
272
+ _signed_measure_from_slicer(
273
+ filtered_complex_,
274
+ shift=(
275
+ filtered_complex_.minpres_degree % 2 if d is None else d % 2
276
+ ),
277
+ )[0]
278
+ for d in degrees
279
+ ]
280
+ if verbose:
281
+ print("Done.")
282
+ elif (invariant is None or "euler" in invariant) and (
283
+ len(degrees) == 1 and degrees[0] is None
284
+ ):
285
+ if verbose:
286
+ print("Retrieving measure from slicer...", end="")
287
+ sms = _signed_measure_from_slicer(
288
+ filtered_complex_,
289
+ shift=0, # no minpres
290
+ )
291
+ if verbose:
292
+ print("Done.")
293
+ else:
294
+ if verbose:
295
+ print("Computing Hilbert function...", end="")
296
+ sms = _hilbert_signed_measure(
297
+ filtered_complex_,
298
+ degrees=degrees,
299
+ zero_pad=fix_mass_default,
300
+ n_jobs=n_jobs,
301
+ verbose=verbose,
302
+ ignore_inf=ignore_infinite_filtration_values,
303
+ )
304
+ fix_mass_default = False
305
+ if verbose:
306
+ print("Done.")
307
+
308
+ elif is_simplextree_multi(filtered_complex_):
309
+ if verbose:
310
+ print("Input is a simplextree.")
311
+ ## we still have a simplextree here
312
+ if invariant in ["rank_invariant", "rank"]:
313
+ if verbose:
314
+ print("Computing rank invariant...", end="")
315
+ assert (
316
+ num_parameters == 2
317
+ ), "Rank invariant only implemented for 2-parameter modules."
318
+ assert not coordinate_measure, "Not implemented"
319
+ from multipers.simplex_tree_multi import _rank_signed_measure as smri
320
+
321
+ sms = smri(
322
+ filtered_complex_,
323
+ mass_default=mass_default,
324
+ degrees=degrees,
325
+ expand_collapse=expand_collapse,
326
+ )
327
+ fix_mass_default = False
328
+ if verbose:
329
+ print("Done.")
330
+ elif len(degrees) == 1 and degrees[0] is None:
331
+ if verbose:
332
+ print("Computing Euler Characteristic...", end="")
333
+ assert invariant is None or invariant in [
334
+ "euler",
335
+ "euler_characteristic",
336
+ ], "Provide a degree to compute hilbert function."
337
+ # assert not coordinate_measure, "Not implemented"
338
+ from multipers.simplex_tree_multi import _euler_signed_measure
339
+
340
+ sms = [
341
+ _euler_signed_measure(
342
+ filtered_complex_,
343
+ mass_default=mass_default,
344
+ verbose=verbose,
345
+ )
346
+ ]
347
+ fix_mass_default = False
348
+ if verbose:
349
+ print("Done.")
350
+ else:
351
+ if verbose:
352
+ print("Computing Hilbert Function...", end="")
353
+ assert invariant is None or invariant in [
354
+ "hilbert",
355
+ "hilbert_function",
356
+ ], "Found homological degrees for euler computation."
357
+ from multipers.simplex_tree_multi import (
358
+ _hilbert_signed_measure as hilbert_signed_measure,
359
+ )
360
+
361
+ sms = hilbert_signed_measure(
362
+ filtered_complex_,
363
+ degrees=degrees,
364
+ mass_default=mass_default,
365
+ verbose=verbose,
366
+ n_jobs=n_jobs,
367
+ expand_collapse=expand_collapse,
368
+ )
369
+ fix_mass_default = False
370
+ if verbose:
371
+ print("Done.")
372
+ else:
373
+ raise ValueError("Filtered complex has to be a SimplexTree or a Slicer.")
374
+
375
+ if clean:
376
+ if verbose:
377
+ print("Cleaning measure...", end="")
378
+ sms = clean_sms(sms)
379
+ if verbose:
380
+ print("Done.")
381
+ if grid is not None and not coordinate_measure:
382
+ if verbose:
383
+ print("Pushing back the measure to the grid...", end="")
384
+ sms = sms_in_grid(
385
+ sms,
386
+ grid=grid,
387
+ mass_default=mass_default,
388
+ num_parameters=num_parameters,
389
+ )
390
+ if verbose:
391
+ print("Done.")
392
+
393
+ if fix_mass_default:
394
+ # TODO : some methods need to use this, this could be optimized
395
+ if verbose:
396
+ print("Seems that fixing mass default is necessary...", end="")
397
+ sms = zero_out_sms(sms, mass_default=mass_default)
398
+ if verbose:
399
+ print("Done.")
400
+ if plot:
401
+ plot_signed_measures(sms)
402
+ return sms
403
+
404
+
405
+ def _signed_measure_from_scc(
406
+ minimal_presentation,
407
+ ) -> list[tuple[np.ndarray, np.ndarray]]:
408
+ pts = np.concatenate([b[0] for b in minimal_presentation])
409
+ weights = np.concatenate(
410
+ [
411
+ (1 - 2 * (i % 2)) * np.ones(len(b[0]))
412
+ for i, b in enumerate(minimal_presentation)
413
+ ]
414
+ )
415
+ sm = [(pts, weights)]
416
+ return sm
417
+
418
+
419
+ def _signed_measure_from_slicer(
420
+ slicer: Slicer_type,
421
+ shift: int = 0,
422
+ ) -> list[tuple[np.ndarray, np.ndarray]]:
423
+ assert not slicer.is_kcritical, "Not implemented for k-critical filtrations yet."
424
+ pts = np.array(slicer.get_filtrations())
425
+ dims = slicer.get_dimensions()
426
+ if shift:
427
+ dims += shift
428
+ weights = 1 - 2 * (dims % 2)
429
+ sm = [(pts, weights)]
430
+ return sm