multipers 2.3.3b6__cp311-cp311-manylinux_2_39_x86_64.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 -0
- multipers/_signed_measure_meta.py +453 -0
- multipers/_slicer_meta.py +211 -0
- multipers/array_api/__init__.py +45 -0
- multipers/array_api/numpy.py +41 -0
- multipers/array_api/torch.py +58 -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 +229 -0
- multipers/filtration_conversions.pxd.tp +84 -0
- multipers/filtrations/__init__.py +18 -0
- multipers/filtrations/density.py +574 -0
- multipers/filtrations/filtrations.py +361 -0
- multipers/filtrations.pxd +224 -0
- multipers/function_rips.cpython-311-x86_64-linux-gnu.so +0 -0
- multipers/function_rips.pyx +105 -0
- multipers/grids.cpython-311-x86_64-linux-gnu.so +0 -0
- multipers/grids.pyx +433 -0
- multipers/gudhi/Persistence_slices_interface.h +132 -0
- multipers/gudhi/Simplex_tree_interface.h +239 -0
- multipers/gudhi/Simplex_tree_multi_interface.h +551 -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 +174 -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 +1441 -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 +152 -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 +256 -0
- multipers/gudhi/mma_interface_h0.h +223 -0
- multipers/gudhi/mma_interface_matrix.h +293 -0
- multipers/gudhi/naive_merge_tree.h +536 -0
- multipers/gudhi/scc_io.h +310 -0
- multipers/gudhi/truc.h +1403 -0
- multipers/io.cpython-311-x86_64-linux-gnu.so +0 -0
- multipers/io.pyx +644 -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 +1589 -0
- multipers/ml/sliced_wasserstein.py +461 -0
- multipers/ml/tools.py +113 -0
- multipers/mma_structures.cpython-311-x86_64-linux-gnu.so +0 -0
- multipers/mma_structures.pxd +128 -0
- multipers/mma_structures.pyx +2786 -0
- multipers/mma_structures.pyx.tp +1094 -0
- multipers/multi_parameter_rank_invariant/diff_helpers.h +84 -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 +2330 -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 +403 -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.cpython-311-x86_64-linux-gnu.so +0 -0
- multipers/multiparameter_module_approximation.pyx +235 -0
- multipers/pickle.py +90 -0
- multipers/plots.py +456 -0
- multipers/point_measure.cpython-311-x86_64-linux-gnu.so +0 -0
- multipers/point_measure.pyx +395 -0
- multipers/simplex_tree_multi.cpython-311-x86_64-linux-gnu.so +0 -0
- multipers/simplex_tree_multi.pxd +134 -0
- multipers/simplex_tree_multi.pyx +10840 -0
- multipers/simplex_tree_multi.pyx.tp +2009 -0
- multipers/slicer.cpython-311-x86_64-linux-gnu.so +0 -0
- multipers/slicer.pxd +3034 -0
- multipers/slicer.pxd.tp +234 -0
- multipers/slicer.pyx +20481 -0
- multipers/slicer.pyx.tp +1088 -0
- multipers/tensor/tensor.h +672 -0
- multipers/tensor.pxd +13 -0
- multipers/test.pyx +44 -0
- multipers/tests/__init__.py +62 -0
- multipers/torch/__init__.py +1 -0
- multipers/torch/diff_grids.py +240 -0
- multipers/torch/rips_density.py +310 -0
- multipers-2.3.3b6.dist-info/METADATA +128 -0
- multipers-2.3.3b6.dist-info/RECORD +182 -0
- multipers-2.3.3b6.dist-info/WHEEL +5 -0
- multipers-2.3.3b6.dist-info/licenses/LICENSE +21 -0
- multipers-2.3.3b6.dist-info/top_level.txt +1 -0
- multipers.libs/libtbb-ca48af5c.so.12.16 +0 -0
|
@@ -0,0 +1,2786 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
"""!
|
|
7
|
+
@package mma
|
|
8
|
+
@brief Files containing the C++ cythonized functions.
|
|
9
|
+
@author David Loiseaux
|
|
10
|
+
@copyright Copyright (c) 2022 Inria.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
# distutils: language = c++
|
|
14
|
+
|
|
15
|
+
###########################################################################
|
|
16
|
+
## PYTHON LIBRARIES
|
|
17
|
+
import gudhi as gd
|
|
18
|
+
import numpy as np
|
|
19
|
+
from typing import Union, Literal
|
|
20
|
+
from collections.abc import Callable, Iterable, Sequence
|
|
21
|
+
import pickle
|
|
22
|
+
import multipers.grids as mpg
|
|
23
|
+
|
|
24
|
+
###########################################################################
|
|
25
|
+
## CPP CLASSES
|
|
26
|
+
from libc.stdint cimport intptr_t
|
|
27
|
+
from libc.stdint cimport uintptr_t
|
|
28
|
+
|
|
29
|
+
###########################################################################
|
|
30
|
+
## CYTHON TYPES
|
|
31
|
+
from libcpp.vector cimport vector
|
|
32
|
+
from libcpp.utility cimport pair
|
|
33
|
+
#from libcpp.list cimport list as clist
|
|
34
|
+
from libcpp cimport bool
|
|
35
|
+
from libcpp cimport int
|
|
36
|
+
from cython.operator cimport dereference
|
|
37
|
+
from libcpp.utility cimport move
|
|
38
|
+
cimport cython
|
|
39
|
+
#########################################################################
|
|
40
|
+
## Multipersistence Module Approximation Classes
|
|
41
|
+
from multipers.mma_structures cimport *
|
|
42
|
+
from multipers.filtration_conversions cimport *
|
|
43
|
+
cimport numpy as cnp
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
#########################################################################
|
|
47
|
+
## Small hack for typing
|
|
48
|
+
from gudhi import SimplexTree
|
|
49
|
+
from multipers.simplex_tree_multi import SimplexTreeMulti
|
|
50
|
+
from joblib import Parallel, delayed
|
|
51
|
+
|
|
52
|
+
available_pymodules = [
|
|
53
|
+
PyModule_f64,
|
|
54
|
+
PyModule_f32,
|
|
55
|
+
PyModule_i32,
|
|
56
|
+
PyModule_i64,
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
PyModule_type = Union[
|
|
60
|
+
PyModule_f64,
|
|
61
|
+
PyModule_f32,
|
|
62
|
+
PyModule_i32,
|
|
63
|
+
PyModule_i64,
|
|
64
|
+
]
|
|
65
|
+
cdef class PySummand_f64:
|
|
66
|
+
r"""
|
|
67
|
+
Stores a Summand of a PyModule
|
|
68
|
+
"""
|
|
69
|
+
cdef Summand[double] sum
|
|
70
|
+
|
|
71
|
+
def get_birth_list(self):
|
|
72
|
+
cdef vector[One_critical_filtration[double]] v = self.sum.get_birth_list()
|
|
73
|
+
return _vff21cview_f64(v, copy = True, duplicate = self.num_parameters())
|
|
74
|
+
|
|
75
|
+
def get_death_list(self):
|
|
76
|
+
cdef vector[One_critical_filtration[double]] v = self.sum.get_death_list()
|
|
77
|
+
return _vff21cview_f64(v, copy = True, duplicate = self.num_parameters())
|
|
78
|
+
@property
|
|
79
|
+
def degree(self)->int:
|
|
80
|
+
return self.sum.get_dimension()
|
|
81
|
+
|
|
82
|
+
cdef set(self, Summand[double]& summand):
|
|
83
|
+
self.sum = summand
|
|
84
|
+
return self
|
|
85
|
+
def get_bounds(self):
|
|
86
|
+
cdef pair[One_critical_filtration[double],One_critical_filtration[double]] cbounds
|
|
87
|
+
with nogil:
|
|
88
|
+
cbounds = self.sum.get_bounds().get_bounding_corners()
|
|
89
|
+
return _ff21cview_f64(&cbounds.first).copy(), _ff21cview_f64(&cbounds.second).copy()
|
|
90
|
+
@property
|
|
91
|
+
def dtype(self):
|
|
92
|
+
return np.float64
|
|
93
|
+
|
|
94
|
+
def num_parameters(self):
|
|
95
|
+
cdef vector[One_critical_filtration[double]] v = self.sum.get_birth_list()
|
|
96
|
+
if v[0].is_finite():
|
|
97
|
+
return v[0].num_parameters()
|
|
98
|
+
v = self.sum.get_death_list()
|
|
99
|
+
return v[0].num_parameters()
|
|
100
|
+
def __eq__(self, PySummand_f64 other):
|
|
101
|
+
return self.sum == other.sum
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
cdef inline get_summand_filtration_values_f64(Summand[double] summand):
|
|
107
|
+
r"""
|
|
108
|
+
Returns a list (over parameter) of the filtrations values of this parameter.
|
|
109
|
+
"""
|
|
110
|
+
cdef vector[One_critical_filtration[double]] vb = summand.get_birth_list()
|
|
111
|
+
cdef vector[One_critical_filtration[double]] vd = summand.get_death_list()
|
|
112
|
+
|
|
113
|
+
if vb[0].is_finite():
|
|
114
|
+
if vd[0].is_finite():
|
|
115
|
+
pts = np.concatenate([_vff21cview_f64(vb, copy=True),
|
|
116
|
+
_vff21cview_f64(vd, copy=True)],axis=0)
|
|
117
|
+
else:
|
|
118
|
+
pts = np.array(_vff21cview_f64(vb, copy=True))
|
|
119
|
+
else:
|
|
120
|
+
if vd[0].is_finite():
|
|
121
|
+
pts = np.array(_vff21cview_f64(vd, copy=True))
|
|
122
|
+
else:
|
|
123
|
+
return []
|
|
124
|
+
|
|
125
|
+
num_parameters = pts.shape[1]
|
|
126
|
+
out = [np.unique(pts[:,parameter]) for parameter in range(num_parameters)]
|
|
127
|
+
out = [f[:-1] if f[-1] == np.inf else f for f in out]
|
|
128
|
+
out = [f[1:] if f[0] == -np.inf else f for f in out]
|
|
129
|
+
return out
|
|
130
|
+
|
|
131
|
+
cdef class PyBox_f64:
|
|
132
|
+
cdef Box[double] box
|
|
133
|
+
def __cinit__(self, vector[double]& bottomCorner, vector[double]& topCorner):
|
|
134
|
+
self.box = Box[double](bottomCorner, topCorner)
|
|
135
|
+
@property
|
|
136
|
+
def num_parameters(self):
|
|
137
|
+
cdef size_t dim = self.box.get_lower_corner().num_parameters()
|
|
138
|
+
if dim == self.box.get_upper_corner().num_parameters(): return dim
|
|
139
|
+
else: print("Bad box definition.")
|
|
140
|
+
def contains(self, x):
|
|
141
|
+
return self.box.contains(x)
|
|
142
|
+
cdef set(self, Box[double]& b):
|
|
143
|
+
self.box = b
|
|
144
|
+
return self
|
|
145
|
+
|
|
146
|
+
def get(self):
|
|
147
|
+
return [<vector[double]>self.box.get_lower_corner(), <vector[double]>self.box.get_upper_corner()]
|
|
148
|
+
def to_multipers(self):
|
|
149
|
+
#assert (self.get_dimension() == 2) "Multipers only works in dimension 2 !"
|
|
150
|
+
return np.array(self.get()).flatten(order = 'F')
|
|
151
|
+
@property
|
|
152
|
+
def dtype(self):
|
|
153
|
+
return np.float64
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
cdef class PyModule_f64:
|
|
158
|
+
r"""
|
|
159
|
+
Stores a representation of a n-persistence module.
|
|
160
|
+
"""
|
|
161
|
+
cdef Module[double] cmod
|
|
162
|
+
|
|
163
|
+
@property
|
|
164
|
+
def dtype(self):
|
|
165
|
+
return np.float64
|
|
166
|
+
|
|
167
|
+
cdef set(self, Module[double] m):
|
|
168
|
+
self.cmod = m
|
|
169
|
+
|
|
170
|
+
def __eq__(self, PyModule_f64 other):
|
|
171
|
+
return self.cmod == other.cmod
|
|
172
|
+
|
|
173
|
+
def merge(self, PyModule_f64 other, int dim=-1):
|
|
174
|
+
r"""
|
|
175
|
+
Merges two modules into one
|
|
176
|
+
"""
|
|
177
|
+
cdef Module[double] c_other = other.cmod
|
|
178
|
+
with nogil:
|
|
179
|
+
for summand in c_other:
|
|
180
|
+
self.cmod.add_summand(summand, dim)
|
|
181
|
+
return self
|
|
182
|
+
|
|
183
|
+
def _set_from_ptr(self, intptr_t module_ptr):
|
|
184
|
+
r"""
|
|
185
|
+
Copy module from a memory pointer. Unsafe.
|
|
186
|
+
"""
|
|
187
|
+
self.cmod = move(dereference(<Module[double]*>(module_ptr)))
|
|
188
|
+
def set_box(self, box):
|
|
189
|
+
assert len(box) == 2, "Box format is [low, hight]"
|
|
190
|
+
pybox = PyBox_f64(box[0], box[1])
|
|
191
|
+
cdef Box[double] cbox = pybox.box
|
|
192
|
+
with nogil:
|
|
193
|
+
self.cmod.set_box(cbox)
|
|
194
|
+
return self
|
|
195
|
+
def get_module_of_degree(self, int degree)->PyModule_f64: # TODO : in c++ ?
|
|
196
|
+
r"""
|
|
197
|
+
Returns a copy of a module of fixed degree.
|
|
198
|
+
"""
|
|
199
|
+
pmodule = PyModule_f64()
|
|
200
|
+
cdef Box[double] c_box = self.cmod.get_box()
|
|
201
|
+
pmodule.cmod.set_box(c_box)
|
|
202
|
+
with nogil:
|
|
203
|
+
for summand in self.cmod:
|
|
204
|
+
if summand.get_dimension() == degree:
|
|
205
|
+
pmodule.cmod.add_summand(summand)
|
|
206
|
+
return pmodule
|
|
207
|
+
def get_module_of_degrees(self, degrees:Iterable[int])->PyModule_f64: # TODO : in c++ ?
|
|
208
|
+
r"""
|
|
209
|
+
Returns a copy of the summands of degrees in `degrees`
|
|
210
|
+
"""
|
|
211
|
+
pmodule = PyModule_f64()
|
|
212
|
+
cdef Box[double] c_box = self.cmod.get_box()
|
|
213
|
+
pmodule.cmod.set_box(c_box)
|
|
214
|
+
cdef vector[int] cdegrees = degrees
|
|
215
|
+
with nogil:
|
|
216
|
+
for summand in self.cmod:
|
|
217
|
+
for d in cdegrees:
|
|
218
|
+
if d == summand.get_dimension():
|
|
219
|
+
pmodule.cmod.add_summand(summand)
|
|
220
|
+
return pmodule
|
|
221
|
+
def __len__(self)->int:
|
|
222
|
+
return self.cmod.size()
|
|
223
|
+
def get_bottom(self)->np.ndarray:
|
|
224
|
+
r"""
|
|
225
|
+
Bottom of the box of the module
|
|
226
|
+
"""
|
|
227
|
+
return np.asarray(<vector[double]>(self.cmod.get_box().get_lower_corner()))
|
|
228
|
+
def get_top(self)->np.ndarray:
|
|
229
|
+
r"""
|
|
230
|
+
Top of the box of the module
|
|
231
|
+
"""
|
|
232
|
+
return np.asarray(<vector[double]>(self.cmod.get_box().get_upper_corner()))
|
|
233
|
+
def get_box(self)->np.ndarray:
|
|
234
|
+
r"""
|
|
235
|
+
Returns the current bounding box of the module.
|
|
236
|
+
"""
|
|
237
|
+
return np.asarray([self.get_bottom(), self.get_top()])
|
|
238
|
+
@property
|
|
239
|
+
def max_degree(self)->int:
|
|
240
|
+
r"""
|
|
241
|
+
Returns the maximum degree of the module.
|
|
242
|
+
"""
|
|
243
|
+
return self.cmod.get_dimension()
|
|
244
|
+
@property
|
|
245
|
+
def num_parameters(self)->int:
|
|
246
|
+
cdef size_t dim = self.cmod.get_box().get_lower_corner().num_parameters()
|
|
247
|
+
assert dim == self.cmod.get_box().get_upper_corner().num_parameters(), "Bad box definition, cannot infer num_parameters."
|
|
248
|
+
return dim
|
|
249
|
+
def dump(self, path:str|None=None):
|
|
250
|
+
r"""
|
|
251
|
+
Dumps the module into a pickle-able format.
|
|
252
|
+
|
|
253
|
+
Parameters
|
|
254
|
+
----------
|
|
255
|
+
|
|
256
|
+
path:str=None (optional) saves the pickled module in specified path
|
|
257
|
+
|
|
258
|
+
Returns
|
|
259
|
+
-------
|
|
260
|
+
|
|
261
|
+
list of list, encoding the module, which can be retrieved with the function `from_dump`.
|
|
262
|
+
"""
|
|
263
|
+
## TODO : optimize, but not really used.
|
|
264
|
+
return dump_cmod_f64(self.cmod)
|
|
265
|
+
def __getstate__(self):
|
|
266
|
+
return self.dump()
|
|
267
|
+
def __setstate__(self,dump):
|
|
268
|
+
cdef Module[double] cmod = cmod_from_dump_f64(dump)
|
|
269
|
+
self.cmod = cmod
|
|
270
|
+
return
|
|
271
|
+
def __getitem__(self, int i) -> PySummand_f64:
|
|
272
|
+
if i == slice(None):
|
|
273
|
+
return self
|
|
274
|
+
summand = PySummand_f64()
|
|
275
|
+
summand.set(self.cmod.at(i % self.cmod.size()))
|
|
276
|
+
return summand
|
|
277
|
+
def __iter__(self):
|
|
278
|
+
cdef int num_summands = self.cmod.size()
|
|
279
|
+
for i in range(num_summands):
|
|
280
|
+
summand = PySummand_f64()
|
|
281
|
+
summand.set(self.cmod.at(i)) ## hmm copy. maybe do summand from ptr
|
|
282
|
+
yield summand
|
|
283
|
+
|
|
284
|
+
def get_bounds(self):
|
|
285
|
+
r"""
|
|
286
|
+
Computes bounds from the summands' bounds.
|
|
287
|
+
Useful to change this' box.
|
|
288
|
+
"""
|
|
289
|
+
cdef pair[One_critical_filtration[double],One_critical_filtration[double]] cbounds
|
|
290
|
+
with nogil:
|
|
291
|
+
cbounds = self.cmod.get_bounds().get_bounding_corners()
|
|
292
|
+
return _ff21cview_f64(&cbounds.first).copy(), _ff21cview_f64(&cbounds.second).copy()
|
|
293
|
+
def rescale(self,rescale_factors, int degree=-1):
|
|
294
|
+
r"""
|
|
295
|
+
Rescales the fitlration values of the summands by this rescaling vector.
|
|
296
|
+
"""
|
|
297
|
+
cdef vector[double] crescale_factors = rescale_factors
|
|
298
|
+
with nogil:
|
|
299
|
+
self.cmod.rescale(crescale_factors,degree)
|
|
300
|
+
def translate(self,translation, int degree=-1):
|
|
301
|
+
r"""
|
|
302
|
+
Translates the module in the filtration space by this vector.
|
|
303
|
+
"""
|
|
304
|
+
cdef vector[double] ctranslation = translation
|
|
305
|
+
with nogil:
|
|
306
|
+
self.cmod.translate(ctranslation,degree)
|
|
307
|
+
|
|
308
|
+
def get_filtration_values(self, bool unique=True):
|
|
309
|
+
r"""
|
|
310
|
+
Retrieves all filtration values of the summands of the module.
|
|
311
|
+
|
|
312
|
+
Output format
|
|
313
|
+
-------------
|
|
314
|
+
|
|
315
|
+
list of filtration values for parameter.
|
|
316
|
+
"""
|
|
317
|
+
if len(self) ==0:
|
|
318
|
+
return np.empty((self.num_parameters,0))
|
|
319
|
+
values = tuple(tuple(stuff) if len(stuff:=get_summand_filtration_values_f64(summand)) == self.num_parameters else list(stuff) + [[]]*(self.num_parameters - len(stuff)) for summand in self.cmod)
|
|
320
|
+
try:
|
|
321
|
+
values = tuple(np.concatenate([
|
|
322
|
+
f[parameter]
|
|
323
|
+
for f in values
|
|
324
|
+
], axis=0) for parameter in range(self.num_parameters)
|
|
325
|
+
)
|
|
326
|
+
except:
|
|
327
|
+
return values
|
|
328
|
+
if unique:
|
|
329
|
+
return [np.unique(f) for f in values]
|
|
330
|
+
return values
|
|
331
|
+
|
|
332
|
+
def plot(self, int degree=-1,**kwargs)->None:
|
|
333
|
+
r"""Shows the module on a plot. Each color corresponds to an apprimation summand of the module, and its shape corresponds to its support.
|
|
334
|
+
Only works with 2-parameter modules.
|
|
335
|
+
|
|
336
|
+
Parameters
|
|
337
|
+
----------
|
|
338
|
+
degree = -1 : integer
|
|
339
|
+
If positive returns only the image of dimension `dimension`.
|
|
340
|
+
box=None : of the form [[b_x,b_y], [d_x,d_y]] where b,d are the bottom and top corner of the rectangle.
|
|
341
|
+
If non-None, will plot the module on this specific rectangle.
|
|
342
|
+
min_persistence =0 : float
|
|
343
|
+
Only plots the summand with a persistence above this threshold.
|
|
344
|
+
separated=False : bool
|
|
345
|
+
If true, plot each summand in a different plot.
|
|
346
|
+
alpha=1 : float
|
|
347
|
+
Transparancy parameter
|
|
348
|
+
save = False : string
|
|
349
|
+
if nontrivial, will save the figure at this path
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
Returns
|
|
353
|
+
-------
|
|
354
|
+
The figure of the plot.
|
|
355
|
+
"""
|
|
356
|
+
from multipers.plots import plot2d_PyModule
|
|
357
|
+
import matplotlib.pyplot as plt
|
|
358
|
+
box = kwargs.pop('box', self.get_box())
|
|
359
|
+
if (len(box[0]) != 2):
|
|
360
|
+
print("Filtration size :", len(box[0]), " != 2")
|
|
361
|
+
return
|
|
362
|
+
if(degree < 0):
|
|
363
|
+
dims = np.unique(self.get_dimensions())
|
|
364
|
+
separated = kwargs.pop("separated", False)
|
|
365
|
+
ndim = len(dims)
|
|
366
|
+
scale = kwargs.pop("scale", 4)
|
|
367
|
+
if separated:
|
|
368
|
+
fig = None
|
|
369
|
+
axes = None
|
|
370
|
+
elif ndim > 1:
|
|
371
|
+
fig, axes = plt.subplots(1,ndim, figsize=(ndim*scale,scale))
|
|
372
|
+
else:
|
|
373
|
+
fig = plt.gcf()
|
|
374
|
+
axes = [plt.gca()]
|
|
375
|
+
for dim_idx in range(ndim):
|
|
376
|
+
if not separated:
|
|
377
|
+
plt.sca(axes[dim_idx])
|
|
378
|
+
self.plot(dims[dim_idx],box=box, separated = separated, **kwargs)
|
|
379
|
+
return
|
|
380
|
+
corners = self.cmod.get_corners_of_dimension(degree)
|
|
381
|
+
plot2d_PyModule(corners, box=box, dimension=degree, **kwargs)
|
|
382
|
+
return
|
|
383
|
+
def degree_splits(self):
|
|
384
|
+
return np.asarray(self.cmod.get_degree_splits(), dtype=np.int64)
|
|
385
|
+
def _compute_pixels(self,coordinates:np.ndarray,
|
|
386
|
+
degrees=None, box=None, double delta=.1,
|
|
387
|
+
double p=1., bool normalize=False, int n_jobs=0):
|
|
388
|
+
r"""
|
|
389
|
+
Computes the image of the module at the given coordinates
|
|
390
|
+
"""
|
|
391
|
+
if degrees is not None: assert np.all(degrees[:-1] <= degrees[1:]), "Degrees have to be sorted"
|
|
392
|
+
cdef vector[int] cdegrees = np.arange(self.max_degree +1) if degrees is None else degrees
|
|
393
|
+
pybox = PyBox_f64(*self.get_box()) if box is None else PyBox_f64(*box)
|
|
394
|
+
cdef Box[double] cbox = pybox.box
|
|
395
|
+
cdef vector[vector[double]] ccoords = coordinates
|
|
396
|
+
cdef vector[vector[double]] out
|
|
397
|
+
with nogil:
|
|
398
|
+
out = self.cmod.compute_pixels(ccoords, cdegrees, cbox, delta, p, normalize, n_jobs)
|
|
399
|
+
return np.asarray(out)
|
|
400
|
+
def barcode(self, basepoint, int degree = -1,*, bool threshold = False): # TODO direction vector interface
|
|
401
|
+
r"""Computes the barcode of module along a lines.
|
|
402
|
+
|
|
403
|
+
Parameters
|
|
404
|
+
----------
|
|
405
|
+
|
|
406
|
+
basepoint : vector
|
|
407
|
+
basepoint of the lines on which to compute the barcodes, i.e. a point on the line
|
|
408
|
+
degree = -1 : integer
|
|
409
|
+
Homology degree on which to compute the bars. If negative, every dimension is computed
|
|
410
|
+
box (default) :
|
|
411
|
+
box on which to compute the barcodes if basepoints is not specified. Default is a linspace of lines crossing that box.
|
|
412
|
+
threshold = False :
|
|
413
|
+
Thre
|
|
414
|
+
|
|
415
|
+
Warning
|
|
416
|
+
-------
|
|
417
|
+
|
|
418
|
+
If the barcodes are not thresholded, essential barcodes will not be plot-able.
|
|
419
|
+
|
|
420
|
+
Returns
|
|
421
|
+
-------
|
|
422
|
+
|
|
423
|
+
PyMultiDiagrams
|
|
424
|
+
Structure that holds the barcodes. Barcodes can be retrieved with a .get_points() or a .to_multipers() or a .plot().
|
|
425
|
+
"""
|
|
426
|
+
out = PyMultiDiagram_f64()
|
|
427
|
+
out.set(self.cmod.get_barcode(Line[double](_py21c_f64(np.asarray(basepoint, dtype=np.float64))), degree, threshold))
|
|
428
|
+
return out
|
|
429
|
+
@staticmethod
|
|
430
|
+
cdef _threshold_bc(bc):
|
|
431
|
+
return tuple(np.fromiter((a for a in stuff if a[0] < np.inf), dtype=np.dtype((np.float64,2)) ) for stuff in bc)
|
|
432
|
+
@staticmethod
|
|
433
|
+
def _bc_to_full(bcs, basepoint, direction=None):
|
|
434
|
+
# i, (b sv d), coords
|
|
435
|
+
basepoint = np.asarray(basepoint)[None,None,:]
|
|
436
|
+
direction = 1 if direction is None else np.asarray(direction)[None,None,:]
|
|
437
|
+
return tuple(bc[:,:,None]*direction + basepoint for bc in bcs)
|
|
438
|
+
def barcode2(self, basepoint, direction=None, int degree = -1,*, bool threshold = False, bool keep_inf = True, bool full = False): # TODO direction vector interface
|
|
439
|
+
r"""
|
|
440
|
+
Compute the 1d-barcode a diagonal line based on basepoint, with some direction.
|
|
441
|
+
|
|
442
|
+
Parameters
|
|
443
|
+
----------
|
|
444
|
+
|
|
445
|
+
- basepoint: 1d array
|
|
446
|
+
- directiont: 1d array or None, if None: diagonal
|
|
447
|
+
- degree: int the degree to compute (-1 means all)
|
|
448
|
+
- threshold: bool if True, threshold the barcode to the modules box
|
|
449
|
+
- keep_inf: bool if False, removes trivial bars
|
|
450
|
+
Note that this removes the order w.r.t. the summands in that case
|
|
451
|
+
- full:bool if True, returns the coordinates of the barcode instead of the coordinate in the line.
|
|
452
|
+
|
|
453
|
+
The output is of the form
|
|
454
|
+
|
|
455
|
+
tuple[np.ndarray of shape (num_bars,2)] or tuple[array of shape (num_bar, 2, num_parameters)]
|
|
456
|
+
"""
|
|
457
|
+
basepoint = np.asarray(basepoint, dtype=np.float64)
|
|
458
|
+
if direction is None:
|
|
459
|
+
bc = tuple(np.asarray(x).reshape(-1,2) for x in self.cmod.get_barcode2(Line[double](_py21c_f64(basepoint)), degree))
|
|
460
|
+
else:
|
|
461
|
+
direction = np.asarray(direction, dtype = np.float64)
|
|
462
|
+
bc = tuple(np.asarray(x).reshape(-1,2) for x in self.cmod.get_barcode2(Line[double](_py21c_f64(basepoint), _py21c_f64(direction)), degree))
|
|
463
|
+
if not keep_inf:
|
|
464
|
+
bc = PyModule_f64._threshold_bc(bc)
|
|
465
|
+
if full:
|
|
466
|
+
bc = PyModule_f64._bc_to_full(bc, basepoint, direction)
|
|
467
|
+
|
|
468
|
+
return bc
|
|
469
|
+
|
|
470
|
+
def get_dimensions(self):
|
|
471
|
+
cdef int num_summands = len(self)
|
|
472
|
+
out = np.empty(shape=num_summands, dtype=np.int32)
|
|
473
|
+
cdef int32_t[:] c_out = out
|
|
474
|
+
for i in range(num_summands):
|
|
475
|
+
c_out[i] = self.cmod.at(i).get_dimension()
|
|
476
|
+
return out
|
|
477
|
+
|
|
478
|
+
def barcodes(self, int degree, basepoints = None, num=100, box = None,threshold = False):
|
|
479
|
+
r"""Computes barcodes of module along a set of lines.
|
|
480
|
+
|
|
481
|
+
Parameters
|
|
482
|
+
----------
|
|
483
|
+
|
|
484
|
+
basepoints = None : list of vectors
|
|
485
|
+
basepoints of the lines on which to compute the barcodes.
|
|
486
|
+
degree = -1 : integer
|
|
487
|
+
Homology degree on which to compute the bars. If negative, every dimension is computed
|
|
488
|
+
box (default) :
|
|
489
|
+
box on which to compute the barcodes if basepoints is not specified. Default is a linspace of lines crossing that box.
|
|
490
|
+
num:int=100
|
|
491
|
+
if basepoints is not specified, defines the number of lines to consider.
|
|
492
|
+
threshold = False : threshold t
|
|
493
|
+
Resolution of the image(s).
|
|
494
|
+
|
|
495
|
+
Warning
|
|
496
|
+
-------
|
|
497
|
+
|
|
498
|
+
If the barcodes are not thresholded, essential barcodes will not be plot-able.
|
|
499
|
+
|
|
500
|
+
Returns
|
|
501
|
+
-------
|
|
502
|
+
|
|
503
|
+
PyMultiDiagrams
|
|
504
|
+
Structure that holds the barcodes. Barcodes can be retrieved with a .get_points() or a .to_multipers() or a .plot().
|
|
505
|
+
"""
|
|
506
|
+
out = PyMultiDiagrams_f64()
|
|
507
|
+
if box is None:
|
|
508
|
+
box = [self.get_bottom(), self.get_top()]
|
|
509
|
+
if (len(box[0]) != 2) and (basepoints is None):
|
|
510
|
+
raise ValueError("Basepoints has to be specified for filtration dimension >= 3 !")
|
|
511
|
+
elif basepoints is None:
|
|
512
|
+
h = box[1][1] - box[0][1]
|
|
513
|
+
basepoints = np.linspace([box[0][0] - h,box[0][1]], [box[1][0],box[0][1]], num=num)
|
|
514
|
+
else :
|
|
515
|
+
num=len(basepoints)
|
|
516
|
+
|
|
517
|
+
cdef double[:,:] basepoints_view = np.asarray(basepoints, dtype = np.float64)
|
|
518
|
+
cdef vector[One_critical_filtration[double]] cbasepoints = _py2v1c_f64(basepoints_view)
|
|
519
|
+
|
|
520
|
+
out.set(self.cmod.get_barcodes(cbasepoints, degree, threshold))
|
|
521
|
+
return out
|
|
522
|
+
|
|
523
|
+
def barcodes2(self, int degree = -1, basepoints = None, int num=100, box = None, bool threshold = False):
|
|
524
|
+
r"""Computes barcodes of module along a set of lines.
|
|
525
|
+
|
|
526
|
+
Parameters
|
|
527
|
+
----------
|
|
528
|
+
|
|
529
|
+
basepoints = None : list of vectors
|
|
530
|
+
basepoints of the lines on which to compute the barcodes.
|
|
531
|
+
degree = -1 : integer
|
|
532
|
+
Homology degree on which to compute the bars. If negative, every dimension is computed
|
|
533
|
+
box (default) :
|
|
534
|
+
box on which to compute the barcodes if basepoints is not specified. Default is a linspace of lines crossing that box.
|
|
535
|
+
num:int=100
|
|
536
|
+
if basepoints is not specified, defines the number of lines to consider.
|
|
537
|
+
threshold = False : threshold t
|
|
538
|
+
Resolution of the image(s).
|
|
539
|
+
|
|
540
|
+
Warning
|
|
541
|
+
-------
|
|
542
|
+
|
|
543
|
+
If the barcodes are not thresholded, essential barcodes will not be plot-able.
|
|
544
|
+
|
|
545
|
+
Returns
|
|
546
|
+
-------
|
|
547
|
+
|
|
548
|
+
tuple of 1d barcodes, based on basepoint, with direction (1,1)
|
|
549
|
+
"""
|
|
550
|
+
if box is None:
|
|
551
|
+
box = [self.get_bottom(), self.get_top()]
|
|
552
|
+
if (len(box[0]) != 2) and (basepoints is None):
|
|
553
|
+
raise ValueError("Basepoints has to be specified for filtration dimension >= 3 !")
|
|
554
|
+
elif basepoints is None:
|
|
555
|
+
h = box[1][1] - box[0][1]
|
|
556
|
+
basepoints = np.linspace([box[0][0] - h,box[0][1]], [box[1][0],box[0][1]], num=num)
|
|
557
|
+
else :
|
|
558
|
+
num=len(basepoints)
|
|
559
|
+
|
|
560
|
+
basepoints = np.asarray(basepoints, dtype=np.float64)
|
|
561
|
+
cdef vector[Line[double]] cbasepoints
|
|
562
|
+
for i in range(num):
|
|
563
|
+
cbasepoints.push_back(Line[double](_py21c_f64(basepoints[i])))
|
|
564
|
+
return tuple(np.asarray(bc) for bc in self.cmod.get_barcodes2(cbasepoints, degree))
|
|
565
|
+
|
|
566
|
+
def landscape(self, degree:int, k:int=0,box:Sequence|np.ndarray|None=None, resolution:Sequence=[100,100], bool plot=False):
|
|
567
|
+
r"""Computes the multiparameter landscape from a PyModule. Python interface only bifiltrations.
|
|
568
|
+
|
|
569
|
+
Parameters
|
|
570
|
+
----------
|
|
571
|
+
|
|
572
|
+
degree : integer
|
|
573
|
+
The homology degree of the landscape.
|
|
574
|
+
k = 0 : int
|
|
575
|
+
the k-th landscape
|
|
576
|
+
resolution = [50,50] : pair of integers
|
|
577
|
+
Resolution of the image.
|
|
578
|
+
box = None : in the format [[a,b], [c,d]]
|
|
579
|
+
If nontrivial, compute the landscape of this box. Default is the PyModule box.
|
|
580
|
+
plot = True : Boolean
|
|
581
|
+
If true, plots the images;
|
|
582
|
+
Returns
|
|
583
|
+
-------
|
|
584
|
+
|
|
585
|
+
The landscape of the module.
|
|
586
|
+
|
|
587
|
+
"""
|
|
588
|
+
import matplotlib.pyplot as plt
|
|
589
|
+
if box is None:
|
|
590
|
+
box = self.get_box()
|
|
591
|
+
cdef Box[double] c_box = Box[double](box)
|
|
592
|
+
out = np.array(self.cmod.get_landscape(degree, k, c_box, resolution))
|
|
593
|
+
if plot:
|
|
594
|
+
aspect = (box[1][0]-box[0][0]) / (box[1][1]-box[0][1])
|
|
595
|
+
extent = [box[0][0], box[1][0], box[0][1], box[1][1]]
|
|
596
|
+
plt.imshow(out.T, origin="lower", extent=extent, aspect=aspect)
|
|
597
|
+
return out
|
|
598
|
+
|
|
599
|
+
def landscapes(self, degree:int, ks:list|np.ndarray=[0],box=None, resolution:list|np.ndarray=[100,100], bool plot=False):
|
|
600
|
+
r"""Computes the multiparameter landscape from a PyModule. Python interface only bifiltrations.
|
|
601
|
+
|
|
602
|
+
Parameters
|
|
603
|
+
----------
|
|
604
|
+
|
|
605
|
+
- degree : integer
|
|
606
|
+
The homology degree of the landscape.
|
|
607
|
+
- ks = 0 : list of int
|
|
608
|
+
the k-th landscape
|
|
609
|
+
- resolution = [50,50] : pair of integers
|
|
610
|
+
Resolution of the image.
|
|
611
|
+
- box = None : in the format [[a,b], [c,d]]
|
|
612
|
+
If nontrivial, compute the landscape of this box. Default is the PyModule box.
|
|
613
|
+
- plot = True : bool
|
|
614
|
+
If true, plots the images;
|
|
615
|
+
Returns
|
|
616
|
+
-------
|
|
617
|
+
|
|
618
|
+
The landscapes of the module with parameters ks.
|
|
619
|
+
|
|
620
|
+
"""
|
|
621
|
+
import matplotlib.pyplot as plt
|
|
622
|
+
if box is None:
|
|
623
|
+
box = self.get_box()
|
|
624
|
+
out = np.array(self.cmod.get_landscapes(degree, ks, Box[double](box), resolution))
|
|
625
|
+
if plot:
|
|
626
|
+
to_plot = np.sum(out, axis=0)
|
|
627
|
+
aspect = (box[1][0]-box[0][0]) / (box[1][1]-box[0][1])
|
|
628
|
+
extent = [box[0][0], box[1][0], box[0][1], box[1][1]]
|
|
629
|
+
plt.imshow(to_plot.T, origin="lower", extent=extent, aspect=aspect)
|
|
630
|
+
return out
|
|
631
|
+
|
|
632
|
+
|
|
633
|
+
def representation(self, degrees=None, double bandwidth=0.1,
|
|
634
|
+
resolution:Sequence[int]|int=50,
|
|
635
|
+
kernel:Literal["gaussian","linear","linear2","exponential"]|Callable = "gaussian",
|
|
636
|
+
bool signed=False,
|
|
637
|
+
bool normalize=False, bool plot=False,
|
|
638
|
+
bool save=False, int dpi=200,double p=2., box=None,
|
|
639
|
+
bool flatten=False, int n_jobs=0,
|
|
640
|
+
grid = None)->np.ndarray:
|
|
641
|
+
r"""Computes a representation of the module, using
|
|
642
|
+
|
|
643
|
+
[A Framework for Fast and Stable Representations of Multiparameter Persistent Homology Decompositions, Neurips2023]
|
|
644
|
+
|
|
645
|
+
Parameters
|
|
646
|
+
----------
|
|
647
|
+
|
|
648
|
+
- degrees = None : integer list
|
|
649
|
+
If given returns only the image(s) of homology degrees `degrees`.
|
|
650
|
+
- bandwidth = 0.1 : float
|
|
651
|
+
Image parameter.
|
|
652
|
+
- resolution = [100,100] : pair of integers
|
|
653
|
+
Resolution of the image(s).
|
|
654
|
+
- normalize = True : Boolean
|
|
655
|
+
Ensures that the image belongs to [0,1].
|
|
656
|
+
- plot = False : Boolean
|
|
657
|
+
If true, plots the images;
|
|
658
|
+
- flatten=False :
|
|
659
|
+
If True, reshapes the output to a flattened shape.
|
|
660
|
+
- kernel: Either linear, gaussian, or callable
|
|
661
|
+
The kernel to apply to the matrix $(d(x,I), x \in \mathrm{pixels}, I\in \mathrm{summands})$.
|
|
662
|
+
signature should be : (distance matrix, summand_weights, bandwidth, p) -> representation
|
|
663
|
+
|
|
664
|
+
Returns
|
|
665
|
+
-------
|
|
666
|
+
|
|
667
|
+
The list of images, or the image of fixed dimension.
|
|
668
|
+
"""
|
|
669
|
+
import matplotlib.pyplot as plt
|
|
670
|
+
# box = kwargs.get("box",[self.get_bottom(),self.get_top()])
|
|
671
|
+
if box is None:
|
|
672
|
+
box = self.get_box()
|
|
673
|
+
num_parameters = self.num_parameters
|
|
674
|
+
if degrees is None:
|
|
675
|
+
degrees = np.arange(self.max_degree +1)
|
|
676
|
+
num_degrees = len(degrees)
|
|
677
|
+
try:
|
|
678
|
+
int(resolution)
|
|
679
|
+
resolution = [resolution]*num_parameters
|
|
680
|
+
except:
|
|
681
|
+
pass
|
|
682
|
+
|
|
683
|
+
if grid is None:
|
|
684
|
+
grid = [np.linspace(*np.asarray(box)[:,parameter], num=res) for parameter, res in zip(range(num_parameters), resolution)]
|
|
685
|
+
else:
|
|
686
|
+
resolution = tuple(len(g) for g in grid)
|
|
687
|
+
coordinates = mpg.todense(grid)
|
|
688
|
+
|
|
689
|
+
if kernel == "linear":
|
|
690
|
+
assert not signed, "This kernel is not compatible with signed."
|
|
691
|
+
concatenated_images = self._compute_pixels(coordinates, degrees=degrees, box=box, delta=bandwidth, p=p, normalize=normalize,n_jobs=n_jobs)
|
|
692
|
+
else:
|
|
693
|
+
if kernel == "linear2":
|
|
694
|
+
def todo(PyModule_f64 mod_degree):
|
|
695
|
+
x = mod_degree.distance_to(coordinates,signed=signed)
|
|
696
|
+
w = mod_degree.get_interleavings()[None]**p
|
|
697
|
+
s = np.where(x>=0,1,-1) if signed else 1
|
|
698
|
+
x = np.abs(x)
|
|
699
|
+
return (s*np.where(x<bandwidth,(bandwidth-x)/bandwidth,0)*w).sum(1)
|
|
700
|
+
elif kernel == "gaussian":
|
|
701
|
+
def todo(PyModule_f64 mod_degree):
|
|
702
|
+
x = mod_degree.distance_to(coordinates,signed=signed)
|
|
703
|
+
w = mod_degree.get_interleavings()[None]**p
|
|
704
|
+
s = np.where(x>=0,1,-1) if signed else 1
|
|
705
|
+
return (s*np.exp( -.5*((x/bandwidth)**2))*w).sum(1)
|
|
706
|
+
elif kernel == "exponential":
|
|
707
|
+
def todo(PyModule_f64 mod_degree):
|
|
708
|
+
x = mod_degree.distance_to(coordinates,signed=signed)
|
|
709
|
+
w = mod_degree.get_interleavings()[None]**p
|
|
710
|
+
s = np.where(x>=0,1,-1) if signed else 1
|
|
711
|
+
x = np.abs(x)
|
|
712
|
+
return (s*np.exp( -((np.abs(x)/bandwidth)))*w).sum(1)
|
|
713
|
+
else:
|
|
714
|
+
assert callable(kernel), r"""
|
|
715
|
+
Kernel should be
|
|
716
|
+
gaussian, linear, linear2, exponential or callable,
|
|
717
|
+
with signature
|
|
718
|
+
(array[shape=(N, M)], array[shape=(M)])-> array(shape=(N)),
|
|
719
|
+
the first argument being a distance matrix (pts) vs (summands of the module)
|
|
720
|
+
and the second argument is a weight vector (weight(summand) for summand in module).
|
|
721
|
+
Note that the distance can be signed.
|
|
722
|
+
"""
|
|
723
|
+
def todo(PyModule_f64 mod_degree):
|
|
724
|
+
x = mod_degree.distance_to(coordinates,signed=signed, n_jobs=n_jobs)
|
|
725
|
+
w = mod_degree.get_interleavings()[None]**p
|
|
726
|
+
return kernel(x/bandwidth,w)
|
|
727
|
+
concatenated_images = np.stack(
|
|
728
|
+
Parallel(n_jobs = n_jobs if n_jobs else -1, backend= "threading")(
|
|
729
|
+
delayed(todo)(self.get_module_of_degree(degree))
|
|
730
|
+
for degree in degrees
|
|
731
|
+
)
|
|
732
|
+
)
|
|
733
|
+
|
|
734
|
+
if flatten:
|
|
735
|
+
image_vector = concatenated_images.reshape((len(degrees),-1))
|
|
736
|
+
if plot:
|
|
737
|
+
raise ValueError("Unflatten to plot.")
|
|
738
|
+
return image_vector
|
|
739
|
+
else:
|
|
740
|
+
image_vector = concatenated_images.reshape((len(degrees),*resolution))
|
|
741
|
+
if plot:
|
|
742
|
+
assert num_parameters == 2, "Plot only available for 2-parameter modules"
|
|
743
|
+
import multipers.plots
|
|
744
|
+
i=0
|
|
745
|
+
n_plots = len(image_vector)
|
|
746
|
+
scale = 4
|
|
747
|
+
if n_plots >1:
|
|
748
|
+
fig, axs = plt.subplots(1,n_plots, figsize=(n_plots*scale,scale))
|
|
749
|
+
else:
|
|
750
|
+
fig = plt.gcf()
|
|
751
|
+
axs = [plt.gca()]
|
|
752
|
+
for image, degree, i in zip(image_vector, degrees, range(num_degrees)):
|
|
753
|
+
ax = axs[i]
|
|
754
|
+
temp = multipers.plots.plot_surface(grid, image.T, ax=ax)
|
|
755
|
+
plt.colorbar(temp, ax = ax)
|
|
756
|
+
if degree < 0 :
|
|
757
|
+
ax.set_title(rf"$H_{i}$ $2$-persistence image")
|
|
758
|
+
if degree >= 0:
|
|
759
|
+
ax.set_title(rf"$H_{degree}$ $2$-persistence image")
|
|
760
|
+
return image_vector
|
|
761
|
+
|
|
762
|
+
def euler_char(self, points:list|np.ndarray) -> np.ndarray:
|
|
763
|
+
r""" Computes the Euler Characteristic of the filtered complex at given (multiparameter) time
|
|
764
|
+
|
|
765
|
+
Parameters
|
|
766
|
+
----------
|
|
767
|
+
|
|
768
|
+
points: list[float] | list[list[float]] | np.ndarray
|
|
769
|
+
List of filtration values on which to compute the euler characteristic.
|
|
770
|
+
WARNING FIXME : the points have to have the same dimension as the simplextree.
|
|
771
|
+
|
|
772
|
+
Returns
|
|
773
|
+
-------
|
|
774
|
+
|
|
775
|
+
The list of euler characteristic values
|
|
776
|
+
"""
|
|
777
|
+
if len(points) == 0:
|
|
778
|
+
return []
|
|
779
|
+
if type(points[0]) is float:
|
|
780
|
+
points = [points]
|
|
781
|
+
if type(points) is np.ndarray:
|
|
782
|
+
assert len(points.shape) in [1,2]
|
|
783
|
+
if len(points.shape) == 1:
|
|
784
|
+
points = [points]
|
|
785
|
+
|
|
786
|
+
cdef double[:,:] points_view = np.asarray(points, dtype = np.float64)
|
|
787
|
+
cdef vector[One_critical_filtration[double]] c_points = _py2v1c_f64(points_view)
|
|
788
|
+
# cdef One_critical_filtration temp
|
|
789
|
+
# for point in points:
|
|
790
|
+
# temp.clear()
|
|
791
|
+
# for truc in point:
|
|
792
|
+
# temp.push_back(<double>(truc))
|
|
793
|
+
# c_points.push_back(temp)
|
|
794
|
+
cdef Module[double] c_mod = self.cmod
|
|
795
|
+
with nogil:
|
|
796
|
+
c_euler = c_mod.euler_curve(c_points)
|
|
797
|
+
euler = c_euler
|
|
798
|
+
return np.asarray(euler, dtype=int)
|
|
799
|
+
def to_idx(self,grid):
|
|
800
|
+
cdef vector[vector[double]] cgrid = grid
|
|
801
|
+
cdef vector[vector[pair[vector[vector[int]],vector[vector[int]]]]] out
|
|
802
|
+
with nogil:
|
|
803
|
+
out = self.cmod.to_idx(cgrid)
|
|
804
|
+
return tuple(tuple((np.asarray(I.first,dtype=np.int64), np.asarray(I.second, dtype=np.int64)) for I in Is_of_degree) for Is_of_degree in out)
|
|
805
|
+
|
|
806
|
+
@cython.wraparound(False)
|
|
807
|
+
@cython.boundscheck(False)
|
|
808
|
+
def to_flat_idx(self,grid):
|
|
809
|
+
if len(self) == 0:
|
|
810
|
+
return np.empty((2,0), dtype = np.int32), np.empty((0, self.num_parameters), dtype = np.int32), np.empty((0, self.num_parameters), dtype = np.int32)
|
|
811
|
+
cdef vector[vector[double]] cgrid = grid
|
|
812
|
+
cdef vector[vector[vector[int]]] out
|
|
813
|
+
cdef int num_summands, num_births, num_deaths
|
|
814
|
+
cdef int num_parameters = self.num_parameters
|
|
815
|
+
with nogil:
|
|
816
|
+
out = self.cmod.to_flat_idx(cgrid)
|
|
817
|
+
num_summands = out[0][0].size()
|
|
818
|
+
num_births = out[1].size()
|
|
819
|
+
num_deaths = out[2].size()
|
|
820
|
+
idx = np.empty((2, num_summands),dtype=np.int32 )
|
|
821
|
+
births = np.empty((num_births,num_parameters),dtype=np.int32)
|
|
822
|
+
deaths = np.empty((num_deaths,num_parameters),dtype=np.int32)
|
|
823
|
+
|
|
824
|
+
cdef int32_t[:,:] idx_view = idx
|
|
825
|
+
cdef int32_t[:,:] births_view = births
|
|
826
|
+
cdef int32_t[:,:] deaths_view = deaths
|
|
827
|
+
|
|
828
|
+
with nogil:
|
|
829
|
+
for i in range(num_summands):
|
|
830
|
+
idx_view[0,i] = out[0][0][i]
|
|
831
|
+
idx_view[1,i] = out[0][1][i]
|
|
832
|
+
for i in range(num_births):
|
|
833
|
+
for j in range(num_parameters):
|
|
834
|
+
births_view[i,j] = out[1][i][j]
|
|
835
|
+
for i in range(num_deaths):
|
|
836
|
+
for j in range(num_parameters):
|
|
837
|
+
deaths_view[i,j] = out[2][i][j]
|
|
838
|
+
|
|
839
|
+
return idx, births,deaths
|
|
840
|
+
|
|
841
|
+
def distances_idx_to(self, pts, bool full=False, int n_jobs=1):
|
|
842
|
+
pts = np.asarray(pts)
|
|
843
|
+
if pts.ndim == 1:
|
|
844
|
+
pts = pts[None]
|
|
845
|
+
cdef vector[vector[double]] cpts = pts
|
|
846
|
+
cdef vector[vector[vector[int]]] out
|
|
847
|
+
with nogil:
|
|
848
|
+
out = self.cmod.compute_distances_idx_to(cpts,full, n_jobs)
|
|
849
|
+
return np.asarray(out, dtype=np.int32)
|
|
850
|
+
|
|
851
|
+
def distance_to(self, pts, bool signed=False, int n_jobs = 0)->np.ndarray:
|
|
852
|
+
r"""
|
|
853
|
+
Distance from a point to each summand's support.
|
|
854
|
+
Signed distance is the distance to the boundary,
|
|
855
|
+
with negative values inside the summands.
|
|
856
|
+
|
|
857
|
+
pts of shape (num_pts, num_parameters)
|
|
858
|
+
|
|
859
|
+
output shape : (num_pts,num_summands)
|
|
860
|
+
"""
|
|
861
|
+
pts = np.asarray(pts)
|
|
862
|
+
if pts.ndim == 1:
|
|
863
|
+
pts = pts[None]
|
|
864
|
+
assert pts.shape[-1] == self.num_parameters
|
|
865
|
+
cdef vector[vector[double]] cpts = pts
|
|
866
|
+
# cdef vector[vector[double]] out
|
|
867
|
+
to_fill = np.empty(shape=(cpts.size(),len(self)), dtype = np.float64)
|
|
868
|
+
cdef double[:,:] c_to_fill = to_fill
|
|
869
|
+
cdef double* data_ptr = &c_to_fill[0,0]
|
|
870
|
+
with nogil:
|
|
871
|
+
self.cmod.compute_distances_to(data_ptr, cpts,signed, n_jobs)
|
|
872
|
+
return to_fill
|
|
873
|
+
|
|
874
|
+
def get_interleavings(self,box=None):
|
|
875
|
+
if box is None:
|
|
876
|
+
box = self.get_box()
|
|
877
|
+
cdef Box[double] cbox = Box[double](box)
|
|
878
|
+
return np.asarray(self.cmod.get_interleavings(cbox))
|
|
879
|
+
|
|
880
|
+
cdef class PyMultiDiagramPoint_f64:
|
|
881
|
+
cdef MultiDiagram_point[One_critical_filtration[double]] point
|
|
882
|
+
cdef set(self, MultiDiagram_point[One_critical_filtration[double]] pt):
|
|
883
|
+
self.point = pt
|
|
884
|
+
return self
|
|
885
|
+
|
|
886
|
+
def get_degree(self):
|
|
887
|
+
return self.point.get_dimension()
|
|
888
|
+
def get_birth(self):
|
|
889
|
+
cdef One_critical_filtration[double] v = self.point.get_birth()
|
|
890
|
+
return _ff21cview_f64(&v, copy=True)
|
|
891
|
+
def get_death(self):
|
|
892
|
+
cdef One_critical_filtration[double] v = self.point.get_death()
|
|
893
|
+
return _ff21cview_f64(&v, copy=True)
|
|
894
|
+
|
|
895
|
+
|
|
896
|
+
cdef class PyMultiDiagram_f64:
|
|
897
|
+
r"""
|
|
898
|
+
Stores the diagram of a PyModule on a line
|
|
899
|
+
"""
|
|
900
|
+
cdef MultiDiagram[One_critical_filtration[double], double] multiDiagram
|
|
901
|
+
cdef set(self, MultiDiagram[One_critical_filtration[double], double] m):
|
|
902
|
+
self.multiDiagram = m
|
|
903
|
+
return self
|
|
904
|
+
def get_points(self, degree:int=-1) -> np.ndarray:
|
|
905
|
+
cdef vector[pair[vector[double],vector[double]]] out = self.multiDiagram.get_points(degree)
|
|
906
|
+
if len(out) == 0 and len(self) == 0:
|
|
907
|
+
return np.empty() # TODO Retrieve good number of parameters if there is no points in diagram
|
|
908
|
+
if len(out) == 0:
|
|
909
|
+
return np.empty((0,2,self.multiDiagram.at(0).get_dimension())) # gets the number of parameters
|
|
910
|
+
return np.array(out)
|
|
911
|
+
def to_multipers(self, dimension:int):
|
|
912
|
+
return self.multiDiagram.to_multipers(dimension)
|
|
913
|
+
def __len__(self) -> int:
|
|
914
|
+
return self.multiDiagram.size()
|
|
915
|
+
def __getitem__(self,i:int) -> PyMultiDiagramPoint_f64:
|
|
916
|
+
return PyMultiDiagramPoint_f64().set(self.multiDiagram.at(i % self.multiDiagram.size()))
|
|
917
|
+
cdef class PyMultiDiagrams_f64:
|
|
918
|
+
"""
|
|
919
|
+
Stores the barcodes of a PyModule on multiple lines
|
|
920
|
+
"""
|
|
921
|
+
cdef MultiDiagrams[One_critical_filtration[double], double] multiDiagrams
|
|
922
|
+
cdef set(self,MultiDiagrams[One_critical_filtration[double], double] m):
|
|
923
|
+
self.multiDiagrams = m
|
|
924
|
+
return self
|
|
925
|
+
def to_multipers(self):
|
|
926
|
+
out = self.multiDiagrams.to_multipers()
|
|
927
|
+
return [np.asarray(summand) for summand in out]
|
|
928
|
+
def __getitem__(self,i:int):
|
|
929
|
+
if i >=0 :
|
|
930
|
+
return PyMultiDiagram_f64().set(self.multiDiagrams.at(i))
|
|
931
|
+
else:
|
|
932
|
+
return PyMultiDiagram_f64().set(self.multiDiagrams.at( self.multiDiagrams.size() - i))
|
|
933
|
+
def __len__(self):
|
|
934
|
+
return self.multiDiagrams.size()
|
|
935
|
+
def get_points(self, degree:int=-1):
|
|
936
|
+
return self.multiDiagrams.get_points()
|
|
937
|
+
cdef _get_plot_bars(self, dimension:int=-1, min_persistence:float=0):
|
|
938
|
+
return self.multiDiagrams._for_python_plot(dimension, min_persistence);
|
|
939
|
+
def plot(self, degree:int=-1, min_persistence:float=0):
|
|
940
|
+
"""
|
|
941
|
+
Plots the barcodes.
|
|
942
|
+
|
|
943
|
+
Parameters
|
|
944
|
+
----------
|
|
945
|
+
|
|
946
|
+
- degree:int=-1
|
|
947
|
+
Only plots the bars of specified homology degree. Useful when the multidiagrams contains multiple dimenions
|
|
948
|
+
- min_persistence:float=0
|
|
949
|
+
Only plot bars of length greater than this value. Useful to reduce the time to plot.
|
|
950
|
+
|
|
951
|
+
Warning
|
|
952
|
+
-------
|
|
953
|
+
|
|
954
|
+
If the barcodes are not thresholded, essential barcodes will not be displayed !
|
|
955
|
+
|
|
956
|
+
"""
|
|
957
|
+
from cycler import cycler
|
|
958
|
+
import matplotlib
|
|
959
|
+
import matplotlib.pyplot as plt
|
|
960
|
+
if len(self) == 0: return
|
|
961
|
+
_cmap = matplotlib.colormaps["Spectral"]
|
|
962
|
+
multibarcodes_, colors = self._get_plot_bars(degree, min_persistence)
|
|
963
|
+
n_summands = np.max(colors)+1 if len(colors)>0 else 1
|
|
964
|
+
|
|
965
|
+
plt.rc('axes', prop_cycle = cycler('color', [_cmap(i/n_summands) for i in colors]))
|
|
966
|
+
return plt.plot(*multibarcodes_)
|
|
967
|
+
|
|
968
|
+
|
|
969
|
+
cdef dump_summand_f64(Summand[double]& summand):
|
|
970
|
+
cdef vector[One_critical_filtration[double]] births = summand.get_birth_list()
|
|
971
|
+
cdef vector[One_critical_filtration[double]] deaths = summand.get_death_list()
|
|
972
|
+
return (
|
|
973
|
+
np.array(_vff21cview_f64(births)),
|
|
974
|
+
np.array(_vff21cview_f64(deaths)),
|
|
975
|
+
summand.get_dimension(),
|
|
976
|
+
)
|
|
977
|
+
|
|
978
|
+
cdef inline Summand[double] summand_from_dump_f64(summand_dump):
|
|
979
|
+
cdef vector[One_critical_filtration[double]] births = _py2v1c_f64(summand_dump[0])
|
|
980
|
+
cdef vector[One_critical_filtration[double]] deaths = _py2v1c_f64(summand_dump[1])
|
|
981
|
+
cdef int dim = summand_dump[2]
|
|
982
|
+
return Summand[double](births,deaths,dim)
|
|
983
|
+
|
|
984
|
+
cdef dump_cmod_f64(Module[double]& mod):
|
|
985
|
+
cdef Box[double] cbox = mod.get_box()
|
|
986
|
+
cdef int dim = mod.get_dimension()
|
|
987
|
+
cdef cnp.ndarray[double, ndim=1] bottom_corner = _ff21cview_f64(&cbox.get_lower_corner())
|
|
988
|
+
cdef cnp.ndarray[double, ndim=1] top_corner = _ff21cview_f64(&cbox.get_upper_corner())
|
|
989
|
+
box = np.asarray([bottom_corner, top_corner])
|
|
990
|
+
summands = tuple(dump_summand_f64(summand) for summand in mod)
|
|
991
|
+
return box, summands
|
|
992
|
+
|
|
993
|
+
cdef Module[double] cmod_from_dump_f64(module_dump):
|
|
994
|
+
box = module_dump[0]
|
|
995
|
+
summands = module_dump[1]
|
|
996
|
+
cdef Module[double] out_module = Module[double]()
|
|
997
|
+
out_module.set_box(Box[double](box))
|
|
998
|
+
for i in range(len(summands)):
|
|
999
|
+
out_module.add_summand(summand_from_dump_f64(summands[i]))
|
|
1000
|
+
return out_module
|
|
1001
|
+
|
|
1002
|
+
|
|
1003
|
+
def from_dump_f64(dump)->PyModule_f64:
|
|
1004
|
+
r"""Retrieves a PyModule from a previous dump.
|
|
1005
|
+
|
|
1006
|
+
Parameters
|
|
1007
|
+
----------
|
|
1008
|
+
|
|
1009
|
+
dump: either the output of the dump function, or a file containing the output of a dump.
|
|
1010
|
+
The dumped module to retrieve
|
|
1011
|
+
|
|
1012
|
+
Returns
|
|
1013
|
+
-------
|
|
1014
|
+
|
|
1015
|
+
PyModule
|
|
1016
|
+
The retrieved module.
|
|
1017
|
+
"""
|
|
1018
|
+
# TODO : optimize...
|
|
1019
|
+
mod = PyModule_f64()
|
|
1020
|
+
if type(dump) is str:
|
|
1021
|
+
dump = pickle.load(open(dump, "rb"))
|
|
1022
|
+
cdef Module[double] cmod = cmod_from_dump_f64(dump)
|
|
1023
|
+
mod.cmod = cmod
|
|
1024
|
+
return mod
|
|
1025
|
+
|
|
1026
|
+
cdef class PySummand_f32:
|
|
1027
|
+
r"""
|
|
1028
|
+
Stores a Summand of a PyModule
|
|
1029
|
+
"""
|
|
1030
|
+
cdef Summand[float] sum
|
|
1031
|
+
|
|
1032
|
+
def get_birth_list(self):
|
|
1033
|
+
cdef vector[One_critical_filtration[float]] v = self.sum.get_birth_list()
|
|
1034
|
+
return _vff21cview_f32(v, copy = True, duplicate = self.num_parameters())
|
|
1035
|
+
|
|
1036
|
+
def get_death_list(self):
|
|
1037
|
+
cdef vector[One_critical_filtration[float]] v = self.sum.get_death_list()
|
|
1038
|
+
return _vff21cview_f32(v, copy = True, duplicate = self.num_parameters())
|
|
1039
|
+
@property
|
|
1040
|
+
def degree(self)->int:
|
|
1041
|
+
return self.sum.get_dimension()
|
|
1042
|
+
|
|
1043
|
+
cdef set(self, Summand[float]& summand):
|
|
1044
|
+
self.sum = summand
|
|
1045
|
+
return self
|
|
1046
|
+
def get_bounds(self):
|
|
1047
|
+
cdef pair[One_critical_filtration[float],One_critical_filtration[float]] cbounds
|
|
1048
|
+
with nogil:
|
|
1049
|
+
cbounds = self.sum.get_bounds().get_bounding_corners()
|
|
1050
|
+
return _ff21cview_f32(&cbounds.first).copy(), _ff21cview_f32(&cbounds.second).copy()
|
|
1051
|
+
@property
|
|
1052
|
+
def dtype(self):
|
|
1053
|
+
return np.float32
|
|
1054
|
+
|
|
1055
|
+
def num_parameters(self):
|
|
1056
|
+
cdef vector[One_critical_filtration[float]] v = self.sum.get_birth_list()
|
|
1057
|
+
if v[0].is_finite():
|
|
1058
|
+
return v[0].num_parameters()
|
|
1059
|
+
v = self.sum.get_death_list()
|
|
1060
|
+
return v[0].num_parameters()
|
|
1061
|
+
def __eq__(self, PySummand_f32 other):
|
|
1062
|
+
return self.sum == other.sum
|
|
1063
|
+
|
|
1064
|
+
|
|
1065
|
+
|
|
1066
|
+
|
|
1067
|
+
cdef inline get_summand_filtration_values_f32(Summand[float] summand):
|
|
1068
|
+
r"""
|
|
1069
|
+
Returns a list (over parameter) of the filtrations values of this parameter.
|
|
1070
|
+
"""
|
|
1071
|
+
cdef vector[One_critical_filtration[float]] vb = summand.get_birth_list()
|
|
1072
|
+
cdef vector[One_critical_filtration[float]] vd = summand.get_death_list()
|
|
1073
|
+
|
|
1074
|
+
if vb[0].is_finite():
|
|
1075
|
+
if vd[0].is_finite():
|
|
1076
|
+
pts = np.concatenate([_vff21cview_f32(vb, copy=True),
|
|
1077
|
+
_vff21cview_f32(vd, copy=True)],axis=0)
|
|
1078
|
+
else:
|
|
1079
|
+
pts = np.array(_vff21cview_f32(vb, copy=True))
|
|
1080
|
+
else:
|
|
1081
|
+
if vd[0].is_finite():
|
|
1082
|
+
pts = np.array(_vff21cview_f32(vd, copy=True))
|
|
1083
|
+
else:
|
|
1084
|
+
return []
|
|
1085
|
+
|
|
1086
|
+
num_parameters = pts.shape[1]
|
|
1087
|
+
out = [np.unique(pts[:,parameter]) for parameter in range(num_parameters)]
|
|
1088
|
+
out = [f[:-1] if f[-1] == np.inf else f for f in out]
|
|
1089
|
+
out = [f[1:] if f[0] == -np.inf else f for f in out]
|
|
1090
|
+
return out
|
|
1091
|
+
|
|
1092
|
+
cdef class PyBox_f32:
|
|
1093
|
+
cdef Box[float] box
|
|
1094
|
+
def __cinit__(self, vector[float]& bottomCorner, vector[float]& topCorner):
|
|
1095
|
+
self.box = Box[float](bottomCorner, topCorner)
|
|
1096
|
+
@property
|
|
1097
|
+
def num_parameters(self):
|
|
1098
|
+
cdef size_t dim = self.box.get_lower_corner().num_parameters()
|
|
1099
|
+
if dim == self.box.get_upper_corner().num_parameters(): return dim
|
|
1100
|
+
else: print("Bad box definition.")
|
|
1101
|
+
def contains(self, x):
|
|
1102
|
+
return self.box.contains(x)
|
|
1103
|
+
cdef set(self, Box[float]& b):
|
|
1104
|
+
self.box = b
|
|
1105
|
+
return self
|
|
1106
|
+
|
|
1107
|
+
def get(self):
|
|
1108
|
+
return [<vector[float]>self.box.get_lower_corner(), <vector[float]>self.box.get_upper_corner()]
|
|
1109
|
+
def to_multipers(self):
|
|
1110
|
+
#assert (self.get_dimension() == 2) "Multipers only works in dimension 2 !"
|
|
1111
|
+
return np.array(self.get()).flatten(order = 'F')
|
|
1112
|
+
@property
|
|
1113
|
+
def dtype(self):
|
|
1114
|
+
return np.float32
|
|
1115
|
+
|
|
1116
|
+
|
|
1117
|
+
|
|
1118
|
+
cdef class PyModule_f32:
|
|
1119
|
+
r"""
|
|
1120
|
+
Stores a representation of a n-persistence module.
|
|
1121
|
+
"""
|
|
1122
|
+
cdef Module[float] cmod
|
|
1123
|
+
|
|
1124
|
+
@property
|
|
1125
|
+
def dtype(self):
|
|
1126
|
+
return np.float32
|
|
1127
|
+
|
|
1128
|
+
cdef set(self, Module[float] m):
|
|
1129
|
+
self.cmod = m
|
|
1130
|
+
|
|
1131
|
+
def __eq__(self, PyModule_f32 other):
|
|
1132
|
+
return self.cmod == other.cmod
|
|
1133
|
+
|
|
1134
|
+
def merge(self, PyModule_f32 other, int dim=-1):
|
|
1135
|
+
r"""
|
|
1136
|
+
Merges two modules into one
|
|
1137
|
+
"""
|
|
1138
|
+
cdef Module[float] c_other = other.cmod
|
|
1139
|
+
with nogil:
|
|
1140
|
+
for summand in c_other:
|
|
1141
|
+
self.cmod.add_summand(summand, dim)
|
|
1142
|
+
return self
|
|
1143
|
+
|
|
1144
|
+
def _set_from_ptr(self, intptr_t module_ptr):
|
|
1145
|
+
r"""
|
|
1146
|
+
Copy module from a memory pointer. Unsafe.
|
|
1147
|
+
"""
|
|
1148
|
+
self.cmod = move(dereference(<Module[float]*>(module_ptr)))
|
|
1149
|
+
def set_box(self, box):
|
|
1150
|
+
assert len(box) == 2, "Box format is [low, hight]"
|
|
1151
|
+
pybox = PyBox_f32(box[0], box[1])
|
|
1152
|
+
cdef Box[float] cbox = pybox.box
|
|
1153
|
+
with nogil:
|
|
1154
|
+
self.cmod.set_box(cbox)
|
|
1155
|
+
return self
|
|
1156
|
+
def get_module_of_degree(self, int degree)->PyModule_f32: # TODO : in c++ ?
|
|
1157
|
+
r"""
|
|
1158
|
+
Returns a copy of a module of fixed degree.
|
|
1159
|
+
"""
|
|
1160
|
+
pmodule = PyModule_f32()
|
|
1161
|
+
cdef Box[float] c_box = self.cmod.get_box()
|
|
1162
|
+
pmodule.cmod.set_box(c_box)
|
|
1163
|
+
with nogil:
|
|
1164
|
+
for summand in self.cmod:
|
|
1165
|
+
if summand.get_dimension() == degree:
|
|
1166
|
+
pmodule.cmod.add_summand(summand)
|
|
1167
|
+
return pmodule
|
|
1168
|
+
def get_module_of_degrees(self, degrees:Iterable[int])->PyModule_f32: # TODO : in c++ ?
|
|
1169
|
+
r"""
|
|
1170
|
+
Returns a copy of the summands of degrees in `degrees`
|
|
1171
|
+
"""
|
|
1172
|
+
pmodule = PyModule_f32()
|
|
1173
|
+
cdef Box[float] c_box = self.cmod.get_box()
|
|
1174
|
+
pmodule.cmod.set_box(c_box)
|
|
1175
|
+
cdef vector[int] cdegrees = degrees
|
|
1176
|
+
with nogil:
|
|
1177
|
+
for summand in self.cmod:
|
|
1178
|
+
for d in cdegrees:
|
|
1179
|
+
if d == summand.get_dimension():
|
|
1180
|
+
pmodule.cmod.add_summand(summand)
|
|
1181
|
+
return pmodule
|
|
1182
|
+
def __len__(self)->int:
|
|
1183
|
+
return self.cmod.size()
|
|
1184
|
+
def get_bottom(self)->np.ndarray:
|
|
1185
|
+
r"""
|
|
1186
|
+
Bottom of the box of the module
|
|
1187
|
+
"""
|
|
1188
|
+
return np.asarray(<vector[float]>(self.cmod.get_box().get_lower_corner()))
|
|
1189
|
+
def get_top(self)->np.ndarray:
|
|
1190
|
+
r"""
|
|
1191
|
+
Top of the box of the module
|
|
1192
|
+
"""
|
|
1193
|
+
return np.asarray(<vector[float]>(self.cmod.get_box().get_upper_corner()))
|
|
1194
|
+
def get_box(self)->np.ndarray:
|
|
1195
|
+
r"""
|
|
1196
|
+
Returns the current bounding box of the module.
|
|
1197
|
+
"""
|
|
1198
|
+
return np.asarray([self.get_bottom(), self.get_top()])
|
|
1199
|
+
@property
|
|
1200
|
+
def max_degree(self)->int:
|
|
1201
|
+
r"""
|
|
1202
|
+
Returns the maximum degree of the module.
|
|
1203
|
+
"""
|
|
1204
|
+
return self.cmod.get_dimension()
|
|
1205
|
+
@property
|
|
1206
|
+
def num_parameters(self)->int:
|
|
1207
|
+
cdef size_t dim = self.cmod.get_box().get_lower_corner().num_parameters()
|
|
1208
|
+
assert dim == self.cmod.get_box().get_upper_corner().num_parameters(), "Bad box definition, cannot infer num_parameters."
|
|
1209
|
+
return dim
|
|
1210
|
+
def dump(self, path:str|None=None):
|
|
1211
|
+
r"""
|
|
1212
|
+
Dumps the module into a pickle-able format.
|
|
1213
|
+
|
|
1214
|
+
Parameters
|
|
1215
|
+
----------
|
|
1216
|
+
|
|
1217
|
+
path:str=None (optional) saves the pickled module in specified path
|
|
1218
|
+
|
|
1219
|
+
Returns
|
|
1220
|
+
-------
|
|
1221
|
+
|
|
1222
|
+
list of list, encoding the module, which can be retrieved with the function `from_dump`.
|
|
1223
|
+
"""
|
|
1224
|
+
## TODO : optimize, but not really used.
|
|
1225
|
+
return dump_cmod_f32(self.cmod)
|
|
1226
|
+
def __getstate__(self):
|
|
1227
|
+
return self.dump()
|
|
1228
|
+
def __setstate__(self,dump):
|
|
1229
|
+
cdef Module[float] cmod = cmod_from_dump_f32(dump)
|
|
1230
|
+
self.cmod = cmod
|
|
1231
|
+
return
|
|
1232
|
+
def __getitem__(self, int i) -> PySummand_f32:
|
|
1233
|
+
if i == slice(None):
|
|
1234
|
+
return self
|
|
1235
|
+
summand = PySummand_f32()
|
|
1236
|
+
summand.set(self.cmod.at(i % self.cmod.size()))
|
|
1237
|
+
return summand
|
|
1238
|
+
def __iter__(self):
|
|
1239
|
+
cdef int num_summands = self.cmod.size()
|
|
1240
|
+
for i in range(num_summands):
|
|
1241
|
+
summand = PySummand_f32()
|
|
1242
|
+
summand.set(self.cmod.at(i)) ## hmm copy. maybe do summand from ptr
|
|
1243
|
+
yield summand
|
|
1244
|
+
|
|
1245
|
+
def get_bounds(self):
|
|
1246
|
+
r"""
|
|
1247
|
+
Computes bounds from the summands' bounds.
|
|
1248
|
+
Useful to change this' box.
|
|
1249
|
+
"""
|
|
1250
|
+
cdef pair[One_critical_filtration[float],One_critical_filtration[float]] cbounds
|
|
1251
|
+
with nogil:
|
|
1252
|
+
cbounds = self.cmod.get_bounds().get_bounding_corners()
|
|
1253
|
+
return _ff21cview_f32(&cbounds.first).copy(), _ff21cview_f32(&cbounds.second).copy()
|
|
1254
|
+
def rescale(self,rescale_factors, int degree=-1):
|
|
1255
|
+
r"""
|
|
1256
|
+
Rescales the fitlration values of the summands by this rescaling vector.
|
|
1257
|
+
"""
|
|
1258
|
+
cdef vector[float] crescale_factors = rescale_factors
|
|
1259
|
+
with nogil:
|
|
1260
|
+
self.cmod.rescale(crescale_factors,degree)
|
|
1261
|
+
def translate(self,translation, int degree=-1):
|
|
1262
|
+
r"""
|
|
1263
|
+
Translates the module in the filtration space by this vector.
|
|
1264
|
+
"""
|
|
1265
|
+
cdef vector[float] ctranslation = translation
|
|
1266
|
+
with nogil:
|
|
1267
|
+
self.cmod.translate(ctranslation,degree)
|
|
1268
|
+
|
|
1269
|
+
def get_filtration_values(self, bool unique=True):
|
|
1270
|
+
r"""
|
|
1271
|
+
Retrieves all filtration values of the summands of the module.
|
|
1272
|
+
|
|
1273
|
+
Output format
|
|
1274
|
+
-------------
|
|
1275
|
+
|
|
1276
|
+
list of filtration values for parameter.
|
|
1277
|
+
"""
|
|
1278
|
+
if len(self) ==0:
|
|
1279
|
+
return np.empty((self.num_parameters,0))
|
|
1280
|
+
values = tuple(tuple(stuff) if len(stuff:=get_summand_filtration_values_f32(summand)) == self.num_parameters else list(stuff) + [[]]*(self.num_parameters - len(stuff)) for summand in self.cmod)
|
|
1281
|
+
try:
|
|
1282
|
+
values = tuple(np.concatenate([
|
|
1283
|
+
f[parameter]
|
|
1284
|
+
for f in values
|
|
1285
|
+
], axis=0) for parameter in range(self.num_parameters)
|
|
1286
|
+
)
|
|
1287
|
+
except:
|
|
1288
|
+
return values
|
|
1289
|
+
if unique:
|
|
1290
|
+
return [np.unique(f) for f in values]
|
|
1291
|
+
return values
|
|
1292
|
+
|
|
1293
|
+
def plot(self, int degree=-1,**kwargs)->None:
|
|
1294
|
+
r"""Shows the module on a plot. Each color corresponds to an apprimation summand of the module, and its shape corresponds to its support.
|
|
1295
|
+
Only works with 2-parameter modules.
|
|
1296
|
+
|
|
1297
|
+
Parameters
|
|
1298
|
+
----------
|
|
1299
|
+
degree = -1 : integer
|
|
1300
|
+
If positive returns only the image of dimension `dimension`.
|
|
1301
|
+
box=None : of the form [[b_x,b_y], [d_x,d_y]] where b,d are the bottom and top corner of the rectangle.
|
|
1302
|
+
If non-None, will plot the module on this specific rectangle.
|
|
1303
|
+
min_persistence =0 : float
|
|
1304
|
+
Only plots the summand with a persistence above this threshold.
|
|
1305
|
+
separated=False : bool
|
|
1306
|
+
If true, plot each summand in a different plot.
|
|
1307
|
+
alpha=1 : float
|
|
1308
|
+
Transparancy parameter
|
|
1309
|
+
save = False : string
|
|
1310
|
+
if nontrivial, will save the figure at this path
|
|
1311
|
+
|
|
1312
|
+
|
|
1313
|
+
Returns
|
|
1314
|
+
-------
|
|
1315
|
+
The figure of the plot.
|
|
1316
|
+
"""
|
|
1317
|
+
from multipers.plots import plot2d_PyModule
|
|
1318
|
+
import matplotlib.pyplot as plt
|
|
1319
|
+
box = kwargs.pop('box', self.get_box())
|
|
1320
|
+
if (len(box[0]) != 2):
|
|
1321
|
+
print("Filtration size :", len(box[0]), " != 2")
|
|
1322
|
+
return
|
|
1323
|
+
if(degree < 0):
|
|
1324
|
+
dims = np.unique(self.get_dimensions())
|
|
1325
|
+
separated = kwargs.pop("separated", False)
|
|
1326
|
+
ndim = len(dims)
|
|
1327
|
+
scale = kwargs.pop("scale", 4)
|
|
1328
|
+
if separated:
|
|
1329
|
+
fig = None
|
|
1330
|
+
axes = None
|
|
1331
|
+
elif ndim > 1:
|
|
1332
|
+
fig, axes = plt.subplots(1,ndim, figsize=(ndim*scale,scale))
|
|
1333
|
+
else:
|
|
1334
|
+
fig = plt.gcf()
|
|
1335
|
+
axes = [plt.gca()]
|
|
1336
|
+
for dim_idx in range(ndim):
|
|
1337
|
+
if not separated:
|
|
1338
|
+
plt.sca(axes[dim_idx])
|
|
1339
|
+
self.plot(dims[dim_idx],box=box, separated = separated, **kwargs)
|
|
1340
|
+
return
|
|
1341
|
+
corners = self.cmod.get_corners_of_dimension(degree)
|
|
1342
|
+
plot2d_PyModule(corners, box=box, dimension=degree, **kwargs)
|
|
1343
|
+
return
|
|
1344
|
+
def degree_splits(self):
|
|
1345
|
+
return np.asarray(self.cmod.get_degree_splits(), dtype=np.int64)
|
|
1346
|
+
def _compute_pixels(self,coordinates:np.ndarray,
|
|
1347
|
+
degrees=None, box=None, float delta=.1,
|
|
1348
|
+
float p=1., bool normalize=False, int n_jobs=0):
|
|
1349
|
+
r"""
|
|
1350
|
+
Computes the image of the module at the given coordinates
|
|
1351
|
+
"""
|
|
1352
|
+
if degrees is not None: assert np.all(degrees[:-1] <= degrees[1:]), "Degrees have to be sorted"
|
|
1353
|
+
cdef vector[int] cdegrees = np.arange(self.max_degree +1) if degrees is None else degrees
|
|
1354
|
+
pybox = PyBox_f32(*self.get_box()) if box is None else PyBox_f32(*box)
|
|
1355
|
+
cdef Box[float] cbox = pybox.box
|
|
1356
|
+
cdef vector[vector[float]] ccoords = coordinates
|
|
1357
|
+
cdef vector[vector[float]] out
|
|
1358
|
+
with nogil:
|
|
1359
|
+
out = self.cmod.compute_pixels(ccoords, cdegrees, cbox, delta, p, normalize, n_jobs)
|
|
1360
|
+
return np.asarray(out)
|
|
1361
|
+
def barcode(self, basepoint, int degree = -1,*, bool threshold = False): # TODO direction vector interface
|
|
1362
|
+
r"""Computes the barcode of module along a lines.
|
|
1363
|
+
|
|
1364
|
+
Parameters
|
|
1365
|
+
----------
|
|
1366
|
+
|
|
1367
|
+
basepoint : vector
|
|
1368
|
+
basepoint of the lines on which to compute the barcodes, i.e. a point on the line
|
|
1369
|
+
degree = -1 : integer
|
|
1370
|
+
Homology degree on which to compute the bars. If negative, every dimension is computed
|
|
1371
|
+
box (default) :
|
|
1372
|
+
box on which to compute the barcodes if basepoints is not specified. Default is a linspace of lines crossing that box.
|
|
1373
|
+
threshold = False :
|
|
1374
|
+
Thre
|
|
1375
|
+
|
|
1376
|
+
Warning
|
|
1377
|
+
-------
|
|
1378
|
+
|
|
1379
|
+
If the barcodes are not thresholded, essential barcodes will not be plot-able.
|
|
1380
|
+
|
|
1381
|
+
Returns
|
|
1382
|
+
-------
|
|
1383
|
+
|
|
1384
|
+
PyMultiDiagrams
|
|
1385
|
+
Structure that holds the barcodes. Barcodes can be retrieved with a .get_points() or a .to_multipers() or a .plot().
|
|
1386
|
+
"""
|
|
1387
|
+
out = PyMultiDiagram_f32()
|
|
1388
|
+
out.set(self.cmod.get_barcode(Line[float](_py21c_f32(np.asarray(basepoint, dtype=np.float32))), degree, threshold))
|
|
1389
|
+
return out
|
|
1390
|
+
@staticmethod
|
|
1391
|
+
cdef _threshold_bc(bc):
|
|
1392
|
+
return tuple(np.fromiter((a for a in stuff if a[0] < np.inf), dtype=np.dtype((np.float32,2)) ) for stuff in bc)
|
|
1393
|
+
@staticmethod
|
|
1394
|
+
def _bc_to_full(bcs, basepoint, direction=None):
|
|
1395
|
+
# i, (b sv d), coords
|
|
1396
|
+
basepoint = np.asarray(basepoint)[None,None,:]
|
|
1397
|
+
direction = 1 if direction is None else np.asarray(direction)[None,None,:]
|
|
1398
|
+
return tuple(bc[:,:,None]*direction + basepoint for bc in bcs)
|
|
1399
|
+
def barcode2(self, basepoint, direction=None, int degree = -1,*, bool threshold = False, bool keep_inf = True, bool full = False): # TODO direction vector interface
|
|
1400
|
+
r"""
|
|
1401
|
+
Compute the 1d-barcode a diagonal line based on basepoint, with some direction.
|
|
1402
|
+
|
|
1403
|
+
Parameters
|
|
1404
|
+
----------
|
|
1405
|
+
|
|
1406
|
+
- basepoint: 1d array
|
|
1407
|
+
- directiont: 1d array or None, if None: diagonal
|
|
1408
|
+
- degree: int the degree to compute (-1 means all)
|
|
1409
|
+
- threshold: bool if True, threshold the barcode to the modules box
|
|
1410
|
+
- keep_inf: bool if False, removes trivial bars
|
|
1411
|
+
Note that this removes the order w.r.t. the summands in that case
|
|
1412
|
+
- full:bool if True, returns the coordinates of the barcode instead of the coordinate in the line.
|
|
1413
|
+
|
|
1414
|
+
The output is of the form
|
|
1415
|
+
|
|
1416
|
+
tuple[np.ndarray of shape (num_bars,2)] or tuple[array of shape (num_bar, 2, num_parameters)]
|
|
1417
|
+
"""
|
|
1418
|
+
basepoint = np.asarray(basepoint, dtype=np.float32)
|
|
1419
|
+
if direction is None:
|
|
1420
|
+
bc = tuple(np.asarray(x).reshape(-1,2) for x in self.cmod.get_barcode2(Line[float](_py21c_f32(basepoint)), degree))
|
|
1421
|
+
else:
|
|
1422
|
+
direction = np.asarray(direction, dtype = np.float32)
|
|
1423
|
+
bc = tuple(np.asarray(x).reshape(-1,2) for x in self.cmod.get_barcode2(Line[float](_py21c_f32(basepoint), _py21c_f32(direction)), degree))
|
|
1424
|
+
if not keep_inf:
|
|
1425
|
+
bc = PyModule_f32._threshold_bc(bc)
|
|
1426
|
+
if full:
|
|
1427
|
+
bc = PyModule_f32._bc_to_full(bc, basepoint, direction)
|
|
1428
|
+
|
|
1429
|
+
return bc
|
|
1430
|
+
|
|
1431
|
+
def get_dimensions(self):
|
|
1432
|
+
cdef int num_summands = len(self)
|
|
1433
|
+
out = np.empty(shape=num_summands, dtype=np.int32)
|
|
1434
|
+
cdef int32_t[:] c_out = out
|
|
1435
|
+
for i in range(num_summands):
|
|
1436
|
+
c_out[i] = self.cmod.at(i).get_dimension()
|
|
1437
|
+
return out
|
|
1438
|
+
|
|
1439
|
+
def barcodes(self, int degree, basepoints = None, num=100, box = None,threshold = False):
|
|
1440
|
+
r"""Computes barcodes of module along a set of lines.
|
|
1441
|
+
|
|
1442
|
+
Parameters
|
|
1443
|
+
----------
|
|
1444
|
+
|
|
1445
|
+
basepoints = None : list of vectors
|
|
1446
|
+
basepoints of the lines on which to compute the barcodes.
|
|
1447
|
+
degree = -1 : integer
|
|
1448
|
+
Homology degree on which to compute the bars. If negative, every dimension is computed
|
|
1449
|
+
box (default) :
|
|
1450
|
+
box on which to compute the barcodes if basepoints is not specified. Default is a linspace of lines crossing that box.
|
|
1451
|
+
num:int=100
|
|
1452
|
+
if basepoints is not specified, defines the number of lines to consider.
|
|
1453
|
+
threshold = False : threshold t
|
|
1454
|
+
Resolution of the image(s).
|
|
1455
|
+
|
|
1456
|
+
Warning
|
|
1457
|
+
-------
|
|
1458
|
+
|
|
1459
|
+
If the barcodes are not thresholded, essential barcodes will not be plot-able.
|
|
1460
|
+
|
|
1461
|
+
Returns
|
|
1462
|
+
-------
|
|
1463
|
+
|
|
1464
|
+
PyMultiDiagrams
|
|
1465
|
+
Structure that holds the barcodes. Barcodes can be retrieved with a .get_points() or a .to_multipers() or a .plot().
|
|
1466
|
+
"""
|
|
1467
|
+
out = PyMultiDiagrams_f32()
|
|
1468
|
+
if box is None:
|
|
1469
|
+
box = [self.get_bottom(), self.get_top()]
|
|
1470
|
+
if (len(box[0]) != 2) and (basepoints is None):
|
|
1471
|
+
raise ValueError("Basepoints has to be specified for filtration dimension >= 3 !")
|
|
1472
|
+
elif basepoints is None:
|
|
1473
|
+
h = box[1][1] - box[0][1]
|
|
1474
|
+
basepoints = np.linspace([box[0][0] - h,box[0][1]], [box[1][0],box[0][1]], num=num)
|
|
1475
|
+
else :
|
|
1476
|
+
num=len(basepoints)
|
|
1477
|
+
|
|
1478
|
+
cdef float[:,:] basepoints_view = np.asarray(basepoints, dtype = np.float32)
|
|
1479
|
+
cdef vector[One_critical_filtration[float]] cbasepoints = _py2v1c_f32(basepoints_view)
|
|
1480
|
+
|
|
1481
|
+
out.set(self.cmod.get_barcodes(cbasepoints, degree, threshold))
|
|
1482
|
+
return out
|
|
1483
|
+
|
|
1484
|
+
def barcodes2(self, int degree = -1, basepoints = None, int num=100, box = None, bool threshold = False):
|
|
1485
|
+
r"""Computes barcodes of module along a set of lines.
|
|
1486
|
+
|
|
1487
|
+
Parameters
|
|
1488
|
+
----------
|
|
1489
|
+
|
|
1490
|
+
basepoints = None : list of vectors
|
|
1491
|
+
basepoints of the lines on which to compute the barcodes.
|
|
1492
|
+
degree = -1 : integer
|
|
1493
|
+
Homology degree on which to compute the bars. If negative, every dimension is computed
|
|
1494
|
+
box (default) :
|
|
1495
|
+
box on which to compute the barcodes if basepoints is not specified. Default is a linspace of lines crossing that box.
|
|
1496
|
+
num:int=100
|
|
1497
|
+
if basepoints is not specified, defines the number of lines to consider.
|
|
1498
|
+
threshold = False : threshold t
|
|
1499
|
+
Resolution of the image(s).
|
|
1500
|
+
|
|
1501
|
+
Warning
|
|
1502
|
+
-------
|
|
1503
|
+
|
|
1504
|
+
If the barcodes are not thresholded, essential barcodes will not be plot-able.
|
|
1505
|
+
|
|
1506
|
+
Returns
|
|
1507
|
+
-------
|
|
1508
|
+
|
|
1509
|
+
tuple of 1d barcodes, based on basepoint, with direction (1,1)
|
|
1510
|
+
"""
|
|
1511
|
+
if box is None:
|
|
1512
|
+
box = [self.get_bottom(), self.get_top()]
|
|
1513
|
+
if (len(box[0]) != 2) and (basepoints is None):
|
|
1514
|
+
raise ValueError("Basepoints has to be specified for filtration dimension >= 3 !")
|
|
1515
|
+
elif basepoints is None:
|
|
1516
|
+
h = box[1][1] - box[0][1]
|
|
1517
|
+
basepoints = np.linspace([box[0][0] - h,box[0][1]], [box[1][0],box[0][1]], num=num)
|
|
1518
|
+
else :
|
|
1519
|
+
num=len(basepoints)
|
|
1520
|
+
|
|
1521
|
+
basepoints = np.asarray(basepoints, dtype=np.float32)
|
|
1522
|
+
cdef vector[Line[float]] cbasepoints
|
|
1523
|
+
for i in range(num):
|
|
1524
|
+
cbasepoints.push_back(Line[float](_py21c_f32(basepoints[i])))
|
|
1525
|
+
return tuple(np.asarray(bc) for bc in self.cmod.get_barcodes2(cbasepoints, degree))
|
|
1526
|
+
|
|
1527
|
+
def landscape(self, degree:int, k:int=0,box:Sequence|np.ndarray|None=None, resolution:Sequence=[100,100], bool plot=False):
|
|
1528
|
+
r"""Computes the multiparameter landscape from a PyModule. Python interface only bifiltrations.
|
|
1529
|
+
|
|
1530
|
+
Parameters
|
|
1531
|
+
----------
|
|
1532
|
+
|
|
1533
|
+
degree : integer
|
|
1534
|
+
The homology degree of the landscape.
|
|
1535
|
+
k = 0 : int
|
|
1536
|
+
the k-th landscape
|
|
1537
|
+
resolution = [50,50] : pair of integers
|
|
1538
|
+
Resolution of the image.
|
|
1539
|
+
box = None : in the format [[a,b], [c,d]]
|
|
1540
|
+
If nontrivial, compute the landscape of this box. Default is the PyModule box.
|
|
1541
|
+
plot = True : Boolean
|
|
1542
|
+
If true, plots the images;
|
|
1543
|
+
Returns
|
|
1544
|
+
-------
|
|
1545
|
+
|
|
1546
|
+
The landscape of the module.
|
|
1547
|
+
|
|
1548
|
+
"""
|
|
1549
|
+
import matplotlib.pyplot as plt
|
|
1550
|
+
if box is None:
|
|
1551
|
+
box = self.get_box()
|
|
1552
|
+
cdef Box[float] c_box = Box[float](box)
|
|
1553
|
+
out = np.array(self.cmod.get_landscape(degree, k, c_box, resolution))
|
|
1554
|
+
if plot:
|
|
1555
|
+
aspect = (box[1][0]-box[0][0]) / (box[1][1]-box[0][1])
|
|
1556
|
+
extent = [box[0][0], box[1][0], box[0][1], box[1][1]]
|
|
1557
|
+
plt.imshow(out.T, origin="lower", extent=extent, aspect=aspect)
|
|
1558
|
+
return out
|
|
1559
|
+
|
|
1560
|
+
def landscapes(self, degree:int, ks:list|np.ndarray=[0],box=None, resolution:list|np.ndarray=[100,100], bool plot=False):
|
|
1561
|
+
r"""Computes the multiparameter landscape from a PyModule. Python interface only bifiltrations.
|
|
1562
|
+
|
|
1563
|
+
Parameters
|
|
1564
|
+
----------
|
|
1565
|
+
|
|
1566
|
+
- degree : integer
|
|
1567
|
+
The homology degree of the landscape.
|
|
1568
|
+
- ks = 0 : list of int
|
|
1569
|
+
the k-th landscape
|
|
1570
|
+
- resolution = [50,50] : pair of integers
|
|
1571
|
+
Resolution of the image.
|
|
1572
|
+
- box = None : in the format [[a,b], [c,d]]
|
|
1573
|
+
If nontrivial, compute the landscape of this box. Default is the PyModule box.
|
|
1574
|
+
- plot = True : bool
|
|
1575
|
+
If true, plots the images;
|
|
1576
|
+
Returns
|
|
1577
|
+
-------
|
|
1578
|
+
|
|
1579
|
+
The landscapes of the module with parameters ks.
|
|
1580
|
+
|
|
1581
|
+
"""
|
|
1582
|
+
import matplotlib.pyplot as plt
|
|
1583
|
+
if box is None:
|
|
1584
|
+
box = self.get_box()
|
|
1585
|
+
out = np.array(self.cmod.get_landscapes(degree, ks, Box[float](box), resolution))
|
|
1586
|
+
if plot:
|
|
1587
|
+
to_plot = np.sum(out, axis=0)
|
|
1588
|
+
aspect = (box[1][0]-box[0][0]) / (box[1][1]-box[0][1])
|
|
1589
|
+
extent = [box[0][0], box[1][0], box[0][1], box[1][1]]
|
|
1590
|
+
plt.imshow(to_plot.T, origin="lower", extent=extent, aspect=aspect)
|
|
1591
|
+
return out
|
|
1592
|
+
|
|
1593
|
+
|
|
1594
|
+
def representation(self, degrees=None, double bandwidth=0.1,
|
|
1595
|
+
resolution:Sequence[int]|int=50,
|
|
1596
|
+
kernel:Literal["gaussian","linear","linear2","exponential"]|Callable = "gaussian",
|
|
1597
|
+
bool signed=False,
|
|
1598
|
+
bool normalize=False, bool plot=False,
|
|
1599
|
+
bool save=False, int dpi=200,double p=2., box=None,
|
|
1600
|
+
bool flatten=False, int n_jobs=0,
|
|
1601
|
+
grid = None)->np.ndarray:
|
|
1602
|
+
r"""Computes a representation of the module, using
|
|
1603
|
+
|
|
1604
|
+
[A Framework for Fast and Stable Representations of Multiparameter Persistent Homology Decompositions, Neurips2023]
|
|
1605
|
+
|
|
1606
|
+
Parameters
|
|
1607
|
+
----------
|
|
1608
|
+
|
|
1609
|
+
- degrees = None : integer list
|
|
1610
|
+
If given returns only the image(s) of homology degrees `degrees`.
|
|
1611
|
+
- bandwidth = 0.1 : float
|
|
1612
|
+
Image parameter.
|
|
1613
|
+
- resolution = [100,100] : pair of integers
|
|
1614
|
+
Resolution of the image(s).
|
|
1615
|
+
- normalize = True : Boolean
|
|
1616
|
+
Ensures that the image belongs to [0,1].
|
|
1617
|
+
- plot = False : Boolean
|
|
1618
|
+
If true, plots the images;
|
|
1619
|
+
- flatten=False :
|
|
1620
|
+
If True, reshapes the output to a flattened shape.
|
|
1621
|
+
- kernel: Either linear, gaussian, or callable
|
|
1622
|
+
The kernel to apply to the matrix $(d(x,I), x \in \mathrm{pixels}, I\in \mathrm{summands})$.
|
|
1623
|
+
signature should be : (distance matrix, summand_weights, bandwidth, p) -> representation
|
|
1624
|
+
|
|
1625
|
+
Returns
|
|
1626
|
+
-------
|
|
1627
|
+
|
|
1628
|
+
The list of images, or the image of fixed dimension.
|
|
1629
|
+
"""
|
|
1630
|
+
import matplotlib.pyplot as plt
|
|
1631
|
+
# box = kwargs.get("box",[self.get_bottom(),self.get_top()])
|
|
1632
|
+
if box is None:
|
|
1633
|
+
box = self.get_box()
|
|
1634
|
+
num_parameters = self.num_parameters
|
|
1635
|
+
if degrees is None:
|
|
1636
|
+
degrees = np.arange(self.max_degree +1)
|
|
1637
|
+
num_degrees = len(degrees)
|
|
1638
|
+
try:
|
|
1639
|
+
int(resolution)
|
|
1640
|
+
resolution = [resolution]*num_parameters
|
|
1641
|
+
except:
|
|
1642
|
+
pass
|
|
1643
|
+
|
|
1644
|
+
if grid is None:
|
|
1645
|
+
grid = [np.linspace(*np.asarray(box)[:,parameter], num=res) for parameter, res in zip(range(num_parameters), resolution)]
|
|
1646
|
+
else:
|
|
1647
|
+
resolution = tuple(len(g) for g in grid)
|
|
1648
|
+
coordinates = mpg.todense(grid)
|
|
1649
|
+
|
|
1650
|
+
if kernel == "linear":
|
|
1651
|
+
assert not signed, "This kernel is not compatible with signed."
|
|
1652
|
+
concatenated_images = self._compute_pixels(coordinates, degrees=degrees, box=box, delta=bandwidth, p=p, normalize=normalize,n_jobs=n_jobs)
|
|
1653
|
+
else:
|
|
1654
|
+
if kernel == "linear2":
|
|
1655
|
+
def todo(PyModule_f32 mod_degree):
|
|
1656
|
+
x = mod_degree.distance_to(coordinates,signed=signed)
|
|
1657
|
+
w = mod_degree.get_interleavings()[None]**p
|
|
1658
|
+
s = np.where(x>=0,1,-1) if signed else 1
|
|
1659
|
+
x = np.abs(x)
|
|
1660
|
+
return (s*np.where(x<bandwidth,(bandwidth-x)/bandwidth,0)*w).sum(1)
|
|
1661
|
+
elif kernel == "gaussian":
|
|
1662
|
+
def todo(PyModule_f32 mod_degree):
|
|
1663
|
+
x = mod_degree.distance_to(coordinates,signed=signed)
|
|
1664
|
+
w = mod_degree.get_interleavings()[None]**p
|
|
1665
|
+
s = np.where(x>=0,1,-1) if signed else 1
|
|
1666
|
+
return (s*np.exp( -.5*((x/bandwidth)**2))*w).sum(1)
|
|
1667
|
+
elif kernel == "exponential":
|
|
1668
|
+
def todo(PyModule_f32 mod_degree):
|
|
1669
|
+
x = mod_degree.distance_to(coordinates,signed=signed)
|
|
1670
|
+
w = mod_degree.get_interleavings()[None]**p
|
|
1671
|
+
s = np.where(x>=0,1,-1) if signed else 1
|
|
1672
|
+
x = np.abs(x)
|
|
1673
|
+
return (s*np.exp( -((np.abs(x)/bandwidth)))*w).sum(1)
|
|
1674
|
+
else:
|
|
1675
|
+
assert callable(kernel), r"""
|
|
1676
|
+
Kernel should be
|
|
1677
|
+
gaussian, linear, linear2, exponential or callable,
|
|
1678
|
+
with signature
|
|
1679
|
+
(array[shape=(N, M)], array[shape=(M)])-> array(shape=(N)),
|
|
1680
|
+
the first argument being a distance matrix (pts) vs (summands of the module)
|
|
1681
|
+
and the second argument is a weight vector (weight(summand) for summand in module).
|
|
1682
|
+
Note that the distance can be signed.
|
|
1683
|
+
"""
|
|
1684
|
+
def todo(PyModule_f32 mod_degree):
|
|
1685
|
+
x = mod_degree.distance_to(coordinates,signed=signed, n_jobs=n_jobs)
|
|
1686
|
+
w = mod_degree.get_interleavings()[None]**p
|
|
1687
|
+
return kernel(x/bandwidth,w)
|
|
1688
|
+
concatenated_images = np.stack(
|
|
1689
|
+
Parallel(n_jobs = n_jobs if n_jobs else -1, backend= "threading")(
|
|
1690
|
+
delayed(todo)(self.get_module_of_degree(degree))
|
|
1691
|
+
for degree in degrees
|
|
1692
|
+
)
|
|
1693
|
+
)
|
|
1694
|
+
|
|
1695
|
+
if flatten:
|
|
1696
|
+
image_vector = concatenated_images.reshape((len(degrees),-1))
|
|
1697
|
+
if plot:
|
|
1698
|
+
raise ValueError("Unflatten to plot.")
|
|
1699
|
+
return image_vector
|
|
1700
|
+
else:
|
|
1701
|
+
image_vector = concatenated_images.reshape((len(degrees),*resolution))
|
|
1702
|
+
if plot:
|
|
1703
|
+
assert num_parameters == 2, "Plot only available for 2-parameter modules"
|
|
1704
|
+
import multipers.plots
|
|
1705
|
+
i=0
|
|
1706
|
+
n_plots = len(image_vector)
|
|
1707
|
+
scale = 4
|
|
1708
|
+
if n_plots >1:
|
|
1709
|
+
fig, axs = plt.subplots(1,n_plots, figsize=(n_plots*scale,scale))
|
|
1710
|
+
else:
|
|
1711
|
+
fig = plt.gcf()
|
|
1712
|
+
axs = [plt.gca()]
|
|
1713
|
+
for image, degree, i in zip(image_vector, degrees, range(num_degrees)):
|
|
1714
|
+
ax = axs[i]
|
|
1715
|
+
temp = multipers.plots.plot_surface(grid, image.T, ax=ax)
|
|
1716
|
+
plt.colorbar(temp, ax = ax)
|
|
1717
|
+
if degree < 0 :
|
|
1718
|
+
ax.set_title(rf"$H_{i}$ $2$-persistence image")
|
|
1719
|
+
if degree >= 0:
|
|
1720
|
+
ax.set_title(rf"$H_{degree}$ $2$-persistence image")
|
|
1721
|
+
return image_vector
|
|
1722
|
+
|
|
1723
|
+
def euler_char(self, points:list|np.ndarray) -> np.ndarray:
|
|
1724
|
+
r""" Computes the Euler Characteristic of the filtered complex at given (multiparameter) time
|
|
1725
|
+
|
|
1726
|
+
Parameters
|
|
1727
|
+
----------
|
|
1728
|
+
|
|
1729
|
+
points: list[float] | list[list[float]] | np.ndarray
|
|
1730
|
+
List of filtration values on which to compute the euler characteristic.
|
|
1731
|
+
WARNING FIXME : the points have to have the same dimension as the simplextree.
|
|
1732
|
+
|
|
1733
|
+
Returns
|
|
1734
|
+
-------
|
|
1735
|
+
|
|
1736
|
+
The list of euler characteristic values
|
|
1737
|
+
"""
|
|
1738
|
+
if len(points) == 0:
|
|
1739
|
+
return []
|
|
1740
|
+
if type(points[0]) is float:
|
|
1741
|
+
points = [points]
|
|
1742
|
+
if type(points) is np.ndarray:
|
|
1743
|
+
assert len(points.shape) in [1,2]
|
|
1744
|
+
if len(points.shape) == 1:
|
|
1745
|
+
points = [points]
|
|
1746
|
+
|
|
1747
|
+
cdef float[:,:] points_view = np.asarray(points, dtype = np.float32)
|
|
1748
|
+
cdef vector[One_critical_filtration[float]] c_points = _py2v1c_f32(points_view)
|
|
1749
|
+
# cdef One_critical_filtration temp
|
|
1750
|
+
# for point in points:
|
|
1751
|
+
# temp.clear()
|
|
1752
|
+
# for truc in point:
|
|
1753
|
+
# temp.push_back(<float>(truc))
|
|
1754
|
+
# c_points.push_back(temp)
|
|
1755
|
+
cdef Module[float] c_mod = self.cmod
|
|
1756
|
+
with nogil:
|
|
1757
|
+
c_euler = c_mod.euler_curve(c_points)
|
|
1758
|
+
euler = c_euler
|
|
1759
|
+
return np.asarray(euler, dtype=int)
|
|
1760
|
+
def to_idx(self,grid):
|
|
1761
|
+
cdef vector[vector[float]] cgrid = grid
|
|
1762
|
+
cdef vector[vector[pair[vector[vector[int]],vector[vector[int]]]]] out
|
|
1763
|
+
with nogil:
|
|
1764
|
+
out = self.cmod.to_idx(cgrid)
|
|
1765
|
+
return tuple(tuple((np.asarray(I.first,dtype=np.int64), np.asarray(I.second, dtype=np.int64)) for I in Is_of_degree) for Is_of_degree in out)
|
|
1766
|
+
|
|
1767
|
+
@cython.wraparound(False)
|
|
1768
|
+
@cython.boundscheck(False)
|
|
1769
|
+
def to_flat_idx(self,grid):
|
|
1770
|
+
if len(self) == 0:
|
|
1771
|
+
return np.empty((2,0), dtype = np.int32), np.empty((0, self.num_parameters), dtype = np.int32), np.empty((0, self.num_parameters), dtype = np.int32)
|
|
1772
|
+
cdef vector[vector[float]] cgrid = grid
|
|
1773
|
+
cdef vector[vector[vector[int]]] out
|
|
1774
|
+
cdef int num_summands, num_births, num_deaths
|
|
1775
|
+
cdef int num_parameters = self.num_parameters
|
|
1776
|
+
with nogil:
|
|
1777
|
+
out = self.cmod.to_flat_idx(cgrid)
|
|
1778
|
+
num_summands = out[0][0].size()
|
|
1779
|
+
num_births = out[1].size()
|
|
1780
|
+
num_deaths = out[2].size()
|
|
1781
|
+
idx = np.empty((2, num_summands),dtype=np.int32 )
|
|
1782
|
+
births = np.empty((num_births,num_parameters),dtype=np.int32)
|
|
1783
|
+
deaths = np.empty((num_deaths,num_parameters),dtype=np.int32)
|
|
1784
|
+
|
|
1785
|
+
cdef int32_t[:,:] idx_view = idx
|
|
1786
|
+
cdef int32_t[:,:] births_view = births
|
|
1787
|
+
cdef int32_t[:,:] deaths_view = deaths
|
|
1788
|
+
|
|
1789
|
+
with nogil:
|
|
1790
|
+
for i in range(num_summands):
|
|
1791
|
+
idx_view[0,i] = out[0][0][i]
|
|
1792
|
+
idx_view[1,i] = out[0][1][i]
|
|
1793
|
+
for i in range(num_births):
|
|
1794
|
+
for j in range(num_parameters):
|
|
1795
|
+
births_view[i,j] = out[1][i][j]
|
|
1796
|
+
for i in range(num_deaths):
|
|
1797
|
+
for j in range(num_parameters):
|
|
1798
|
+
deaths_view[i,j] = out[2][i][j]
|
|
1799
|
+
|
|
1800
|
+
return idx, births,deaths
|
|
1801
|
+
|
|
1802
|
+
def distances_idx_to(self, pts, bool full=False, int n_jobs=1):
|
|
1803
|
+
pts = np.asarray(pts)
|
|
1804
|
+
if pts.ndim == 1:
|
|
1805
|
+
pts = pts[None]
|
|
1806
|
+
cdef vector[vector[float]] cpts = pts
|
|
1807
|
+
cdef vector[vector[vector[int]]] out
|
|
1808
|
+
with nogil:
|
|
1809
|
+
out = self.cmod.compute_distances_idx_to(cpts,full, n_jobs)
|
|
1810
|
+
return np.asarray(out, dtype=np.int32)
|
|
1811
|
+
|
|
1812
|
+
def distance_to(self, pts, bool signed=False, int n_jobs = 0)->np.ndarray:
|
|
1813
|
+
r"""
|
|
1814
|
+
Distance from a point to each summand's support.
|
|
1815
|
+
Signed distance is the distance to the boundary,
|
|
1816
|
+
with negative values inside the summands.
|
|
1817
|
+
|
|
1818
|
+
pts of shape (num_pts, num_parameters)
|
|
1819
|
+
|
|
1820
|
+
output shape : (num_pts,num_summands)
|
|
1821
|
+
"""
|
|
1822
|
+
pts = np.asarray(pts)
|
|
1823
|
+
if pts.ndim == 1:
|
|
1824
|
+
pts = pts[None]
|
|
1825
|
+
assert pts.shape[-1] == self.num_parameters
|
|
1826
|
+
cdef vector[vector[float]] cpts = pts
|
|
1827
|
+
# cdef vector[vector[float]] out
|
|
1828
|
+
to_fill = np.empty(shape=(cpts.size(),len(self)), dtype = np.float32)
|
|
1829
|
+
cdef float[:,:] c_to_fill = to_fill
|
|
1830
|
+
cdef float* data_ptr = &c_to_fill[0,0]
|
|
1831
|
+
with nogil:
|
|
1832
|
+
self.cmod.compute_distances_to(data_ptr, cpts,signed, n_jobs)
|
|
1833
|
+
return to_fill
|
|
1834
|
+
|
|
1835
|
+
def get_interleavings(self,box=None):
|
|
1836
|
+
if box is None:
|
|
1837
|
+
box = self.get_box()
|
|
1838
|
+
cdef Box[float] cbox = Box[float](box)
|
|
1839
|
+
return np.asarray(self.cmod.get_interleavings(cbox))
|
|
1840
|
+
|
|
1841
|
+
cdef class PyMultiDiagramPoint_f32:
|
|
1842
|
+
cdef MultiDiagram_point[One_critical_filtration[float]] point
|
|
1843
|
+
cdef set(self, MultiDiagram_point[One_critical_filtration[float]] pt):
|
|
1844
|
+
self.point = pt
|
|
1845
|
+
return self
|
|
1846
|
+
|
|
1847
|
+
def get_degree(self):
|
|
1848
|
+
return self.point.get_dimension()
|
|
1849
|
+
def get_birth(self):
|
|
1850
|
+
cdef One_critical_filtration[float] v = self.point.get_birth()
|
|
1851
|
+
return _ff21cview_f32(&v, copy=True)
|
|
1852
|
+
def get_death(self):
|
|
1853
|
+
cdef One_critical_filtration[float] v = self.point.get_death()
|
|
1854
|
+
return _ff21cview_f32(&v, copy=True)
|
|
1855
|
+
|
|
1856
|
+
|
|
1857
|
+
cdef class PyMultiDiagram_f32:
|
|
1858
|
+
r"""
|
|
1859
|
+
Stores the diagram of a PyModule on a line
|
|
1860
|
+
"""
|
|
1861
|
+
cdef MultiDiagram[One_critical_filtration[float], float] multiDiagram
|
|
1862
|
+
cdef set(self, MultiDiagram[One_critical_filtration[float], float] m):
|
|
1863
|
+
self.multiDiagram = m
|
|
1864
|
+
return self
|
|
1865
|
+
def get_points(self, degree:int=-1) -> np.ndarray:
|
|
1866
|
+
cdef vector[pair[vector[float],vector[float]]] out = self.multiDiagram.get_points(degree)
|
|
1867
|
+
if len(out) == 0 and len(self) == 0:
|
|
1868
|
+
return np.empty() # TODO Retrieve good number of parameters if there is no points in diagram
|
|
1869
|
+
if len(out) == 0:
|
|
1870
|
+
return np.empty((0,2,self.multiDiagram.at(0).get_dimension())) # gets the number of parameters
|
|
1871
|
+
return np.array(out)
|
|
1872
|
+
def to_multipers(self, dimension:int):
|
|
1873
|
+
return self.multiDiagram.to_multipers(dimension)
|
|
1874
|
+
def __len__(self) -> int:
|
|
1875
|
+
return self.multiDiagram.size()
|
|
1876
|
+
def __getitem__(self,i:int) -> PyMultiDiagramPoint_f32:
|
|
1877
|
+
return PyMultiDiagramPoint_f32().set(self.multiDiagram.at(i % self.multiDiagram.size()))
|
|
1878
|
+
cdef class PyMultiDiagrams_f32:
|
|
1879
|
+
"""
|
|
1880
|
+
Stores the barcodes of a PyModule on multiple lines
|
|
1881
|
+
"""
|
|
1882
|
+
cdef MultiDiagrams[One_critical_filtration[float], float] multiDiagrams
|
|
1883
|
+
cdef set(self,MultiDiagrams[One_critical_filtration[float], float] m):
|
|
1884
|
+
self.multiDiagrams = m
|
|
1885
|
+
return self
|
|
1886
|
+
def to_multipers(self):
|
|
1887
|
+
out = self.multiDiagrams.to_multipers()
|
|
1888
|
+
return [np.asarray(summand) for summand in out]
|
|
1889
|
+
def __getitem__(self,i:int):
|
|
1890
|
+
if i >=0 :
|
|
1891
|
+
return PyMultiDiagram_f32().set(self.multiDiagrams.at(i))
|
|
1892
|
+
else:
|
|
1893
|
+
return PyMultiDiagram_f32().set(self.multiDiagrams.at( self.multiDiagrams.size() - i))
|
|
1894
|
+
def __len__(self):
|
|
1895
|
+
return self.multiDiagrams.size()
|
|
1896
|
+
def get_points(self, degree:int=-1):
|
|
1897
|
+
return self.multiDiagrams.get_points()
|
|
1898
|
+
cdef _get_plot_bars(self, dimension:int=-1, min_persistence:float=0):
|
|
1899
|
+
return self.multiDiagrams._for_python_plot(dimension, min_persistence);
|
|
1900
|
+
def plot(self, degree:int=-1, min_persistence:float=0):
|
|
1901
|
+
"""
|
|
1902
|
+
Plots the barcodes.
|
|
1903
|
+
|
|
1904
|
+
Parameters
|
|
1905
|
+
----------
|
|
1906
|
+
|
|
1907
|
+
- degree:int=-1
|
|
1908
|
+
Only plots the bars of specified homology degree. Useful when the multidiagrams contains multiple dimenions
|
|
1909
|
+
- min_persistence:float=0
|
|
1910
|
+
Only plot bars of length greater than this value. Useful to reduce the time to plot.
|
|
1911
|
+
|
|
1912
|
+
Warning
|
|
1913
|
+
-------
|
|
1914
|
+
|
|
1915
|
+
If the barcodes are not thresholded, essential barcodes will not be displayed !
|
|
1916
|
+
|
|
1917
|
+
"""
|
|
1918
|
+
from cycler import cycler
|
|
1919
|
+
import matplotlib
|
|
1920
|
+
import matplotlib.pyplot as plt
|
|
1921
|
+
if len(self) == 0: return
|
|
1922
|
+
_cmap = matplotlib.colormaps["Spectral"]
|
|
1923
|
+
multibarcodes_, colors = self._get_plot_bars(degree, min_persistence)
|
|
1924
|
+
n_summands = np.max(colors)+1 if len(colors)>0 else 1
|
|
1925
|
+
|
|
1926
|
+
plt.rc('axes', prop_cycle = cycler('color', [_cmap(i/n_summands) for i in colors]))
|
|
1927
|
+
return plt.plot(*multibarcodes_)
|
|
1928
|
+
|
|
1929
|
+
|
|
1930
|
+
cdef dump_summand_f32(Summand[float]& summand):
|
|
1931
|
+
cdef vector[One_critical_filtration[float]] births = summand.get_birth_list()
|
|
1932
|
+
cdef vector[One_critical_filtration[float]] deaths = summand.get_death_list()
|
|
1933
|
+
return (
|
|
1934
|
+
np.array(_vff21cview_f32(births)),
|
|
1935
|
+
np.array(_vff21cview_f32(deaths)),
|
|
1936
|
+
summand.get_dimension(),
|
|
1937
|
+
)
|
|
1938
|
+
|
|
1939
|
+
cdef inline Summand[float] summand_from_dump_f32(summand_dump):
|
|
1940
|
+
cdef vector[One_critical_filtration[float]] births = _py2v1c_f32(summand_dump[0])
|
|
1941
|
+
cdef vector[One_critical_filtration[float]] deaths = _py2v1c_f32(summand_dump[1])
|
|
1942
|
+
cdef int dim = summand_dump[2]
|
|
1943
|
+
return Summand[float](births,deaths,dim)
|
|
1944
|
+
|
|
1945
|
+
cdef dump_cmod_f32(Module[float]& mod):
|
|
1946
|
+
cdef Box[float] cbox = mod.get_box()
|
|
1947
|
+
cdef int dim = mod.get_dimension()
|
|
1948
|
+
cdef cnp.ndarray[float, ndim=1] bottom_corner = _ff21cview_f32(&cbox.get_lower_corner())
|
|
1949
|
+
cdef cnp.ndarray[float, ndim=1] top_corner = _ff21cview_f32(&cbox.get_upper_corner())
|
|
1950
|
+
box = np.asarray([bottom_corner, top_corner])
|
|
1951
|
+
summands = tuple(dump_summand_f32(summand) for summand in mod)
|
|
1952
|
+
return box, summands
|
|
1953
|
+
|
|
1954
|
+
cdef Module[float] cmod_from_dump_f32(module_dump):
|
|
1955
|
+
box = module_dump[0]
|
|
1956
|
+
summands = module_dump[1]
|
|
1957
|
+
cdef Module[float] out_module = Module[float]()
|
|
1958
|
+
out_module.set_box(Box[float](box))
|
|
1959
|
+
for i in range(len(summands)):
|
|
1960
|
+
out_module.add_summand(summand_from_dump_f32(summands[i]))
|
|
1961
|
+
return out_module
|
|
1962
|
+
|
|
1963
|
+
|
|
1964
|
+
def from_dump_f32(dump)->PyModule_f32:
|
|
1965
|
+
r"""Retrieves a PyModule from a previous dump.
|
|
1966
|
+
|
|
1967
|
+
Parameters
|
|
1968
|
+
----------
|
|
1969
|
+
|
|
1970
|
+
dump: either the output of the dump function, or a file containing the output of a dump.
|
|
1971
|
+
The dumped module to retrieve
|
|
1972
|
+
|
|
1973
|
+
Returns
|
|
1974
|
+
-------
|
|
1975
|
+
|
|
1976
|
+
PyModule
|
|
1977
|
+
The retrieved module.
|
|
1978
|
+
"""
|
|
1979
|
+
# TODO : optimize...
|
|
1980
|
+
mod = PyModule_f32()
|
|
1981
|
+
if type(dump) is str:
|
|
1982
|
+
dump = pickle.load(open(dump, "rb"))
|
|
1983
|
+
cdef Module[float] cmod = cmod_from_dump_f32(dump)
|
|
1984
|
+
mod.cmod = cmod
|
|
1985
|
+
return mod
|
|
1986
|
+
|
|
1987
|
+
cdef class PySummand_i32:
|
|
1988
|
+
r"""
|
|
1989
|
+
Stores a Summand of a PyModule
|
|
1990
|
+
"""
|
|
1991
|
+
cdef Summand[int32_t] sum
|
|
1992
|
+
|
|
1993
|
+
def get_birth_list(self):
|
|
1994
|
+
cdef vector[One_critical_filtration[int32_t]] v = self.sum.get_birth_list()
|
|
1995
|
+
return _vff21cview_i32(v, copy = True, duplicate = self.num_parameters())
|
|
1996
|
+
|
|
1997
|
+
def get_death_list(self):
|
|
1998
|
+
cdef vector[One_critical_filtration[int32_t]] v = self.sum.get_death_list()
|
|
1999
|
+
return _vff21cview_i32(v, copy = True, duplicate = self.num_parameters())
|
|
2000
|
+
@property
|
|
2001
|
+
def degree(self)->int:
|
|
2002
|
+
return self.sum.get_dimension()
|
|
2003
|
+
|
|
2004
|
+
cdef set(self, Summand[int32_t]& summand):
|
|
2005
|
+
self.sum = summand
|
|
2006
|
+
return self
|
|
2007
|
+
def get_bounds(self):
|
|
2008
|
+
cdef pair[One_critical_filtration[int32_t],One_critical_filtration[int32_t]] cbounds
|
|
2009
|
+
with nogil:
|
|
2010
|
+
cbounds = self.sum.get_bounds().get_bounding_corners()
|
|
2011
|
+
return _ff21cview_i32(&cbounds.first).copy(), _ff21cview_i32(&cbounds.second).copy()
|
|
2012
|
+
@property
|
|
2013
|
+
def dtype(self):
|
|
2014
|
+
return np.int32
|
|
2015
|
+
|
|
2016
|
+
def num_parameters(self):
|
|
2017
|
+
cdef vector[One_critical_filtration[int32_t]] v = self.sum.get_birth_list()
|
|
2018
|
+
if v[0].is_finite():
|
|
2019
|
+
return v[0].num_parameters()
|
|
2020
|
+
v = self.sum.get_death_list()
|
|
2021
|
+
return v[0].num_parameters()
|
|
2022
|
+
def __eq__(self, PySummand_i32 other):
|
|
2023
|
+
return self.sum == other.sum
|
|
2024
|
+
|
|
2025
|
+
|
|
2026
|
+
|
|
2027
|
+
|
|
2028
|
+
cdef inline get_summand_filtration_values_i32(Summand[int32_t] summand):
|
|
2029
|
+
r"""
|
|
2030
|
+
Returns a list (over parameter) of the filtrations values of this parameter.
|
|
2031
|
+
"""
|
|
2032
|
+
cdef vector[One_critical_filtration[int32_t]] vb = summand.get_birth_list()
|
|
2033
|
+
cdef vector[One_critical_filtration[int32_t]] vd = summand.get_death_list()
|
|
2034
|
+
|
|
2035
|
+
if vb[0].is_finite():
|
|
2036
|
+
if vd[0].is_finite():
|
|
2037
|
+
pts = np.concatenate([_vff21cview_i32(vb, copy=True),
|
|
2038
|
+
_vff21cview_i32(vd, copy=True)],axis=0)
|
|
2039
|
+
else:
|
|
2040
|
+
pts = np.array(_vff21cview_i32(vb, copy=True))
|
|
2041
|
+
else:
|
|
2042
|
+
if vd[0].is_finite():
|
|
2043
|
+
pts = np.array(_vff21cview_i32(vd, copy=True))
|
|
2044
|
+
else:
|
|
2045
|
+
return []
|
|
2046
|
+
|
|
2047
|
+
num_parameters = pts.shape[1]
|
|
2048
|
+
out = [np.unique(pts[:,parameter]) for parameter in range(num_parameters)]
|
|
2049
|
+
out = [f[:-1] if f[-1] == np.inf else f for f in out]
|
|
2050
|
+
out = [f[1:] if f[0] == -np.inf else f for f in out]
|
|
2051
|
+
return out
|
|
2052
|
+
|
|
2053
|
+
cdef class PyBox_i32:
|
|
2054
|
+
cdef Box[int32_t] box
|
|
2055
|
+
def __cinit__(self, vector[int32_t]& bottomCorner, vector[int32_t]& topCorner):
|
|
2056
|
+
self.box = Box[int32_t](bottomCorner, topCorner)
|
|
2057
|
+
@property
|
|
2058
|
+
def num_parameters(self):
|
|
2059
|
+
cdef size_t dim = self.box.get_lower_corner().num_parameters()
|
|
2060
|
+
if dim == self.box.get_upper_corner().num_parameters(): return dim
|
|
2061
|
+
else: print("Bad box definition.")
|
|
2062
|
+
def contains(self, x):
|
|
2063
|
+
return self.box.contains(x)
|
|
2064
|
+
cdef set(self, Box[int32_t]& b):
|
|
2065
|
+
self.box = b
|
|
2066
|
+
return self
|
|
2067
|
+
|
|
2068
|
+
def get(self):
|
|
2069
|
+
return [<vector[int32_t]>self.box.get_lower_corner(), <vector[int32_t]>self.box.get_upper_corner()]
|
|
2070
|
+
def to_multipers(self):
|
|
2071
|
+
#assert (self.get_dimension() == 2) "Multipers only works in dimension 2 !"
|
|
2072
|
+
return np.array(self.get()).flatten(order = 'F')
|
|
2073
|
+
@property
|
|
2074
|
+
def dtype(self):
|
|
2075
|
+
return np.int32
|
|
2076
|
+
|
|
2077
|
+
|
|
2078
|
+
|
|
2079
|
+
cdef class PyModule_i32:
|
|
2080
|
+
r"""
|
|
2081
|
+
Stores a representation of a n-persistence module.
|
|
2082
|
+
"""
|
|
2083
|
+
cdef Module[int32_t] cmod
|
|
2084
|
+
|
|
2085
|
+
@property
|
|
2086
|
+
def dtype(self):
|
|
2087
|
+
return np.int32
|
|
2088
|
+
|
|
2089
|
+
cdef set(self, Module[int32_t] m):
|
|
2090
|
+
self.cmod = m
|
|
2091
|
+
|
|
2092
|
+
def __eq__(self, PyModule_i32 other):
|
|
2093
|
+
return self.cmod == other.cmod
|
|
2094
|
+
|
|
2095
|
+
def merge(self, PyModule_i32 other, int dim=-1):
|
|
2096
|
+
r"""
|
|
2097
|
+
Merges two modules into one
|
|
2098
|
+
"""
|
|
2099
|
+
cdef Module[int32_t] c_other = other.cmod
|
|
2100
|
+
with nogil:
|
|
2101
|
+
for summand in c_other:
|
|
2102
|
+
self.cmod.add_summand(summand, dim)
|
|
2103
|
+
return self
|
|
2104
|
+
|
|
2105
|
+
def _set_from_ptr(self, intptr_t module_ptr):
|
|
2106
|
+
r"""
|
|
2107
|
+
Copy module from a memory pointer. Unsafe.
|
|
2108
|
+
"""
|
|
2109
|
+
self.cmod = move(dereference(<Module[int32_t]*>(module_ptr)))
|
|
2110
|
+
def set_box(self, box):
|
|
2111
|
+
assert len(box) == 2, "Box format is [low, hight]"
|
|
2112
|
+
pybox = PyBox_i32(box[0], box[1])
|
|
2113
|
+
cdef Box[int32_t] cbox = pybox.box
|
|
2114
|
+
with nogil:
|
|
2115
|
+
self.cmod.set_box(cbox)
|
|
2116
|
+
return self
|
|
2117
|
+
def get_module_of_degree(self, int degree)->PyModule_i32: # TODO : in c++ ?
|
|
2118
|
+
r"""
|
|
2119
|
+
Returns a copy of a module of fixed degree.
|
|
2120
|
+
"""
|
|
2121
|
+
pmodule = PyModule_i32()
|
|
2122
|
+
cdef Box[int32_t] c_box = self.cmod.get_box()
|
|
2123
|
+
pmodule.cmod.set_box(c_box)
|
|
2124
|
+
with nogil:
|
|
2125
|
+
for summand in self.cmod:
|
|
2126
|
+
if summand.get_dimension() == degree:
|
|
2127
|
+
pmodule.cmod.add_summand(summand)
|
|
2128
|
+
return pmodule
|
|
2129
|
+
def get_module_of_degrees(self, degrees:Iterable[int])->PyModule_i32: # TODO : in c++ ?
|
|
2130
|
+
r"""
|
|
2131
|
+
Returns a copy of the summands of degrees in `degrees`
|
|
2132
|
+
"""
|
|
2133
|
+
pmodule = PyModule_i32()
|
|
2134
|
+
cdef Box[int32_t] c_box = self.cmod.get_box()
|
|
2135
|
+
pmodule.cmod.set_box(c_box)
|
|
2136
|
+
cdef vector[int] cdegrees = degrees
|
|
2137
|
+
with nogil:
|
|
2138
|
+
for summand in self.cmod:
|
|
2139
|
+
for d in cdegrees:
|
|
2140
|
+
if d == summand.get_dimension():
|
|
2141
|
+
pmodule.cmod.add_summand(summand)
|
|
2142
|
+
return pmodule
|
|
2143
|
+
def __len__(self)->int:
|
|
2144
|
+
return self.cmod.size()
|
|
2145
|
+
def get_bottom(self)->np.ndarray:
|
|
2146
|
+
r"""
|
|
2147
|
+
Bottom of the box of the module
|
|
2148
|
+
"""
|
|
2149
|
+
return np.asarray(<vector[int32_t]>(self.cmod.get_box().get_lower_corner()))
|
|
2150
|
+
def get_top(self)->np.ndarray:
|
|
2151
|
+
r"""
|
|
2152
|
+
Top of the box of the module
|
|
2153
|
+
"""
|
|
2154
|
+
return np.asarray(<vector[int32_t]>(self.cmod.get_box().get_upper_corner()))
|
|
2155
|
+
def get_box(self)->np.ndarray:
|
|
2156
|
+
r"""
|
|
2157
|
+
Returns the current bounding box of the module.
|
|
2158
|
+
"""
|
|
2159
|
+
return np.asarray([self.get_bottom(), self.get_top()])
|
|
2160
|
+
@property
|
|
2161
|
+
def max_degree(self)->int:
|
|
2162
|
+
r"""
|
|
2163
|
+
Returns the maximum degree of the module.
|
|
2164
|
+
"""
|
|
2165
|
+
return self.cmod.get_dimension()
|
|
2166
|
+
@property
|
|
2167
|
+
def num_parameters(self)->int:
|
|
2168
|
+
cdef size_t dim = self.cmod.get_box().get_lower_corner().num_parameters()
|
|
2169
|
+
assert dim == self.cmod.get_box().get_upper_corner().num_parameters(), "Bad box definition, cannot infer num_parameters."
|
|
2170
|
+
return dim
|
|
2171
|
+
def dump(self, path:str|None=None):
|
|
2172
|
+
r"""
|
|
2173
|
+
Dumps the module into a pickle-able format.
|
|
2174
|
+
|
|
2175
|
+
Parameters
|
|
2176
|
+
----------
|
|
2177
|
+
|
|
2178
|
+
path:str=None (optional) saves the pickled module in specified path
|
|
2179
|
+
|
|
2180
|
+
Returns
|
|
2181
|
+
-------
|
|
2182
|
+
|
|
2183
|
+
list of list, encoding the module, which can be retrieved with the function `from_dump`.
|
|
2184
|
+
"""
|
|
2185
|
+
## TODO : optimize, but not really used.
|
|
2186
|
+
return dump_cmod_i32(self.cmod)
|
|
2187
|
+
def __getstate__(self):
|
|
2188
|
+
return self.dump()
|
|
2189
|
+
def __setstate__(self,dump):
|
|
2190
|
+
cdef Module[int32_t] cmod = cmod_from_dump_i32(dump)
|
|
2191
|
+
self.cmod = cmod
|
|
2192
|
+
return
|
|
2193
|
+
def __getitem__(self, int i) -> PySummand_i32:
|
|
2194
|
+
if i == slice(None):
|
|
2195
|
+
return self
|
|
2196
|
+
summand = PySummand_i32()
|
|
2197
|
+
summand.set(self.cmod.at(i % self.cmod.size()))
|
|
2198
|
+
return summand
|
|
2199
|
+
def __iter__(self):
|
|
2200
|
+
cdef int num_summands = self.cmod.size()
|
|
2201
|
+
for i in range(num_summands):
|
|
2202
|
+
summand = PySummand_i32()
|
|
2203
|
+
summand.set(self.cmod.at(i)) ## hmm copy. maybe do summand from ptr
|
|
2204
|
+
yield summand
|
|
2205
|
+
|
|
2206
|
+
def get_bounds(self):
|
|
2207
|
+
r"""
|
|
2208
|
+
Computes bounds from the summands' bounds.
|
|
2209
|
+
Useful to change this' box.
|
|
2210
|
+
"""
|
|
2211
|
+
cdef pair[One_critical_filtration[int32_t],One_critical_filtration[int32_t]] cbounds
|
|
2212
|
+
with nogil:
|
|
2213
|
+
cbounds = self.cmod.get_bounds().get_bounding_corners()
|
|
2214
|
+
return _ff21cview_i32(&cbounds.first).copy(), _ff21cview_i32(&cbounds.second).copy()
|
|
2215
|
+
def rescale(self,rescale_factors, int degree=-1):
|
|
2216
|
+
r"""
|
|
2217
|
+
Rescales the fitlration values of the summands by this rescaling vector.
|
|
2218
|
+
"""
|
|
2219
|
+
cdef vector[int32_t] crescale_factors = rescale_factors
|
|
2220
|
+
with nogil:
|
|
2221
|
+
self.cmod.rescale(crescale_factors,degree)
|
|
2222
|
+
def translate(self,translation, int degree=-1):
|
|
2223
|
+
r"""
|
|
2224
|
+
Translates the module in the filtration space by this vector.
|
|
2225
|
+
"""
|
|
2226
|
+
cdef vector[int32_t] ctranslation = translation
|
|
2227
|
+
with nogil:
|
|
2228
|
+
self.cmod.translate(ctranslation,degree)
|
|
2229
|
+
|
|
2230
|
+
def get_filtration_values(self, bool unique=True):
|
|
2231
|
+
r"""
|
|
2232
|
+
Retrieves all filtration values of the summands of the module.
|
|
2233
|
+
|
|
2234
|
+
Output format
|
|
2235
|
+
-------------
|
|
2236
|
+
|
|
2237
|
+
list of filtration values for parameter.
|
|
2238
|
+
"""
|
|
2239
|
+
if len(self) ==0:
|
|
2240
|
+
return np.empty((self.num_parameters,0))
|
|
2241
|
+
values = tuple(tuple(stuff) if len(stuff:=get_summand_filtration_values_i32(summand)) == self.num_parameters else list(stuff) + [[]]*(self.num_parameters - len(stuff)) for summand in self.cmod)
|
|
2242
|
+
try:
|
|
2243
|
+
values = tuple(np.concatenate([
|
|
2244
|
+
f[parameter]
|
|
2245
|
+
for f in values
|
|
2246
|
+
], axis=0) for parameter in range(self.num_parameters)
|
|
2247
|
+
)
|
|
2248
|
+
except:
|
|
2249
|
+
return values
|
|
2250
|
+
if unique:
|
|
2251
|
+
return [np.unique(f) for f in values]
|
|
2252
|
+
return values
|
|
2253
|
+
|
|
2254
|
+
def plot(self, int degree=-1,**kwargs)->None:
|
|
2255
|
+
r"""Shows the module on a plot. Each color corresponds to an apprimation summand of the module, and its shape corresponds to its support.
|
|
2256
|
+
Only works with 2-parameter modules.
|
|
2257
|
+
|
|
2258
|
+
Parameters
|
|
2259
|
+
----------
|
|
2260
|
+
degree = -1 : integer
|
|
2261
|
+
If positive returns only the image of dimension `dimension`.
|
|
2262
|
+
box=None : of the form [[b_x,b_y], [d_x,d_y]] where b,d are the bottom and top corner of the rectangle.
|
|
2263
|
+
If non-None, will plot the module on this specific rectangle.
|
|
2264
|
+
min_persistence =0 : float
|
|
2265
|
+
Only plots the summand with a persistence above this threshold.
|
|
2266
|
+
separated=False : bool
|
|
2267
|
+
If true, plot each summand in a different plot.
|
|
2268
|
+
alpha=1 : float
|
|
2269
|
+
Transparancy parameter
|
|
2270
|
+
save = False : string
|
|
2271
|
+
if nontrivial, will save the figure at this path
|
|
2272
|
+
|
|
2273
|
+
|
|
2274
|
+
Returns
|
|
2275
|
+
-------
|
|
2276
|
+
The figure of the plot.
|
|
2277
|
+
"""
|
|
2278
|
+
from multipers.plots import plot2d_PyModule
|
|
2279
|
+
import matplotlib.pyplot as plt
|
|
2280
|
+
box = kwargs.pop('box', self.get_box())
|
|
2281
|
+
if (len(box[0]) != 2):
|
|
2282
|
+
print("Filtration size :", len(box[0]), " != 2")
|
|
2283
|
+
return
|
|
2284
|
+
if(degree < 0):
|
|
2285
|
+
dims = np.unique(self.get_dimensions())
|
|
2286
|
+
separated = kwargs.pop("separated", False)
|
|
2287
|
+
ndim = len(dims)
|
|
2288
|
+
scale = kwargs.pop("scale", 4)
|
|
2289
|
+
if separated:
|
|
2290
|
+
fig = None
|
|
2291
|
+
axes = None
|
|
2292
|
+
elif ndim > 1:
|
|
2293
|
+
fig, axes = plt.subplots(1,ndim, figsize=(ndim*scale,scale))
|
|
2294
|
+
else:
|
|
2295
|
+
fig = plt.gcf()
|
|
2296
|
+
axes = [plt.gca()]
|
|
2297
|
+
for dim_idx in range(ndim):
|
|
2298
|
+
if not separated:
|
|
2299
|
+
plt.sca(axes[dim_idx])
|
|
2300
|
+
self.plot(dims[dim_idx],box=box, separated = separated, **kwargs)
|
|
2301
|
+
return
|
|
2302
|
+
corners = self.cmod.get_corners_of_dimension(degree)
|
|
2303
|
+
plot2d_PyModule(corners, box=box, dimension=degree, **kwargs)
|
|
2304
|
+
return
|
|
2305
|
+
def degree_splits(self):
|
|
2306
|
+
return np.asarray(self.cmod.get_degree_splits(), dtype=np.int64)
|
|
2307
|
+
|
|
2308
|
+
|
|
2309
|
+
cdef dump_summand_i32(Summand[int32_t]& summand):
|
|
2310
|
+
cdef vector[One_critical_filtration[int32_t]] births = summand.get_birth_list()
|
|
2311
|
+
cdef vector[One_critical_filtration[int32_t]] deaths = summand.get_death_list()
|
|
2312
|
+
return (
|
|
2313
|
+
np.array(_vff21cview_i32(births)),
|
|
2314
|
+
np.array(_vff21cview_i32(deaths)),
|
|
2315
|
+
summand.get_dimension(),
|
|
2316
|
+
)
|
|
2317
|
+
|
|
2318
|
+
cdef inline Summand[int32_t] summand_from_dump_i32(summand_dump):
|
|
2319
|
+
cdef vector[One_critical_filtration[int32_t]] births = _py2v1c_i32(summand_dump[0])
|
|
2320
|
+
cdef vector[One_critical_filtration[int32_t]] deaths = _py2v1c_i32(summand_dump[1])
|
|
2321
|
+
cdef int dim = summand_dump[2]
|
|
2322
|
+
return Summand[int32_t](births,deaths,dim)
|
|
2323
|
+
|
|
2324
|
+
cdef dump_cmod_i32(Module[int32_t]& mod):
|
|
2325
|
+
cdef Box[int32_t] cbox = mod.get_box()
|
|
2326
|
+
cdef int dim = mod.get_dimension()
|
|
2327
|
+
cdef cnp.ndarray[int32_t, ndim=1] bottom_corner = _ff21cview_i32(&cbox.get_lower_corner())
|
|
2328
|
+
cdef cnp.ndarray[int32_t, ndim=1] top_corner = _ff21cview_i32(&cbox.get_upper_corner())
|
|
2329
|
+
box = np.asarray([bottom_corner, top_corner])
|
|
2330
|
+
summands = tuple(dump_summand_i32(summand) for summand in mod)
|
|
2331
|
+
return box, summands
|
|
2332
|
+
|
|
2333
|
+
cdef Module[int32_t] cmod_from_dump_i32(module_dump):
|
|
2334
|
+
box = module_dump[0]
|
|
2335
|
+
summands = module_dump[1]
|
|
2336
|
+
cdef Module[int32_t] out_module = Module[int32_t]()
|
|
2337
|
+
out_module.set_box(Box[int32_t](box))
|
|
2338
|
+
for i in range(len(summands)):
|
|
2339
|
+
out_module.add_summand(summand_from_dump_i32(summands[i]))
|
|
2340
|
+
return out_module
|
|
2341
|
+
|
|
2342
|
+
|
|
2343
|
+
def from_dump_i32(dump)->PyModule_i32:
|
|
2344
|
+
r"""Retrieves a PyModule from a previous dump.
|
|
2345
|
+
|
|
2346
|
+
Parameters
|
|
2347
|
+
----------
|
|
2348
|
+
|
|
2349
|
+
dump: either the output of the dump function, or a file containing the output of a dump.
|
|
2350
|
+
The dumped module to retrieve
|
|
2351
|
+
|
|
2352
|
+
Returns
|
|
2353
|
+
-------
|
|
2354
|
+
|
|
2355
|
+
PyModule
|
|
2356
|
+
The retrieved module.
|
|
2357
|
+
"""
|
|
2358
|
+
# TODO : optimize...
|
|
2359
|
+
mod = PyModule_i32()
|
|
2360
|
+
if type(dump) is str:
|
|
2361
|
+
dump = pickle.load(open(dump, "rb"))
|
|
2362
|
+
cdef Module[int32_t] cmod = cmod_from_dump_i32(dump)
|
|
2363
|
+
mod.cmod = cmod
|
|
2364
|
+
return mod
|
|
2365
|
+
|
|
2366
|
+
cdef class PySummand_i64:
|
|
2367
|
+
r"""
|
|
2368
|
+
Stores a Summand of a PyModule
|
|
2369
|
+
"""
|
|
2370
|
+
cdef Summand[int64_t] sum
|
|
2371
|
+
|
|
2372
|
+
def get_birth_list(self):
|
|
2373
|
+
cdef vector[One_critical_filtration[int64_t]] v = self.sum.get_birth_list()
|
|
2374
|
+
return _vff21cview_i64(v, copy = True, duplicate = self.num_parameters())
|
|
2375
|
+
|
|
2376
|
+
def get_death_list(self):
|
|
2377
|
+
cdef vector[One_critical_filtration[int64_t]] v = self.sum.get_death_list()
|
|
2378
|
+
return _vff21cview_i64(v, copy = True, duplicate = self.num_parameters())
|
|
2379
|
+
@property
|
|
2380
|
+
def degree(self)->int:
|
|
2381
|
+
return self.sum.get_dimension()
|
|
2382
|
+
|
|
2383
|
+
cdef set(self, Summand[int64_t]& summand):
|
|
2384
|
+
self.sum = summand
|
|
2385
|
+
return self
|
|
2386
|
+
def get_bounds(self):
|
|
2387
|
+
cdef pair[One_critical_filtration[int64_t],One_critical_filtration[int64_t]] cbounds
|
|
2388
|
+
with nogil:
|
|
2389
|
+
cbounds = self.sum.get_bounds().get_bounding_corners()
|
|
2390
|
+
return _ff21cview_i64(&cbounds.first).copy(), _ff21cview_i64(&cbounds.second).copy()
|
|
2391
|
+
@property
|
|
2392
|
+
def dtype(self):
|
|
2393
|
+
return np.int64
|
|
2394
|
+
|
|
2395
|
+
def num_parameters(self):
|
|
2396
|
+
cdef vector[One_critical_filtration[int64_t]] v = self.sum.get_birth_list()
|
|
2397
|
+
if v[0].is_finite():
|
|
2398
|
+
return v[0].num_parameters()
|
|
2399
|
+
v = self.sum.get_death_list()
|
|
2400
|
+
return v[0].num_parameters()
|
|
2401
|
+
def __eq__(self, PySummand_i64 other):
|
|
2402
|
+
return self.sum == other.sum
|
|
2403
|
+
|
|
2404
|
+
|
|
2405
|
+
|
|
2406
|
+
|
|
2407
|
+
cdef inline get_summand_filtration_values_i64(Summand[int64_t] summand):
|
|
2408
|
+
r"""
|
|
2409
|
+
Returns a list (over parameter) of the filtrations values of this parameter.
|
|
2410
|
+
"""
|
|
2411
|
+
cdef vector[One_critical_filtration[int64_t]] vb = summand.get_birth_list()
|
|
2412
|
+
cdef vector[One_critical_filtration[int64_t]] vd = summand.get_death_list()
|
|
2413
|
+
|
|
2414
|
+
if vb[0].is_finite():
|
|
2415
|
+
if vd[0].is_finite():
|
|
2416
|
+
pts = np.concatenate([_vff21cview_i64(vb, copy=True),
|
|
2417
|
+
_vff21cview_i64(vd, copy=True)],axis=0)
|
|
2418
|
+
else:
|
|
2419
|
+
pts = np.array(_vff21cview_i64(vb, copy=True))
|
|
2420
|
+
else:
|
|
2421
|
+
if vd[0].is_finite():
|
|
2422
|
+
pts = np.array(_vff21cview_i64(vd, copy=True))
|
|
2423
|
+
else:
|
|
2424
|
+
return []
|
|
2425
|
+
|
|
2426
|
+
num_parameters = pts.shape[1]
|
|
2427
|
+
out = [np.unique(pts[:,parameter]) for parameter in range(num_parameters)]
|
|
2428
|
+
out = [f[:-1] if f[-1] == np.inf else f for f in out]
|
|
2429
|
+
out = [f[1:] if f[0] == -np.inf else f for f in out]
|
|
2430
|
+
return out
|
|
2431
|
+
|
|
2432
|
+
cdef class PyBox_i64:
|
|
2433
|
+
cdef Box[int64_t] box
|
|
2434
|
+
def __cinit__(self, vector[int64_t]& bottomCorner, vector[int64_t]& topCorner):
|
|
2435
|
+
self.box = Box[int64_t](bottomCorner, topCorner)
|
|
2436
|
+
@property
|
|
2437
|
+
def num_parameters(self):
|
|
2438
|
+
cdef size_t dim = self.box.get_lower_corner().num_parameters()
|
|
2439
|
+
if dim == self.box.get_upper_corner().num_parameters(): return dim
|
|
2440
|
+
else: print("Bad box definition.")
|
|
2441
|
+
def contains(self, x):
|
|
2442
|
+
return self.box.contains(x)
|
|
2443
|
+
cdef set(self, Box[int64_t]& b):
|
|
2444
|
+
self.box = b
|
|
2445
|
+
return self
|
|
2446
|
+
|
|
2447
|
+
def get(self):
|
|
2448
|
+
return [<vector[int64_t]>self.box.get_lower_corner(), <vector[int64_t]>self.box.get_upper_corner()]
|
|
2449
|
+
def to_multipers(self):
|
|
2450
|
+
#assert (self.get_dimension() == 2) "Multipers only works in dimension 2 !"
|
|
2451
|
+
return np.array(self.get()).flatten(order = 'F')
|
|
2452
|
+
@property
|
|
2453
|
+
def dtype(self):
|
|
2454
|
+
return np.int64
|
|
2455
|
+
|
|
2456
|
+
|
|
2457
|
+
|
|
2458
|
+
cdef class PyModule_i64:
|
|
2459
|
+
r"""
|
|
2460
|
+
Stores a representation of a n-persistence module.
|
|
2461
|
+
"""
|
|
2462
|
+
cdef Module[int64_t] cmod
|
|
2463
|
+
|
|
2464
|
+
@property
|
|
2465
|
+
def dtype(self):
|
|
2466
|
+
return np.int64
|
|
2467
|
+
|
|
2468
|
+
cdef set(self, Module[int64_t] m):
|
|
2469
|
+
self.cmod = m
|
|
2470
|
+
|
|
2471
|
+
def __eq__(self, PyModule_i64 other):
|
|
2472
|
+
return self.cmod == other.cmod
|
|
2473
|
+
|
|
2474
|
+
def merge(self, PyModule_i64 other, int dim=-1):
|
|
2475
|
+
r"""
|
|
2476
|
+
Merges two modules into one
|
|
2477
|
+
"""
|
|
2478
|
+
cdef Module[int64_t] c_other = other.cmod
|
|
2479
|
+
with nogil:
|
|
2480
|
+
for summand in c_other:
|
|
2481
|
+
self.cmod.add_summand(summand, dim)
|
|
2482
|
+
return self
|
|
2483
|
+
|
|
2484
|
+
def _set_from_ptr(self, intptr_t module_ptr):
|
|
2485
|
+
r"""
|
|
2486
|
+
Copy module from a memory pointer. Unsafe.
|
|
2487
|
+
"""
|
|
2488
|
+
self.cmod = move(dereference(<Module[int64_t]*>(module_ptr)))
|
|
2489
|
+
def set_box(self, box):
|
|
2490
|
+
assert len(box) == 2, "Box format is [low, hight]"
|
|
2491
|
+
pybox = PyBox_i64(box[0], box[1])
|
|
2492
|
+
cdef Box[int64_t] cbox = pybox.box
|
|
2493
|
+
with nogil:
|
|
2494
|
+
self.cmod.set_box(cbox)
|
|
2495
|
+
return self
|
|
2496
|
+
def get_module_of_degree(self, int degree)->PyModule_i64: # TODO : in c++ ?
|
|
2497
|
+
r"""
|
|
2498
|
+
Returns a copy of a module of fixed degree.
|
|
2499
|
+
"""
|
|
2500
|
+
pmodule = PyModule_i64()
|
|
2501
|
+
cdef Box[int64_t] c_box = self.cmod.get_box()
|
|
2502
|
+
pmodule.cmod.set_box(c_box)
|
|
2503
|
+
with nogil:
|
|
2504
|
+
for summand in self.cmod:
|
|
2505
|
+
if summand.get_dimension() == degree:
|
|
2506
|
+
pmodule.cmod.add_summand(summand)
|
|
2507
|
+
return pmodule
|
|
2508
|
+
def get_module_of_degrees(self, degrees:Iterable[int])->PyModule_i64: # TODO : in c++ ?
|
|
2509
|
+
r"""
|
|
2510
|
+
Returns a copy of the summands of degrees in `degrees`
|
|
2511
|
+
"""
|
|
2512
|
+
pmodule = PyModule_i64()
|
|
2513
|
+
cdef Box[int64_t] c_box = self.cmod.get_box()
|
|
2514
|
+
pmodule.cmod.set_box(c_box)
|
|
2515
|
+
cdef vector[int] cdegrees = degrees
|
|
2516
|
+
with nogil:
|
|
2517
|
+
for summand in self.cmod:
|
|
2518
|
+
for d in cdegrees:
|
|
2519
|
+
if d == summand.get_dimension():
|
|
2520
|
+
pmodule.cmod.add_summand(summand)
|
|
2521
|
+
return pmodule
|
|
2522
|
+
def __len__(self)->int:
|
|
2523
|
+
return self.cmod.size()
|
|
2524
|
+
def get_bottom(self)->np.ndarray:
|
|
2525
|
+
r"""
|
|
2526
|
+
Bottom of the box of the module
|
|
2527
|
+
"""
|
|
2528
|
+
return np.asarray(<vector[int64_t]>(self.cmod.get_box().get_lower_corner()))
|
|
2529
|
+
def get_top(self)->np.ndarray:
|
|
2530
|
+
r"""
|
|
2531
|
+
Top of the box of the module
|
|
2532
|
+
"""
|
|
2533
|
+
return np.asarray(<vector[int64_t]>(self.cmod.get_box().get_upper_corner()))
|
|
2534
|
+
def get_box(self)->np.ndarray:
|
|
2535
|
+
r"""
|
|
2536
|
+
Returns the current bounding box of the module.
|
|
2537
|
+
"""
|
|
2538
|
+
return np.asarray([self.get_bottom(), self.get_top()])
|
|
2539
|
+
@property
|
|
2540
|
+
def max_degree(self)->int:
|
|
2541
|
+
r"""
|
|
2542
|
+
Returns the maximum degree of the module.
|
|
2543
|
+
"""
|
|
2544
|
+
return self.cmod.get_dimension()
|
|
2545
|
+
@property
|
|
2546
|
+
def num_parameters(self)->int:
|
|
2547
|
+
cdef size_t dim = self.cmod.get_box().get_lower_corner().num_parameters()
|
|
2548
|
+
assert dim == self.cmod.get_box().get_upper_corner().num_parameters(), "Bad box definition, cannot infer num_parameters."
|
|
2549
|
+
return dim
|
|
2550
|
+
def dump(self, path:str|None=None):
|
|
2551
|
+
r"""
|
|
2552
|
+
Dumps the module into a pickle-able format.
|
|
2553
|
+
|
|
2554
|
+
Parameters
|
|
2555
|
+
----------
|
|
2556
|
+
|
|
2557
|
+
path:str=None (optional) saves the pickled module in specified path
|
|
2558
|
+
|
|
2559
|
+
Returns
|
|
2560
|
+
-------
|
|
2561
|
+
|
|
2562
|
+
list of list, encoding the module, which can be retrieved with the function `from_dump`.
|
|
2563
|
+
"""
|
|
2564
|
+
## TODO : optimize, but not really used.
|
|
2565
|
+
return dump_cmod_i64(self.cmod)
|
|
2566
|
+
def __getstate__(self):
|
|
2567
|
+
return self.dump()
|
|
2568
|
+
def __setstate__(self,dump):
|
|
2569
|
+
cdef Module[int64_t] cmod = cmod_from_dump_i64(dump)
|
|
2570
|
+
self.cmod = cmod
|
|
2571
|
+
return
|
|
2572
|
+
def __getitem__(self, int i) -> PySummand_i64:
|
|
2573
|
+
if i == slice(None):
|
|
2574
|
+
return self
|
|
2575
|
+
summand = PySummand_i64()
|
|
2576
|
+
summand.set(self.cmod.at(i % self.cmod.size()))
|
|
2577
|
+
return summand
|
|
2578
|
+
def __iter__(self):
|
|
2579
|
+
cdef int num_summands = self.cmod.size()
|
|
2580
|
+
for i in range(num_summands):
|
|
2581
|
+
summand = PySummand_i64()
|
|
2582
|
+
summand.set(self.cmod.at(i)) ## hmm copy. maybe do summand from ptr
|
|
2583
|
+
yield summand
|
|
2584
|
+
|
|
2585
|
+
def get_bounds(self):
|
|
2586
|
+
r"""
|
|
2587
|
+
Computes bounds from the summands' bounds.
|
|
2588
|
+
Useful to change this' box.
|
|
2589
|
+
"""
|
|
2590
|
+
cdef pair[One_critical_filtration[int64_t],One_critical_filtration[int64_t]] cbounds
|
|
2591
|
+
with nogil:
|
|
2592
|
+
cbounds = self.cmod.get_bounds().get_bounding_corners()
|
|
2593
|
+
return _ff21cview_i64(&cbounds.first).copy(), _ff21cview_i64(&cbounds.second).copy()
|
|
2594
|
+
def rescale(self,rescale_factors, int degree=-1):
|
|
2595
|
+
r"""
|
|
2596
|
+
Rescales the fitlration values of the summands by this rescaling vector.
|
|
2597
|
+
"""
|
|
2598
|
+
cdef vector[int64_t] crescale_factors = rescale_factors
|
|
2599
|
+
with nogil:
|
|
2600
|
+
self.cmod.rescale(crescale_factors,degree)
|
|
2601
|
+
def translate(self,translation, int degree=-1):
|
|
2602
|
+
r"""
|
|
2603
|
+
Translates the module in the filtration space by this vector.
|
|
2604
|
+
"""
|
|
2605
|
+
cdef vector[int64_t] ctranslation = translation
|
|
2606
|
+
with nogil:
|
|
2607
|
+
self.cmod.translate(ctranslation,degree)
|
|
2608
|
+
|
|
2609
|
+
def get_filtration_values(self, bool unique=True):
|
|
2610
|
+
r"""
|
|
2611
|
+
Retrieves all filtration values of the summands of the module.
|
|
2612
|
+
|
|
2613
|
+
Output format
|
|
2614
|
+
-------------
|
|
2615
|
+
|
|
2616
|
+
list of filtration values for parameter.
|
|
2617
|
+
"""
|
|
2618
|
+
if len(self) ==0:
|
|
2619
|
+
return np.empty((self.num_parameters,0))
|
|
2620
|
+
values = tuple(tuple(stuff) if len(stuff:=get_summand_filtration_values_i64(summand)) == self.num_parameters else list(stuff) + [[]]*(self.num_parameters - len(stuff)) for summand in self.cmod)
|
|
2621
|
+
try:
|
|
2622
|
+
values = tuple(np.concatenate([
|
|
2623
|
+
f[parameter]
|
|
2624
|
+
for f in values
|
|
2625
|
+
], axis=0) for parameter in range(self.num_parameters)
|
|
2626
|
+
)
|
|
2627
|
+
except:
|
|
2628
|
+
return values
|
|
2629
|
+
if unique:
|
|
2630
|
+
return [np.unique(f) for f in values]
|
|
2631
|
+
return values
|
|
2632
|
+
|
|
2633
|
+
def plot(self, int degree=-1,**kwargs)->None:
|
|
2634
|
+
r"""Shows the module on a plot. Each color corresponds to an apprimation summand of the module, and its shape corresponds to its support.
|
|
2635
|
+
Only works with 2-parameter modules.
|
|
2636
|
+
|
|
2637
|
+
Parameters
|
|
2638
|
+
----------
|
|
2639
|
+
degree = -1 : integer
|
|
2640
|
+
If positive returns only the image of dimension `dimension`.
|
|
2641
|
+
box=None : of the form [[b_x,b_y], [d_x,d_y]] where b,d are the bottom and top corner of the rectangle.
|
|
2642
|
+
If non-None, will plot the module on this specific rectangle.
|
|
2643
|
+
min_persistence =0 : float
|
|
2644
|
+
Only plots the summand with a persistence above this threshold.
|
|
2645
|
+
separated=False : bool
|
|
2646
|
+
If true, plot each summand in a different plot.
|
|
2647
|
+
alpha=1 : float
|
|
2648
|
+
Transparancy parameter
|
|
2649
|
+
save = False : string
|
|
2650
|
+
if nontrivial, will save the figure at this path
|
|
2651
|
+
|
|
2652
|
+
|
|
2653
|
+
Returns
|
|
2654
|
+
-------
|
|
2655
|
+
The figure of the plot.
|
|
2656
|
+
"""
|
|
2657
|
+
from multipers.plots import plot2d_PyModule
|
|
2658
|
+
import matplotlib.pyplot as plt
|
|
2659
|
+
box = kwargs.pop('box', self.get_box())
|
|
2660
|
+
if (len(box[0]) != 2):
|
|
2661
|
+
print("Filtration size :", len(box[0]), " != 2")
|
|
2662
|
+
return
|
|
2663
|
+
if(degree < 0):
|
|
2664
|
+
dims = np.unique(self.get_dimensions())
|
|
2665
|
+
separated = kwargs.pop("separated", False)
|
|
2666
|
+
ndim = len(dims)
|
|
2667
|
+
scale = kwargs.pop("scale", 4)
|
|
2668
|
+
if separated:
|
|
2669
|
+
fig = None
|
|
2670
|
+
axes = None
|
|
2671
|
+
elif ndim > 1:
|
|
2672
|
+
fig, axes = plt.subplots(1,ndim, figsize=(ndim*scale,scale))
|
|
2673
|
+
else:
|
|
2674
|
+
fig = plt.gcf()
|
|
2675
|
+
axes = [plt.gca()]
|
|
2676
|
+
for dim_idx in range(ndim):
|
|
2677
|
+
if not separated:
|
|
2678
|
+
plt.sca(axes[dim_idx])
|
|
2679
|
+
self.plot(dims[dim_idx],box=box, separated = separated, **kwargs)
|
|
2680
|
+
return
|
|
2681
|
+
corners = self.cmod.get_corners_of_dimension(degree)
|
|
2682
|
+
plot2d_PyModule(corners, box=box, dimension=degree, **kwargs)
|
|
2683
|
+
return
|
|
2684
|
+
def degree_splits(self):
|
|
2685
|
+
return np.asarray(self.cmod.get_degree_splits(), dtype=np.int64)
|
|
2686
|
+
|
|
2687
|
+
|
|
2688
|
+
cdef dump_summand_i64(Summand[int64_t]& summand):
|
|
2689
|
+
cdef vector[One_critical_filtration[int64_t]] births = summand.get_birth_list()
|
|
2690
|
+
cdef vector[One_critical_filtration[int64_t]] deaths = summand.get_death_list()
|
|
2691
|
+
return (
|
|
2692
|
+
np.array(_vff21cview_i64(births)),
|
|
2693
|
+
np.array(_vff21cview_i64(deaths)),
|
|
2694
|
+
summand.get_dimension(),
|
|
2695
|
+
)
|
|
2696
|
+
|
|
2697
|
+
cdef inline Summand[int64_t] summand_from_dump_i64(summand_dump):
|
|
2698
|
+
cdef vector[One_critical_filtration[int64_t]] births = _py2v1c_i64(summand_dump[0])
|
|
2699
|
+
cdef vector[One_critical_filtration[int64_t]] deaths = _py2v1c_i64(summand_dump[1])
|
|
2700
|
+
cdef int dim = summand_dump[2]
|
|
2701
|
+
return Summand[int64_t](births,deaths,dim)
|
|
2702
|
+
|
|
2703
|
+
cdef dump_cmod_i64(Module[int64_t]& mod):
|
|
2704
|
+
cdef Box[int64_t] cbox = mod.get_box()
|
|
2705
|
+
cdef int dim = mod.get_dimension()
|
|
2706
|
+
cdef cnp.ndarray[int64_t, ndim=1] bottom_corner = _ff21cview_i64(&cbox.get_lower_corner())
|
|
2707
|
+
cdef cnp.ndarray[int64_t, ndim=1] top_corner = _ff21cview_i64(&cbox.get_upper_corner())
|
|
2708
|
+
box = np.asarray([bottom_corner, top_corner])
|
|
2709
|
+
summands = tuple(dump_summand_i64(summand) for summand in mod)
|
|
2710
|
+
return box, summands
|
|
2711
|
+
|
|
2712
|
+
cdef Module[int64_t] cmod_from_dump_i64(module_dump):
|
|
2713
|
+
box = module_dump[0]
|
|
2714
|
+
summands = module_dump[1]
|
|
2715
|
+
cdef Module[int64_t] out_module = Module[int64_t]()
|
|
2716
|
+
out_module.set_box(Box[int64_t](box))
|
|
2717
|
+
for i in range(len(summands)):
|
|
2718
|
+
out_module.add_summand(summand_from_dump_i64(summands[i]))
|
|
2719
|
+
return out_module
|
|
2720
|
+
|
|
2721
|
+
|
|
2722
|
+
def from_dump_i64(dump)->PyModule_i64:
|
|
2723
|
+
r"""Retrieves a PyModule from a previous dump.
|
|
2724
|
+
|
|
2725
|
+
Parameters
|
|
2726
|
+
----------
|
|
2727
|
+
|
|
2728
|
+
dump: either the output of the dump function, or a file containing the output of a dump.
|
|
2729
|
+
The dumped module to retrieve
|
|
2730
|
+
|
|
2731
|
+
Returns
|
|
2732
|
+
-------
|
|
2733
|
+
|
|
2734
|
+
PyModule
|
|
2735
|
+
The retrieved module.
|
|
2736
|
+
"""
|
|
2737
|
+
# TODO : optimize...
|
|
2738
|
+
mod = PyModule_i64()
|
|
2739
|
+
if type(dump) is str:
|
|
2740
|
+
dump = pickle.load(open(dump, "rb"))
|
|
2741
|
+
cdef Module[int64_t] cmod = cmod_from_dump_i64(dump)
|
|
2742
|
+
mod.cmod = cmod
|
|
2743
|
+
return mod
|
|
2744
|
+
|
|
2745
|
+
|
|
2746
|
+
|
|
2747
|
+
global PyModule_type, PySummand_type
|
|
2748
|
+
PyModule_type = Union[
|
|
2749
|
+
PyModule_f64,
|
|
2750
|
+
PyModule_f32,
|
|
2751
|
+
PyModule_i32,
|
|
2752
|
+
PyModule_i64,
|
|
2753
|
+
]
|
|
2754
|
+
PySummand_type = Union[
|
|
2755
|
+
PySummand_f64,
|
|
2756
|
+
PySummand_f32,
|
|
2757
|
+
PySummand_i32,
|
|
2758
|
+
PySummand_i64,
|
|
2759
|
+
]
|
|
2760
|
+
|
|
2761
|
+
PyBox_type = Union[
|
|
2762
|
+
PyBox_f64,
|
|
2763
|
+
PyBox_f32,
|
|
2764
|
+
PyBox_i32,
|
|
2765
|
+
PyBox_i64,
|
|
2766
|
+
]
|
|
2767
|
+
|
|
2768
|
+
|
|
2769
|
+
PyMultiDiagram_type = Union[
|
|
2770
|
+
PyMultiDiagram_f64,
|
|
2771
|
+
PyMultiDiagram_f32,
|
|
2772
|
+
]
|
|
2773
|
+
|
|
2774
|
+
|
|
2775
|
+
PyMultiDiagrams_type = Union[
|
|
2776
|
+
PyMultiDiagrams_f64,
|
|
2777
|
+
PyMultiDiagrams_f32,
|
|
2778
|
+
]
|
|
2779
|
+
|
|
2780
|
+
def is_mma(stuff):
|
|
2781
|
+
return (False
|
|
2782
|
+
or isinstance(stuff,PyModule_f64)
|
|
2783
|
+
or isinstance(stuff,PyModule_f32)
|
|
2784
|
+
or isinstance(stuff,PyModule_i32)
|
|
2785
|
+
or isinstance(stuff,PyModule_i64)
|
|
2786
|
+
)
|