atlas-schema 0.2.0__tar.gz → 0.2.2__tar.gz

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.
@@ -70,6 +70,7 @@ instance/
70
70
 
71
71
  # Sphinx documentation
72
72
  docs/_build/
73
+ docs/_generated/
73
74
 
74
75
  # PyBuilder
75
76
  .pybuilder/
@@ -1,11 +1,13 @@
1
- Metadata-Version: 2.3
1
+ Metadata-Version: 2.4
2
2
  Name: atlas-schema
3
- Version: 0.2.0
3
+ Version: 0.2.2
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: Changelog, https://github.com/scipp-atlas/atlas-schema/releases
8
+ Project-URL: Documentation, https://atlas-schema.readthedocs.io/en/v0.2.2/
9
+ Project-URL: Releases, https://github.com/scipp-atlas/atlas-schema/releases
10
+ Project-URL: Release Notes, https://atlas-schema.readthedocs.io/en/latest/history.html
9
11
  Author-email: Giordon Stark <kratsg@gmail.com>
10
12
  License:
11
13
  Apache License
@@ -209,6 +211,7 @@ License:
209
211
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
210
212
  See the License for the specific language governing permissions and
211
213
  limitations under the License.
214
+ License-File: LICENSE
212
215
  Classifier: Development Status :: 1 - Planning
213
216
  Classifier: Intended Audience :: Developers
214
217
  Classifier: Intended Audience :: Science/Research
@@ -225,14 +228,19 @@ Classifier: Topic :: Scientific/Engineering
225
228
  Classifier: Typing :: Typed
226
229
  Requires-Python: >=3.9
227
230
  Requires-Dist: coffea[dask]>=2024.4.1
231
+ Requires-Dist: particle>=0.25.0
228
232
  Provides-Extra: dev
229
233
  Requires-Dist: pytest-cov>=3; extra == 'dev'
230
234
  Requires-Dist: pytest>=6; extra == 'dev'
231
235
  Provides-Extra: docs
232
236
  Requires-Dist: furo>=2023.08.17; extra == 'docs'
237
+ Requires-Dist: intersphinx-registry>=0.2411.17; extra == 'docs'
238
+ Requires-Dist: ipywidgets; extra == 'docs'
233
239
  Requires-Dist: myst-parser>=0.13; extra == 'docs'
234
240
  Requires-Dist: sphinx-autodoc-typehints; extra == 'docs'
235
- Requires-Dist: sphinx-copybutton; extra == 'docs'
241
+ Requires-Dist: sphinx-click; extra == 'docs'
242
+ Requires-Dist: sphinx-copybutton!=0.5.1,>=0.3.2; extra == 'docs'
243
+ Requires-Dist: sphinx-issues; extra == 'docs'
236
244
  Requires-Dist: sphinx>=7.0; extra == 'docs'
237
245
  Provides-Extra: test
238
246
  Requires-Dist: build; extra == 'test'
@@ -243,7 +251,7 @@ Requires-Dist: tbump>=6.7.0; extra == 'test'
243
251
  Requires-Dist: twine; extra == 'test'
244
252
  Description-Content-Type: text/markdown
245
253
 
246
- # atlas-schema v0.2.0
254
+ # atlas-schema v0.2.2
247
255
 
248
256
  [![Actions Status][actions-badge]][actions-link]
249
257
  [![Documentation Status][rtd-badge]][rtd-link]
@@ -1,4 +1,4 @@
1
- # atlas-schema v0.2.0
1
+ # atlas-schema v0.2.2
2
2
 
3
3
  [![Actions Status][actions-badge]][actions-link]
4
4
  [![Documentation Status][rtd-badge]][rtd-link]
@@ -28,7 +28,7 @@ classifiers = [
28
28
  "Typing :: Typed",
29
29
  ]
30
30
  dynamic = ["version"]
31
- dependencies = ["coffea[dask] >= 2024.4.1"]
31
+ dependencies = ["coffea[dask] >= 2024.4.1", "particle >= 0.25.0"]
32
32
 
33
33
  [project.optional-dependencies]
34
34
  test = [
@@ -47,16 +47,22 @@ dev = [
47
47
  docs = [
48
48
  "sphinx>=7.0",
49
49
  "myst_parser>=0.13",
50
- "sphinx_copybutton",
50
+ "sphinx-copybutton>=0.3.2,!=0.5.1",
51
51
  "sphinx_autodoc_typehints",
52
52
  "furo>=2023.08.17",
53
+ "sphinx-click",
54
+ "ipywidgets",
55
+ "intersphinx_registry>=0.2411.17",
56
+ "sphinx-issues",
53
57
  ]
54
58
 
55
59
  [project.urls]
56
60
  Homepage = "https://github.com/scipp-atlas/atlas-schema"
57
61
  "Bug Tracker" = "https://github.com/scipp-atlas/atlas-schema/issues"
58
62
  Discussions = "https://github.com/scipp-atlas/atlas-schema/discussions"
59
- Changelog = "https://github.com/scipp-atlas/atlas-schema/releases"
63
+ Documentation = "https://atlas-schema.readthedocs.io/en/v0.2.2/"
64
+ Releases = "https://github.com/scipp-atlas/atlas-schema/releases"
65
+ "Release Notes" = "https://atlas-schema.readthedocs.io/en/latest/history.html"
60
66
 
61
67
 
62
68
  [tool.hatch]
@@ -90,7 +96,19 @@ packages = ["src/atlas_schema"]
90
96
 
91
97
  [tool.pytest.ini_options]
92
98
  minversion = "6.0"
93
- addopts = ["-ra", "--showlocals", "--strict-markers", "--strict-config"]
99
+ addopts = [
100
+ "-ra",
101
+ "--showlocals",
102
+ "--strict-markers",
103
+ "--strict-config",
104
+ "--doctest-modules",
105
+ "--doctest-glob=*.rst",
106
+ "--cov",
107
+ "--cov-report=xml",
108
+ "--cov-report=term",
109
+ "--durations=20",
110
+ "--ignore=docs/conf.py",
111
+ ]
94
112
  xfail_strict = true
95
113
  filterwarnings = [
96
114
  "error",
@@ -98,9 +116,9 @@ filterwarnings = [
98
116
  log_cli_level = "INFO"
99
117
  testpaths = [
100
118
  "tests",
119
+ "docs",
101
120
  ]
102
121
 
103
-
104
122
  [tool.coverage]
105
123
  run.source = ["atlas_schema"]
106
124
  report.exclude_also = [
@@ -109,6 +127,7 @@ report.exclude_also = [
109
127
  ]
110
128
 
111
129
  [tool.mypy]
130
+ mypy_path = ["src"]
112
131
  files = ["src", "tests"]
113
132
  python_version = "3.9"
114
133
  warn_unused_configs = true
@@ -129,6 +148,7 @@ disallow_incomplete_defs = true
129
148
  module = [
130
149
  'awkward.*',
131
150
  'coffea.*',
151
+ 'dask_awkward.*',
132
152
  ]
133
153
  ignore_missing_imports = true
134
154
 
@@ -6,7 +6,12 @@ atlas_schema: Collection of utilities and helper functions for HEP ATLAS analyse
6
6
 
7
7
  from __future__ import annotations
8
8
 
9
+ import warnings
10
+
9
11
  from atlas_schema._version import version as __version__
10
12
  from atlas_schema.enums import ParticleOrigin, PhotonID
13
+ from atlas_schema.utils import isin
14
+
15
+ warnings.filterwarnings("ignore", module="coffea.*")
11
16
 
12
- __all__ = ["__version__", "ParticleOrigin", "PhotonID"]
17
+ __all__ = ["ParticleOrigin", "PhotonID", "__version__", "isin"]
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '0.2.0'
16
- __version_tuple__ = version_tuple = (0, 2, 0)
15
+ __version__ = version = '0.2.2'
16
+ __version_tuple__ = version_tuple = (0, 2, 2)
@@ -0,0 +1,145 @@
1
+ from __future__ import annotations
2
+
3
+ import sys
4
+ from enum import Enum, IntEnum
5
+
6
+ if sys.version_info >= (3, 11):
7
+ from enum import EnumType
8
+ else:
9
+ from enum import EnumMeta as EnumType
10
+
11
+ from typing import Callable, TypeVar, cast
12
+
13
+ _E = TypeVar("_E", bound=Enum)
14
+
15
+
16
+ class MultipleEnumAccessMeta(EnumType):
17
+ """
18
+ Enum Metaclass to provide a way to access multiple values all at once.
19
+ """
20
+
21
+ def __getitem__(self: type[_E], key: str | tuple[str]) -> _E | list[_E]: # type:ignore[misc,override]
22
+ getitem = cast(Callable[[str], _E], super().__getitem__) # type:ignore[misc]
23
+ if isinstance(key, tuple):
24
+ return [getitem(name) for name in key]
25
+ return getitem(key)
26
+
27
+
28
+ class ParticleType(IntEnum, metaclass=MultipleEnumAccessMeta):
29
+ """
30
+ Taken from `ATLAS Truth Utilities for ParticleType <https://gitlab.cern.ch/atlas/athena/-/blob/74f43ff0910edb2a2bd3778880ccbdad648dc037/Generators/TruthUtils/TruthUtils/TruthClasses.h#L8-49>`_.
31
+ """
32
+
33
+ Unknown = 0
34
+ UnknownElectron = 1
35
+ IsoElectron = 2
36
+ NonIsoElectron = 3
37
+ BkgElectron = 4
38
+ UnknownMuon = 5
39
+ IsoMuon = 6
40
+ NonIsoMuon = 7
41
+ BkgMuon = 8
42
+ UnknownTau = 9
43
+ IsoTau = 10
44
+ NonIsoTau = 11
45
+ BkgTau = 12
46
+ UnknownPhoton = 13
47
+ IsoPhoton = 14
48
+ NonIsoPhoton = 15
49
+ BkgPhoton = 16
50
+ Hadron = 17
51
+ Neutrino = 18
52
+ NuclFrag = 19
53
+ NonPrimary = 20
54
+ GenParticle = 21
55
+ SUSYParticle = 22
56
+ OtherBSMParticle = 39
57
+ BBbarMesonPart = 23
58
+ BottomMesonPart = 24
59
+ CCbarMesonPart = 25
60
+ CharmedMesonPart = 26
61
+ BottomBaryonPart = 27
62
+ CharmedBaryonPart = 28
63
+ StrangeBaryonPart = 29
64
+ LightBaryonPart = 30
65
+ StrangeMesonPart = 31
66
+ LightMesonPart = 32
67
+ BJet = 33
68
+ CJet = 34
69
+ LJet = 35
70
+ GJet = 36
71
+ TauJet = 37
72
+ UnknownJet = 38
73
+
74
+
75
+ class ParticleOrigin(IntEnum, metaclass=MultipleEnumAccessMeta):
76
+ """
77
+ Taken from `ATLAS Truth Utilities for ParticleOrigin <https://gitlab.cern.ch/atlas/athena/-/blob/74f43ff0910edb2a2bd3778880ccbdad648dc037/Generators/TruthUtils/TruthUtils/TruthClasses.h#L51-103>`_.
78
+ """
79
+
80
+ NonDefined = 0
81
+ SingleElec = 1
82
+ SingleMuon = 2
83
+ SinglePhot = 3
84
+ SingleTau = 4
85
+ PhotonConv = 5
86
+ DalitzDec = 6
87
+ ElMagProc = 7
88
+ Mu = 8
89
+ TauLep = 9
90
+ top = 10
91
+ QuarkWeakDec = 11
92
+ WBoson = 12
93
+ ZBoson = 13
94
+ Higgs = 14
95
+ HiggsMSSM = 15
96
+ HeavyBoson = 16
97
+ WBosonLRSM = 17
98
+ NuREle = 18
99
+ NuRMu = 19
100
+ NuRTau = 20
101
+ LQ = 21
102
+ SUSY = 22
103
+ OtherBSM = 46
104
+ LightMeson = 23
105
+ StrangeMeson = 24
106
+ CharmedMeson = 25
107
+ BottomMeson = 26
108
+ CCbarMeson = 27
109
+ JPsi = 28
110
+ BBbarMeson = 29
111
+ LightBaryon = 30
112
+ StrangeBaryon = 31
113
+ CharmedBaryon = 32
114
+ BottomBaryon = 33
115
+ PionDecay = 34
116
+ KaonDecay = 35
117
+ BremPhot = 36
118
+ PromptPhot = 37
119
+ UndrPhot = 38
120
+ ISRPhot = 39
121
+ FSRPhot = 40
122
+ NucReact = 41
123
+ PiZero = 42
124
+ DiBoson = 43
125
+ ZorHeavyBoson = 44
126
+ MultiBoson = 47
127
+ QCD = 45
128
+
129
+
130
+ class PhotonID(IntEnum, metaclass=MultipleEnumAccessMeta):
131
+ """
132
+ Taken from the `EGamma Identification CP group's twiki <https://twiki.cern.ch/twiki/bin/viewauth/AtlasProtected/EGammaIdentificationRun2#Photon_isEM_word>`_.
133
+ """
134
+
135
+ Rhad = 10 # ClusterHadronicLeakage_Photon
136
+ E277 = 11 # ClusterMiddleEnergy_Photon
137
+ Reta = 12 # ClusterMiddleEratio37_Photon
138
+ Rphi = 13 # ClusterMiddleEratio33_Photon
139
+ Weta2 = 14 # ClusterMiddleWidth_Photon
140
+ f1 = 15 # ClusterStripsEratio_Photon
141
+ DeltaE = 17 # ClusterStripsDeltaE_Photon
142
+ Wstot = 18 # ClusterStripsWtot_Photon
143
+ fside = 19 # ClusterStripsFracm_Photon
144
+ Ws3 = 20 # ClusterStripsWeta1c_Photon
145
+ ERatio = 21 # ClusterStripsDEmaxs1_Photon
@@ -0,0 +1,241 @@
1
+ """Mixins for the Ntuple schema"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from functools import reduce
6
+ from operator import ior
7
+
8
+ import awkward
9
+ import particle
10
+ from coffea.nanoevents.methods import base, candidate, vector
11
+ from dask_awkward import dask_method
12
+
13
+ from atlas_schema.enums import PhotonID
14
+ from atlas_schema.typing_compat import Behavior
15
+
16
+ behavior: Behavior = {}
17
+ behavior.update(base.behavior)
18
+ # vector behavior is included in candidate behavior
19
+ behavior.update(candidate.behavior)
20
+
21
+
22
+ class NtupleEvents(behavior["NanoEvents"]): # type: ignore[misc, valid-type, name-defined]
23
+ def __repr__(self):
24
+ return f"<event {getattr(self, 'runNumber', '??')}:\
25
+ {getattr(self, 'eventNumber', '??')}:\
26
+ {getattr(self, 'mcChannelNumber', '??')}>"
27
+
28
+
29
+ behavior["NanoEvents"] = NtupleEvents
30
+
31
+
32
+ def _set_repr_name(classname):
33
+ def namefcn(_self):
34
+ return classname
35
+
36
+ behavior[("__typestr__", classname)] = classname[0].lower() + classname[1:]
37
+ behavior[classname].__repr__ = namefcn
38
+
39
+
40
+ @awkward.mixin_class(behavior)
41
+ class Weight(base.NanoCollection, base.Systematic): ...
42
+
43
+
44
+ _set_repr_name("Weight")
45
+
46
+
47
+ @awkward.mixin_class(behavior)
48
+ class Pass(base.NanoCollection, base.Systematic): ...
49
+
50
+
51
+ _set_repr_name("Pass")
52
+
53
+ behavior.update(
54
+ awkward._util.copy_behaviors("PtEtaPhiMLorentzVector", "Particle", behavior)
55
+ )
56
+
57
+
58
+ @awkward.mixin_class(behavior)
59
+ class Particle(vector.PtEtaPhiMLorentzVector):
60
+ """Generic particle collection that has Lorentz vector properties
61
+
62
+ Also handles the following additional branches:
63
+ - '{obj}_select'
64
+ """
65
+
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
+ def passes(self, name):
76
+ return self[f"select_{name}"] == 1
77
+
78
+ @passes.dask
79
+ def passes(self, dask_array, name):
80
+ return dask_array[f"select_{name}"] == 1
81
+
82
+ # NB: fields with the name 'pt' take precedence over this
83
+ # @dask_property
84
+ # def pt(self):
85
+ # print('inside non-dask prop')
86
+ # return self["pt_NOSYS"]
87
+
88
+ # @pt.dask
89
+ # def pt(self, dask_array):
90
+ # branch = 'pt'
91
+ # print('inside dask prop')
92
+ # variation = dask_array._events().metadata.get("systematic", "NOSYS")
93
+ # with contextlib.suppress(Exception):
94
+ # return dask_array[f"{branch}_{variation}"]
95
+
96
+ # if variation != "NOSYS":
97
+ # with contextlib.suppress(Exception):
98
+ # return dask_array[f"{branch}_NOSYS"]
99
+
100
+ # return dask_array[branch]
101
+
102
+
103
+ _set_repr_name("Particle")
104
+
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
109
+
110
+
111
+ behavior.update(awkward._util.copy_behaviors("PolarTwoVector", "MissingET", behavior))
112
+
113
+
114
+ @awkward.mixin_class(behavior)
115
+ class MissingET(vector.PolarTwoVector, base.NanoCollection, base.Systematic):
116
+ @property
117
+ def r(self):
118
+ """Distance from origin in XY plane"""
119
+ return self["met"]
120
+
121
+
122
+ _set_repr_name("MissingET")
123
+
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
128
+
129
+ behavior.update(awkward._util.copy_behaviors("Particle", "Photon", behavior))
130
+
131
+
132
+ @awkward.mixin_class(behavior)
133
+ class Photon(Particle, base.NanoCollection, base.Systematic):
134
+ @property
135
+ def mass(self):
136
+ """Return zero mass for photon."""
137
+ return awkward.zeros_like(self.pt)
138
+
139
+ @property
140
+ def charge(self):
141
+ """Return zero charge for photon."""
142
+ return awkward.zeros_like(self.pt)
143
+
144
+ @property
145
+ def isEM(self):
146
+ return self.isEM_syst.NOSYS == 0
147
+
148
+ def pass_isEM(self, words: list[PhotonID]):
149
+ # 0 is pass, 1 is fail
150
+ return (
151
+ self.isEM_syst.NOSYS & reduce(ior, (1 << word.value for word in words))
152
+ ) == 0
153
+
154
+
155
+ _set_repr_name("Photon")
156
+
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
161
+
162
+ behavior.update(awkward._util.copy_behaviors("Particle", "Electron", behavior))
163
+
164
+
165
+ @awkward.mixin_class(behavior)
166
+ class Electron(Particle, base.NanoCollection, base.Systematic):
167
+ @property
168
+ def mass(self):
169
+ """Electron mass in GeV"""
170
+ return particle.literals.e_minus.mass / 1.0e3
171
+
172
+
173
+ _set_repr_name("Electron")
174
+
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
179
+
180
+ behavior.update(awkward._util.copy_behaviors("Particle", "Muon", behavior))
181
+
182
+
183
+ @awkward.mixin_class(behavior)
184
+ class Muon(Particle, base.NanoCollection, base.Systematic):
185
+ @property
186
+ def mass(self):
187
+ """Muon mass in GeV"""
188
+ return particle.literals.mu_minus.mass / 1.0e3
189
+
190
+
191
+ _set_repr_name("Muon")
192
+
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
197
+
198
+ behavior.update(awkward._util.copy_behaviors("Particle", "Tau", behavior))
199
+
200
+
201
+ @awkward.mixin_class(behavior)
202
+ class Tau(Particle, base.NanoCollection, base.Systematic):
203
+ @property
204
+ def mass(self):
205
+ """Tau mass in GeV"""
206
+ return particle.literals.tau_minus.mass / 1.0e3
207
+
208
+
209
+ _set_repr_name("Tau")
210
+
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
215
+
216
+
217
+ behavior.update(awkward._util.copy_behaviors("Particle", "Jet", behavior))
218
+
219
+
220
+ @awkward.mixin_class(behavior)
221
+ class Jet(Particle, base.NanoCollection, base.Systematic): ...
222
+
223
+
224
+ _set_repr_name("Jet")
225
+
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
230
+
231
+ __all__ = [
232
+ "Electron",
233
+ "Jet",
234
+ "MissingET",
235
+ "Muon",
236
+ "NtupleEvents",
237
+ "Particle",
238
+ "Pass",
239
+ "Photon",
240
+ "Weight",
241
+ ]
@@ -0,0 +1,39 @@
1
+ from __future__ import annotations
2
+
3
+ from enum import Enum
4
+ from typing import TypeVar, Union, cast
5
+
6
+ import awkward as ak
7
+ import dask_awkward as dak
8
+
9
+ Array = TypeVar("Array", bound=Union[dak.Array, ak.Array])
10
+ _E = TypeVar("_E", bound=Enum)
11
+
12
+
13
+ def isin(haystack: Array, needles: dak.Array | ak.Array, axis: int = -1) -> Array:
14
+ """
15
+ Find needles in haystack.
16
+
17
+ This works by first transforming needles to an array with one more
18
+ dimension than the haystack, placing the needles at axis, and then doing a
19
+ comparison.
20
+
21
+ Args:
22
+ haystack (dak.Array or ak.Array): haystack of values.
23
+ needles (dak.Array or ak.Array): one-dimensional set of needles to find in haystack.
24
+ axis (int): the axis along which the comparison is performed
25
+
26
+ Returns:
27
+ dak.Array or ak.Array: result of comparison for needles in haystack
28
+ """
29
+ assert needles.ndim == 1, "Needles must be one-dimensional"
30
+ assert axis >= -1, "axis must be -1 or positive-valued"
31
+ assert axis < haystack.ndim + 1, "axis too large for the haystack"
32
+
33
+ # First, build up the transformation, with slice(None) indicating where to stick the needles
34
+ reshaper: list[None | slice] = [None] * haystack.ndim
35
+ axis = haystack.ndim if axis == -1 else axis
36
+ reshaper.insert(axis, slice(None))
37
+
38
+ # Note: reshaper needs to be a tuple for indexing purposes
39
+ return cast(Array, ak.any(haystack == needles[tuple(reshaper)], axis=-1))
@@ -1,116 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from enum import IntEnum
4
-
5
- from atlas_schema.typing_compat import Annotated
6
-
7
-
8
- # https://gitlab.cern.ch/atlas/athena/-/blob/74f43ff0910edb2a2bd3778880ccbdad648dc037/Generators/TruthUtils/TruthUtils/TruthClasses.h#L51-103
9
- class ParticleType(IntEnum):
10
- Unknown: Annotated[int, "Unknown"] = 0
11
- UnknownElectron: Annotated[int, "UnknownElectron"] = 1
12
- IsoElectron: Annotated[int, "IsoElectron"] = 2
13
- NonIsoElectron: Annotated[int, "NonIsoElectron"] = 3
14
- BkgElectron: Annotated[int, "BkgElectron"] = 4
15
- UnknownMuon: Annotated[int, "UnknownMuon"] = 5
16
- IsoMuon: Annotated[int, "IsoMuon"] = 6
17
- NonIsoMuon: Annotated[int, "NonIsoMuon"] = 7
18
- BkgMuon: Annotated[int, "BkgMuon"] = 8
19
- UnknownTau: Annotated[int, "UnknownTau"] = 9
20
- IsoTau: Annotated[int, "IsoTau"] = 10
21
- NonIsoTau: Annotated[int, "NonIsoTau"] = 11
22
- BkgTau: Annotated[int, "BkgTau"] = 12
23
- UnknownPhoton: Annotated[int, "UnknownPhoton"] = 13
24
- IsoPhoton: Annotated[int, "IsoPhoton"] = 14
25
- NonIsoPhoton: Annotated[int, "NonIsoPhoton"] = 15
26
- BkgPhoton: Annotated[int, "BkgPhoton"] = 16
27
- Hadron: Annotated[int, "Hadron"] = 17
28
- Neutrino: Annotated[int, "Neutrino"] = 18
29
- NuclFrag: Annotated[int, "NuclFrag"] = 19
30
- NonPrimary: Annotated[int, "NonPrimary"] = 20
31
- GenParticle: Annotated[int, "GenParticle"] = 21
32
- SUSYParticle: Annotated[int, "SUSYParticle"] = 22
33
- OtherBSMParticle: Annotated[int, "OtherBSMParticle"] = 39
34
- BBbarMesonPart: Annotated[int, "BBbarMesonPart"] = 23
35
- BottomMesonPart: Annotated[int, "BottomMesonPart"] = 24
36
- CCbarMesonPart: Annotated[int, "CCbarMesonPart"] = 25
37
- CharmedMesonPart: Annotated[int, "CharmedMesonPart"] = 26
38
- BottomBaryonPart: Annotated[int, "BottomBaryonPart"] = 27
39
- CharmedBaryonPart: Annotated[int, "CharmedBaryonPart"] = 28
40
- StrangeBaryonPart: Annotated[int, "StrangeBaryonPart"] = 29
41
- LightBaryonPart: Annotated[int, "LightBaryonPart"] = 30
42
- StrangeMesonPart: Annotated[int, "StrangeMesonPart"] = 31
43
- LightMesonPart: Annotated[int, "LightMesonPart"] = 32
44
- BJet: Annotated[int, "BJet"] = 33
45
- CJet: Annotated[int, "CJet"] = 34
46
- LJet: Annotated[int, "LJet"] = 35
47
- GJet: Annotated[int, "GJet"] = 36
48
- TauJet: Annotated[int, "TauJet"] = 37
49
- UnknownJet: Annotated[int, "UnknownJet"] = 38
50
-
51
-
52
- # https://gitlab.cern.ch/atlas/athena/-/blob/74f43ff0910edb2a2bd3778880ccbdad648dc037/Generators/TruthUtils/TruthUtils/TruthClasses.h#L51-103
53
- class ParticleOrigin(IntEnum):
54
- NonDefined: Annotated[int, "NonDefined"] = 0
55
- SingleElec: Annotated[int, "SingleElec"] = 1
56
- SingleMuon: Annotated[int, "SingleMuon"] = 2
57
- SinglePhot: Annotated[int, "SinglePhot"] = 3
58
- SingleTau: Annotated[int, "SingleTau"] = 4
59
- PhotonConv: Annotated[int, "PhotonConv"] = 5
60
- DalitzDec: Annotated[int, "DalitzDec"] = 6
61
- ElMagProc: Annotated[int, "ElMagProc"] = 7
62
- Mu: Annotated[int, "Mu"] = 8
63
- TauLep: Annotated[int, "TauLep"] = 9
64
- top: Annotated[int, "top"] = 10
65
- QuarkWeakDec: Annotated[int, "QuarkWeakDec"] = 11
66
- WBoson: Annotated[int, "WBoson"] = 12
67
- ZBoson: Annotated[int, "ZBoson"] = 13
68
- Higgs: Annotated[int, "Higgs"] = 14
69
- HiggsMSSM: Annotated[int, "HiggsMSSM"] = 15
70
- HeavyBoson: Annotated[int, "HeavyBoson"] = 16
71
- WBosonLRSM: Annotated[int, "WBosonLRSM"] = 17
72
- NuREle: Annotated[int, "NuREle"] = 18
73
- NuRMu: Annotated[int, "NuRMu"] = 19
74
- NuRTau: Annotated[int, "NuRTau"] = 20
75
- LQ: Annotated[int, "LQ"] = 21
76
- SUSY: Annotated[int, "SUSY"] = 22
77
- OtherBSM: Annotated[int, "OtherBSM"] = 46
78
- LightMeson: Annotated[int, "LightMeson"] = 23
79
- StrangeMeson: Annotated[int, "StrangeMeson"] = 24
80
- CharmedMeson: Annotated[int, "CharmedMeson"] = 25
81
- BottomMeson: Annotated[int, "BottomMeson"] = 26
82
- CCbarMeson: Annotated[int, "CCbarMeson"] = 27
83
- JPsi: Annotated[int, "JPsi"] = 28
84
- BBbarMeson: Annotated[int, "BBbarMeson"] = 29
85
- LightBaryon: Annotated[int, "LightBaryon"] = 30
86
- StrangeBaryon: Annotated[int, "StrangeBaryon"] = 31
87
- CharmedBaryon: Annotated[int, "CharmedBaryon"] = 32
88
- BottomBaryon: Annotated[int, "BottomBaryon"] = 33
89
- PionDecay: Annotated[int, "PionDecay"] = 34
90
- KaonDecay: Annotated[int, "KaonDecay"] = 35
91
- BremPhot: Annotated[int, "BremPhot"] = 36
92
- PromptPhot: Annotated[int, "PromptPhot"] = 37
93
- UndrPhot: Annotated[int, "UndrPhot"] = 38
94
- ISRPhot: Annotated[int, "ISRPhot"] = 39
95
- FSRPhot: Annotated[int, "FSRPhot"] = 40
96
- NucReact: Annotated[int, "NucReact"] = 41
97
- PiZero: Annotated[int, "PiZero"] = 42
98
- DiBoson: Annotated[int, "DiBoson"] = 43
99
- ZorHeavyBoson: Annotated[int, "ZorHeavyBoson"] = 44
100
- MultiBoson: Annotated[int, "MultiBoson"] = 47
101
- QCD: Annotated[int, "QCD"] = 45
102
-
103
-
104
- # https://twiki.cern.ch/twiki/bin/viewauth/AtlasProtected/EGammaIdentificationRun2#Photon_isEM_word
105
- class PhotonID(IntEnum):
106
- Rhad: Annotated[int, "ClusterHadronicLeakage_Photon"] = 10
107
- E277: Annotated[int, "ClusterMiddleEnergy_Photon"] = 11
108
- Reta: Annotated[int, "ClusterMiddleEratio37_Photon"] = 12
109
- Rphi: Annotated[int, "ClusterMiddleEratio33_Photon"] = 13
110
- Weta2: Annotated[int, "ClusterMiddleWidth_Photon"] = 14
111
- f1: Annotated[int, "ClusterStripsEratio_Photon"] = 15
112
- DeltaE: Annotated[int, "ClusterStripsDeltaE_Photon"] = 17
113
- Wstot: Annotated[int, "ClusterStripsWtot_Photon"] = 18
114
- fside: Annotated[int, "ClusterStripsFracm_Photon"] = 19
115
- Ws3: Annotated[int, "ClusterStripsWeta1c_Photon"] = 20
116
- ERatio: Annotated[int, "ClusterStripsDEmaxs1_Photon"] = 21
@@ -1,181 +0,0 @@
1
- """Mixins for the Ntuple schema"""
2
-
3
- from __future__ import annotations
4
-
5
- from functools import reduce
6
- from operator import ior
7
-
8
- import awkward
9
- from coffea.nanoevents.methods import base, candidate, vector
10
- from dask_awkward import dask_method
11
-
12
- from atlas_schema.enums import PhotonID
13
- from atlas_schema.typing_compat import Behavior
14
-
15
- behavior: Behavior = {}
16
- behavior.update(base.behavior)
17
- # vector behavior is included in candidate behavior
18
- behavior.update(candidate.behavior)
19
-
20
-
21
- class NtupleEvents(behavior["NanoEvents"]): # type: ignore[misc, valid-type, name-defined]
22
- def __repr__(self):
23
- return f"<event {getattr(self,'runNumber','??')}:\
24
- {getattr(self,'eventNumber','??')}:\
25
- {getattr(self,'mcChannelNumber','??')}>"
26
-
27
-
28
- behavior["NanoEvents"] = NtupleEvents
29
-
30
-
31
- def _set_repr_name(classname):
32
- def namefcn(_self):
33
- return classname
34
-
35
- behavior[("__typestr__", classname)] = classname[0].lower() + classname[1:]
36
- behavior[classname].__repr__ = namefcn
37
-
38
-
39
- @awkward.mixin_class(behavior)
40
- class Weight(base.NanoCollection, base.Systematic): ...
41
-
42
-
43
- _set_repr_name("Weight")
44
-
45
-
46
- @awkward.mixin_class(behavior)
47
- class Pass(base.NanoCollection, base.Systematic): ...
48
-
49
-
50
- _set_repr_name("Pass")
51
-
52
-
53
- @awkward.mixin_class(behavior)
54
- class Particle(vector.PtEtaPhiMLorentzVector):
55
- """Generic particle collection that has Lorentz vector properties
56
-
57
- Also handles the following additional branches:
58
- - '{obj}_select'
59
- """
60
-
61
- @property
62
- def mass(self):
63
- r"""Invariant mass (+, -, -, -)
64
-
65
- :math:`\sqrt{t^2-x^2-y^2-z^2}`
66
- """
67
- return self["mass"] / 1.0e3
68
-
69
- @dask_method
70
- def passes(self, name):
71
- return self[f"select_{name}"] == 1
72
-
73
- @passes.dask
74
- def passes(self, dask_array, name):
75
- return dask_array[f"select_{name}"] == 1
76
-
77
- # NB: fields with the name 'pt' take precedence over this
78
- # @dask_property
79
- # def pt(self):
80
- # print('inside non-dask prop')
81
- # return self["pt_NOSYS"]
82
-
83
- # @pt.dask
84
- # def pt(self, dask_array):
85
- # branch = 'pt'
86
- # print('inside dask prop')
87
- # variation = dask_array._events().metadata.get("systematic", "NOSYS")
88
- # with contextlib.suppress(Exception):
89
- # return dask_array[f"{branch}_{variation}"]
90
-
91
- # if variation != "NOSYS":
92
- # with contextlib.suppress(Exception):
93
- # return dask_array[f"{branch}_NOSYS"]
94
-
95
- # return dask_array[branch]
96
-
97
-
98
- _set_repr_name("Particle")
99
-
100
-
101
- @awkward.mixin_class(behavior)
102
- class MasslessParticle(Particle, base.NanoCollection):
103
- @property
104
- def mass(self):
105
- r"""Invariant mass (+, -, -, -)
106
-
107
- :math:`\sqrt{t^2-x^2-y^2-z^2}`
108
- """
109
- return 0.0 * self.pt
110
-
111
-
112
- _set_repr_name("MasslessParticle")
113
-
114
-
115
- @awkward.mixin_class(behavior)
116
- class MissingET(MasslessParticle, base.NanoCollection, base.Systematic):
117
- @property
118
- def pt(self):
119
- """Alias for `r`"""
120
- return self["met"] / 1.0e3
121
-
122
- @property
123
- def eta(self):
124
- r"""Pseudorapidity
125
-
126
- :math:`-\ln\tan(\theta/2) = \text{arcsinh}(z/r)`
127
- """
128
- return 0.0 * self.pt
129
-
130
-
131
- _set_repr_name("MissingET")
132
-
133
-
134
- @awkward.mixin_class(behavior)
135
- class Photon(MasslessParticle, base.NanoCollection, base.Systematic):
136
- @property
137
- def isEM(self):
138
- return self.isEM_syst.NOSYS == 0
139
-
140
- def pass_isEM(self, words: list[PhotonID]):
141
- # 0 is pass, 1 is fail
142
- return (
143
- self.isEM_syst.NOSYS & reduce(ior, (1 << word.value for word in words))
144
- ) == 0
145
-
146
-
147
- _set_repr_name("Photon")
148
-
149
-
150
- @awkward.mixin_class(behavior)
151
- class Electron(MasslessParticle, base.NanoCollection, base.Systematic): ...
152
-
153
-
154
- _set_repr_name("Electron")
155
-
156
-
157
- @awkward.mixin_class(behavior)
158
- class Muon(MasslessParticle, base.NanoCollection, base.Systematic): ...
159
-
160
-
161
- _set_repr_name("Muon")
162
-
163
-
164
- @awkward.mixin_class(behavior)
165
- class Jet(Particle, base.NanoCollection, base.Systematic): ...
166
-
167
-
168
- _set_repr_name("Jet")
169
-
170
-
171
- __all__ = [
172
- "NtupleEvents",
173
- "Weight",
174
- "Pass",
175
- "MissingET",
176
- "Particle",
177
- "Photon",
178
- "Electron",
179
- "Muon",
180
- "Jet",
181
- ]
File without changes