dag-modelling 0.12__py3-none-any.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.
Files changed (136) hide show
  1. dag_modelling/__init__.py +0 -0
  2. dag_modelling/bundles/__init__.py +0 -0
  3. dag_modelling/bundles/file_reader.py +592 -0
  4. dag_modelling/bundles/load_array.py +100 -0
  5. dag_modelling/bundles/load_graph.py +163 -0
  6. dag_modelling/bundles/load_hist.py +169 -0
  7. dag_modelling/bundles/load_parameters.py +489 -0
  8. dag_modelling/bundles/load_record.py +135 -0
  9. dag_modelling/bundles/make_y_parameters_for_x.py +72 -0
  10. dag_modelling/core/__init__.py +2 -0
  11. dag_modelling/core/data_descriptor.py +125 -0
  12. dag_modelling/core/edges.py +278 -0
  13. dag_modelling/core/exception.py +115 -0
  14. dag_modelling/core/flags_descriptor.py +123 -0
  15. dag_modelling/core/graph.py +197 -0
  16. dag_modelling/core/graph_base.py +45 -0
  17. dag_modelling/core/input.py +285 -0
  18. dag_modelling/core/input_strategy.py +292 -0
  19. dag_modelling/core/iter.py +8 -0
  20. dag_modelling/core/iterators.py +44 -0
  21. dag_modelling/core/labels.py +693 -0
  22. dag_modelling/core/make_fcn.py +117 -0
  23. dag_modelling/core/meta_node.py +307 -0
  24. dag_modelling/core/node.py +712 -0
  25. dag_modelling/core/node_base.py +187 -0
  26. dag_modelling/core/output.py +533 -0
  27. dag_modelling/core/storage.py +903 -0
  28. dag_modelling/core/type_functions/__init__.py +41 -0
  29. dag_modelling/core/type_functions/axes_type_functions.py +289 -0
  30. dag_modelling/core/type_functions/copy_type_functions.py +101 -0
  31. dag_modelling/core/type_functions/input_type_functions.py +379 -0
  32. dag_modelling/core/type_functions/output_type_functions.py +46 -0
  33. dag_modelling/core/type_functions/tools_for_type_functions.py +62 -0
  34. dag_modelling/core/types.py +8 -0
  35. dag_modelling/export/__init__.py +0 -0
  36. dag_modelling/export/to_root.py +262 -0
  37. dag_modelling/lib/__init__.py +0 -0
  38. dag_modelling/lib/abstract/__init__.py +7 -0
  39. dag_modelling/lib/abstract/block_to_one_node.py +255 -0
  40. dag_modelling/lib/abstract/many_to_one_node.py +243 -0
  41. dag_modelling/lib/abstract/one_to_one_node.py +174 -0
  42. dag_modelling/lib/arithmetic.py +195 -0
  43. dag_modelling/lib/axis/__init__.py +5 -0
  44. dag_modelling/lib/axis/bin_center.py +47 -0
  45. dag_modelling/lib/axis/bin_width.py +48 -0
  46. dag_modelling/lib/axis/mesh_to_edges.py +48 -0
  47. dag_modelling/lib/calculus/__init__.py +3 -0
  48. dag_modelling/lib/calculus/jacobian.py +161 -0
  49. dag_modelling/lib/common/__init__.py +17 -0
  50. dag_modelling/lib/common/array.py +230 -0
  51. dag_modelling/lib/common/cache.py +20 -0
  52. dag_modelling/lib/common/concatenation.py +57 -0
  53. dag_modelling/lib/common/copy.py +14 -0
  54. dag_modelling/lib/common/dummy.py +13 -0
  55. dag_modelling/lib/common/proxy.py +26 -0
  56. dag_modelling/lib/common/view.py +77 -0
  57. dag_modelling/lib/common/view_concat.py +51 -0
  58. dag_modelling/lib/exponential.py +73 -0
  59. dag_modelling/lib/hist/__init__.py +15 -0
  60. dag_modelling/lib/hist/axis_distortion_matrix.py +196 -0
  61. dag_modelling/lib/hist/axis_distortion_matrix_linear.py +181 -0
  62. dag_modelling/lib/hist/axis_distortion_matrix_pointwise.py +373 -0
  63. dag_modelling/lib/hist/hist_smear_normal_matrix_b_c.py +125 -0
  64. dag_modelling/lib/hist/rebin.py +140 -0
  65. dag_modelling/lib/hist/rebin_matrix.py +211 -0
  66. dag_modelling/lib/integration/__init__.py +7 -0
  67. dag_modelling/lib/integration/integrator.py +149 -0
  68. dag_modelling/lib/integration/integrator_core.py +287 -0
  69. dag_modelling/lib/integration/integrator_sampler.py +296 -0
  70. dag_modelling/lib/interpolation/__init__.py +7 -0
  71. dag_modelling/lib/interpolation/interpolator.py +144 -0
  72. dag_modelling/lib/interpolation/interpolator_core.py +399 -0
  73. dag_modelling/lib/interpolation/segment_index.py +143 -0
  74. dag_modelling/lib/linalg/__init__.py +13 -0
  75. dag_modelling/lib/linalg/cholesky.py +57 -0
  76. dag_modelling/lib/linalg/linear_function.py +43 -0
  77. dag_modelling/lib/linalg/matrix_product_AB.py +82 -0
  78. dag_modelling/lib/linalg/matrix_product_DDt.py +38 -0
  79. dag_modelling/lib/linalg/matrix_product_DVDt.py +66 -0
  80. dag_modelling/lib/linalg/vector_matrix_product.py +104 -0
  81. dag_modelling/lib/normalization/__init__.py +5 -0
  82. dag_modelling/lib/normalization/normalize_matrix.py +85 -0
  83. dag_modelling/lib/normalization/renormalize_diag.py +121 -0
  84. dag_modelling/lib/parameters/__init__.py +3 -0
  85. dag_modelling/lib/parameters/par_array_input.py +64 -0
  86. dag_modelling/lib/physics/__init__.py +4 -0
  87. dag_modelling/lib/physics/energy_resolution.py +207 -0
  88. dag_modelling/lib/physics/energy_resolution_sigma_rel_a_b_c.py +95 -0
  89. dag_modelling/lib/statistics/__init__.py +21 -0
  90. dag_modelling/lib/statistics/chi2.py +170 -0
  91. dag_modelling/lib/statistics/cnp_stat.py +117 -0
  92. dag_modelling/lib/statistics/covariance_matrix_group.py +271 -0
  93. dag_modelling/lib/statistics/covmatrix_from_cormatrix.py +54 -0
  94. dag_modelling/lib/statistics/log_poisson_ratio.py +97 -0
  95. dag_modelling/lib/statistics/log_prod_diag.py +77 -0
  96. dag_modelling/lib/statistics/monte_carlo.py +471 -0
  97. dag_modelling/lib/statistics/normalize_correlated_vars.py +117 -0
  98. dag_modelling/lib/statistics/normalize_correlated_vars_two_ways.py +190 -0
  99. dag_modelling/lib/summation/__init__.py +15 -0
  100. dag_modelling/lib/summation/array_sum.py +32 -0
  101. dag_modelling/lib/summation/el_sum_sq.py +39 -0
  102. dag_modelling/lib/summation/partial_sums.py +74 -0
  103. dag_modelling/lib/summation/sum_mat_or_diag.py +88 -0
  104. dag_modelling/lib/summation/sum_sq.py +41 -0
  105. dag_modelling/lib/summation/weighted_sum.py +75 -0
  106. dag_modelling/lib/summation/weighted_sum_args.py +77 -0
  107. dag_modelling/lib/trigonometry.py +105 -0
  108. dag_modelling/parameters/__init__.py +9 -0
  109. dag_modelling/parameters/gaussian_parameter.py +140 -0
  110. dag_modelling/parameters/gaussian_parameters.py +296 -0
  111. dag_modelling/parameters/parameter.py +170 -0
  112. dag_modelling/parameters/parameters.py +219 -0
  113. dag_modelling/plot/__init__.py +0 -0
  114. dag_modelling/plot/graphviz.py +871 -0
  115. dag_modelling/plot/plot.py +763 -0
  116. dag_modelling/tools/__init__.py +0 -0
  117. dag_modelling/tools/formatter.py +74 -0
  118. dag_modelling/tools/iter.py +16 -0
  119. dag_modelling/tools/logger.py +62 -0
  120. dag_modelling/tools/profiling/__init__.py +18 -0
  121. dag_modelling/tools/profiling/count_calls_profiler.py +100 -0
  122. dag_modelling/tools/profiling/delay_node.py +41 -0
  123. dag_modelling/tools/profiling/fit_simulation_profiling.py +203 -0
  124. dag_modelling/tools/profiling/framework_profiler.py +142 -0
  125. dag_modelling/tools/profiling/memory_profiler.py +176 -0
  126. dag_modelling/tools/profiling/node_profiler.py +131 -0
  127. dag_modelling/tools/profiling/profiler.py +222 -0
  128. dag_modelling/tools/profiling/timer_profiler.py +198 -0
  129. dag_modelling/tools/profiling/utils.py +85 -0
  130. dag_modelling/tools/save_records.py +123 -0
  131. dag_modelling/tools/schema.py +180 -0
  132. dag_modelling-0.12.dist-info/METADATA +126 -0
  133. dag_modelling-0.12.dist-info/RECORD +136 -0
  134. dag_modelling-0.12.dist-info/WHEEL +5 -0
  135. dag_modelling-0.12.dist-info/licenses/LICENSE +21 -0
  136. dag_modelling-0.12.dist-info/top_level.txt +1 -0
File without changes
File without changes
@@ -0,0 +1,592 @@
1
+ from __future__ import annotations
2
+
3
+ from contextlib import suppress
4
+ from os import listdir
5
+ from pathlib import Path
6
+ from typing import TYPE_CHECKING
7
+
8
+ from numpy import concatenate, double, dtype, frombuffer, linspace, ndarray
9
+
10
+ from nested_mapping.tools.map import make_reorder_function
11
+ from nested_mapping.typing import properkey
12
+
13
+ from ..tools.logger import INFO1, INFO2, INFO3, logger
14
+
15
+ if TYPE_CHECKING:
16
+ from collections.abc import Generator, Sequence
17
+ from typing import Any
18
+
19
+ import ROOT
20
+ from numpy.typing import NDArray
21
+
22
+ from nested_mapping.typing import KeyLike, TupleKey
23
+
24
+ file_readers = {}
25
+
26
+ _log_float_format = ".3g"
27
+
28
+
29
+ class HistGetter:
30
+ __slots__ = ()
31
+
32
+ def __getitem__(self, names: tuple[str | Path, str]) -> tuple[NDArray, NDArray]:
33
+ file_name, object_name = names
34
+ fr = FileReader[file_name]
35
+ return fr.get_hist(object_name)
36
+
37
+
38
+ _HistGetter = HistGetter()
39
+
40
+
41
+ class GraphGetter:
42
+ __slots__ = ()
43
+
44
+ def __getitem__(self, names: tuple[str | Path, str]) -> tuple[NDArray, NDArray]:
45
+ file_name, object_name = names
46
+ fr = FileReader[file_name]
47
+ return fr.get_graph(object_name)
48
+
49
+
50
+ _GraphGetter = GraphGetter()
51
+
52
+
53
+ class ArrayGetter:
54
+ __slots__ = ()
55
+
56
+ def __getitem__(self, names: tuple[str | Path, str]) -> NDArray:
57
+ file_name, object_name = names
58
+ fr = FileReader[file_name]
59
+ return fr.get_array(object_name)
60
+
61
+
62
+ _ArrayGetter = ArrayGetter()
63
+
64
+
65
+ class RecordGetter:
66
+ __slots__ = ()
67
+
68
+ def __getitem__(self, names: tuple[str | Path, str]) -> NDArray | dict[str, NDArray]:
69
+ file_name, object_name = names
70
+ fr = FileReader[file_name]
71
+ return fr.get_record(object_name)
72
+
73
+
74
+ _RecordGetter = RecordGetter()
75
+
76
+
77
+ class FileReaderMeta(type):
78
+ """Metaclass for `FileReader` class, implementing `FileReader[file_name]`
79
+ method."""
80
+
81
+ _opened_files: dict[str, FileReader] = {}
82
+ _last_used_file: str = ""
83
+
84
+ def __init__(self, name: str, parents: tuple, args: dict) -> None:
85
+ """Register the file reader based on the `_extension`"""
86
+ super().__init__(name, parents, args)
87
+ try:
88
+ ext = args["_extension"]
89
+ except KeyError as e:
90
+ raise e
91
+ else:
92
+ file_readers[ext] = self
93
+
94
+ def __getitem__(self, file_name: str | Path) -> FileReader:
95
+ file_name_str = file_name if isinstance(file_name, str) else str(file_name)
96
+ try:
97
+ ret = self._opened_files[file_name_str]
98
+ action = "Use" if file_name_str != self._last_used_file else None
99
+ except KeyError:
100
+ ret = FileReader.open(file_name)
101
+ action = "Read"
102
+
103
+ if action:
104
+ logger.log(INFO1, f"{action}: {file_name_str}")
105
+
106
+ self._opened_files[file_name_str] = ret
107
+ self._last_used_file = file_name_str
108
+
109
+ return ret
110
+
111
+ def __enter__(self) -> None:
112
+ pass
113
+
114
+ def __exit__(self, *args, **kwargs) -> None:
115
+ self.release_files()
116
+
117
+ def release_files(self) -> None:
118
+ for k, v in list(self._opened_files.items()):
119
+ v._close()
120
+ del self._opened_files[k]
121
+
122
+ logger.log(INFO3, f"Close: {v._file_name!s}")
123
+
124
+ @property
125
+ def array(self) -> ArrayGetter:
126
+ return _ArrayGetter
127
+
128
+ @property
129
+ def graph(self) -> GraphGetter:
130
+ return _GraphGetter
131
+
132
+ @property
133
+ def hist(self) -> HistGetter:
134
+ return _HistGetter
135
+
136
+ @property
137
+ def record(self) -> RecordGetter:
138
+ return _RecordGetter
139
+
140
+
141
+ class FileReader(metaclass=FileReaderMeta):
142
+ _extension: str = ""
143
+ _file: Any = None
144
+ _file_name: Path = Path("")
145
+ _opened_files: dict[str, FileReader] = FileReaderMeta._opened_files
146
+ _read_objects: dict[str, Any]
147
+
148
+ def __init__(self, file_name: str | Path):
149
+ self._file_name = Path(file_name)
150
+ self._read_objects = {}
151
+
152
+ @classmethod
153
+ def open(cls, file_name: str | Path) -> FileReader:
154
+ file_path = file_name if isinstance(file_name, Path) else Path(file_name)
155
+ ext = file_path.suffix
156
+
157
+ try:
158
+ cls = file_readers[ext]
159
+ except KeyError as e:
160
+ raise ValueError(
161
+ f"Do not know how to load ext {ext}. Available file_readers:"
162
+ f" {', '.join(file_readers)}"
163
+ ) from e
164
+
165
+ try:
166
+ return cls(file_name)
167
+ except FileNotFoundError as e:
168
+ raise FileNotFoundError(f"Can not open file {file_name!s} (loader {ext})") from e
169
+
170
+ def _close(self) -> None:
171
+ self._read_objects = {}
172
+
173
+ def keys(self) -> tuple[str, ...]:
174
+ raise RuntimeError("not implemented method")
175
+
176
+ def _get_object_impl(self, object_name: str, **kwargs) -> Any:
177
+ raise RuntimeError("not implemented method")
178
+
179
+ def _get_object(self, object_name: str, **kwargs) -> Any:
180
+ object_name = object_name.replace(".", "_")
181
+ with suppress(KeyError):
182
+ return self._read_objects[object_name]
183
+
184
+ try:
185
+ self._read_objects[object_name] = (
186
+ object := self._get_object_impl(object_name, **kwargs)
187
+ )
188
+ return object
189
+ except KeyError as e:
190
+ raise KeyError(f"Can not read {object_name} from {self._file_name!s}") from e
191
+
192
+ def _get_graph(self, object_name: str) -> tuple[NDArray, NDArray]:
193
+ raise RuntimeError("not implemented method")
194
+
195
+ def get_graph(self, object_name: str) -> tuple[NDArray, NDArray]:
196
+ x, y = self._get_graph(object_name)
197
+ logger.log(
198
+ INFO2,
199
+ f"graph {object_name} ({len(y)}): x"
200
+ f" {x[0]:{_log_float_format}}→{x[-1]:{_log_float_format}},"
201
+ f" ymin={y.min():{_log_float_format}}, ymax={y.max():{_log_float_format}}",
202
+ )
203
+ return x, y
204
+
205
+ def _get_hist(self, object_name: str) -> tuple[NDArray, NDArray]:
206
+ raise RuntimeError("not implemented method")
207
+
208
+ def get_hist(self, object_name: str) -> tuple[NDArray, NDArray]:
209
+ x, y = self._get_hist(object_name)
210
+ logger.log(
211
+ INFO2,
212
+ f"hist {object_name} ({len(y)}): x"
213
+ f" {x[0]:{_log_float_format}}→{x[-1]:{_log_float_format}},"
214
+ f" min={y.min():{_log_float_format}}, max={y.max():{_log_float_format}},"
215
+ f" Σh={y.sum():{_log_float_format}}",
216
+ )
217
+ return x, y
218
+
219
+ def _get_array(self, object_name: str) -> NDArray:
220
+ raise RuntimeError("not implemented method")
221
+
222
+ def get_array(self, object_name: str) -> NDArray:
223
+ a = self._get_array(object_name)
224
+ logger.log(
225
+ INFO2,
226
+ f"array {object_name} {'x'.join(map(str,a.shape))}: min={a.min():{_log_float_format}},"
227
+ f" max={a.max():{_log_float_format}}, Σ={a.sum():{_log_float_format}}",
228
+ )
229
+ return a
230
+
231
+ def _get_record(self, object_name: str) -> NDArray | dict[str, NDArray]:
232
+ raise RuntimeError("not implemented method")
233
+
234
+ def get_record(self, object_name: str) -> NDArray | dict[str, NDArray]:
235
+ rec = self._get_record(object_name)
236
+
237
+ match rec:
238
+ case ndarray():
239
+ nrows = rec.shape[0]
240
+ columns = ", ".join(rec.dtype.names) if rec.dtype.names else "???"
241
+ case dict():
242
+ nrows = next(iter(rec.values())).shape[0]
243
+ columns = ", ".join(rec.keys())
244
+ case _:
245
+ nrows = -1
246
+ columns = "???"
247
+
248
+ logger.log(
249
+ INFO2,
250
+ f"record {object_name} ({nrows}):" f" {columns}",
251
+ )
252
+ return rec
253
+
254
+
255
+ class FileReaderArray(FileReader):
256
+ _extension: str = ""
257
+
258
+ def _get_graph(self, object_name: str) -> tuple[NDArray, NDArray]:
259
+ return self._get_xy(object_name)
260
+
261
+ def _get_hist(self, object_name: str) -> tuple[NDArray, NDArray]:
262
+ emin, emax, h = self._get_emin_emax_h(object_name)
263
+ if not (emin[1:] == emax[:-1]).all():
264
+ raise ValueError("Inconsistent histogram edges")
265
+ edges = concatenate((emin, emax[-1:]))
266
+ return edges, h
267
+
268
+ def _get_array(self, object_name: str) -> NDArray:
269
+ return self._get_object(object_name)
270
+
271
+ def _get_record(self, object_name: str) -> NDArray:
272
+ return self._get_object(object_name)
273
+
274
+ def _get_xy(self, object_name: str) -> tuple[NDArray, NDArray]:
275
+ data = self._get_object(object_name)
276
+ cols = data.dtype.names
277
+ return data[cols[0]], data[cols[1]]
278
+
279
+ def _get_emin_emax_h(self, object_name: str) -> tuple[NDArray, NDArray, NDArray]:
280
+ data = self._get_object(object_name)
281
+ cols = data.dtype.names
282
+ return data[cols[0]], data[cols[1]], data[cols[2]]
283
+
284
+
285
+ class FileReaderNPZ(FileReaderArray):
286
+ _extension: str = ".npz"
287
+
288
+ def __init__(self, file_name: str | Path) -> None:
289
+ super().__init__(file_name)
290
+ from numpy import load
291
+
292
+ self._file = load(self._file_name, allow_pickle=True)
293
+
294
+ def _close(self) -> None:
295
+ super()._close()
296
+ del self._file
297
+
298
+ def _get_object_impl(self, object_name: str, **kwargs) -> Any:
299
+ assert not kwargs
300
+ return self._file[object_name]
301
+
302
+ def keys(self) -> tuple[str, ...]:
303
+ return tuple(self._file.keys())
304
+
305
+
306
+ class FileReaderHDF5(FileReaderArray):
307
+ _extension: str = ".hdf5"
308
+
309
+ def __init__(self, file_name: str | Path) -> None:
310
+ super().__init__(file_name)
311
+ from h5py import File
312
+
313
+ self._file = File(self._file_name, "r")
314
+
315
+ def _close(self) -> None:
316
+ super()._close()
317
+ self._file.close()
318
+
319
+ def _get_object_impl(self, object_name: str, **kwargs) -> Any:
320
+ assert not kwargs
321
+ return self._file[object_name]
322
+
323
+ def _get_array(self, object_name: str) -> NDArray:
324
+ ret = self._get_object(object_name)
325
+ return ret[:]
326
+
327
+ def keys(self) -> tuple[str, ...]:
328
+ return tuple(self._file.keys())
329
+
330
+
331
+ class FileReaderTSV(FileReaderArray):
332
+ _extension: str = ".tsv"
333
+
334
+ def __init__(self, file_name: str | Path) -> None:
335
+ super().__init__(file_name)
336
+
337
+ def _get_filenames(self, object_name: str) -> tuple[str, ...]:
338
+ uncompressed = (
339
+ str(self._file_name / f"{object_name}{self._extension}"),
340
+ f"{self._file_name.parent/self._file_name.stem}_{object_name}{self._extension}",
341
+ str(self._file_name / f"{self._file_name.stem}_{object_name}{self._extension}"),
342
+ )
343
+
344
+ return uncompressed + tuple(f"{fname}.bz2" for fname in uncompressed)
345
+
346
+ def _get_object_impl(self, object_name: str, return_record: bool = True) -> Any:
347
+ filenames = self._get_filenames(object_name)
348
+
349
+ if return_record:
350
+ from pandas import read_table
351
+
352
+ for filename in filenames:
353
+ with suppress(FileNotFoundError):
354
+ df = read_table(filename, comment="#", sep=None, engine="python")
355
+ logger.log(INFO1, f"Read: {filename}")
356
+ return df.to_records(index=False)
357
+ else:
358
+ from numpy import loadtxt
359
+
360
+ for filename in filenames:
361
+ with suppress(FileNotFoundError):
362
+ ret = loadtxt(filename)
363
+ logger.log(INFO1, f"Read: {filename}")
364
+ return ret
365
+
366
+ raise FileNotFoundError(", ".join(map(str, filenames)))
367
+
368
+ def _get_array(self, object_name: str) -> NDArray:
369
+ return self._get_object(object_name, return_record=False)
370
+
371
+ def keys(self) -> tuple[str, ...]:
372
+ return tuple(file for file in listdir(self._file_name) if file.endswith(self._extension))
373
+
374
+
375
+ class FileReaderROOTUpROOT(FileReader):
376
+ _extension: str = ".root"
377
+
378
+ def __init__(self, file_name: str | Path) -> None:
379
+ super().__init__(file_name)
380
+ from uproot import open
381
+
382
+ self._file = open(file_name)
383
+
384
+ def _close(self) -> None:
385
+ super()._close()
386
+ self._file.close()
387
+
388
+ def _get_object_impl(self, object_name: str, **kwargs) -> Any:
389
+ assert not kwargs
390
+ return self._file[object_name]
391
+
392
+ def _get_hist(self, object_name: str) -> tuple[NDArray, NDArray]:
393
+ obj = self._get_object(object_name)
394
+ y, x = obj.to_numpy()
395
+ return x, y
396
+
397
+ def _get_graph(self, object_name: str) -> tuple[NDArray, NDArray]:
398
+ obj = self._get_object(object_name)
399
+ y, x = obj.to_numpy()
400
+ return x[:-1], y
401
+
402
+ def _get_array(self, object_name: str) -> NDArray:
403
+ obj = self._get_object(object_name)
404
+ y, _ = obj.to_numpy()
405
+ return y
406
+
407
+ def _get_record(self, object_name: str) -> dict[str, NDArray]:
408
+ tree = self._get_object(object_name)
409
+ return {key: tree[key].array().to_numpy().copy() for key in tree.keys()}
410
+
411
+ def keys(self) -> tuple[str, ...]:
412
+ return tuple(key.split(";", 1)[0] for key in self._file.GetListOfKeys())
413
+
414
+
415
+ with suppress(ImportError):
416
+ import ROOT
417
+
418
+ class FileReaderROOTROOT(FileReader):
419
+ _extension: str = ".root"
420
+ _reader_uproot: FileReaderROOTUpROOT | None = None
421
+
422
+ def __init__(self, file_name: str | Path) -> None:
423
+ super().__init__(file_name)
424
+ from ROOT import TFile
425
+
426
+ self._file = TFile(file_name)
427
+ if self._file.IsZombie():
428
+ raise FileNotFoundError(file_name)
429
+
430
+ @property
431
+ def reader_uproot(self) -> FileReaderROOTUpROOT:
432
+ if self._reader_uproot is None:
433
+ self._reader_uproot = FileReaderROOTUpROOT(self._file_name)
434
+
435
+ return self._reader_uproot
436
+
437
+ def _close(self) -> None:
438
+ super()._close()
439
+ self._file.Close()
440
+
441
+ if self._reader_uproot is not None:
442
+ self._reader_uproot._close()
443
+
444
+ def _get_object_impl(self, object_name: str, **kwargs) -> Any:
445
+ assert not kwargs
446
+ ret = self._file.Get(object_name)
447
+ if not ret:
448
+ raise KeyError(object_name)
449
+ return ret
450
+
451
+ def _get_hist(self, object_name: str) -> tuple[NDArray, NDArray]:
452
+ import ROOT
453
+
454
+ obj = self._get_object(object_name)
455
+ if isinstance(obj, ROOT.TH1) and obj.GetDimension() == 1:
456
+ return _get_bin_edges(obj.GetXaxis()), _get_buffer_hist1(obj, flows=False)
457
+
458
+ raise ValueError(f"Do not know ho to convert {obj} to hist")
459
+
460
+ def _get_graph(self, object_name: str) -> tuple[NDArray, NDArray]:
461
+ import ROOT
462
+
463
+ obj = self._get_object(object_name)
464
+ if isinstance(obj, ROOT.TH1) and obj.GetDimension() == 1:
465
+ return _get_bin_left_edges(obj.GetXaxis()), _get_buffer_hist1(obj, flows=False)
466
+ if isinstance(obj, ROOT.TGraph):
467
+ return _get_buffers_graph(obj)
468
+
469
+ raise ValueError(f"Do not know ho to convert {obj} to graph")
470
+
471
+ def _get_array(self, object_name: str) -> NDArray:
472
+ import ROOT
473
+
474
+ obj = self._get_object(object_name)
475
+
476
+ if isinstance(obj, ROOT.TH1) and obj.GetDimension() == 1:
477
+ return _get_buffer_hist1(obj)
478
+ if isinstance(obj, ROOT.TH2) and obj.GetDimension() == 2:
479
+ return _get_buffer_hist2(obj)
480
+ if isinstance(obj, (ROOT.TMatrixD, ROOT.TMatrixF)):
481
+ return _get_buffer_matrix(obj)
482
+
483
+ raise ValueError(f"Do not know ho to convert {obj} to array")
484
+
485
+ def _get_record(self, object_name: str) -> dict[str, NDArray]:
486
+ return self.reader_uproot.get_record(object_name)
487
+
488
+ def keys(self) -> tuple[str, ...]:
489
+ return tuple(key.GetName().split(";", 1)[0] for key in self._file.GetListOfKeys())
490
+
491
+
492
+ def iterate_filenames(
493
+ filenames: Sequence[str | Path], keys: Sequence[KeyLike]
494
+ ) -> Generator[tuple[TupleKey, str | Path], None, None]:
495
+ for keylike in keys:
496
+ key = properkey(keylike)
497
+ for afilename in filenames:
498
+ filename = str(afilename)
499
+ if "{" in filename:
500
+ ffilename = filename.format(*key)
501
+ yield key, ffilename
502
+ break
503
+ elif all(map(filename.__contains__, key)):
504
+ yield key, afilename
505
+ break
506
+ else:
507
+ raise RuntimeError(f"Could not find a file for key {'.'.join(key)}")
508
+
509
+
510
+ def iterate_filenames_and_objectnames(
511
+ filenames: Sequence[str | Path],
512
+ filename_keys: Sequence[KeyLike],
513
+ keys: Sequence[KeyLike],
514
+ *,
515
+ skip: Sequence[set[str]] | None = None,
516
+ key_order: Sequence[int] | None = None,
517
+ ) -> Generator[tuple[TupleKey, str | Path, TupleKey, TupleKey], None, None]:
518
+ reorder_key = make_reorder_function(key_order)
519
+ for filekey, filename in iterate_filenames(filenames, filename_keys):
520
+ for key in keys:
521
+ key = properkey(key)
522
+ fullkey = filekey + key
523
+ if skip is not None and any(skipkey.issubset(fullkey) for skipkey in skip):
524
+ continue
525
+ fullkey = reorder_key(fullkey)
526
+ yield filekey, filename, key, fullkey
527
+
528
+
529
+ def _get_buffer_hist1(h: ROOT.TH1, flows: bool = False) -> NDArray:
530
+ """Return TH1* histogram data buffer if flows=False, exclude underflow and
531
+ overflow."""
532
+ buf = h.GetArray()
533
+ buf = frombuffer(buf, dtype(buf.typecode), h.GetNbinsX() + 2)
534
+ if not flows:
535
+ buf = buf[1:-1]
536
+
537
+ return buf.copy()
538
+
539
+
540
+ def _get_buffer_hist2(h, flows=False):
541
+ """Return histogram data buffer
542
+ if flows=False, exclude underflow and overflow
543
+ NOTE: buf[biny][binx] is the right access signature
544
+ """
545
+ nx, ny = h.GetNbinsX(), h.GetNbinsY()
546
+ buf = h.GetArray()
547
+ res = frombuffer(buf, dtype(buf.typecode), (nx + 2) * (ny + 2)).reshape((ny + 2, nx + 2))
548
+ if not flows:
549
+ res = res[1 : ny + 1, 1 : nx + 1]
550
+
551
+ return res.copy()
552
+
553
+
554
+ def _get_bin_edges(ax: ROOT.TAxis) -> NDArray:
555
+ """Get the array with bin edges."""
556
+ xbins = ax.GetXbins()
557
+ n = xbins.GetSize()
558
+ if n > 0:
559
+ lims = frombuffer(xbins.GetArray(), double, n)
560
+ return lims.copy()
561
+ return linspace(ax.GetXmin(), ax.GetXmax(), ax.GetNbins() + 1)
562
+
563
+
564
+ def _get_bin_left_edges(ax: ROOT.TAxis) -> NDArray:
565
+ """Get the array with bin left edges."""
566
+ xbins = ax.GetXbins()
567
+ n = xbins.GetSize()
568
+ if n > 0:
569
+ lims = frombuffer(xbins.GetArray(), double, n)
570
+ return lims[:-1].copy()
571
+ return linspace(ax.GetXmin(), ax.GetXmax(), ax.GetNbins() + 1)[:-1]
572
+
573
+
574
+ def _get_buffers_graph(g: ROOT.TGraph) -> tuple[NDArray, NDArray]:
575
+ """Get TGraph x and y buffers."""
576
+ npoints = g.GetN()
577
+ if npoints == 0:
578
+ raise RuntimeError("Got graph with 0 points")
579
+
580
+ return (
581
+ frombuffer(g.GetX(), dtype=double, count=npoints).copy(),
582
+ frombuffer(g.GetY(), dtype=double, count=npoints).copy(),
583
+ )
584
+
585
+
586
+ def _get_buffer_matrix(m):
587
+ """Get TMatrix buffer."""
588
+ cbuf = m.GetMatrixArray()
589
+ res = frombuffer(cbuf, dtype(cbuf.typecode), m.GetNoElements()).reshape(
590
+ m.GetNrows(), m.GetNcols()
591
+ )
592
+ return res.astype(double).copy()
@@ -0,0 +1,100 @@
1
+ from collections.abc import Callable, Mapping
2
+ from pathlib import Path
3
+
4
+ from numpy import asarray
5
+ from schema import And, Optional, Or, Schema, Use
6
+
7
+ from nested_mapping.tools.map import make_reorder_function
8
+ from nested_mapping.typing import strkey
9
+
10
+ from ..core.storage import NodeStorage
11
+ from ..lib.common import Array
12
+ from ..tools.logger import INFO3, logger
13
+ from ..tools.schema import (
14
+ AllFileswithExt,
15
+ IsFilenameSeqOrFilename,
16
+ IsStrSeqOrStr,
17
+ LoadFileWithExt,
18
+ LoadYaml,
19
+ )
20
+ from .file_reader import FileReader, file_readers, iterate_filenames_and_objectnames
21
+
22
+ _schema_cfg = Schema(
23
+ {
24
+ "name": str,
25
+ "filenames": And(IsFilenameSeqOrFilename, AllFileswithExt(*file_readers.keys())),
26
+ Optional("dtype", default=None): Or("d", "f"),
27
+ Optional("replicate_outputs", default=((),)): Or((IsStrSeqOrStr,), [IsStrSeqOrStr]),
28
+ Optional("replicate_files", default=((),)): Or((IsStrSeqOrStr,), [IsStrSeqOrStr]),
29
+ Optional("skip", default=None): And(
30
+ Or(((str,),), [[str]]), Use(lambda l: tuple(set(k) for k in l))
31
+ ),
32
+ Optional("key_order", default=None): Or(
33
+ ((str,), (str,)),
34
+ [[str], [str]],
35
+ (int,),
36
+ [int],
37
+ ),
38
+ Optional("output_key_order", default=None): Or(
39
+ ((str,), (str,)),
40
+ [[str], [str]],
41
+ (int,),
42
+ [int],
43
+ ),
44
+ Optional("name_function", default=lambda: lambda st, tpl: st): Or(
45
+ Callable, And({str: str}, Use(lambda dct: lambda st, tpl: dct.get(st, st)))
46
+ ),
47
+ }
48
+ )
49
+
50
+ _schema_loadable_cfg = And(
51
+ {"load": Or(str, And(Path, Use(str))), Optional(str): object},
52
+ Use(
53
+ LoadFileWithExt(yaml=LoadYaml, key="load", update=True),
54
+ error="Failed to load {}",
55
+ ),
56
+ _schema_cfg,
57
+ )
58
+
59
+
60
+ def _validate_cfg(cfg):
61
+ if isinstance(cfg, dict) and "load" in cfg:
62
+ return _schema_loadable_cfg.validate(cfg)
63
+ else:
64
+ return _schema_cfg.validate(cfg)
65
+
66
+
67
+ def load_array(acfg: Mapping | None = None, *, array_kwargs: Mapping = {}, **kwargs) -> NodeStorage:
68
+ acfg = dict(acfg or {}, **kwargs)
69
+ cfg = _validate_cfg(acfg)
70
+
71
+ name = (cfg["name"],)
72
+ filenames = cfg["filenames"]
73
+ keys = cfg["replicate_outputs"]
74
+ file_keys = cfg["replicate_files"]
75
+ name_function = cfg["name_function"]
76
+ skip = cfg["skip"]
77
+ key_order = cfg["key_order"]
78
+ output_key_order = cfg["output_key_order"]
79
+ dtype = cfg["dtype"]
80
+
81
+ data = {}
82
+ reorder_output_key = make_reorder_function(output_key_order)
83
+ for _, filename, _, key in iterate_filenames_and_objectnames(
84
+ filenames, file_keys, keys, skip=skip, key_order=key_order
85
+ ):
86
+ skey = strkey(key)
87
+ logger.log(INFO3, f"Process {skey}")
88
+
89
+ array = FileReader.array[filename, name_function(skey, key)]
90
+ output_key = reorder_output_key(key)
91
+ data[output_key] = asarray(array, dtype)
92
+
93
+ storage = NodeStorage(default_containers=True)
94
+ with storage:
95
+ for key, array in data.items():
96
+ Array.replicate(name=".".join(name + key), array=array, **array_kwargs)
97
+
98
+ NodeStorage.update_current(storage, strict=True)
99
+
100
+ return storage