multipers 2.2.3__cp311-cp311-win_amd64.whl → 2.3.0__cp311-cp311-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of multipers might be problematic. Click here for more details.

Files changed (182) hide show
  1. multipers/__init__.py +33 -31
  2. multipers/_signed_measure_meta.py +430 -430
  3. multipers/_slicer_meta.py +211 -212
  4. multipers/data/MOL2.py +458 -458
  5. multipers/data/UCR.py +18 -18
  6. multipers/data/graphs.py +466 -466
  7. multipers/data/immuno_regions.py +27 -27
  8. multipers/data/pytorch2simplextree.py +90 -90
  9. multipers/data/shape3d.py +101 -101
  10. multipers/data/synthetic.py +113 -111
  11. multipers/distances.py +198 -198
  12. multipers/filtration_conversions.pxd.tp +84 -84
  13. multipers/filtrations/__init__.py +18 -0
  14. multipers/filtrations/filtrations.py +289 -0
  15. multipers/filtrations.pxd +224 -224
  16. multipers/function_rips.cp311-win_amd64.pyd +0 -0
  17. multipers/function_rips.pyx +105 -105
  18. multipers/grids.cp311-win_amd64.pyd +0 -0
  19. multipers/grids.pyx +350 -350
  20. multipers/gudhi/Persistence_slices_interface.h +132 -132
  21. multipers/gudhi/Simplex_tree_interface.h +239 -245
  22. multipers/gudhi/Simplex_tree_multi_interface.h +516 -561
  23. multipers/gudhi/cubical_to_boundary.h +59 -59
  24. multipers/gudhi/gudhi/Bitmap_cubical_complex.h +450 -450
  25. multipers/gudhi/gudhi/Bitmap_cubical_complex_base.h +1070 -1070
  26. multipers/gudhi/gudhi/Bitmap_cubical_complex_periodic_boundary_conditions_base.h +579 -579
  27. multipers/gudhi/gudhi/Debug_utils.h +45 -45
  28. multipers/gudhi/gudhi/Fields/Multi_field.h +484 -484
  29. multipers/gudhi/gudhi/Fields/Multi_field_operators.h +455 -455
  30. multipers/gudhi/gudhi/Fields/Multi_field_shared.h +450 -450
  31. multipers/gudhi/gudhi/Fields/Multi_field_small.h +531 -531
  32. multipers/gudhi/gudhi/Fields/Multi_field_small_operators.h +507 -507
  33. multipers/gudhi/gudhi/Fields/Multi_field_small_shared.h +531 -531
  34. multipers/gudhi/gudhi/Fields/Z2_field.h +355 -355
  35. multipers/gudhi/gudhi/Fields/Z2_field_operators.h +376 -376
  36. multipers/gudhi/gudhi/Fields/Zp_field.h +420 -420
  37. multipers/gudhi/gudhi/Fields/Zp_field_operators.h +400 -400
  38. multipers/gudhi/gudhi/Fields/Zp_field_shared.h +418 -418
  39. multipers/gudhi/gudhi/Flag_complex_edge_collapser.h +337 -337
  40. multipers/gudhi/gudhi/Matrix.h +2107 -2107
  41. multipers/gudhi/gudhi/Multi_critical_filtration.h +1038 -1038
  42. multipers/gudhi/gudhi/Multi_persistence/Box.h +171 -171
  43. multipers/gudhi/gudhi/Multi_persistence/Line.h +282 -282
  44. multipers/gudhi/gudhi/Off_reader.h +173 -173
  45. multipers/gudhi/gudhi/One_critical_filtration.h +1432 -1431
  46. multipers/gudhi/gudhi/Persistence_matrix/Base_matrix.h +769 -769
  47. multipers/gudhi/gudhi/Persistence_matrix/Base_matrix_with_column_compression.h +686 -686
  48. multipers/gudhi/gudhi/Persistence_matrix/Boundary_matrix.h +842 -842
  49. multipers/gudhi/gudhi/Persistence_matrix/Chain_matrix.h +1350 -1350
  50. multipers/gudhi/gudhi/Persistence_matrix/Id_to_index_overlay.h +1105 -1105
  51. multipers/gudhi/gudhi/Persistence_matrix/Position_to_index_overlay.h +859 -859
  52. multipers/gudhi/gudhi/Persistence_matrix/RU_matrix.h +910 -910
  53. multipers/gudhi/gudhi/Persistence_matrix/allocators/entry_constructors.h +139 -139
  54. multipers/gudhi/gudhi/Persistence_matrix/base_pairing.h +230 -230
  55. multipers/gudhi/gudhi/Persistence_matrix/base_swap.h +211 -211
  56. multipers/gudhi/gudhi/Persistence_matrix/boundary_cell_position_to_id_mapper.h +60 -60
  57. multipers/gudhi/gudhi/Persistence_matrix/boundary_face_position_to_id_mapper.h +60 -60
  58. multipers/gudhi/gudhi/Persistence_matrix/chain_pairing.h +136 -136
  59. multipers/gudhi/gudhi/Persistence_matrix/chain_rep_cycles.h +190 -190
  60. multipers/gudhi/gudhi/Persistence_matrix/chain_vine_swap.h +616 -616
  61. multipers/gudhi/gudhi/Persistence_matrix/columns/chain_column_extra_properties.h +150 -150
  62. multipers/gudhi/gudhi/Persistence_matrix/columns/column_dimension_holder.h +106 -106
  63. multipers/gudhi/gudhi/Persistence_matrix/columns/column_utilities.h +219 -219
  64. multipers/gudhi/gudhi/Persistence_matrix/columns/entry_types.h +327 -327
  65. multipers/gudhi/gudhi/Persistence_matrix/columns/heap_column.h +1140 -1140
  66. multipers/gudhi/gudhi/Persistence_matrix/columns/intrusive_list_column.h +934 -934
  67. multipers/gudhi/gudhi/Persistence_matrix/columns/intrusive_set_column.h +934 -934
  68. multipers/gudhi/gudhi/Persistence_matrix/columns/list_column.h +980 -980
  69. multipers/gudhi/gudhi/Persistence_matrix/columns/naive_vector_column.h +1092 -1092
  70. multipers/gudhi/gudhi/Persistence_matrix/columns/row_access.h +192 -192
  71. multipers/gudhi/gudhi/Persistence_matrix/columns/set_column.h +921 -921
  72. multipers/gudhi/gudhi/Persistence_matrix/columns/small_vector_column.h +1093 -1093
  73. multipers/gudhi/gudhi/Persistence_matrix/columns/unordered_set_column.h +1012 -1012
  74. multipers/gudhi/gudhi/Persistence_matrix/columns/vector_column.h +1244 -1244
  75. multipers/gudhi/gudhi/Persistence_matrix/matrix_dimension_holders.h +186 -186
  76. multipers/gudhi/gudhi/Persistence_matrix/matrix_row_access.h +164 -164
  77. multipers/gudhi/gudhi/Persistence_matrix/ru_pairing.h +156 -156
  78. multipers/gudhi/gudhi/Persistence_matrix/ru_rep_cycles.h +376 -376
  79. multipers/gudhi/gudhi/Persistence_matrix/ru_vine_swap.h +540 -540
  80. multipers/gudhi/gudhi/Persistent_cohomology/Field_Zp.h +118 -118
  81. multipers/gudhi/gudhi/Persistent_cohomology/Multi_field.h +173 -173
  82. multipers/gudhi/gudhi/Persistent_cohomology/Persistent_cohomology_column.h +128 -128
  83. multipers/gudhi/gudhi/Persistent_cohomology.h +745 -745
  84. multipers/gudhi/gudhi/Points_off_io.h +171 -171
  85. multipers/gudhi/gudhi/Simple_object_pool.h +69 -69
  86. multipers/gudhi/gudhi/Simplex_tree/Simplex_tree_iterators.h +463 -463
  87. multipers/gudhi/gudhi/Simplex_tree/Simplex_tree_node_explicit_storage.h +83 -83
  88. multipers/gudhi/gudhi/Simplex_tree/Simplex_tree_siblings.h +106 -106
  89. multipers/gudhi/gudhi/Simplex_tree/Simplex_tree_star_simplex_iterators.h +277 -277
  90. multipers/gudhi/gudhi/Simplex_tree/hooks_simplex_base.h +62 -62
  91. multipers/gudhi/gudhi/Simplex_tree/indexing_tag.h +27 -27
  92. multipers/gudhi/gudhi/Simplex_tree/serialization_utils.h +62 -62
  93. multipers/gudhi/gudhi/Simplex_tree/simplex_tree_options.h +157 -157
  94. multipers/gudhi/gudhi/Simplex_tree.h +2794 -2794
  95. multipers/gudhi/gudhi/Simplex_tree_multi.h +152 -163
  96. multipers/gudhi/gudhi/distance_functions.h +62 -62
  97. multipers/gudhi/gudhi/graph_simplicial_complex.h +104 -104
  98. multipers/gudhi/gudhi/persistence_interval.h +253 -253
  99. multipers/gudhi/gudhi/persistence_matrix_options.h +170 -170
  100. multipers/gudhi/gudhi/reader_utils.h +367 -367
  101. multipers/gudhi/mma_interface_coh.h +256 -255
  102. multipers/gudhi/mma_interface_h0.h +223 -231
  103. multipers/gudhi/mma_interface_matrix.h +284 -282
  104. multipers/gudhi/naive_merge_tree.h +536 -575
  105. multipers/gudhi/scc_io.h +310 -289
  106. multipers/gudhi/truc.h +890 -888
  107. multipers/io.cp311-win_amd64.pyd +0 -0
  108. multipers/io.pyx +711 -711
  109. multipers/ml/accuracies.py +90 -90
  110. multipers/ml/convolutions.py +520 -520
  111. multipers/ml/invariants_with_persistable.py +79 -79
  112. multipers/ml/kernels.py +176 -176
  113. multipers/ml/mma.py +713 -714
  114. multipers/ml/one.py +472 -472
  115. multipers/ml/point_clouds.py +352 -346
  116. multipers/ml/signed_measures.py +1589 -1589
  117. multipers/ml/sliced_wasserstein.py +461 -461
  118. multipers/ml/tools.py +113 -113
  119. multipers/mma_structures.cp311-win_amd64.pyd +0 -0
  120. multipers/mma_structures.pxd +127 -127
  121. multipers/mma_structures.pyx +4 -4
  122. multipers/mma_structures.pyx.tp +1085 -1085
  123. multipers/multi_parameter_rank_invariant/diff_helpers.h +84 -93
  124. multipers/multi_parameter_rank_invariant/euler_characteristic.h +97 -97
  125. multipers/multi_parameter_rank_invariant/function_rips.h +322 -322
  126. multipers/multi_parameter_rank_invariant/hilbert_function.h +769 -769
  127. multipers/multi_parameter_rank_invariant/persistence_slices.h +148 -148
  128. multipers/multi_parameter_rank_invariant/rank_invariant.h +369 -369
  129. multipers/multiparameter_edge_collapse.py +41 -41
  130. multipers/multiparameter_module_approximation/approximation.h +2296 -2295
  131. multipers/multiparameter_module_approximation/combinatory.h +129 -129
  132. multipers/multiparameter_module_approximation/debug.h +107 -107
  133. multipers/multiparameter_module_approximation/format_python-cpp.h +286 -286
  134. multipers/multiparameter_module_approximation/heap_column.h +238 -238
  135. multipers/multiparameter_module_approximation/images.h +79 -79
  136. multipers/multiparameter_module_approximation/list_column.h +174 -174
  137. multipers/multiparameter_module_approximation/list_column_2.h +232 -232
  138. multipers/multiparameter_module_approximation/ru_matrix.h +347 -347
  139. multipers/multiparameter_module_approximation/set_column.h +135 -135
  140. multipers/multiparameter_module_approximation/structure_higher_dim_barcode.h +36 -36
  141. multipers/multiparameter_module_approximation/unordered_set_column.h +166 -166
  142. multipers/multiparameter_module_approximation/utilities.h +403 -419
  143. multipers/multiparameter_module_approximation/vector_column.h +223 -223
  144. multipers/multiparameter_module_approximation/vector_matrix.h +331 -331
  145. multipers/multiparameter_module_approximation/vineyards.h +464 -464
  146. multipers/multiparameter_module_approximation/vineyards_trajectories.h +649 -649
  147. multipers/multiparameter_module_approximation.cp311-win_amd64.pyd +0 -0
  148. multipers/multiparameter_module_approximation.pyx +216 -217
  149. multipers/pickle.py +90 -53
  150. multipers/plots.py +342 -334
  151. multipers/point_measure.cp311-win_amd64.pyd +0 -0
  152. multipers/point_measure.pyx +322 -320
  153. multipers/simplex_tree_multi.cp311-win_amd64.pyd +0 -0
  154. multipers/simplex_tree_multi.pxd +133 -133
  155. multipers/simplex_tree_multi.pyx +18 -15
  156. multipers/simplex_tree_multi.pyx.tp +1939 -1935
  157. multipers/slicer.cp311-win_amd64.pyd +0 -0
  158. multipers/slicer.pxd +81 -20
  159. multipers/slicer.pxd.tp +215 -214
  160. multipers/slicer.pyx +1091 -308
  161. multipers/slicer.pyx.tp +924 -914
  162. multipers/tensor/tensor.h +672 -672
  163. multipers/tensor.pxd +13 -13
  164. multipers/test.pyx +44 -44
  165. multipers/tests/__init__.py +57 -57
  166. multipers/torch/diff_grids.py +217 -217
  167. multipers/torch/rips_density.py +310 -304
  168. {multipers-2.2.3.dist-info → multipers-2.3.0.dist-info}/LICENSE +21 -21
  169. {multipers-2.2.3.dist-info → multipers-2.3.0.dist-info}/METADATA +21 -11
  170. multipers-2.3.0.dist-info/RECORD +182 -0
  171. multipers/tests/test_diff_helper.py +0 -73
  172. multipers/tests/test_hilbert_function.py +0 -82
  173. multipers/tests/test_mma.py +0 -83
  174. multipers/tests/test_point_clouds.py +0 -49
  175. multipers/tests/test_python-cpp_conversion.py +0 -82
  176. multipers/tests/test_signed_betti.py +0 -181
  177. multipers/tests/test_signed_measure.py +0 -89
  178. multipers/tests/test_simplextreemulti.py +0 -221
  179. multipers/tests/test_slicer.py +0 -221
  180. multipers-2.2.3.dist-info/RECORD +0 -189
  181. {multipers-2.2.3.dist-info → multipers-2.3.0.dist-info}/WHEEL +0 -0
  182. {multipers-2.2.3.dist-info → multipers-2.3.0.dist-info}/top_level.txt +0 -0
@@ -1,320 +1,322 @@
1
- # cimport multipers.tensor as mt
2
- from libc.stdint cimport intptr_t, uint16_t, uint32_t, int32_t, int64_t
3
- from libcpp.vector cimport vector
4
- from libcpp cimport bool, int, float
5
- import numpy as np
6
- cimport numpy as cnp
7
- import itertools
8
- from typing import Optional,Iterable
9
-
10
- from collections import defaultdict
11
- cnp.import_array()
12
- from scipy import sparse
13
-
14
-
15
- import multipers.grids as mpg
16
-
17
- ctypedef fused some_int:
18
- int32_t
19
- int64_t
20
- int
21
-
22
- ctypedef fused some_float:
23
- int32_t
24
- int64_t
25
- int
26
- float
27
- double
28
-
29
-
30
- import cython
31
- cimport cython
32
-
33
-
34
-
35
- # from scipy.sparse import coo_array
36
- # from scipy.ndimage import convolve1d
37
-
38
- def signed_betti(hilbert_function:np.ndarray, bool threshold=False):
39
- cdef int n = hilbert_function.ndim
40
- # zero out the "end" of the Hilbert function
41
- if threshold:
42
- for dimension in range(n):
43
- slicer = tuple([slice(None) if i != dimension else -1 for i in range(n)])
44
- hilbert_function[slicer] = 0
45
- for i in range(n):
46
- minus = tuple([slice(None) if j!=i else slice(0,-1) for j in range(n)])
47
- plus = tuple([slice(None) if j!=i else slice(1,None) for j in range(n)])
48
- hilbert_function[plus] -= hilbert_function[minus]
49
- return hilbert_function
50
-
51
- def rank_decomposition_by_rectangles(rank_invariant:np.ndarray, bool threshold=False):
52
- # takes as input the rank invariant of an n-parameter persistence module
53
- # M : [0, ..., s_1 - 1] x ... x [0, ..., s_n - 1] ---> Vec
54
- # on a grid with dimensions of sizes s_1, ..., s_n. The input is assumed to be
55
- # given as a tensor of dimensions (s_1, ..., s_n, s_1, ..., s_n), so that,
56
- # at index [i_1, ..., i_n, j_1, ..., j_n] we have the rank of the structure
57
- # map M(i) -> M(j), where i = (i_1, ..., i_n) and j = (j_1, ..., j_n), and
58
- # i <= j, meaning that i_1 <= j_1, ..., i_n <= j_n.
59
- # NOTE :
60
- # - About the input, we assume that, if not( i <= j ), then at index
61
- # [i_1, ..., i_n, j_1, ..., j_n] we have a zero.
62
- # - Similarly, the output at index [i_1, ..., i_n, j_1, ..., j_n] only
63
- # makes sense when i <= j. For indices where not( i <= j ) the output
64
- # may take arbitrary values and they should be ignored.
65
- cdef int n = rank_invariant.ndim // 2
66
- if threshold:
67
- # zero out the "end"
68
- for dimension in range(n):
69
- slicer = tuple(
70
- [slice(None) for _ in range(n)]
71
- + [slice(None) if i != dimension else -1 for i in range(n)]
72
- )
73
- rank_invariant[slicer] = 0
74
- to_flip = tuple(range(n, 2 * n))
75
- return np.flip(signed_betti(np.flip(rank_invariant, to_flip)), to_flip)
76
-
77
-
78
-
79
-
80
-
81
-
82
-
83
- @cython.boundscheck(False)
84
- @cython.wraparound(False)
85
- def integrate_measure(
86
- some_float[:,:] pts,
87
- some_int[:] weights,
88
- filtration_grid:Optional[Iterable[np.ndarray]]=None,
89
- grid_strategy:str="regular",
90
- resolution:int|list[int]=100,
91
- bool return_grid=False,
92
- **get_fitration_kwargs,
93
- ):
94
- """
95
- Integrate a point measure on a grid.
96
- Measure is a sum of diracs, based on points `pts` and weights `weights`.
97
- For instance, if the signed measure comes from the hilbert signed measure,
98
- this integration will return the hilbert function on this grid.
99
- - pts : array of points (num_pts, D)
100
- - weights : array of weights (num_pts,)
101
- - filtration_grid (optional) : list of 1d arrays
102
- - resolution : int or list of int
103
- - return_grid : return the grid of the measure
104
- - **get_fitration_kwargs : arguments to compute the grid,
105
- if the grid is not given.
106
- """
107
- if filtration_grid is None:
108
- import multipers.simplex_tree_multi
109
- filtration_grid = mpg.compute_grid(
110
- np.asarray(pts).T,
111
- strategy=grid_strategy,
112
- resolution=resolution,
113
- **get_fitration_kwargs
114
- )
115
- resolution = np.asarray([len(f) for f in filtration_grid])
116
- cdef int num_pts = pts.shape[0]
117
- cdef int num_parameters = pts.shape[1]
118
- assert weights.shape[0] == num_pts
119
- out = np.zeros(shape=resolution, dtype=np.int32) ## dim cannot be known at compiletime
120
- # cdef some_float[:] filtration_of_parameter
121
- # cdef cnp.ndarray indices = np.zeros(shape=num_parameters, dtype=int)
122
- #
123
- pts_coords = np.empty((num_parameters, num_pts), dtype=np.int64)
124
- for parameter in range(num_parameters):
125
- pts_coords[parameter] = np.searchsorted(filtration_grid[parameter], pts[:,parameter])
126
- for i in range(num_pts):
127
- cone = tuple(slice(c,r) for r,c in zip(resolution,pts_coords[:,i]))
128
- out[cone] += weights[i]
129
- if return_grid:
130
- return out,filtration_grid
131
- return out
132
-
133
- ## for benchmark purposes
134
- def integrate_measure_python(pts, weights, filtrations):
135
- resolution = tuple([len(f) for f in filtrations])
136
- out = np.zeros(shape=resolution, dtype=pts.dtype)
137
- num_pts = pts.shape[0]
138
- num_parameters = pts.shape[1]
139
- for i in range(num_pts): #this is slow.
140
- indices = (filtrations[parameter]>=pts[i][parameter] for parameter in range(num_parameters))
141
- out[np.ix_(*indices)] += weights[i]
142
- return out
143
-
144
-
145
- def sparsify(x):
146
- """
147
- Given an arbitrary dimensional numpy array, returns (coordinates,data).
148
- --
149
- cost : scipy sparse + num_points*num_parameters^2 divisions
150
- """
151
- num_parameters = x.ndim
152
- sx = sparse.coo_array(x.ravel())
153
- idx = sx.col
154
- data = sx.data
155
- coords = np.empty((data.shape[0], num_parameters), dtype=np.int64)
156
- for parameter in range(num_parameters-1,-1,-1):
157
- idx,coord_of_parameter = np.divmod(idx, x.shape[parameter])
158
- coords[:, parameter] = coord_of_parameter
159
- return coords,data
160
-
161
-
162
-
163
-
164
- @cython.boundscheck(False)
165
- @cython.wraparound(False)
166
- def clean_signed_measure(some_float[:,:] pts, some_int[:] weights, dtype = np.float32):
167
- """
168
- Sum the diracs at the same locations. i.e.,
169
- returns the minimal sized measure to represent the input.
170
- Mostly useful for, e.g., euler_characteristic from simplical complexes.
171
- """
172
- cdef dict[tuple, int] out = {}
173
- cdef int num_diracs
174
- cdef int num_parameters
175
- num_diracs, num_parameters = pts.shape[:2]
176
- for i in range(num_diracs):
177
- key = tuple(pts[i]) # size cannot be known at compiletime
178
- out[key] = out.get(key,0)+ weights[i]
179
- num_keys = len(out)
180
- new_pts = np.fromiter(out.keys(), dtype=np.dtype((dtype,num_parameters)), count=num_keys)
181
- new_weights = np.fromiter(out.values(), dtype=np.int32, count=num_keys)
182
- idx = np.nonzero(new_weights)
183
- new_pts = new_pts[idx]
184
- new_weights = new_weights[idx]
185
- return (new_pts, new_weights)
186
-
187
- def clean_sms(sms):
188
- """
189
- Sum the diracs at the same locations. i.e.,
190
- returns the minimal sized measure to represent the input.
191
- Mostly useful for, e.g., euler_characteristic from simplical complexes.
192
- """
193
- return tuple(clean_signed_measure(pts,weights) for pts,weights in sms)
194
-
195
- def zero_out_sm(pts,weights, mass_default):
196
- """
197
- Zeros out the modules outside of \f$ \{ x\in \mathbb R^n \mid x \le \mathrm{mass_default}\}\f$.
198
- """
199
- from itertools import product
200
- # merge pts, weights
201
- PTS = np.concatenate([pts,weights[:,None]], axis=1)[None]
202
-
203
- num_diracs, num_parameters=pts.shape
204
-
205
- # corners of the square of dim num_parameters. shape : (num_corners,num_parameters)
206
- C = np.fromiter(product(*([[1,0]]*num_parameters)), dtype=np.dtype((np.bool_, num_parameters)),)
207
- Cw = 1-2*((~C).sum(axis=1)%2)
208
- # add 1 in the end to copy the shape
209
- C = np.concatenate([C, np.ones((C.shape[0],1))], axis=1).astype(np.bool_)
210
-
211
- # signs of each corner
212
- Cw = 1-2*((~C).sum(axis=1)%2)
213
-
214
- # something useless in the end to ensure that the shape is correct (as we added 1 in pts)
215
- mass_default = np.concatenate([mass_default,[np.nan]])
216
- mass_default = mass_default[None,None,:] ## num c, num_pts, num_params
217
- # each point `pt` becomes a corner of the square [`pt`, `mass_default`]
218
- new= np.where(C[:,None,:], PTS,mass_default).reshape(-1,(num_parameters+1))
219
- new_pts = new[:,:-1]
220
- new_masses = new[:,-1]*np.repeat(Cw,num_diracs)
221
- ## a bunch of stuff are in the same place, better clean it
222
- return clean_signed_measure(new_pts.astype(np.float64), new_masses.astype(np.int64), dtype=np.float64)
223
-
224
- def zero_out_sms(sms, mass_default):
225
- """
226
- Zeros out the modules outside of \f$ \{ x\in \mathbb R^n \mid x \le \mathrm{mass_default}\}\f$.
227
- """
228
- return tuple(zero_out_sm(pts,weights, mass_default) for pts,weights in sms)
229
-
230
- @cython.boundscheck(False)
231
- @cython.wraparound(False)
232
- def barcode_from_rank_sm(
233
- sm: tuple[np.ndarray, np.ndarray],
234
- basepoint: np.ndarray,
235
- direction: Optional[np.ndarray] = None,
236
- bool full = False,
237
- ):
238
- """
239
- Given a rank signed measure `sm` and a line with basepoint `basepoint` (1darray) and
240
- direction `direction` (1darray), projects the rank signed measure on the given line,
241
- and returns the associated estimated barcode.
242
- If full is True, the barcode is given as coordinates in R^{`num_parameters`} instead
243
- of coordinates w.r.t. the line.
244
- """
245
- basepoint = np.asarray(basepoint)
246
- num_parameters = basepoint.shape[0]
247
- x, w = sm
248
- assert (
249
- x.shape[1] // 2 == num_parameters
250
- ), f"Incoherent number of parameters. sm:{x.shape[1]//2} vs {num_parameters}"
251
- x, y = x[:, :num_parameters], x[:, num_parameters:]
252
- if direction is not None:
253
- direction = np.asarray(direction)
254
- ok_idx = direction > 0
255
- if ok_idx.sum() == 0:
256
- raise ValueError(f"Got invalid direction {direction}")
257
- zero_idx = None if np.all(ok_idx) else direction == 0
258
- else:
259
- direction = np.asarray([1], dtype=int)
260
- ok_idx = slice(None)
261
- zero_idx = None
262
- xa = np.max(
263
- (x[:, ok_idx] - basepoint[ok_idx]) / direction[ok_idx], axis=1, keepdims=1
264
- )
265
- ya = np.min(
266
- (y[:, ok_idx] - basepoint[ok_idx]) / direction[ok_idx], axis=1, keepdims=1
267
- )
268
- if zero_idx is not None:
269
- xb = np.where(x[:, zero_idx] <= basepoint[zero_idx], -np.inf, np.inf)
270
- yb = np.where(y[:, zero_idx] <= basepoint[zero_idx], -np.inf, np.inf)
271
- xs = np.max(np.concatenate([xa, xb], axis=1), axis=1, keepdims=1)
272
- ys = np.min(np.concatenate([ya, yb], axis=1), axis=1, keepdims=1)
273
- else:
274
- xs = xa
275
- ys = ya
276
- out = np.concatenate([xs, ys], axis=1,dtype=np.float64)
277
-
278
- ## TODO: check if this is faster than doing a np.repeat on x ?
279
- cdef dict[dict,tuple] d = {}
280
- cdef double[:,:] c_out = out # view
281
- cdef int64_t[:] c_w = np.asarray(w,dtype=np.int64)
282
- cdef int num_pts = out.shape[0]
283
- # for i, stuff in enumerate(out):
284
- # if stuff[0] < np.inf:
285
- # d[tuple(stuff)] = d.get(tuple(stuff), 0) + w[i]
286
- for i in range(num_pts):
287
- if c_out[i][0] < np.inf:
288
- machin = tuple(c_out[i])
289
- d[machin] = d.get(machin, 0) + c_w[i]
290
-
291
- out = np.fromiter(
292
- itertools.chain.from_iterable(([x] * w for x, w in d.items() if x[0] < x[1])),
293
- dtype=np.dtype((np.float64, 2)),
294
- )
295
- if full:
296
- out = basepoint[None, None] + out[..., None] * direction[None, None]
297
- return out
298
-
299
-
300
- def estimate_rank_from_rank_sm(sm:tuple, a, b) -> int:
301
- """
302
- Given a rank signed measure (sm) and two points (a) and (b),
303
- estimates the rank between these two points.
304
- """
305
- a = np.asarray(a)
306
- b = np.asarray(b)
307
- if not (a <= b).all():
308
- return 0
309
- x, w = sm
310
- num_parameters = x.shape[1] // 2
311
- assert (
312
- a.shape[0] == b.shape[0] == num_parameters
313
- ), f"Incoherent number of parameters. sm:{num_parameters} vs {a.shape[0]} and {b.shape[0]}"
314
- idx = (
315
- (x[:, :num_parameters] <= a[None]).all(1)
316
- * (x[:, num_parameters:] >= b[None]).all(1)
317
- ).ravel()
318
- return w[idx].sum()
319
-
320
-
1
+ # cimport multipers.tensor as mt
2
+ from libc.stdint cimport intptr_t, uint16_t, uint32_t, int32_t, int64_t
3
+ from libcpp.vector cimport vector
4
+ from libcpp cimport bool, int, float
5
+ import numpy as np
6
+ cimport numpy as cnp
7
+ import itertools
8
+ from typing import Optional,Iterable
9
+
10
+ from collections import defaultdict
11
+ cnp.import_array()
12
+ from scipy import sparse
13
+
14
+
15
+ import multipers.grids as mpg
16
+
17
+ ctypedef fused some_int:
18
+ int32_t
19
+ int64_t
20
+ int
21
+
22
+ ctypedef fused some_float:
23
+ int32_t
24
+ int64_t
25
+ int
26
+ float
27
+ double
28
+
29
+
30
+ import cython
31
+ cimport cython
32
+
33
+
34
+
35
+ # from scipy.sparse import coo_array
36
+ # from scipy.ndimage import convolve1d
37
+
38
+ def signed_betti(hilbert_function:np.ndarray, bool threshold=False):
39
+ cdef int n = hilbert_function.ndim
40
+ # zero out the "end" of the Hilbert function
41
+ if threshold:
42
+ for dimension in range(n):
43
+ slicer = tuple([slice(None) if i != dimension else -1 for i in range(n)])
44
+ hilbert_function[slicer] = 0
45
+ for i in range(n):
46
+ minus = tuple([slice(None) if j!=i else slice(0,-1) for j in range(n)])
47
+ plus = tuple([slice(None) if j!=i else slice(1,None) for j in range(n)])
48
+ hilbert_function[plus] -= hilbert_function[minus]
49
+ return hilbert_function
50
+
51
+ def rank_decomposition_by_rectangles(rank_invariant:np.ndarray, bool threshold=False):
52
+ # takes as input the rank invariant of an n-parameter persistence module
53
+ # M : [0, ..., s_1 - 1] x ... x [0, ..., s_n - 1] ---> Vec
54
+ # on a grid with dimensions of sizes s_1, ..., s_n. The input is assumed to be
55
+ # given as a tensor of dimensions (s_1, ..., s_n, s_1, ..., s_n), so that,
56
+ # at index [i_1, ..., i_n, j_1, ..., j_n] we have the rank of the structure
57
+ # map M(i) -> M(j), where i = (i_1, ..., i_n) and j = (j_1, ..., j_n), and
58
+ # i <= j, meaning that i_1 <= j_1, ..., i_n <= j_n.
59
+ # NOTE :
60
+ # - About the input, we assume that, if not( i <= j ), then at index
61
+ # [i_1, ..., i_n, j_1, ..., j_n] we have a zero.
62
+ # - Similarly, the output at index [i_1, ..., i_n, j_1, ..., j_n] only
63
+ # makes sense when i <= j. For indices where not( i <= j ) the output
64
+ # may take arbitrary values and they should be ignored.
65
+ cdef int n = rank_invariant.ndim // 2
66
+ if threshold:
67
+ # zero out the "end"
68
+ for dimension in range(n):
69
+ slicer = tuple(
70
+ [slice(None) for _ in range(n)]
71
+ + [slice(None) if i != dimension else -1 for i in range(n)]
72
+ )
73
+ rank_invariant[slicer] = 0
74
+ to_flip = tuple(range(n, 2 * n))
75
+ return np.flip(signed_betti(np.flip(rank_invariant, to_flip)), to_flip)
76
+
77
+
78
+
79
+
80
+
81
+
82
+
83
+ @cython.boundscheck(False)
84
+ @cython.wraparound(False)
85
+ def integrate_measure(
86
+ some_float[:,:] pts,
87
+ some_int[:] weights,
88
+ filtration_grid:Optional[Iterable[np.ndarray]]=None,
89
+ grid_strategy:str="regular",
90
+ resolution:int|list[int]=100,
91
+ bool return_grid=False,
92
+ **get_fitration_kwargs,
93
+ ):
94
+ """
95
+ Integrate a point measure on a grid.
96
+ Measure is a sum of diracs, based on points `pts` and weights `weights`.
97
+ For instance, if the signed measure comes from the hilbert signed measure,
98
+ this integration will return the hilbert function on this grid.
99
+ - pts : array of points (num_pts, D)
100
+ - weights : array of weights (num_pts,)
101
+ - filtration_grid (optional) : list of 1d arrays
102
+ - resolution : int or list of int
103
+ - return_grid : return the grid of the measure
104
+ - **get_fitration_kwargs : arguments to compute the grid,
105
+ if the grid is not given.
106
+ """
107
+ if filtration_grid is None:
108
+ import multipers.simplex_tree_multi
109
+ filtration_grid = mpg.compute_grid(
110
+ np.asarray(pts).T,
111
+ strategy=grid_strategy,
112
+ resolution=resolution,
113
+ **get_fitration_kwargs
114
+ )
115
+ resolution = np.asarray([len(f) for f in filtration_grid])
116
+ cdef int num_pts = pts.shape[0]
117
+ cdef int num_parameters = pts.shape[1]
118
+ assert weights.shape[0] == num_pts
119
+ out = np.zeros(shape=resolution, dtype=np.int32) ## dim cannot be known at compiletime
120
+ # cdef some_float[:] filtration_of_parameter
121
+ # cdef cnp.ndarray indices = np.zeros(shape=num_parameters, dtype=int)
122
+ #
123
+ pts_coords = np.empty((num_parameters, num_pts), dtype=np.int64)
124
+ for parameter in range(num_parameters):
125
+ pts_coords[parameter] = np.searchsorted(filtration_grid[parameter], pts[:,parameter])
126
+ for i in range(num_pts):
127
+ cone = tuple(slice(c,r) for r,c in zip(resolution,pts_coords[:,i]))
128
+ out[cone] += weights[i]
129
+ if return_grid:
130
+ return out,filtration_grid
131
+ return out
132
+
133
+ ## for benchmark purposes
134
+ def integrate_measure_python(pts, weights, filtrations):
135
+ resolution = tuple([len(f) for f in filtrations])
136
+ out = np.zeros(shape=resolution, dtype=pts.dtype)
137
+ num_pts = pts.shape[0]
138
+ num_parameters = pts.shape[1]
139
+ for i in range(num_pts): #this is slow.
140
+ indices = (filtrations[parameter]>=pts[i][parameter] for parameter in range(num_parameters))
141
+ out[np.ix_(*indices)] += weights[i]
142
+ return out
143
+
144
+
145
+ @cython.boundscheck(False)
146
+ @cython.wraparound(False)
147
+ def sparsify(x):
148
+ """
149
+ Given an arbitrary dimensional numpy array, returns (coordinates,data).
150
+ --
151
+ cost : scipy sparse + num_points*num_parameters^2 divisions
152
+ """
153
+ num_parameters = x.ndim
154
+ sx = sparse.coo_array(x.ravel())
155
+ idx = sx.col
156
+ data = sx.data
157
+ coords = np.empty((data.shape[0], num_parameters), dtype=np.int64)
158
+ for parameter in range(num_parameters-1,-1,-1):
159
+ idx,coord_of_parameter = np.divmod(idx, x.shape[parameter])
160
+ coords[:, parameter] = coord_of_parameter
161
+ return coords,data
162
+
163
+
164
+
165
+
166
+ @cython.boundscheck(False)
167
+ @cython.wraparound(False)
168
+ def clean_signed_measure(some_float[:,:] pts, some_int[:] weights, dtype = np.float32):
169
+ """
170
+ Sum the diracs at the same locations. i.e.,
171
+ returns the minimal sized measure to represent the input.
172
+ Mostly useful for, e.g., euler_characteristic from simplical complexes.
173
+ """
174
+ cdef dict[tuple, int] out = {}
175
+ cdef int num_diracs
176
+ cdef int num_parameters
177
+ num_diracs, num_parameters = pts.shape[:2]
178
+ for i in range(num_diracs):
179
+ key = tuple(pts[i]) # size cannot be known at compiletime
180
+ out[key] = out.get(key,0)+ weights[i]
181
+ num_keys = len(out)
182
+ new_pts = np.fromiter(out.keys(), dtype=np.dtype((dtype,num_parameters)), count=num_keys)
183
+ new_weights = np.fromiter(out.values(), dtype=np.int32, count=num_keys)
184
+ idx = np.nonzero(new_weights)
185
+ new_pts = new_pts[idx]
186
+ new_weights = new_weights[idx]
187
+ return (new_pts, new_weights)
188
+
189
+ def clean_sms(sms):
190
+ """
191
+ Sum the diracs at the same locations. i.e.,
192
+ returns the minimal sized measure to represent the input.
193
+ Mostly useful for, e.g., euler_characteristic from simplical complexes.
194
+ """
195
+ return tuple(clean_signed_measure(pts,weights) for pts,weights in sms)
196
+
197
+ def zero_out_sm(pts,weights, mass_default):
198
+ """
199
+ Zeros out the modules outside of \f$ \{ x\in \mathbb R^n \mid x \le \mathrm{mass_default}\}\f$.
200
+ """
201
+ from itertools import product
202
+ # merge pts, weights
203
+ PTS = np.concatenate([pts,weights[:,None]], axis=1)[None]
204
+
205
+ num_diracs, num_parameters=pts.shape
206
+
207
+ # corners of the square of dim num_parameters. shape : (num_corners,num_parameters)
208
+ C = np.fromiter(product(*([[1,0]]*num_parameters)), dtype=np.dtype((np.bool_, num_parameters)),)
209
+ Cw = 1-2*((~C).sum(axis=1)%2)
210
+ # add 1 in the end to copy the shape
211
+ C = np.concatenate([C, np.ones((C.shape[0],1))], axis=1).astype(np.bool_)
212
+
213
+ # signs of each corner
214
+ Cw = 1-2*((~C).sum(axis=1)%2)
215
+
216
+ # something useless in the end to ensure that the shape is correct (as we added 1 in pts)
217
+ mass_default = np.concatenate([mass_default,[np.nan]])
218
+ mass_default = mass_default[None,None,:] ## num c, num_pts, num_params
219
+ # each point `pt` becomes a corner of the square [`pt`, `mass_default`]
220
+ new= np.where(C[:,None,:], PTS,mass_default).reshape(-1,(num_parameters+1))
221
+ new_pts = new[:,:-1]
222
+ new_masses = new[:,-1]*np.repeat(Cw,num_diracs)
223
+ ## a bunch of stuff are in the same place, better clean it
224
+ return clean_signed_measure(new_pts.astype(np.float64), new_masses.astype(np.int64), dtype=np.float64)
225
+
226
+ def zero_out_sms(sms, mass_default):
227
+ """
228
+ Zeros out the modules outside of \f$ \{ x\in \mathbb R^n \mid x \le \mathrm{mass_default}\}\f$.
229
+ """
230
+ return tuple(zero_out_sm(pts,weights, mass_default) for pts,weights in sms)
231
+
232
+ @cython.boundscheck(False)
233
+ @cython.wraparound(False)
234
+ def barcode_from_rank_sm(
235
+ sm: tuple[np.ndarray, np.ndarray],
236
+ basepoint: np.ndarray,
237
+ direction: Optional[np.ndarray] = None,
238
+ bool full = False,
239
+ ):
240
+ """
241
+ Given a rank signed measure `sm` and a line with basepoint `basepoint` (1darray) and
242
+ direction `direction` (1darray), projects the rank signed measure on the given line,
243
+ and returns the associated estimated barcode.
244
+ If full is True, the barcode is given as coordinates in R^{`num_parameters`} instead
245
+ of coordinates w.r.t. the line.
246
+ """
247
+ basepoint = np.asarray(basepoint)
248
+ num_parameters = basepoint.shape[0]
249
+ x, w = sm
250
+ assert (
251
+ x.shape[1] // 2 == num_parameters
252
+ ), f"Incoherent number of parameters. sm:{x.shape[1]//2} vs {num_parameters}"
253
+ x, y = x[:, :num_parameters], x[:, num_parameters:]
254
+ if direction is not None:
255
+ direction = np.asarray(direction)
256
+ ok_idx = direction > 0
257
+ if ok_idx.sum() == 0:
258
+ raise ValueError(f"Got invalid direction {direction}")
259
+ zero_idx = None if np.all(ok_idx) else direction == 0
260
+ else:
261
+ direction = np.asarray([1], dtype=int)
262
+ ok_idx = slice(None)
263
+ zero_idx = None
264
+ xa = np.max(
265
+ (x[:, ok_idx] - basepoint[ok_idx]) / direction[ok_idx], axis=1, keepdims=1
266
+ )
267
+ ya = np.min(
268
+ (y[:, ok_idx] - basepoint[ok_idx]) / direction[ok_idx], axis=1, keepdims=1
269
+ )
270
+ if zero_idx is not None:
271
+ xb = np.where(x[:, zero_idx] <= basepoint[zero_idx], -np.inf, np.inf)
272
+ yb = np.where(y[:, zero_idx] <= basepoint[zero_idx], -np.inf, np.inf)
273
+ xs = np.max(np.concatenate([xa, xb], axis=1), axis=1, keepdims=1)
274
+ ys = np.min(np.concatenate([ya, yb], axis=1), axis=1, keepdims=1)
275
+ else:
276
+ xs = xa
277
+ ys = ya
278
+ out = np.concatenate([xs, ys], axis=1,dtype=np.float64)
279
+
280
+ ## TODO: check if this is faster than doing a np.repeat on x ?
281
+ cdef dict[dict,tuple] d = {}
282
+ cdef double[:,:] c_out = out # view
283
+ cdef int64_t[:] c_w = np.asarray(w,dtype=np.int64)
284
+ cdef int num_pts = out.shape[0]
285
+ # for i, stuff in enumerate(out):
286
+ # if stuff[0] < np.inf:
287
+ # d[tuple(stuff)] = d.get(tuple(stuff), 0) + w[i]
288
+ for i in range(num_pts):
289
+ if c_out[i][0] < np.inf:
290
+ machin = tuple(c_out[i])
291
+ d[machin] = d.get(machin, 0) + c_w[i]
292
+
293
+ out = np.fromiter(
294
+ itertools.chain.from_iterable(([x] * w for x, w in d.items() if x[0] < x[1])),
295
+ dtype=np.dtype((np.float64, 2)),
296
+ )
297
+ if full:
298
+ out = basepoint[None, None] + out[..., None] * direction[None, None]
299
+ return out
300
+
301
+
302
+ def estimate_rank_from_rank_sm(sm:tuple, a, b) -> int:
303
+ """
304
+ Given a rank signed measure (sm) and two points (a) and (b),
305
+ estimates the rank between these two points.
306
+ """
307
+ a = np.asarray(a)
308
+ b = np.asarray(b)
309
+ if not (a <= b).all():
310
+ return 0
311
+ x, w = sm
312
+ num_parameters = x.shape[1] // 2
313
+ assert (
314
+ a.shape[0] == b.shape[0] == num_parameters
315
+ ), f"Incoherent number of parameters. sm:{num_parameters} vs {a.shape[0]} and {b.shape[0]}"
316
+ idx = (
317
+ (x[:, :num_parameters] <= a[None]).all(1)
318
+ * (x[:, num_parameters:] >= b[None]).all(1)
319
+ ).ravel()
320
+ return w[idx].sum()
321
+
322
+