ATLAS Offline Software
Loading...
Searching...
No Matches
PartonHistoryConfig.py
Go to the documentation of this file.
1# Copyright (C) 2002-2026 CERN for the benefit of the ATLAS collaboration
2
3from AnalysisAlgorithmsConfig.ConfigBlock import ConfigBlock
4
5
6# ---------------------------------------------------------------------------
7# Helpers
8# ---------------------------------------------------------------------------
9
10KINEMATIC_VARS = ("pt", "eta", "phi", "m")
11INT_SUFFIXES = frozenset(("pdgId", "IsOnShell", "origin"))
12VECTOR_PREFIXES = ("MC_b_", "MC_bbar_", "MC_c_", "MC_cbar_")
13VECTOR_HISTORIES = frozenset(("Ttbarbbbar", "Ttbarccbar"))
14
15def _get_aux_type(branch: str, prefix: str) -> str:
16 # Handle specific cases where vectors are returned for each particle type
17 # In those cases, only external particles can be vectors, there is still
18 # a single particle allowed in named positions within decay chains
19 is_vector = prefix in VECTOR_HISTORIES and branch.startswith(VECTOR_PREFIXES) and "_from_" not in branch
20
21 if is_vector:
22 return "vector_int" if any(branch.endswith(s) for s in INT_SUFFIXES) else "vector_float"
23 else:
24 return "int" if any(branch.endswith(s) for s in INT_SUFFIXES) else "float"
25
27 prefix: str,
28 *,
29 include_pdgid: bool = True,
30 stages: tuple = None,
31 extra: tuple = (),
32) -> list:
33 """Generate standard kinematic branch names for a particle.
34
35 Parameters
36 ----------
37 prefix:
38 Particle label as it appears in the branch name, e.g. ``"t"`` ->
39 ``MC_t_beforeFSR_pt``. When the prefix already encodes the FSR stage
40 (e.g. ``"W_beforeFSR_from_t"``), pass ``stages=()``.
41 include_pdgid:
42 Append ``pdgId`` after the kinematic variables for each stage.
43 stages:
44 FSR stage tokens to iterate over. Defaults to
45 ``("beforeFSR", "afterFSR")``. Pass ``()`` when the FSR stage is
46 already embedded in prefix — branches become ``MC_{prefix}_{var}``.
47 extra:
48 Additional branch suffixes appended verbatim, e.g. ``("IsOnShell",)``.
49 """
50 if stages is None:
51 stages = ("beforeFSR", "afterFSR")
52 result = []
53 if stages:
54 for stage in stages:
55 for var in KINEMATIC_VARS:
56 result.append(f"MC_{prefix}_{stage}_{var}")
57 if include_pdgid:
58 result.append(f"MC_{prefix}_{stage}_pdgId")
59 else:
60 for var in KINEMATIC_VARS:
61 result.append(f"MC_{prefix}_{var}")
62 if include_pdgid:
63 result.append(f"MC_{prefix}_pdgId")
64 for suffix in extra:
65 result.append(f"MC_{prefix}_{suffix}")
66 return result
67
68
69def _replace_in_list(string_list: list[str], old: str, new: str) -> list[str]:
70 return [item.replace(old, new) for item in string_list]
71
72
73# ---------------------------------------------------------------------------
74# Per-particle branch lists
75# ---------------------------------------------------------------------------
76
77
78def _t_branches(suffix: str = "t") -> list:
79 """Branches for a top quark and its full decay chain.
80
81 Sub-particles carry the FSR stage in their prefix so we pass ``stages=()``
82 to avoid appending a spurious stage token to the variable name.
83 For tbar, the b-quark is named 'bbar'.
84 """
85 b_name = "bbar" if suffix == "tbar" else "b"
86 sub_particles = ("W", b_name, "Wdecay1", "Wdecay2")
87 result = _make_particle_branches(suffix)
88 for particle in sub_particles:
89 for stage in ("beforeFSR", "afterFSR"):
91 f"{particle}_{stage}_from_{suffix}", stages=()
92 )
93 return result
94
95def _t_FCNC_branches(suffix: str = "t") -> list:
96 """Branches for a top quark undergoing FCNC decay t -> q X, X -> decay1 decay2.
97
98 Sub-particles carry the FSR stage in their prefix so we pass ``stages=()``
99 to avoid appending a spurious stage token to the variable name.
100 For tbar, the q-quark is named 'qbar'.
101 """
102 q_name = "qbar" if suffix == "tbar" else "q"
103 sub_particles = ("X", q_name, "Xdecay1", "Xdecay2")
104 result = _make_particle_branches(suffix)
105 for particle in sub_particles:
106 for stage in ("beforeFSR", "afterFSR"):
107 result += _make_particle_branches(
108 f"{particle}_{stage}_from_{suffix}", stages=()
109 )
110 return result
111
112BRANCHES: dict[str, list[str]] = {
113 "t": _t_branches("t"),
114 "tbar": _t_branches("tbar"),
115 "t_FCNC": _t_FCNC_branches("t"),
116 "tbar_FCNC": _t_FCNC_branches("tbar"),
117 "ttbar": (
118 _make_particle_branches("ttbar", include_pdgid=False)
119 + _make_particle_branches("ttbar_fromDecay", include_pdgid=False)
120 ),
121 "b": _make_particle_branches("b"),
122 "bbar": _make_particle_branches("bbar"),
123 "c": _make_particle_branches("c"),
124 "cbar": _make_particle_branches("cbar"),
125 "Z": (
126 _make_particle_branches("Z", extra=("IsOnShell",))
127 + _make_particle_branches("Zdecay1")
128 + _make_particle_branches("Zdecay2")
129 ),
130 "Z_extended": (
131 _make_particle_branches("Z", extra=("IsOnShell",))
132 + _make_particle_branches("Zdecay1")
133 + _make_particle_branches("Zdecay1_decay1")
134 + _make_particle_branches("Zdecay1_decay2")
135 + _make_particle_branches("Zdecay1_decay3")
136 + _make_particle_branches("Zdecay2")
137 + _make_particle_branches("Zdecay2_decay1")
138 + _make_particle_branches("Zdecay2_decay2")
139 + _make_particle_branches("Zdecay2_decay3")
140 ),
141 "W": (
142 _make_particle_branches("W", extra=("IsOnShell",))
143 + _make_particle_branches("Wdecay1")
144 + _make_particle_branches("Wdecay2")
145 ),
146 "Photon": [
147 "MC_gamma_m",
148 "MC_gamma_pt",
149 "MC_gamma_eta",
150 "MC_gamma_phi",
151 "MC_gamma_origin",
152 "MC_gamma_pdgId",
153 ],
154 "Higgs": (
156 + _make_particle_branches("Hdecay1")
157 + _make_particle_branches("Hdecay2")
158 + _make_particle_branches("Hdecay1_decay1")
159 + _make_particle_branches("Hdecay1_decay2")
160 + _make_particle_branches("Hdecay2_decay1")
161 + _make_particle_branches("Hdecay2_decay2")
162 ),
163}
164
165_TTBAR = BRANCHES["t"] + BRANCHES["tbar"] + BRANCHES["ttbar"]
166
167TRUTH_BRANCHES: dict[str, list[str]] = {
168 "Ttbar": _TTBAR,
169 "TtbarFCNC": BRANCHES["t_FCNC"] + BRANCHES["tbar_FCNC"] + BRANCHES["ttbar"],
170 "Ttbarbbbar": _TTBAR + BRANCHES["b"] + BRANCHES["bbar"],
171 "Ttbarccbar": _TTBAR + BRANCHES["c"] + BRANCHES["cbar"],
172 "Ttz": _TTBAR + BRANCHES["Z"],
173 "Ttw": _TTBAR + BRANCHES["W"],
174 "Tth": _TTBAR + BRANCHES["Higgs"],
175 "Ttgamma": _TTBAR + BRANCHES["Photon"],
176 "Tzq": BRANCHES["t"] + BRANCHES["Z"] + BRANCHES["b"],
177 "Thq": BRANCHES["t"] + BRANCHES["Higgs"] + BRANCHES["b"] + BRANCHES["W"],
178 "Tqgamma": BRANCHES["t"] + BRANCHES["Photon"] + BRANCHES["b"],
179 "Wtb": BRANCHES["t"] + BRANCHES["W"] + BRANCHES["b"],
180 "FourTop": (
181 _replace_in_list(BRANCHES["t"], "t_", "t1_")
182 + _replace_in_list(BRANCHES["t"], "t_", "t2_")
183 + _replace_in_list(BRANCHES["tbar"], "tbar_", "tbar1_")
184 + _replace_in_list(BRANCHES["tbar"], "tbar_", "tbar2_")
185 ),
186 "HWW": BRANCHES["Higgs"],
187 "WW_nonresonant": (
188 _replace_in_list(BRANCHES["W"], "W", "W1")
189 + _replace_in_list(BRANCHES["W"], "W", "W2")
190 ),
191 "HWW_nonresonant": BRANCHES["Higgs"],
192 "HZZ": BRANCHES["Higgs"],
193 "Zb": BRANCHES["Z"] + BRANCHES["b"] + BRANCHES["bbar"],
194 "Ztautau": BRANCHES["Z_extended"],
195}
196
197# ---------------------------------------------------------------------------
198# Config block
199# ---------------------------------------------------------------------------
200
201
202class PartonHistoryBlock(ConfigBlock):
203 """ConfigBlock for truth/parton-level resonant histories."""
204
205 def __init__(self):
206 super().__init__()
207 self.addOption(
208 "history",
209 None,
210 type=str,
211 required=True,
212 info="parton-level interpretation of the MC truth record. Possible values:"
213 + ", ".join(sorted(TRUTH_BRANCHES))
214 + ".",
215 )
216 # Always skip on data
217 self.setOptionValue("skipOnData", True)
218
219 def makeAlgs(self, config):
220 if self.history not in TRUTH_BRANCHES:
221 valid = ", ".join(sorted(TRUTH_BRANCHES))
222 raise ValueError(
223 f"Unknown parton history '{self.history}'. Valid options: {valid}"
224 )
225
226 alg = config.createAlgorithm(
227 "CP::RunPartonHistoryAlg", f"PartonHistory{self.history}"
228 )
229 alg.partonScheme = self.history
230
231 for branch in TRUTH_BRANCHES[self.history]:
232 config.addOutputVar(
233 "EventInfo",
234 f"{self.history}_{branch}",
235 f"{self.history}_{branch}",
236 noSys=True,
237 auxType=_get_aux_type(branch, self.history),
238 )
list _t_FCNC_branches(str suffix="t")
list _t_branches(str suffix="t")
str _get_aux_type(str branch, str prefix)
list _make_particle_branches(str prefix, *, bool include_pdgid=True, tuple stages=None, tuple extra=())
list[str] _replace_in_list(list[str] string_list, str old, str new)