f9columnar 0.1.0__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.
- f9columnar/__init__.py +0 -0
- f9columnar/analysis/.gitignore +2 -0
- f9columnar/arrays.py +286 -0
- f9columnar/collections.py +165 -0
- f9columnar/data/PMGxsecDB_mc16.txt +103645 -0
- f9columnar/data/PMGxsecDB_mc21.txt +815 -0
- f9columnar/data/campaigns.json +27 -0
- f9columnar/data/luminosity.json +18 -0
- f9columnar/data/periods.json +32 -0
- f9columnar/data/truth_classification.json +94 -0
- f9columnar/dataloader_monitor.py +301 -0
- f9columnar/dataset_builder.py +576 -0
- f9columnar/hdf5_writer.py +324 -0
- f9columnar/histograms.py +641 -0
- f9columnar/object_collections.py +165 -0
- f9columnar/plotting.py +321 -0
- f9columnar/processors.py +466 -0
- f9columnar/root_dataloader.py +718 -0
- f9columnar/run.py +285 -0
- f9columnar/submit/__init__.py +0 -0
- f9columnar/submit/act_get.py +193 -0
- f9columnar/submit/act_handler.py +321 -0
- f9columnar/submit/act_merge.py +84 -0
- f9columnar/submit/act_monitor.py +33 -0
- f9columnar/submit/act_submit.py +307 -0
- f9columnar/submit/config/act-client/config.yaml +16 -0
- f9columnar/utils/__init__.py +0 -0
- f9columnar/utils/ak_helpers.py +143 -0
- f9columnar/utils/helpers.py +240 -0
- f9columnar/utils/loggers.py +163 -0
- f9columnar/utils/regex_helpers.py +34 -0
- f9columnar/utils/rucio_utils.py +128 -0
- f9columnar/xsec_db.py +88 -0
- f9columnar-0.1.0.dist-info/METADATA +347 -0
- f9columnar-0.1.0.dist-info/RECORD +36 -0
- f9columnar-0.1.0.dist-info/WHEEL +4 -0
f9columnar/__init__.py
ADDED
|
File without changes
|
f9columnar/arrays.py
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from dataclasses import field as dataclass_field
|
|
4
|
+
from typing import List
|
|
5
|
+
|
|
6
|
+
import awkward as ak
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
from f9columnar.processors import Processor
|
|
10
|
+
from f9columnar.utils.ak_helpers import check_list_type, check_numpy_type
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class BaseArrays:
|
|
15
|
+
array: ak.Array
|
|
16
|
+
fields: list = dataclass_field(default_factory=list)
|
|
17
|
+
|
|
18
|
+
def __post_init__(self):
|
|
19
|
+
self.fields = self.array.fields
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
def shape(self):
|
|
23
|
+
return (len(self.array), len(self.fields))
|
|
24
|
+
|
|
25
|
+
def mask(self, mask, inplace=True):
|
|
26
|
+
if inplace:
|
|
27
|
+
self.array = self.array[mask]
|
|
28
|
+
return self
|
|
29
|
+
else:
|
|
30
|
+
return self.array[mask]
|
|
31
|
+
|
|
32
|
+
def _check_type(self, array):
|
|
33
|
+
raise NotImplementedError
|
|
34
|
+
|
|
35
|
+
def __setitem__(self, field, new_array):
|
|
36
|
+
if not self._check_type(new_array):
|
|
37
|
+
raise ValueError(f"Field {field} is not of the correct type!")
|
|
38
|
+
|
|
39
|
+
self.array[field] = new_array
|
|
40
|
+
self.fields.append(field)
|
|
41
|
+
|
|
42
|
+
return self
|
|
43
|
+
|
|
44
|
+
def __getitem__(self, value):
|
|
45
|
+
if type(value) is str:
|
|
46
|
+
return self.array[value]
|
|
47
|
+
elif type(value) is int:
|
|
48
|
+
return self.array[value]
|
|
49
|
+
elif type(value) is ak.Array or type(value) is np.ndarray:
|
|
50
|
+
return self.mask(value)
|
|
51
|
+
elif type(value) is slice:
|
|
52
|
+
return self.array[value]
|
|
53
|
+
else:
|
|
54
|
+
raise ValueError("Value must be a field name or an array mask!")
|
|
55
|
+
|
|
56
|
+
def __len__(self):
|
|
57
|
+
return len(self.array)
|
|
58
|
+
|
|
59
|
+
def __delitem__(self, field):
|
|
60
|
+
self.array = ak.without_field(self.array, field)
|
|
61
|
+
self.fields.remove(field)
|
|
62
|
+
return self
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@dataclass
|
|
66
|
+
class FlatArrays(BaseArrays):
|
|
67
|
+
def _check_type(self, array):
|
|
68
|
+
return check_numpy_type(array)
|
|
69
|
+
|
|
70
|
+
def __str__(self):
|
|
71
|
+
return f"{self.__class__.__name__}(shape={self.shape})"
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@dataclass
|
|
75
|
+
class JaggedArrays(BaseArrays):
|
|
76
|
+
def _check_type(self, array):
|
|
77
|
+
return check_list_type(array)
|
|
78
|
+
|
|
79
|
+
def _get_non_empty_mask(self):
|
|
80
|
+
return ak.num(self.array[self.fields[0]]) > 0
|
|
81
|
+
|
|
82
|
+
def remove_empty(self):
|
|
83
|
+
mask = self._get_non_empty_mask()
|
|
84
|
+
self.mask(mask, inplace=True)
|
|
85
|
+
return self
|
|
86
|
+
|
|
87
|
+
def __str__(self):
|
|
88
|
+
return f"{self.__class__.__name__}(shape={self.shape})"
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@dataclass
|
|
92
|
+
class Arrays:
|
|
93
|
+
"""This class is used to store flat and jagged arrays separately. It is used to store awkward arrays in a more
|
|
94
|
+
structured way. It is initialized in the `ArrayProcessor` to separate flat and jagged fields. The funconality is
|
|
95
|
+
similar to `ak.Array` but with seperate handling of flat and jagged arrays. It is intended to make it easier to
|
|
96
|
+
work with ntuples that contain both flat (e.g. MC weights) and jagged fields (e.g. pt).
|
|
97
|
+
|
|
98
|
+
Parameters
|
|
99
|
+
----------
|
|
100
|
+
flat_arrays : FlatArrays
|
|
101
|
+
Flat arrays.
|
|
102
|
+
jagged_arrays : JaggedArrays
|
|
103
|
+
Jagged arrays.
|
|
104
|
+
|
|
105
|
+
Other Parameters
|
|
106
|
+
----------------
|
|
107
|
+
flat_fields : list
|
|
108
|
+
List of flat fields.
|
|
109
|
+
jagged_fields : list
|
|
110
|
+
List of jagged fields.
|
|
111
|
+
fields : list
|
|
112
|
+
List of all fields.
|
|
113
|
+
|
|
114
|
+
Attributes
|
|
115
|
+
----------
|
|
116
|
+
shape : tuple
|
|
117
|
+
Shape of the arrays (number of events, number of flat array, number of jagged arrays).
|
|
118
|
+
|
|
119
|
+
Methods
|
|
120
|
+
-------
|
|
121
|
+
update_fields()
|
|
122
|
+
Update flat and jagged fields on (or after) initialization.
|
|
123
|
+
mask_flat(mask, inplace=True)
|
|
124
|
+
Mask flat arrays.
|
|
125
|
+
mask_jagged(mask, inplace=True)
|
|
126
|
+
Mask jagged arrays.
|
|
127
|
+
remove_empty(inplace=True, return_mask=False)
|
|
128
|
+
Remove empty sub arrays.
|
|
129
|
+
|
|
130
|
+
Warning
|
|
131
|
+
-------
|
|
132
|
+
Masking is done inplace by default using `__setitem__`. To return a masked array, set `inplace=False` and use
|
|
133
|
+
`mask_flat` or `mask_jagged` methods.
|
|
134
|
+
|
|
135
|
+
Note
|
|
136
|
+
----
|
|
137
|
+
To delete a field, use `del arrays[field]` syntax.
|
|
138
|
+
|
|
139
|
+
"""
|
|
140
|
+
|
|
141
|
+
flat_arrays: FlatArrays
|
|
142
|
+
jagged_arrays: JaggedArrays
|
|
143
|
+
|
|
144
|
+
flat_fields: List[str] = dataclass_field(default_factory=list)
|
|
145
|
+
jagged_fields: List[str] = dataclass_field(default_factory=list)
|
|
146
|
+
fields: List[str] = dataclass_field(default_factory=list)
|
|
147
|
+
|
|
148
|
+
def __post_init__(self):
|
|
149
|
+
self.update_fields()
|
|
150
|
+
|
|
151
|
+
def update_fields(self):
|
|
152
|
+
self.flat_fields = self.flat_arrays.fields
|
|
153
|
+
self.jagged_fields = self.jagged_arrays.fields
|
|
154
|
+
self.fields = self.flat_fields + self.jagged_fields
|
|
155
|
+
return self
|
|
156
|
+
|
|
157
|
+
@property
|
|
158
|
+
def shape(self):
|
|
159
|
+
return (len(self), len(self.flat_fields), len(self.jagged_fields))
|
|
160
|
+
|
|
161
|
+
def mask_flat(self, mask, inplace=True):
|
|
162
|
+
if inplace:
|
|
163
|
+
self.flat_arrays.mask(mask, inplace=True)
|
|
164
|
+
self.jagged_arrays.mask(mask, inplace=True)
|
|
165
|
+
return self
|
|
166
|
+
else:
|
|
167
|
+
return self.flat_arrays.mask(mask, inplace=False), self.jagged_arrays.mask(mask, inplace=False)
|
|
168
|
+
|
|
169
|
+
def mask_jagged(self, mask, inplace=True):
|
|
170
|
+
if inplace:
|
|
171
|
+
self.jagged_arrays.mask(mask, inplace=True)
|
|
172
|
+
return self
|
|
173
|
+
else:
|
|
174
|
+
return self.jagged_arrays.mask(mask, inplace=False)
|
|
175
|
+
|
|
176
|
+
def remove_empty(self, inplace=True, return_mask=False):
|
|
177
|
+
non_empty_mask = self.jagged_arrays._get_non_empty_mask()
|
|
178
|
+
if return_mask:
|
|
179
|
+
return self.mask_flat(non_empty_mask, inplace=inplace), non_empty_mask
|
|
180
|
+
else:
|
|
181
|
+
return self.mask_flat(non_empty_mask, inplace=inplace)
|
|
182
|
+
|
|
183
|
+
def __setitem__(self, field, new_array):
|
|
184
|
+
if check_numpy_type(new_array):
|
|
185
|
+
self.flat_arrays[field] = new_array
|
|
186
|
+
elif check_list_type(new_array):
|
|
187
|
+
self.jagged_arrays[field] = new_array
|
|
188
|
+
else:
|
|
189
|
+
raise ValueError("Array is not of the correct type!")
|
|
190
|
+
|
|
191
|
+
self.update_fields()
|
|
192
|
+
|
|
193
|
+
return self
|
|
194
|
+
|
|
195
|
+
def _get_by_field(self, field):
|
|
196
|
+
if field in self.flat_fields:
|
|
197
|
+
return self.flat_arrays[field]
|
|
198
|
+
elif field in self.jagged_fields:
|
|
199
|
+
return self.jagged_arrays[field]
|
|
200
|
+
else:
|
|
201
|
+
raise KeyError(f"Field {field} not found in flat or jagged fields!")
|
|
202
|
+
|
|
203
|
+
def _get_by_slice(self, slice_idx):
|
|
204
|
+
return self.flat_arrays[slice_idx], self.jagged_arrays[slice_idx]
|
|
205
|
+
|
|
206
|
+
def _get_by_mask(self, mask):
|
|
207
|
+
if check_numpy_type(mask):
|
|
208
|
+
self.mask_flat(mask, inplace=True)
|
|
209
|
+
elif check_list_type(mask):
|
|
210
|
+
self.mask_jagged(mask, inplace=True)
|
|
211
|
+
else:
|
|
212
|
+
raise ValueError("Mask is not of the correct type!")
|
|
213
|
+
|
|
214
|
+
return self
|
|
215
|
+
|
|
216
|
+
def __getitem__(self, value):
|
|
217
|
+
if type(value) is str:
|
|
218
|
+
return self._get_by_field(value)
|
|
219
|
+
elif type(value) is int:
|
|
220
|
+
return self._get_by_slice(value)
|
|
221
|
+
elif type(value) is ak.Array or type(value) is np.ndarray:
|
|
222
|
+
return self._get_by_mask(value)
|
|
223
|
+
elif type(value) is slice:
|
|
224
|
+
return self._get_by_slice(value)
|
|
225
|
+
else:
|
|
226
|
+
raise ValueError("Value must be a field name, int, array mask or slice!")
|
|
227
|
+
|
|
228
|
+
def __len__(self):
|
|
229
|
+
len_flat, len_jagged = len(self.flat_arrays), len(self.jagged_arrays)
|
|
230
|
+
assert len_flat == len_jagged, "Flat and jagged arrays have different lengths!"
|
|
231
|
+
return len_flat
|
|
232
|
+
|
|
233
|
+
def __delitem__(self, field):
|
|
234
|
+
if field in self.flat_fields:
|
|
235
|
+
del self.flat_arrays[field]
|
|
236
|
+
elif field in self.jagged_fields:
|
|
237
|
+
del self.jagged_arrays[field]
|
|
238
|
+
else:
|
|
239
|
+
raise KeyError(f"Field {field} not found in flat or jagged fields!")
|
|
240
|
+
|
|
241
|
+
self.fields.remove(field)
|
|
242
|
+
|
|
243
|
+
return self
|
|
244
|
+
|
|
245
|
+
def __str__(self):
|
|
246
|
+
return f"{self.__class__.__name__}(shape={self.shape})"
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
class ArrayProcessor(Processor):
|
|
250
|
+
def __init__(self):
|
|
251
|
+
"""Processor to separate flat and jagged fields into two arrays from an awkward array.
|
|
252
|
+
|
|
253
|
+
Note
|
|
254
|
+
----
|
|
255
|
+
This will modify the input arrays. It is intended to be used as the first processor in the analysis chain in
|
|
256
|
+
the case of ntuples that contain both flat and jagged fields.
|
|
257
|
+
|
|
258
|
+
"""
|
|
259
|
+
super().__init__(name="arrayProcessor")
|
|
260
|
+
self.flat_fields, self.jagged_fields, self.other_fields = [], [], []
|
|
261
|
+
|
|
262
|
+
def _get_fields(self, arrays):
|
|
263
|
+
arrays_contents = arrays.type.content.contents
|
|
264
|
+
arrays_fields = arrays.type.content.fields
|
|
265
|
+
|
|
266
|
+
for content, array_field in zip(arrays_contents, arrays_fields):
|
|
267
|
+
if isinstance(content, ak.types.NumpyType):
|
|
268
|
+
self.flat_fields.append(array_field)
|
|
269
|
+
elif isinstance(content, ak.types.ListType):
|
|
270
|
+
self.jagged_fields.append(array_field)
|
|
271
|
+
else:
|
|
272
|
+
self.other_fields.append(array_field)
|
|
273
|
+
|
|
274
|
+
if len(self.other_fields) != 0:
|
|
275
|
+
logging.warning(f"Fields {self.other_fields} are not flat or jagged! Will not be processed.")
|
|
276
|
+
|
|
277
|
+
return self.flat_fields, self.jagged_fields
|
|
278
|
+
|
|
279
|
+
def run(self, arrays):
|
|
280
|
+
self._get_fields(arrays)
|
|
281
|
+
|
|
282
|
+
flat_arrays, jagged_arrays = arrays[self.flat_fields], arrays[self.jagged_fields]
|
|
283
|
+
|
|
284
|
+
arrays = Arrays(FlatArrays(flat_arrays), JaggedArrays(jagged_arrays))
|
|
285
|
+
|
|
286
|
+
return {"arrays": arrays}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
import logging
|
|
3
|
+
import time
|
|
4
|
+
from abc import abstractmethod
|
|
5
|
+
from collections import OrderedDict
|
|
6
|
+
|
|
7
|
+
import awkward as ak
|
|
8
|
+
|
|
9
|
+
from f9columnar.processors import Processor
|
|
10
|
+
from f9columnar.utils.helpers import load_json
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ObjectCollection:
|
|
14
|
+
def __init__(self, collection_name, *objects, init=False):
|
|
15
|
+
"""Collection of objects.
|
|
16
|
+
|
|
17
|
+
Parameters
|
|
18
|
+
----------
|
|
19
|
+
collection_name : str
|
|
20
|
+
Name of this collection.
|
|
21
|
+
objects : *args
|
|
22
|
+
obj objects to store.
|
|
23
|
+
init : bool
|
|
24
|
+
Whether to initialize the objects or not.
|
|
25
|
+
"""
|
|
26
|
+
self.name = collection_name
|
|
27
|
+
self.init = init
|
|
28
|
+
self.objects = OrderedDict()
|
|
29
|
+
|
|
30
|
+
for obj in objects:
|
|
31
|
+
if init:
|
|
32
|
+
obj = obj()
|
|
33
|
+
self.objects[obj.name] = obj
|
|
34
|
+
else:
|
|
35
|
+
self.objects[obj.name] = obj
|
|
36
|
+
|
|
37
|
+
self.branch_names = self._get_branch_names()
|
|
38
|
+
|
|
39
|
+
def __getitem__(self, name):
|
|
40
|
+
return self.objects[name]
|
|
41
|
+
|
|
42
|
+
def __add__(self, obj):
|
|
43
|
+
if isinstance(obj, ObjectCollection):
|
|
44
|
+
for v in obj.objects.values():
|
|
45
|
+
if v in self.objects:
|
|
46
|
+
raise ValueError(f"Object {v.name} already exists in the {self.collection_name} collection!")
|
|
47
|
+
else:
|
|
48
|
+
self.objects[v.name] = v
|
|
49
|
+
else:
|
|
50
|
+
if obj in self.objects:
|
|
51
|
+
raise ValueError(f"Object {obj.name} already exists in the {self.collection_name} collection!")
|
|
52
|
+
else:
|
|
53
|
+
if self.init:
|
|
54
|
+
obj = obj()
|
|
55
|
+
self.objects[obj.name] = obj
|
|
56
|
+
else:
|
|
57
|
+
self.objects[obj.name] = obj
|
|
58
|
+
|
|
59
|
+
self.branch_names = self._get_branch_names()
|
|
60
|
+
|
|
61
|
+
return self
|
|
62
|
+
|
|
63
|
+
def _get_branch_names(self):
|
|
64
|
+
branch_names = []
|
|
65
|
+
for v in self.objects.values():
|
|
66
|
+
if not hasattr(v, "branch_name"):
|
|
67
|
+
continue
|
|
68
|
+
|
|
69
|
+
branch_name = v.branch_name
|
|
70
|
+
|
|
71
|
+
if branch_name is None:
|
|
72
|
+
continue
|
|
73
|
+
elif type(branch_name) is list:
|
|
74
|
+
branch_names += [br for br in branch_name if br is not None]
|
|
75
|
+
else:
|
|
76
|
+
branch_names.append(v.branch_name)
|
|
77
|
+
|
|
78
|
+
return branch_names
|
|
79
|
+
|
|
80
|
+
def branch_name_filter(self, branch):
|
|
81
|
+
if branch.name in self.branch_names:
|
|
82
|
+
return True
|
|
83
|
+
else:
|
|
84
|
+
return False
|
|
85
|
+
|
|
86
|
+
def as_list(self):
|
|
87
|
+
return list(self.objects.values())
|
|
88
|
+
|
|
89
|
+
def __str__(self):
|
|
90
|
+
str_output = f"Object Collection\n{17 * '-'}\n"
|
|
91
|
+
|
|
92
|
+
for name, obj in self.objects.items():
|
|
93
|
+
str_output += f"{name}: {str(obj)}\n"
|
|
94
|
+
|
|
95
|
+
return str_output[:-1]
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class Variable(Processor):
|
|
99
|
+
name = None
|
|
100
|
+
branch_name = None
|
|
101
|
+
|
|
102
|
+
def __init__(self):
|
|
103
|
+
super().__init__(self.name)
|
|
104
|
+
|
|
105
|
+
@abstractmethod
|
|
106
|
+
def run(self):
|
|
107
|
+
pass
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class VariableCollection(ObjectCollection):
|
|
111
|
+
def __init__(self, *variables, init=False):
|
|
112
|
+
super().__init__("Variables", *variables, init=init)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class Cut(Processor):
|
|
116
|
+
name = None
|
|
117
|
+
branch_name = None
|
|
118
|
+
|
|
119
|
+
def __init__(self):
|
|
120
|
+
super().__init__(self.name)
|
|
121
|
+
self.start_n, self.end_n = None, None
|
|
122
|
+
|
|
123
|
+
def _run(self, arrays, **kwargs):
|
|
124
|
+
self.start_n, start_time = len(arrays), time.time()
|
|
125
|
+
self._results = copy.deepcopy(self.run(arrays, **kwargs))
|
|
126
|
+
self.end_n, self.delta_time = len(self._results["arrays"]), time.time() - start_time
|
|
127
|
+
return self
|
|
128
|
+
|
|
129
|
+
@abstractmethod
|
|
130
|
+
def run(self):
|
|
131
|
+
pass
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class CutCollection(ObjectCollection):
|
|
135
|
+
def __init__(self, *cuts, init=False):
|
|
136
|
+
super().__init__("Cuts", *cuts, init=init)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class Weight(Processor):
|
|
140
|
+
name = None
|
|
141
|
+
branch_name = None
|
|
142
|
+
|
|
143
|
+
def __init__(self):
|
|
144
|
+
"""MC weights processor.
|
|
145
|
+
|
|
146
|
+
References
|
|
147
|
+
----------
|
|
148
|
+
[1] - https://ipnp.cz/scheirich/?page_id=292
|
|
149
|
+
|
|
150
|
+
"""
|
|
151
|
+
super().__init__(self.name)
|
|
152
|
+
|
|
153
|
+
@abstractmethod
|
|
154
|
+
def run(self):
|
|
155
|
+
pass
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class WeightCollection(ObjectCollection):
|
|
159
|
+
def __init__(self, *weights, init=False):
|
|
160
|
+
super().__init__("Weights", *weights, init=init)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
class HistogramCollection(ObjectCollection):
|
|
164
|
+
def __init__(self, *histograms, init=False):
|
|
165
|
+
super().__init__("Histograms", *histograms, init=init)
|