multipers 2.4.0b1__cp312-cp312-macosx_11_0_arm64.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.
- multipers/.dylibs/libboost_timer.dylib +0 -0
- multipers/.dylibs/libc++.1.0.dylib +0 -0
- multipers/.dylibs/libtbb.12.17.dylib +0 -0
- multipers/__init__.py +33 -0
- multipers/_signed_measure_meta.py +426 -0
- multipers/_slicer_meta.py +231 -0
- multipers/array_api/__init__.py +62 -0
- multipers/array_api/numpy.py +124 -0
- multipers/array_api/torch.py +133 -0
- multipers/data/MOL2.py +458 -0
- multipers/data/UCR.py +18 -0
- multipers/data/__init__.py +1 -0
- multipers/data/graphs.py +466 -0
- multipers/data/immuno_regions.py +27 -0
- multipers/data/minimal_presentation_to_st_bf.py +0 -0
- multipers/data/pytorch2simplextree.py +91 -0
- multipers/data/shape3d.py +101 -0
- multipers/data/synthetic.py +113 -0
- multipers/distances.py +202 -0
- multipers/filtration_conversions.pxd +736 -0
- multipers/filtration_conversions.pxd.tp +226 -0
- multipers/filtrations/__init__.py +21 -0
- multipers/filtrations/density.py +529 -0
- multipers/filtrations/filtrations.py +480 -0
- multipers/filtrations.pxd +534 -0
- multipers/filtrations.pxd.tp +332 -0
- multipers/function_rips.cpython-312-darwin.so +0 -0
- multipers/function_rips.pyx +104 -0
- multipers/grids.cpython-312-darwin.so +0 -0
- multipers/grids.pyx +538 -0
- multipers/gudhi/Persistence_slices_interface.h +213 -0
- multipers/gudhi/Simplex_tree_interface.h +274 -0
- multipers/gudhi/Simplex_tree_multi_interface.h +648 -0
- multipers/gudhi/gudhi/Bitmap_cubical_complex.h +450 -0
- multipers/gudhi/gudhi/Bitmap_cubical_complex_base.h +1070 -0
- multipers/gudhi/gudhi/Bitmap_cubical_complex_periodic_boundary_conditions_base.h +579 -0
- multipers/gudhi/gudhi/Debug_utils.h +52 -0
- multipers/gudhi/gudhi/Degree_rips_bifiltration.h +2307 -0
- multipers/gudhi/gudhi/Dynamic_multi_parameter_filtration.h +2524 -0
- multipers/gudhi/gudhi/Fields/Multi_field.h +453 -0
- multipers/gudhi/gudhi/Fields/Multi_field_operators.h +460 -0
- multipers/gudhi/gudhi/Fields/Multi_field_shared.h +444 -0
- multipers/gudhi/gudhi/Fields/Multi_field_small.h +584 -0
- multipers/gudhi/gudhi/Fields/Multi_field_small_operators.h +490 -0
- multipers/gudhi/gudhi/Fields/Multi_field_small_shared.h +580 -0
- multipers/gudhi/gudhi/Fields/Z2_field.h +391 -0
- multipers/gudhi/gudhi/Fields/Z2_field_operators.h +389 -0
- multipers/gudhi/gudhi/Fields/Zp_field.h +493 -0
- multipers/gudhi/gudhi/Fields/Zp_field_operators.h +384 -0
- multipers/gudhi/gudhi/Fields/Zp_field_shared.h +492 -0
- multipers/gudhi/gudhi/Flag_complex_edge_collapser.h +337 -0
- multipers/gudhi/gudhi/Matrix.h +2200 -0
- multipers/gudhi/gudhi/Multi_filtration/Multi_parameter_generator.h +1712 -0
- multipers/gudhi/gudhi/Multi_filtration/multi_filtration_conversions.h +237 -0
- multipers/gudhi/gudhi/Multi_filtration/multi_filtration_utils.h +225 -0
- multipers/gudhi/gudhi/Multi_parameter_filtered_complex.h +485 -0
- multipers/gudhi/gudhi/Multi_parameter_filtration.h +2643 -0
- multipers/gudhi/gudhi/Multi_persistence/Box.h +233 -0
- multipers/gudhi/gudhi/Multi_persistence/Line.h +309 -0
- multipers/gudhi/gudhi/Multi_persistence/Multi_parameter_filtered_complex_pcoh_interface.h +268 -0
- multipers/gudhi/gudhi/Multi_persistence/Persistence_interface_cohomology.h +159 -0
- multipers/gudhi/gudhi/Multi_persistence/Persistence_interface_matrix.h +463 -0
- multipers/gudhi/gudhi/Multi_persistence/Point.h +853 -0
- multipers/gudhi/gudhi/Off_reader.h +173 -0
- multipers/gudhi/gudhi/Persistence_matrix/Base_matrix.h +834 -0
- multipers/gudhi/gudhi/Persistence_matrix/Base_matrix_with_column_compression.h +838 -0
- multipers/gudhi/gudhi/Persistence_matrix/Boundary_matrix.h +833 -0
- multipers/gudhi/gudhi/Persistence_matrix/Chain_matrix.h +1367 -0
- multipers/gudhi/gudhi/Persistence_matrix/Id_to_index_overlay.h +1157 -0
- multipers/gudhi/gudhi/Persistence_matrix/Position_to_index_overlay.h +869 -0
- multipers/gudhi/gudhi/Persistence_matrix/RU_matrix.h +905 -0
- multipers/gudhi/gudhi/Persistence_matrix/allocators/entry_constructors.h +122 -0
- multipers/gudhi/gudhi/Persistence_matrix/base_pairing.h +260 -0
- multipers/gudhi/gudhi/Persistence_matrix/base_swap.h +288 -0
- multipers/gudhi/gudhi/Persistence_matrix/chain_pairing.h +170 -0
- multipers/gudhi/gudhi/Persistence_matrix/chain_rep_cycles.h +247 -0
- multipers/gudhi/gudhi/Persistence_matrix/chain_vine_swap.h +571 -0
- multipers/gudhi/gudhi/Persistence_matrix/columns/chain_column_extra_properties.h +182 -0
- multipers/gudhi/gudhi/Persistence_matrix/columns/column_dimension_holder.h +130 -0
- multipers/gudhi/gudhi/Persistence_matrix/columns/column_utilities.h +235 -0
- multipers/gudhi/gudhi/Persistence_matrix/columns/entry_types.h +312 -0
- multipers/gudhi/gudhi/Persistence_matrix/columns/heap_column.h +1092 -0
- multipers/gudhi/gudhi/Persistence_matrix/columns/intrusive_list_column.h +923 -0
- multipers/gudhi/gudhi/Persistence_matrix/columns/intrusive_set_column.h +914 -0
- multipers/gudhi/gudhi/Persistence_matrix/columns/list_column.h +930 -0
- multipers/gudhi/gudhi/Persistence_matrix/columns/naive_vector_column.h +1071 -0
- multipers/gudhi/gudhi/Persistence_matrix/columns/row_access.h +203 -0
- multipers/gudhi/gudhi/Persistence_matrix/columns/set_column.h +886 -0
- multipers/gudhi/gudhi/Persistence_matrix/columns/unordered_set_column.h +984 -0
- multipers/gudhi/gudhi/Persistence_matrix/columns/vector_column.h +1213 -0
- multipers/gudhi/gudhi/Persistence_matrix/index_mapper.h +58 -0
- multipers/gudhi/gudhi/Persistence_matrix/matrix_dimension_holders.h +227 -0
- multipers/gudhi/gudhi/Persistence_matrix/matrix_row_access.h +200 -0
- multipers/gudhi/gudhi/Persistence_matrix/ru_pairing.h +166 -0
- multipers/gudhi/gudhi/Persistence_matrix/ru_rep_cycles.h +319 -0
- multipers/gudhi/gudhi/Persistence_matrix/ru_vine_swap.h +562 -0
- multipers/gudhi/gudhi/Persistence_on_a_line.h +152 -0
- multipers/gudhi/gudhi/Persistence_on_rectangle.h +617 -0
- multipers/gudhi/gudhi/Persistent_cohomology/Field_Zp.h +118 -0
- multipers/gudhi/gudhi/Persistent_cohomology/Multi_field.h +173 -0
- multipers/gudhi/gudhi/Persistent_cohomology/Persistent_cohomology_column.h +128 -0
- multipers/gudhi/gudhi/Persistent_cohomology.h +769 -0
- multipers/gudhi/gudhi/Points_off_io.h +171 -0
- multipers/gudhi/gudhi/Projective_cover_kernel.h +379 -0
- multipers/gudhi/gudhi/Simple_object_pool.h +69 -0
- multipers/gudhi/gudhi/Simplex_tree/Simplex_tree_iterators.h +559 -0
- multipers/gudhi/gudhi/Simplex_tree/Simplex_tree_node_explicit_storage.h +83 -0
- multipers/gudhi/gudhi/Simplex_tree/Simplex_tree_siblings.h +121 -0
- multipers/gudhi/gudhi/Simplex_tree/Simplex_tree_star_simplex_iterators.h +277 -0
- multipers/gudhi/gudhi/Simplex_tree/filtration_value_utils.h +155 -0
- multipers/gudhi/gudhi/Simplex_tree/hooks_simplex_base.h +62 -0
- multipers/gudhi/gudhi/Simplex_tree/indexing_tag.h +27 -0
- multipers/gudhi/gudhi/Simplex_tree/serialization_utils.h +60 -0
- multipers/gudhi/gudhi/Simplex_tree/simplex_tree_options.h +105 -0
- multipers/gudhi/gudhi/Simplex_tree.h +3170 -0
- multipers/gudhi/gudhi/Slicer.h +848 -0
- multipers/gudhi/gudhi/Thread_safe_slicer.h +393 -0
- multipers/gudhi/gudhi/distance_functions.h +62 -0
- multipers/gudhi/gudhi/graph_simplicial_complex.h +104 -0
- multipers/gudhi/gudhi/multi_simplex_tree_helpers.h +147 -0
- multipers/gudhi/gudhi/persistence_interval.h +263 -0
- multipers/gudhi/gudhi/persistence_matrix_options.h +188 -0
- multipers/gudhi/gudhi/reader_utils.h +367 -0
- multipers/gudhi/gudhi/simple_mdspan.h +484 -0
- multipers/gudhi/gudhi/slicer_helpers.h +779 -0
- multipers/gudhi/tmp_h0_pers/mma_interface_h0.h +223 -0
- multipers/gudhi/tmp_h0_pers/naive_merge_tree.h +536 -0
- multipers/io.cpython-312-darwin.so +0 -0
- multipers/io.pyx +472 -0
- multipers/ml/__init__.py +0 -0
- multipers/ml/accuracies.py +90 -0
- multipers/ml/invariants_with_persistable.py +79 -0
- multipers/ml/kernels.py +176 -0
- multipers/ml/mma.py +713 -0
- multipers/ml/one.py +472 -0
- multipers/ml/point_clouds.py +352 -0
- multipers/ml/signed_measures.py +1667 -0
- multipers/ml/sliced_wasserstein.py +461 -0
- multipers/ml/tools.py +113 -0
- multipers/mma_structures.cpython-312-darwin.so +0 -0
- multipers/mma_structures.pxd +134 -0
- multipers/mma_structures.pyx +1483 -0
- multipers/mma_structures.pyx.tp +1126 -0
- multipers/multi_parameter_rank_invariant/diff_helpers.h +85 -0
- multipers/multi_parameter_rank_invariant/euler_characteristic.h +95 -0
- multipers/multi_parameter_rank_invariant/function_rips.h +317 -0
- multipers/multi_parameter_rank_invariant/hilbert_function.h +761 -0
- multipers/multi_parameter_rank_invariant/persistence_slices.h +149 -0
- multipers/multi_parameter_rank_invariant/rank_invariant.h +350 -0
- multipers/multiparameter_edge_collapse.py +41 -0
- multipers/multiparameter_module_approximation/approximation.h +2541 -0
- multipers/multiparameter_module_approximation/debug.h +107 -0
- multipers/multiparameter_module_approximation/format_python-cpp.h +292 -0
- multipers/multiparameter_module_approximation/utilities.h +428 -0
- multipers/multiparameter_module_approximation.cpython-312-darwin.so +0 -0
- multipers/multiparameter_module_approximation.pyx +286 -0
- multipers/ops.cpython-312-darwin.so +0 -0
- multipers/ops.pyx +231 -0
- multipers/pickle.py +89 -0
- multipers/plots.py +550 -0
- multipers/point_measure.cpython-312-darwin.so +0 -0
- multipers/point_measure.pyx +409 -0
- multipers/simplex_tree_multi.cpython-312-darwin.so +0 -0
- multipers/simplex_tree_multi.pxd +136 -0
- multipers/simplex_tree_multi.pyx +11719 -0
- multipers/simplex_tree_multi.pyx.tp +2102 -0
- multipers/slicer.cpython-312-darwin.so +0 -0
- multipers/slicer.pxd +2097 -0
- multipers/slicer.pxd.tp +263 -0
- multipers/slicer.pyx +13042 -0
- multipers/slicer.pyx.tp +1259 -0
- multipers/tensor/tensor.h +672 -0
- multipers/tensor.pxd +13 -0
- multipers/test.pyx +44 -0
- multipers/tests/__init__.py +70 -0
- multipers/torch/__init__.py +1 -0
- multipers/torch/diff_grids.py +240 -0
- multipers/torch/rips_density.py +310 -0
- multipers/vector_interface.pxd +46 -0
- multipers-2.4.0b1.dist-info/METADATA +131 -0
- multipers-2.4.0b1.dist-info/RECORD +184 -0
- multipers-2.4.0b1.dist-info/WHEEL +6 -0
- multipers-2.4.0b1.dist-info/licenses/LICENSE +21 -0
- multipers-2.4.0b1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
"""!
|
|
2
|
+
@package mma
|
|
3
|
+
@brief Files containing the C++ cythonized functions.
|
|
4
|
+
@author David Loiseaux
|
|
5
|
+
@copyright Copyright (c) 2022 Inria.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
# distutils: language = c++
|
|
9
|
+
|
|
10
|
+
###########################################################################
|
|
11
|
+
## PYTHON LIBRARIES
|
|
12
|
+
import gudhi as gd
|
|
13
|
+
import numpy as np
|
|
14
|
+
from typing import List
|
|
15
|
+
from joblib import Parallel, delayed
|
|
16
|
+
import sys
|
|
17
|
+
from warnings import warn
|
|
18
|
+
|
|
19
|
+
###########################################################################
|
|
20
|
+
## CPP CLASSES
|
|
21
|
+
from libc.stdint cimport intptr_t
|
|
22
|
+
from libc.stdint cimport uintptr_t
|
|
23
|
+
|
|
24
|
+
###########################################################################
|
|
25
|
+
## CYTHON TYPES
|
|
26
|
+
from libcpp.vector cimport vector
|
|
27
|
+
from libcpp.utility cimport pair
|
|
28
|
+
#from libcpp.list cimport list as clist
|
|
29
|
+
from libcpp cimport bool
|
|
30
|
+
from libcpp cimport int
|
|
31
|
+
from typing import Iterable,Optional, Literal
|
|
32
|
+
from cython.operator import dereference
|
|
33
|
+
#########################################################################
|
|
34
|
+
## Multipersistence Module Approximation Classes
|
|
35
|
+
from multipers.mma_structures cimport *
|
|
36
|
+
from multipers.filtrations cimport *
|
|
37
|
+
from multipers.filtration_conversions cimport *
|
|
38
|
+
cimport numpy as cnp
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
#########################################################################
|
|
42
|
+
## Small hack for typing
|
|
43
|
+
from multipers.simplex_tree_multi import is_simplextree_multi, SimplexTreeMulti_type
|
|
44
|
+
from multipers.slicer import Slicer_type, is_slicer
|
|
45
|
+
from multipers.mma_structures import *
|
|
46
|
+
from multipers.mma_structures import PyModule_type
|
|
47
|
+
from typing import Union
|
|
48
|
+
from multipers.slicer cimport _multiparameter_module_approximation_f32, _multiparameter_module_approximation_f64
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def module_approximation_from_slicer(
|
|
53
|
+
slicer:Slicer_type,
|
|
54
|
+
box:Optional[np.ndarray]=None,
|
|
55
|
+
max_error=-1,
|
|
56
|
+
bool complete=True,
|
|
57
|
+
bool threshold=False,
|
|
58
|
+
bool verbose=False,
|
|
59
|
+
list[float] direction = [],
|
|
60
|
+
bool warnings = True,
|
|
61
|
+
unsqueeze_grid = None,
|
|
62
|
+
)->PyModule_type:
|
|
63
|
+
|
|
64
|
+
cdef Module[float] mod_f32
|
|
65
|
+
cdef Module[double] mod_f64
|
|
66
|
+
cdef intptr_t ptr
|
|
67
|
+
if not slicer.is_vine:
|
|
68
|
+
if warnings:
|
|
69
|
+
warn(r"(copy warning) Got a non-vine slicer as an input. Use `vineyard=True` to remove this copy.")
|
|
70
|
+
from multipers._slicer_meta import Slicer
|
|
71
|
+
slicer = Slicer(slicer, vineyard=True, backend="matrix")
|
|
72
|
+
# if slicer.is_squeezed and unsqueeze_grid not None:
|
|
73
|
+
# raise ValueError("Got a squeezed slicer. Should have been unsqueezed before !")
|
|
74
|
+
|
|
75
|
+
direction_ = np.asarray(direction, dtype=slicer.dtype)
|
|
76
|
+
if slicer.dtype == np.float32:
|
|
77
|
+
approx_mod = PyModule_f32()
|
|
78
|
+
if box is None:
|
|
79
|
+
box = slicer.filtration_bounds()
|
|
80
|
+
mod_f32 = _multiparameter_module_approximation_f32(slicer,_py2p_f32(direction_), max_error,Box[float](box),threshold, complete, verbose)
|
|
81
|
+
ptr = <intptr_t>(&mod_f32)
|
|
82
|
+
elif slicer.dtype == np.float64:
|
|
83
|
+
approx_mod = PyModule_f64()
|
|
84
|
+
if box is None:
|
|
85
|
+
box = slicer.filtration_bounds()
|
|
86
|
+
mod_f64 = _multiparameter_module_approximation_f64(slicer,_py2p_f64(direction_), max_error,Box[double](box),threshold, complete, verbose)
|
|
87
|
+
ptr = <intptr_t>(&mod_f64)
|
|
88
|
+
else:
|
|
89
|
+
raise ValueError(f"Slicer must be float-like. Got {slicer.dtype}.")
|
|
90
|
+
|
|
91
|
+
approx_mod._set_from_ptr(ptr)
|
|
92
|
+
|
|
93
|
+
if unsqueeze_grid is not None:
|
|
94
|
+
if verbose:
|
|
95
|
+
print("Reevaluating module in filtration grid...",end="", flush=True)
|
|
96
|
+
approx_mod.evaluate_in_grid(unsqueeze_grid)
|
|
97
|
+
from multipers.grids import compute_bounding_box
|
|
98
|
+
if len(approx_mod):
|
|
99
|
+
approx_mod.set_box(compute_bounding_box(approx_mod))
|
|
100
|
+
if verbose:
|
|
101
|
+
print("Done.",flush=True)
|
|
102
|
+
|
|
103
|
+
return approx_mod
|
|
104
|
+
|
|
105
|
+
def module_approximation(
|
|
106
|
+
input:Union[SimplexTreeMulti_type,Slicer_type, tuple],
|
|
107
|
+
box:Optional[np.ndarray]=None,
|
|
108
|
+
double max_error=-1,
|
|
109
|
+
int nlines=557,
|
|
110
|
+
bool from_coordinates = False,
|
|
111
|
+
bool complete=True,
|
|
112
|
+
bool threshold=False,
|
|
113
|
+
bool verbose=False,
|
|
114
|
+
bool ignore_warnings=False,
|
|
115
|
+
vector[double] direction = [],
|
|
116
|
+
vector[int] swap_box_coords = [],
|
|
117
|
+
*,
|
|
118
|
+
int n_jobs = -1,
|
|
119
|
+
)->PyModule_type:
|
|
120
|
+
"""Computes an interval module approximation of a multiparameter filtration.
|
|
121
|
+
|
|
122
|
+
Parameters
|
|
123
|
+
----------
|
|
124
|
+
input: SimplexTreeMulti or Slicer-like.
|
|
125
|
+
Holds the multifiltered complex.
|
|
126
|
+
max_error: positive float
|
|
127
|
+
Trade-off between approximation and computational complexity.
|
|
128
|
+
Upper bound of the module approximation, in bottleneck distance,
|
|
129
|
+
for interval-decomposable modules.
|
|
130
|
+
nlines: int = 557
|
|
131
|
+
Alternative to max_error;
|
|
132
|
+
specifies the number of persistence computation used for the approximation.
|
|
133
|
+
box : (Optional) pair of list of floats
|
|
134
|
+
Defines a rectangle on which to compute the approximation.
|
|
135
|
+
Format : [x,y], This defines a rectangle on which we draw the lines,
|
|
136
|
+
uniformly drawn (with a max_error step).
|
|
137
|
+
The first line is `x`.
|
|
138
|
+
**Warning**: For custom boxes, and directions, you **must** ensure
|
|
139
|
+
that the first line captures a generic barcode.
|
|
140
|
+
direction: float[:] = []
|
|
141
|
+
If given, the line are drawn with this angle.
|
|
142
|
+
**Warning**: You must ensure that the first line, defined by box,
|
|
143
|
+
captures a generic barcode.
|
|
144
|
+
threshold: bool
|
|
145
|
+
When true, intersects the module support with the box,
|
|
146
|
+
i.e. no more infinite summands.
|
|
147
|
+
verbose: bool
|
|
148
|
+
Prints C++ infos.
|
|
149
|
+
ignore_warning : bool
|
|
150
|
+
Unless set to true, prevents computing on more than 10k lines.
|
|
151
|
+
Useful to prevent a segmentation fault due to "infinite" recursion.
|
|
152
|
+
Returns
|
|
153
|
+
-------
|
|
154
|
+
PyModule
|
|
155
|
+
An interval decomposable module approximation of the module defined by the
|
|
156
|
+
homology of this multi-filtration.
|
|
157
|
+
"""
|
|
158
|
+
if isinstance(input, tuple) or isinstance(input, list):
|
|
159
|
+
|
|
160
|
+
assert all(is_slicer(s) and (s.is_minpres or len(s)==0) for s in input), "Modules cannot be merged unless they are minimal presentations."
|
|
161
|
+
|
|
162
|
+
assert (np.unique([s.minpres_degree for s in input if len(s)], return_counts=True)[1] <=1).all(), "Multiple modules are at the same degree, cannot merge modules"
|
|
163
|
+
if len(input) == 0:
|
|
164
|
+
return PyModule_f64()
|
|
165
|
+
modules = tuple(Parallel(n_jobs=n_jobs, prefer="threads")(
|
|
166
|
+
delayed(module_approximation)(
|
|
167
|
+
input=slicer,
|
|
168
|
+
box=box,
|
|
169
|
+
max_error=max_error,
|
|
170
|
+
nlines=nlines,
|
|
171
|
+
from_coordinates=from_coordinates,
|
|
172
|
+
complete=complete,
|
|
173
|
+
threshold=threshold,
|
|
174
|
+
verbose=verbose,
|
|
175
|
+
ignore_warnings=ignore_warnings,
|
|
176
|
+
direction=direction,
|
|
177
|
+
swap_box_coords=swap_box_coords,
|
|
178
|
+
)
|
|
179
|
+
for slicer in input
|
|
180
|
+
))
|
|
181
|
+
box = np.array([
|
|
182
|
+
np.min([m.get_box()[0] for m in modules if len(m)], axis=0),
|
|
183
|
+
np.max([m.get_box()[1] for m in modules if len(m)], axis=0),
|
|
184
|
+
])
|
|
185
|
+
mod = PyModule_f64().set_box(box)
|
|
186
|
+
for i,m in enumerate(modules):
|
|
187
|
+
mod.merge(m, input[i].minpres_degree)
|
|
188
|
+
return mod
|
|
189
|
+
if len(input) == 0:
|
|
190
|
+
if verbose:
|
|
191
|
+
print("Empty input, returning the trivial module.")
|
|
192
|
+
return PyModule_f64()
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
cdef bool is_degenerate=False
|
|
196
|
+
for i in range(direction.size()):
|
|
197
|
+
if direction[i]<0:
|
|
198
|
+
raise ValueError(f"Got an invalid negative direction. {direction=}")
|
|
199
|
+
if direction[i] == 0:
|
|
200
|
+
is_degenerate=True
|
|
201
|
+
if is_degenerate and not ignore_warnings:
|
|
202
|
+
warn("Got a degenerate direction. This function may fail if the first line is not generic.")
|
|
203
|
+
|
|
204
|
+
if from_coordinates and not input.is_squeezed:
|
|
205
|
+
input = input.grid_squeeze()
|
|
206
|
+
unsqueeze_grid = None
|
|
207
|
+
if input.is_squeezed:
|
|
208
|
+
if not ignore_warnings:
|
|
209
|
+
warn("(copy warning) Got a squeezed input. ")
|
|
210
|
+
if verbose:
|
|
211
|
+
print("Preparing filtration (unsqueeze)... ",end="", flush=True)
|
|
212
|
+
if from_coordinates:
|
|
213
|
+
from multipers.grids import sanitize_grid
|
|
214
|
+
unsqueeze_grid = sanitize_grid(input.filtration_grid, numpyfy=True, add_inf=True)
|
|
215
|
+
input = input.astype(dtype=np.float64)
|
|
216
|
+
if direction.size() == 0:
|
|
217
|
+
_direction = np.asarray([1/g.size for g in unsqueeze_grid], dtype=np.float64)
|
|
218
|
+
_direction /= np.sqrt((_direction**2).sum())
|
|
219
|
+
direction = _direction
|
|
220
|
+
if verbose:
|
|
221
|
+
print(f"Updated `{direction=}`, and `{max_error=}` ",end="")
|
|
222
|
+
|
|
223
|
+
else:
|
|
224
|
+
input = input.unsqueeze()
|
|
225
|
+
if verbose:
|
|
226
|
+
print("Done.", flush=True)
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
if box is None:
|
|
231
|
+
if verbose:
|
|
232
|
+
print("No box given. Using filtration bounds to infer it.")
|
|
233
|
+
box = input.filtration_bounds()
|
|
234
|
+
if verbose:
|
|
235
|
+
print(f"Using {box=}.",flush=True)
|
|
236
|
+
|
|
237
|
+
box = np.asarray(box)
|
|
238
|
+
if box.ndim !=2:
|
|
239
|
+
raise ValueError(f"Invalid box dimension. Got {box.ndim=} != 2")
|
|
240
|
+
# empty coords
|
|
241
|
+
zero_idx = box[1] == box[0]
|
|
242
|
+
if np.any(zero_idx):
|
|
243
|
+
if not ignore_warnings:
|
|
244
|
+
warn(f"Got {(box[1] == box[0])=} trivial box coordinates.")
|
|
245
|
+
box[1] += zero_idx
|
|
246
|
+
|
|
247
|
+
for i in swap_box_coords:
|
|
248
|
+
box[[0,1],i] = box[[1,0],i]
|
|
249
|
+
num_parameters = box.shape[1]
|
|
250
|
+
if num_parameters <=0:
|
|
251
|
+
num_parameters = box.shape[1]
|
|
252
|
+
assert direction.size() == 0 or direction.size() == box[0].size, f"Invalid line direction size, has to be 0 or {num_parameters=}"
|
|
253
|
+
|
|
254
|
+
prod = sum(np.abs(box[1] - box[0])[:i].prod() * np.abs(box[1] - box[0])[i+1:].prod() for i in range(0,num_parameters) if (direction.size() ==0 or direction[i]!=0))
|
|
255
|
+
|
|
256
|
+
if max_error <= 0:
|
|
257
|
+
max_error = (prod/nlines)**(1/(num_parameters-1))
|
|
258
|
+
|
|
259
|
+
estimated_nlines = prod/(max_error**(num_parameters -1))
|
|
260
|
+
if not ignore_warnings and estimated_nlines >= 10_000:
|
|
261
|
+
raise ValueError(f"""
|
|
262
|
+
Warning : the number of lines (around {np.round(estimated_nlines)}) may be too high.
|
|
263
|
+
This may be due to extreme box or filtration bounds :
|
|
264
|
+
|
|
265
|
+
{box=}
|
|
266
|
+
|
|
267
|
+
Try to increase the precision parameter, or set `ignore_warnings=True` to compute this module.
|
|
268
|
+
Returning the trivial module.
|
|
269
|
+
"""
|
|
270
|
+
)
|
|
271
|
+
if is_simplextree_multi(input):
|
|
272
|
+
from multipers._slicer_meta import Slicer
|
|
273
|
+
input = Slicer(input,backend="matrix", vineyard=True)
|
|
274
|
+
if not is_slicer(input):
|
|
275
|
+
raise ValueError("First argument must be a simplextree or a slicer !")
|
|
276
|
+
return module_approximation_from_slicer(
|
|
277
|
+
slicer=input,
|
|
278
|
+
box=box,
|
|
279
|
+
max_error=max_error,
|
|
280
|
+
complete=complete,
|
|
281
|
+
threshold=threshold,
|
|
282
|
+
verbose=verbose,
|
|
283
|
+
direction=direction,
|
|
284
|
+
unsqueeze_grid=unsqueeze_grid,
|
|
285
|
+
)
|
|
286
|
+
|
|
Binary file
|
multipers/ops.pyx
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import cython
|
|
2
|
+
from multipers.vector_interface cimport *
|
|
3
|
+
import numpy as np
|
|
4
|
+
import multipers as mp
|
|
5
|
+
|
|
6
|
+
@cython.boundscheck(False)
|
|
7
|
+
@cython.wraparound(False)
|
|
8
|
+
cdef vector[pair[double, double]] array_view_to_vect_pair(double[:, :] arr_view) noexcept nogil:
|
|
9
|
+
cdef int n = arr_view.shape[0]
|
|
10
|
+
cdef vector[pair[double, double]] result_vector
|
|
11
|
+
result_vector.resize(n)
|
|
12
|
+
for i in range(n):
|
|
13
|
+
result_vector[i] = pair[double, double](arr_view[i, 0], arr_view[i, 1])
|
|
14
|
+
return result_vector
|
|
15
|
+
|
|
16
|
+
def _aida(col_degrees, row_degrees, matrix):
|
|
17
|
+
cdef multipers_interface_input stuff = multipers_interface_input(col_degrees, row_degrees, matrix)
|
|
18
|
+
cdef AIDA_functor truc
|
|
19
|
+
truc.config.show_info = True
|
|
20
|
+
truc.config.sort_output = True
|
|
21
|
+
truc.config.sort = True
|
|
22
|
+
cdef multipers_interface_output stuff2 = truc.multipers_interface(stuff)
|
|
23
|
+
out = []
|
|
24
|
+
for i in range(stuff2.summands.size()):
|
|
25
|
+
out.append((stuff2.summands[i].col_degrees, stuff2.summands[i].row_degrees, stuff2.summands[i].matrix))
|
|
26
|
+
return out
|
|
27
|
+
|
|
28
|
+
def aida(s, bool sort=True, bool verbose=False, bool progress = False):
|
|
29
|
+
"""
|
|
30
|
+
Decomposes (a minimal presentation of a) 2-parameter persistence module as
|
|
31
|
+
a direct sum of indecomposables.
|
|
32
|
+
|
|
33
|
+
From [Decomposing Multiparameter Persistence Modules](https://doi.org/10.4230/LIPIcs.SoCG.2025.41).
|
|
34
|
+
|
|
35
|
+
Parameters:
|
|
36
|
+
- s : The slicer to reduce. Has to be a minimal presentation.
|
|
37
|
+
- verbose : shows log.
|
|
38
|
+
- progress : shows a progress bar
|
|
39
|
+
- sort : sorts the input first in a colexical order (debug)
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
from multipers.slicer import is_slicer
|
|
43
|
+
if not is_slicer(s):
|
|
44
|
+
raise ValueError(f"Input has to be a slicer. Got {type(s)=}.")
|
|
45
|
+
if not s.is_minpres:
|
|
46
|
+
raise ValueError(f"AIDA takes a minimal presentation as an input. Got {s.minpres_degree=}.")
|
|
47
|
+
if s.num_parameters != 2 or not s.is_minpres:
|
|
48
|
+
raise ValueError(f"AIDA is only compatible with 2-parameter minimal presentations. Got {s.num_parameters=} and {s.is_minpres=}.")
|
|
49
|
+
cdef bool is_squeezed = s.is_squeezed
|
|
50
|
+
|
|
51
|
+
cdef int degree = s.minpres_degree
|
|
52
|
+
if sort:
|
|
53
|
+
s = s.to_colexical()
|
|
54
|
+
F = np.asarray(s.get_filtrations())
|
|
55
|
+
D = s.get_dimensions()
|
|
56
|
+
cdef double[:,:] row_degree_ = np.asarray(F[D==degree], dtype = np.float64)
|
|
57
|
+
cdef double[:,:] col_degree_ = np.asarray(F[D==degree+1], dtype = np.float64)
|
|
58
|
+
cdef vector[pair[double,double]] row_degree = array_view_to_vect_pair(row_degree_)
|
|
59
|
+
cdef vector[pair[double,double]] col_degree = array_view_to_vect_pair(col_degree_)
|
|
60
|
+
i,j = np.searchsorted(D, [degree+1,degree+2])
|
|
61
|
+
cdef vector[vector[int]] matrix = s.get_boundaries()[i:j]
|
|
62
|
+
|
|
63
|
+
cdef AIDA_functor truc
|
|
64
|
+
cdef multipers_interface_input stuff
|
|
65
|
+
cdef multipers_interface_output stuff2
|
|
66
|
+
with nogil:
|
|
67
|
+
truc.config.show_info = verbose
|
|
68
|
+
truc.config.sort_output = False
|
|
69
|
+
truc.config.sort = sort
|
|
70
|
+
truc.config.progress = progress
|
|
71
|
+
stuff = multipers_interface_input(col_degree, row_degree, matrix)
|
|
72
|
+
stuff2 = truc.multipers_interface(stuff)
|
|
73
|
+
out = []
|
|
74
|
+
_Slicer = mp.Slicer(return_type_only=True, dtype=np.float64)
|
|
75
|
+
out = [_Slicer() for _ in range(stuff2.summands.size())]
|
|
76
|
+
dim_container_ = s.get_dimensions().copy()
|
|
77
|
+
cdef int32_t[:] dim_container = np.asarray(dim_container_, dtype=np.int32)
|
|
78
|
+
cdef list boundary_container
|
|
79
|
+
cdef vector[pair[double,double]] FR
|
|
80
|
+
cdef vector[pair[double,double]] FG
|
|
81
|
+
cdef vector[vector[int]] B
|
|
82
|
+
for i in range(stuff2.summands.size()):
|
|
83
|
+
FR = stuff2.summands[i].col_degrees
|
|
84
|
+
FG = stuff2.summands[i].row_degrees
|
|
85
|
+
B = stuff2.summands[i].matrix
|
|
86
|
+
|
|
87
|
+
for j in range(FG.size()):
|
|
88
|
+
dim_container[j] = degree
|
|
89
|
+
for j in range(FG.size(),FG.size()+FR.size()):
|
|
90
|
+
dim_container[j] = degree +1
|
|
91
|
+
|
|
92
|
+
boundary_container = [[] for _ in range(FG.size())]
|
|
93
|
+
boundary_container.extend(B)
|
|
94
|
+
|
|
95
|
+
if FR.size() == 0:
|
|
96
|
+
filtration_values = np.asarray(FG)
|
|
97
|
+
else:
|
|
98
|
+
filtration_values = np.concatenate([FG,FR], dtype=np.float64)
|
|
99
|
+
|
|
100
|
+
s_summand = _Slicer(
|
|
101
|
+
boundary_container,
|
|
102
|
+
dim_container[:FG.size()+FR.size()],
|
|
103
|
+
filtration_values
|
|
104
|
+
)
|
|
105
|
+
if s.is_squeezed:
|
|
106
|
+
s_summand.filtration_grid = s.filtration_grid
|
|
107
|
+
s_summand._clean_filtration_grid()
|
|
108
|
+
out[i] = s_summand
|
|
109
|
+
|
|
110
|
+
return out
|
|
111
|
+
|
|
112
|
+
def one_criticalify(
|
|
113
|
+
slicer,
|
|
114
|
+
bool reduce=False,
|
|
115
|
+
degree:Optional[int]=None,
|
|
116
|
+
bool clear = True,
|
|
117
|
+
swedish:Optional[bool] = None,
|
|
118
|
+
bool verbose = False,
|
|
119
|
+
bool kcritical=False,
|
|
120
|
+
str algo:Literal["path","tree"]="path",
|
|
121
|
+
):
|
|
122
|
+
"""
|
|
123
|
+
Computes a free implicit representation of a given multi-critical
|
|
124
|
+
multifiltration of a given homological degree (i.e., for a given
|
|
125
|
+
homological degree, a quasi-isomorphic 1-critical filtration), or free
|
|
126
|
+
resolution of the multifiltration (i.e., quasi-isomorphic 1-critical chain
|
|
127
|
+
complex).
|
|
128
|
+
|
|
129
|
+
From [Fast free resolutions of bifiltered chain complexes](https://doi.org/10.48550/arXiv.2512.08652),
|
|
130
|
+
whose code is available here: https://bitbucket.org/mkerber/multi_critical
|
|
131
|
+
|
|
132
|
+
Parameters:
|
|
133
|
+
- slicer : multicritical filtration to represent
|
|
134
|
+
- reduce : returns a (or multiple, see degree) minimal presentation(s) instead of the chain complex.
|
|
135
|
+
- degree : If an int is given, and `reduce` is true, only returns the minimal presentation of this degree.
|
|
136
|
+
If None is given and `reduce` is true, returns a minimal presentation of all possible degrees.
|
|
137
|
+
If reduce is false : has no effect.
|
|
138
|
+
- clear : Clears the temporary files.
|
|
139
|
+
- swedish : if True, `reduce=True` and `degree=None` skips the computation of the 1critical chain complex,
|
|
140
|
+
and directly (sequentially) computes the individual minimal presentations.
|
|
141
|
+
- verbose : shows log
|
|
142
|
+
- kcritical : do not use
|
|
143
|
+
- algo : see ref.
|
|
144
|
+
"""
|
|
145
|
+
from multipers.io import _multi_critical_from_slicer
|
|
146
|
+
from multipers.slicer import is_slicer
|
|
147
|
+
if not is_slicer(slicer):
|
|
148
|
+
raise ValueError(f"Invalid input. Expected `SlicerType` got {type(slicer)=}.")
|
|
149
|
+
if not slicer.is_kcritical:
|
|
150
|
+
return slicer
|
|
151
|
+
if slicer.is_squeezed:
|
|
152
|
+
F = slicer.filtration_grid
|
|
153
|
+
else:
|
|
154
|
+
F = None
|
|
155
|
+
out = _multi_critical_from_slicer(
|
|
156
|
+
slicer, reduce=reduce, algo=algo,
|
|
157
|
+
degree=degree, clear=clear,
|
|
158
|
+
swedish=swedish, verbose=verbose,
|
|
159
|
+
kcritical=kcritical
|
|
160
|
+
)
|
|
161
|
+
if is_slicer(out, allow_minpres=False):
|
|
162
|
+
out.filtration_grid = F
|
|
163
|
+
else:
|
|
164
|
+
for stuff in out:
|
|
165
|
+
stuff.filtration_grid = F
|
|
166
|
+
return out
|
|
167
|
+
|
|
168
|
+
def minimal_presentation(
|
|
169
|
+
slicer,
|
|
170
|
+
int degree = -1,
|
|
171
|
+
degrees:Iterable[int]=[],
|
|
172
|
+
str backend:Literal["mpfree", "2pac", ""]="mpfree",
|
|
173
|
+
int n_jobs = -1,
|
|
174
|
+
bool force=False,
|
|
175
|
+
bool auto_clean = True,
|
|
176
|
+
):
|
|
177
|
+
"""
|
|
178
|
+
Computes a minimal presentation a (1-critical) multifiltered complex.
|
|
179
|
+
|
|
180
|
+
From [Fast minimal presentations of bi-graded persistence modules](https://doi.org/10.1137/1.9781611976472.16),
|
|
181
|
+
whose code is available here: https://bitbucket.org/mkerber/mpfree
|
|
182
|
+
|
|
183
|
+
Backends differents than `mpfree` are unstable.
|
|
184
|
+
|
|
185
|
+
Parameters:
|
|
186
|
+
- slicer : the filtration/free implicit representation to reduce
|
|
187
|
+
- degree : the homological degree to reduce
|
|
188
|
+
- degrees : a list of homological degrees to reduce. Output will be a list.
|
|
189
|
+
- backend : a callable `scc`-compatible backend
|
|
190
|
+
- n_jobs : process minpres in parallel if degrees is given
|
|
191
|
+
- force : if input is already reduced, force the re-computation of the minimal presentation.
|
|
192
|
+
- auto_clean : if input is squeezed, some filtraton values may disappear.
|
|
193
|
+
This is a postprocessing to remove unnecessary coordinates.
|
|
194
|
+
"""
|
|
195
|
+
from multipers.io import _init_external_softwares, scc_reduce_from_str_to_slicer
|
|
196
|
+
from joblib import Parallel, delayed
|
|
197
|
+
from multipers.slicer import is_slicer
|
|
198
|
+
from multipers import Slicer
|
|
199
|
+
import os
|
|
200
|
+
import tempfile
|
|
201
|
+
|
|
202
|
+
if is_slicer(slicer) and slicer.is_minpres and not force:
|
|
203
|
+
from warnings import warn
|
|
204
|
+
warn(f"(unnecessary computation) The slicer seems to be already reduced, from homology of degree {slicer.minpres_degree}.")
|
|
205
|
+
return slicer
|
|
206
|
+
_init_external_softwares(requires=[backend])
|
|
207
|
+
if len(degrees)>0:
|
|
208
|
+
def todo(int degree):
|
|
209
|
+
return minimal_presentation(slicer, degree=degree, backend=backend, force=force, auto_clean=auto_clean)
|
|
210
|
+
return tuple(
|
|
211
|
+
Parallel(n_jobs=n_jobs, backend="threading")(delayed(todo)(d) for d in degrees)
|
|
212
|
+
)
|
|
213
|
+
assert degree>=0, f"Degree not provided."
|
|
214
|
+
if not np.any(slicer.get_dimensions() == degree):
|
|
215
|
+
return type(slicer)()
|
|
216
|
+
dimension = slicer.dimension - degree # latest = L-1, which is empty, -1 for degree 0, -2 for degree 1 etc.
|
|
217
|
+
with tempfile.TemporaryDirectory(prefix="multipers") as tmpdir:
|
|
218
|
+
tmp_path = os.path.join(tmpdir, "multipers.scc")
|
|
219
|
+
slicer.to_scc(path=tmp_path, strip_comments=True, degree=degree-1, unsqueeze = False)
|
|
220
|
+
new_slicer = type(slicer)()
|
|
221
|
+
if backend=="mpfree":
|
|
222
|
+
shift_dimension=degree-1
|
|
223
|
+
else:
|
|
224
|
+
shift_dimension=degree
|
|
225
|
+
scc_reduce_from_str_to_slicer(path=tmp_path, slicer=new_slicer, dimension=dimension, backend=backend, shift_dimension=shift_dimension)
|
|
226
|
+
|
|
227
|
+
new_slicer.minpres_degree = degree
|
|
228
|
+
new_slicer.filtration_grid = slicer.filtration_grid if slicer.is_squeezed else None
|
|
229
|
+
if new_slicer.is_squeezed and auto_clean:
|
|
230
|
+
new_slicer = new_slicer._clean_filtration_grid()
|
|
231
|
+
return new_slicer
|
multipers/pickle.py
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def save_with_axis(path: str, signed_measures):
|
|
5
|
+
np.savez(
|
|
6
|
+
path,
|
|
7
|
+
**{
|
|
8
|
+
f"{i}_{axis}_{degree}": np.c_[
|
|
9
|
+
sm_of_degree[0], sm_of_degree[1][:, np.newaxis]
|
|
10
|
+
]
|
|
11
|
+
for i, sm in enumerate(signed_measures)
|
|
12
|
+
for axis, sm_of_axis in enumerate(sm)
|
|
13
|
+
for degree, sm_of_degree in enumerate(sm_of_axis)
|
|
14
|
+
},
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def save_without_axis(path: str, signed_measures):
|
|
19
|
+
np.savez(
|
|
20
|
+
path,
|
|
21
|
+
**{
|
|
22
|
+
f"{i}_{degree}": np.c_[sm_of_degree[0], sm_of_degree[1][:, np.newaxis]]
|
|
23
|
+
for i, sm in enumerate(signed_measures)
|
|
24
|
+
for degree, sm_of_degree in enumerate(sm)
|
|
25
|
+
},
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def get_sm_with_axis(sms, idx, axis, degree):
|
|
30
|
+
sm = sms[f"{idx}_{axis}_{degree}"]
|
|
31
|
+
return (sm[:, :-1], sm[:, -1])
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def get_sm_without_axis(sms, idx, degree):
|
|
35
|
+
sm = sms[f"{idx}_{degree}"]
|
|
36
|
+
return (sm[:, :-1], sm[:, -1])
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def load_without_axis(sms):
|
|
40
|
+
indices = np.array(
|
|
41
|
+
[[int(i) for i in key.split("_")] for key in sms.keys()], dtype=int
|
|
42
|
+
)
|
|
43
|
+
num_data, num_degrees = indices.max(axis=0) + 1
|
|
44
|
+
signed_measures_reconstructed = [
|
|
45
|
+
[get_sm_without_axis(sms, idx, degree) for degree in range(num_degrees)]
|
|
46
|
+
for idx in range(num_data)
|
|
47
|
+
]
|
|
48
|
+
return signed_measures_reconstructed
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
# test : np.all([np.array_equal(a[0],b[0]) and np.array_equal(a[1],b[1]) and len(a) == len(b) == 2 for x,y in zip(signed_measures_reconstructed,signed_measures_reconstructed) for a,b in zip(x,y)])
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def load_with_axis(sms):
|
|
55
|
+
indices = np.array(
|
|
56
|
+
[[int(i) for i in key.split("_")] for key in sms.keys()], dtype=int
|
|
57
|
+
)
|
|
58
|
+
num_data, num_axis, num_degrees = indices.max(axis=0) + 1
|
|
59
|
+
signed_measures_reconstructed = [
|
|
60
|
+
[
|
|
61
|
+
[get_sm_with_axis(sms, idx, axis, degree) for degree in range(num_degrees)]
|
|
62
|
+
for axis in range(num_axis)
|
|
63
|
+
]
|
|
64
|
+
for idx in range(num_data)
|
|
65
|
+
]
|
|
66
|
+
return signed_measures_reconstructed
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def save(path: str, signed_measures):
|
|
70
|
+
if isinstance(signed_measures[0][0], tuple):
|
|
71
|
+
save_without_axis(path=path, signed_measures=signed_measures)
|
|
72
|
+
else:
|
|
73
|
+
save_with_axis(path=path, signed_measures=signed_measures)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def load(path: str):
|
|
77
|
+
sms = np.load(path)
|
|
78
|
+
item = None
|
|
79
|
+
for i in sms.keys():
|
|
80
|
+
item = i
|
|
81
|
+
break
|
|
82
|
+
n = len(item.split("_"))
|
|
83
|
+
match n:
|
|
84
|
+
case 2:
|
|
85
|
+
return load_without_axis(sms)
|
|
86
|
+
case 3:
|
|
87
|
+
return load_with_axis(sms)
|
|
88
|
+
case _:
|
|
89
|
+
raise Exception("Invalid Signed Measure !")
|