multipers 2.2.3__cp310-cp310-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 +31 -0
- multipers/_signed_measure_meta.py +430 -0
- multipers/_slicer_meta.py +212 -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 +111 -0
- multipers/distances.py +198 -0
- multipers/filtration_conversions.pxd +229 -0
- multipers/filtration_conversions.pxd.tp +84 -0
- multipers/filtrations.pxd +224 -0
- multipers/function_rips.cp310-win_amd64.pyd +0 -0
- multipers/function_rips.pyx +105 -0
- multipers/grids.cp310-win_amd64.pyd +0 -0
- multipers/grids.pyx +350 -0
- multipers/gudhi/Persistence_slices_interface.h +132 -0
- multipers/gudhi/Simplex_tree_interface.h +245 -0
- multipers/gudhi/Simplex_tree_multi_interface.h +561 -0
- multipers/gudhi/cubical_to_boundary.h +59 -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 +45 -0
- multipers/gudhi/gudhi/Fields/Multi_field.h +484 -0
- multipers/gudhi/gudhi/Fields/Multi_field_operators.h +455 -0
- multipers/gudhi/gudhi/Fields/Multi_field_shared.h +450 -0
- multipers/gudhi/gudhi/Fields/Multi_field_small.h +531 -0
- multipers/gudhi/gudhi/Fields/Multi_field_small_operators.h +507 -0
- multipers/gudhi/gudhi/Fields/Multi_field_small_shared.h +531 -0
- multipers/gudhi/gudhi/Fields/Z2_field.h +355 -0
- multipers/gudhi/gudhi/Fields/Z2_field_operators.h +376 -0
- multipers/gudhi/gudhi/Fields/Zp_field.h +420 -0
- multipers/gudhi/gudhi/Fields/Zp_field_operators.h +400 -0
- multipers/gudhi/gudhi/Fields/Zp_field_shared.h +418 -0
- multipers/gudhi/gudhi/Flag_complex_edge_collapser.h +337 -0
- multipers/gudhi/gudhi/Matrix.h +2107 -0
- multipers/gudhi/gudhi/Multi_critical_filtration.h +1038 -0
- multipers/gudhi/gudhi/Multi_persistence/Box.h +171 -0
- multipers/gudhi/gudhi/Multi_persistence/Line.h +282 -0
- multipers/gudhi/gudhi/Off_reader.h +173 -0
- multipers/gudhi/gudhi/One_critical_filtration.h +1431 -0
- multipers/gudhi/gudhi/Persistence_matrix/Base_matrix.h +769 -0
- multipers/gudhi/gudhi/Persistence_matrix/Base_matrix_with_column_compression.h +686 -0
- multipers/gudhi/gudhi/Persistence_matrix/Boundary_matrix.h +842 -0
- multipers/gudhi/gudhi/Persistence_matrix/Chain_matrix.h +1350 -0
- multipers/gudhi/gudhi/Persistence_matrix/Id_to_index_overlay.h +1105 -0
- multipers/gudhi/gudhi/Persistence_matrix/Position_to_index_overlay.h +859 -0
- multipers/gudhi/gudhi/Persistence_matrix/RU_matrix.h +910 -0
- multipers/gudhi/gudhi/Persistence_matrix/allocators/entry_constructors.h +139 -0
- multipers/gudhi/gudhi/Persistence_matrix/base_pairing.h +230 -0
- multipers/gudhi/gudhi/Persistence_matrix/base_swap.h +211 -0
- multipers/gudhi/gudhi/Persistence_matrix/boundary_cell_position_to_id_mapper.h +60 -0
- multipers/gudhi/gudhi/Persistence_matrix/boundary_face_position_to_id_mapper.h +60 -0
- multipers/gudhi/gudhi/Persistence_matrix/chain_pairing.h +136 -0
- multipers/gudhi/gudhi/Persistence_matrix/chain_rep_cycles.h +190 -0
- multipers/gudhi/gudhi/Persistence_matrix/chain_vine_swap.h +616 -0
- multipers/gudhi/gudhi/Persistence_matrix/columns/chain_column_extra_properties.h +150 -0
- multipers/gudhi/gudhi/Persistence_matrix/columns/column_dimension_holder.h +106 -0
- multipers/gudhi/gudhi/Persistence_matrix/columns/column_utilities.h +219 -0
- multipers/gudhi/gudhi/Persistence_matrix/columns/entry_types.h +327 -0
- multipers/gudhi/gudhi/Persistence_matrix/columns/heap_column.h +1140 -0
- multipers/gudhi/gudhi/Persistence_matrix/columns/intrusive_list_column.h +934 -0
- multipers/gudhi/gudhi/Persistence_matrix/columns/intrusive_set_column.h +934 -0
- multipers/gudhi/gudhi/Persistence_matrix/columns/list_column.h +980 -0
- multipers/gudhi/gudhi/Persistence_matrix/columns/naive_vector_column.h +1092 -0
- multipers/gudhi/gudhi/Persistence_matrix/columns/row_access.h +192 -0
- multipers/gudhi/gudhi/Persistence_matrix/columns/set_column.h +921 -0
- multipers/gudhi/gudhi/Persistence_matrix/columns/small_vector_column.h +1093 -0
- multipers/gudhi/gudhi/Persistence_matrix/columns/unordered_set_column.h +1012 -0
- multipers/gudhi/gudhi/Persistence_matrix/columns/vector_column.h +1244 -0
- multipers/gudhi/gudhi/Persistence_matrix/matrix_dimension_holders.h +186 -0
- multipers/gudhi/gudhi/Persistence_matrix/matrix_row_access.h +164 -0
- multipers/gudhi/gudhi/Persistence_matrix/ru_pairing.h +156 -0
- multipers/gudhi/gudhi/Persistence_matrix/ru_rep_cycles.h +376 -0
- multipers/gudhi/gudhi/Persistence_matrix/ru_vine_swap.h +540 -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 +745 -0
- multipers/gudhi/gudhi/Points_off_io.h +171 -0
- multipers/gudhi/gudhi/Simple_object_pool.h +69 -0
- multipers/gudhi/gudhi/Simplex_tree/Simplex_tree_iterators.h +463 -0
- multipers/gudhi/gudhi/Simplex_tree/Simplex_tree_node_explicit_storage.h +83 -0
- multipers/gudhi/gudhi/Simplex_tree/Simplex_tree_siblings.h +106 -0
- multipers/gudhi/gudhi/Simplex_tree/Simplex_tree_star_simplex_iterators.h +277 -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 +62 -0
- multipers/gudhi/gudhi/Simplex_tree/simplex_tree_options.h +157 -0
- multipers/gudhi/gudhi/Simplex_tree.h +2794 -0
- multipers/gudhi/gudhi/Simplex_tree_multi.h +163 -0
- multipers/gudhi/gudhi/distance_functions.h +62 -0
- multipers/gudhi/gudhi/graph_simplicial_complex.h +104 -0
- multipers/gudhi/gudhi/persistence_interval.h +253 -0
- multipers/gudhi/gudhi/persistence_matrix_options.h +170 -0
- multipers/gudhi/gudhi/reader_utils.h +367 -0
- multipers/gudhi/mma_interface_coh.h +255 -0
- multipers/gudhi/mma_interface_h0.h +231 -0
- multipers/gudhi/mma_interface_matrix.h +282 -0
- multipers/gudhi/naive_merge_tree.h +575 -0
- multipers/gudhi/scc_io.h +289 -0
- multipers/gudhi/truc.h +888 -0
- multipers/io.cp310-win_amd64.pyd +0 -0
- multipers/io.pyx +711 -0
- multipers/ml/__init__.py +0 -0
- multipers/ml/accuracies.py +90 -0
- multipers/ml/convolutions.py +520 -0
- multipers/ml/invariants_with_persistable.py +79 -0
- multipers/ml/kernels.py +176 -0
- multipers/ml/mma.py +714 -0
- multipers/ml/one.py +472 -0
- multipers/ml/point_clouds.py +346 -0
- multipers/ml/signed_measures.py +1589 -0
- multipers/ml/sliced_wasserstein.py +461 -0
- multipers/ml/tools.py +113 -0
- multipers/mma_structures.cp310-win_amd64.pyd +0 -0
- multipers/mma_structures.pxd +127 -0
- multipers/mma_structures.pyx +2746 -0
- multipers/mma_structures.pyx.tp +1085 -0
- multipers/multi_parameter_rank_invariant/diff_helpers.h +93 -0
- multipers/multi_parameter_rank_invariant/euler_characteristic.h +97 -0
- multipers/multi_parameter_rank_invariant/function_rips.h +322 -0
- multipers/multi_parameter_rank_invariant/hilbert_function.h +769 -0
- multipers/multi_parameter_rank_invariant/persistence_slices.h +148 -0
- multipers/multi_parameter_rank_invariant/rank_invariant.h +369 -0
- multipers/multiparameter_edge_collapse.py +41 -0
- multipers/multiparameter_module_approximation/approximation.h +2295 -0
- multipers/multiparameter_module_approximation/combinatory.h +129 -0
- multipers/multiparameter_module_approximation/debug.h +107 -0
- multipers/multiparameter_module_approximation/euler_curves.h +0 -0
- multipers/multiparameter_module_approximation/format_python-cpp.h +286 -0
- multipers/multiparameter_module_approximation/heap_column.h +238 -0
- multipers/multiparameter_module_approximation/images.h +79 -0
- multipers/multiparameter_module_approximation/list_column.h +174 -0
- multipers/multiparameter_module_approximation/list_column_2.h +232 -0
- multipers/multiparameter_module_approximation/ru_matrix.h +347 -0
- multipers/multiparameter_module_approximation/set_column.h +135 -0
- multipers/multiparameter_module_approximation/structure_higher_dim_barcode.h +36 -0
- multipers/multiparameter_module_approximation/unordered_set_column.h +166 -0
- multipers/multiparameter_module_approximation/utilities.h +419 -0
- multipers/multiparameter_module_approximation/vector_column.h +223 -0
- multipers/multiparameter_module_approximation/vector_matrix.h +331 -0
- multipers/multiparameter_module_approximation/vineyards.h +464 -0
- multipers/multiparameter_module_approximation/vineyards_trajectories.h +649 -0
- multipers/multiparameter_module_approximation.cp310-win_amd64.pyd +0 -0
- multipers/multiparameter_module_approximation.pyx +217 -0
- multipers/pickle.py +53 -0
- multipers/plots.py +334 -0
- multipers/point_measure.cp310-win_amd64.pyd +0 -0
- multipers/point_measure.pyx +320 -0
- multipers/simplex_tree_multi.cp310-win_amd64.pyd +0 -0
- multipers/simplex_tree_multi.pxd +133 -0
- multipers/simplex_tree_multi.pyx +10335 -0
- multipers/simplex_tree_multi.pyx.tp +1935 -0
- multipers/slicer.cp310-win_amd64.pyd +0 -0
- multipers/slicer.pxd +2371 -0
- multipers/slicer.pxd.tp +214 -0
- multipers/slicer.pyx +15467 -0
- multipers/slicer.pyx.tp +914 -0
- multipers/tbb12.dll +0 -0
- multipers/tbbbind_2_5.dll +0 -0
- multipers/tbbmalloc.dll +0 -0
- multipers/tbbmalloc_proxy.dll +0 -0
- multipers/tensor/tensor.h +672 -0
- multipers/tensor.pxd +13 -0
- multipers/test.pyx +44 -0
- multipers/tests/__init__.py +57 -0
- multipers/tests/test_diff_helper.py +73 -0
- multipers/tests/test_hilbert_function.py +82 -0
- multipers/tests/test_mma.py +83 -0
- multipers/tests/test_point_clouds.py +49 -0
- multipers/tests/test_python-cpp_conversion.py +82 -0
- multipers/tests/test_signed_betti.py +181 -0
- multipers/tests/test_signed_measure.py +89 -0
- multipers/tests/test_simplextreemulti.py +221 -0
- multipers/tests/test_slicer.py +221 -0
- multipers/torch/__init__.py +1 -0
- multipers/torch/diff_grids.py +217 -0
- multipers/torch/rips_density.py +304 -0
- multipers-2.2.3.dist-info/LICENSE +21 -0
- multipers-2.2.3.dist-info/METADATA +134 -0
- multipers-2.2.3.dist-info/RECORD +189 -0
- multipers-2.2.3.dist-info/WHEEL +5 -0
- multipers-2.2.3.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,217 @@
|
|
|
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
|
+
|
|
18
|
+
###########################################################################
|
|
19
|
+
## CPP CLASSES
|
|
20
|
+
from libc.stdint cimport intptr_t
|
|
21
|
+
from libc.stdint cimport uintptr_t
|
|
22
|
+
|
|
23
|
+
###########################################################################
|
|
24
|
+
## CYTHON TYPES
|
|
25
|
+
from libcpp.vector cimport vector
|
|
26
|
+
from libcpp.utility cimport pair
|
|
27
|
+
#from libcpp.list cimport list as clist
|
|
28
|
+
from libcpp cimport bool
|
|
29
|
+
from libcpp cimport int
|
|
30
|
+
from typing import Iterable,Optional, Literal
|
|
31
|
+
from cython.operator import dereference
|
|
32
|
+
#########################################################################
|
|
33
|
+
## Multipersistence Module Approximation Classes
|
|
34
|
+
from multipers.mma_structures cimport *
|
|
35
|
+
from multipers.filtrations cimport *
|
|
36
|
+
from multipers.filtration_conversions cimport *
|
|
37
|
+
cimport numpy as cnp
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
#########################################################################
|
|
41
|
+
## Small hack for typing
|
|
42
|
+
from multipers.simplex_tree_multi import is_simplextree_multi, SimplexTreeMulti_type
|
|
43
|
+
from multipers.slicer import Slicer_type, is_slicer
|
|
44
|
+
from multipers._slicer_meta import Slicer
|
|
45
|
+
from multipers.mma_structures import *
|
|
46
|
+
from multipers.mma_structures import PyModule_type
|
|
47
|
+
from typing import Union
|
|
48
|
+
import multipers
|
|
49
|
+
import multipers.io as mio
|
|
50
|
+
from multipers.slicer cimport _multiparameter_module_approximation_f32, _multiparameter_module_approximation_f64
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def module_approximation_from_slicer(
|
|
55
|
+
slicer:Slicer_type,
|
|
56
|
+
box:Optional[np.ndarray]=None,
|
|
57
|
+
max_error=-1,
|
|
58
|
+
bool complete=True,
|
|
59
|
+
bool threshold=False,
|
|
60
|
+
bool verbose=False,
|
|
61
|
+
list[float] direction = [],
|
|
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
|
+
print(r"Got a non-vine slicer as an input. Use `vineyard=True` to remove this copy.", file=sys.stderr)
|
|
69
|
+
slicer = Slicer(slicer, vineyard=True)
|
|
70
|
+
direction_ = np.asarray(direction, dtype=slicer.dtype)
|
|
71
|
+
if slicer.dtype == np.float32:
|
|
72
|
+
approx_mod = PyModule_f32()
|
|
73
|
+
if box is None:
|
|
74
|
+
box = slicer.filtration_bounds()
|
|
75
|
+
mod_f32 = _multiparameter_module_approximation_f32(slicer,_py21c_f32(direction_), max_error,Box[float](box),threshold, complete, verbose)
|
|
76
|
+
ptr = <intptr_t>(&mod_f32)
|
|
77
|
+
elif slicer.dtype == np.float64:
|
|
78
|
+
approx_mod = PyModule_f64()
|
|
79
|
+
if box is None:
|
|
80
|
+
box = slicer.filtration_bounds()
|
|
81
|
+
mod_f64 = _multiparameter_module_approximation_f64(slicer,_py21c_f64(direction_), max_error,Box[double](box),threshold, complete, verbose)
|
|
82
|
+
ptr = <intptr_t>(&mod_f64)
|
|
83
|
+
else:
|
|
84
|
+
raise ValueError(f"Slicer must be float-like. Got {slicer.dtype}.")
|
|
85
|
+
|
|
86
|
+
approx_mod._set_from_ptr(ptr)
|
|
87
|
+
|
|
88
|
+
return approx_mod
|
|
89
|
+
|
|
90
|
+
def module_approximation(
|
|
91
|
+
input:Union[SimplexTreeMulti_type,Slicer_type, tuple],
|
|
92
|
+
box:Optional[np.ndarray]=None,
|
|
93
|
+
float max_error=-1,
|
|
94
|
+
int nlines=500,
|
|
95
|
+
slicer_backend:Literal["matrix","clement","graph"]="matrix",
|
|
96
|
+
minpres:Optional[Literal["mpfree"]]=None,
|
|
97
|
+
degree:Optional[int]=None,
|
|
98
|
+
bool complete=True,
|
|
99
|
+
bool threshold=False,
|
|
100
|
+
bool verbose=False,
|
|
101
|
+
bool ignore_warning=False,
|
|
102
|
+
id="",
|
|
103
|
+
list[float] direction = [],
|
|
104
|
+
list[int] swap_box_coords = [],
|
|
105
|
+
*,
|
|
106
|
+
int n_jobs = -1,
|
|
107
|
+
)->PyModule_type:
|
|
108
|
+
"""Computes an interval module approximation of a multiparameter filtration.
|
|
109
|
+
|
|
110
|
+
Parameters
|
|
111
|
+
----------
|
|
112
|
+
input: SimplexTreeMulti or Slicer-like.
|
|
113
|
+
Holds the multifiltered complex.
|
|
114
|
+
max_error: positive float
|
|
115
|
+
Trade-off between approximation and computational complexity.
|
|
116
|
+
Upper bound of the module approximation, in bottleneck distance,
|
|
117
|
+
for interval-decomposable modules.
|
|
118
|
+
nlines: int = 200
|
|
119
|
+
Alternative to max_error;
|
|
120
|
+
specifies the number of persistence computation used for the approximation.
|
|
121
|
+
box : (Optional) pair of list of floats
|
|
122
|
+
Defines a rectangle on which to compute the approximation.
|
|
123
|
+
Format : [x,y], This defines a rectangle on which we draw the lines,
|
|
124
|
+
uniformly drawn (with a max_error step).
|
|
125
|
+
The first line is `x`.
|
|
126
|
+
**Warning**: For custom boxes, and directions, you **must** ensure
|
|
127
|
+
that the first line captures a generic barcode.
|
|
128
|
+
direction: float[:] = []
|
|
129
|
+
If given, the line are drawn with this angle.
|
|
130
|
+
**Warning**: You must ensure that the first line, defined by box,
|
|
131
|
+
captures a generic barcode.
|
|
132
|
+
slicer_backend: Either "matrix","clement", or "graph".
|
|
133
|
+
If a simplextree is given, it is first converted to this structure,
|
|
134
|
+
with different choices of backends.
|
|
135
|
+
minpres: (Optional) "mpfree" only for the moment.
|
|
136
|
+
If given, and the input is a simplextree,
|
|
137
|
+
computes a minimal presentation before starting the computation.
|
|
138
|
+
A degree has to be given.
|
|
139
|
+
degree: int Only required when minpres is given.
|
|
140
|
+
Homological degree of the minimal degree.
|
|
141
|
+
threshold: bool
|
|
142
|
+
When true, intersects the module support with the box,
|
|
143
|
+
i.e. no more infinite summands.
|
|
144
|
+
verbose: bool
|
|
145
|
+
Prints C++ infos.
|
|
146
|
+
ignore_warning : bool
|
|
147
|
+
Unless set to true, prevents computing on more than 10k lines.
|
|
148
|
+
Useful to prevent a segmentation fault due to "infinite" recursion.
|
|
149
|
+
Returns
|
|
150
|
+
-------
|
|
151
|
+
PyModule
|
|
152
|
+
An interval decomposable module approximation of the module defined by the
|
|
153
|
+
homology of this multi-filtration.
|
|
154
|
+
"""
|
|
155
|
+
if isinstance(input, tuple) or isinstance(input, list):
|
|
156
|
+
assert all(s.is_minpres for s in input), "Modules cannot be merged unless they are minimal presentations."
|
|
157
|
+
assert np.unique([s.minpres_degree for s in input]).shape[0] == len(input), "Multiple modules are at the same degree, cannot merge modules"
|
|
158
|
+
if len(input) == 0:
|
|
159
|
+
return PyModule_f64()
|
|
160
|
+
if n_jobs <= 1:
|
|
161
|
+
modules = tuple(module_approximation(slicer, box, max_error, nlines, slicer_backend, minpres, degree, complete, threshold, verbose, ignore_warning, id, direction, swap_box_coords) for slicer in input)
|
|
162
|
+
else:
|
|
163
|
+
modules = tuple(Parallel(n_jobs=n_jobs, prefer="threads")(
|
|
164
|
+
delayed(module_approximation)(slicer, box, max_error, nlines, slicer_backend, minpres, degree, complete, threshold, verbose, ignore_warning, id, direction, swap_box_coords)
|
|
165
|
+
for slicer in input
|
|
166
|
+
))
|
|
167
|
+
mod = PyModule_f64().set_box(PyBox_f64(*modules[0].get_box()))
|
|
168
|
+
for i,m in enumerate(modules):
|
|
169
|
+
mod.merge(m, input[i].minpres_degree)
|
|
170
|
+
return mod
|
|
171
|
+
if box is None:
|
|
172
|
+
if is_simplextree_multi(input):
|
|
173
|
+
box = input.filtration_bounds()
|
|
174
|
+
else:
|
|
175
|
+
box = input.filtration_bounds()
|
|
176
|
+
box = np.asarray(box)
|
|
177
|
+
|
|
178
|
+
# empty coords
|
|
179
|
+
zero_idx = box[1] == box[0]
|
|
180
|
+
if np.any(zero_idx):
|
|
181
|
+
box[1] += zero_idx
|
|
182
|
+
|
|
183
|
+
for i in swap_box_coords:
|
|
184
|
+
box[0,i], box[1,i] = box[1,i], box[0,i]
|
|
185
|
+
num_parameters = box.shape[1]
|
|
186
|
+
if num_parameters <=0:
|
|
187
|
+
num_parameters = box.shape[1]
|
|
188
|
+
assert len(direction) == 0 or len(direction) == len(box[0]), f"Invalid line direction, has to be 0 or {num_parameters=}"
|
|
189
|
+
|
|
190
|
+
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))
|
|
191
|
+
|
|
192
|
+
if max_error <= 0:
|
|
193
|
+
max_error = (prod/nlines)**(1/(num_parameters-1))
|
|
194
|
+
|
|
195
|
+
if not ignore_warning and prod >= 10_000:
|
|
196
|
+
raise ValueError(f"""
|
|
197
|
+
Warning : the number of lines (around {np.round(prod)}) may be too high.
|
|
198
|
+
Try to increase the precision parameter, or set `ignore_warning=True` to compute this module.
|
|
199
|
+
Returning the trivial module."""
|
|
200
|
+
)
|
|
201
|
+
if is_simplextree_multi(input):
|
|
202
|
+
input = multipers.Slicer(input,backend=slicer_backend, vineyard=True)
|
|
203
|
+
assert is_slicer(input), "First argument must be a simplextree or a slicer !"
|
|
204
|
+
return module_approximation_from_slicer(
|
|
205
|
+
slicer=input,
|
|
206
|
+
box=box,
|
|
207
|
+
max_error=max_error,
|
|
208
|
+
complete=complete,
|
|
209
|
+
threshold=threshold,
|
|
210
|
+
verbose=verbose,
|
|
211
|
+
direction=direction,
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
|
multipers/pickle.py
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
def save_with_axis(path:str, signed_measures):
|
|
4
|
+
np.savez(path,
|
|
5
|
+
**{f"{i}_{axis}_{degree}":np.c_[sm_of_degree[0],sm_of_degree[1][:,np.newaxis]] for i,sm in enumerate(signed_measures) for axis,sm_of_axis in enumerate(sm) for degree,sm_of_degree in enumerate(sm_of_axis)},
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
def save_without_axis(path:str, signed_measures):
|
|
9
|
+
np.savez(path,
|
|
10
|
+
**{f"{i}_{degree}":np.c_[sm_of_degree[0],sm_of_degree[1][:,np.newaxis]] for i,sm in enumerate(signed_measures) for degree,sm_of_degree in enumerate(sm)},
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
def get_sm_with_axis(sms,idx,axis,degree):
|
|
14
|
+
sm = sms[f"{idx}_{axis}_{degree}"]
|
|
15
|
+
return (sm[:,:-1],sm[:,-1])
|
|
16
|
+
def get_sm_without_axis(sms,idx,degree):
|
|
17
|
+
sm = sms[f"{idx}_{degree}"]
|
|
18
|
+
return (sm[:,:-1],sm[:,-1])
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def load_without_axis(sms):
|
|
22
|
+
indices = np.array([[int(i) for i in key.split('_')] for key in sms.keys()], dtype=int)
|
|
23
|
+
num_data,num_degrees = indices.max(axis=0)+1
|
|
24
|
+
signed_measures_reconstructed = [[get_sm_without_axis(sms,idx,degree) for degree in range(num_degrees)] for idx in range(num_data)]
|
|
25
|
+
return signed_measures_reconstructed
|
|
26
|
+
# 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)])
|
|
27
|
+
|
|
28
|
+
def load_with_axis(sms):
|
|
29
|
+
indices = np.array([[int(i) for i in key.split('_')] for key in sms.keys()], dtype=int)
|
|
30
|
+
num_data,num_axis,num_degrees = indices.max(axis=0)+1
|
|
31
|
+
signed_measures_reconstructed = [[[get_sm_with_axis(sms,idx,axis,degree) for degree in range(num_degrees)] for axis in range(num_axis)] for idx in range(num_data)]
|
|
32
|
+
return signed_measures_reconstructed
|
|
33
|
+
|
|
34
|
+
def save(path:str, signed_measures):
|
|
35
|
+
if isinstance(signed_measures[0][0], tuple):
|
|
36
|
+
save_without_axis(path=path,signed_measures=signed_measures)
|
|
37
|
+
else:
|
|
38
|
+
save_with_axis(path=path,signed_measures=signed_measures)
|
|
39
|
+
|
|
40
|
+
def load(path:str):
|
|
41
|
+
sms = np.load(path)
|
|
42
|
+
item=None
|
|
43
|
+
for i in sms.keys():
|
|
44
|
+
item=i
|
|
45
|
+
break
|
|
46
|
+
n = len(item.split('_'))
|
|
47
|
+
match n:
|
|
48
|
+
case 2:
|
|
49
|
+
return load_without_axis(sms)
|
|
50
|
+
case 3:
|
|
51
|
+
return load_with_axis(sms)
|
|
52
|
+
case _:
|
|
53
|
+
raise Exception("Invalid Signed Measure !")
|
multipers/plots.py
ADDED
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
import matplotlib.pyplot as plt
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def _plot_rectangle(rectangle: np.ndarray, weight, **plt_kwargs):
|
|
8
|
+
rectangle = np.asarray(rectangle)
|
|
9
|
+
x_axis = rectangle[[0, 2]]
|
|
10
|
+
y_axis = rectangle[[1, 3]]
|
|
11
|
+
color = "blue" if weight > 0 else "red"
|
|
12
|
+
plt.plot(x_axis, y_axis, c=color, **plt_kwargs)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _plot_signed_measure_2(
|
|
16
|
+
pts, weights, temp_alpha=0.7, threshold=(np.inf, np.inf), **plt_kwargs
|
|
17
|
+
):
|
|
18
|
+
import matplotlib.colors
|
|
19
|
+
|
|
20
|
+
pts = np.clip(pts, a_min=-np.inf, a_max=np.asarray(threshold)[None, :])
|
|
21
|
+
weights = np.asarray(weights)
|
|
22
|
+
color_weights = np.array(weights, dtype=float)
|
|
23
|
+
neg_idx = weights < 0
|
|
24
|
+
pos_idx = weights > 0
|
|
25
|
+
if np.any(neg_idx):
|
|
26
|
+
current_weights = -weights[neg_idx]
|
|
27
|
+
min_weight = np.max(current_weights)
|
|
28
|
+
color_weights[neg_idx] /= min_weight
|
|
29
|
+
color_weights[neg_idx] -= 1
|
|
30
|
+
else:
|
|
31
|
+
min_weight = 0
|
|
32
|
+
|
|
33
|
+
if np.any(pos_idx):
|
|
34
|
+
current_weights = weights[pos_idx]
|
|
35
|
+
max_weight = np.max(current_weights)
|
|
36
|
+
color_weights[pos_idx] /= max_weight
|
|
37
|
+
color_weights[pos_idx] += 1
|
|
38
|
+
else:
|
|
39
|
+
max_weight = 1
|
|
40
|
+
|
|
41
|
+
bordeaux = np.array([0.70567316, 0.01555616, 0.15023281, 1])
|
|
42
|
+
light_bordeaux = np.array([0.70567316, 0.01555616, 0.15023281, temp_alpha])
|
|
43
|
+
bleu = np.array([0.2298057, 0.29871797, 0.75368315, 1])
|
|
44
|
+
light_bleu = np.array([0.2298057, 0.29871797, 0.75368315, temp_alpha])
|
|
45
|
+
norm = plt.Normalize(-2, 2)
|
|
46
|
+
cmap = matplotlib.colors.LinearSegmentedColormap.from_list(
|
|
47
|
+
"", [bordeaux, light_bordeaux, "white", light_bleu, bleu]
|
|
48
|
+
)
|
|
49
|
+
plt.scatter(
|
|
50
|
+
pts[:, 0], pts[:, 1], c=color_weights, cmap=cmap, norm=norm, **plt_kwargs
|
|
51
|
+
)
|
|
52
|
+
plt.scatter([], [], color=bleu, label="positive mass", **plt_kwargs)
|
|
53
|
+
plt.scatter([], [], color=bordeaux, label="negative mass", **plt_kwargs)
|
|
54
|
+
plt.legend()
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _plot_signed_measure_4(
|
|
58
|
+
pts,
|
|
59
|
+
weights,
|
|
60
|
+
x_smoothing: float = 1,
|
|
61
|
+
area_alpha: bool = True,
|
|
62
|
+
threshold=(np.inf, np.inf),
|
|
63
|
+
alpha=None,
|
|
64
|
+
**plt_kwargs, # ignored ftm
|
|
65
|
+
):
|
|
66
|
+
# compute the maximal rectangle area
|
|
67
|
+
pts = np.clip(pts, a_min=-np.inf, a_max=np.array((*threshold, *threshold))[None, :])
|
|
68
|
+
alpha_rescaling = 0
|
|
69
|
+
for rectangle, weight in zip(pts, weights):
|
|
70
|
+
if rectangle[2] > x_smoothing * rectangle[0]:
|
|
71
|
+
alpha_rescaling = max(
|
|
72
|
+
alpha_rescaling,
|
|
73
|
+
(rectangle[2] / x_smoothing - rectangle[0])
|
|
74
|
+
* (rectangle[3] - rectangle[1]),
|
|
75
|
+
)
|
|
76
|
+
# draw the rectangles
|
|
77
|
+
for rectangle, weight in zip(pts, weights):
|
|
78
|
+
# draw only the rectangles that have not been reduced to the empty set
|
|
79
|
+
if rectangle[2] > x_smoothing * rectangle[0]:
|
|
80
|
+
# make the alpha channel proportional to the rectangle's area
|
|
81
|
+
if area_alpha:
|
|
82
|
+
_plot_rectangle(
|
|
83
|
+
rectangle=[
|
|
84
|
+
rectangle[0],
|
|
85
|
+
rectangle[1],
|
|
86
|
+
rectangle[2] / x_smoothing,
|
|
87
|
+
rectangle[3],
|
|
88
|
+
],
|
|
89
|
+
weight=weight,
|
|
90
|
+
alpha=(
|
|
91
|
+
(rectangle[2] / x_smoothing - rectangle[0])
|
|
92
|
+
* (rectangle[3] - rectangle[1])
|
|
93
|
+
/ alpha_rescaling
|
|
94
|
+
if alpha is None
|
|
95
|
+
else alpha
|
|
96
|
+
),
|
|
97
|
+
**plt_kwargs,
|
|
98
|
+
)
|
|
99
|
+
else:
|
|
100
|
+
_plot_rectangle(
|
|
101
|
+
rectangle=[
|
|
102
|
+
rectangle[0],
|
|
103
|
+
rectangle[1],
|
|
104
|
+
rectangle[2] / x_smoothing,
|
|
105
|
+
rectangle[3],
|
|
106
|
+
],
|
|
107
|
+
weight=weight,
|
|
108
|
+
alpha=1 if alpha is None else alpha,
|
|
109
|
+
**plt_kwargs,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def plot_signed_measure(signed_measure, threshold=None, ax=None, **plt_kwargs):
|
|
114
|
+
if ax is None:
|
|
115
|
+
ax = plt.gca()
|
|
116
|
+
else:
|
|
117
|
+
plt.sca(ax)
|
|
118
|
+
pts, weights = signed_measure
|
|
119
|
+
pts = np.asarray(pts)
|
|
120
|
+
num_pts = pts.shape[0]
|
|
121
|
+
num_parameters = pts.shape[1]
|
|
122
|
+
if threshold is None:
|
|
123
|
+
if num_pts == 0:
|
|
124
|
+
threshold = (np.inf, np.inf)
|
|
125
|
+
else:
|
|
126
|
+
if num_parameters == 4:
|
|
127
|
+
pts_ = np.concatenate([pts[:, :2], pts[:, 2:]], axis=0)
|
|
128
|
+
else:
|
|
129
|
+
pts_ = pts
|
|
130
|
+
threshold = np.max(np.ma.masked_invalid(pts_), axis=0)
|
|
131
|
+
if isinstance(pts, np.ndarray):
|
|
132
|
+
pass
|
|
133
|
+
else:
|
|
134
|
+
import torch
|
|
135
|
+
|
|
136
|
+
if isinstance(pts, torch.Tensor):
|
|
137
|
+
pts = pts.detach().numpy()
|
|
138
|
+
else:
|
|
139
|
+
raise Exception("Invalid measure type.")
|
|
140
|
+
|
|
141
|
+
assert num_parameters in (2, 4)
|
|
142
|
+
if num_parameters == 2:
|
|
143
|
+
_plot_signed_measure_2(
|
|
144
|
+
pts=pts, weights=weights, threshold=threshold, **plt_kwargs
|
|
145
|
+
)
|
|
146
|
+
else:
|
|
147
|
+
_plot_signed_measure_4(
|
|
148
|
+
pts=pts, weights=weights, threshold=threshold, **plt_kwargs
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def plot_signed_measures(signed_measures, threshold=None, size=4):
|
|
153
|
+
num_degrees = len(signed_measures)
|
|
154
|
+
if num_degrees <= 1:
|
|
155
|
+
axes = [plt.gca()]
|
|
156
|
+
else:
|
|
157
|
+
fig, axes = plt.subplots(
|
|
158
|
+
nrows=1, ncols=num_degrees, figsize=(num_degrees * size, size)
|
|
159
|
+
)
|
|
160
|
+
for ax, signed_measure in zip(axes, signed_measures):
|
|
161
|
+
plot_signed_measure(signed_measure=signed_measure, ax=ax, threshold=threshold)
|
|
162
|
+
plt.tight_layout()
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def plot_surface(
|
|
166
|
+
grid,
|
|
167
|
+
hf,
|
|
168
|
+
fig=None,
|
|
169
|
+
ax=None,
|
|
170
|
+
cmap: Optional[str] = None,
|
|
171
|
+
discrete_surface=False,
|
|
172
|
+
has_negative_values=False,
|
|
173
|
+
**plt_args,
|
|
174
|
+
):
|
|
175
|
+
import matplotlib
|
|
176
|
+
|
|
177
|
+
if ax is None:
|
|
178
|
+
ax = plt.gca()
|
|
179
|
+
else:
|
|
180
|
+
plt.sca(ax)
|
|
181
|
+
if hf.ndim == 3 and hf.shape[0] == 1:
|
|
182
|
+
hf = hf[0]
|
|
183
|
+
assert hf.ndim == 2, "Can only plot a 2d surface"
|
|
184
|
+
fig = plt.gcf() if fig is None else fig
|
|
185
|
+
if cmap is None:
|
|
186
|
+
if discrete_surface:
|
|
187
|
+
cmap = matplotlib.colormaps["gray_r"]
|
|
188
|
+
else:
|
|
189
|
+
cmap = matplotlib.colormaps["plasma"]
|
|
190
|
+
if discrete_surface:
|
|
191
|
+
if has_negative_values:
|
|
192
|
+
bounds = np.arange(-5, 6, 1, dtype=int)
|
|
193
|
+
else:
|
|
194
|
+
bounds = np.arange(0, 11, 1, dtype=int)
|
|
195
|
+
norm = matplotlib.colors.BoundaryNorm(bounds, cmap.N, extend="max")
|
|
196
|
+
im = ax.pcolormesh(grid[0], grid[1], hf.T, cmap=cmap, norm=norm, **plt_args)
|
|
197
|
+
cbar = fig.colorbar(
|
|
198
|
+
matplotlib.cm.ScalarMappable(cmap=cmap, norm=norm),
|
|
199
|
+
spacing="proportional",
|
|
200
|
+
ax=ax,
|
|
201
|
+
)
|
|
202
|
+
cbar.set_ticks(ticks=bounds, labels=bounds)
|
|
203
|
+
return im
|
|
204
|
+
im = ax.pcolormesh(grid[0], grid[1], hf.T, cmap=cmap, **plt_args)
|
|
205
|
+
return im
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def plot_surfaces(HF, size=4, **plt_args):
|
|
209
|
+
grid, hf = HF
|
|
210
|
+
assert (
|
|
211
|
+
hf.ndim == 3
|
|
212
|
+
), f"Found hf.shape = {hf.shape}, expected ndim = 3 : degree, 2-parameter surface."
|
|
213
|
+
num_degrees = hf.shape[0]
|
|
214
|
+
fig, axes = plt.subplots(
|
|
215
|
+
nrows=1, ncols=num_degrees, figsize=(num_degrees * size, size)
|
|
216
|
+
)
|
|
217
|
+
if num_degrees == 1:
|
|
218
|
+
axes = [axes]
|
|
219
|
+
for ax, hf_of_degree in zip(axes, hf):
|
|
220
|
+
plot_surface(grid=grid, hf=hf_of_degree, fig=fig, ax=ax, **plt_args)
|
|
221
|
+
plt.tight_layout()
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def _rectangle(x, y, color, alpha):
|
|
225
|
+
"""
|
|
226
|
+
Defines a rectangle patch in the format {z | x ≤ z ≤ y} with color and alpha
|
|
227
|
+
"""
|
|
228
|
+
from matplotlib.patches import Rectangle as RectanglePatch
|
|
229
|
+
|
|
230
|
+
return RectanglePatch(
|
|
231
|
+
x, max(y[0] - x[0], 0), max(y[1] - x[1], 0), color=color, alpha=alpha
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def _d_inf(a, b):
|
|
236
|
+
if type(a) != np.ndarray or type(b) != np.ndarray:
|
|
237
|
+
a = np.array(a)
|
|
238
|
+
b = np.array(b)
|
|
239
|
+
return np.min(np.abs(b - a))
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def plot2d_PyModule(
|
|
243
|
+
corners,
|
|
244
|
+
box,
|
|
245
|
+
*,
|
|
246
|
+
dimension=-1,
|
|
247
|
+
separated=False,
|
|
248
|
+
min_persistence=0,
|
|
249
|
+
alpha=1,
|
|
250
|
+
verbose=False,
|
|
251
|
+
save=False,
|
|
252
|
+
dpi=200,
|
|
253
|
+
shapely=True,
|
|
254
|
+
xlabel=None,
|
|
255
|
+
ylabel=None,
|
|
256
|
+
cmap=None,
|
|
257
|
+
):
|
|
258
|
+
import matplotlib
|
|
259
|
+
|
|
260
|
+
try:
|
|
261
|
+
from shapely import union_all
|
|
262
|
+
from shapely.geometry import Polygon as _Polygon
|
|
263
|
+
from shapely.geometry import box as _rectangle_box
|
|
264
|
+
|
|
265
|
+
shapely = True and shapely
|
|
266
|
+
except ImportError:
|
|
267
|
+
from warnings import warn
|
|
268
|
+
|
|
269
|
+
shapely = False
|
|
270
|
+
warn(
|
|
271
|
+
"Shapely not installed. Fallbacking to matplotlib. The plots may be inacurate."
|
|
272
|
+
)
|
|
273
|
+
cmap = (
|
|
274
|
+
matplotlib.colormaps["Spectral"] if cmap is None else matplotlib.colormaps[cmap]
|
|
275
|
+
)
|
|
276
|
+
box = list(box)
|
|
277
|
+
if not (separated):
|
|
278
|
+
# fig, ax = plt.subplots()
|
|
279
|
+
ax = plt.gca()
|
|
280
|
+
ax.set(xlim=[box[0][0], box[1][0]], ylim=[box[0][1], box[1][1]])
|
|
281
|
+
n_summands = len(corners)
|
|
282
|
+
for i in range(n_summands):
|
|
283
|
+
trivial_summand = True
|
|
284
|
+
list_of_rect = []
|
|
285
|
+
for birth in corners[i][0]:
|
|
286
|
+
if len(birth) == 1:
|
|
287
|
+
birth = np.asarray([birth[0]] * 2)
|
|
288
|
+
birth = np.asarray(birth).clip(min=box[0])
|
|
289
|
+
for death in corners[i][1]:
|
|
290
|
+
if len(death) == 1:
|
|
291
|
+
death = np.asarray([death[0]] * 2)
|
|
292
|
+
death = np.asarray(death).clip(max=box[1])
|
|
293
|
+
if death[1] > birth[1] and death[0] > birth[0]:
|
|
294
|
+
if trivial_summand and _d_inf(birth, death) > min_persistence:
|
|
295
|
+
trivial_summand = False
|
|
296
|
+
if shapely:
|
|
297
|
+
list_of_rect.append(
|
|
298
|
+
_rectangle_box(birth[0], birth[1], death[0], death[1])
|
|
299
|
+
)
|
|
300
|
+
else:
|
|
301
|
+
list_of_rect.append(
|
|
302
|
+
_rectangle(birth, death, cmap(i / n_summands), alpha)
|
|
303
|
+
)
|
|
304
|
+
if not (trivial_summand):
|
|
305
|
+
if separated:
|
|
306
|
+
fig, ax = plt.subplots()
|
|
307
|
+
ax.set(xlim=[box[0][0], box[1][0]], ylim=[box[0][1], box[1][1]])
|
|
308
|
+
if shapely:
|
|
309
|
+
summand_shape = union_all(list_of_rect)
|
|
310
|
+
if type(summand_shape) is _Polygon:
|
|
311
|
+
xs, ys = summand_shape.exterior.xy
|
|
312
|
+
ax.fill(xs, ys, alpha=alpha, fc=cmap(i / n_summands), ec="None")
|
|
313
|
+
else:
|
|
314
|
+
for polygon in summand_shape.geoms:
|
|
315
|
+
xs, ys = polygon.exterior.xy
|
|
316
|
+
ax.fill(xs, ys, alpha=alpha, fc=cmap(i / n_summands), ec="None")
|
|
317
|
+
else:
|
|
318
|
+
for rectangle in list_of_rect:
|
|
319
|
+
ax.add_patch(rectangle)
|
|
320
|
+
if separated:
|
|
321
|
+
if xlabel:
|
|
322
|
+
plt.xlabel(xlabel)
|
|
323
|
+
if ylabel:
|
|
324
|
+
plt.ylabel(ylabel)
|
|
325
|
+
if dimension >= 0:
|
|
326
|
+
plt.title(rf"$H_{dimension}$ $2$-persistence")
|
|
327
|
+
if not (separated):
|
|
328
|
+
if xlabel is not None:
|
|
329
|
+
plt.xlabel(xlabel)
|
|
330
|
+
if ylabel is not None:
|
|
331
|
+
plt.ylabel(ylabel)
|
|
332
|
+
if dimension >= 0:
|
|
333
|
+
plt.title(rf"$H_{dimension}$ $2$-persistence")
|
|
334
|
+
return
|
|
Binary file
|