multipers 2.3.3b6__cp313-cp313-macosx_10_13_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/.dylibs/libc++.1.0.dylib +0 -0
- multipers/.dylibs/libtbb.12.16.dylib +0 -0
- 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-313-darwin.so +0 -0
- multipers/function_rips.pyx +105 -0
- multipers/grids.cpython-313-darwin.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-313-darwin.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-313-darwin.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-313-darwin.so +0 -0
- multipers/multiparameter_module_approximation.pyx +235 -0
- multipers/pickle.py +90 -0
- multipers/plots.py +456 -0
- multipers/point_measure.cpython-313-darwin.so +0 -0
- multipers/point_measure.pyx +395 -0
- multipers/simplex_tree_multi.cpython-313-darwin.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-313-darwin.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 +183 -0
- multipers-2.3.3b6.dist-info/WHEEL +6 -0
- multipers-2.3.3b6.dist-info/licenses/LICENSE +21 -0
- multipers-2.3.3b6.dist-info/top_level.txt +1 -0
multipers/slicer.pyx.tp
ADDED
|
@@ -0,0 +1,1088 @@
|
|
|
1
|
+
{{py:
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Vine and non-vine slicers.
|
|
5
|
+
both type have the same interface, defined the slicer.pyx file
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
import pickle
|
|
10
|
+
|
|
11
|
+
with open("build/tmp/_slicer_names.pkl", "rb") as f:
|
|
12
|
+
slicers=pickle.load(f)
|
|
13
|
+
|
|
14
|
+
dtypes = set([(D['PY_VALUE_TYPE'], D['C_VALUE_TYPE'], D['SHORT_VALUE_TYPE']) for D in slicers])
|
|
15
|
+
|
|
16
|
+
}}
|
|
17
|
+
|
|
18
|
+
from multipers.simplex_tree_multi import SimplexTreeMulti, SimplexTreeMulti_type
|
|
19
|
+
import multipers
|
|
20
|
+
from typing import Optional,Literal
|
|
21
|
+
import threading
|
|
22
|
+
import os
|
|
23
|
+
from joblib import Parallel, delayed
|
|
24
|
+
from warnings import warn
|
|
25
|
+
|
|
26
|
+
from multipers.slicer cimport *
|
|
27
|
+
from multipers.filtrations cimport *
|
|
28
|
+
from multipers.filtration_conversions cimport *
|
|
29
|
+
## TODO: these two are not needed, remove that by updating rank code.
|
|
30
|
+
from multipers.point_measure import sparsify, rank_decomposition_by_rectangles
|
|
31
|
+
from multipers.grids import compute_grid, sanitize_grid, evaluate_in_grid, _push_pts_to_line
|
|
32
|
+
from multipers.array_api import api_from_tensor
|
|
33
|
+
|
|
34
|
+
import numpy as np
|
|
35
|
+
cimport cython
|
|
36
|
+
from libcpp.string cimport string
|
|
37
|
+
# python_value_type = np.float32
|
|
38
|
+
from typing import Union
|
|
39
|
+
from cython.operator cimport dereference
|
|
40
|
+
|
|
41
|
+
## WARNING : This is repeated in the pxd file ...
|
|
42
|
+
python_indices_type=np.int32
|
|
43
|
+
python_tensor_dtype = np.int32
|
|
44
|
+
|
|
45
|
+
global available_slicers
|
|
46
|
+
available_slicers = tuple((
|
|
47
|
+
{{for D in slicers}}
|
|
48
|
+
{{D['PYTHON_TYPE']}},
|
|
49
|
+
{{endfor}}
|
|
50
|
+
))
|
|
51
|
+
|
|
52
|
+
global available_columns
|
|
53
|
+
available_columns = set((
|
|
54
|
+
{{for D in slicers}}
|
|
55
|
+
"{{D['COLUMN_TYPE']}}",
|
|
56
|
+
{{endfor}}
|
|
57
|
+
))
|
|
58
|
+
|
|
59
|
+
global available_dtype
|
|
60
|
+
available_dtype = set([
|
|
61
|
+
{{for D in slicers}}
|
|
62
|
+
"{{D['PY_VALUE_TYPE']}}",
|
|
63
|
+
{{endfor}}
|
|
64
|
+
])
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
global available_pers_backend
|
|
68
|
+
available_pers_backend = set([
|
|
69
|
+
{{for D in slicers}}
|
|
70
|
+
"{{D['PERS_BACKEND_TYPE']}}",
|
|
71
|
+
{{endfor}}
|
|
72
|
+
])
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
global column_type
|
|
77
|
+
_column_type = Literal[
|
|
78
|
+
{{for D in slicers}}
|
|
79
|
+
"{{D['COLUMN_TYPE']}}",
|
|
80
|
+
{{endfor}}
|
|
81
|
+
]
|
|
82
|
+
|
|
83
|
+
global _slicers_type
|
|
84
|
+
Slicer_type = Union[
|
|
85
|
+
{{for D in slicers}}
|
|
86
|
+
{{D['PYTHON_TYPE']}},
|
|
87
|
+
{{endfor}}
|
|
88
|
+
]
|
|
89
|
+
|
|
90
|
+
global _valid_dtypes
|
|
91
|
+
_valid_dtype = Union[
|
|
92
|
+
{{for D in slicers}}
|
|
93
|
+
{{D['PY_VALUE_TYPE']}},
|
|
94
|
+
{{endfor}}
|
|
95
|
+
]
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
global _valid_pers_backend
|
|
99
|
+
_valid_pers_backend = Literal[
|
|
100
|
+
{{for D in slicers}}
|
|
101
|
+
"{{D['PERS_BACKEND_TYPE']}}",
|
|
102
|
+
{{endfor}}
|
|
103
|
+
]
|
|
104
|
+
|
|
105
|
+
{{for D in slicers}}
|
|
106
|
+
|
|
107
|
+
#------------------------------------------------------------------------------
|
|
108
|
+
cdef class {{D['PYTHON_TYPE']}}:
|
|
109
|
+
cdef {{D['C_TEMPLATE_TYPE']}} truc
|
|
110
|
+
cdef public object filtration_grid
|
|
111
|
+
cdef public int minpres_degree ## TODO : maybe change directly the degree in the minpres ?
|
|
112
|
+
|
|
113
|
+
def __repr__(self):
|
|
114
|
+
return f"slicer[backend={self.pers_backend},dtype={np.dtype(self.dtype).name},num_param={self.num_parameters},vineyard={self.is_vine},kcritical={self.is_kcritical},is_squeezed={self.is_squeezed},is_minpres={self.is_minpres},max_dim={self.dimension}]"
|
|
115
|
+
|
|
116
|
+
@property
|
|
117
|
+
def is_squeezed(self)->bool:
|
|
118
|
+
return self.filtration_grid is not None and len(self.filtration_grid) > 0 and len(self.filtration_grid[0]) > 0
|
|
119
|
+
@property
|
|
120
|
+
def is_minpres(self)->bool:
|
|
121
|
+
return self.minpres_degree>=0
|
|
122
|
+
@staticmethod
|
|
123
|
+
def _inf_value():
|
|
124
|
+
return np.asarray(np.inf,dtype={{D['PY_VALUE_TYPE']}}) if issubclass({{D['PY_VALUE_TYPE']}},np.floating) else np.iinfo({{D['PY_VALUE_TYPE']}}).max
|
|
125
|
+
|
|
126
|
+
def get_ptr(self):
|
|
127
|
+
"""
|
|
128
|
+
Returns a pointer to the underlying C++ slicer.
|
|
129
|
+
"""
|
|
130
|
+
return <intptr_t>(&self.truc)
|
|
131
|
+
def _from_ptr(self, intptr_t slicer_ptr):
|
|
132
|
+
self.truc = dereference(<{{D['C_TEMPLATE_TYPE']}}*>(slicer_ptr))
|
|
133
|
+
return self
|
|
134
|
+
|
|
135
|
+
{{if D['IS_SIMPLICIAL']}}
|
|
136
|
+
def __init__(self, st=None):
|
|
137
|
+
"""
|
|
138
|
+
Constructs a slicer from a simplex tree.
|
|
139
|
+
"""
|
|
140
|
+
pass
|
|
141
|
+
def __cinit__(self, st=None):
|
|
142
|
+
if st is None:
|
|
143
|
+
return
|
|
144
|
+
cdef intptr_t ptr = st.thisptr
|
|
145
|
+
cdef Simplex_tree_multi_interface[{{D['FILTRATION_TYPE']}},{{D['C_VALUE_TYPE']}}]* st_ptr = <Simplex_tree_multi_interface[{{D['FILTRATION_TYPE']}},{{D['C_VALUE_TYPE']}}]*>(ptr)
|
|
146
|
+
self.truc = {{D['C_TEMPLATE_TYPE']}}(st_ptr)
|
|
147
|
+
self.minpres_degree = -1
|
|
148
|
+
{{elif D['IS_KCRITICAL']}}
|
|
149
|
+
def __init__(self, generator_maps=[], generator_dimensions=[], filtration_values=[]):
|
|
150
|
+
"""
|
|
151
|
+
Constructs a slicer from
|
|
152
|
+
- scc-like blocks
|
|
153
|
+
or
|
|
154
|
+
- generator maps (Iterable of list of ints)
|
|
155
|
+
- generator dimensions (Iterable of int)
|
|
156
|
+
- filtration values (Iterable of filtration values)
|
|
157
|
+
"""
|
|
158
|
+
pass
|
|
159
|
+
def __cinit__(self, generator_maps=[], generator_dimensions=[], filtration_values=[]):
|
|
160
|
+
"""
|
|
161
|
+
Cython constructor
|
|
162
|
+
"""
|
|
163
|
+
if len(generator_maps)>0 and len(generator_dimensions) == 0 and len(filtration_values) == 0:
|
|
164
|
+
from multipers._slicer_meta import _blocks2boundary_dimension_grades
|
|
165
|
+
generator_maps, generator_dimensions, filtration_values = _blocks2boundary_dimension_grades(
|
|
166
|
+
generator_maps,
|
|
167
|
+
inplace=False,
|
|
168
|
+
)
|
|
169
|
+
cdef uint32_t num_generators = len(generator_maps)
|
|
170
|
+
cdef vector[vector[uint32_t]] c_generator_maps
|
|
171
|
+
cdef vector[Multi_critical_filtration[{{D['C_VALUE_TYPE']}}]] c_filtration_values
|
|
172
|
+
for stuff in generator_maps:
|
|
173
|
+
c_generator_maps.push_back(<vector[uint32_t]>(stuff))
|
|
174
|
+
cdef Multi_critical_filtration[{{D['C_VALUE_TYPE']}}] cf
|
|
175
|
+
cdef One_critical_filtration[{{D['C_VALUE_TYPE']}}] inf
|
|
176
|
+
inf[0] = -inf[0]
|
|
177
|
+
cdef {{D['C_VALUE_TYPE']}}[:,:] F_view
|
|
178
|
+
for F in filtration_values:
|
|
179
|
+
cf.push_to_least_common_upper_bound(inf)
|
|
180
|
+
F_view = np.asarray(F, dtype = {{D['PY_VALUE_TYPE']}} )
|
|
181
|
+
for i in range(F_view.shape[0]):
|
|
182
|
+
cf.add_generator(_py21c_{{D['SHORT_VALUE_TYPE']}}(F_view[i]))
|
|
183
|
+
c_filtration_values.push_back(cf)
|
|
184
|
+
cdef vector[int] c_generator_dimensions = generator_dimensions
|
|
185
|
+
assert num_generators == c_generator_maps.size() == c_filtration_values.size(), "Invalid input, shape do not coincide."
|
|
186
|
+
self.truc = {{D['C_TEMPLATE_TYPE']}}(c_generator_maps,c_generator_dimensions, c_filtration_values)
|
|
187
|
+
self.minpres_degree = -1
|
|
188
|
+
{{else}}
|
|
189
|
+
def __init__(self, generator_maps=[], generator_dimensions=[], filtration_values=[]):
|
|
190
|
+
"""
|
|
191
|
+
Constructs a slicer from
|
|
192
|
+
- generator maps (Iterable of list of ints)
|
|
193
|
+
- generator dimensions (Iterable of int)
|
|
194
|
+
- filtration values (Iterable of filtration values)
|
|
195
|
+
"""
|
|
196
|
+
pass
|
|
197
|
+
@cython.boundscheck(False)
|
|
198
|
+
@cython.wraparound(False)
|
|
199
|
+
def __cinit__(self, generator_maps=[], generator_dimensions=[], filtration_values=[]):
|
|
200
|
+
"""
|
|
201
|
+
Cython constructor
|
|
202
|
+
"""
|
|
203
|
+
cdef uint32_t num_generators = len(generator_maps)
|
|
204
|
+
filtration_values = np.asarray(filtration_values, dtype = {{D['PY_VALUE_TYPE']}} )
|
|
205
|
+
assert len(filtration_values) == num_generators, f"Invalid input, shape do not coicide. Got sizes {num_generators,len(generator_dimensions),len(filtration_values)}."
|
|
206
|
+
cdef vector[vector[uint32_t]] c_generator_maps
|
|
207
|
+
cdef vector[One_critical_filtration[{{D['C_VALUE_TYPE']}}]] c_filtration_values
|
|
208
|
+
|
|
209
|
+
c_generator_maps.resize(num_generators)
|
|
210
|
+
c_filtration_values.resize(num_generators)
|
|
211
|
+
for i in range(num_generators):
|
|
212
|
+
c_generator_maps[i] = <vector[uint32_t]>(generator_maps[i])
|
|
213
|
+
c_filtration_values[i] = _py21c_{{D['SHORT_VALUE_TYPE']}}(filtration_values[i])
|
|
214
|
+
cdef vector[int] c_generator_dimensions = generator_dimensions
|
|
215
|
+
|
|
216
|
+
assert num_generators == c_generator_maps.size() == c_filtration_values.size() == c_generator_dimensions.size(), "Invalid input, shape do not coincide."
|
|
217
|
+
self.truc = {{D['C_TEMPLATE_TYPE']}}(c_generator_maps,c_generator_dimensions, c_filtration_values)
|
|
218
|
+
self.minpres_degree = -1
|
|
219
|
+
{{endif}}
|
|
220
|
+
|
|
221
|
+
def to_colexical(self, bool return_permutation = False)->{{D['PYTHON_TYPE']}}|tuple[{{D['PYTHON_TYPE']}},np.ndarray]:
|
|
222
|
+
# assert not self.is_squeezed, "Unsqueeze first, this is not implented yet for squeezed slicers"
|
|
223
|
+
new_slicer = {{D['PYTHON_TYPE']}}()
|
|
224
|
+
cdef pair[{{D['C_TEMPLATE_TYPE']}}, vector[unsigned int]] stuff = self.truc.colexical_rearange()
|
|
225
|
+
|
|
226
|
+
new_slicer.truc = stuff.first
|
|
227
|
+
new_slicer.minpres_degree = self.minpres_degree
|
|
228
|
+
new_slicer.filtration_grid = self.filtration_grid
|
|
229
|
+
|
|
230
|
+
if return_permutation:
|
|
231
|
+
return new_slicer, np.array(stuff.second, dtype=np.int32)
|
|
232
|
+
return new_slicer
|
|
233
|
+
def permute_generators(self, permutation)->{{D['PYTHON_TYPE']}}:
|
|
234
|
+
cdef vector[unsigned int] c_perm = permutation
|
|
235
|
+
new_slicer = {{D['PYTHON_TYPE']}}()
|
|
236
|
+
new_slicer.truc = self.truc.permute(c_perm)
|
|
237
|
+
new_slicer.minpres_degree = self.minpres_degree
|
|
238
|
+
return new_slicer
|
|
239
|
+
|
|
240
|
+
def copy(self)->{{D['PYTHON_TYPE']}}:
|
|
241
|
+
"""
|
|
242
|
+
Returns a copy of the slicer.
|
|
243
|
+
"""
|
|
244
|
+
copy_ = {{D['PYTHON_TYPE']}}()
|
|
245
|
+
copy_.truc = self.truc
|
|
246
|
+
copy_.minpres_degree = self.minpres_degree
|
|
247
|
+
copy_.filtration_grid = self.filtration_grid
|
|
248
|
+
return copy_
|
|
249
|
+
def compute_kernel_projective_cover(self, dim:Optional[int]=None)->{{D['PYTHON_TYPE']}}:
|
|
250
|
+
if len(self) == 0:
|
|
251
|
+
return {{D['PYTHON_TYPE']}}()
|
|
252
|
+
if dim is None:
|
|
253
|
+
dim = self.truc.get_dimension(0)
|
|
254
|
+
out = {{D['PYTHON_TYPE']}}()
|
|
255
|
+
out.truc = self.truc.projective_cover_kernel(dim)
|
|
256
|
+
out.filtration_grid = self.filtration_grid
|
|
257
|
+
return out
|
|
258
|
+
|
|
259
|
+
def get_barcode_idx(self, bool keep_inf = False):
|
|
260
|
+
"""
|
|
261
|
+
Returns the current barcode.
|
|
262
|
+
"""
|
|
263
|
+
return tuple(np.asarray(x) if len(x) else np.empty((0,2), dtype=int)for x in self.truc.get_barcode_idx())
|
|
264
|
+
def get_barcode(self, bool keep_inf = False):
|
|
265
|
+
"""
|
|
266
|
+
Returns the current barcode.
|
|
267
|
+
"""
|
|
268
|
+
if keep_inf:
|
|
269
|
+
bcs = tuple(np.asarray(stuff, dtype = {{D['PY_VALUE_TYPE']}}) for stuff in self.truc.get_barcode())
|
|
270
|
+
else:
|
|
271
|
+
bcs = {{D['PYTHON_TYPE']}}._threshold_bcs(self.truc.get_barcode())
|
|
272
|
+
return bcs
|
|
273
|
+
def push_to_line(self, basepoint, direction=None)->{{D['PYTHON_TYPE']}}:
|
|
274
|
+
"""
|
|
275
|
+
Pushes the current slicer to the line defined by a basepoint and an optional direction.
|
|
276
|
+
If the direction is not provided, it is assumed to be diagonal.
|
|
277
|
+
"""
|
|
278
|
+
{{if D['IS_FLOAT']}}
|
|
279
|
+
basepoint = np.asarray(basepoint, dtype = {{D['PY_VALUE_TYPE']}})
|
|
280
|
+
cdef Line[{{D['C_VALUE_TYPE']}}] line
|
|
281
|
+
if direction is None:
|
|
282
|
+
line = Line[{{D['C_VALUE_TYPE']}}](_py21c_{{D['SHORT_VALUE_TYPE']}}(basepoint))
|
|
283
|
+
else:
|
|
284
|
+
direction = np.asarray(direction, dtype = {{D['PY_VALUE_TYPE']}})
|
|
285
|
+
line = Line[{{D['C_VALUE_TYPE']}}](_py21c_{{D['SHORT_VALUE_TYPE']}}(basepoint),_py21c_{{D['SHORT_VALUE_TYPE']}}(direction))
|
|
286
|
+
self.truc.push_to(line)
|
|
287
|
+
return self
|
|
288
|
+
{{else}}
|
|
289
|
+
raise NotImplementedError("There is no `int` slicing.")
|
|
290
|
+
{{endif}}
|
|
291
|
+
|
|
292
|
+
@staticmethod
|
|
293
|
+
cdef _threshold_bcs(vector[vector[pair[{{D['C_VALUE_TYPE']}}, {{D['C_VALUE_TYPE']}}]]] bcs):
|
|
294
|
+
return tuple(np.fromiter((a for a in stuff if a.first < {{D['PYTHON_TYPE']}}._inf_value()), dtype=np.dtype(({{D['PY_VALUE_TYPE']}},2))) for stuff in bcs)
|
|
295
|
+
@staticmethod
|
|
296
|
+
def _bc_to_full(bcs, basepoint, direction=None):
|
|
297
|
+
# i, (b sv d), coords
|
|
298
|
+
basepoint = basepoint[None,None,:]
|
|
299
|
+
direction = 1 if direction is None else direction[None,None,:]
|
|
300
|
+
return tuple(bc[:,:,None]*direction + basepoint for bc in bcs)
|
|
301
|
+
|
|
302
|
+
def persistence_on_line(self,basepoint,direction=None, bool keep_inf=True, bool full=False, bool ignore_infinite_filtration_values = True):
|
|
303
|
+
"""
|
|
304
|
+
Computes the persistence on a line L defined by
|
|
305
|
+
- a basepoint (num_parameters,) array
|
|
306
|
+
- an optional direction (num_parameters,) array
|
|
307
|
+
|
|
308
|
+
Warning: This is not parallelizable. Use `persitence_on_lines`.
|
|
309
|
+
"""
|
|
310
|
+
{{if D['IS_FLOAT']}}
|
|
311
|
+
self.push_to_line(basepoint,direction)
|
|
312
|
+
self.truc.compute_persistence(ignore_infinite_filtration_values)
|
|
313
|
+
if keep_inf:
|
|
314
|
+
bcs = tuple(np.asarray(stuff, dtype = {{D['PY_VALUE_TYPE']}}) for stuff in self.truc.get_barcode())
|
|
315
|
+
else:
|
|
316
|
+
bcs = {{D['PYTHON_TYPE']}}._threshold_bcs(self.truc.get_barcode())
|
|
317
|
+
|
|
318
|
+
if full:
|
|
319
|
+
bcs = {{D['PYTHON_TYPE']}}._bc_to_full(bcs, basepoint, direction)
|
|
320
|
+
return bcs
|
|
321
|
+
{{else}}
|
|
322
|
+
if not self.is_squeezed:
|
|
323
|
+
raise ValueError("Unsqueeze tensor, or provide a filtration grid. Cannot slice lines with integers...")
|
|
324
|
+
api = api_from_tensor(self.filtration_grid[0])
|
|
325
|
+
s = self.unsqueeze()
|
|
326
|
+
fil = evaluate_in_grid(np.asarray(self.get_filtrations()), self.filtration_grid)
|
|
327
|
+
projected_fil =_push_pts_to_line(fil, basepoint, direction)
|
|
328
|
+
s.compute_persistence(projected_fil)
|
|
329
|
+
bcs_idx = s.get_barcode_idx()
|
|
330
|
+
bcs = tuple(
|
|
331
|
+
api.stack([projected_fil[bc[:,0]], projected_fil[bc[:,1]]], axis=1)
|
|
332
|
+
if bc.size>0
|
|
333
|
+
else api.empty((0,2), dtype = self.filtration_grid[0].dtype)
|
|
334
|
+
for bc in bcs_idx
|
|
335
|
+
)
|
|
336
|
+
if full:
|
|
337
|
+
bcs = self._bc_to_full(bcs, basepoint, direction)
|
|
338
|
+
return bcs
|
|
339
|
+
{{endif}}
|
|
340
|
+
|
|
341
|
+
def _custom_persistences_idx(self, filtration_array, bool ignore_inf=True):
|
|
342
|
+
filtration_array = np.asarray(filtration_array, dtype= {{D['PY_VALUE_TYPE']}})
|
|
343
|
+
cdef {{D['C_VALUE_TYPE']}}[:,:] arr_view = filtration_array
|
|
344
|
+
cdef int size = arr_view.shape[0]
|
|
345
|
+
if <int>arr_view.shape[1] != <int>self.truc.num_generators():
|
|
346
|
+
raise ValueError(f"Got filtration array of shape {filtration_array.shape=} / {arr_view.shape=}. Was expecting (-1, {len(self)=})")
|
|
347
|
+
|
|
348
|
+
return tuple(tuple(np.array(bc_idx_degree, dtype=int) for bc_idx_degree in bc_idx) for bc_idx in self.truc.custom_persistences(&arr_view[0,0], size, ignore_inf))
|
|
349
|
+
|
|
350
|
+
def persistence_on_lines(self, basepoints=None, directions=None, bool keep_inf=True, bool full=False, bool ignore_infinite_filtration_values = True):
|
|
351
|
+
"""
|
|
352
|
+
Same as `persistence_on_line`, but with vineyards operation between
|
|
353
|
+
lines if `self.is_vine`, and in parallel otherwise.
|
|
354
|
+
"""
|
|
355
|
+
cdef vector[vector[{{D['C_VALUE_TYPE']}}]] c_basepoints
|
|
356
|
+
cdef vector[pair[vector[{{D['C_VALUE_TYPE']}}], vector[{{D['C_VALUE_TYPE']}}]]] c_truc
|
|
357
|
+
cdef vector[vector[vector[pair[{{D['C_VALUE_TYPE']}}, {{D['C_VALUE_TYPE']}}]]]] c_out
|
|
358
|
+
if directions is None:
|
|
359
|
+
c_basepoints = basepoints
|
|
360
|
+
with nogil:
|
|
361
|
+
c_out = self.truc.persistence_on_lines(c_basepoints, ignore_infinite_filtration_values)
|
|
362
|
+
else:
|
|
363
|
+
c_truc = zip(basepoints,directions)
|
|
364
|
+
with nogil:
|
|
365
|
+
c_out = self.truc.persistence_on_lines(c_truc, ignore_infinite_filtration_values)
|
|
366
|
+
cdef int num_bc = c_basepoints.size()
|
|
367
|
+
|
|
368
|
+
if keep_inf:
|
|
369
|
+
out = tuple(tuple(
|
|
370
|
+
np.asarray(y, dtype = {{D['PY_VALUE_TYPE']}}) if len(y)>0 else np.empty((0,2), dtype = {{D['PY_VALUE_TYPE']}})
|
|
371
|
+
for y in x) for x in c_out)
|
|
372
|
+
else:
|
|
373
|
+
out = tuple({{D['PYTHON_TYPE']}}._threshold_bcs(x) for x in c_out)
|
|
374
|
+
|
|
375
|
+
if full:
|
|
376
|
+
_dirs = [None]*len(basepoints) if directions is None else directions
|
|
377
|
+
out = tuple({{D['PYTHON_TYPE']}}._bc_to_full(bcs, bp, dir) for bcs, bp, dir in zip(out,basepoints,_dirs))
|
|
378
|
+
return out
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
def __getstate__(self):
|
|
382
|
+
return (
|
|
383
|
+
self.get_boundaries(),
|
|
384
|
+
self.get_dimensions(),
|
|
385
|
+
self.get_filtrations(),
|
|
386
|
+
self.filtration_grid,
|
|
387
|
+
self.minpres_degree,
|
|
388
|
+
)
|
|
389
|
+
def __setstate__(self, tuple dump):
|
|
390
|
+
B, D, F, filtration_grid, minpres_degree = dump
|
|
391
|
+
copy = {{D['PYTHON_TYPE']}}(B,D,F)
|
|
392
|
+
self.truc = copy.truc
|
|
393
|
+
self.minpres_degree= minpres_degree
|
|
394
|
+
self.filtration_grid=filtration_grid
|
|
395
|
+
|
|
396
|
+
def __eq__(self, other):
|
|
397
|
+
## True if they rpz the same complex (no reordering allowed yet),
|
|
398
|
+
# i.e., ignores minpres, squeeze
|
|
399
|
+
if other.is_squeezed:
|
|
400
|
+
return self == other.unsqueeze()
|
|
401
|
+
if self.is_squeezed:
|
|
402
|
+
return self.unsqueeze() == other
|
|
403
|
+
return (
|
|
404
|
+
np.array_equal(self.get_dimensions(), other.get_dimensions())
|
|
405
|
+
and
|
|
406
|
+
self.get_boundaries() == other.get_boundaries()
|
|
407
|
+
and
|
|
408
|
+
## Kcritical are sorted + filtrationvalues is a dump.
|
|
409
|
+
# for non kcritical there is an unnecessary copy though
|
|
410
|
+
np.array_equal(self.get_filtrations_values(), other.get_filtrations_values())
|
|
411
|
+
)
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
def compute_persistence(self,one_filtration=None, bool ignore_infinite_filtration_values = True)->tuple:
|
|
416
|
+
"""
|
|
417
|
+
Computes the current persistence, or the persistence
|
|
418
|
+
given by the filtration one_filtration (num_generators,).
|
|
419
|
+
"""
|
|
420
|
+
if one_filtration is not None:
|
|
421
|
+
api = api_from_tensor(one_filtration)
|
|
422
|
+
one_filtration=api.astensor(one_filtration)
|
|
423
|
+
# self.truc.set_one_filtration(one_filtration)
|
|
424
|
+
# s = self.unsqueeze()
|
|
425
|
+
# fil = evaluate_in_grid(np.asarray(self.get_filtrations()), self.filtration_grid)
|
|
426
|
+
# projected_fil =_push_pts_to_line(fil, basepoint, direction)
|
|
427
|
+
if one_filtration.ndim > 2 or one_filtration.ndim == 0:
|
|
428
|
+
raise ValueError(f"Expected a filtration shape of the form ((num_1_param), num_generators). Got {one_filtration.shape=}")
|
|
429
|
+
squeeze = False
|
|
430
|
+
if one_filtration.ndim == 1:
|
|
431
|
+
one_filtration = one_filtration[None]
|
|
432
|
+
squeeze = True
|
|
433
|
+
|
|
434
|
+
bcs = self._custom_persistences_idx(api.asnumpy(one_filtration),ignore_infinite_filtration_values)
|
|
435
|
+
|
|
436
|
+
bcs = tuple(tuple(
|
|
437
|
+
api.stack([F[bc[:,0]], F[bc[:,1]]], axis=1)
|
|
438
|
+
if bc.size>0
|
|
439
|
+
else api.empty((0,2), dtype = F.dtype)
|
|
440
|
+
for bc in bcs_idx
|
|
441
|
+
)
|
|
442
|
+
for bcs_idx,F in zip(bcs,one_filtration)
|
|
443
|
+
)
|
|
444
|
+
return bcs[0] if squeeze else bcs
|
|
445
|
+
|
|
446
|
+
# TODO: Later
|
|
447
|
+
# if len(degrees)>0:
|
|
448
|
+
# self.truc.compute_persistence(degrees)
|
|
449
|
+
# else:
|
|
450
|
+
# self.truc.compute_persistence()
|
|
451
|
+
self.truc.compute_persistence(ignore_infinite_filtration_values)
|
|
452
|
+
# return self
|
|
453
|
+
return self.get_barcode()
|
|
454
|
+
def get_barcode(self):
|
|
455
|
+
"""
|
|
456
|
+
Returns the barcode of the current 1d-persistence.
|
|
457
|
+
"""
|
|
458
|
+
return tuple(np.asarray(bc) for bc in self.truc.get_barcode())
|
|
459
|
+
def sliced_filtration(self,basepoint, direction=None):
|
|
460
|
+
"""
|
|
461
|
+
Computes the filtration on a line L defined by
|
|
462
|
+
- a basepoint (num_parameters,) array
|
|
463
|
+
- an optional direction (num_parameters,) array
|
|
464
|
+
"""
|
|
465
|
+
self.push_to_line(basepoint,direction)
|
|
466
|
+
return np.asarray(self.truc.get_one_filtration())
|
|
467
|
+
def __len__(self):
|
|
468
|
+
return self.truc.num_generators()
|
|
469
|
+
@property
|
|
470
|
+
def num_generators(self):
|
|
471
|
+
return self.truc.num_generators()
|
|
472
|
+
@property
|
|
473
|
+
def num_parameters(self):
|
|
474
|
+
return self.truc.num_parameters()
|
|
475
|
+
@property
|
|
476
|
+
def info(self):
|
|
477
|
+
print(self.truc.to_str().decode())
|
|
478
|
+
def filtration_bounds(self) -> np.ndarray:
|
|
479
|
+
"""
|
|
480
|
+
Computes the bounding box of the current multifiltration.
|
|
481
|
+
"""
|
|
482
|
+
cdef pair[One_critical_filtration[{{D['C_VALUE_TYPE']}}],One_critical_filtration[{{D['C_VALUE_TYPE']}}]] box = self.truc.get_bounding_box()
|
|
483
|
+
cdef cnp.ndarray[{{D['C_VALUE_TYPE']}}, ndim=1] a = _ff21cview_{{D['SHORT_VALUE_TYPE']}}(&box.first)
|
|
484
|
+
cdef cnp.ndarray[{{D['C_VALUE_TYPE']}}, ndim=1] b = _ff21cview_{{D['SHORT_VALUE_TYPE']}}(&box.second)
|
|
485
|
+
return np.asarray([a,b])
|
|
486
|
+
def get_filtrations_values(self)->np.ndarray:
|
|
487
|
+
"""
|
|
488
|
+
Returns the current filtration values of the slicer.
|
|
489
|
+
"""
|
|
490
|
+
cdef vector[One_critical_filtration[{{D['C_VALUE_TYPE']}}]] v = self.truc.get_filtration_values()
|
|
491
|
+
out = _vff21cview_{{D['SHORT_VALUE_TYPE']}}(v, copy=True, duplicate=self.num_parameters)
|
|
492
|
+
return np.asarray(out)
|
|
493
|
+
def get_filtration_grid(self,grid_strategy:str="exact", **infer_grid_kwargs):
|
|
494
|
+
return compute_grid(
|
|
495
|
+
self.get_filtrations_values().T,
|
|
496
|
+
strategy=grid_strategy,
|
|
497
|
+
**infer_grid_kwargs,
|
|
498
|
+
)
|
|
499
|
+
def get_filtrations(self):
|
|
500
|
+
"""
|
|
501
|
+
Returns a view of the filtration values, as a list of numpy arrays.
|
|
502
|
+
"""
|
|
503
|
+
{{if D['IS_KCRITICAL']}}
|
|
504
|
+
return _vff2kcview_{{D['SHORT_VALUE_TYPE']}}(self.truc.get_filtrations(), copy=False, duplicate=self.num_parameters)
|
|
505
|
+
{{else}}
|
|
506
|
+
return _vff21cview_{{D['SHORT_VALUE_TYPE']}}(self.truc.get_filtrations(), copy=False, duplicate=self.num_parameters)
|
|
507
|
+
{{endif}}
|
|
508
|
+
|
|
509
|
+
def get_dimensions(self)-> np.ndarray:
|
|
510
|
+
"""
|
|
511
|
+
Returns the ordered dimensions of the generators.
|
|
512
|
+
"""
|
|
513
|
+
return np.asarray(self.truc.get_dimensions())
|
|
514
|
+
@property
|
|
515
|
+
def dimension(self)-> int:
|
|
516
|
+
"""
|
|
517
|
+
Returns the maximum dimension of the complex.
|
|
518
|
+
"""
|
|
519
|
+
return self.get_dimensions()[-1] if len(self)>0 else -np.inf
|
|
520
|
+
def prune_above_dimension(self,int max_dimension)->{{D['PYTHON_TYPE']}}:
|
|
521
|
+
"""
|
|
522
|
+
Prunes the generators above a given dimension.
|
|
523
|
+
"""
|
|
524
|
+
self.truc.prune_above_dimension(max_dimension)
|
|
525
|
+
return self
|
|
526
|
+
def get_boundaries(self)->tuple[tuple]:
|
|
527
|
+
"""
|
|
528
|
+
Returns the boundaries of the generators.
|
|
529
|
+
"""
|
|
530
|
+
return tuple(tuple(b) for b in self.truc.get_boundaries())
|
|
531
|
+
def grid_squeeze(
|
|
532
|
+
self,
|
|
533
|
+
filtration_grid=None,
|
|
534
|
+
strategy="exact",
|
|
535
|
+
resolution:Optional[int]=None,
|
|
536
|
+
bool coordinates=True,
|
|
537
|
+
bool inplace = False,
|
|
538
|
+
grid_strategy=None
|
|
539
|
+
)->{{D['PYTHON_TYPE'][:-3]+"i32"}}|{{D['PYTHON_TYPE']}}:
|
|
540
|
+
"""
|
|
541
|
+
Coarsen the filtration values on a grid. This is necessary to compute some invariants.
|
|
542
|
+
|
|
543
|
+
If the filtration grid is not given, it is infered from filtration values,
|
|
544
|
+
using the :func:`multipers.grids.compute_grid` function, whose args are
|
|
545
|
+
- grid_strategy:str see `multipers.grids.available_strategies`. Defaults to exact.
|
|
546
|
+
- resolution:int if strategy is not exact.
|
|
547
|
+
|
|
548
|
+
- inplace:bool if true, does the operation inplace, i.e., doesn't return a copy.
|
|
549
|
+
"""
|
|
550
|
+
if grid_strategy is not None:
|
|
551
|
+
warn("`grid_strategy` is deprecated, use `strategy` instead.",DeprecationWarning)
|
|
552
|
+
strategy=grid_strategy
|
|
553
|
+
|
|
554
|
+
if self.is_squeezed:
|
|
555
|
+
warn("(copy warning) Squeezing an already squeezed slicer.")
|
|
556
|
+
temp = self.unsqueeze()
|
|
557
|
+
subgrid = compute_grid(self.filtration_grid, strategy=strategy, resolution=resolution)
|
|
558
|
+
return temp.grid_squeeze(subgrid, coordinates=coordinates, inplace=inplace)
|
|
559
|
+
|
|
560
|
+
if filtration_grid is None:
|
|
561
|
+
filtration_grid = compute_grid(
|
|
562
|
+
self.get_filtrations_values().T,
|
|
563
|
+
strategy=strategy,
|
|
564
|
+
resolution=resolution)
|
|
565
|
+
cdef vector[vector[{{D['C_VALUE_TYPE']}}]] grid = filtration_grid
|
|
566
|
+
if inplace or not coordinates:
|
|
567
|
+
self.truc.coarsen_on_grid_inplace(grid, coordinates)
|
|
568
|
+
if coordinates:
|
|
569
|
+
self.filtration_grid = filtration_grid
|
|
570
|
+
else:
|
|
571
|
+
{{if D['COLUMN_TYPE'] is None}}
|
|
572
|
+
raise ValueError("WIP")
|
|
573
|
+
{{else}}
|
|
574
|
+
out = {{D['PYTHON_TYPE'][:-3]+"i32"}}()
|
|
575
|
+
out.truc = self.truc.coarsen_on_grid(grid)
|
|
576
|
+
if coordinates:
|
|
577
|
+
out.filtration_grid = sanitize_grid(filtration_grid)
|
|
578
|
+
out.minpres_degree = self.minpres_degree
|
|
579
|
+
return out
|
|
580
|
+
{{endif}}
|
|
581
|
+
return self
|
|
582
|
+
def _clean_filtration_grid(self):
|
|
583
|
+
"""
|
|
584
|
+
Removes the values in filtration_grid that are not linked to any splx.
|
|
585
|
+
"""
|
|
586
|
+
if not self.is_squeezed:
|
|
587
|
+
raise ValueError("No grid to clean.")
|
|
588
|
+
F = self.filtration_grid
|
|
589
|
+
self.filtration_grid=None
|
|
590
|
+
cleaned_coordinates = compute_grid(self)
|
|
591
|
+
new_slicer = self.grid_squeeze(cleaned_coordinates)
|
|
592
|
+
|
|
593
|
+
self._from_ptr(new_slicer.get_ptr())
|
|
594
|
+
self.filtration_grid = tuple(f[g] for f,g in zip(F,cleaned_coordinates))
|
|
595
|
+
return self
|
|
596
|
+
|
|
597
|
+
def minpres(self,
|
|
598
|
+
int degree=-1,
|
|
599
|
+
list[int] degrees=[],
|
|
600
|
+
str backend:Literal["mpfree", "2pac"]="mpfree",
|
|
601
|
+
str slicer_backend:Literal["matrix","clement","graph"]="matrix",
|
|
602
|
+
bool vineyard={{D['IS_VINE']}},
|
|
603
|
+
id :Optional[str] = None,
|
|
604
|
+
dtype = {{D['PY_VALUE_TYPE']}},
|
|
605
|
+
**minpres_kwargs
|
|
606
|
+
)->Slicer_type:
|
|
607
|
+
"""
|
|
608
|
+
Computes the minimal presentation of the slicer, and returns it as a new slicer.
|
|
609
|
+
See :func:`multipers.slicer.minimal_presentation`.
|
|
610
|
+
"""
|
|
611
|
+
new_slicer = minimal_presentation(self, degree=degree, degrees=degrees, backend=backend, slicer_backend=slicer_backend, dtype=dtype, vineyard=vineyard, id=id, **minpres_kwargs)
|
|
612
|
+
return new_slicer
|
|
613
|
+
|
|
614
|
+
@property
|
|
615
|
+
def dtype(self)->type:
|
|
616
|
+
return {{D['PY_VALUE_TYPE']}}
|
|
617
|
+
@property
|
|
618
|
+
def col_type(self)->str:
|
|
619
|
+
return "{{D['COLUMN_TYPE']}}"
|
|
620
|
+
@property
|
|
621
|
+
def is_vine(self)->bool:
|
|
622
|
+
return {{D['IS_VINE']}}
|
|
623
|
+
@property
|
|
624
|
+
def is_kcritical(self)->bool:
|
|
625
|
+
return {{D['IS_KCRITICAL']}}
|
|
626
|
+
|
|
627
|
+
@property
|
|
628
|
+
def pers_backend(self)->str:
|
|
629
|
+
return "{{D['PERS_BACKEND_TYPE']}}"
|
|
630
|
+
|
|
631
|
+
{{if D['IS_VINE']}}
|
|
632
|
+
def vine_update(self,basepoint,direction=None)->{{D['PYTHON_TYPE']}}:
|
|
633
|
+
"""
|
|
634
|
+
Updates the barcode, on a line, using the vineyard algorithm.
|
|
635
|
+
"""
|
|
636
|
+
self.push_to_line(basepoint,direction)
|
|
637
|
+
self.truc.vineyard_update()
|
|
638
|
+
return self
|
|
639
|
+
def get_representative_cycles(self, bool update=True, bool detailed=False):
|
|
640
|
+
"""
|
|
641
|
+
Returns the representative cycles of the current barcode.
|
|
642
|
+
Recomputes the generators if update=True
|
|
643
|
+
"""
|
|
644
|
+
return self.truc.get_representative_cycles(update, detailed)
|
|
645
|
+
def get_permutation(self):
|
|
646
|
+
"""
|
|
647
|
+
Returns the current generator permutation (w.r.t. vineyard).
|
|
648
|
+
"""
|
|
649
|
+
return self.truc.get_current_order()
|
|
650
|
+
{{endif}}
|
|
651
|
+
|
|
652
|
+
def to_scc(
|
|
653
|
+
self,
|
|
654
|
+
path:os.PathLike,
|
|
655
|
+
int num_parameters = -1,
|
|
656
|
+
int degree = -1,
|
|
657
|
+
bool rivet_compatible = False,
|
|
658
|
+
bool ignore_last_generators = False,
|
|
659
|
+
bool strip_comments = False,
|
|
660
|
+
bool reverse = False,
|
|
661
|
+
bool unsqueeze = True,
|
|
662
|
+
):
|
|
663
|
+
"""
|
|
664
|
+
Writes current slicer to a file in scc format.
|
|
665
|
+
"""
|
|
666
|
+
if degree == -1 and not rivet_compatible:
|
|
667
|
+
degree = 1
|
|
668
|
+
cdef string c_path = path.encode(encoding="utf-8")
|
|
669
|
+
if self.is_squeezed and unsqueeze:
|
|
670
|
+
kwargs = locals()
|
|
671
|
+
kwargs.pop("self",0)
|
|
672
|
+
kwargs.pop("c_path",0)
|
|
673
|
+
self.unsqueeze().to_scc(**kwargs)
|
|
674
|
+
return
|
|
675
|
+
with nogil:
|
|
676
|
+
self.truc.write_to_scc_file(c_path, num_parameters, degree, rivet_compatible, ignore_last_generators, strip_comments, reverse)
|
|
677
|
+
|
|
678
|
+
{{if not D['IS_KCRITICAL']}}
|
|
679
|
+
def _build_from_scc_file(self, path:os.PathLike, bool rivet_compatible = False, bool reverse = False, int shift_dimension = 0)->{{D['PYTHON_TYPE']}}:
|
|
680
|
+
"""
|
|
681
|
+
Builds the slicer from the given scc file. Should be empty before, otherwise will be completely overwritten.
|
|
682
|
+
"""
|
|
683
|
+
cdef string c_path = path.encode(encoding="utf-8")
|
|
684
|
+
with nogil:
|
|
685
|
+
self.truc.build_from_scc_file(c_path, rivet_compatible, reverse, shift_dimension)
|
|
686
|
+
return self
|
|
687
|
+
|
|
688
|
+
def unsqueeze(self, grid=None)->{{D['PYTHON_TYPE'][:-3]+"f64"}}:
|
|
689
|
+
from multipers.grids import evaluate_in_grid, sanitize_grid
|
|
690
|
+
from multipers import Slicer
|
|
691
|
+
grid = self.filtration_grid if grid is None else grid
|
|
692
|
+
grid = sanitize_grid(grid, numpyfy=True)
|
|
693
|
+
new_filtrations = evaluate_in_grid(np.asarray(self.get_filtrations(), dtype=np.int32), grid)
|
|
694
|
+
new_slicer = {{D['PYTHON_TYPE'][:-3]+"f64"}}(
|
|
695
|
+
self.get_boundaries(),
|
|
696
|
+
self.get_dimensions(),
|
|
697
|
+
new_filtrations,
|
|
698
|
+
)
|
|
699
|
+
new_slicer.minpres_degree=self.minpres_degree
|
|
700
|
+
return new_slicer
|
|
701
|
+
{{endif}}
|
|
702
|
+
|
|
703
|
+
|
|
704
|
+
{{endfor}}
|
|
705
|
+
|
|
706
|
+
from libcpp.vector cimport vector
|
|
707
|
+
from libcpp.set cimport set as cset
|
|
708
|
+
cdef extern from "gudhi/cubical_to_boundary.h" namespace "":
|
|
709
|
+
void _to_boundary(const vector[unsigned int]&, vector[vector[unsigned int]]&, vector[int]&) except + nogil
|
|
710
|
+
void get_vertices(unsigned int, cset[unsigned int]&, const vector[vector[unsigned int]]&) nogil
|
|
711
|
+
|
|
712
|
+
|
|
713
|
+
{{for pytype,ctype,fshort in dtypes}}
|
|
714
|
+
def _from_bitmap{{fshort}}(image, **slicer_kwargs):
|
|
715
|
+
from multipers import Slicer
|
|
716
|
+
dtype = slicer_kwargs.get("dtype", image.dtype)
|
|
717
|
+
slicer_kwargs["dtype"] = dtype
|
|
718
|
+
if image.dtype != dtype:
|
|
719
|
+
raise ValueError(f"Invalid type matching. Got {dtype=} and {image.dtype=}")
|
|
720
|
+
_Slicer = Slicer(return_type_only=True, **slicer_kwargs)
|
|
721
|
+
cdef vector[unsigned int] img_shape = image.shape[:-1]
|
|
722
|
+
cdef unsigned int num_parameters = image.shape[-1]
|
|
723
|
+
cdef vector[vector[unsigned int]] gen_maps
|
|
724
|
+
cdef vector[int] gen_dims
|
|
725
|
+
|
|
726
|
+
with nogil:
|
|
727
|
+
_to_boundary(img_shape,gen_maps, gen_dims)
|
|
728
|
+
|
|
729
|
+
cdef cset[unsigned int] vertices
|
|
730
|
+
|
|
731
|
+
cdef unsigned int num_gens = gen_dims.size()
|
|
732
|
+
filtration_values = np.zeros(shape=(num_gens, num_parameters), dtype = {{pytype}}) - _Slicer._inf_value()
|
|
733
|
+
cdef {{ctype}}[:,:] F = filtration_values
|
|
734
|
+
cdef {{ctype}}[:,:] c_img = image.reshape(-1,num_parameters)
|
|
735
|
+
with nogil:
|
|
736
|
+
for i in range(num_gens):
|
|
737
|
+
# with gil:
|
|
738
|
+
# print(f"idx {i}:", end="")
|
|
739
|
+
vertices.clear()
|
|
740
|
+
get_vertices(i,vertices,gen_maps)
|
|
741
|
+
|
|
742
|
+
# with gil:
|
|
743
|
+
# print(f"v = {vertices}:", end="")
|
|
744
|
+
for k in vertices:
|
|
745
|
+
for j in range(num_parameters):
|
|
746
|
+
F[i,j] = max(F[i,j], c_img[k,j])
|
|
747
|
+
|
|
748
|
+
# with gil:
|
|
749
|
+
# print(f"F = {np.asarray(F[i])}")
|
|
750
|
+
slicer = _Slicer(gen_maps, gen_dims, filtration_values)
|
|
751
|
+
return slicer
|
|
752
|
+
{{endfor}}
|
|
753
|
+
|
|
754
|
+
def from_bitmap(img, **kwargs):
|
|
755
|
+
img = np.asarray(img)
|
|
756
|
+
{{for pytype,ctype,stype in dtypes}}
|
|
757
|
+
if img.dtype == {{pytype}}:
|
|
758
|
+
return _from_bitmap{{stype}}(img, **kwargs)
|
|
759
|
+
{{endfor}}
|
|
760
|
+
raise ValueError(f"Invalid dtype. Got {img.dtype=}, was expecting {available_dtype=}.")
|
|
761
|
+
|
|
762
|
+
def from_function_delaunay(
|
|
763
|
+
points,
|
|
764
|
+
grades,
|
|
765
|
+
int degree=-1,
|
|
766
|
+
backend:Optional[_valid_pers_backend]=None,
|
|
767
|
+
vineyard=None, # Optionmal[bool], wait for cython
|
|
768
|
+
dtype=np.float64,
|
|
769
|
+
bool verbose = False,
|
|
770
|
+
bool clear = True,
|
|
771
|
+
):
|
|
772
|
+
"""
|
|
773
|
+
Given points in $\mathbb R^n$ and function grades, compute the function-delaunay
|
|
774
|
+
bifiltration as a in an scc format, and converts it into a slicer.
|
|
775
|
+
|
|
776
|
+
points : (num_pts, n) float array
|
|
777
|
+
grades : (num_pts,) float array
|
|
778
|
+
degree (opt) : if given, computes a minimal presentation of this homological degree first
|
|
779
|
+
backend : slicer backend, e.g. "matrix", "clement"
|
|
780
|
+
vineyard : bool, use a vineyard-compatible backend
|
|
781
|
+
"""
|
|
782
|
+
from multipers.io import function_delaunay_presentation_to_slicer, _init_external_softwares
|
|
783
|
+
s = multipers.Slicer(None, backend=backend, vineyard=vineyard, dtype=dtype)
|
|
784
|
+
_init_external_softwares(requires=["function_delaunay"])
|
|
785
|
+
function_delaunay_presentation_to_slicer(s, points, grades, degree=degree, verbose=verbose,clear=clear)
|
|
786
|
+
if degree >= 0:
|
|
787
|
+
s.minpres_degree = degree
|
|
788
|
+
# s = s.minpres(degree=degree, force=True)
|
|
789
|
+
return s
|
|
790
|
+
|
|
791
|
+
def slicer2blocks(slicer, int degree = -1, bool reverse=True):
|
|
792
|
+
"""
|
|
793
|
+
Convert any slicer to the block format a.k.a. scc format for python
|
|
794
|
+
"""
|
|
795
|
+
dims = slicer.get_dimensions()
|
|
796
|
+
num_empty_blocks_to_add = 1 if degree == -1 else dims.min()-degree +1
|
|
797
|
+
_,counts = np.unique(dims, return_counts=True, )
|
|
798
|
+
indices = np.concatenate([[0],counts], dtype=np.int32).cumsum()
|
|
799
|
+
filtration_values = slicer.get_filtrations()
|
|
800
|
+
filtration_values = [filtration_values[indices[i]:indices[i+1]] for i in range(len(indices)-1)]
|
|
801
|
+
boundaries = slicer.get_boundaries()
|
|
802
|
+
boundaries = [boundaries[indices[i]:indices[i+1]] for i in range(len(indices)-1)]
|
|
803
|
+
shift = np.concatenate([[0], indices], dtype=np.int32)
|
|
804
|
+
boundaries = [tuple(np.asarray(x-s, dtype=np.int32) for x in block) for s,block in zip(shift,boundaries)]
|
|
805
|
+
blocks = [tuple((f,tuple(b))) for b,f in zip(boundaries,filtration_values)]
|
|
806
|
+
blocks = ([(np.empty((0,)),[])]*num_empty_blocks_to_add) + blocks
|
|
807
|
+
if reverse:
|
|
808
|
+
blocks.reverse()
|
|
809
|
+
return blocks
|
|
810
|
+
|
|
811
|
+
def minimal_presentation(
|
|
812
|
+
slicer,
|
|
813
|
+
int degree = -1,
|
|
814
|
+
degrees:Iterable[int]=[],
|
|
815
|
+
str backend:Literal["mpfree", "2pac", ""]="mpfree",
|
|
816
|
+
str slicer_backend:Literal["matrix","clement","graph"]="matrix",
|
|
817
|
+
bool vineyard=True,
|
|
818
|
+
id :Optional[str] =None,
|
|
819
|
+
dtype:type|_valid_dtypes=None,
|
|
820
|
+
int n_jobs = -1,
|
|
821
|
+
bool force=False,
|
|
822
|
+
bool auto_clean = True,
|
|
823
|
+
**minpres_kwargs
|
|
824
|
+
):
|
|
825
|
+
"""
|
|
826
|
+
Computes a minimal presentation of the multifiltered complex given by the slicer,
|
|
827
|
+
and returns it as a slicer.
|
|
828
|
+
Backends differents than `mpfree` are unstable.
|
|
829
|
+
"""
|
|
830
|
+
from multipers.io import _init_external_softwares, input_path, scc_reduce_from_str_to_slicer
|
|
831
|
+
if is_slicer(slicer) and slicer.is_minpres and not force:
|
|
832
|
+
from warnings import warn
|
|
833
|
+
warn(f"(unnecessary computation) The slicer seems to be already reduced, from homology of degree {slicer.minpres_degree}.")
|
|
834
|
+
return slicer
|
|
835
|
+
_init_external_softwares(requires=[backend])
|
|
836
|
+
if len(degrees)>0:
|
|
837
|
+
def todo(int degree):
|
|
838
|
+
return minimal_presentation(slicer, degree=degree, backend=backend, slicer_backend=slicer_backend, vineyard=vineyard, id=id, **minpres_kwargs)
|
|
839
|
+
return tuple(
|
|
840
|
+
Parallel(n_jobs=n_jobs, backend="threading")(delayed(todo)(d) for d in degrees)
|
|
841
|
+
)
|
|
842
|
+
# return tuple(minimal_presentation(slicer, degree=d, backend=backend, slicer_backend=slicer_backend, vineyard=vineyard, id=id, **minpres_kwargs) for d in degrees)
|
|
843
|
+
assert degree>=0, f"Degree not provided."
|
|
844
|
+
if not np.any(slicer.get_dimensions() == degree):
|
|
845
|
+
return type(slicer)()
|
|
846
|
+
if id is None:
|
|
847
|
+
id = str(threading.get_native_id())
|
|
848
|
+
if dtype is None:
|
|
849
|
+
dtype = slicer.dtype
|
|
850
|
+
dimension = slicer.dimension - degree # latest = L-1, which is empty, -1 for degree 0, -2 for degree 1 etc.
|
|
851
|
+
slicer.to_scc(path=input_path+id, strip_comments=True, degree=degree-1, unsqueeze = False)
|
|
852
|
+
new_slicer = multipers.Slicer(None,backend=slicer_backend, vineyard=vineyard, dtype=dtype)
|
|
853
|
+
if backend=="mpfree":
|
|
854
|
+
shift_dimension=degree-1
|
|
855
|
+
else:
|
|
856
|
+
shift_dimension=degree
|
|
857
|
+
scc_reduce_from_str_to_slicer(path=input_path+id, slicer=new_slicer, dimension=dimension, backend=backend, shift_dimension=shift_dimension, **minpres_kwargs)
|
|
858
|
+
|
|
859
|
+
new_slicer.minpres_degree = degree
|
|
860
|
+
new_slicer.filtration_grid = slicer.filtration_grid if slicer.is_squeezed else None
|
|
861
|
+
if new_slicer.is_squeezed and auto_clean:
|
|
862
|
+
new_slicer = new_slicer._clean_filtration_grid()
|
|
863
|
+
return new_slicer
|
|
864
|
+
|
|
865
|
+
|
|
866
|
+
def to_simplextree(s:Slicer_type, max_dim:int=-1) -> SimplexTreeMulti_type:
|
|
867
|
+
"""
|
|
868
|
+
Turns a --simplicial-- slicer into a simplextree.
|
|
869
|
+
|
|
870
|
+
Warning: Won't work for non-simplicial complexes,
|
|
871
|
+
i.e., complexes $K$ not satisfying
|
|
872
|
+
$\forall \sigma \in K,\, \mathrm{dim}(\sigma) = |\partial \sigma|-1$
|
|
873
|
+
"""
|
|
874
|
+
dims = s.get_dimensions()
|
|
875
|
+
assert np.all(dims[:-1] <= dims[1:]), "Dims is not sorted."
|
|
876
|
+
idx = np.searchsorted(dims, np.unique(dims))
|
|
877
|
+
idx = np.concatenate([idx, [dims.shape[0]]])
|
|
878
|
+
if max_dim>=0:
|
|
879
|
+
idx = idx[:max_dim+2]
|
|
880
|
+
|
|
881
|
+
cdef vector[vector[int]] boundaries_ = s.get_boundaries()
|
|
882
|
+
cdef int a
|
|
883
|
+
cdef int b
|
|
884
|
+
if len(idx)>2:
|
|
885
|
+
a = idx[2]
|
|
886
|
+
b = idx[-1]
|
|
887
|
+
for i in range(a, b):
|
|
888
|
+
boundaries_[i] = np.unique(np.concatenate([boundaries_[k] for k in boundaries_[i]]))
|
|
889
|
+
cdef int num_dims = len(idx)-1
|
|
890
|
+
boundaries = [np.asarray(boundaries_[idx[i]:idx[i+1]], dtype=np.int32).T for i in range(num_dims)]
|
|
891
|
+
boundaries[0] = np.arange(boundaries[0].shape[1])[None,:]
|
|
892
|
+
filtrations = s.get_filtrations()
|
|
893
|
+
num_parameters = s.num_parameters
|
|
894
|
+
filtrations=tuple(filtrations[idx[i]:idx[i+1]] for i in range(num_dims)) # TODO : optimize ?
|
|
895
|
+
st = SimplexTreeMulti(num_parameters = num_parameters, dtype = s.dtype, kcritical=s.is_kcritical)
|
|
896
|
+
for dim in range(num_dims):
|
|
897
|
+
if s.is_kcritical: ## TODO : this may be very slow
|
|
898
|
+
# for b,f in zip(boundaries[i].T,filtrations[i]):
|
|
899
|
+
# for g in np.asarray(f):
|
|
900
|
+
# st.insert(np.asarray(b, dtype = np.int32),np.asarray(g, dtype=s.dtype))
|
|
901
|
+
for j in range(boundaries[i].shape[1]):
|
|
902
|
+
splx = boundaries[i][:,j]
|
|
903
|
+
for k in range(filtrations[i][j]):
|
|
904
|
+
st.insert(splx, np.asarray(filtrations[i][j][k], dtype = s.dtype))
|
|
905
|
+
else:
|
|
906
|
+
st.insert_batch(np.asarray(boundaries[dim], dtype= np.int32),np.asarray(filtrations[dim], dtype=s.dtype))
|
|
907
|
+
return st
|
|
908
|
+
|
|
909
|
+
|
|
910
|
+
def _is_slicer(object input)->bool:
|
|
911
|
+
"""
|
|
912
|
+
Checks if the input is a slicer. Equivalent (but faster) to `isinstance(input, multipers.slicer.Slicer_type)`.
|
|
913
|
+
"""
|
|
914
|
+
return (False
|
|
915
|
+
{{for D in slicers}}
|
|
916
|
+
or isinstance(input, {{D['PYTHON_TYPE']}})
|
|
917
|
+
{{endfor}}
|
|
918
|
+
)
|
|
919
|
+
def is_slicer(input, bool allow_minpres=True)->bool:
|
|
920
|
+
if _is_slicer(input):
|
|
921
|
+
return True
|
|
922
|
+
if allow_minpres and isinstance(input, list) or isinstance(input, tuple):
|
|
923
|
+
if len(input)>0 and all((_is_slicer(s) and s.is_minpres for s in input)):
|
|
924
|
+
return True
|
|
925
|
+
return False
|
|
926
|
+
|
|
927
|
+
|
|
928
|
+
def to_blocks(input):
|
|
929
|
+
"""
|
|
930
|
+
Converts input to blocks, if possible.
|
|
931
|
+
"""
|
|
932
|
+
if is_slicer(input):
|
|
933
|
+
return slicer2blocks(input)
|
|
934
|
+
if isinstance(input, list) or isinstance(input, tuple):
|
|
935
|
+
return input
|
|
936
|
+
from multipers.simplex_tree_multi import is_simplextree_multi
|
|
937
|
+
if is_simplextree_multi(input):
|
|
938
|
+
return input._to_scc()
|
|
939
|
+
if isinstance(input, str) or isinstance(input, os.PathLike):
|
|
940
|
+
from multipers.io import scc_parser
|
|
941
|
+
return scc_parser(input)
|
|
942
|
+
raise ValueError("Input cannot be converted to blocks.")
|
|
943
|
+
|
|
944
|
+
|
|
945
|
+
def get_matrix_slicer(bool is_vineyard, bool is_k_critical, dtype:type|_valid_dtypes, str col, str pers_backend):
|
|
946
|
+
"""
|
|
947
|
+
Given various parameters, returns the specific slicer type associated with them.
|
|
948
|
+
"""
|
|
949
|
+
if False:
|
|
950
|
+
pass
|
|
951
|
+
{{for D in slicers}}
|
|
952
|
+
{{if not D['IS_SIMPLICIAL']}}
|
|
953
|
+
elif is_vineyard == {{D['IS_VINE']}} and is_k_critical == {{D['IS_KCRITICAL']}} and np.dtype(dtype) is np.dtype({{D['PY_VALUE_TYPE']}}) and col.lower() == "{{D['COLUMN_TYPE']}}".lower() and "{{D['PERS_BACKEND_TYPE']}}".lower() == pers_backend.lower():
|
|
954
|
+
return {{D['PYTHON_TYPE']}}
|
|
955
|
+
{{endif}}
|
|
956
|
+
{{endfor}}
|
|
957
|
+
else:
|
|
958
|
+
raise ValueError(f"Unimplemented combo for {pers_backend} : {is_vineyard=}, {is_k_critical=}, {dtype=}, {col=}")
|
|
959
|
+
|
|
960
|
+
|
|
961
|
+
|
|
962
|
+
|
|
963
|
+
def _hilbert_signed_measure(slicer,
|
|
964
|
+
vector[indices_type] degrees,
|
|
965
|
+
bool zero_pad=False,
|
|
966
|
+
indices_type n_jobs=0,
|
|
967
|
+
bool verbose=False,
|
|
968
|
+
# bool expand_collapse=False,
|
|
969
|
+
# grid_conversion = None,
|
|
970
|
+
bool ignore_inf = True,
|
|
971
|
+
):
|
|
972
|
+
"""
|
|
973
|
+
Computes the signed measures given by the decomposition of the hilbert function.
|
|
974
|
+
|
|
975
|
+
Input
|
|
976
|
+
-----
|
|
977
|
+
|
|
978
|
+
- simplextree:SimplexTreeMulti, the multifiltered simplicial complex
|
|
979
|
+
- degrees:array-like of ints, the degrees to compute
|
|
980
|
+
- n_jobs:int, number of jobs. Defaults to #cpu, but when doing parallel computations of signed measures, we recommend setting this to 1.
|
|
981
|
+
- verbose:bool, prints c++ logs.
|
|
982
|
+
|
|
983
|
+
Output
|
|
984
|
+
------
|
|
985
|
+
|
|
986
|
+
`[signed_measure_of_degree for degree in degrees]`
|
|
987
|
+
with `signed_measure_of_degree` of the form `(dirac location, dirac weights)`.
|
|
988
|
+
"""
|
|
989
|
+
|
|
990
|
+
assert slicer.is_squeezed, "Squeeze grid first."
|
|
991
|
+
if slicer.is_squeezed:
|
|
992
|
+
grid_shape = np.array([len(f) for f in slicer.filtration_grid])
|
|
993
|
+
else:
|
|
994
|
+
grid_shape = (slicer.filtration_bounds()[1]).astype(python_indices_type)+1
|
|
995
|
+
if zero_pad:
|
|
996
|
+
for i, _ in enumerate(grid_shape):
|
|
997
|
+
grid_shape[i] += 1 # adds a 0
|
|
998
|
+
assert len(grid_shape) == slicer.num_parameters, "Grid shape size has to be the number of parameters."
|
|
999
|
+
grid_shape_with_degree = np.asarray(np.concatenate([[len(degrees)], grid_shape]), dtype=python_indices_type)
|
|
1000
|
+
container_array = np.ascontiguousarray(np.zeros(grid_shape_with_degree, dtype=python_tensor_dtype).flatten())
|
|
1001
|
+
assert len(container_array) < np.iinfo(np.uint32).max, "Too large container. Raise an issue on github if you encounter this issue. (Due to tensor's operator[])"
|
|
1002
|
+
cdef vector[indices_type] c_grid_shape = grid_shape_with_degree
|
|
1003
|
+
cdef tensor_dtype[::1] container = container_array
|
|
1004
|
+
cdef tensor_dtype* container_ptr = &container[0]
|
|
1005
|
+
cdef signed_measure_type out = _compute_hilbert_sm(slicer, container_ptr, c_grid_shape, degrees,n_jobs, verbose, zero_pad, ignore_inf)
|
|
1006
|
+
pts, weights = np.asarray(out.first, dtype=python_indices_type).reshape(-1, slicer.num_parameters+1), np.asarray(out.second, dtype=python_tensor_dtype)
|
|
1007
|
+
slices = np.concatenate([np.searchsorted(pts[:,0], np.arange(degrees.size())), [pts.shape[0]] ])
|
|
1008
|
+
sms = [
|
|
1009
|
+
(pts[slices[i]:slices[i+1],1:],weights[slices[i]:slices[i+1]])
|
|
1010
|
+
for i in range(slices.shape[0]-1)
|
|
1011
|
+
]
|
|
1012
|
+
return sms
|
|
1013
|
+
|
|
1014
|
+
|
|
1015
|
+
## Rank invariant
|
|
1016
|
+
|
|
1017
|
+
|
|
1018
|
+
## TODO : It is not necessary to do the Möbius inversion in python.
|
|
1019
|
+
## fill rank in flipped death, then differentiate in cpp, then reflip with numpy.
|
|
1020
|
+
def _rank_from_slicer(
|
|
1021
|
+
slicer,
|
|
1022
|
+
vector[indices_type] degrees,
|
|
1023
|
+
bool verbose=False,
|
|
1024
|
+
indices_type n_jobs=1,
|
|
1025
|
+
bool zero_pad = False,
|
|
1026
|
+
grid_shape=None,
|
|
1027
|
+
bool plot=False,
|
|
1028
|
+
bool return_raw=False,
|
|
1029
|
+
bool ignore_inf = True,
|
|
1030
|
+
):
|
|
1031
|
+
# cdef intptr_t slicer_ptr = <intptr_t>(slicer.get_ptr())
|
|
1032
|
+
if grid_shape is None:
|
|
1033
|
+
if slicer.is_squeezed:
|
|
1034
|
+
grid_shape = np.array([len(f) for f in slicer.filtration_grid])
|
|
1035
|
+
else:
|
|
1036
|
+
grid_shape = (slicer.filtration_bounds()[1]).astype(python_indices_type)+1
|
|
1037
|
+
grid_shape = np.asarray(grid_shape)
|
|
1038
|
+
|
|
1039
|
+
cdef int num_parameters = len(grid_shape)
|
|
1040
|
+
|
|
1041
|
+
# if zero_pad:
|
|
1042
|
+
# grid_shape += 1
|
|
1043
|
+
# for i, _ in enumerate(grid_shape):
|
|
1044
|
+
# grid_shape[i] += 1 # adds a 0
|
|
1045
|
+
# for i,f in enumerate(grid_conversion):
|
|
1046
|
+
# grid_conversion[i] = np.concatenate([f, [mass_default[i]]])
|
|
1047
|
+
|
|
1048
|
+
|
|
1049
|
+
grid_shape_with_degree = np.asarray(np.concatenate([[len(degrees)], grid_shape, grid_shape]), dtype=python_indices_type)
|
|
1050
|
+
if verbose:
|
|
1051
|
+
print("Container shape: ", grid_shape_with_degree)
|
|
1052
|
+
container_array = np.ascontiguousarray(np.zeros(grid_shape_with_degree, dtype=python_tensor_dtype).ravel())
|
|
1053
|
+
assert len(container_array) < np.iinfo(python_indices_type).max, "Too large container. Raise an issue on github if you encounter this issue. (Due to tensor's operator[])"
|
|
1054
|
+
# if zero_pad:
|
|
1055
|
+
# grid_shape_with_degree[1:] -= 1
|
|
1056
|
+
cdef vector[indices_type] c_grid_shape = grid_shape_with_degree
|
|
1057
|
+
cdef tensor_dtype[::1] container = container_array
|
|
1058
|
+
cdef tensor_dtype* container_ptr = &container[0]
|
|
1059
|
+
|
|
1060
|
+
## SLICERS
|
|
1061
|
+
if verbose:
|
|
1062
|
+
print("Computing rank invariant...", end="")
|
|
1063
|
+
_compute_rank_invariant(slicer, container_ptr, c_grid_shape, degrees, n_jobs, ignore_inf)
|
|
1064
|
+
if verbose:
|
|
1065
|
+
print("Done.")
|
|
1066
|
+
|
|
1067
|
+
if verbose:
|
|
1068
|
+
print("Computing Möbius inversion...", end="")
|
|
1069
|
+
# if zero_pad:
|
|
1070
|
+
# grid_shape_with_degree[1:] += 1
|
|
1071
|
+
rank = container_array.reshape(grid_shape_with_degree)
|
|
1072
|
+
rank = tuple(rank_decomposition_by_rectangles(rank_of_degree, threshold=zero_pad) for rank_of_degree in rank)
|
|
1073
|
+
if return_raw:
|
|
1074
|
+
return rank
|
|
1075
|
+
out = []
|
|
1076
|
+
def clean_rank(rank_decomposition):
|
|
1077
|
+
(coords, weights) = sparsify(np.ascontiguousarray(rank_decomposition))
|
|
1078
|
+
births = coords[:,:num_parameters]
|
|
1079
|
+
deaths = coords[:,num_parameters:]
|
|
1080
|
+
correct_indices = np.all(births<=deaths, axis=1)
|
|
1081
|
+
coords = coords[correct_indices]
|
|
1082
|
+
weights = weights[correct_indices]
|
|
1083
|
+
return coords, weights
|
|
1084
|
+
|
|
1085
|
+
out = tuple(clean_rank(rank_decomposition) for rank_decomposition in rank)
|
|
1086
|
+
if verbose:
|
|
1087
|
+
print("Done.")
|
|
1088
|
+
return out
|