multipers 2.2.3__cp312-cp312-win_amd64.whl → 2.3.0__cp312-cp312-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.cp312-win_amd64.pyd +0 -0
  17. multipers/function_rips.pyx +105 -105
  18. multipers/grids.cp312-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.cp312-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.cp312-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.cp312-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.cp312-win_amd64.pyd +0 -0
  152. multipers/point_measure.pyx +322 -320
  153. multipers/simplex_tree_multi.cp312-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.cp312-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
multipers/io.pyx CHANGED
@@ -1,711 +1,711 @@
1
- import re
2
- from gudhi import SimplexTree
3
- import multipers.slicer as mps
4
- import gudhi as gd
5
- import numpy as np
6
- import os
7
- from shutil import which
8
- from libcpp cimport bool
9
- from typing import Optional, Literal
10
- from collections import defaultdict
11
- import itertools
12
- import threading
13
- import cython
14
- cimport cython
15
-
16
- # from multipers.filtration_conversions cimport *
17
- # from multipers.mma_structures cimport boundary_matrix,float,pair,vector,intptr_t
18
- # cimport numpy as cnp
19
-
20
- doc_soft_urls = {
21
- "mpfree":"https://bitbucket.org/mkerber/mpfree/",
22
- "multi_chunk":"",
23
- "function_delaunay":"https://bitbucket.org/mkerber/function_delaunay/",
24
- "2pac":"https://gitlab.com/flenzen/2pac",
25
- }
26
- doc_soft_easy_install = {
27
- "mpfree":f"""
28
- ```sh
29
- git clone {doc_soft_urls["mpfree"]}
30
- cd mpfree
31
- sudo cp mpfree /usr/bin/
32
- cd ..
33
- rm -rf mpfree
34
- ```
35
- """,
36
- "multi_chunk":f"""
37
- ```sh
38
- git clone {doc_soft_urls["multi_chunk"]}
39
- cd multi_chunk
40
- sudo cp multi_chunk /usr/bin/
41
- cd ..
42
- rm -rf multi_chunk
43
- ```
44
- """,
45
- "function_delaunay":f"""
46
- ```sh
47
- git clone {doc_soft_urls["function_delaunay"]}
48
- cd function_delaunay
49
- sudo cp main /usr/bin/function_delaunay
50
- cd ..
51
- rm -rf function_delaunay
52
- ```
53
- """,
54
- "2pac":f"""
55
- ```sh
56
- git clone {doc_soft_urls["2pac"]} 2pac
57
- cd 2pac && mkdir build && cd build
58
- cmake ..
59
- make
60
- sudo cp 2pac /usr/bin
61
- ```
62
- """,
63
- }
64
- doc_soft_urls = defaultdict(lambda:"<Unknown url>", doc_soft_urls)
65
- doc_soft_easy_install = defaultdict(lambda:"<Unknown>", doc_soft_easy_install)
66
-
67
- available_reduce_softs = Literal["mpfree","multi_chunk","2pac"]
68
-
69
-
70
- def _path_init(soft:str|os.PathLike):
71
- a = which(f"./{soft}")
72
- b = which(f"{soft}")
73
- if a:
74
- pathes[soft] = a
75
- elif b:
76
- pathes[soft] = b
77
-
78
- if pathes[soft] is not None:
79
- verbose_arg = "> /dev/null 2>&1"
80
- test = os.system(pathes[soft] + " --help " + verbose_arg)
81
- if test:
82
- from warnings import warn
83
- warn(f"""
84
- Found external software {soft} at {pathes[soft]}
85
- but may not behave well.
86
- """)
87
-
88
-
89
-
90
- cdef dict[str,str|None] pathes = {
91
- "mpfree":None,
92
- "2pac":None,
93
- "function_delaunay":None,
94
- "multi_chunk":None,
95
- }
96
-
97
- # mpfree_in_path:str|os.PathLike = "multipers_mpfree_input.scc"
98
- # mpfree_out_path:str|os.PathLike = "multipers_mpfree_output.scc"
99
- # twopac_in_path:str|os.PathLike = "multipers_twopac_input.scc"
100
- # twopac_out_path:str|os.PathLike = "multipers_twopac_output.scc"
101
- # multi_chunk_in_path:str|os.PathLike = "multipers_multi_chunk_input.scc"
102
- # multi_chunk_out_path:str|os.PathLike = "multipers_multi_chunk_output.scc"
103
- # function_delaunay_out_path:str|os.PathLike = "function_delaunay_output.scc"
104
- # function_delaunay_in_path:str|os.PathLike = "function_delaunay_input.txt" # point cloud
105
- input_path:str|os.PathLike = "multipers_input.scc"
106
- output_path:str|os.PathLike = "multipers_output.scc"
107
-
108
-
109
-
110
- ## TODO : optimize with Python.h ?
111
- def scc_parser(path: str| os.PathLike):
112
- """
113
- Parse an scc file into the scc python format, aka blocks.
114
- """
115
- pass_line_regex = re.compile(r"^\s*$|^#|^scc2020$")
116
- def valid_line(line):
117
- return pass_line_regex.match(line) is None
118
- parse_line_regex = re.compile(r"^(?P<filtration>[^;]+);(?P<boundary>[^;]*)$")
119
- cdef tuple[tuple[str,str]] clines
120
- with open(path, "r") as f:
121
- lines =(x.strip() for x in f if valid_line(x))
122
- num_parameters = int(next(lines))
123
- sizes = np.cumsum(np.asarray([0] + next(lines).split(), dtype=np.int32))
124
- lines = (parse_line_regex.match(a) for a in lines)
125
- clines = tuple((a.group("filtration"),a.group("boundary")) for a in lines)
126
- # F = np.fromiter((a[0].split() for a in clines), dtype=np.dtype((np.float64,2)), count = sizes[-1])
127
- F = np.fromiter((np.fromstring(a[0], sep=r' ', dtype=np.float64) for a in clines), dtype=np.dtype((np.float64,num_parameters)), count = sizes[-1])
128
-
129
- # B = tuple(np.asarray(a[1].split(), dtype=np.int32) if len(a[1])>0 else np.empty(0, dtype=np.int32) for a in clines) ## TODO : this is very slow : optimize
130
- B = tuple(np.fromstring(a[1], sep=' ', dtype=np.int32) for a in clines)
131
- # block_lines = (tuple(get_bf(x, num_parameters) for x in lines[sizes[i]:sizes[i+1]]) for i in range(len(sizes)-1))
132
-
133
- # blocks = [(np.asarray([x[0] for x in b if len(x)>0], dtype=float),tuple(x[1] for x in b)) for b in block_lines]
134
- blocks = [(F[sizes[i]:sizes[i+1]], B[sizes[i]:sizes[i+1]]) for i in range(len(sizes)-1)]
135
-
136
- return blocks
137
-
138
-
139
- def scc_parser__old(path: str):
140
- """
141
- Parse an scc file into the scc python format, aka blocks.
142
- """
143
- with open(path, "r") as f:
144
- lines = f.readlines()
145
- # Find scc2020
146
- while lines[0].strip() != "scc2020":
147
- lines = lines[1:]
148
- lines = lines[1:]
149
- # stripped scc2020 we can start
150
-
151
- def pass_line(line):
152
- return re.match(r"^\s*$|^#", line) is not None
153
-
154
- for i, line in enumerate(lines):
155
- line = line.strip()
156
- if pass_line(line):
157
- continue
158
- num_parameters = int(line)
159
- lines = lines[i + 1 :]
160
- break
161
-
162
- block_sizes = []
163
-
164
- for i, line in enumerate(lines):
165
- line = line.strip()
166
- if pass_line(line):
167
- continue
168
- block_sizes = tuple(int(i) for i in line.split(" "))
169
- lines = lines[i + 1 :]
170
- break
171
- blocks = []
172
- cdef int counter
173
- for block_size in block_sizes:
174
- counter = block_size
175
- block_filtrations = []
176
- block_boundaries = []
177
- for i, line in enumerate(lines):
178
- if counter == 0:
179
- lines = lines[i:]
180
- break
181
- line = line.strip()
182
- if pass_line(line):
183
- continue
184
- splitted_line = re.match(r"^(?P<floats>[^;]+);(?P<ints>[^;]*)$", line)
185
- filtrations = np.asarray(splitted_line.group("floats").split(), dtype=float)
186
- boundary = np.asarray(splitted_line.group("ints").split(), dtype=int)
187
- block_filtrations.append(filtrations)
188
- block_boundaries.append(boundary)
189
- # filtration_boundary = line.split(";")
190
- # if len(filtration_boundary) == 1:
191
- # # happens when last generators do not have a ";" in the end
192
- # filtration_boundary.append(" ")
193
- # filtration, boundary = filtration_boundary
194
- # block_filtrations.append(
195
- # tuple(float(x) for x in filtration.split(" ") if len(x) > 0)
196
- # )
197
- # block_boundaries.append(tuple(int(x) for x in boundary.split(" ") if len(x) > 0))
198
- counter -= 1
199
- blocks.append((np.asarray(block_filtrations, dtype=float), tuple(block_boundaries)))
200
-
201
- return blocks
202
-
203
-
204
-
205
- def _put_temp_files_to_ram():
206
- global input_path,output_path
207
- shm_memory = "/tmp/" # on unix, we can write in RAM instead of disk.
208
- if os.access(shm_memory, os.W_OK) and not input_path.startswith(shm_memory):
209
- input_path = shm_memory + input_path
210
- output_path = shm_memory + output_path
211
-
212
- def _init_external_softwares(requires=[]):
213
- global pathes
214
- cdef bool any = False
215
- for soft,soft_path in pathes.items():
216
- if soft_path is None:
217
- _path_init(soft)
218
- any = any or (soft in requires)
219
-
220
- if any:
221
- _put_temp_files_to_ram()
222
- for soft in requires:
223
- if pathes[soft] is None:
224
- global doc_soft_urls
225
- raise ValueError(f"""
226
- Did not found {soft}.
227
- Install it from {doc_soft_urls[soft]}, and put it in your current directory,
228
- or in you $PATH.
229
- For instance:
230
- {doc_soft_easy_install[soft]}
231
- """)
232
- def _check_available(soft:str):
233
- _init_external_softwares()
234
- return pathes.get(soft,None) is not None
235
-
236
-
237
- def scc_reduce_from_str(
238
- path:str|os.PathLike,
239
- bool full_resolution=True,
240
- int dimension: int | np.int64 = 1,
241
- bool clear: bool = True,
242
- id: Optional[str] = None, # For parallel stuff
243
- bool verbose:bool=False,
244
- backend:Literal["mpfree","multi_chunk","twopac"]="mpfree"
245
- ):
246
- """
247
- Computes a minimal presentation of the file in path,
248
- using mpfree.
249
-
250
- path:PathLike
251
- full_resolution: bool
252
- dimension: int, presentation dimension to consider
253
- clear: bool, removes temporary files if True
254
- id: str, temporary files are of this id, allowing for multiprocessing
255
- verbose: bool
256
- backend: "mpfree", "multi_chunk" or "2pac"
257
- """
258
- global pathes, input_path, output_path
259
- if pathes[backend] is None:
260
- _init_external_softwares(requires=[backend])
261
-
262
-
263
- resolution_str = "--resolution" if full_resolution else ""
264
- # print(mpfree_in_path + id, mpfree_out_path + id)
265
- if id is None:
266
- id = str(threading.get_native_id())
267
- if not os.path.exists(path):
268
- raise ValueError(f"No file found at {path}.")
269
- if os.path.exists(output_path + id):
270
- os.remove(output_path + id)
271
- verbose_arg = "> /dev/null 2>&1" if not verbose else ""
272
- if backend == "mpfree":
273
- more_verbose = "-v" if verbose else ""
274
- command = (
275
- f"{pathes[backend]} {more_verbose} {resolution_str} --dim={dimension} {path} {output_path+id} {verbose_arg}"
276
- )
277
- elif backend == "multi_chunk":
278
- command = (
279
- f"{pathes[backend]} {path} {output_path+id} {verbose_arg}"
280
- )
281
- elif backend in ["twopac", "2pac"]:
282
- command = (
283
- f"{pathes[backend]} -f {path} --scc-input -n{dimension} --save-resolution-scc {output_path+id} {verbose_arg}"
284
- )
285
- else:
286
- raise ValueError(f"Unsupported backend {backend}.")
287
- if verbose:
288
- print(f"Calling :\n\n {command}")
289
- os.system(command)
290
-
291
- blocks = scc_parser(output_path + id)
292
- if clear:
293
- clear_io(input_path+id, output_path + id)
294
-
295
-
296
- ## mpfree workaround: last size is 0 but shouldn't...
297
- if len(blocks) and not len(blocks[-1][1]):
298
- blocks=blocks[:-1]
299
-
300
- return blocks
301
-
302
- def scc_reduce_from_str_to_slicer(
303
- path:str|os.PathLike,
304
- slicer,
305
- bool full_resolution=True,
306
- int dimension: int | np.int64 = 1,
307
- bool clear: bool = True,
308
- id: Optional[str] = None, # For parallel stuff
309
- bool verbose:bool=False,
310
- backend:Literal["mpfree","multi_chunk","twopac"]="mpfree",
311
- shift_dimension=0
312
- ):
313
- """
314
- Computes a minimal presentation of the file in path,
315
- using mpfree.
316
-
317
- path:PathLike
318
- slicer: empty slicer to fill
319
- full_resolution: bool
320
- dimension: int, presentation dimension to consider
321
- clear: bool, removes temporary files if True
322
- id: str, temporary files are of this id, allowing for multiprocessing
323
- verbose: bool
324
- backend: "mpfree", "multi_chunk" or "2pac"
325
- """
326
- global pathes, input_path, output_path
327
- if pathes[backend] is None:
328
- _init_external_softwares(requires=[backend])
329
-
330
-
331
- resolution_str = "--resolution" if full_resolution else ""
332
- # print(mpfree_in_path + id, mpfree_out_path + id)
333
- if id is None:
334
- id = str(threading.get_native_id())
335
- if not os.path.exists(path):
336
- raise ValueError(f"No file found at {path}.")
337
- if os.path.exists(output_path + id):
338
- os.remove(output_path + id)
339
- verbose_arg = "> /dev/null 2>&1" if not verbose else ""
340
- if backend == "mpfree":
341
- more_verbose = "-v" if verbose else ""
342
- command = (
343
- f"{pathes[backend]} {more_verbose} {resolution_str} --dim={dimension} {path} {output_path+id} {verbose_arg}"
344
- )
345
- elif backend == "multi_chunk":
346
- command = (
347
- f"{pathes[backend]} {path} {output_path+id} {verbose_arg}"
348
- )
349
- elif backend in ["twopac", "2pac"]:
350
- command = (
351
- f"{pathes[backend]} -f {path} --scc-input -n{dimension} --save-resolution-scc {output_path+id} {verbose_arg}"
352
- )
353
- else:
354
- raise ValueError(f"Unsupported backend {backend}.")
355
- if verbose:
356
- print(f"Calling :\n\n {command}")
357
- os.system(command)
358
-
359
- slicer._build_from_scc_file(path=output_path+id, shift_dimension=shift_dimension)
360
-
361
- if clear:
362
- clear_io(input_path+id, output_path + id)
363
-
364
-
365
- def reduce_complex(
366
- complex, # Simplextree, Slicer, or str
367
- bool full_resolution: bool = True,
368
- int dimension: int | np.int64 = 1,
369
- bool clear: bool = True,
370
- id: Optional[str]=None, # For parallel stuff
371
- bool verbose:bool=False,
372
- backend:available_reduce_softs="mpfree"
373
- ):
374
- """
375
- Computes a minimal presentation of the file in path,
376
- using `backend`.
377
-
378
- simplextree
379
- full_resolution: bool
380
- dimension: int, presentation dimension to consider
381
- clear: bool, removes temporary files if True
382
- id: str, temporary files are of this id, allowing for multiprocessing
383
- verbose: bool
384
- """
385
-
386
- from multipers.simplex_tree_multi import is_simplextree_multi
387
- if id is None:
388
- id = str(threading.get_native_id())
389
- path = input_path+id
390
- if is_simplextree_multi(complex):
391
- complex.to_scc(
392
- path=path,
393
- rivet_compatible=False,
394
- strip_comments=False,
395
- ignore_last_generators=False,
396
- overwrite=True,
397
- reverse_block=False,
398
- )
399
- dimension = complex.dimension - dimension
400
- elif isinstance(complex,str):
401
- path = complex
402
- elif isinstance(complex, list) or isinstance(complex, tuple):
403
- scc2disk(complex,path=path)
404
- else:
405
- # Assumes its a slicer
406
- blocks = mps.slicer2blocks(complex)
407
- scc2disk(blocks,path=path)
408
- dimension = len(blocks) -2 -dimension
409
-
410
- return scc_reduce_from_str(
411
- path=path,
412
- full_resolution=full_resolution,
413
- dimension=dimension,
414
- clear=clear,
415
- id=id,
416
- verbose=verbose,
417
- backend=backend
418
- )
419
-
420
-
421
-
422
-
423
- def function_delaunay_presentation(
424
- point_cloud:np.ndarray,
425
- function_values:np.ndarray,
426
- id:Optional[str] = None,
427
- bool clear:bool = True,
428
- bool verbose:bool=False,
429
- int degree = -1,
430
- bool multi_chunk = False,
431
- ):
432
- """
433
- Computes a function delaunay presentation, and returns it as blocks.
434
-
435
- points : (num_pts, n) float array
436
- grades : (num_pts,) float array
437
- degree (opt) : if given, computes a minimal presentation of this homological degree first
438
- clear:bool, removes temporary files if true
439
- degree: computes minimal presentation of this degree if given
440
- verbose : bool
441
- """
442
- if id is None:
443
- id = str(threading.get_native_id())
444
- global input_path, output_path, pathes
445
- backend = "function_delaunay"
446
- if pathes[backend] is None :
447
- _init_external_softwares(requires=[backend])
448
-
449
- to_write = np.concatenate([point_cloud, function_values.reshape(-1,1)], axis=1)
450
- np.savetxt(input_path+id,to_write,delimiter=' ')
451
- verbose_arg = "> /dev/null 2>&1" if not verbose else ""
452
- degree_arg = f"--minpres {degree}" if degree >= 0 else ""
453
- multi_chunk_arg = "--multi-chunk" if multi_chunk else ""
454
- if os.path.exists(output_path + id):
455
- os.remove(output_path+ id)
456
- command = f"{pathes[backend]} {degree_arg} {multi_chunk_arg} {input_path+id} {output_path+id} {verbose_arg} --no-delaunay-compare"
457
- if verbose:
458
- print(command)
459
- os.system(command)
460
-
461
- blocks = scc_parser(output_path + id)
462
- if clear:
463
- clear_io(output_path + id, input_path + id)
464
- ## Function Delaunay workaround: last size is 0 but shouldn't...
465
- if degree<0 and len(blocks) and not len(blocks[-1][1]):
466
- blocks=blocks[:-1]
467
-
468
- return blocks
469
-
470
- def function_delaunay_presentation_to_slicer(
471
- slicer,
472
- point_cloud:np.ndarray,
473
- function_values:np.ndarray,
474
- id:Optional[str] = None,
475
- bool clear:bool = True,
476
- bool verbose:bool=False,
477
- int degree = -1,
478
- bool multi_chunk = False,
479
- ):
480
- """
481
- Computes a function delaunay presentation, and returns it as a slicer.
482
-
483
- slicer: empty slicer to fill
484
- points : (num_pts, n) float array
485
- grades : (num_pts,) float array
486
- degree (opt) : if given, computes a minimal presentation of this homological degree first
487
- clear:bool, removes temporary files if true
488
- degree: computes minimal presentation of this degree if given
489
- verbose : bool
490
- """
491
- if id is None:
492
- id = str(threading.get_native_id())
493
- global input_path, output_path, pathes
494
- backend = "function_delaunay"
495
- if pathes[backend] is None :
496
- _init_external_softwares(requires=[backend])
497
-
498
- to_write = np.concatenate([point_cloud, function_values.reshape(-1,1)], axis=1)
499
- np.savetxt(input_path+id,to_write,delimiter=' ')
500
- verbose_arg = "> /dev/null 2>&1" if not verbose else ""
501
- degree_arg = f"--minpres {degree}" if degree >= 0 else ""
502
- multi_chunk_arg = "--multi-chunk" if multi_chunk else ""
503
- if os.path.exists(output_path + id):
504
- os.remove(output_path+ id)
505
- command = f"{pathes[backend]} {degree_arg} {multi_chunk_arg} {input_path+id} {output_path+id} {verbose_arg} --no-delaunay-compare"
506
- if verbose:
507
- print(command)
508
- os.system(command)
509
-
510
- slicer._build_from_scc_file(path=output_path+id, shift_dimension=-1 if degree <= 0 else degree-1 )
511
-
512
- if clear:
513
- clear_io(output_path + id, input_path + id)
514
-
515
-
516
-
517
- def clear_io(*args):
518
- """Removes temporary files"""
519
- global input_path,output_path
520
- for x in [input_path,output_path] + list(args):
521
- if os.path.exists(x):
522
- os.remove(x)
523
-
524
-
525
-
526
-
527
-
528
-
529
- # cdef extern from "multiparameter_module_approximation/format_python-cpp.h" namespace "Gudhi::multiparameter::mma":
530
- # pair[boundary_matrix, vector[One_critical_filtration[double]]] simplextree_to_boundary_filtration(intptr_t)
531
- # vector[pair[ vector[vector[float]],boundary_matrix]] simplextree_to_scc(intptr_t)
532
- # vector[pair[ vector[vector[vector[float]]],boundary_matrix]] function_simplextree_to_scc(intptr_t)
533
- # pair[vector[vector[float]],boundary_matrix ] simplextree_to_ordered_bf(intptr_t)
534
-
535
- # def simplex_tree2boundary_filtrations(simplextree:SimplexTreeMulti | SimplexTree):
536
- # """Computes a (sparse) boundary matrix, with associated filtration. Can be used as an input of approx afterwards.
537
- #
538
- # Parameters
539
- # ----------
540
- # simplextree: Gudhi or mma simplextree
541
- # The simplextree defining the filtration to convert to boundary-filtration.
542
- #
543
- # Returns
544
- # -------
545
- # B:List of lists of ints
546
- # The boundary matrix.
547
- # F: List of 1D filtration
548
- # The filtrations aligned with B; the i-th simplex of this simplextree has boundary B[i] and filtration(s) F[i].
549
- #
550
- # """
551
- # cdef intptr_t cptr
552
- # if isinstance(simplextree, SimplexTreeMulti):
553
- # cptr = simplextree.thisptr
554
- # elif isinstance(simplextree, SimplexTree):
555
- # temp_st = gd.SimplexTreeMulti(simplextree, parameters=1)
556
- # cptr = temp_st.thisptr
557
- # else:
558
- # raise TypeError("Has to be a simplextree")
559
- # cdef pair[boundary_matrix, vector[One_critical_filtration[double]]] cboundary_filtration = simplextree_to_boundary_filtration(cptr)
560
- # boundary = cboundary_filtration.first
561
- # # multi_filtrations = np.array(<vector[vector[float]]>One_critical_filtration.to_python(cboundary_filtration.second))
562
- # cdef cnp.ndarray[double, ndim=2] multi_filtrations = _fmf2numpy_f64(cboundary_filtration.second)
563
- # return boundary, multi_filtrations
564
-
565
- # def simplextree2scc(simplextree:SimplexTreeMulti | SimplexTree, filtration_dtype=np.float32, bool flattened=False):
566
- # """
567
- # Turns a simplextree into a (simplicial) module presentation.
568
- # """
569
- # cdef intptr_t cptr
570
- # cdef bool is_function_st = False
571
- # if isinstance(simplextree, SimplexTreeMulti):
572
- # cptr = simplextree.thisptr
573
- # is_function_st = simplextree._is_function_simplextree
574
- # elif isinstance(simplextree, SimplexTree):
575
- # temp_st = gd.SimplexTreeMulti(simplextree, parameters=1)
576
- # cptr = temp_st.thisptr
577
- # else:
578
- # raise TypeError("Has to be a simplextree")
579
- #
580
- # cdef pair[vector[vector[float]], boundary_matrix] out
581
- # if flattened:
582
- # out = simplextree_to_ordered_bf(cptr)
583
- # return np.asarray(out.first,dtype=filtration_dtype), tuple(out.second)
584
- #
585
- # if is_function_st:
586
- # blocks = function_simplextree_to_scc(cptr)
587
- # else:
588
- # blocks = simplextree_to_scc(cptr)
589
- # # reduces the space in memory
590
- # if is_function_st:
591
- # blocks = [(tuple(f), tuple(b)) for f,b in blocks[::-1]]
592
- # else:
593
- # blocks = [(np.asarray(f,dtype=filtration_dtype), tuple(b)) for f,b in blocks[::-1]] ## presentation is on the other order
594
- # return blocks+[(np.empty(0,dtype=filtration_dtype),[])]
595
-
596
- @cython.boundscheck(False)
597
- @cython.wraparound(False)
598
- def scc2disk(
599
- stuff,
600
- path:str|os.PathLike,
601
- int num_parameters = -1,
602
- bool reverse_block = False,
603
- bool rivet_compatible = False,
604
- bool ignore_last_generators = False,
605
- bool strip_comments = False,
606
- ):
607
- """
608
- Writes a scc python format / blocks into a file.
609
- """
610
- if num_parameters == -1:
611
- for block in stuff:
612
- if len(block[0]) == 0:
613
- continue
614
- num_gens, num_parameters_= np.asarray(block[0]).shape
615
- num_parameters = num_parameters_
616
- break
617
- assert num_parameters > 0, f"Invalid number of parameters {num_parameters}"
618
-
619
- if reverse_block: stuff.reverse()
620
- with open(path, "w") as f:
621
- f.write("scc2020\n") if not rivet_compatible else f.write("firep\n")
622
- if not strip_comments and not rivet_compatible: f.write("# Number of parameters\n")
623
- if rivet_compatible:
624
- assert num_parameters == 2
625
- f.write("Filtration 1\n")
626
- f.write("Filtration 2\n")
627
- else:
628
- f.write(f"{num_parameters}\n")
629
-
630
- if not strip_comments: f.write("# Sizes of generating sets\n")
631
- for block in stuff: f.write(f"{len(block[0])} ")
632
- f.write("\n")
633
- for i,block in enumerate(stuff):
634
- if (rivet_compatible or ignore_last_generators) and i == len(stuff)-1: continue
635
- if not strip_comments: f.write(f"# Block of dimension {len(stuff)-1-i}\n")
636
- filtration, boundary = block
637
- filtration = np.asarray(filtration).astype(str)
638
- # boundary = tuple(x.astype(str) for x in boundary)
639
- f.write(" ".join(itertools.chain.from_iterable(
640
- ((*(f.tolist()), ";", *(np.asarray(b).astype(str).tolist()), "\n")
641
- for f,b in zip(filtration, boundary))
642
- )
643
- ))
644
- # for j in range(<int>len(filtration)):
645
- # line = " ".join((
646
- # *filtration[j],
647
- # ";",
648
- # *boundary[j],
649
- # "\n",
650
- # ))
651
- # f.write(line)
652
-
653
- def scc2disk_old(
654
- stuff,
655
- path:str|os.PathLike,
656
- num_parameters = -1,
657
- reverse_block = False,
658
- rivet_compatible = False,
659
- ignore_last_generators = False,
660
- strip_comments = False,
661
- ):
662
- """
663
- Writes a scc python format / blocks into a file.
664
- """
665
- if num_parameters == -1:
666
- for block in stuff:
667
- if len(block[0]) == 0:
668
- continue
669
- num_gens, num_parameters_= np.asarray(block[0]).shape
670
- num_parameters = num_parameters_
671
- break
672
- assert num_parameters > 0, f"Invalid number of parameters {num_parameters}"
673
-
674
- if reverse_block: stuff.reverse()
675
- out = []
676
- if rivet_compatible:
677
- out.append(r"firep")
678
- else:
679
- out.append(r"scc2020")
680
- if not strip_comments and not rivet_compatible:
681
- out.append(r"# Number of parameters")
682
- if rivet_compatible:
683
- out.append("Filtration 1")
684
- out.append("Filtration 2\n")
685
- else:
686
- out.append(f"{num_parameters}")
687
-
688
- if not strip_comments:
689
- out.append("# Sizes of generating sets")
690
-
691
- # for block in stuff:
692
- # f.write(f"{len(block[0])} ")
693
- out.append(" ".join(str(len(block[0])) for block in stuff))
694
- str_blocks = [out]
695
- for i,block in enumerate(stuff):
696
- if (rivet_compatible or ignore_last_generators) and i == len(stuff)-1: continue
697
- if not strip_comments:
698
- str_blocks.append([f"# Block of dimension {len(stuff)-1-i}"])
699
- filtration, boundary = block
700
- if len(filtration) == 0:
701
- continue
702
- filtration = filtration.astype(str)
703
- C = filtration[:,0]
704
- for i in range(1,filtration.shape[1]):
705
- C = np.char.add(C," ")
706
- C = np.char.add(C,filtration[:,i])
707
- C = np.char.add(C, ";")
708
- D = np.fromiter((" ".join(b.astype(str).tolist()) for b in boundary), dtype="<U11") #int32-> str is "<U11" #check np.array(1, dtype=np.int32).astype(str)
709
- str_blocks.append(np.char.add(C,D))
710
-
711
- np.savetxt("test.scc", np.concatenate(str_blocks), delimiter="", fmt="%s")
1
+ import re
2
+ from gudhi import SimplexTree
3
+ import gudhi as gd
4
+ import numpy as np
5
+ import os
6
+ from shutil import which
7
+ from libcpp cimport bool
8
+ from typing import Optional, Literal
9
+ from collections import defaultdict
10
+ import itertools
11
+ import threading
12
+ import cython
13
+ cimport cython
14
+
15
+ # from multipers.filtration_conversions cimport *
16
+ # from multipers.mma_structures cimport boundary_matrix,float,pair,vector,intptr_t
17
+ # cimport numpy as cnp
18
+
19
+ doc_soft_urls = {
20
+ "mpfree":"https://bitbucket.org/mkerber/mpfree/",
21
+ "multi_chunk":"",
22
+ "function_delaunay":"https://bitbucket.org/mkerber/function_delaunay/",
23
+ "2pac":"https://gitlab.com/flenzen/2pac",
24
+ }
25
+ doc_soft_easy_install = {
26
+ "mpfree":f"""
27
+ ```sh
28
+ git clone {doc_soft_urls["mpfree"]}
29
+ cd mpfree
30
+ sudo cp mpfree /usr/bin/
31
+ cd ..
32
+ rm -rf mpfree
33
+ ```
34
+ """,
35
+ "multi_chunk":f"""
36
+ ```sh
37
+ git clone {doc_soft_urls["multi_chunk"]}
38
+ cd multi_chunk
39
+ sudo cp multi_chunk /usr/bin/
40
+ cd ..
41
+ rm -rf multi_chunk
42
+ ```
43
+ """,
44
+ "function_delaunay":f"""
45
+ ```sh
46
+ git clone {doc_soft_urls["function_delaunay"]}
47
+ cd function_delaunay
48
+ sudo cp main /usr/bin/function_delaunay
49
+ cd ..
50
+ rm -rf function_delaunay
51
+ ```
52
+ """,
53
+ "2pac":f"""
54
+ ```sh
55
+ git clone {doc_soft_urls["2pac"]} 2pac
56
+ cd 2pac && mkdir build && cd build
57
+ cmake ..
58
+ make
59
+ sudo cp 2pac /usr/bin
60
+ ```
61
+ """,
62
+ }
63
+ doc_soft_urls = defaultdict(lambda:"<Unknown url>", doc_soft_urls)
64
+ doc_soft_easy_install = defaultdict(lambda:"<Unknown>", doc_soft_easy_install)
65
+
66
+ available_reduce_softs = Literal["mpfree","multi_chunk","2pac"]
67
+
68
+
69
+ def _path_init(soft:str|os.PathLike):
70
+ a = which(f"./{soft}")
71
+ b = which(f"{soft}")
72
+ if a:
73
+ pathes[soft] = a
74
+ elif b:
75
+ pathes[soft] = b
76
+
77
+ if pathes[soft] is not None:
78
+ verbose_arg = "> /dev/null 2>&1"
79
+ test = os.system(pathes[soft] + " --help " + verbose_arg)
80
+ if test:
81
+ from warnings import warn
82
+ warn(f"""
83
+ Found external software {soft} at {pathes[soft]}
84
+ but may not behave well.
85
+ """)
86
+
87
+
88
+
89
+ cdef dict[str,str|None] pathes = {
90
+ "mpfree":None,
91
+ "2pac":None,
92
+ "function_delaunay":None,
93
+ "multi_chunk":None,
94
+ }
95
+
96
+ # mpfree_in_path:str|os.PathLike = "multipers_mpfree_input.scc"
97
+ # mpfree_out_path:str|os.PathLike = "multipers_mpfree_output.scc"
98
+ # twopac_in_path:str|os.PathLike = "multipers_twopac_input.scc"
99
+ # twopac_out_path:str|os.PathLike = "multipers_twopac_output.scc"
100
+ # multi_chunk_in_path:str|os.PathLike = "multipers_multi_chunk_input.scc"
101
+ # multi_chunk_out_path:str|os.PathLike = "multipers_multi_chunk_output.scc"
102
+ # function_delaunay_out_path:str|os.PathLike = "function_delaunay_output.scc"
103
+ # function_delaunay_in_path:str|os.PathLike = "function_delaunay_input.txt" # point cloud
104
+ input_path:str|os.PathLike = "multipers_input.scc"
105
+ output_path:str|os.PathLike = "multipers_output.scc"
106
+
107
+
108
+
109
+ ## TODO : optimize with Python.h ?
110
+ def scc_parser(path: str| os.PathLike):
111
+ """
112
+ Parse an scc file into the scc python format, aka blocks.
113
+ """
114
+ pass_line_regex = re.compile(r"^\s*$|^#|^scc2020$")
115
+ def valid_line(line):
116
+ return pass_line_regex.match(line) is None
117
+ parse_line_regex = re.compile(r"^(?P<filtration>[^;]+);(?P<boundary>[^;]*)$")
118
+ cdef tuple[tuple[str,str]] clines
119
+ with open(path, "r") as f:
120
+ lines =(x.strip() for x in f if valid_line(x))
121
+ num_parameters = int(next(lines))
122
+ sizes = np.cumsum(np.asarray([0] + next(lines).split(), dtype=np.int32))
123
+ lines = (parse_line_regex.match(a) for a in lines)
124
+ clines = tuple((a.group("filtration"),a.group("boundary")) for a in lines)
125
+ # F = np.fromiter((a[0].split() for a in clines), dtype=np.dtype((np.float64,2)), count = sizes[-1])
126
+ F = np.fromiter((np.fromstring(a[0], sep=r' ', dtype=np.float64) for a in clines), dtype=np.dtype((np.float64,num_parameters)), count = sizes[-1])
127
+
128
+ # B = tuple(np.asarray(a[1].split(), dtype=np.int32) if len(a[1])>0 else np.empty(0, dtype=np.int32) for a in clines) ## TODO : this is very slow : optimize
129
+ B = tuple(np.fromstring(a[1], sep=' ', dtype=np.int32) for a in clines)
130
+ # block_lines = (tuple(get_bf(x, num_parameters) for x in lines[sizes[i]:sizes[i+1]]) for i in range(len(sizes)-1))
131
+
132
+ # blocks = [(np.asarray([x[0] for x in b if len(x)>0], dtype=float),tuple(x[1] for x in b)) for b in block_lines]
133
+ blocks = [(F[sizes[i]:sizes[i+1]], B[sizes[i]:sizes[i+1]]) for i in range(len(sizes)-1)]
134
+
135
+ return blocks
136
+
137
+
138
+ def scc_parser__old(path: str):
139
+ """
140
+ Parse an scc file into the scc python format, aka blocks.
141
+ """
142
+ with open(path, "r") as f:
143
+ lines = f.readlines()
144
+ # Find scc2020
145
+ while lines[0].strip() != "scc2020":
146
+ lines = lines[1:]
147
+ lines = lines[1:]
148
+ # stripped scc2020 we can start
149
+
150
+ def pass_line(line):
151
+ return re.match(r"^\s*$|^#", line) is not None
152
+
153
+ for i, line in enumerate(lines):
154
+ line = line.strip()
155
+ if pass_line(line):
156
+ continue
157
+ num_parameters = int(line)
158
+ lines = lines[i + 1 :]
159
+ break
160
+
161
+ block_sizes = []
162
+
163
+ for i, line in enumerate(lines):
164
+ line = line.strip()
165
+ if pass_line(line):
166
+ continue
167
+ block_sizes = tuple(int(i) for i in line.split(" "))
168
+ lines = lines[i + 1 :]
169
+ break
170
+ blocks = []
171
+ cdef int counter
172
+ for block_size in block_sizes:
173
+ counter = block_size
174
+ block_filtrations = []
175
+ block_boundaries = []
176
+ for i, line in enumerate(lines):
177
+ if counter == 0:
178
+ lines = lines[i:]
179
+ break
180
+ line = line.strip()
181
+ if pass_line(line):
182
+ continue
183
+ splitted_line = re.match(r"^(?P<floats>[^;]+);(?P<ints>[^;]*)$", line)
184
+ filtrations = np.asarray(splitted_line.group("floats").split(), dtype=float)
185
+ boundary = np.asarray(splitted_line.group("ints").split(), dtype=int)
186
+ block_filtrations.append(filtrations)
187
+ block_boundaries.append(boundary)
188
+ # filtration_boundary = line.split(";")
189
+ # if len(filtration_boundary) == 1:
190
+ # # happens when last generators do not have a ";" in the end
191
+ # filtration_boundary.append(" ")
192
+ # filtration, boundary = filtration_boundary
193
+ # block_filtrations.append(
194
+ # tuple(float(x) for x in filtration.split(" ") if len(x) > 0)
195
+ # )
196
+ # block_boundaries.append(tuple(int(x) for x in boundary.split(" ") if len(x) > 0))
197
+ counter -= 1
198
+ blocks.append((np.asarray(block_filtrations, dtype=float), tuple(block_boundaries)))
199
+
200
+ return blocks
201
+
202
+
203
+
204
+ def _put_temp_files_to_ram():
205
+ global input_path,output_path
206
+ shm_memory = "/tmp/" # on unix, we can write in RAM instead of disk.
207
+ if os.access(shm_memory, os.W_OK) and not input_path.startswith(shm_memory):
208
+ input_path = shm_memory + input_path
209
+ output_path = shm_memory + output_path
210
+
211
+ def _init_external_softwares(requires=[]):
212
+ global pathes
213
+ cdef bool any = False
214
+ for soft,soft_path in pathes.items():
215
+ if soft_path is None:
216
+ _path_init(soft)
217
+ any = any or (soft in requires)
218
+
219
+ if any:
220
+ _put_temp_files_to_ram()
221
+ for soft in requires:
222
+ if pathes[soft] is None:
223
+ global doc_soft_urls
224
+ raise ValueError(f"""
225
+ Did not found {soft}.
226
+ Install it from {doc_soft_urls[soft]}, and put it in your current directory,
227
+ or in you $PATH.
228
+ For instance:
229
+ {doc_soft_easy_install[soft]}
230
+ """)
231
+ def _check_available(soft:str):
232
+ _init_external_softwares()
233
+ return pathes.get(soft,None) is not None
234
+
235
+
236
+ def scc_reduce_from_str(
237
+ path:str|os.PathLike,
238
+ bool full_resolution=True,
239
+ int dimension: int | np.int64 = 1,
240
+ bool clear: bool = True,
241
+ id: Optional[str] = None, # For parallel stuff
242
+ bool verbose:bool=False,
243
+ backend:Literal["mpfree","multi_chunk","twopac"]="mpfree"
244
+ ):
245
+ """
246
+ Computes a minimal presentation of the file in path,
247
+ using mpfree.
248
+
249
+ path:PathLike
250
+ full_resolution: bool
251
+ dimension: int, presentation dimension to consider
252
+ clear: bool, removes temporary files if True
253
+ id: str, temporary files are of this id, allowing for multiprocessing
254
+ verbose: bool
255
+ backend: "mpfree", "multi_chunk" or "2pac"
256
+ """
257
+ global pathes, input_path, output_path
258
+ if pathes[backend] is None:
259
+ _init_external_softwares(requires=[backend])
260
+
261
+
262
+ resolution_str = "--resolution" if full_resolution else ""
263
+ # print(mpfree_in_path + id, mpfree_out_path + id)
264
+ if id is None:
265
+ id = str(threading.get_native_id())
266
+ if not os.path.exists(path):
267
+ raise ValueError(f"No file found at {path}.")
268
+ if os.path.exists(output_path + id):
269
+ os.remove(output_path + id)
270
+ verbose_arg = "> /dev/null 2>&1" if not verbose else ""
271
+ if backend == "mpfree":
272
+ more_verbose = "-v" if verbose else ""
273
+ command = (
274
+ f"{pathes[backend]} {more_verbose} {resolution_str} --dim={dimension} {path} {output_path+id} {verbose_arg}"
275
+ )
276
+ elif backend == "multi_chunk":
277
+ command = (
278
+ f"{pathes[backend]} {path} {output_path+id} {verbose_arg}"
279
+ )
280
+ elif backend in ["twopac", "2pac"]:
281
+ command = (
282
+ f"{pathes[backend]} -f {path} --scc-input -n{dimension} --save-resolution-scc {output_path+id} {verbose_arg}"
283
+ )
284
+ else:
285
+ raise ValueError(f"Unsupported backend {backend}.")
286
+ if verbose:
287
+ print(f"Calling :\n\n {command}")
288
+ os.system(command)
289
+
290
+ blocks = scc_parser(output_path + id)
291
+ if clear:
292
+ clear_io(input_path+id, output_path + id)
293
+
294
+
295
+ ## mpfree workaround: last size is 0 but shouldn't...
296
+ if len(blocks) and not len(blocks[-1][1]):
297
+ blocks=blocks[:-1]
298
+
299
+ return blocks
300
+
301
+ def scc_reduce_from_str_to_slicer(
302
+ path:str|os.PathLike,
303
+ slicer,
304
+ bool full_resolution=True,
305
+ int dimension: int | np.int64 = 1,
306
+ bool clear: bool = True,
307
+ id: Optional[str] = None, # For parallel stuff
308
+ bool verbose:bool=False,
309
+ backend:Literal["mpfree","multi_chunk","twopac"]="mpfree",
310
+ shift_dimension=0
311
+ ):
312
+ """
313
+ Computes a minimal presentation of the file in path,
314
+ using mpfree.
315
+
316
+ path:PathLike
317
+ slicer: empty slicer to fill
318
+ full_resolution: bool
319
+ dimension: int, presentation dimension to consider
320
+ clear: bool, removes temporary files if True
321
+ id: str, temporary files are of this id, allowing for multiprocessing
322
+ verbose: bool
323
+ backend: "mpfree", "multi_chunk" or "2pac"
324
+ """
325
+ global pathes, input_path, output_path
326
+ if pathes[backend] is None:
327
+ _init_external_softwares(requires=[backend])
328
+
329
+
330
+ resolution_str = "--resolution" if full_resolution else ""
331
+ # print(mpfree_in_path + id, mpfree_out_path + id)
332
+ if id is None:
333
+ id = str(threading.get_native_id())
334
+ if not os.path.exists(path):
335
+ raise ValueError(f"No file found at {path}.")
336
+ if os.path.exists(output_path + id):
337
+ os.remove(output_path + id)
338
+ verbose_arg = "> /dev/null 2>&1" if not verbose else ""
339
+ if backend == "mpfree":
340
+ more_verbose = "-v" if verbose else ""
341
+ command = (
342
+ f"{pathes[backend]} {more_verbose} {resolution_str} --dim={dimension} {path} {output_path+id} {verbose_arg}"
343
+ )
344
+ elif backend == "multi_chunk":
345
+ command = (
346
+ f"{pathes[backend]} {path} {output_path+id} {verbose_arg}"
347
+ )
348
+ elif backend in ["twopac", "2pac"]:
349
+ command = (
350
+ f"{pathes[backend]} -f {path} --scc-input -n{dimension} --save-resolution-scc {output_path+id} {verbose_arg}"
351
+ )
352
+ else:
353
+ raise ValueError(f"Unsupported backend {backend}.")
354
+ if verbose:
355
+ print(f"Calling :\n\n {command}")
356
+ os.system(command)
357
+
358
+ slicer._build_from_scc_file(path=output_path+id, shift_dimension=shift_dimension)
359
+
360
+ if clear:
361
+ clear_io(input_path+id, output_path + id)
362
+
363
+
364
+ def reduce_complex(
365
+ complex, # Simplextree, Slicer, or str
366
+ bool full_resolution: bool = True,
367
+ int dimension: int | np.int64 = 1,
368
+ bool clear: bool = True,
369
+ id: Optional[str]=None, # For parallel stuff
370
+ bool verbose:bool=False,
371
+ backend:available_reduce_softs="mpfree"
372
+ ):
373
+ """
374
+ Computes a minimal presentation of the file in path,
375
+ using `backend`.
376
+
377
+ simplextree
378
+ full_resolution: bool
379
+ dimension: int, presentation dimension to consider
380
+ clear: bool, removes temporary files if True
381
+ id: str, temporary files are of this id, allowing for multiprocessing
382
+ verbose: bool
383
+ """
384
+
385
+ from multipers.simplex_tree_multi import is_simplextree_multi
386
+ from multipers.slicer import slicer2blocks
387
+ if id is None:
388
+ id = str(threading.get_native_id())
389
+ path = input_path+id
390
+ if is_simplextree_multi(complex):
391
+ complex.to_scc(
392
+ path=path,
393
+ rivet_compatible=False,
394
+ strip_comments=False,
395
+ ignore_last_generators=False,
396
+ overwrite=True,
397
+ reverse_block=False,
398
+ )
399
+ dimension = complex.dimension - dimension
400
+ elif isinstance(complex,str):
401
+ path = complex
402
+ elif isinstance(complex, list) or isinstance(complex, tuple):
403
+ scc2disk(complex,path=path)
404
+ else:
405
+ # Assumes its a slicer
406
+ blocks = slicer2blocks(complex)
407
+ scc2disk(blocks,path=path)
408
+ dimension = len(blocks) -2 -dimension
409
+
410
+ return scc_reduce_from_str(
411
+ path=path,
412
+ full_resolution=full_resolution,
413
+ dimension=dimension,
414
+ clear=clear,
415
+ id=id,
416
+ verbose=verbose,
417
+ backend=backend
418
+ )
419
+
420
+
421
+
422
+
423
+ def function_delaunay_presentation(
424
+ point_cloud:np.ndarray,
425
+ function_values:np.ndarray,
426
+ id:Optional[str] = None,
427
+ bool clear:bool = True,
428
+ bool verbose:bool=False,
429
+ int degree = -1,
430
+ bool multi_chunk = False,
431
+ ):
432
+ """
433
+ Computes a function delaunay presentation, and returns it as blocks.
434
+
435
+ points : (num_pts, n) float array
436
+ grades : (num_pts,) float array
437
+ degree (opt) : if given, computes a minimal presentation of this homological degree first
438
+ clear:bool, removes temporary files if true
439
+ degree: computes minimal presentation of this degree if given
440
+ verbose : bool
441
+ """
442
+ if id is None:
443
+ id = str(threading.get_native_id())
444
+ global input_path, output_path, pathes
445
+ backend = "function_delaunay"
446
+ if pathes[backend] is None :
447
+ _init_external_softwares(requires=[backend])
448
+
449
+ to_write = np.concatenate([point_cloud, function_values.reshape(-1,1)], axis=1)
450
+ np.savetxt(input_path+id,to_write,delimiter=' ')
451
+ verbose_arg = "> /dev/null 2>&1" if not verbose else ""
452
+ degree_arg = f"--minpres {degree}" if degree >= 0 else ""
453
+ multi_chunk_arg = "--multi-chunk" if multi_chunk else ""
454
+ if os.path.exists(output_path + id):
455
+ os.remove(output_path+ id)
456
+ command = f"{pathes[backend]} {degree_arg} {multi_chunk_arg} {input_path+id} {output_path+id} {verbose_arg} --no-delaunay-compare"
457
+ if verbose:
458
+ print(command)
459
+ os.system(command)
460
+
461
+ blocks = scc_parser(output_path + id)
462
+ if clear:
463
+ clear_io(output_path + id, input_path + id)
464
+ ## Function Delaunay workaround: last size is 0 but shouldn't...
465
+ if degree<0 and len(blocks) and not len(blocks[-1][1]):
466
+ blocks=blocks[:-1]
467
+
468
+ return blocks
469
+
470
+ def function_delaunay_presentation_to_slicer(
471
+ slicer,
472
+ point_cloud:np.ndarray,
473
+ function_values:np.ndarray,
474
+ id:Optional[str] = None,
475
+ bool clear:bool = True,
476
+ bool verbose:bool=False,
477
+ int degree = -1,
478
+ bool multi_chunk = False,
479
+ ):
480
+ """
481
+ Computes a function delaunay presentation, and returns it as a slicer.
482
+
483
+ slicer: empty slicer to fill
484
+ points : (num_pts, n) float array
485
+ grades : (num_pts,) float array
486
+ degree (opt) : if given, computes a minimal presentation of this homological degree first
487
+ clear:bool, removes temporary files if true
488
+ degree: computes minimal presentation of this degree if given
489
+ verbose : bool
490
+ """
491
+ if id is None:
492
+ id = str(threading.get_native_id())
493
+ global input_path, output_path, pathes
494
+ backend = "function_delaunay"
495
+ if pathes[backend] is None :
496
+ _init_external_softwares(requires=[backend])
497
+
498
+ to_write = np.concatenate([point_cloud, function_values.reshape(-1,1)], axis=1)
499
+ np.savetxt(input_path+id,to_write,delimiter=' ')
500
+ verbose_arg = "> /dev/null 2>&1" if not verbose else ""
501
+ degree_arg = f"--minpres {degree}" if degree >= 0 else ""
502
+ multi_chunk_arg = "--multi-chunk" if multi_chunk else ""
503
+ if os.path.exists(output_path + id):
504
+ os.remove(output_path+ id)
505
+ command = f"{pathes[backend]} {degree_arg} {multi_chunk_arg} {input_path+id} {output_path+id} {verbose_arg} --no-delaunay-compare"
506
+ if verbose:
507
+ print(command)
508
+ os.system(command)
509
+
510
+ slicer._build_from_scc_file(path=output_path+id, shift_dimension=-1 if degree <= 0 else degree-1 )
511
+
512
+ if clear:
513
+ clear_io(output_path + id, input_path + id)
514
+
515
+
516
+
517
+ def clear_io(*args):
518
+ """Removes temporary files"""
519
+ global input_path,output_path
520
+ for x in [input_path,output_path] + list(args):
521
+ if os.path.exists(x):
522
+ os.remove(x)
523
+
524
+
525
+
526
+
527
+
528
+
529
+ # cdef extern from "multiparameter_module_approximation/format_python-cpp.h" namespace "Gudhi::multiparameter::mma":
530
+ # pair[boundary_matrix, vector[One_critical_filtration[double]]] simplextree_to_boundary_filtration(intptr_t)
531
+ # vector[pair[ vector[vector[float]],boundary_matrix]] simplextree_to_scc(intptr_t)
532
+ # vector[pair[ vector[vector[vector[float]]],boundary_matrix]] function_simplextree_to_scc(intptr_t)
533
+ # pair[vector[vector[float]],boundary_matrix ] simplextree_to_ordered_bf(intptr_t)
534
+
535
+ # def simplex_tree2boundary_filtrations(simplextree:SimplexTreeMulti | SimplexTree):
536
+ # """Computes a (sparse) boundary matrix, with associated filtration. Can be used as an input of approx afterwards.
537
+ #
538
+ # Parameters
539
+ # ----------
540
+ # simplextree: Gudhi or mma simplextree
541
+ # The simplextree defining the filtration to convert to boundary-filtration.
542
+ #
543
+ # Returns
544
+ # -------
545
+ # B:List of lists of ints
546
+ # The boundary matrix.
547
+ # F: List of 1D filtration
548
+ # The filtrations aligned with B; the i-th simplex of this simplextree has boundary B[i] and filtration(s) F[i].
549
+ #
550
+ # """
551
+ # cdef intptr_t cptr
552
+ # if isinstance(simplextree, SimplexTreeMulti):
553
+ # cptr = simplextree.thisptr
554
+ # elif isinstance(simplextree, SimplexTree):
555
+ # temp_st = gd.SimplexTreeMulti(simplextree, parameters=1)
556
+ # cptr = temp_st.thisptr
557
+ # else:
558
+ # raise TypeError("Has to be a simplextree")
559
+ # cdef pair[boundary_matrix, vector[One_critical_filtration[double]]] cboundary_filtration = simplextree_to_boundary_filtration(cptr)
560
+ # boundary = cboundary_filtration.first
561
+ # # multi_filtrations = np.array(<vector[vector[float]]>One_critical_filtration.to_python(cboundary_filtration.second))
562
+ # cdef cnp.ndarray[double, ndim=2] multi_filtrations = _fmf2numpy_f64(cboundary_filtration.second)
563
+ # return boundary, multi_filtrations
564
+
565
+ # def simplextree2scc(simplextree:SimplexTreeMulti | SimplexTree, filtration_dtype=np.float32, bool flattened=False):
566
+ # """
567
+ # Turns a simplextree into a (simplicial) module presentation.
568
+ # """
569
+ # cdef intptr_t cptr
570
+ # cdef bool is_function_st = False
571
+ # if isinstance(simplextree, SimplexTreeMulti):
572
+ # cptr = simplextree.thisptr
573
+ # is_function_st = simplextree._is_function_simplextree
574
+ # elif isinstance(simplextree, SimplexTree):
575
+ # temp_st = gd.SimplexTreeMulti(simplextree, parameters=1)
576
+ # cptr = temp_st.thisptr
577
+ # else:
578
+ # raise TypeError("Has to be a simplextree")
579
+ #
580
+ # cdef pair[vector[vector[float]], boundary_matrix] out
581
+ # if flattened:
582
+ # out = simplextree_to_ordered_bf(cptr)
583
+ # return np.asarray(out.first,dtype=filtration_dtype), tuple(out.second)
584
+ #
585
+ # if is_function_st:
586
+ # blocks = function_simplextree_to_scc(cptr)
587
+ # else:
588
+ # blocks = simplextree_to_scc(cptr)
589
+ # # reduces the space in memory
590
+ # if is_function_st:
591
+ # blocks = [(tuple(f), tuple(b)) for f,b in blocks[::-1]]
592
+ # else:
593
+ # blocks = [(np.asarray(f,dtype=filtration_dtype), tuple(b)) for f,b in blocks[::-1]] ## presentation is on the other order
594
+ # return blocks+[(np.empty(0,dtype=filtration_dtype),[])]
595
+
596
+ @cython.boundscheck(False)
597
+ @cython.wraparound(False)
598
+ def scc2disk(
599
+ stuff,
600
+ path:str|os.PathLike,
601
+ int num_parameters = -1,
602
+ bool reverse_block = False,
603
+ bool rivet_compatible = False,
604
+ bool ignore_last_generators = False,
605
+ bool strip_comments = False,
606
+ ):
607
+ """
608
+ Writes a scc python format / blocks into a file.
609
+ """
610
+ if num_parameters == -1:
611
+ for block in stuff:
612
+ if len(block[0]) == 0:
613
+ continue
614
+ num_gens, num_parameters_= np.asarray(block[0]).shape
615
+ num_parameters = num_parameters_
616
+ break
617
+ assert num_parameters > 0, f"Invalid number of parameters {num_parameters}"
618
+
619
+ if reverse_block: stuff.reverse()
620
+ with open(path, "w") as f:
621
+ f.write("scc2020\n") if not rivet_compatible else f.write("firep\n")
622
+ if not strip_comments and not rivet_compatible: f.write("# Number of parameters\n")
623
+ if rivet_compatible:
624
+ assert num_parameters == 2
625
+ f.write("Filtration 1\n")
626
+ f.write("Filtration 2\n")
627
+ else:
628
+ f.write(f"{num_parameters}\n")
629
+
630
+ if not strip_comments: f.write("# Sizes of generating sets\n")
631
+ for block in stuff: f.write(f"{len(block[0])} ")
632
+ f.write("\n")
633
+ for i,block in enumerate(stuff):
634
+ if (rivet_compatible or ignore_last_generators) and i == len(stuff)-1: continue
635
+ if not strip_comments: f.write(f"# Block of dimension {len(stuff)-1-i}\n")
636
+ filtration, boundary = block
637
+ filtration = np.asarray(filtration).astype(str)
638
+ # boundary = tuple(x.astype(str) for x in boundary)
639
+ f.write(" ".join(itertools.chain.from_iterable(
640
+ ((*(f.tolist()), ";", *(np.asarray(b).astype(str).tolist()), "\n")
641
+ for f,b in zip(filtration, boundary))
642
+ )
643
+ ))
644
+ # for j in range(<int>len(filtration)):
645
+ # line = " ".join((
646
+ # *filtration[j],
647
+ # ";",
648
+ # *boundary[j],
649
+ # "\n",
650
+ # ))
651
+ # f.write(line)
652
+
653
+ def scc2disk_old(
654
+ stuff,
655
+ path:str|os.PathLike,
656
+ num_parameters = -1,
657
+ reverse_block = False,
658
+ rivet_compatible = False,
659
+ ignore_last_generators = False,
660
+ strip_comments = False,
661
+ ):
662
+ """
663
+ Writes a scc python format / blocks into a file.
664
+ """
665
+ if num_parameters == -1:
666
+ for block in stuff:
667
+ if len(block[0]) == 0:
668
+ continue
669
+ num_gens, num_parameters_= np.asarray(block[0]).shape
670
+ num_parameters = num_parameters_
671
+ break
672
+ assert num_parameters > 0, f"Invalid number of parameters {num_parameters}"
673
+
674
+ if reverse_block: stuff.reverse()
675
+ out = []
676
+ if rivet_compatible:
677
+ out.append(r"firep")
678
+ else:
679
+ out.append(r"scc2020")
680
+ if not strip_comments and not rivet_compatible:
681
+ out.append(r"# Number of parameters")
682
+ if rivet_compatible:
683
+ out.append("Filtration 1")
684
+ out.append("Filtration 2\n")
685
+ else:
686
+ out.append(f"{num_parameters}")
687
+
688
+ if not strip_comments:
689
+ out.append("# Sizes of generating sets")
690
+
691
+ # for block in stuff:
692
+ # f.write(f"{len(block[0])} ")
693
+ out.append(" ".join(str(len(block[0])) for block in stuff))
694
+ str_blocks = [out]
695
+ for i,block in enumerate(stuff):
696
+ if (rivet_compatible or ignore_last_generators) and i == len(stuff)-1: continue
697
+ if not strip_comments:
698
+ str_blocks.append([f"# Block of dimension {len(stuff)-1-i}"])
699
+ filtration, boundary = block
700
+ if len(filtration) == 0:
701
+ continue
702
+ filtration = filtration.astype(str)
703
+ C = filtration[:,0]
704
+ for i in range(1,filtration.shape[1]):
705
+ C = np.char.add(C," ")
706
+ C = np.char.add(C,filtration[:,i])
707
+ C = np.char.add(C, ";")
708
+ D = np.fromiter((" ".join(b.astype(str).tolist()) for b in boundary), dtype="<U11") #int32-> str is "<U11" #check np.array(1, dtype=np.int32).astype(str)
709
+ str_blocks.append(np.char.add(C,D))
710
+
711
+ np.savetxt("test.scc", np.concatenate(str_blocks), delimiter="", fmt="%s")