KaliVeda
Toolkit for HIC analysis
Loading...
Searching...
No Matches
Nuclei and Events

Particles & nuclei

The base class for the description of the kinematics of all particles in KaliVeda is KVParticle. It implements all kinematical manipulations necessary for obtaining angles, kinetic energies, transverse energies etc. etc., defining and changing relativistic reference frames.

Unless otherwise stated,

  • all energies are in MeV
  • all velocities are in cm/ns
  • all angles are in degrees (polar angles between 0 and \(180^o\); azimuthal angles between 0 and \(360^o\)) except for TRotation objects (used for rotations of kinematical frames)
  • all momenta are in MeV/c
  • all vector quantities are descibed using TVector3

KVParticle is derived from TLorentzVector - a particle is basically a Lorentz 4-vector with added attributes (mass!). This means that all particles and kinematics are relativistic in KaliVeda.

Particle kinematical properties can be defined either using one of the constructors :

// using KVParticle(Double_t M, const TVector3& P)
KVParticle part(939., {250./sqrt(3.),250./sqrt(3.),250./sqrt(3.)});
part.Print();
Base class for relativistic kinematics of massive particles.
Definition KVParticle.h:396
virtual void Print(Option_t *t="") const
print out characteristics of particle
Expr< UnaryOp< Sqrt< T >, Expr< A, T, D, D2, R >, T >, T, D, D2, R > sqrt(const Expr< A, T, D, D2, R > &rhs)
KVParticle mass=939 Theta=54.7356 Phi=45 KE=32.7103 Vpar=4.45311

or with the usual 'Set' methods:

part.SetEnergy(100);
part.Print();
KVParticle mass=939 Theta=54.7356 Phi=45 KE=100 Vpar=7.40897
part.SetTheta(0);
part.GetMomentum().Print();
TVector3 A 3D physics vector (x,y,z)=(0.000000,0.000000,444.747119) (rho,theta,phi)=(444.747119,0.000000,0.000000)

Particle kinematics

Particles are created with a given kinetic energy and direction (or momentum) Particle kinematics in different frames can be accessed with several methods provided by KVParticle, all using the KVFrameTransform class which represents one of the allowed kinematical transformations:

  • Lorentz boost
  • rotation
  • combined boost and rotation

KVParticle::InFrame()

This method can be used to access the kinematics of any particle in a different frame to the one in which its kinematics were defined

Example: inspect kinematics of particle

  • in a frame moving at 5 cm/ns in the beam direction:
// get longitudinal velocity (z-component, beam direction) in other frame
part.InFrame(TVector3(0,0,5)).GetVpar();

KVParticle::ChangeFrame()

Particle kinematics can be permanently modified using this method:

Example: change kinematics of particle

  • to a frame rotated by \(90^o\) clockwise around the +ve beam direction:
rot.RotateZ(TMath::PiOver2()); // angles in radians for TRotation
part.ChangeFrame(rot);
TRotation & RotateZ(Double_t)
constexpr Double_t PiOver2()

Using several reference frames

Rather than changing the reference frame of the particle, you can define and use several different reference frames while keeping the original kinematics as the default. Each frame can be used independently, and new frames can be defined based on any of the existing frames:

Example:

  • define a new frame moving at 5 cm/ns in the beam direction:
part.SetFrame("moving_frame", TVector3(0,0,5));
  • define a rotated coordinate frame in the "moving_frame", rotated by \(90^o\) clockwise around the +ve beam direction:
part.SetFrame("rotated_moving_frame", "moving_frame", rot);

‍Note that the same frame can be defined directly from the original particle by using a combined boost-then-rotation transform:

part.SetFrame("rotated_moving_frame", KVFrameTransform({0,0,5},rot));
Utility class for kinematical transformations of KVParticle class.
  • define a similarly rotated coordinate frame in the original (default) reference frame:
part.SetFrame("rotated_frame", rot);
  • access kinematical information in any of these frames:
part.GetFrame("moving_frame")->GetVpar();
part.GetFrame("rotated_frame")->GetPhi();
part.GetFrame("rotated_moving_frame")->GetTransverseEnergy();

Note that the frame "rotated_moving_frame" is directly accessible even if it is defined in two steps as a rotation of the "moving_frame".

The total number of defined frames (including the default frame) is given by KVParticle::GetNumberOfDefinedFrames(). Calling KVParticle::Print() will show all reference frames defined for the particle:

part.Print()
KVParticle mass=0 Theta=0 Phi=0 KE=0 Vpar=0
moving_frame: Theta=0 Phi=0 KE=0 Vpar=0
rotated_moving_frame: Theta=0 Phi=0 KE=0 Vpar=0
rotated_frame: Theta=0 Phi=0 KE=0 Vpar=0

Indentation indicates the relationships between frames: "rotated_moving_frame" is a child frame of "moving_frame". The first line is the default kinematics, which by default has no name (a name can be set using KVParticle::SetFrameName()).

The current default kinematics can always be accessed using KVParticle::GetCurrentDefaultKinematics(): this method returns the default kinematical frame for the particle when accessed from any kinematical frame:

part.SetFrameName("lab"); // set name for default kinematics
auto show_default_frame =
[](const KVParticle* pp)
{ std::cout << pp->GetCurrentDefaultKinematics()->GetFrameName() << std::endl; };
show_default_frame( part.GetFrame("moving_frame") )
// prints "lab"

Changing the default kinematics

Let us consider a particle for which the different reference frames in the previous paragraph have been defined. For an example, imagine that the default kinematics are that of particle with rest mass 939 MeV moving with a momentum of 250 MeV/c at an angle of \(45^o\) to the +ve z-direction:

KVParticle part(939,0,0,250);
part.SetTheta(45);
part.SetFrame("moving_frame", TVector3(0,0,5));
part.SetFrame("rotated_moving_frame", "moving_frame", rot);
part.SetFrame("rotated_frame", rot);

Now if we want to change the default kinematical frame for this particle:

part.ChangeDefaultFrame("rotated_moving_frame", "lab"); // second argument is name for previous default frame, if not defined before
part.Print();
KVParticle mass=939 Theta=85.1751 Phi=270 KE=16.6117 Vpar=0.468125
moving_frame: Theta=85.1751 Phi=0 KE=16.6117 Vpar=0.468125
lab: Theta=45 Phi=0 KE=32.7103 Vpar=5.45392
rotated_frame: Theta=45 Phi=270 KE=32.7103 Vpar=5.45392
KVNameValueList::ParticleParameters : Parameters associated with a particle in an event (0x7f5a1ff8b1b8)
<frameName=rotated_moving_frame>
RooCmdArg Parameters(const RooArgSet &params)
TArc a

The relationships between frames are preserved, i.e. if we present the frames as graphs:

with "lab" as default frame:

lab
|
+--moving_frame
| |
| +--rotated_moving_frame
|
+--rotated_frame

with "rotated_moving_frame" as default frame:

rotated_moving_frame
|
+--moving_frame
|
+--lab
|
+--rotated_frame

Updating stored kinematical frames

If you call KVParticle::SetFrame() several times with the same frame name [note that frame names are case insensitive], the existing reference frame will be updated to use the new transformation, which will be applied to the kinematics of the particle in the 'parent' frame used to define the frame. Any frames which were defined based on the frame will be updated too.

Example: for the previous particle & frame definitions, after resetting the default kinematics:

part.ChangeDefaultFrame("lab");
  • change the angle of rotation in the moving rotated frame:
part.SetFrame("rotated_moving_frame", rot);
part.Print();
KVParticle mass=939 Theta=45 Phi=0 KE=32.7103 Vpar=5.45392
rotated_frame: Theta=45 Phi=270 KE=32.7103 Vpar=5.45392
moving_frame: Theta=85.1751 Phi=0 KE=16.6117 Vpar=0.468125
rotated_moving_frame: Theta=45 Phi=315 KE=32.7103 Vpar=5.45392
KVNameValueList::ParticleParameters : Parameters associated with a particle in an event (0x7faae41241b8)
<frameName=lab>
virtual void Print(Option_t *option="") const
constexpr Double_t PiOver4()
  • change the velocity of the moving frame to \(0.1c\):
part.SetFrame("moving_frame", KVFrameTransform(TVector3(0,0,0.1),kTRUE));
part.Print();
KVParticle mass=939 Theta=45 Phi=0 KE=32.7103 Vpar=5.45392
rotated_frame: Theta=45 Phi=270 KE=32.7103 Vpar=5.45392
moving_frame: Theta=65.6491 Phi=0 KE=19.8389 Vpar=2.50151
rotated_moving_frame: Theta=65.6491 Phi=315 KE=19.8389 Vpar=2.50151
KVNameValueList::ParticleParameters : Parameters associated with a particle in an event (0x7faae41241b8)
<frameName=lab>

Note that in this case, the "rotated_moving_frame" is updated automatically to take account of the new velocity of "moving_frame".

However, if you change the kinematics of the particle in its original (default) frame, you have to update the kinematics in all defined frames by hand, it does not occur automatically:

part.SetTheta(30);
part.Print();
KVParticle mass=939 Theta=30 Phi=0 KE=32.7103 Vpar=6.67966
rotated_frame: Theta=45 Phi=270 KE=32.7103 Vpar=5.45392
moving_frame: Theta=65.6491 Phi=0 KE=19.8389 Vpar=2.50151
rotated_moving_frame: Theta=65.6491 Phi=315 KE=19.8389 Vpar=2.50151
KVNameValueList::ParticleParameters : Parameters associated with a particle in an event (0x7faae41241b8)
<frameName=lab>
part.UpdateAllFrames();
part.Print();
KVParticle mass=939 Theta=30 Phi=0 KE=32.7103 Vpar=6.67966
rotated_frame: Theta=30 Phi=270 KE=32.7103 Vpar=6.67966
moving_frame: Theta=46.1843 Phi=0 KE=15.8459 Vpar=3.76564
rotated_moving_frame: Theta=46.1843 Phi=315 KE=15.8459 Vpar=3.76564
KVNameValueList::ParticleParameters : Parameters associated with a particle in an event (0x7faae41241b8)
<frameName=lab>

Atomic nuclei

Atomic nuclei are described by the KVNucleus class. Nuclei can be initialised using \(Z\), \(Z\) and \(A\), or the symbol for the element (eventually with an isotopic mass):

int Z,A;
KVNucleus carbon(Z=6);
KVNucleus alpha(Z=2, A=4);
KVNucleus ni70("70Ni");
Description of properties and kinematics of atomic nuclei.
Definition KVNucleus.h:126

When no isotope mass is specified for an element \(Z\), the mass number \(A\) is calculated according to some formula:

KVNucleus nuc(82);//create Pb nucleus
std::cout << nuc.GetA() << std::endl;//by default this is the A corresponding to beta-stability, i.e. 208
nuc.SetMassFormula(KVNucleus::kVedaMass);//change the mass formula used for this nucleus
std::cout << nuc.GetA() << std::endl;//now the value is that calculated according to the Veda formula, 202
nuc.SetMassFormula(KVNucleus::kEALMass);//Evaporation Attractor Line from R.J. Charity
std::cout << nuc.GetA() << std::endl;//gives 186
nuc.SetMassFormula(KVNucleus::kBetaMass);//restore the default mass formula

\(Z\) and \(A\) can also be specified in separate steps:

KVNucleus a; //no A or Z specified
a.SetZ(10); //at this moment the nucleus' mass number is 20 (beta-stability)
a.SetA(24); //now this represents a 24Ne nucleus
Note
be careful not to use KVNucleus::SetZ() after KVNucleus::SetA(), because the mass number is always automatically recalculated from \(Z\) after a call to KVNucleus::SetZ().

Nuclear properties & systematics

Many informations on the known properties of atomic nuclei are included in the toolkit:

KVNucleus ca48("48Ca");
std::cout << ca48.GetBindingEnergy() << std::endl
std::cout << ca48.GetBindingEnergyPerNucleon() << std::endl
std::cout << ca48.GetMassExcess() << std::endl
std::cout << ca48.GetAbundance() << std::endl
std::cout << ca48.GetAtomicMass() << std::endl
std::cout << ca48.GetAWithMaxBindingEnergy() << std::endl
std::cout << ca48.GetMostAbundantA() << std::endl
std::cout << ca48.GetNaturalA() << std::endl
std::cout << ca48.GetChargeRadius() << std::endl
KVNucleus u236("236U");
std::cout << u236.GetFissionTKE() << std::endl
std::cout << u236.GetFissionVelocity() << std::endl
std::cout << u236.GetLifeTime() << std::endl
std::cout << u236.GetLiquidDropBindingEnergy() << std::endl
KVNucleus na23("23Na");
std::cout << na23.GetSpin() << std::endl
std::cout << na23.GetParity() << std::endl

This will print out:

415.99 // ca48.GetBindingEnergy()
8.66647 // ca48.GetBindingEnergyPerNucleon()
-54.434 // ca48.GetMassExcess()
0.187 // ca48.GetAbundance() [%]
47.9416 // ca48.GetAtomicMass()
46 // ca48.GetAWithMaxBindingEnergy()
40 // ca48.GetMostAbundantA()
40.1158 // ca48.GetNaturalA()
3.4771 // ca48.GetChargeRadius() [fm]
170.157 // u236.GetFissionTKE() [symmetric fission]
2.36015 // u236.GetFissionVelocity() [symmetric fission]
7.38573e+14 // u236.GetLifeTime() [seconds]
1740.24 // u236.GetLiquidDropBindingEnergy()
1.5 // na23.GetSpin()
1 // na23.GetParity()

Discrete excited states

Although not part of or accessible from a KVNucleus object, the KVLevelScheme class includes information on nuclear levels and excited states for nuclei. To use it:

KVLevelScheme li6("6Li");
li6.Draw();
Tool to simulate nucleus multi-particle decay.
6Li level scheme generated with KVLevelScheme

Nuclear arithmetic operators

The '+', '-' and '=' operators have been redefined for the KVNucleus class. One can therefore perform "nuclear arithmetic":

Example:

KVNucleus a("27Al", 12.0); // 27Al nucleus with 12 MeV/u kinetic energy
KVNucleus b("40Ca"); // 40Ca nucleus at rest
auto c = a + b;
c.Print();
#define c(i)
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t Float_t Float_t Float_t b

which produces the following output:

KVNucleus Z=33 A=67 E*=197.718
KVParticle mass=62534.3 Theta=0 Phi=0 KE=130.887 Vpar=1.93662

The \(Z\), \(A\), momentum vector and excitation energy of 'c' are calculated from the appropriate conservation laws. The mass excesses of the 3 nuclei are obviously taken into consideration. Therefore if 'a' and 'b' are projectile and target, 'c' is the compound nucleus after fusion, with its \(Z\), \(A\), recoil velocity and excitation energy.

In order to perform calorimetry (calculation of source characteristics from daughter nuclei) one need only sum all nuclei associated with the source. The resulting nucleus is the source nucleus with its Z, A, momentum and excitation energy.

Note
if one defines an excitation energy for a nucleus using the SetExcitEnergy() method, the mass and the total energy is modified (M = Mgs + Ex) when excitation energy is set, one can access to the ground state mass via GetMassGS()

The subtraction operator allows to perform energy balance for a binary splitting of a nucleus. Example:

KVNucleus d = c - b;
#define d(i)

In this case, the resulting nucleus 'd' should be identical to 'a' in the first example. One could also imagine

KVNucleus e = c - alpha;
#define e(i)

where 'alpha' is a KVNucleus alpha-particle, for which we specify the momentum after emission. The resulting nucleus 'e' is the residue of the fusion compound after evaporation of an alpha particle.

Note
It is the user's responsiblitly to handle excitation energy sharing between the outgoing nuclei.

The operators '+=' and '-=' also exist. 'a+=b' means 'a = a + b' etc.

Multi-particle events

The main business of KaliVeda is the analysis of multi-body events produced in heavy-ion reactions, therefore it is no surprise that a central role is played by container classes for collections of nuclei, known as events. In fact there are three main event classes:

Each of these classes inherits from the abstract base class KVEvent which can be used as an argument for general functions which can manipulate any type of event:

auto print_event = [](KVEvent& event){ event.Print(); };
print_event(recev); // OK
KVSimEvent simev;
print_event(simev); // OK
Abstract base class container for multi-particle events.
Definition KVEvent.h:67
Event containing KVReconstructedNucleus nuclei reconstructed from hits in detectors.
Container class for simulated nuclei, KVSimNucleus.
Definition KVSimEvent.h:22

Event building

Events are built up by successive calls to the KVEvent::AddParticle() method, which adds a particle object to the event corresponding to the event's defined particle type, and returns a pointer to the new particle:

auto nuc = recev.AddParticle();
std::cout << nuc->ClassName() << std::endl;
"KVReconstructedNucleus"
Particle * AddParticle()
virtual const char * ClassName() const

A small subtlety of this (virtual) method is that, when the event is accessed through a base pointer or reference, the pointer returned by KVEvent::AddParticle() is also a base pointer (to a KVParticle), although the added particle is of course always of the correct type for the event:

KVEvent& eventRef = recev;
auto nuc = eventRef.AddParticle();
std::cout << nuc->ClassName() << std::endl;
"KVReconstructedNucleus" // type of the nucleus object
std::cout << nuc->Class_Name() << std::endl;
"KVParticle" // type of the pointer
virtual KVParticle * AddParticle()=0

We also provide the KVEvent::AddNucleus() method, which does exactly the same thing as KVEvent::AddParticle(), except that it always returns a pointer to KVNucleus, as long as the particles contained in the event derive from KVNucleus (otherwise it returns nullptr):

KVEvent& eventRef = recev;
auto nuc = eventRef.AddNucleus();
std::cout << nuc->ClassName() << std::endl;
"KVReconstructedNucleus" // type of the nucleus object
std::cout << nuc->Class_Name() << std::endl;
"KVNucleus" // type of the pointer
KVNucleus * AddNucleus()
Definition KVEvent.cpp:109

Once added to an event, a particle cannot be removed from the event; however, all particles can be removed (deleted) in order to build a new event with method Clear():

recev.Clear();
recev.GetMult(); // size (multiplicity) of event
0
void Clear(Option_t *opt="")
Definition KVEvent.h:238
virtual Int_t GetMult(Option_t *opt="") const

Event multiplicities

The number of particles in any event (the size of the container) is called the multiplicity of the event, and is given by the method KVEvent::GetMult():

KVEvent* event; // pointer to some valid event object
event->GetMult();
2

The multiplicity can also be restricted to consider only nuclei which are considered "OK" for analysis (this usually concerns only reconstructed events) or counting only nuclei belonging to a previously defined "group":

KVEvent* event; // pointer to some valid event object
event->GetParticle(1)->SetIsOK(false); // note that by default all particles are considered "OK"
event->GetMult("ok"); // case insensitive: "ok" or "OK" or "oK" or "Ok" are all OK!
1
event->GetParticle(2)->AddGroup("QP"); // affect nucleus #2 to the "QP" group
event->GetMult("qP"); // group names are also case insensitive
1

Object ownership

All nuclei in an event belong to the event and are destroyed by the KVEvent destructor when it goes out of scope.

Therefore a KVEvent cannot be used to store references to nuclei in another KVEvent object, for example if one wants to handle a subset of the nuclei in the event. This is why the iterators presented below allow to iterate only over selected subsets (groups) of particles if required.

Storing references to nuclei in an event

If you really need to store a list of references to some nuclei in an event, you can use an STL container, TCollection or KVSeqCollection collection class to store the nuclei's pointers, as long as the collection does not try to delete the objects when it goes out of scope (e.g. don't use KVList which owns its objects by default).

Making a copy of all or part of an event

You can if you wish copy all or part of an event, as long as you understand that the nuclei in the copy will be new independent objects; they will not change if you change the original event after the copy (there may also be unwanted side-effects, especially for KVReconstructedNucleus particles).

Making a complete copy of an event (included the associated list of parameters) can be done like this:

KVEvent event, event2;
// make a copy of event in event2
event2.Clear(); // reset event2 before copying
event.Copy(event2);

Merging several events into one

Although it is difficult/unwise to separate events into subevents, on the other hand it is possible to merge several event fragments into one single event:

// 'event_list' is a TList (for example) containing KVEvent objects to merge
KVEvent merged_event;
merged_event.MergeEventFragments(&event_list);
virtual void MergeEventFragments(TCollection *events, Option_t *opt="")
Definition KVEvent.cpp:154

WARNING after merging, the subevents in the list will be empty and useless. Do not try to use them after merging!

Writing & reading events in ROOT TTree branches

Events i.e. KVEvent-derived objects can be written in a branch of a TTree in order to store them in a ROOT file. This is the format used for all event-based data handled (or generated) by KaliVeda.

Here is a complete example of how to write events in a TTree on disk:

TFile f("events.root", "recreate");
TTree* t = new TTree;
// the event object has to be instantiated on the heap (with 'new') before branch creation
KVSimEvent* simev = new KVSimEvent;
// note that a static method is used: the event pointer is given in argument
KVEvent::MakeEventBranch(t, "event_branch", simev);
// now we can begin to fill the events, and the TTree
simev->AddParticle();
simev->AddParticle();
...etc...
t->Fill();
// and finally write to disk
f.Write()
#define f(i)
static void MakeEventBranch(TTree *tree, const TString &branchname, T &event, Int_t bufsize=10000000)
Definition KVEvent.h:210

Reading back the events written in the branch of a TTree can be done as follows:

TFile f("events.root");
f.ls();
TFile** events.root
TFile* events.root
KEY: TTree tree;1 My tree
tree->Print();
******************************************************************************
*Tree :tree : My tree *
*Entries : 1 : Total = 2317 bytes File Size = 819 *
* : : Tree compression factor = 3.37 *
******************************************************************************
*Br 0 :event_branch : KVSimEvent *
*Entries : 1 : Total Size= 1951 bytes File Size = 429 *
*Baskets : 1 : Basket Size= 10000000 bytes Compression= 3.37 *
*............................................................................*
// to connect to the branch requires a null-initialised pointer
KVSimEvent* simev = nullptr;
// NB. the address of the pointer is passed as argument
tree->SetBranchAddress("event_branch", &simev);
// read an event
tree->GetEntry(0);
// simev is now initialised and points to the event read from the TTree
simev->Print();
KVSimEvent with 3 particles :
------------------------------------
KVNucleus Z=0 A=0 E*=-nan
KVParticle mass=0 Theta=0 Phi=0 KE=0 Vpar=0
etc. etc.
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t Float_t Float_t Float_t Int_t Int_t UInt_t UInt_t Rectangle_t Int_t Int_t Window_t TString Int_t GCValues_t GetPrimarySelectionOwner GetDisplay GetScreen GetColormap GetNativeEvent const char const char dpyName wid window const char font_name cursor keysym reg const char only_if_exist regb h Point_t winding char text const char depth char const char Int_t count const char ColorStruct_t color const char Pixmap_t Pixmap_t PictureAttributes_t attr const char char ret_data h unsigned char height h Atom_t Int_t ULong_t ULong_t bytes
virtual void Print(Option_t *t="") const
constexpr Double_t E()
const char * Size

Iterating over nuclei

There are several different ways to loop over all (or part of) the nuclei in an event and do something useful (for analysis) with them.

Simple indexed loop

KVSimEvent simev;
int ztot(0);
for(int i=1; i<=simev.GetMult(); ++i) ztot += simev.GetParticle(i)->GetZ();
Int_t GetZ() const
Return the number of proton / atomic number.
Particle * GetParticle(Int_t npart) const

Note that the same result can be achieved thus:

auto ztot = simev.GetSum("GetZ");
Double_t GetSum(const Char_t *Nucleus_method, Option_t *opt="")

As for the KVEvent::AddParticle() method (see above), the type of pointer returned by KVEvent::GetParticle() changes if the event is accessed through a base pointer or reference: in this case a base KVParticle pointer or reference is returned. If the particles in the event are derived from KVNucleus, KVEvent::GetNucleus() can be used in cases where a pointer to KVNucleus is sufficient. If more specific child class methods are accessed for the nuclei in the event, casting is required:

KVSimEvent simev;
TVector3 total_spin;
for(int i=1; i<=simev.GetMult(); ++i)
total_spin += simev.GetParticle(i)->GetAngMom(); // OK: KVSimEvent::GetParticle() returns KVSimNucleus*
KVEvent* event = &simev;
int ztot = 0;
for(int i=1; i<=event->GetMult(); ++i) {
// ztot += event->GetParticle(i)->GetZ(); => compile error: KVEvent::GetParticle() returns KVParticle*
ztot += event->GetNucleus(i)->GetZ(); // OK : KVEvent::GetNucleus() always returns KVNucleus*
total_spin += dynamic_cast<KVSimNucleus*>(event->GetParticle(i))->GetAngMom(); // OK
/* total_spin += dynamic_cast<KVSimNucleus*>(event->GetNucleus(i))->GetAngMom(); // also OK */
}
KVNucleus * GetNucleus(Int_t npart) const
Definition KVEvent.cpp:92
Nucleus in a simulated event.
const TVector3 * GetAngMom() const

In a simple indexed loop, if the iteration is to be restricted to only "OK" particles, or only those belonging to a previously-defined group, the loop must still be carried out over the full event (i.e. using KVEvent::GetMult() with no arguments), and the test performed for each particle:

// sum of Z for all "OK" nuclei
for(int i=1; i<=simev.GetMult(); ++i)
if(simev.GetParticle(i)->IsOK())
ztot_ok += simev.GetNucleus(i)->GetZ();
// sum of Z for all "QP" particles
for(int i=1; i<=simev.GetMult(); ++i)
if(simev.GetParticle(i)->BelongsToGroup("QP"))
ztot_qp += simev.GetNucleus(i)->GetZ();
Bool_t IsOK() const
Bool_t BelongsToGroup(const Char_t *groupname) const

Note that each of these iterations can be replaced by:

// sum of Z for all "OK" particles
ztot_ok = simev.GetSum("GetZ","Ok"); // case insensitive "OK"
// sum of Z for all "QP" particles
ztot_qp = simev.GetSum("GetZ","Qp"); // groupname case insensitive

Simple (deprecated) iterator

The KVEvent::GetNextParticle() and KVEvent::GetNextNucleus() methods can be used to easily loop over nuclei in an event (the former returns a pointer of the type of the particles in the event - unless the event is accessed through a base KVEvent pointer or reference, in which case a KVParticle pointer is returned - , the latter a KVNucleus pointer):

// print name of detector in which each particle of event was stopped -
// this only makes sense for KVReconstructedNucleus objects, reconstructed from
// hits recorded in different detectors
while( (nuc = recev.GetNextParticle()) ) { std::cout << nuc->GetStoppingDetector()->GetName() << std::endl; }
// note extra parentheses around assignment to avoid compiler warnings
Nuclei reconstructed from data measured by a detector array .
KVDetector * GetStoppingDetector() const
void SetDetector(int i, KVDetector *);
Particle * GetNextParticle(Option_t *opt="") const
const char * GetName() const override

This method is deprecated as it uses a shared internal iterator which makes it impossible to perform a nested loop using two such iterations; some methods such as KVEvent::GetMult() may also use the same internal iterator, therefore using any of those inside a loop using KVEvent::GetNextParticle() will have unexpected results!

As for the KVEvent::GetParticle() method (see above), the type of pointer returned by KVEvent::GetNextParticle() changes if the event is accessed through a base pointer or reference: in this case a base KVParticle pointer or reference is returned. Therefore if specific child class methods are accessed for the nuclei in the event, care must be taken:

KVEvent* event = &recev; // base class pointer to event
// while( (nuc = event->GetNextParticle()) ) { ... } => does not compile: event->GetNextParticle() returns KVParticle*
KVNucleus* nunuc;
while( (nunuc = event->GetNextNucleus()) ) // OK
// { std::cout << nunuc->GetStoppingDetector()->GetName() << std::endl; } => does not compile: KVNucleus has no "GetStoppingDetector" method
while( (nunuc = event->GetNextNucleus()) )
{ std::cout << dynamic_cast<KVReconstructedNucleus*>(nunuc)->GetStoppingDetector()->GetName() << std::endl; } // => OK
const Char_t * GetName() const
return the field fName

With the KVEvent::GetNextParticle()/KVEvent::GetNextNucleus() methods, it is easy to restrict the loop to only nuclei which are "OK" or only those belonging to a previously defined group:

// only print stopping detector for nuclei which are "OK":
while( (nuc = recev.GetNextParticle("ok")) ) { std::cout << nuc->GetStoppingDetector()->GetName() << std::endl; }
// only print stopping detector for nuclei in group "QP":
while( (nuc = recev.GetNextParticle("qp")) ) { std::cout << nuc->GetStoppingDetector()->GetName() << std::endl; }

Again, the name of the group or the "OK" status argument are case insensitive.

STL-style iterators

STL containers such as std::vector can be iterated over using the std::vector::iterator class:

std::vector<int> numbers {1,3,6,10};
for(std::vector<int>::iterator it = numbers.begin(); it != numbers.end(); ++it)
{ std::cout << (*it) << std::endl; }
1
3
6
10

KVEvent containers can be iterated over in an analogous fashion:

for(KVReconstructedEvent::Iterator it = recev.begin(); it!=recev.end(); ++it)
{
std::cout << (*it).GetStoppingDetector()->GetName();
}
// or more simply:
for(auto it = recev.begin(); it!=recev.end(); ++it)
{
std::cout << (*it).GetStoppingDetector()->GetName();
}
Iterator begin() const
Iterator end() const

Dereferencing the iterator (i.e. the result of *it) gives a reference to the type of nucleus contained in the event: in this case, a reference to a KVReconstructedNucleus. The following table resumes the different event classes & associated iterators and underlying nucleus types:

Event class Iterator class Type of nucleus
KVNucleusEvent KVNucleusEvent::Iterator KVNucleus
KVSimEvent KVSimEvent::Iterator KVSimNucleus
KVReconstructedEvent KVReconstructedEvent::Iterator KVReconstructedNucleus

Iterating over selected subgroups of particles

The iterator returned by KVReconstructedEvent::begin() in the previous example will include all nuclei of the event in the loop: this Iterator is of type KVReconstructedEvent::Iterator::Type::All. Iterating over only a selection of particles can be achieved using the KVTemplateParticleCondition class, which is just a wrapper for a lambda function used to select each particle according to user-defined criteria:

KVTemplateParticleCondition<KVNucleus> is_lcp("Z<3", [](const KVNucleus* n){ return n->GetZ()<3; });
KVNucleus helium("4He"), carbon("12C");
std::cout << std::boolalpha << is_lcp.Test(helium) << std::endl;
true
std::cout << std::boolalpha << is_lcp.Test(carbon) << std::endl;
false
An object for handling particle selection.
const Int_t n

To iterate over a selected subgroup of particles can therefore be carried out as in the following example:

for(auto it = KVReconstructedEvent::Iterator(recev, {"ZId",[](const KVReconstructedNucleus* n){ return n->IsZMeasured(); }}); it!=recev.end(); ++it)
{
// loop over all reconstructed nuclei whose Z was measured (identified)
}

Note that the type of the particle pointer argument in the lambda function must match the type of particles in the event.

Alternatively, you can call the ConditionalIterator() method of the event:

for(auto it = recev.ConditionalIterator({"ZId",[](const KVReconstructedNucleus* n){ return n->IsZMeasured(); }}); it!=recev.end(); ++it)
{
// loop over all reconstructed nuclei whose Z was measured (identified)
}
EventIterator ConditionalIterator(const KVTemplateParticleCondition< Particle > &c)

In order to simplify the use of such iterators, and especially when events are accessed through a KVEvent base pointer or reference (see below), we provide wrapper classes and aliases for an easier interface:

for(auto it = ReconEventIterator(recev, {"ZId",[](const KVReconstructedNucleus* n){ return n->IsZMeasured(); }}).begin(); it!=recev.end(); ++it)
{
// loop over all reconstructed nuclei whose Z was measured (identified)
}
Wrapper class for iterating over nuclei in KVReconstructedEvent accessed through base pointer or refe...

The following table resumes all types, wrappers and aliases for the three main event classes:

Event class Iterator type Wrapper Alias
KVNucleusEvent KVNucleusEvent::Iterator::Type::All KVNucleusEvent::EventIterator EventIterator
KVNucleusEvent::Iterator::Type::OK KVNucleusEvent::EventOKIterator EventOKIterator
KVNucleusEvent::Iterator::Type::Group KVNucleusEvent::EventGroupIterator EventGroupIterator
KVReconstructedEvent KVReconstructedEvent::Iterator::Type::All KVReconstructedEvent::EventIterator ReconEventIterator
KVReconstructedEvent::Iterator::Type::OK KVReconstructedEvent::EventOKIterator ReconEventOKIterator
KVReconstructedEvent::Iterator::Type::Group KVReconstructedEvent::EventGroupIterator ReconEventGroupIterator
KVSimEvent KVSimEvent::Iterator::Type::All KVSimEvent::EventIterator SimEventIterator
KVSimEvent::Iterator::Type::OK KVSimEvent::EventOKIterator SimEventOKIterator
KVSimEvent::Iterator::Type::Group KVSimEvent::EventGroupIterator SimEventGroupIterator

The Group and OK variants are specialized versions for selecting all particles belonging to a named group, or all particles which are "OK".

Range-based for loops

Just as STL containers can be iterated over using a range-based for loop,

std::vector<int> numbers {1,3,6,10};
for(auto number : numbers)
{ std::cout << number << std::endl; }
1
3
6
10

the same facility is also available for event classes, thanks to the iterator classes and wrappers presented above:

for(auto& nuc : event) { nuc.Print(); }
An event container for KVNucleus objects.
virtual void Print(Option_t *option="") const

Note the use of an automatic reference type for nuc: this is recommended to avoid copying of underlying nucleus objects, which may have unfortunate side-effects in certain cases (KVReconstructedNucleus).

Range-based for loops limited to subsets of selected particles can also be easily implemented thanks to the wrappers introduced above:

for(auto& nuc : ReconEventIterator(recev, {"ZId",[](const KVReconstructedNucleus* n){ return n->IsZMeasured(); }}))
{
// loop over all reconstructed nuclei whose Z was measured (identified)
}
for(auto& nuc : ReconEventOKIterator(recev))
{
// loop over all "OK" particles
}
KVSimEvent simev;
for(auto& nuc : SimEventGroupIterator(simev,"QP"))
{
// loop over particles in "QP" group
}
Wrapper class for iterating over "OK" nuclei in KVReconstructedEvent accessed through base pointer or...
Wrapper class for iterating over groups of nuclei in KVSimEvent accessed through base pointer or refe...

STL iterators & range-based for loops with base pointers/references to events

Often, for example in analysis classes, the event to be analysed (=iterated over) is accessed through a pointer or reference of base type KVEvent, for example via the KVEventSelector::GetEvent() method. The STL-type iterators which allow to perform the range-based for loops presented above are not defined for KVEvent, only in daughter classes inheriting from the templated class KVTemplateEvent. In such a case we do not necessarily know in advance what is the actual type of the event (and hence of the contained nuclei). However, the wrapper classes presented above require only a base pointer or reference (KVEvent* or KVEvent&), and so still can be used:

// assuming we are in a KVEventSelector analysis class: GetEvent() method returns a KVEvent*
// pointer to the events read from the TTree being analysed
for(auto& nuc : ReconEventOKIterator(GetEvent()))
{
// loop over all "OK" particles
std::cout << nuc.GetStoppingDetector()->GetName() << std::endl;
}

The choice of wrapper to use depends on the type of nucleus reference which is required in the loop, which in turn depends on the methods applied to each nucleus in the code: if only those methods which are defined for KVNucleus are required, an EventIterator wrapper (which returns KVNucleus& references) can be used; if methods specific to KVSimNucleus are used, a SimEventIterator wrapper should be used; etc. The following table resumes the different options:

Reference type Wrappers
KVNucleus& EventIterator
EventOKIterator
EventGroupIterator
KVReconstructedNucleus& ReconEventIterator
ReconEventOKIterator
ReconEventGroupIterator
KVSimNucleus& SimEventIterator
SimEventOKIterator
SimEventGroupIterator

Warning about type safety

It should be noted that these iterator wrappers are not 'type-safe': at compile time, there is no way of knowing if the event whose pointer or reference is supplied to the wrapper at run time will contain nuclei of the class corresponding to the wrapper (note that it is perfectly OK for the nuclei in the event to be of a class which inherits from the wrapper nucleus type). Consider the following example:

KVSimEvent simev;
KVEvent& base_ref = simev;
for(auto& nuc : ReconEventIterator(base_ref))
{
std::cout << nuc.GetStoppingDetector()->GetName() << std::endl;
}

The KVSimEvent contains KVSimNucleus nuclei; using the ReconEventIterator wrapper with a base reference to this event, we attempt to use a method with these nuclei which is only defined for KVReconstructedNucleus nuclei. This would have disastrous consequences (segmentation violation). To avoid this, a warning will be printed if the wrapper does not correspond to the type of nuclei in the event, and no iteration will be performed: in the previous case, this will give:

Warning in <KVTemplateEvent::Iterator>: KVTemplateEvent<KVReconstructedNucleus>::Iterator for KVReconstructedNucleus nuclei requested for event containing KVSimNucleus nuclei. Iteration is aborted.
Class used for iterating over particles in events.
void Warning(const char *location, const char *fmt,...)

Defining and using different kinematical reference frames for events

Defining and accessing different reference frames

You can define and use several different reference frames for the particles in an event. Each frame can be used independently, and new frames can be defined based on any of the existing frames:

Example:

  • define a new frame moving at 5 cm/ns in the beam direction:
KVEvent* event; // pointer to some valid event object
event->SetFrame("moving_frame", TVector3(0,0,5));
  • define a rotated coordinate frame in the "moving_frame", rotated by $90^o$ clockwise around the +ve beam direction:
KVEvent* event; // pointer to some valid event object
event->SetFrame("rotated_moving_frame", "moving_frame", rot);

Note that the same frame can be defined directly from the original frame of all particles in the event by using a combined boost-then-rotation transform:

KVEvent* event; // pointer to some valid event object
event->SetFrame("rotated_moving_frame", KVFrameTransform(TVector3(0,0,5),rot));
// OR, with C++11 and later:
event->SetFrame("rotated_moving_frame", {{0,0,5},rot});
  • define a similarly rotated coordinate frame in the original (default) reference frame:
KVEvent* event; // pointer to some valid event object
event->SetFrame("rotated_frame", rot);
  • access kinematical information in any of these frames for any of the particles in the event:
KVEvent* event; // pointer to some valid event object
event->GetParticle(i)->GetFrame("moving_frame")->GetVpar();
event->GetParticle(i)->GetFrame("rotated_frame")->GetPhi();
event->GetParticle(i)->GetFrame("rotated_moving_frame")->GetTransverseEnergy();

Note that the frame "rotated_moving_frame" is directly accessible even if it is defined in two steps as a rotation of the "moving_frame".

Changing the default reference frame of an event

Let us consider an event for which the different reference frames in the previous paragraph have been defined. Calling method KVEvent::Print() will show all reference frames defined for each particle:

KVEvent* event; // pointer to some valid event object
event->Print()
KVParticle mass=939 Theta=45 Phi=0 KE=32.7103 Vpar=5.45392
moving_frame: Theta=85.1751 Phi=0 KE=16.6117 Vpar=0.468125
rotated_moving_frame: Theta=85.1751 Phi=270 KE=16.6117 Vpar=0.468125
rotated_frame: Theta=45 Phi=270 KE=32.7103 Vpar=5.45392
etc. etc.

Indentation indicates the relationships between frames: "rotated_moving_frame" is a child frame of "moving_frame". The first line is the default kinematics. As yet it has no name, but if we want we can set a name for the default kinematics of each particle in the event:

KVEvent* event; // pointer to some valid event object
event->SetFrameName("lab");

Now if we want to change the default kinematical frame for the event by using KVEvent::ChangeDefaultFrame():

KVEvent* event;// pointer to some valid event object
event->ChangeDefaultFrame("rotated_moving_frame");
event->Print();
KVParticle mass=939 Theta=85.1751 Phi=270 KE=16.6117 Vpar=0.468125
moving_frame: Theta=85.1751 Phi=0 KE=16.6117 Vpar=0.468125
lab: Theta=45 Phi=0 KE=32.7103 Vpar=5.45392
rotated_frame: Theta=45 Phi=270 KE=32.7103 Vpar=5.45392
KVNameValueList::ParticleParameters : Parameters associated with a particle in an event (0x7f5a1ff8b1b8)
<frameName=rotated_moving_frame>

Note that the name of the default kinematics is stored as a parameter "frameName" and can be retrieved with method KVEvent::GetFrameName(). Note also how the relationships between frames are preserved, i.e. if we present the frames as graphs:

with "lab" as default frame:

lab
|
+--moving_frame
| |
| +--rotated_moving_frame
|
+--rotated_frame

with "rotated_moving_frame" as default frame:

rotated_moving_frame
|
+--moving_frame
|
+--lab
|
+--rotated_frame