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