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.
- multipers/__init__.py +33 -31
- multipers/_signed_measure_meta.py +430 -430
- multipers/_slicer_meta.py +211 -212
- multipers/data/MOL2.py +458 -458
- multipers/data/UCR.py +18 -18
- multipers/data/graphs.py +466 -466
- multipers/data/immuno_regions.py +27 -27
- multipers/data/pytorch2simplextree.py +90 -90
- multipers/data/shape3d.py +101 -101
- multipers/data/synthetic.py +113 -111
- multipers/distances.py +198 -198
- multipers/filtration_conversions.pxd.tp +84 -84
- multipers/filtrations/__init__.py +18 -0
- multipers/{ml/convolutions.py → filtrations/density.py} +563 -520
- multipers/filtrations/filtrations.py +289 -0
- multipers/filtrations.pxd +224 -224
- multipers/function_rips.cp311-win_amd64.pyd +0 -0
- multipers/function_rips.pyx +105 -105
- multipers/grids.cp311-win_amd64.pyd +0 -0
- multipers/grids.pyx +350 -350
- multipers/gudhi/Persistence_slices_interface.h +132 -132
- multipers/gudhi/Simplex_tree_interface.h +239 -245
- multipers/gudhi/Simplex_tree_multi_interface.h +516 -561
- multipers/gudhi/cubical_to_boundary.h +59 -59
- multipers/gudhi/gudhi/Bitmap_cubical_complex.h +450 -450
- multipers/gudhi/gudhi/Bitmap_cubical_complex_base.h +1070 -1070
- multipers/gudhi/gudhi/Bitmap_cubical_complex_periodic_boundary_conditions_base.h +579 -579
- multipers/gudhi/gudhi/Debug_utils.h +45 -45
- multipers/gudhi/gudhi/Fields/Multi_field.h +484 -484
- multipers/gudhi/gudhi/Fields/Multi_field_operators.h +455 -455
- multipers/gudhi/gudhi/Fields/Multi_field_shared.h +450 -450
- multipers/gudhi/gudhi/Fields/Multi_field_small.h +531 -531
- multipers/gudhi/gudhi/Fields/Multi_field_small_operators.h +507 -507
- multipers/gudhi/gudhi/Fields/Multi_field_small_shared.h +531 -531
- multipers/gudhi/gudhi/Fields/Z2_field.h +355 -355
- multipers/gudhi/gudhi/Fields/Z2_field_operators.h +376 -376
- multipers/gudhi/gudhi/Fields/Zp_field.h +420 -420
- multipers/gudhi/gudhi/Fields/Zp_field_operators.h +400 -400
- multipers/gudhi/gudhi/Fields/Zp_field_shared.h +418 -418
- multipers/gudhi/gudhi/Flag_complex_edge_collapser.h +337 -337
- multipers/gudhi/gudhi/Matrix.h +2107 -2107
- multipers/gudhi/gudhi/Multi_critical_filtration.h +1038 -1038
- multipers/gudhi/gudhi/Multi_persistence/Box.h +171 -171
- multipers/gudhi/gudhi/Multi_persistence/Line.h +282 -282
- multipers/gudhi/gudhi/Off_reader.h +173 -173
- multipers/gudhi/gudhi/One_critical_filtration.h +1433 -1431
- multipers/gudhi/gudhi/Persistence_matrix/Base_matrix.h +769 -769
- multipers/gudhi/gudhi/Persistence_matrix/Base_matrix_with_column_compression.h +686 -686
- multipers/gudhi/gudhi/Persistence_matrix/Boundary_matrix.h +842 -842
- multipers/gudhi/gudhi/Persistence_matrix/Chain_matrix.h +1350 -1350
- multipers/gudhi/gudhi/Persistence_matrix/Id_to_index_overlay.h +1105 -1105
- multipers/gudhi/gudhi/Persistence_matrix/Position_to_index_overlay.h +859 -859
- multipers/gudhi/gudhi/Persistence_matrix/RU_matrix.h +910 -910
- multipers/gudhi/gudhi/Persistence_matrix/allocators/entry_constructors.h +139 -139
- multipers/gudhi/gudhi/Persistence_matrix/base_pairing.h +230 -230
- multipers/gudhi/gudhi/Persistence_matrix/base_swap.h +211 -211
- multipers/gudhi/gudhi/Persistence_matrix/boundary_cell_position_to_id_mapper.h +60 -60
- multipers/gudhi/gudhi/Persistence_matrix/boundary_face_position_to_id_mapper.h +60 -60
- multipers/gudhi/gudhi/Persistence_matrix/chain_pairing.h +136 -136
- multipers/gudhi/gudhi/Persistence_matrix/chain_rep_cycles.h +190 -190
- multipers/gudhi/gudhi/Persistence_matrix/chain_vine_swap.h +616 -616
- multipers/gudhi/gudhi/Persistence_matrix/columns/chain_column_extra_properties.h +150 -150
- multipers/gudhi/gudhi/Persistence_matrix/columns/column_dimension_holder.h +106 -106
- multipers/gudhi/gudhi/Persistence_matrix/columns/column_utilities.h +219 -219
- multipers/gudhi/gudhi/Persistence_matrix/columns/entry_types.h +327 -327
- multipers/gudhi/gudhi/Persistence_matrix/columns/heap_column.h +1140 -1140
- multipers/gudhi/gudhi/Persistence_matrix/columns/intrusive_list_column.h +934 -934
- multipers/gudhi/gudhi/Persistence_matrix/columns/intrusive_set_column.h +934 -934
- multipers/gudhi/gudhi/Persistence_matrix/columns/list_column.h +980 -980
- multipers/gudhi/gudhi/Persistence_matrix/columns/naive_vector_column.h +1092 -1092
- multipers/gudhi/gudhi/Persistence_matrix/columns/row_access.h +192 -192
- multipers/gudhi/gudhi/Persistence_matrix/columns/set_column.h +921 -921
- multipers/gudhi/gudhi/Persistence_matrix/columns/small_vector_column.h +1093 -1093
- multipers/gudhi/gudhi/Persistence_matrix/columns/unordered_set_column.h +1012 -1012
- multipers/gudhi/gudhi/Persistence_matrix/columns/vector_column.h +1244 -1244
- multipers/gudhi/gudhi/Persistence_matrix/matrix_dimension_holders.h +186 -186
- multipers/gudhi/gudhi/Persistence_matrix/matrix_row_access.h +164 -164
- multipers/gudhi/gudhi/Persistence_matrix/ru_pairing.h +156 -156
- multipers/gudhi/gudhi/Persistence_matrix/ru_rep_cycles.h +376 -376
- multipers/gudhi/gudhi/Persistence_matrix/ru_vine_swap.h +540 -540
- multipers/gudhi/gudhi/Persistent_cohomology/Field_Zp.h +118 -118
- multipers/gudhi/gudhi/Persistent_cohomology/Multi_field.h +173 -173
- multipers/gudhi/gudhi/Persistent_cohomology/Persistent_cohomology_column.h +128 -128
- multipers/gudhi/gudhi/Persistent_cohomology.h +745 -745
- multipers/gudhi/gudhi/Points_off_io.h +171 -171
- multipers/gudhi/gudhi/Simple_object_pool.h +69 -69
- multipers/gudhi/gudhi/Simplex_tree/Simplex_tree_iterators.h +463 -463
- multipers/gudhi/gudhi/Simplex_tree/Simplex_tree_node_explicit_storage.h +83 -83
- multipers/gudhi/gudhi/Simplex_tree/Simplex_tree_siblings.h +106 -106
- multipers/gudhi/gudhi/Simplex_tree/Simplex_tree_star_simplex_iterators.h +277 -277
- multipers/gudhi/gudhi/Simplex_tree/hooks_simplex_base.h +62 -62
- multipers/gudhi/gudhi/Simplex_tree/indexing_tag.h +27 -27
- multipers/gudhi/gudhi/Simplex_tree/serialization_utils.h +62 -62
- multipers/gudhi/gudhi/Simplex_tree/simplex_tree_options.h +157 -157
- multipers/gudhi/gudhi/Simplex_tree.h +2794 -2794
- multipers/gudhi/gudhi/Simplex_tree_multi.h +152 -163
- multipers/gudhi/gudhi/distance_functions.h +62 -62
- multipers/gudhi/gudhi/graph_simplicial_complex.h +104 -104
- multipers/gudhi/gudhi/persistence_interval.h +253 -253
- multipers/gudhi/gudhi/persistence_matrix_options.h +170 -170
- multipers/gudhi/gudhi/reader_utils.h +367 -367
- multipers/gudhi/mma_interface_coh.h +256 -255
- multipers/gudhi/mma_interface_h0.h +223 -231
- multipers/gudhi/mma_interface_matrix.h +291 -282
- multipers/gudhi/naive_merge_tree.h +536 -575
- multipers/gudhi/scc_io.h +310 -289
- multipers/gudhi/truc.h +957 -888
- multipers/io.cp311-win_amd64.pyd +0 -0
- multipers/io.pyx +714 -711
- multipers/ml/accuracies.py +90 -90
- multipers/ml/invariants_with_persistable.py +79 -79
- multipers/ml/kernels.py +176 -176
- multipers/ml/mma.py +713 -714
- multipers/ml/one.py +472 -472
- multipers/ml/point_clouds.py +352 -346
- multipers/ml/signed_measures.py +1589 -1589
- multipers/ml/sliced_wasserstein.py +461 -461
- multipers/ml/tools.py +113 -113
- multipers/mma_structures.cp311-win_amd64.pyd +0 -0
- multipers/mma_structures.pxd +127 -127
- multipers/mma_structures.pyx +4 -8
- multipers/mma_structures.pyx.tp +1083 -1085
- multipers/multi_parameter_rank_invariant/diff_helpers.h +84 -93
- multipers/multi_parameter_rank_invariant/euler_characteristic.h +97 -97
- multipers/multi_parameter_rank_invariant/function_rips.h +322 -322
- multipers/multi_parameter_rank_invariant/hilbert_function.h +769 -769
- multipers/multi_parameter_rank_invariant/persistence_slices.h +148 -148
- multipers/multi_parameter_rank_invariant/rank_invariant.h +369 -369
- multipers/multiparameter_edge_collapse.py +41 -41
- multipers/multiparameter_module_approximation/approximation.h +2298 -2295
- multipers/multiparameter_module_approximation/combinatory.h +129 -129
- multipers/multiparameter_module_approximation/debug.h +107 -107
- multipers/multiparameter_module_approximation/format_python-cpp.h +286 -286
- multipers/multiparameter_module_approximation/heap_column.h +238 -238
- multipers/multiparameter_module_approximation/images.h +79 -79
- multipers/multiparameter_module_approximation/list_column.h +174 -174
- multipers/multiparameter_module_approximation/list_column_2.h +232 -232
- multipers/multiparameter_module_approximation/ru_matrix.h +347 -347
- multipers/multiparameter_module_approximation/set_column.h +135 -135
- multipers/multiparameter_module_approximation/structure_higher_dim_barcode.h +36 -36
- multipers/multiparameter_module_approximation/unordered_set_column.h +166 -166
- multipers/multiparameter_module_approximation/utilities.h +403 -419
- multipers/multiparameter_module_approximation/vector_column.h +223 -223
- multipers/multiparameter_module_approximation/vector_matrix.h +331 -331
- multipers/multiparameter_module_approximation/vineyards.h +464 -464
- multipers/multiparameter_module_approximation/vineyards_trajectories.h +649 -649
- multipers/multiparameter_module_approximation.cp311-win_amd64.pyd +0 -0
- multipers/multiparameter_module_approximation.pyx +218 -217
- multipers/pickle.py +90 -53
- multipers/plots.py +342 -334
- multipers/point_measure.cp311-win_amd64.pyd +0 -0
- multipers/point_measure.pyx +322 -320
- multipers/simplex_tree_multi.cp311-win_amd64.pyd +0 -0
- multipers/simplex_tree_multi.pxd +133 -133
- multipers/simplex_tree_multi.pyx +115 -48
- multipers/simplex_tree_multi.pyx.tp +1947 -1935
- multipers/slicer.cp311-win_amd64.pyd +0 -0
- multipers/slicer.pxd +301 -120
- multipers/slicer.pxd.tp +218 -214
- multipers/slicer.pyx +1570 -507
- multipers/slicer.pyx.tp +931 -914
- multipers/tensor/tensor.h +672 -672
- multipers/tensor.pxd +13 -13
- multipers/test.pyx +44 -44
- multipers/tests/__init__.py +57 -57
- multipers/torch/diff_grids.py +217 -217
- multipers/torch/rips_density.py +310 -304
- {multipers-2.2.3.dist-info → multipers-2.3.1.dist-info}/LICENSE +21 -21
- {multipers-2.2.3.dist-info → multipers-2.3.1.dist-info}/METADATA +21 -11
- multipers-2.3.1.dist-info/RECORD +182 -0
- {multipers-2.2.3.dist-info → multipers-2.3.1.dist-info}/WHEEL +1 -1
- multipers/tests/test_diff_helper.py +0 -73
- multipers/tests/test_hilbert_function.py +0 -82
- multipers/tests/test_mma.py +0 -83
- multipers/tests/test_point_clouds.py +0 -49
- multipers/tests/test_python-cpp_conversion.py +0 -82
- multipers/tests/test_signed_betti.py +0 -181
- multipers/tests/test_signed_measure.py +0 -89
- multipers/tests/test_simplextreemulti.py +0 -221
- multipers/tests/test_slicer.py +0 -221
- multipers-2.2.3.dist-info/RECORD +0 -189
- {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
|
|
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
|
|
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
|
|
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
|