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
95
96BRANCHES: dict[str, list[str]] = {
97 "t": _t_branches("t"),
98 "tbar": _t_branches("tbar"),
99 "ttbar": (
100 _make_particle_branches("ttbar", include_pdgid=False)
101 + _make_particle_branches("ttbar_fromDecay", include_pdgid=False)
102 ),
103 "b": _make_particle_branches("b"),
104 "bbar": _make_particle_branches("bbar"),
105 "c": _make_particle_branches("c"),
106 "cbar": _make_particle_branches("cbar"),
107 "Z": (
108 _make_particle_branches("Z", extra=("IsOnShell",))
109 + _make_particle_branches("Zdecay1")
110 + _make_particle_branches("Zdecay2")
111 ),
112 "Z_extended": (
113 _make_particle_branches("Z", extra=("IsOnShell",))
114 + _make_particle_branches("Zdecay1")
115 + _make_particle_branches("Zdecay1_decay1")
116 + _make_particle_branches("Zdecay1_decay2")
117 + _make_particle_branches("Zdecay1_decay3")
118 + _make_particle_branches("Zdecay2")
119 + _make_particle_branches("Zdecay2_decay1")
120 + _make_particle_branches("Zdecay2_decay2")
121 + _make_particle_branches("Zdecay2_decay3")
122 ),
123 "W": (
124 _make_particle_branches("W", extra=("IsOnShell",))
125 + _make_particle_branches("Wdecay1")
126 + _make_particle_branches("Wdecay2")
127 ),
128 "Photon": [
129 "MC_gamma_m",
130 "MC_gamma_pt",
131 "MC_gamma_eta",
132 "MC_gamma_phi",
133 "MC_gamma_origin",
134 "MC_gamma_pdgId",
135 ],
136 "Higgs": (
138 + _make_particle_branches("Hdecay1")
139 + _make_particle_branches("Hdecay2")
140 + _make_particle_branches("Hdecay1_decay1")
141 + _make_particle_branches("Hdecay1_decay2")
142 + _make_particle_branches("Hdecay2_decay1")
143 + _make_particle_branches("Hdecay2_decay2")
144 ),
145}
146
147_TTBAR = BRANCHES["t"] + BRANCHES["tbar"] + BRANCHES["ttbar"]
148
149TRUTH_BRANCHES: dict[str, list[str]] = {
150 "Ttbar": _TTBAR,
151 "Ttbarbbbar": _TTBAR + BRANCHES["b"] + BRANCHES["bbar"],
152 "Ttbarccbar": _TTBAR + BRANCHES["c"] + BRANCHES["cbar"],
153 "Ttz": _TTBAR + BRANCHES["Z"],
154 "Ttw": _TTBAR + BRANCHES["W"],
155 "Tth": _TTBAR + BRANCHES["Higgs"],
156 "Ttgamma": _TTBAR + BRANCHES["Photon"],
157 "Tzq": BRANCHES["t"] + BRANCHES["Z"] + BRANCHES["b"],
158 "Thq": BRANCHES["t"] + BRANCHES["Higgs"] + BRANCHES["b"] + BRANCHES["W"],
159 "Tqgamma": BRANCHES["t"] + BRANCHES["Photon"] + BRANCHES["b"],
160 "Wtb": BRANCHES["t"] + BRANCHES["W"] + BRANCHES["b"],
161 "FourTop": (
162 _replace_in_list(BRANCHES["t"], "t_", "t1_")
163 + _replace_in_list(BRANCHES["t"], "t_", "t2_")
164 + _replace_in_list(BRANCHES["tbar"], "tbar_", "tbar1_")
165 + _replace_in_list(BRANCHES["tbar"], "tbar_", "tbar2_")
166 ),
167 "HWW": BRANCHES["Higgs"],
168 "WW_nonresonant": (
169 _replace_in_list(BRANCHES["W"], "W", "W1")
170 + _replace_in_list(BRANCHES["W"], "W", "W2")
171 ),
172 "HWW_nonresonant": BRANCHES["Higgs"],
173 "HZZ": BRANCHES["Higgs"],
174 "Zb": BRANCHES["Z"] + BRANCHES["b"] + BRANCHES["bbar"],
175 "Ztautau": BRANCHES["Z_extended"],
176}
177
178# ---------------------------------------------------------------------------
179# Config block
180# ---------------------------------------------------------------------------
181
182
183class PartonHistoryBlock(ConfigBlock):
184 """ConfigBlock for truth/parton-level resonant histories."""
185
186 def __init__(self):
187 super().__init__()
188 self.addOption(
189 "history",
190 None,
191 type=str,
192 required=True,
193 info="parton-level interpretation of the MC truth record. Possible values:"
194 + ", ".join(sorted(TRUTH_BRANCHES))
195 + ".",
196 )
197 # Always skip on data
198 self.setOptionValue("skipOnData", True)
199
200 def makeAlgs(self, config):
201 if self.history not in TRUTH_BRANCHES:
202 valid = ", ".join(sorted(TRUTH_BRANCHES))
203 raise ValueError(
204 f"Unknown parton history '{self.history}'. Valid options: {valid}"
205 )
206
207 alg = config.createAlgorithm(
208 "CP::RunPartonHistoryAlg", f"PartonHistory{self.history}"
209 )
210 alg.partonScheme = self.history
211
212 for branch in TRUTH_BRANCHES[self.history]:
213 config.addOutputVar(
214 "EventInfo",
215 f"{self.history}_{branch}",
216 f"{self.history}_{branch}",
217 noSys=True,
218 auxType=_get_aux_type(branch, self.history),
219 )
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)
list _t_branches(str suffix="t")
str _get_aux_type(str branch, str prefix)