5"""Toolkit for Analysis."""
12from ShipGeoConfig
import AttrDict, load_from_root_file
13from tabulate
import tabulate
17 """Class to perform various selection checks on the candidate."""
20 """Initialize the selection_check class with geometry and configuration."""
22 self.
ship_geo = load_from_root_file(geo_file,
"ShipGeo")
24 fairship = ROOT.gSystem.Getenv(
"FAIRSHIP")
26 if self.
ship_geo.DecayVolumeMedium ==
"helium":
27 with open(fairship +
"/geometry/veto_config_helium.yaml")
as file:
28 config = yaml.safe_load(file)
31 if self.
ship_geo.DecayVolumeMedium ==
"vacuums":
32 with open(fairship +
"/geometry/veto_config_vacuums.yaml")
as file:
33 config = yaml.safe_load(file)
37 """Access event data."""
41 """Calculate time associated with the candidate decay vertex using strawtubes MCPoint info."""
42 t0 = self.
tree.ShipEventHeader.GetEventTime()
43 candidate_pos = ROOT.TVector3()
44 candidate.GetVertex(candidate_pos)
46 d1, d2 = candidate.GetDaughter(0), candidate.GetDaughter(1)
47 d1_mc, d2_mc = self.
tree.fitTrack2MC[d1], self.
tree.fitTrack2MC[d2]
49 time_vtx_from_strawhits = []
51 for hit
in self.
tree.strawtubesPoint:
52 if not (int(str(hit.GetDetectorID())[:1]) == 1
or int(str(hit.GetDetectorID())[:1]) == 2):
55 if not (hit.GetTrackID() == d1_mc
or hit.GetTrackID() == d2_mc):
58 t_straw = hit.GetTime()
61 (candidate_pos.X() - hit.GetX()) ** 2
62 + (candidate_pos.Y() - hit.GetY()) ** 2
63 + (candidate_pos.Z() - hit.GetZ()) ** 2
66 d_mom = self.
tree.MCTrack[hit.GetTrackID()].GetP() / u.GeV
67 mass = self.
tree.MCTrack[hit.GetTrackID()].GetMass()
68 v = u.c_light * d_mom / np.sqrt(d_mom**2 + (mass) ** 2)
70 t_vertex = t_straw - (dist / v)
72 time_vtx_from_strawhits.append(t_vertex)
74 t_vtx = np.average(time_vtx_from_strawhits) + t0
79 """Calculate the impact parameter of the candidate relative to (0,0,target.z0)."""
80 candidate_pos = ROOT.TVector3()
81 candidate.GetVertex(candidate_pos)
83 candidate_mom = ROOT.TLorentzVector()
84 candidate.Momentum(candidate_mom)
85 target_point = ROOT.TVector3(0, 0, self.
ship_geo.target.z0)
88 if hasattr(candidate_mom,
"P"):
91 P = candidate_mom.Mag()
93 projection_factor += candidate_mom(i) / P * (target_point(i) - candidate_pos(i))
97 dist += (target_point(i) - candidate_pos(i) - projection_factor * candidate_mom(i) / P) ** 2
98 dist = ROOT.TMath.Sqrt(dist)
103 """Calculate the minimum distance(in XY plane) of the candidate decay vertex to the inner wall of the decay vessel. If outside the decay volume, or if distance > 100cm,Return 0."""
104 candidate_pos = ROOT.TVector3()
105 candidate.GetVertex(candidate_pos)
106 position = (candidate_pos.X(), candidate_pos.Y(), candidate_pos.Z())
109 dalpha = 2 * ROOT.TMath.Pi() / nsteps
110 min_distance = float(
"inf")
117 for n
in range(nsteps):
120 ROOT.TMath.Sin(alpha),
121 ROOT.TMath.Cos(alpha),
129 min_distance = min(min_distance, distance)
131 return min_distance
if min_distance < 100 * u.m
else 0
134 """Calculate the distance of the candidate decay vertex to the entrance of the decay vessel."""
135 candidate_pos = ROOT.TVector3()
136 candidate.GetVertex(candidate_pos)
137 return candidate_pos.Z() - self.
veto_geo.z0
140 """Return the number of degrees of freedom (nDOF) for the particle's daughter tracks."""
142 t1, t2 = candidate.GetDaughter(0), candidate.GetDaughter(1)
145 fit_status = self.
tree.FitTracks[tr].getFitStatus()
146 nmeas.append(round(fit_status.getNdf()))
148 return np.array(nmeas)
151 """Return the momentum(Mag) of the particle's daughter tracks."""
153 t1, t2 = candidate.GetDaughter(0), candidate.GetDaughter(1)
155 x = self.
tree.FitTracks[trD]
156 xx = x.getFittedState()
157 daughter_mom.append(xx.getMom().Mag())
159 return np.array(daughter_mom)
162 """Invariant mass of the candidate."""
163 return candidate.GetMass()
166 """Distance of Closest Approach."""
167 return candidate.GetDoca()
170 """Check if the candidate decay vertex is within the Fiducial Volume."""
171 candidate_pos = ROOT.TVector3()
172 candidate.GetVertex(candidate_pos)
174 if candidate_pos.Z() > self.
ship_geo.TrackStation1.z:
176 if candidate_pos.Z() < self.
veto_geo.z0:
181 vertex_node = ROOT.gGeoManager.FindNode(candidate_pos.X(), candidate_pos.Y(), candidate_pos.Z())
182 vertex_elem = vertex_node.GetVolume().GetName()
183 return vertex_elem.startswith(
"DecayVacuum_")
186 """Return the reduced chi^2 of the particle's daughter tracks."""
187 t1, t2 = candidate.GetDaughter(0), candidate.GetDaughter(1)
191 fit_status = self.
tree.FitTracks[tr].getFitStatus()
192 chi2ndf.append(fit_status.getChi2() / fit_status.getNdf())
194 return np.array(chi2ndf)
196 def preselection_cut(self, candidate, IP_cut: float = 250, show_table: bool =
False) -> bool:
198 Umbrella method to apply the pre-selection cuts on the candidate.
200 show_table=True tabulates the pre-selection parameters.
204 if len(self.
tree.Particles) != 1:
214 if self.
DOCA(candidate) >= 1 * u.cm:
216 if np.any(self.
nDOF(candidate) <= 25):
218 if np.any(self.
chi2nDOF(candidate) >= 5):
226 "Number of candidates in event",
227 len(self.
tree.Particles),
229 len(self.
tree.Particles) == 1,
232 "Time @ decay vertex (ns)",
238 "Impact Parameter (cm)",
240 f
"IP < {IP_cut * u.cm} cm",
245 self.
DOCA(candidate),
247 self.
DOCA(candidate) < 1 * u.cm,
250 "Is within Fiducial Volume?",
256 "Dist2InnerWall (cm)",
262 "Dist2VesselEntrance (cm)",
269 "Daughter Momentum [d1, d2] (GeV)",
275 "Degrees of Freedom [d1, d2]",
276 self.
nDOF(candidate),
278 np.all(self.
nDOF(candidate) > 25),
281 "Reduced Chi^2 [d1, d2]",
284 np.all(self.
chi2nDOF(candidate) < 5),
286 [
"\033[1mPre-selection passed:\033[0m",
"",
"", flag],
291 f
"\033[1;32m{row[3]}\033[0m" if row[3]
else f
"\033[1;31m{row[3]}\033[0m"
301 "Pre-selection Check",
310 """Class to inspect MCtruth of an Event."""
313 """Initialize ROOT PDG database."""
314 self.
pdg = ROOT.TDatabasePDG.Instance()
315 pythia8_conf.addHNLtoROOT()
317 def dump_event(self, event, mom_threshold: float = 0) ->
None:
318 """Dump the MCtruth of the event."""
324 "Momentum [Px,Py,Pz] (GeV/c)",
325 "StartVertex[x,y,z] (m)",
331 for trackNr, track
in enumerate(event.MCTrack):
332 if track.GetP() / u.GeV < mom_threshold:
335 particle = self.
pdg.GetParticle(track.GetPdgCode())
336 particlename = particle.GetName()
if particle
else "----"
344 f
"[{track.GetPx() / u.GeV:7.3f},{track.GetPy() / u.GeV:7.3f},{track.GetPz() / u.GeV:7.3f}]",
345 f
"[{track.GetStartX() / u.m:7.3f},{track.GetStartY() / u.m:7.3f},{track.GetStartZ() / u.m:7.3f}]",
346 track.GetProcName().Data(),
351 print(tabulate(event_table, headers=headers, floatfmt=
".3f", tablefmt=
"simple_outline"))
int open(const char *, int)
Opens a file descriptor.