title | author | short-author | institute | short-institute | date | aspectratio | theme | colorlinks | ship | csquotes |
---|---|---|---|---|---|---|---|---|---|---|
Analysis tutorial |
Oliver Lantwin |
O. Lantwin |
INFN Napoli |
INFN Napoli |
2024-07-04 |
169 |
olantwin |
true |
true |
true |
Based on the old analysis tutorial by E. Graverini, T. Ruf, A. Buonaura and A. Baranov.
The latest version of this tutorial can be found on Github: slides, pdf
Please report issues with this tutorial on Github so that they can be fixed.
For this tutorial we are going to generate, reconstruct and analyse an ntuple of HNLs decaying to
The python script run_simScript.py
has several options (check it's --help
message).
It can be used to
- generate HNLs,
- muon background,
- neutrino background, and each one of these use cases can be customized with further options.
\framebreak
For now we will just generate a sample of 100
python $FAIRSHIP/macro/run_simScript.py -n 100
Now let’s run the reconstruction:
python $FAIRSHIP/macro/ShipReco.py -f ship.conical.Pythia8-TGeant4.root -g geofile_full.conical.Pythia8-TGeant4.root
Now let us open the output reconstructed file in PyROOT and see what’s inside.
We will perform a simple interactive analysis of the reconstructed tree using ipython
, but a jupyter notebook or the basic python
REPL work well.
> import ROOT as r
> f = r.TFile.Open('ship.conical.Pythia8-TGeant4_rec.root', 'read')
> f.ls()
TFile** ship.conical.Pythia8-TGeant4_rec.root
TFile* ship.conical.Pythia8-TGeant4_rec.root
KEY: TFolder cbmroot;1 Main Folder
KEY: TList BranchList;1 Doubly linked list
KEY: TList TimeBasedBranchList;1 Doubly linked list
KEY: FairFileHeader FileHeader;1
KEY: TTree cbmsim;2 /cbmroot_0 [current cycle]
KEY: TTree cbmsim;1 /cbmroot_0 [backup cycle]
> tree = f.cbmsim
> tree.Print()
******************************************************************************
*Tree :cbmsim : /cbmroot_0 *
*Entries : 100 : Total = 4489865 bytes File Size = 1774112 *
* : : Tree compression factor = 2.47 *
******************************************************************************
*Br 0 :MCTrack : Int_t cbmroot_0.Stack.MCTrack_ *
*Entries : 100 : Total Size= 11475 bytes File Size = 464 *
*Baskets : 1 : Basket Size= 32000 bytes Compression= 1.91 *
*............................................................................*
[...]
::: columns
::: {.column width=0.3}
> r.TBrowser()
Tip: Also available as rootbrowse
executable
:::
::: {.column width=0.7}
:::
:::
MCTracks are the basic objects representing the simulated particles
> tree.GetEntry(1)
> tracks = tree.MCTrack
> tracks[0]
<cppyy.gbl.ShipMCTrack object at 0x5654818f6870>
As usual, help
and dir
are useful tools to explore the classes methods. Have a look at what you can do with an ShipMCTrack!
The ROOT PDG database allows quick identification of particles based on their PDG code/name
> pdg = r.TDatabasePDG.Instance()
> for track in tracks:
print(track.GetPdgCode())
2212
431
9900015
211
13
11
[...]
The guy with the funny number above is an HNL. By default, ROOT PDG database does not know about HNL. But FairShip has a function to add it.
> import pythia8_conf
> pythia8_conf.addHNLtoROOT()
Let's define something useful to avoid repeating ourselves:
> def particle_name(track):
return pdg.GetParticle(track.GetPdgCode()).GetName()
\framebreak
> for track in tracks:
print(particle_name(track))
proton
D_s+
N2
pi+
mu-
e-
[...]
Every ShipMCTrack contains information about its origin:
> for i, track in enumerate(tracks):
print(i, particle_name(track), track.GetMotherId())
0 proton -1
1 D_s+ 0
2 N2 1
3 pi+ 2
4 mu- 2
5 e- 4
[...]
\tikz [overlay]
\node[anchor=north east] at ([xshift=-2cm]current page.east)
{\includegraphics[height=0.5\textheight]{event.pdf}};
Let's open the geometry file.
This file is generated by run_simScript.py
along with the data file.
It is unique to your production. Please always refer to the same geometry you use
to generate your file, when analysing it.
> geo = r.TFile.Open('geofile_full.conical.Pythia8-TGeant4.root', 'read')
> geo_manager = geo.FAIRGeom
> geo_manager = r.gGeoManager # alternative way
> geo.ls()
TFile** geofile_full.conical.Pythia8-TGeant4.root
TFile* geofile_full.conical.Pythia8-TGeant4.root
KEY: TGeoManager FAIRGeom;1 FAIR geometry
KEY: TObjString ShipGeo;1 Collectable string class
TGeoManager::FindNode
is a useful method: It takes
> for track in tracks:
print(geo_manager.FindNode(
track.GetStartX(), track.GetStartY(), track.GetStartZ()
).GetName())
Target_1_1
Target_1_1
Target_1_1
DecayVacuum_block1_0
DecayVacuum_block1_0
Cavern_1
[...]
> top = geo_manager.GetTopVolume()
> nodes = top.GetNodes()
> for node in nodes:
print(node.GetName())
TargetArea_1
Cavern_1
MuonShieldArea_1
SHiPMagnet_1
DecayVolume_1
MagVolume_1
tTauNuDet_1
Tr1_1
[...]
Let us find the z coordinate of the decay volume
> decay_volume = top.GetNode('DecayVolume_1')
> decay_volume.GetMatrix()
<cppyy.gbl.TGeoTranslation object at 0x555c630e5030>
> from shipunit import cm
> decay_volume.GetMatrix().GetTranslation()[2] / cm
-2481.0
-
GEANT4
andTGeo
use \si{\centi\metre} as default length unit - What this
coordinate signifies depends on the subsystem (experiment with different systems!) - The
getGeoInformation.py
tool is also very useful for understanding the geometry (try it!)
Nodes are volumes in the geometry tree. Let's look at the UBT node and its volume:
> ubt = top.GetNode('Upstream_Tagger_1')
> ubt_z = ubt.GetMatrix().GetTranslation()[2] / cm # -2497.0
> ubt_dz = ubt.GetVolume().GetShape().GetDZ() / cm # 8.5012 (half-length)
> ubt_z_end = ubt_z + ubt_dz
And for the first tracking station:
> tr1 = top.GetNode('Tr1_1')
> tr1_z_start = (tr1.GetMatrix().GetTranslation()[2] -
tr1.GetVolume().GetShape().GetDZ()) / cm
Reconstructed particles are saved in the Particles
branch (as ShipParticle
)
> tree.Particles[0]
<cppyy.gbl.ShipParticle object at 0x558a7d2a86f0>
Have a look what methods are available for ShipParticles
!
NB: This branch may be empty in some events!
> for i, event in enumerate(tree):
print(i, len(event.Particles))
0 0
1 1
2 0
[...]
Let us look at the reconstructed vertices:
> for event in tree:
for candidate in event.Particles:
vtx = r.TLorentzVector()
candidate.ProductionVertex(vtx)
print('vertex:', vtx.X(), vtx.Y(), vtx.Z(), vtx.T())
vertex: -49.264756706722345 -88.02536880120692 -1313.5768318662156 0.0
vertex: -102.67749138341733 -138.65193841223035 1959.5814731189923 0.0
vertex: -3.064717476903692 143.68577636840655 1833.386507383547 0.0
[...]
You could also have a look at e.g. the reconstructed momentum and DOCA (distance of closest approach) using the provided methods.
In addition to the TGeo
geometry, a copy of the config used to generate the geometry is saved in the geofile.
> from rootpyPickler import Unpickler
> unpickler = Unpickler(geo)
> geo_config = unpickler.load('ShipGeo')
> print(geo_config.target.z0)
-5886.5
This config contains a lot of useful information. We can use the target position to implement the impact parameter for our analysis.
Implement the impact parameter relative to the target
> import numpy as np
> from numpy.linalg import norm
> def impact_parameter(vertex, momentum):
vertex = np.array([vertex.X(), vertex.Y(), vertex.Z()])
momentum = np.array([momentum.X(), momentum.Y(), momentum.Z()])
target = np.array([0, 0, geo_config.target.z0])
return norm(np.cross(target - vertex, momentum)) / norm(momentum)
NP: Don't implement these things yourself, try to use the implementations from SHiP software where available, and help fill in the gaps!
> for event in tree:
for candidate in event.Particles:
vtx = r.TLorentzVector()
candidate.ProductionVertex(vtx)
print('DOCA:', candidate.GetDoca())
mom = r.TLorentzVector()
candidate.Momentum(mom)
print('IP:', impact_parameter(vtx, mom))
doca: 112.1899990968525
IP: 126.8368321009212
[...]
\centering \Huge
To be continued...