KaliVeda
Toolkit for HIC analysis
Loading...
Searching...
No Matches
Detector Array Geometries

The main purpose of KaliVeda is to describe charged-particle detector arrays in order to perform the following tasks:

  • simulate detection of charged particles by the array;
  • simulate reconstruction & identification of charged particles detected by the array;
  • reconstruct & identify charged particles detected by the array from experimental data.

In order to carry out these tasks we need a complete description of the array's geometry, including detector dimensions and positions and dead zones which may or may not be associated to the structure of the detectors themselves. The base class for decribing a single detector array geometry is KVMultiDetArray, while KVExpSetUp describes a combined array of two or more KVMultiDetArray objects.

In this chapter we will first show how to create new arrays by describing the detector geometry using the ROOT geometry package and KVMaterial to represent the different absorbers (for energy loss calculations). Then we will show how to import the array geometry into the KaliVeda framework using KVGeoImport and KVMultiDetArray. We will present the various structures and objects of different types which are automatically deduced from the geometry:

  • KVDetector to represent single- or multi-layer detectors;
  • KVGeoStrucElement to represent composite structures consisting of groups of detectors and/or other structures;
  • KVGeoDNTrajectory to represent each unique trajectory i.e. unique set of detectors a particle may pass through after leaving the target;
  • KVReconNucTrajectory for each possible unique trajectory which may be associated with a reconstructed detected particle;
  • KVIDTelescope for each possibility for particle identification along a given reconstruction trajectory;
  • KVGroup for each subset of detectors which can be treated independently of all others, because they share a common set of trajectories;

and how they can be used to navigate around the geometry of either a newly-created or already existing multidetector array geometry.

Creating a valid geometry

Simple (single-layer) detectors

Let us begin with a simple example: a single telescope made up of a stack of 3 identical silicon wafers. The first thing to do when describing any geometry is to initialise the geometry manager:

TGeoManager* myGeo = new TGeoManager("simpleGeo1", "A simple detector array");

Next we set up the materials required for the construction of our array, and create a 'top' volume which will be the 'world' containing our geometry:

/* materials required for creation of geometry */
KVMaterial silicon("Si");
/* top volume must be big enough to contain all detectors - if in doubt, make it too big!! */
TGeoVolume *top = myGeo->MakeBox("WORLD", silicon.GetGeoMedium("Vacuum"),
myGeo->SetTopVolume(top);
Description of physical materials used to construct detectors & targets; interface to range tables.
Definition KVMaterial.h:94
TGeoVolume * MakeBox(const char *name, TGeoMedium *medium, Double_t dx, Double_t dy, Double_t dz)
void SetTopVolume(TGeoVolume *vol)
const long double cm
Definition KVUnits.h:66

In this case, our WORLD is a vacuum-filled box with sides whose half-lengths are 50 cm, i.e. it is a cube of side 1 metre. The KVMaterial class is an interface to the energy loss calculator (see Energy loss & range calculations ). Note that any KVMaterial object can be used to obtain the "Vacuum" medium.

Now we can build our silicon wafers, which are to be 5cm square and 300$\mu$m thick:

double si_dim = 5 *KVUnits::cm; // square silicon detector 5cm x 5cm
double si_thick= 300*KVUnits::um;// silicon thickness 300um
// build the detector
TGeoVolume *si_det = myGeo->MakeBox("DET_SILICON",silicon.GetGeoMedium(),si_dim/2.,si_dim/2.,si_thick/2.);
const long double um
Definition KVUnits.h:68

Note that, once again, the dimensions are given in terms of half-lengths. The name of our silicon box is very important: in order for KaliVeda to recognise a volume as a detector, and add a KVDetector object to the KVMultiDetArray representing it, its name must begin with DET_.

Next we place three silicon detectors one behind the other, with the first one being 10 cm from the centre of the WORLD volume (the origin of the coordinate system), and the two others placed respectively 0.25 cm and 0.5 cm behind it:

double si_dist = 10*KVUnits::cm; // distance from target (origin): 10cm
// positioning of volumes is done with respect to the centre of the object:
// therefore in order to have the entrance window of the first detector at si_dist
// centimetres from the origin, its centre must be at si_dist + half the thickness
// of the detector (0.5*si_thick)
top->AddNode(si_det, 1, new TGeoTranslation(0,0,si_dist+0.5*si_thick));
top->AddNode(si_det, 2, new TGeoTranslation(0,0,si_dist+0.5*si_thick+0.25*KVUnits::cm));
top->AddNode(si_det, 3, new TGeoTranslation(0,0,si_dist+0.5*si_thick+0.50*KVUnits::cm));
virtual TGeoNode * AddNode(TGeoVolume *vol, Int_t copy_no, TGeoMatrix *mat=nullptr, Option_t *option="")

Notice how we use the same volume 3 times to create 3 independent detectors? There is no need to create more than one volume (geometric shape/medium) to describe detectors which are the same apart from their position (node) in the geometry. The second argument in each call to top->AddNode(...) is a 'node number' which is used to distinguish the three detectors: they will be automatically given names DET_SILICON_1, DET_SILICON_2 and DET_SILICON_3. They will in turn be represented by KVDetector objects with names SILICON_1, SILICON_2 and SILICON_3 in the final geometry.

The last step in creating a valid ROOT geometry description of an array is

myGeo->CloseGeometry();
void CloseGeometry(Option_t *option="d")

Closing the geometry prepares and optimizes the geometrical modeller; no further changes can be made to the geometry after this step. You can now view the geometry in 3D using OpenGL:

myGeo->GetTopVolume()->Draw("ogl");
TGeoVolume * GetTopVolume() const
void Draw(Option_t *option="") override

Multi-layer detectors & dead zones

Now for a slightly more realistic example: an ionisation chamber consisting of an aluminium frame, two mylar windows, and between the windows: isobutane gas at a pressure of 50 mbar. As before we begin by initialising the geometry manager, the materials we'll need and create a WORLD volume:

/* must create geomanager before anything else */
TGeoManager* myGeo = new TGeoManager("simpleGeo2", "A simple ionisation chamber");
/* materials required for creation of geometry */
KVMaterial aluminium("Al");
KVMaterial mylar("Mylar");
KVMaterial isobutane("Isobutane");
isobutane.SetPressure(50*KVUnits::mbar);
/* top volume must be big enough to contain all detectors - if in doubt, make it too big!! */
TGeoVolume *top = myGeo->MakeBox("WORLD", aluminium.GetGeoMedium("Vacuum"),50,50,50);
myGeo->SetTopVolume(top);
const long double mbar
Definition KVUnits.h:81

Now we build the volume corresponding to our two (square) mylar windows:

double dx_chio = 4.*KVUnits::cm; // 4 cm square detector
double thick_frame = 1*KVUnits::mm; // 1 mm thick aluminium frame
double thick_win = 2.5*KVUnits::um;// 2.5 um thick mylar window
TGeoVolume* window = myGeo->MakeBox("WINDOW", mylar.GetGeoMedium(),
(dx_chio-2*thick_frame)/2, (dx_chio-2*thick_frame)/2., thick_win/2.);
const long double mm
Definition KVUnits.h:69

Now the volumes for building the frame: one for the top & bottom, and one for the sides:

double thick_gas = 2.*KVUnits::cm;;// 2 cm thick gas
double thick_chio = thick_gas+2*thick_win;
/* frame = dead zone (stops all particles) of ionisation chamber */
TGeoVolume* frame_top = myGeo->MakeBox("DEADZONE_CHIOFRAME_TOP", aluminium.GetGeoMedium(),
(dx_chio-2*thick_frame)/2, thick_frame/2.,thick_chio/2.);
TGeoVolume* frame_side = myGeo->MakeBox("DEADZONE_CHIOFRAME_SIDE", aluminium.GetGeoMedium(),
thick_frame/2., dx_chio/2, thick_chio/2.);

The keyword DEADZONE_ in the name of these volumes will be recognised by KaliVeda when the geometry is imported: when particles are propagated through the array during detection simulation, any which arrive in these volumes will be stopped regardless of their kinetic energy.

Finally we construct the gas volume where the energy losses of charged particles traversing the detector will be reported as the energy measured in the detector: this is the 'active' part of the detector, the volume name has the keyword ACTIVE_:

/* active layer of ionisation chamber */
TGeoVolume* gas = myGeo->MakeBox("ACTIVE_GAS", isobutane.GetGeoMedium(),
(dx_chio-2*thick_frame)/2, (dx_chio-2*thick_frame)/2., thick_gas/2.);

Now in order to build our detector, we put together all the pieces in a special kind of volume which is built from other volumes: a TGeoVolumeAssembly, to which we give the name for our new detector, beginning with the keyword DET_:

/* put everything together in detector structure */
TGeoVolume* chio = myGeo->MakeVolumeAssembly("DET_CHIO");
chio->AddNode(window, 1, new TGeoTranslation(0,0, -thick_chio/2. + thick_win/2.));
chio->AddNode(gas, 1);
chio->AddNode(window, 2, new TGeoTranslation(0,0, thick_chio/2. - thick_win/2.));
chio->AddNode(frame_top, 1, new TGeoTranslation(0, (dx_chio-thick_frame)/2.,0.));
chio->AddNode(frame_top, 2, new TGeoTranslation(0, -(dx_chio-thick_frame)/2.,0.));
chio->AddNode(frame_side, 1, new TGeoTranslation((dx_chio-thick_frame)/2.,0,0.));
chio->AddNode(frame_side, 2, new TGeoTranslation(-(dx_chio-thick_frame)/2.,0,0.));
TGeoVolumeAssembly * MakeVolumeAssembly(const char *name)

Last of all we position our ionisation chamber where we want in our geometry:

double dist_chio = 20*KVUnits::cm; // place 20 cm from target
top->AddNode(chio, 1, new TGeoTranslation(0,0,dist_chio+0.5*thick_chio));

As in the previous example, in order to have the entrance window of the ionisation chamber at a given distance from the origin, because positioning of volumes is done with respect to the centre of the volume, we place our volume at the distance plus half of the (total) thickness of the ionisation chamber.

After closing the geometry we are then ready to visualize and import the geometry into KaliVeda.

Angular positioning & detector alignment

There is a trick to placing detectors at given angular coordinates \((\theta,\phi)\) while keeping the detector's axis aligned with the flight path of particles coming from the target (assumed to be placed at the origin). As it is not easy to deduce, we provide the static method

double Double_t
static TGeoHMatrix * GetVolumePositioningMatrix(Double_t distance, Double_t theta, Double_t phi, TGeoTranslation *postTrans=nullptr)

As always, using the KaliVeda coordinate-system convention: i.e.

  • the positive \(z\)-axis is aligned with the beam direction;
  • the positive \(x\)-axis is vertically upwards (12 o'clock);
  • the positive \(y\)-axis (corresponding to \(\phi=+90^o\) is in the horizontal plane, pointing to the right when looking in the beam direction

Then in order to position a detector volume at a distance \(D\) cm from the target at angular coordinates \((\theta,\phi)\) with its entrance window perpendicular to the vector connecting the centre of the detector to the origin, do the following:

[TGeoVolume* det = pointer to our detector volume]
[Double_t phi,theta = detector angles]
[Double_t D = distance to entrance window]
[Double_t detector_thickness = ... guess]
[Int_t node_number = number to give to this detector in geometry]
top->AddNode(det, node_number,
KVMultiDetArray::GetVolumePositioningMatrix(D+0.5*detector_thickness,theta,phi));
int Int_t

Detector structures

Any association of detectors which occurs several times in a geometry in different places, with the same internal structure, can be defined as a 'structure'. Such a structure can be defined once and then re-used many times in your detector array. They will be converted into KVGeoStrucElement objects when the geometry is imported into KaliVeda. A KVGeoStrucElement is a basic building block of any detector array geometry, which can contain detectors and other structures. KVMultiDetArray itself is a child class of KVGeoStrucElement.

As an example, we could combine our ionisation chamber example with a silicon detector placed immediately behind in order to create a telescope:

TGeoVolume* chio_si = myGeo->MakeVolumeAssembly("STRUCT_TELESCOPE");
double si_dist = 1*KVUnits::cm; // place silicon 1cm behind IC
double tel_length = thick_chio+si_dist+si_thick; // total length of telescope
chio_si->AddNode(chio, 1, new TGeoTranslation(0,0,0.5*(thick_chio-tel_length)));
chio_si->AddNode(si_det, 1, new TGeoTranslation(0,0,0.5*(tel_length-si_thick)));

The keyword STRUCT in the structure's volume name is essential. Each node added to the geometry using this structure will generate a KVGeoStrucElement (after importing the geometry) with name TELESCOPE_1, TELESCOPE_2, etc. The detectors inside each telescope will generate KVDetector objects with names TELESCOPE_1_CHIO_1, TELESCOPE_1_SI_1, TELESCOPE_2_CHIO_1, TELESCOPE_2_SI_1, etc. (note that the _1 after CHIO and SI is the node number referring to the positioning of the detectors inside the structure, which does not change).

Importing the geometry

Once you have a valid ROOT geometry describing your array, you can import it into KaliVeda using the KVGeoImport class to initialise a new KVMultiDetArray object:

gimp.ImportGeometry();
Import detector array described by ROOT geometry and set up corresponding KVMultiDetArray object.
Definition KVGeoImport.h:68
static KVIonRangeTable * GetRangeTable()
Base class for describing the geometry of a detector array.

Here we have used the gGeoManager global pointer to the currently active geometry: you can of course use whatever valid pointer you have to your geometry (e.g. myGeo in the above examples).

As an example, let us see what happens if you import the first example geometry given above, composed of a telescope of three silicon detectors:

Info in <KVGeoImport::ImportGeometry>: Importing geometry in angular ranges : Theta=[0.000000,180.000000:0.100000] Phi=[0.000000,360.000000:1.000000]
Info in <KVGeoImport::ImportGeometry>: tested 650161 directions
void Info(const char *location, const char *fmt,...)

The first step is a scan of the geometry over the angular ranges indicated, which in this case is overkill: you can change the default range and angular step size if you wish when you call KVGeoImport::ImportGeometry().

Info in <KVGeoImport::ImportGeometry>: Imported 3 detectors into array
Info in <KVMultiDetArray::AssociateTrajectoriesAndNodes>: Removed 2 duplicated sub-trajectories
Info in <KVMultiDetArray::AssociateTrajectoriesAndNodes>: Calculated 1 particle trajectories
Info in <KVMultiDetArray::DeduceGroupsFromTrajectories>: Deducing groups of detectors from trajectories
Info in <KVMultiDetArray::DeduceGroupsFromTrajectories>: Filling group trajectory lists
Info in <KVMultiDetArray::DeduceIdentificationTelescopesFromGeometry>: Calculating...
-- created 2 telescopes
Info in <KVMultiDetArray::CalculateReconstructionTrajectories>: Calculating trajectories for particle reconstruction:
-- calculated 3 reconstruction trajectories

These are the results of the scan: 3 detectors were correctly identified and imported. You can see the list by doing:

gMultiDetArray->GetDetectors()->ls();
OBJ: KVDetector SILICON_1 Si : 0 at: 0x34f3110
OBJ: KVDetector SILICON_2 Si : 0 at: 0x34f5770
OBJ: KVDetector SILICON_3 Si : 0 at: 0x34f6b60
Base class for detector geometry description.
Definition KVDetector.h:160
const KVSeqCollection * GetDetectors() const
void ls(Option_t *option="") const override

Next we calculate all possible trajectories through the detectors of the array that a particle leaving the target (origin) could take. In this case the answer is: 1! You can obtain the list with:

gMultiDetArray->GetTrajectories()->ls();
GDNTraj_1 : SILICON_3/SILICON_2/SILICON_1/
const TSeqCollection * GetTrajectories() const

(Notice that trajectories always begin with the detector the furthest from the target (origin). See class KVGeoDNTrajectory for a full explanation of how to use these trajectories to navigate through the geometry)

From this information, we then proceed to deduce all possible ways that a (charged) particle could be identified from its energy losses in the detectors on the different trajectories. By default, this means associating each pair of successive detectors on the same trajectory into a \(\Delta E\)- \(E\) identification telescope, but it is possible (by defining appropriate plugins for the KVIDTelescope base class) to add single-detector identification methods (such as pulse shape analysis in silicon detectors or cesium iodide scintillators). To see the list of deduced identification telescopes:

gMultiDetArray->GetListOfIDTelescopes()->ls();
OBJ: KVIDTelescope ID_SILICON_2_SILICON_3 : 0 at: 0x3dab370
OBJ: KVIDTelescope ID_SILICON_1_SILICON_2 : 0 at: 0x3dac490
Base class for all detectors or associations of detectors in array which can identify charged particl...
KVSeqCollection * GetListOfIDTelescopes() const

Finally, we calculate all possible trajectories that could correspond to particles stopping in the detectors of the array, from which we can try to reconstruct the identity of the particle. In this case, there are 3 possibilities, corresponding to particles stopping in either the first, second, or third detector. To see the list of reconstruction trajectories:

gMultiDetArray->GetReconTrajectories()->ls();
GDNTraj_1_SILICON_3 : SILICON_3/SILICON_2/SILICON_1/
Identifications [2/2] :
ID_SILICON_2_SILICON_3 (1)
ID_SILICON_1_SILICON_2 (1)
GDNTraj_1_SILICON_2 : SILICON_2/SILICON_1/
Identifications [1/1] :
ID_SILICON_1_SILICON_2 (1)
GDNTraj_1_SILICON_1 : SILICON_1/
Identifications [0/0] :
void ls(Option_t *option="") const override

Each reconstruction trajectory has an associated list of identification telescopes which could be used to try to identify corresponding particles. See class KVReconNucTrajectory.

Now let us see what happens if we import our ionisation chamber+silicon telescope structure example:

Info in <KVGeoImport::ImportGeometry>: tested 650161 directions
Info in <KVGeoImport::ImportGeometry>: Imported 2 detectors into array
Info in <KVMultiDetArray::CalculateTrajectories>: Calculating all possible trajectories:
-- calculated 1 trajectories
Info in <KVMultiDetArray::DeduceIdentificationTelescopesFromGeometry>: Calculating...
-- created 1 telescopes
Info in <KVMultiDetArray::CalculateReconstructionTrajectories>: Calculating...
-- calculated 2 trajectories
gMultiDetArray->Print();
KVMultiDetArray::simpleGeo3 [TYPE=Ionisation chamber+silicon telescope]
DETECTORS :
TELESCOPE_1_CHIO_1
TELESCOPE_1_SILICON_1
KVGeoStrucElement::TELESCOPE_1 [TYPE=TELESCOPE]
DETECTORS :
TELESCOPE_1_CHIO_1
TELESCOPE_1_SILICON_1
KVGroup::Group_1 [TYPE=GROUP]
DETECTORS :
TELESCOPE_1_CHIO_1
TELESCOPE_1_SILICON_1
void Print(Option_t *option="") const
const RequestId_t TYPE

The output of the KVMultiDetArray::Print() method is two lists: first the list of all detectors in the array, and then the list of all structures (KVGeoStrucElement) in the array, and the lists of all detectors that they contain. Here we can see that our TELESCOPE structure has been faithfully imported into the array as expected as a KVGeoStrucElement object, but there is also a second structure which is of type GROUP. These structures are defined automatically for all detector geometries, a group (KVGroup) corresponds to the largest subset of detectors which can be treated independently of all others in the array: they are defined by a common set of trajectories which concern only the detectors of the group and no others.

Structure & detector names

The names of the detectors reflect their positioning in a TELESCOPE structure, which gives quite long names, especially for the derived identification telescope(s):

gMultiDetArray->GetListOfIDTelescopes()->ls()
OBJ: KVIDTelescope ID_TELESCOPE_1_CHIO_1_TELESCOPE_1_SILICON_1 : 0 at: 0x3d56770

What can we do?

Change default formatting

We can change the default names given to structures and detectors using two methods provided by KVGeoNavigator (the base class for KVGeoImport). They allow to define our own formatting conventions for the names derived from the geometry. For example, if we do

// change default formatting of structure & detector names
gimp.SetStructureNameFormat("TELESCOPE", "T$number%02d$");
gimp.SetDetectorNameFormat("$det:name%.2s$_$struc:TELESCOPE:name$");
gimp.ImportGeometry();

then the result is:

gMultiDetArray->Print()
KVMultiDetArray::simpleGeo3 [TYPE=Ionisation chamber+silicon telescope]
DETECTORS :
CH_T01
SI_T01
[...]
gMultiDetArray->GetListOfIDTelescopes()->ls()
OBJ: KVIDTelescope ID_CH_T01_SI_T01 : 0 at: 0x3d56770

See KVGeoNavigator::SetStructureNameFormat() and KVGeoNavigator::SetDetectorNameFormat() for more details on the syntax.

Use correspondance list name translation

Not every case can be treated using formatting strings as above however, so we provide another possibility. If you provide a file containing lines like:

[file names.txt]
TELESCOPE_1_CHIO_1: DE-01
TELESCOPE_1_SILICON_1: E-01
constexpr Double_t E()

and then do

// set correspondance list for name translation
gimp.SetNameCorrespondanceList("names.txt");
gimp.ImportGeometry();

the file will be used to look up the name of each detector and/or structure as derived from the geometry using the default formatting rules (or whatever formatting you impose), and replace it with whatever name is given in the file:

gMultiDetArray->Print()
KVMultiDetArray::simpleGeo3 [TYPE=Ionisation chamber+silicon telescope]
DETECTORS :
DE-01
E-01
gMultiDetArray->GetListOfIDTelescopes()->ls()
OBJ: KVIDTelescope ID_DE-01_E-01 : 0 at: 0x4eeb2d0

Navigating the geometry of a multidetector array

With a detector array geometry described by KVMultiDetArray or a derived class, you have access to all useful information on the geometry, the detectors, the structures and the identification telescopes etc. of the array, e.g.:

gMultiDetArray->GetDetectors(); // list of all detectors
gMultiDetArray->GetDetector(name); // find detector by name
gMultiDetArray->GetDetectorByType(type); // find detector by type
KVDetector * GetDetector(const Char_t *name) const
Return detector in this structure with given name.
KVDetector * GetDetectorByType(const Char_t *type) const
Return detector in this structure with given type.

(these methods are actually defined by base class KVGeoStrucElement).

gMultiDetArray->Print();
KVMultiDetArray::simpleGeo3 [TYPE=Ionisation chamber+silicon telescope]
DETECTORS :
TELESCOPE_1_CHIO_1
TELESCOPE_1_SILICON_1
KVGeoStrucElement::TELESCOPE_1 [TYPE=TELESCOPE]
DETECTORS :
TELESCOPE_1_CHIO_1
TELESCOPE_1_SILICON_1
KVGroup::Group_1 [TYPE=GROUP]
DETECTORS :
TELESCOPE_1_CHIO_1
TELESCOPE_1_SILICON_1

The output of the KVMultiDetArray::Print() method is two lists: first the list of all detectors in the array, and then the list of all structures (KVGeoStrucElement) in the array, each with the list of all detectors that it contains. As well as user-defined structures (such as the association of two detectors into a TELESCOPE structure above) there are also structures of type GROUP which correspond to objects of class KVGroup. These structures are defined automatically for all detector geometries, a group corresponds to the largest subset of detectors which can be treated independently of all others in the array: they are defined by a common set of trajectories which concern only the detectors of the group and no others.

Nodes & trajectories

The geometry is represented as a set of nodes (KVGeoDetectorNode) which are linked by trajectories (KVGeoDNTrajectory) corresponding to all unique paths any particle may take starting from the origin (target position) and traversing the detectors of the array. Each node is associated with a detector. Each node/detector can be associated with one or more trajectories going either forwards (towards the target) or backwards (away from the target), depending on how detectors in the array are aligned.

For example, a simple array composed of a stack of 3 silicon detectors, placed one behind the other, with SILICON_1 being closest to the target and SILICON_3 furthest away, contains a single trajectory:

gMultiDetArray->GetTrajectories()->ls();
GDNTraj_1 : SILICON_3/SILICON_2/SILICON_1/

The name/title of the trajectory indicates the order of crossing different detectors when moving along the trajectory: notice that trajectories always begin with the detector the furthest from the target. It is simple to access the trajectories associated with any given detector, but you first have to access the associated node in the geometry, e.g.:

// retrieve first (in case there are several) trajectory going forwards from SOME_DETECTOR
KVGeoDNTrajectory* tr = (KVGeoDNTrajectory*)gMultiDetArray->GetDetector("SOME_DETECTOR")->GetNode()->GetForwardTrajectories()->First();
KVGeoDetectorNode * GetNode()
Definition KVDetector.h:326
Path taken by particles through multidetector geometry.
KVSeqCollection * GetForwardTrajectories() const
virtual TObject * First() const

It is then easy to follow the trajectory i.e. to iterate over all nodes/detectors in a given direction:

// start new forwards (towards target) iteration
while( ( n = tr->GetNextNode() ) )
{
KVDetector* det = n->GetDetector();
// do something with each detector on trajectory
// ...
}
void IterateFrom(const KVGeoDetectorNode *node0=nullptr) const
KVGeoDetectorNode * GetNextNode() const
Information on relative positions of detectors & particle trajectories.
const Int_t n

In addition, for each trajectory consisting of \(N\) nodes/detectors, there are \(N\) reconstruction trajectories which may be used to reconstruct particles from measured energy losses in the detectors of the array, depending on which detector the particle stopped in (which is assumed to be the first i.e. furthest from the target detector on the trajectory which fired). Using the same example of a 3-silicon telescope array, we have:

gMultiDetArray->GetReconTrajectories()->ls();
GDNTraj_1_SILICON_3 : SILICON_3/SILICON_2/SILICON_1/
Identifications [2/2] :
ID_SILICON_2_SILICON_3 (1)
ID_SILICON_1_SILICON_2 (1)
GDNTraj_1_SILICON_2 : SILICON_2/SILICON_1/
Identifications [1/1] :
ID_SILICON_1_SILICON_2 (1)
GDNTraj_1_SILICON_1 : SILICON_1/
Identifications [0/0] :

These are KVReconNucTrajectory objects. As reconstructed particles are always associated with a reconstruction trajectory, one can always access the trajectory for each particle when analysing reconstructed i.e. experimental (or filtered) data. These trajectories can be iterated over in the same way as the others:

// e.g. in reconstructed data analysis class
for(auto& rnuc : ReconEventIterator(GetEvent()))
{
auto rtraj = rnuc.GetReconstructionTrajectory();
rtraj->IterateBackFrom(); // backwards iteration: i.e. start from detector closest to target,
// iteration stops with 'stopping detector'
// e.g. to calculate theoretical energy losses of particle along trajectory
auto einc = rnuc.GetE();
while( ( node = rtraj->GetNextNode() ) )
{
KVDetector* det = node->GetDetector();
einc -= det->GetTotalDeltaE(rnuc.GetZ(), rnuc.GetA(), einc); // total energy loss, not just in active layer (e.g. gas + windows)
}
if(einc!=0){ /* in an ideal world einc=0 here! */ }
}
virtual Double_t GetTotalDeltaE(Int_t Z, Int_t A, Double_t Einc)
KVDetector * GetDetector() const
Wrapper class for iterating over nuclei in KVReconstructedEvent accessed through base pointer or refe...