atlas-schema 0.2.4__py3-none-any.whl → 0.4.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.
- atlas_schema/_version.py +22 -4
- atlas_schema/enums.py +1 -1
- atlas_schema/methods.py +189 -79
- atlas_schema/schema.py +234 -82
- atlas_schema/utils.py +6 -7
- {atlas_schema-0.2.4.dist-info → atlas_schema-0.4.0.dist-info}/METADATA +68 -18
- atlas_schema-0.4.0.dist-info/RECORD +13 -0
- atlas_schema-0.2.4.dist-info/RECORD +0 -13
- {atlas_schema-0.2.4.dist-info → atlas_schema-0.4.0.dist-info}/WHEEL +0 -0
- {atlas_schema-0.2.4.dist-info → atlas_schema-0.4.0.dist-info}/licenses/LICENSE +0 -0
atlas_schema/_version.py
CHANGED
@@ -1,16 +1,34 @@
|
|
1
|
-
# file generated by
|
1
|
+
# file generated by setuptools-scm
|
2
2
|
# don't change, don't track in version control
|
3
|
+
|
4
|
+
__all__ = [
|
5
|
+
"__version__",
|
6
|
+
"__version_tuple__",
|
7
|
+
"version",
|
8
|
+
"version_tuple",
|
9
|
+
"__commit_id__",
|
10
|
+
"commit_id",
|
11
|
+
]
|
12
|
+
|
3
13
|
TYPE_CHECKING = False
|
4
14
|
if TYPE_CHECKING:
|
5
|
-
from typing import Tuple
|
15
|
+
from typing import Tuple
|
16
|
+
from typing import Union
|
17
|
+
|
6
18
|
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
19
|
+
COMMIT_ID = Union[str, None]
|
7
20
|
else:
|
8
21
|
VERSION_TUPLE = object
|
22
|
+
COMMIT_ID = object
|
9
23
|
|
10
24
|
version: str
|
11
25
|
__version__: str
|
12
26
|
__version_tuple__: VERSION_TUPLE
|
13
27
|
version_tuple: VERSION_TUPLE
|
28
|
+
commit_id: COMMIT_ID
|
29
|
+
__commit_id__: COMMIT_ID
|
30
|
+
|
31
|
+
__version__ = version = '0.4.0'
|
32
|
+
__version_tuple__ = version_tuple = (0, 4, 0)
|
14
33
|
|
15
|
-
|
16
|
-
__version_tuple__ = version_tuple = (0, 2, 4)
|
34
|
+
__commit_id__ = commit_id = None
|
atlas_schema/enums.py
CHANGED
@@ -18,7 +18,7 @@ class MultipleEnumAccessMeta(EnumType):
|
|
18
18
|
Enum Metaclass to provide a way to access multiple values all at once.
|
19
19
|
"""
|
20
20
|
|
21
|
-
def __getitem__(
|
21
|
+
def __getitem__(cls: type[_E], key: str | tuple[str]) -> _E | list[_E]: # type:ignore[misc,override]
|
22
22
|
getitem = cast(Callable[[str], _E], super().__getitem__) # type:ignore[misc]
|
23
23
|
if isinstance(key, tuple):
|
24
24
|
return [getitem(name) for name in key]
|
atlas_schema/methods.py
CHANGED
@@ -8,7 +8,6 @@ from operator import ior
|
|
8
8
|
import awkward
|
9
9
|
import particle
|
10
10
|
from coffea.nanoevents.methods import base, candidate, vector
|
11
|
-
from dask_awkward import dask_method
|
12
11
|
|
13
12
|
from atlas_schema.enums import PhotonID
|
14
13
|
from atlas_schema.typing_compat import Behavior
|
@@ -19,22 +18,127 @@ behavior.update(base.behavior)
|
|
19
18
|
behavior.update(candidate.behavior)
|
20
19
|
|
21
20
|
|
21
|
+
def _set_repr_name(classname):
|
22
|
+
def namefcn(_self):
|
23
|
+
return classname
|
24
|
+
|
25
|
+
behavior[("__typestr__", classname)] = classname[0].lower() + classname[1:]
|
26
|
+
behavior[classname].__repr__ = namefcn
|
27
|
+
|
28
|
+
|
22
29
|
class NtupleEvents(behavior["NanoEvents"]): # type: ignore[misc, valid-type, name-defined]
|
30
|
+
"""Individual systematic variation of events."""
|
31
|
+
|
23
32
|
def __repr__(self):
|
24
|
-
return f"<event {getattr(self, 'runNumber', '??')}
|
25
|
-
{getattr(self, 'eventNumber', '??')}:\
|
26
|
-
{getattr(self, 'mcChannelNumber', '??')}>"
|
33
|
+
return f"<event {getattr(self, 'runNumber', '??')}:{getattr(self, 'eventNumber', '??')}:{getattr(self, 'mcChannelNumber', '??')}>"
|
27
34
|
|
35
|
+
def __getitem__(self, key):
|
36
|
+
"""Support accessing systematic variations via bracket notation.
|
28
37
|
|
29
|
-
|
38
|
+
Args:
|
39
|
+
key: The systematic variation name. "NOSYS" returns the nominal events.
|
30
40
|
|
41
|
+
Returns:
|
42
|
+
The requested systematic variation or nominal events for "NOSYS".
|
43
|
+
"""
|
44
|
+
if key == "NOSYS":
|
45
|
+
return self
|
46
|
+
return super().__getitem__(key)
|
31
47
|
|
32
|
-
|
33
|
-
def
|
34
|
-
|
48
|
+
@property
|
49
|
+
def systematic(self):
|
50
|
+
"""Get the systematic variation name for this event collection."""
|
51
|
+
return "nominal"
|
35
52
|
|
36
|
-
|
37
|
-
|
53
|
+
@property
|
54
|
+
def systematic_names(self):
|
55
|
+
"""Get all systematic variations available in this event collection.
|
56
|
+
|
57
|
+
Returns a list of systematic variation names, including 'NOSYS' for nominal.
|
58
|
+
"""
|
59
|
+
# Get systematics from metadata stored during schema building
|
60
|
+
systematics = self.metadata.get("systematics", [])
|
61
|
+
return ["NOSYS", *systematics]
|
62
|
+
|
63
|
+
@property
|
64
|
+
def systematics(self):
|
65
|
+
"""Get all systematic variations available in this event collection.
|
66
|
+
|
67
|
+
Returns a list of systematic variation names, excluding 'nominal'.
|
68
|
+
"""
|
69
|
+
# Get systematics from metadata stored during schema building
|
70
|
+
return [
|
71
|
+
getattr(self, systematic)
|
72
|
+
for systematic in self.systematic_names
|
73
|
+
if systematic != "NOSYS"
|
74
|
+
]
|
75
|
+
|
76
|
+
|
77
|
+
behavior["NtupleEvents"] = NtupleEvents
|
78
|
+
|
79
|
+
|
80
|
+
class NtupleEventsArray(behavior[("*", "NanoEvents")]): # type: ignore[misc, valid-type, name-defined]
|
81
|
+
"""Collection of NtupleEvents objects, one for each systematic variation."""
|
82
|
+
|
83
|
+
def __getitem__(self, key):
|
84
|
+
"""Support accessing systematic variations via bracket notation.
|
85
|
+
|
86
|
+
Args:
|
87
|
+
key: The systematic variation name. "NOSYS" returns the nominal events.
|
88
|
+
|
89
|
+
Returns:
|
90
|
+
The requested systematic variation or nominal events for "NOSYS".
|
91
|
+
"""
|
92
|
+
if key == "NOSYS":
|
93
|
+
return self
|
94
|
+
return super().__getitem__(key)
|
95
|
+
|
96
|
+
@property
|
97
|
+
def systematic_names(self):
|
98
|
+
"""Get all systematic variations available in this event collection.
|
99
|
+
|
100
|
+
Returns a list of systematic variation names, including 'NOSYS' for nominal.
|
101
|
+
"""
|
102
|
+
# Get systematics from metadata stored during schema building
|
103
|
+
systematics = self.metadata.get("systematics", [])
|
104
|
+
return ["NOSYS", *systematics]
|
105
|
+
|
106
|
+
@property
|
107
|
+
def systematics(self):
|
108
|
+
"""Get all systematic variations available in this event collection.
|
109
|
+
|
110
|
+
Returns a list of systematic variation names, excluding 'nominal'.
|
111
|
+
"""
|
112
|
+
# Get systematics from metadata stored during schema building
|
113
|
+
return [
|
114
|
+
getattr(self, systematic)
|
115
|
+
for systematic in self.systematic_names
|
116
|
+
if systematic != "NOSYS"
|
117
|
+
]
|
118
|
+
|
119
|
+
|
120
|
+
behavior[("*", "NtupleEvents")] = NtupleEventsArray
|
121
|
+
|
122
|
+
|
123
|
+
@awkward.mixin_class(behavior)
|
124
|
+
class Systematic(base.NanoCollection, base.Systematic):
|
125
|
+
"""Base class for systematic variations."""
|
126
|
+
|
127
|
+
@property
|
128
|
+
def metadata(self):
|
129
|
+
"""Arbitrary metadata"""
|
130
|
+
return self.layout.purelist_parameter("metadata") # pylint: disable=no-member
|
131
|
+
|
132
|
+
@property
|
133
|
+
def systematic(self):
|
134
|
+
"""Get the systematic variation name for this event collection."""
|
135
|
+
return self.metadata["systematic"]
|
136
|
+
|
137
|
+
def __repr__(self):
|
138
|
+
return f"<event {self.systematic}>"
|
139
|
+
|
140
|
+
|
141
|
+
_set_repr_name("Systematic")
|
38
142
|
|
39
143
|
|
40
144
|
@awkward.mixin_class(behavior)
|
@@ -51,7 +155,7 @@ class Pass(base.NanoCollection, base.Systematic): ...
|
|
51
155
|
_set_repr_name("Pass")
|
52
156
|
|
53
157
|
behavior.update(
|
54
|
-
awkward._util.copy_behaviors("PtEtaPhiMLorentzVector", "Particle", behavior)
|
158
|
+
awkward._util.copy_behaviors("PtEtaPhiMLorentzVector", "Particle", behavior) # pylint: disable=protected-access
|
55
159
|
)
|
56
160
|
|
57
161
|
|
@@ -63,22 +167,9 @@ class Particle(vector.PtEtaPhiMLorentzVector):
|
|
63
167
|
- '{obj}_select'
|
64
168
|
"""
|
65
169
|
|
66
|
-
@property
|
67
|
-
def mass(self):
|
68
|
-
r"""Invariant mass (+, -, -, -)
|
69
|
-
|
70
|
-
:math:`\sqrt{t^2-x^2-y^2-z^2}`
|
71
|
-
"""
|
72
|
-
return self["mass"] / 1.0e3
|
73
|
-
|
74
|
-
@dask_method
|
75
170
|
def passes(self, name):
|
76
171
|
return self[f"select_{name}"] == 1
|
77
172
|
|
78
|
-
@passes.dask
|
79
|
-
def passes(self, dask_array, name):
|
80
|
-
return dask_array[f"select_{name}"] == 1
|
81
|
-
|
82
173
|
# NB: fields with the name 'pt' take precedence over this
|
83
174
|
# @dask_property
|
84
175
|
# def pt(self):
|
@@ -102,17 +193,19 @@ class Particle(vector.PtEtaPhiMLorentzVector):
|
|
102
193
|
|
103
194
|
_set_repr_name("Particle")
|
104
195
|
|
105
|
-
ParticleArray.ProjectionClass2D = vector.TwoVectorArray # noqa: F821
|
106
|
-
ParticleArray.ProjectionClass3D = vector.ThreeVectorArray # noqa: F821
|
107
|
-
ParticleArray.ProjectionClass4D = ParticleArray # noqa: F821
|
108
|
-
ParticleArray.MomentumClass = vector.LorentzVectorArray # noqa: F821
|
196
|
+
ParticleArray.ProjectionClass2D = vector.TwoVectorArray # noqa: F821 # pylint: disable=undefined-variable,no-member
|
197
|
+
ParticleArray.ProjectionClass3D = vector.ThreeVectorArray # noqa: F821 # pylint: disable=undefined-variable,no-member
|
198
|
+
ParticleArray.ProjectionClass4D = ParticleArray # noqa: F821 # pylint: disable=undefined-variable
|
199
|
+
ParticleArray.MomentumClass = vector.LorentzVectorArray # noqa: F821 # pylint: disable=undefined-variable,no-member
|
109
200
|
|
110
201
|
|
111
|
-
behavior.update(awkward._util.copy_behaviors("PolarTwoVector", "MissingET", behavior))
|
202
|
+
behavior.update(awkward._util.copy_behaviors("PolarTwoVector", "MissingET", behavior)) # pylint: disable=protected-access
|
112
203
|
|
113
204
|
|
114
205
|
@awkward.mixin_class(behavior)
|
115
206
|
class MissingET(vector.PolarTwoVector, base.NanoCollection, base.Systematic):
|
207
|
+
"""Missing transverse energy collection."""
|
208
|
+
|
116
209
|
@property
|
117
210
|
def r(self):
|
118
211
|
"""Distance from origin in XY plane"""
|
@@ -121,16 +214,18 @@ class MissingET(vector.PolarTwoVector, base.NanoCollection, base.Systematic):
|
|
121
214
|
|
122
215
|
_set_repr_name("MissingET")
|
123
216
|
|
124
|
-
MissingETArray.ProjectionClass2D = MissingETArray # noqa: F821
|
125
|
-
MissingETArray.ProjectionClass3D = vector.SphericalThreeVectorArray # noqa: F821
|
126
|
-
MissingETArray.ProjectionClass4D = vector.LorentzVectorArray # noqa: F821
|
127
|
-
MissingETArray.MomentumClass = MissingETArray # noqa: F821
|
217
|
+
MissingETArray.ProjectionClass2D = MissingETArray # noqa: F821 # pylint: disable=undefined-variable
|
218
|
+
MissingETArray.ProjectionClass3D = vector.SphericalThreeVectorArray # noqa: F821 # pylint: disable=undefined-variable,no-member
|
219
|
+
MissingETArray.ProjectionClass4D = vector.LorentzVectorArray # noqa: F821 # pylint: disable=undefined-variable,no-member
|
220
|
+
MissingETArray.MomentumClass = MissingETArray # noqa: F821 # pylint: disable=undefined-variable
|
128
221
|
|
129
|
-
behavior.update(awkward._util.copy_behaviors("Particle", "Photon", behavior))
|
222
|
+
behavior.update(awkward._util.copy_behaviors("Particle", "Photon", behavior)) # pylint: disable=protected-access
|
130
223
|
|
131
224
|
|
132
225
|
@awkward.mixin_class(behavior)
|
133
226
|
class Photon(Particle, base.NanoCollection, base.Systematic):
|
227
|
+
"""Photon particle collection."""
|
228
|
+
|
134
229
|
@property
|
135
230
|
def mass(self):
|
136
231
|
"""Return zero mass for photon."""
|
@@ -143,111 +238,126 @@ class Photon(Particle, base.NanoCollection, base.Systematic):
|
|
143
238
|
|
144
239
|
@property
|
145
240
|
def isEM(self):
|
146
|
-
return self.isEM_syst.NOSYS == 0
|
241
|
+
return self.isEM_syst.NOSYS == 0 # pylint: disable=no-member
|
147
242
|
|
148
243
|
def pass_isEM(self, words: list[PhotonID]):
|
149
244
|
# 0 is pass, 1 is fail
|
150
245
|
return (
|
151
|
-
self.isEM_syst.NOSYS & reduce(ior, (1 << word.value for word in words))
|
246
|
+
self.isEM_syst.NOSYS & reduce(ior, (1 << word.value for word in words)) # pylint: disable=no-member
|
152
247
|
) == 0
|
153
248
|
|
154
249
|
|
155
250
|
_set_repr_name("Photon")
|
156
251
|
|
157
|
-
PhotonArray.ProjectionClass2D = vector.TwoVectorArray # noqa: F821
|
158
|
-
PhotonArray.ProjectionClass3D = vector.ThreeVectorArray # noqa: F821
|
159
|
-
PhotonArray.ProjectionClass4D = PhotonArray # noqa: F821
|
160
|
-
PhotonArray.MomentumClass = vector.LorentzVectorArray # noqa: F821
|
252
|
+
PhotonArray.ProjectionClass2D = vector.TwoVectorArray # noqa: F821 # pylint: disable=undefined-variable,no-member
|
253
|
+
PhotonArray.ProjectionClass3D = vector.ThreeVectorArray # noqa: F821 # pylint: disable=undefined-variable,no-member
|
254
|
+
PhotonArray.ProjectionClass4D = PhotonArray # noqa: F821 # pylint: disable=undefined-variable
|
255
|
+
PhotonArray.MomentumClass = vector.LorentzVectorArray # noqa: F821 # pylint: disable=undefined-variable,no-member
|
161
256
|
|
162
|
-
behavior.update(awkward._util.copy_behaviors("Particle", "Electron", behavior))
|
257
|
+
behavior.update(awkward._util.copy_behaviors("Particle", "Electron", behavior)) # pylint: disable=protected-access
|
163
258
|
|
164
259
|
|
165
260
|
@awkward.mixin_class(behavior)
|
166
261
|
class Electron(Particle, base.NanoCollection, base.Systematic):
|
262
|
+
"""Electron particle collection."""
|
263
|
+
|
167
264
|
@property
|
168
265
|
def mass(self):
|
169
|
-
"""Electron mass in
|
170
|
-
return particle.literals.e_minus.mass
|
266
|
+
"""Electron mass in MeV"""
|
267
|
+
return awkward.ones_like(self.pt) * particle.literals.e_minus.mass # pylint: disable=no-member
|
171
268
|
|
172
269
|
|
173
270
|
_set_repr_name("Electron")
|
174
271
|
|
175
|
-
ElectronArray.ProjectionClass2D = vector.TwoVectorArray # noqa: F821
|
176
|
-
ElectronArray.ProjectionClass3D = vector.ThreeVectorArray # noqa: F821
|
177
|
-
ElectronArray.ProjectionClass4D = ElectronArray # noqa: F821
|
178
|
-
ElectronArray.MomentumClass = vector.LorentzVectorArray # noqa: F821
|
272
|
+
ElectronArray.ProjectionClass2D = vector.TwoVectorArray # noqa: F821 # pylint: disable=undefined-variable,no-member
|
273
|
+
ElectronArray.ProjectionClass3D = vector.ThreeVectorArray # noqa: F821 # pylint: disable=undefined-variable,no-member
|
274
|
+
ElectronArray.ProjectionClass4D = ElectronArray # noqa: F821 # pylint: disable=undefined-variable
|
275
|
+
ElectronArray.MomentumClass = vector.LorentzVectorArray # noqa: F821 # pylint: disable=undefined-variable,no-member
|
179
276
|
|
180
|
-
behavior.update(awkward._util.copy_behaviors("Particle", "Muon", behavior))
|
277
|
+
behavior.update(awkward._util.copy_behaviors("Particle", "Muon", behavior)) # pylint: disable=protected-access
|
181
278
|
|
182
279
|
|
183
280
|
@awkward.mixin_class(behavior)
|
184
281
|
class Muon(Particle, base.NanoCollection, base.Systematic):
|
282
|
+
"""Muon particle collection."""
|
283
|
+
|
185
284
|
@property
|
186
285
|
def mass(self):
|
187
|
-
"""Muon mass in
|
188
|
-
return particle.literals.mu_minus.mass
|
286
|
+
"""Muon mass in MeV"""
|
287
|
+
return awkward.ones_like(self.pt) * particle.literals.mu_minus.mass # pylint: disable=no-member
|
189
288
|
|
190
289
|
|
191
290
|
_set_repr_name("Muon")
|
192
291
|
|
193
|
-
MuonArray.ProjectionClass2D = vector.TwoVectorArray # noqa: F821
|
194
|
-
MuonArray.ProjectionClass3D = vector.ThreeVectorArray # noqa: F821
|
195
|
-
MuonArray.ProjectionClass4D = MuonArray # noqa: F821
|
196
|
-
MuonArray.MomentumClass = vector.LorentzVectorArray # noqa: F821
|
292
|
+
MuonArray.ProjectionClass2D = vector.TwoVectorArray # noqa: F821 # pylint: disable=undefined-variable,no-member
|
293
|
+
MuonArray.ProjectionClass3D = vector.ThreeVectorArray # noqa: F821 # pylint: disable=undefined-variable,no-member
|
294
|
+
MuonArray.ProjectionClass4D = MuonArray # noqa: F821 # pylint: disable=undefined-variable
|
295
|
+
MuonArray.MomentumClass = vector.LorentzVectorArray # noqa: F821 # pylint: disable=undefined-variable,no-member
|
197
296
|
|
198
|
-
behavior.update(awkward._util.copy_behaviors("Particle", "Tau", behavior))
|
297
|
+
behavior.update(awkward._util.copy_behaviors("Particle", "Tau", behavior)) # pylint: disable=protected-access
|
199
298
|
|
200
299
|
|
201
300
|
@awkward.mixin_class(behavior)
|
202
301
|
class Tau(Particle, base.NanoCollection, base.Systematic):
|
302
|
+
"""Tau particle collection."""
|
303
|
+
|
203
304
|
@property
|
204
305
|
def mass(self):
|
205
|
-
"""Tau mass in
|
206
|
-
return particle.literals.tau_minus.mass
|
306
|
+
"""Tau mass in MeV"""
|
307
|
+
return awkward.ones_like(self.pt) * particle.literals.tau_minus.mass # pylint: disable=no-member
|
207
308
|
|
208
309
|
|
209
310
|
_set_repr_name("Tau")
|
210
311
|
|
211
|
-
TauArray.ProjectionClass2D = vector.TwoVectorArray # noqa: F821
|
212
|
-
TauArray.ProjectionClass3D = vector.ThreeVectorArray # noqa: F821
|
213
|
-
TauArray.ProjectionClass4D = TauArray # noqa: F821
|
214
|
-
TauArray.MomentumClass = vector.LorentzVectorArray # noqa: F821
|
312
|
+
TauArray.ProjectionClass2D = vector.TwoVectorArray # noqa: F821 # pylint: disable=undefined-variable,no-member
|
313
|
+
TauArray.ProjectionClass3D = vector.ThreeVectorArray # noqa: F821 # pylint: disable=undefined-variable,no-member
|
314
|
+
TauArray.ProjectionClass4D = TauArray # noqa: F821 # pylint: disable=undefined-variable
|
315
|
+
TauArray.MomentumClass = vector.LorentzVectorArray # noqa: F821 # pylint: disable=undefined-variable,no-member
|
215
316
|
|
216
317
|
|
217
|
-
behavior.update(awkward._util.copy_behaviors("Particle", "Jet", behavior))
|
318
|
+
behavior.update(awkward._util.copy_behaviors("Particle", "Jet", behavior)) # pylint: disable=protected-access
|
218
319
|
|
219
320
|
|
220
321
|
@awkward.mixin_class(behavior)
|
221
|
-
class Jet(Particle, base.NanoCollection, base.Systematic):
|
322
|
+
class Jet(Particle, base.NanoCollection, base.Systematic):
|
323
|
+
"""Jet particle collection."""
|
324
|
+
|
325
|
+
@property
|
326
|
+
def mass(self):
|
327
|
+
r"""Invariant mass (+, -, -, -)
|
328
|
+
|
329
|
+
:math:`\sqrt{t^2-x^2-y^2-z^2}`
|
330
|
+
"""
|
331
|
+
return self["m"]
|
222
332
|
|
223
333
|
|
224
334
|
_set_repr_name("Jet")
|
225
335
|
|
226
|
-
JetArray.ProjectionClass2D = vector.TwoVectorArray # noqa: F821
|
227
|
-
JetArray.ProjectionClass3D = vector.ThreeVectorArray # noqa: F821
|
228
|
-
JetArray.ProjectionClass4D = JetArray # noqa: F821
|
229
|
-
JetArray.MomentumClass = vector.LorentzVectorArray # noqa: F821
|
336
|
+
JetArray.ProjectionClass2D = vector.TwoVectorArray # noqa: F821 # pylint: disable=undefined-variable,no-member
|
337
|
+
JetArray.ProjectionClass3D = vector.ThreeVectorArray # noqa: F821 # pylint: disable=undefined-variable,no-member
|
338
|
+
JetArray.ProjectionClass4D = JetArray # noqa: F821 # pylint: disable=undefined-variable
|
339
|
+
JetArray.MomentumClass = vector.LorentzVectorArray # noqa: F821 # pylint: disable=undefined-variable,no-member
|
230
340
|
|
231
341
|
__all__ = [
|
232
342
|
"Electron",
|
233
|
-
"ElectronArray", # noqa: F822
|
234
|
-
"ElectronRecord", # noqa: F822
|
343
|
+
"ElectronArray", # noqa: F822 # pylint: disable=undefined-all-variable
|
344
|
+
"ElectronRecord", # noqa: F822 # pylint: disable=undefined-all-variable
|
235
345
|
"Jet",
|
236
|
-
"JetArray", # noqa: F822
|
237
|
-
"JetRecord", # noqa: F822
|
346
|
+
"JetArray", # noqa: F822 # pylint: disable=undefined-all-variable
|
347
|
+
"JetRecord", # noqa: F822 # pylint: disable=undefined-all-variable
|
238
348
|
"MissingET",
|
239
|
-
"MissingETArray", # noqa: F822
|
240
|
-
"MissingETRecord", # noqa: F822
|
349
|
+
"MissingETArray", # noqa: F822 # pylint: disable=undefined-all-variable
|
350
|
+
"MissingETRecord", # noqa: F822 # pylint: disable=undefined-all-variable
|
241
351
|
"Muon",
|
242
|
-
"MuonArray", # noqa: F822
|
243
|
-
"MuonRecord", # noqa: F822
|
352
|
+
"MuonArray", # noqa: F822 # pylint: disable=undefined-all-variable
|
353
|
+
"MuonRecord", # noqa: F822 # pylint: disable=undefined-all-variable
|
244
354
|
"NtupleEvents",
|
245
355
|
"Particle",
|
246
|
-
"ParticleArray", # noqa: F822
|
247
|
-
"ParticleRecord", # noqa: F822
|
356
|
+
"ParticleArray", # noqa: F822 # pylint: disable=undefined-all-variable
|
357
|
+
"ParticleRecord", # noqa: F822 # pylint: disable=undefined-all-variable
|
248
358
|
"Pass",
|
249
359
|
"Photon",
|
250
|
-
"PhotonArray", # noqa: F822
|
251
|
-
"PhotonRecord", # noqa: F822
|
360
|
+
"PhotonArray", # noqa: F822 # pylint: disable=undefined-all-variable
|
361
|
+
"PhotonRecord", # noqa: F822 # pylint: disable=undefined-all-variable
|
252
362
|
"Weight",
|
253
363
|
]
|
atlas_schema/schema.py
CHANGED
@@ -7,6 +7,7 @@ from typing import Any, ClassVar
|
|
7
7
|
|
8
8
|
from coffea.nanoevents.schemas.base import BaseSchema, zip_forms
|
9
9
|
|
10
|
+
from atlas_schema.methods import behavior as roaster
|
10
11
|
from atlas_schema.typing_compat import Behavior, Self
|
11
12
|
|
12
13
|
|
@@ -170,10 +171,12 @@ class NtupleSchema(BaseSchema): # type: ignore[misc]
|
|
170
171
|
pass
|
171
172
|
else:
|
172
173
|
pass
|
173
|
-
self._form["fields"], self._form["contents"] =
|
174
|
-
self._form["fields"], self._form["contents"]
|
174
|
+
self._form["fields"], self._form["contents"], discovered_systematics = (
|
175
|
+
self._build_collections(self._form["fields"], self._form["contents"])
|
175
176
|
)
|
176
177
|
self._form["parameters"]["metadata"]["version"] = self._version
|
178
|
+
self._form["parameters"]["metadata"]["systematics"] = discovered_systematics
|
179
|
+
self._form["parameters"]["__record__"] = "NtupleEvents"
|
177
180
|
|
178
181
|
@classmethod
|
179
182
|
def v1(cls, base_form: dict[str, Any]) -> Self:
|
@@ -186,11 +189,13 @@ class NtupleSchema(BaseSchema): # type: ignore[misc]
|
|
186
189
|
|
187
190
|
def _build_collections(
|
188
191
|
self, field_names: list[str], input_contents: list[Any]
|
189
|
-
) -> tuple[KeysView[str], ValuesView[dict[str, Any]]]:
|
192
|
+
) -> tuple[KeysView[str], ValuesView[dict[str, Any]], list[str]]:
|
190
193
|
branch_forms = dict(zip(field_names, input_contents))
|
191
194
|
|
192
195
|
# parse into high-level records (collections, list collections, and singletons)
|
193
|
-
collections = {
|
196
|
+
collections = {
|
197
|
+
k.split("_")[0] for k in branch_forms if k not in self.singletons
|
198
|
+
}
|
194
199
|
collections -= self.event_ids
|
195
200
|
collections -= set(self.singletons)
|
196
201
|
|
@@ -227,17 +232,38 @@ class NtupleSchema(BaseSchema): # type: ignore[misc]
|
|
227
232
|
branch_forms[k.replace("_NOSYS", "") + "_NOSYS"] = branch_forms.pop(k)
|
228
233
|
|
229
234
|
# these are collections with systematic variations
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
+
try:
|
236
|
+
subcollections = {
|
237
|
+
k.split("__")[0].split("_", 1)[1].replace("_NOSYS", "")
|
238
|
+
for k in branch_forms
|
239
|
+
if "NOSYS" in k and k not in self.singletons
|
240
|
+
}
|
241
|
+
except IndexError as exc:
|
242
|
+
msg = "One of the branches does not follow the assumed pattern for this schema. [invalid-branch-name]"
|
243
|
+
raise RuntimeError(msg) from exc
|
244
|
+
|
245
|
+
all_systematics = self._discover_systematics(
|
246
|
+
branch_forms, collections, subcollections
|
247
|
+
)
|
248
|
+
|
249
|
+
# Pre-compute systematic branch patterns for O(1) lookups
|
250
|
+
# This replaces the expensive O(m*s) nested condition checks
|
251
|
+
systematic_branch_patterns = set()
|
252
|
+
for collection in collections:
|
253
|
+
for subcoll in subcollections:
|
254
|
+
for sys in all_systematics:
|
255
|
+
if sys != "NOSYS":
|
256
|
+
systematic_branch_patterns.add(f"{collection}_{subcoll}_{sys}")
|
235
257
|
|
236
258
|
# Check the presence of the event_ids
|
237
259
|
missing_event_ids = [
|
238
260
|
event_id for event_id in self.event_ids if event_id not in branch_forms
|
239
261
|
]
|
240
262
|
|
263
|
+
missing_singletons = [
|
264
|
+
singleton for singleton in self.singletons if singleton not in branch_forms
|
265
|
+
]
|
266
|
+
|
241
267
|
if len(missing_event_ids) > 0:
|
242
268
|
if self.error_missing_event_ids:
|
243
269
|
msg = f"There are missing event ID fields: {missing_event_ids} \n\n\
|
@@ -253,99 +279,227 @@ class NtupleSchema(BaseSchema): # type: ignore[misc]
|
|
253
279
|
stacklevel=2,
|
254
280
|
)
|
255
281
|
|
282
|
+
if len(missing_singletons) > 0:
|
283
|
+
# These singletons are simply branches we do not parse or handle
|
284
|
+
# explicitly in atlas-schema (e.g. they are copied directly to the
|
285
|
+
# output structure we provide you), however there can be false
|
286
|
+
# positives when you submit multiple files with different branch
|
287
|
+
# structures and this warning could be safely ignored.
|
288
|
+
warnings.warn(
|
289
|
+
f"Missing singletons : {missing_singletons}. [singleton-missing]",
|
290
|
+
RuntimeWarning,
|
291
|
+
stacklevel=2,
|
292
|
+
)
|
293
|
+
|
256
294
|
output = {}
|
257
295
|
|
258
296
|
# first, register singletons (event-level, others)
|
259
297
|
for name in {*self.event_ids, *self.singletons}:
|
260
|
-
if name in missing_event_ids:
|
298
|
+
if name in [*missing_event_ids, *missing_singletons]:
|
261
299
|
continue
|
300
|
+
|
262
301
|
output[name] = branch_forms[name]
|
263
302
|
|
264
|
-
#
|
265
|
-
|
266
|
-
|
303
|
+
# First, build nominal collections the traditional way
|
304
|
+
nominal_collections = {}
|
305
|
+
for collection_name in collections:
|
306
|
+
collection_content = {}
|
267
307
|
used = set()
|
268
308
|
|
309
|
+
# Process subcollections with NOSYS variations
|
269
310
|
for subname in subcollections:
|
270
|
-
prefix = f"{
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
311
|
+
prefix = f"{collection_name}_{subname}_"
|
312
|
+
nosys_branch = f"{prefix}NOSYS"
|
313
|
+
|
314
|
+
if nosys_branch in branch_forms:
|
315
|
+
collection_content[subname] = branch_forms[nosys_branch]
|
316
|
+
used.add(nosys_branch)
|
317
|
+
|
318
|
+
# Add non-systematic branches (like eta, phi that don't vary)
|
319
|
+
for k, form in branch_forms.items():
|
320
|
+
if (
|
321
|
+
k.startswith(collection_name + "_")
|
322
|
+
and k not in used
|
323
|
+
and "_NOSYS" not in k
|
324
|
+
and k
|
325
|
+
not in systematic_branch_patterns # O(1) lookup instead of O(m*s)
|
326
|
+
):
|
327
|
+
field_name = k[len(collection_name) + 1 :]
|
328
|
+
if field_name not in collection_content:
|
329
|
+
collection_content[field_name] = form
|
330
|
+
|
331
|
+
if collection_content:
|
332
|
+
behavior = self.mixins.get(collection_name, "")
|
333
|
+
if not behavior:
|
334
|
+
behavior = self.suggested_behavior(collection_name)
|
335
|
+
warnings.warn(
|
336
|
+
f"I found a collection with no defined mixin: '{collection_name}'. I will assume behavior: '{behavior}'. To suppress this warning next time, please define mixins for your custom collections. [mixin-undefined]",
|
337
|
+
RuntimeWarning,
|
338
|
+
stacklevel=2,
|
283
339
|
)
|
340
|
+
nominal_collections[collection_name] = zip_forms(
|
341
|
+
collection_content, collection_name, record_name=behavior
|
342
|
+
)
|
343
|
+
nominal_collections[collection_name].setdefault("parameters", {})
|
344
|
+
nominal_collections[collection_name]["parameters"].update(
|
345
|
+
{"collection_name": collection_name}
|
346
|
+
)
|
284
347
|
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
348
|
+
# Add nominal collections to output
|
349
|
+
output.update(nominal_collections)
|
350
|
+
|
351
|
+
# Now build systematic event structures
|
352
|
+
for systematic in all_systematics:
|
353
|
+
if systematic == "NOSYS":
|
354
|
+
continue
|
355
|
+
|
356
|
+
# Check which collections actually have this systematic variation
|
357
|
+
systematic_collections = {}
|
358
|
+
|
359
|
+
for collection_name in collections:
|
360
|
+
# Check if this collection has any systematic branches for this systematic
|
361
|
+
has_systematic_data = False
|
362
|
+
collection_content = {}
|
363
|
+
used = set()
|
364
|
+
|
365
|
+
# Process subcollections with systematic variations
|
366
|
+
for subname in subcollections:
|
367
|
+
prefix = f"{collection_name}_{subname}_"
|
368
|
+
target_branch = f"{prefix}{systematic}"
|
369
|
+
fallback_branch = f"{prefix}NOSYS"
|
370
|
+
|
371
|
+
if target_branch in branch_forms:
|
372
|
+
# Use the systematic variation
|
373
|
+
collection_content[subname] = branch_forms[target_branch]
|
374
|
+
used.add(target_branch)
|
375
|
+
has_systematic_data = True
|
376
|
+
elif fallback_branch in branch_forms:
|
377
|
+
# Fall back to nominal
|
378
|
+
collection_content[subname] = branch_forms[fallback_branch]
|
379
|
+
used.add(fallback_branch)
|
380
|
+
|
381
|
+
# Add non-systematic branches
|
382
|
+
for k, form in branch_forms.items():
|
383
|
+
if (
|
384
|
+
k.startswith(collection_name + "_")
|
385
|
+
and k not in used
|
386
|
+
and "_NOSYS" not in k
|
387
|
+
and k
|
388
|
+
not in systematic_branch_patterns # O(1) lookup instead of O(m*s)
|
389
|
+
):
|
390
|
+
field_name = k[len(collection_name) + 1 :]
|
391
|
+
if field_name not in collection_content:
|
392
|
+
collection_content[field_name] = form
|
393
|
+
|
394
|
+
# If this collection has systematic data or fallback data, include it
|
395
|
+
if collection_content:
|
396
|
+
behavior = self.mixins.get(collection_name, "")
|
397
|
+
if not behavior:
|
398
|
+
behavior = self.suggested_behavior(collection_name)
|
399
|
+
# Only warn once (for nominal collections)
|
400
|
+
|
401
|
+
# If no systematic data, use the nominal collection directly
|
402
|
+
if (
|
403
|
+
not has_systematic_data
|
404
|
+
and collection_name in nominal_collections
|
405
|
+
):
|
406
|
+
systematic_collections[collection_name] = nominal_collections[
|
407
|
+
collection_name
|
408
|
+
]
|
409
|
+
else:
|
410
|
+
# Build the systematic collection
|
411
|
+
systematic_collections[collection_name] = zip_forms(
|
412
|
+
collection_content, collection_name, record_name=behavior
|
413
|
+
)
|
414
|
+
systematic_collections[collection_name].setdefault(
|
415
|
+
"parameters", {}
|
416
|
+
)
|
417
|
+
systematic_collections[collection_name]["parameters"].update(
|
418
|
+
{"collection_name": collection_name}
|
419
|
+
)
|
420
|
+
|
421
|
+
# Only create systematic event if there are collections for it
|
422
|
+
if systematic_collections:
|
423
|
+
output[systematic] = {
|
424
|
+
"class": "RecordArray",
|
425
|
+
"contents": list(systematic_collections.values()),
|
426
|
+
"fields": list(systematic_collections.keys()),
|
427
|
+
"form_key": f"%21invalid%2C{systematic}",
|
428
|
+
"parameters": {
|
429
|
+
"__record__": "Systematic",
|
430
|
+
"metadata": {"systematic": systematic},
|
431
|
+
},
|
290
432
|
}
|
291
|
-
)
|
292
433
|
|
293
|
-
|
434
|
+
# Handle any remaining unrecognized branches as singletons
|
435
|
+
processed_branches = set()
|
436
|
+
# Add event IDs and explicit singletons
|
437
|
+
processed_branches.update(self.event_ids)
|
438
|
+
processed_branches.update(self.singletons)
|
439
|
+
# Add collection-related branches
|
440
|
+
for collection_name in collections:
|
441
|
+
for branch_name in branch_forms:
|
442
|
+
if branch_name.startswith(collection_name + "_"):
|
443
|
+
processed_branches.add(branch_name)
|
444
|
+
|
445
|
+
# Find unrecognized branches
|
446
|
+
for branch_name, form in branch_forms.items():
|
447
|
+
if branch_name not in processed_branches:
|
448
|
+
# This is an unrecognized branch - treat as singleton with warning
|
294
449
|
warnings.warn(
|
295
|
-
f"I identified a branch that likely does not have any leaves: '{
|
450
|
+
f"I identified a branch that likely does not have any leaves: '{branch_name}'. I will treat this as a 'singleton'. To suppress this warning, add this branch to the singletons set. [singleton-undefined]",
|
296
451
|
RuntimeWarning,
|
297
452
|
stacklevel=2,
|
298
453
|
)
|
299
|
-
|
300
|
-
output[name] = branch_forms[name]
|
454
|
+
output[branch_name] = form
|
301
455
|
|
302
|
-
|
303
|
-
|
304
|
-
if not behavior:
|
305
|
-
behavior = self.suggested_behavior(name)
|
306
|
-
warnings.warn(
|
307
|
-
f"I found a collection with no defined mixin: '{name}'. I will assume behavior: '{behavior}'. To suppress this warning next time, please define mixins for your custom collections. [mixin-undefined]",
|
308
|
-
RuntimeWarning,
|
309
|
-
stacklevel=2,
|
310
|
-
)
|
456
|
+
# Return discovered systematics (excluding NOSYS/nominal)
|
457
|
+
discovered_systematics = sorted([s for s in all_systematics if s != "NOSYS"])
|
311
458
|
|
312
|
-
|
313
|
-
|
314
|
-
output[name].setdefault("parameters", {})
|
315
|
-
output[name]["parameters"].update({"collection_name": name})
|
316
|
-
|
317
|
-
if output[name]["class"] == "ListOffsetArray":
|
318
|
-
if output[name]["class"] == "RecordArray":
|
319
|
-
parameters = output[name]["content"]["fields"]
|
320
|
-
contents = output[name]["content"]["contents"]
|
321
|
-
else:
|
322
|
-
# these are also singletons of another kind that we just pass through
|
323
|
-
continue
|
324
|
-
elif output[name]["class"] == "RecordArray":
|
325
|
-
parameters = output[name]["fields"]
|
326
|
-
contents = output[name]["contents"]
|
327
|
-
elif output[name]["class"] == "NumpyArray":
|
328
|
-
# these are singletons that we just pass through
|
329
|
-
continue
|
330
|
-
else:
|
331
|
-
msg = f"Unhandled class {output[name]['class']}"
|
332
|
-
raise RuntimeError(msg)
|
459
|
+
return output.keys(), output.values(), discovered_systematics
|
333
460
|
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
461
|
+
def _discover_systematics(
|
462
|
+
self,
|
463
|
+
branch_forms: dict[str, Any],
|
464
|
+
collections: set[str],
|
465
|
+
subcollections: set[str],
|
466
|
+
) -> set[str]:
|
467
|
+
"""Extract systematic variations from branch names.
|
468
|
+
|
469
|
+
Returns:
|
470
|
+
set: Set of all systematic variation names found in branches
|
471
|
+
"""
|
472
|
+
# Optimize systematic discovery: pre-index branches by pattern
|
473
|
+
# This avoids O(n*m) nested loops in systematic discovery
|
474
|
+
subcoll_patterns = {f"{subcoll}_" for subcoll in subcollections}
|
475
|
+
|
476
|
+
all_systematics = set()
|
477
|
+
for k in branch_forms:
|
478
|
+
if not ("_" in k and k not in self.singletons):
|
479
|
+
continue
|
480
|
+
# Handle the pattern: collection_subcollection_systematic
|
481
|
+
# where systematic can contain double underscores like "JET_EnergyResolution__1up"
|
482
|
+
parts = k.split("_")
|
483
|
+
if len(parts) < 3:
|
484
|
+
continue
|
485
|
+
# Find the collection and subcollection parts
|
486
|
+
collection = parts[0]
|
487
|
+
if collection not in collections:
|
488
|
+
continue
|
489
|
+
# Find where the subcollection ends by looking for a known pattern
|
490
|
+
# The systematic starts after the subcollection
|
491
|
+
remaining = "_".join(parts[1:])
|
492
|
+
# Use optimized lookup instead of iterating all subcollections
|
493
|
+
for pattern in subcoll_patterns:
|
494
|
+
if remaining.startswith(pattern):
|
495
|
+
systematic = remaining[len(pattern) :]
|
496
|
+
if systematic and systematic != "NOSYS":
|
497
|
+
all_systematics.add(systematic)
|
498
|
+
break
|
347
499
|
|
348
|
-
|
500
|
+
# Always include NOSYS as the nominal case
|
501
|
+
all_systematics.add("NOSYS")
|
502
|
+
return all_systematics
|
349
503
|
|
350
504
|
@classmethod
|
351
505
|
def behavior(cls) -> Behavior:
|
@@ -354,8 +508,6 @@ class NtupleSchema(BaseSchema): # type: ignore[misc]
|
|
354
508
|
Returns:
|
355
509
|
dict[str | tuple['*', str], type[awkward.Record]]: an :data:`awkward.behavior` dictionary
|
356
510
|
"""
|
357
|
-
from atlas_schema.methods import behavior as roaster
|
358
|
-
|
359
511
|
return roaster
|
360
512
|
|
361
513
|
@classmethod
|
atlas_schema/utils.py
CHANGED
@@ -1,16 +1,15 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
from enum import Enum
|
4
|
-
from typing import TypeVar,
|
4
|
+
from typing import TypeVar, cast
|
5
5
|
|
6
6
|
import awkward as ak
|
7
|
-
import dask_awkward as dak
|
8
7
|
|
9
|
-
Array = TypeVar("Array", bound=
|
8
|
+
Array = TypeVar("Array", bound=ak.Array)
|
10
9
|
_E = TypeVar("_E", bound=Enum)
|
11
10
|
|
12
11
|
|
13
|
-
def isin(element: Array, test_elements:
|
12
|
+
def isin(element: Array, test_elements: ak.Array, axis: int = -1) -> Array:
|
14
13
|
"""
|
15
14
|
Find test_elements in element. Similar in API as :func:`numpy.isin`.
|
16
15
|
|
@@ -21,12 +20,12 @@ def isin(element: Array, test_elements: dak.Array | ak.Array, axis: int = -1) ->
|
|
21
20
|
comparison.
|
22
21
|
|
23
22
|
Args:
|
24
|
-
element (
|
25
|
-
test_elements (
|
23
|
+
element (ak.Array): input array of values.
|
24
|
+
test_elements (ak.Array): one-dimensional set of values against which to test each value of *element*.
|
26
25
|
axis (int): the axis along which the comparison is performed
|
27
26
|
|
28
27
|
Returns:
|
29
|
-
|
28
|
+
ak.Array: result of comparison for test_elements in *element*
|
30
29
|
|
31
30
|
Example:
|
32
31
|
>>> import awkward as ak
|
@@ -1,11 +1,11 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: atlas-schema
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.4.0
|
4
4
|
Summary: Helper python package for ATLAS Common NTuple Analysis work.
|
5
5
|
Project-URL: Homepage, https://github.com/scipp-atlas/atlas-schema
|
6
6
|
Project-URL: Bug Tracker, https://github.com/scipp-atlas/atlas-schema/issues
|
7
7
|
Project-URL: Discussions, https://github.com/scipp-atlas/atlas-schema/discussions
|
8
|
-
Project-URL: Documentation, https://atlas-schema.readthedocs.io/en/v0.
|
8
|
+
Project-URL: Documentation, https://atlas-schema.readthedocs.io/en/v0.4.0/
|
9
9
|
Project-URL: Releases, https://github.com/scipp-atlas/atlas-schema/releases
|
10
10
|
Project-URL: Release Notes, https://atlas-schema.readthedocs.io/en/latest/history.html
|
11
11
|
Author-email: Giordon Stark <kratsg@gmail.com>
|
@@ -227,7 +227,7 @@ Classifier: Programming Language :: Python :: 3.12
|
|
227
227
|
Classifier: Topic :: Scientific/Engineering
|
228
228
|
Classifier: Typing :: Typed
|
229
229
|
Requires-Python: >=3.9
|
230
|
-
Requires-Dist: coffea[dask]>=
|
230
|
+
Requires-Dist: coffea[dask]>=2025.7.0
|
231
231
|
Requires-Dist: particle>=0.25.0
|
232
232
|
Provides-Extra: dev
|
233
233
|
Requires-Dist: pytest-cov>=3; extra == 'dev'
|
@@ -251,7 +251,7 @@ Requires-Dist: tbump>=6.7.0; extra == 'test'
|
|
251
251
|
Requires-Dist: twine; extra == 'test'
|
252
252
|
Description-Content-Type: text/markdown
|
253
253
|
|
254
|
-
# atlas-schema v0.
|
254
|
+
# atlas-schema v0.4.0
|
255
255
|
|
256
256
|
[![Actions Status][actions-badge]][actions-link]
|
257
257
|
[![Documentation Status][rtd-badge]][rtd-link]
|
@@ -335,11 +335,9 @@ like below:
|
|
335
335
|
|
336
336
|
```python
|
337
337
|
import awkward as ak
|
338
|
-
import
|
339
|
-
import hist.dask as had
|
338
|
+
from hist import Hist
|
340
339
|
import matplotlib.pyplot as plt
|
341
340
|
from coffea import processor
|
342
|
-
from coffea.nanoevents import NanoEventsFactory
|
343
341
|
from distributed import Client
|
344
342
|
|
345
343
|
from atlas_schema.schema import NtupleSchema
|
@@ -352,7 +350,7 @@ class MyFirstProcessor(processor.ProcessorABC):
|
|
352
350
|
def process(self, events):
|
353
351
|
dataset = events.metadata["dataset"]
|
354
352
|
h_ph_pt = (
|
355
|
-
|
353
|
+
Hist.new.StrCat(["all", "pass", "fail"], name="isEM")
|
356
354
|
.Regular(200, 0.0, 2000.0, name="pt", label="$pt_{\gamma}$ [GeV]")
|
357
355
|
.Int64()
|
358
356
|
)
|
@@ -376,17 +374,18 @@ class MyFirstProcessor(processor.ProcessorABC):
|
|
376
374
|
if __name__ == "__main__":
|
377
375
|
client = Client()
|
378
376
|
|
379
|
-
|
380
|
-
events = NanoEventsFactory.from_root(
|
381
|
-
{fname: "analysis"},
|
382
|
-
schemaclass=NtupleSchema,
|
383
|
-
metadata={"dataset": "700352.Zqqgamma.mc20d.v1"},
|
384
|
-
).events()
|
377
|
+
fileset = {"700352.Zqqgamma.mc20d.v1": {"files": {"ntuple.root": "analysis"}}}
|
385
378
|
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
379
|
+
run = processor.Runner(
|
380
|
+
executor=processor.IterativeExecutor(compression=None),
|
381
|
+
schema=NtupleSchema,
|
382
|
+
savemetrics=True,
|
383
|
+
)
|
384
|
+
|
385
|
+
out, metrics = run(fileset, processor_instance=MyFirstProcessor())
|
386
|
+
|
387
|
+
print(out)
|
388
|
+
print(metrics)
|
390
389
|
|
391
390
|
fig, ax = plt.subplots()
|
392
391
|
computed["700352.Zqqgamma.mc20d.v1"]["ph_pt"].plot1d(ax=ax)
|
@@ -400,6 +399,57 @@ which produces
|
|
400
399
|
|
401
400
|
<img src="https://raw.githubusercontent.com/scipp-atlas/atlas-schema/main/docs/_static/img/ph_pt.png" alt="three stacked histograms of photon pT, with each stack corresponding to: no selection, requiring the isEM flag, and inverting the isEM requirement" width="500" style="display: block; margin-left: auto; margin-right: auto;">
|
402
401
|
|
402
|
+
## Processing with Systematic Variations
|
403
|
+
|
404
|
+
For analyses requiring systematic uncertainty evaluation, you can easily iterate
|
405
|
+
over all systematic variations using the new `events["NOSYS"]` alias and
|
406
|
+
`systematic_names` property:
|
407
|
+
|
408
|
+
```python
|
409
|
+
import awkward as ak
|
410
|
+
from hist import Hist
|
411
|
+
from coffea import processor
|
412
|
+
from atlas_schema.schema import NtupleSchema
|
413
|
+
|
414
|
+
|
415
|
+
class SystematicsProcessor(processor.ProcessorABC):
|
416
|
+
def __init__(self):
|
417
|
+
self.h = (
|
418
|
+
Hist.new.StrCat([], name="variation", growth=True)
|
419
|
+
.Regular(50, 0.0, 500.0, name="jet_pt", label="Leading Jet $p_T$ [GeV]")
|
420
|
+
.Int64()
|
421
|
+
)
|
422
|
+
|
423
|
+
def process(self, events):
|
424
|
+
dsid = events.metadata["dataset"]
|
425
|
+
|
426
|
+
# Process all systematic variations including nominal ("NOSYS")
|
427
|
+
for variation in events.systematic_names:
|
428
|
+
event_view = events[variation]
|
429
|
+
|
430
|
+
# Fill histogram with leading jet pT for this systematic variation
|
431
|
+
leading_jet_pt = event_view.jet.pt[:, 0] / 1_000 # Convert MeV to GeV
|
432
|
+
weights = (
|
433
|
+
event_view.weight.mc
|
434
|
+
if hasattr(event_view, "weight")
|
435
|
+
else ak.ones_like(leading_jet_pt)
|
436
|
+
)
|
437
|
+
|
438
|
+
self.h.fill(variation=variation, jet_pt=leading_jet_pt, weight=weights)
|
439
|
+
|
440
|
+
return {
|
441
|
+
"hist": self.h,
|
442
|
+
"meta": {"sumw": {dsid: {(events.metadata["fileuuid"], ak.sum(weights))}}},
|
443
|
+
}
|
444
|
+
|
445
|
+
def postprocess(self, accumulator):
|
446
|
+
return accumulator
|
447
|
+
```
|
448
|
+
|
449
|
+
This approach allows you to seamlessly process both nominal and systematic
|
450
|
+
variations in a single loop, eliminating the need for special-case handling of
|
451
|
+
the nominal variation.
|
452
|
+
|
403
453
|
<!-- SPHINX-END -->
|
404
454
|
|
405
455
|
## Developer Notes
|
@@ -0,0 +1,13 @@
|
|
1
|
+
atlas_schema/__init__.py,sha256=ebY-rTiwSGnfvt1yWATze2GE7K3fVgJj6fT64Sl4sH8,469
|
2
|
+
atlas_schema/_version.py,sha256=2_0GUP7yBCXRus-qiJKxQD62z172WSs1sQ6DVpPsbmM,704
|
3
|
+
atlas_schema/_version.pyi,sha256=j5kbzfm6lOn8BzASXWjGIA1yT0OlHTWqlbyZ8Si_o0E,118
|
4
|
+
atlas_schema/enums.py,sha256=GDDKSBZY-L8X5W41Kwi0G5Yd4Vu4Kiga-ttSbztEXEM,3687
|
5
|
+
atlas_schema/methods.py,sha256=rQRQgD26ndCzwpxAuAeEbXIHd8v64cK2rP5A5GxvBn8,12934
|
6
|
+
atlas_schema/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
7
|
+
atlas_schema/schema.py,sha256=TbNxekA0DRwS5Mye0frHo7p1K7LW7FBXrkEwmPUQ8MA,27884
|
8
|
+
atlas_schema/typing_compat.py,sha256=3G8h4WfLoDmrtWZvtYKLCwEpCQ_O4Fwygb2WlDRSE4E,488
|
9
|
+
atlas_schema/utils.py,sha256=E3jCka-pf_0h_r3OO0hMLlbF6dQKoxr2T1Gd18-aJ4U,2034
|
10
|
+
atlas_schema-0.4.0.dist-info/METADATA,sha256=9d1QN2OaZ0i68ZDAR3GBKUNw9Lm4l1nAJycCb9jSmqg,21755
|
11
|
+
atlas_schema-0.4.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
12
|
+
atlas_schema-0.4.0.dist-info/licenses/LICENSE,sha256=snem82NV8fgAi4DKaaUIfReaM5RqIWbH5OOXOvy40_w,11344
|
13
|
+
atlas_schema-0.4.0.dist-info/RECORD,,
|
@@ -1,13 +0,0 @@
|
|
1
|
-
atlas_schema/__init__.py,sha256=ebY-rTiwSGnfvt1yWATze2GE7K3fVgJj6fT64Sl4sH8,469
|
2
|
-
atlas_schema/_version.py,sha256=4gL0W4-u58XR5lRLpeoIPrGhcewTk0-527de6uTNmkg,411
|
3
|
-
atlas_schema/_version.pyi,sha256=j5kbzfm6lOn8BzASXWjGIA1yT0OlHTWqlbyZ8Si_o0E,118
|
4
|
-
atlas_schema/enums.py,sha256=hwgOvFBmITNxL0MQkrNpbiPv9VMezFoE-eyGgjzem8E,3688
|
5
|
-
atlas_schema/methods.py,sha256=hFdtKXnyCcx4M05WhAM24fKwzEhh_ubA7jNa6_xv67k,7238
|
6
|
-
atlas_schema/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
7
|
-
atlas_schema/schema.py,sha256=4OAvuPrOds-taVES32y4K8dvNDf8PKdu83DZqAlTdp8,20621
|
8
|
-
atlas_schema/typing_compat.py,sha256=3G8h4WfLoDmrtWZvtYKLCwEpCQ_O4Fwygb2WlDRSE4E,488
|
9
|
-
atlas_schema/utils.py,sha256=IqMbWqq0ib_kZdJCaM5ghURZatmb8pKidlewx3dpy0A,2164
|
10
|
-
atlas_schema-0.2.4.dist-info/METADATA,sha256=KZDH5fsZon5wFXuU-iSUeqgjoplOwAoqTM1I9LgaTiM,20107
|
11
|
-
atlas_schema-0.2.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
12
|
-
atlas_schema-0.2.4.dist-info/licenses/LICENSE,sha256=snem82NV8fgAi4DKaaUIfReaM5RqIWbH5OOXOvy40_w,11344
|
13
|
-
atlas_schema-0.2.4.dist-info/RECORD,,
|
File without changes
|
File without changes
|