KaliVeda
Toolkit for HIC analysis
Loading...
Searching...
No Matches
KVEventMixer< ParticleInfoStruct, NumBins > Class Template Reference

Detailed Description

template<typename ParticleInfoStruct, int NumBins = 1>
class KVEventMixer< ParticleInfoStruct, NumBins >

Generic event mixing algorithm for two-particle correlation studies.

Template Parameters
ParticleInfoStructuser-provided structure which will be used to store any required information on each particle to be used in the event mixing
NumBinsnumber of different event classes to be treated separately in the event mixing

This class is a generic implementation of the event mixing technique used to estimate the uncorrelated background for two-particle correlation functions. Events belonging to different user-defined event classes can be treated separately at the same time.

The generic problem adressed here is the construction of 2-particle correlation functions using an event mixing technique. Given events containing particles of type A and particles type B, it is assumed the user wants to construct some quantity which can be calculated from the properties of any pair (A,B), using both correlated and uncorrelated (mixed) pairs. The algorithm requires the user to provide:

  • iterators to perform loops over A and B particles in each event;
  • functions to treat each correlated and uncorrelated pair (A,B)

For each analysed event, the function for correlated pairs will be called for each pair in the event, and then each previous event stored in buffers will be used to generate uncorrelated pairs for which the corresponding function will be called. The particles of each type of the \(N\) previous events (by default \(N=10\): can be changed with an argument to the class constructor) are kept in memory: decorrelating begins when each buffer is full, after this each new event which is added causes the previous 'oldest' event in the same buffer to be discarded.

Defining storage for particle properties to be used in event mixing buffers

The user must define a class/struct which will be used to store any necessary information on each particle in the buffers used in the event mixing. This struct must define a constructor whose single argument is compatible with whatever type of reference to the particles in the event is returned by the iterators provided by the user (see ProcessEvent() method below). In the case of events stored in KVEvent (or derived) objects, this class/struct must have a constructor which can be called with a reference to a KVNucleus object (or derived class) and which will extract and store the required information.

Here are some examples:

struct azimuthal_cor
{
double phi;
azimuthal_cor(const KVNucleus& nuc) : phi{nuc.GetPhi()} {}
};
Description of properties and kinematics of atomic nuclei.
Definition KVNucleus.h:126

This could be used for azimuthal correlations, supposing that all is required is the azimuthal angle of each particle.

In experimental cases (i.e. analysing reconstructed data), you probably need to store some information on which detector was hit by the particle, in order to avoid generating uncorrelated pairs which could not exist experimentally. For example:

struct azimuthal_cor
{
double phi;
int det_index;
azimuthal_cor(const KVReconstructedNucleus& nuc)
: phi{nuc.GetPhi()}, det_index{nuc.GetStoppingDetector()->GetIndex()} {}
};
Nuclei reconstructed from data measured by a detector array .

If more detailed informations on each particle are required, the most simple (but not the most lightweight in terms of memory) way may be to store the informations in a KVNucleus object in your structure. In this case, rather than trying to do a direct 'copy' of the nucleus passed to the constructor (especially when dealing with KVReconstructedNucleus objects), you should use the appropriate 'Set' methods of KVNucleus in order to copy the required characteristics for each nucleus. In this example, we show the minimum required in order to keep all kinematic information (in a given reference frame - not all frames can be copied) on each nucleus:

struct my_nucleus
{
KVNucleus nuc;
my_nucleus(const KVReconstructedNucleus& n)
{
nuc.SetZandA(n.GetZ(),n.GetA());
nuc.SetMomentum(n.GetMomentum()); // this is momentum in whatever 'default' frame is defined for n
}
};
void SetZandA(Int_t z, Int_t a)
Set atomic number and mass number.
void SetMomentum(const TVector3 *v)
Definition KVParticle.h:542
const Int_t n

In all cases, your class/struct needs to have a default constructor, default copy constructor, and default move constructor: just add them as shown:

struct my_part_struct
{
// add compiler-generated default constructors
my_part_struct() = default;
my_part_struct(const azimuthal_cor&) = default;
my_part_struct(azimuthal_cor&&) = default;
};

Using the event mixer in data analysis

Once the class/struct for particle info storage is defined, the event mixer can be declared as a member variable of your analysis class:

struct azimuthal_cor { ... };
Generic event mixing algorithm for two-particle correlation studies.

The 2 template parameters are:

  • the particle storage class;
  • the number of different classes/bins that events are sorted into (default=1: no binning).

The constructor optionally takes an argument defining the number of events to use for the mixing (if not given, default value 10 is used).

In your event by event analysis, you need to call the ProcessEvent() method, providing the required iterators and functions to treat each pair of particles, along with the 'bin' number corresponding to the class of the current event (e.g. events could be classed according to total multiplicity into bins of centrality - it is up to you how you sort your events):

event_mixer.ProcessEvent(bin,IterA,IterB,CorrelatedPairFunction,UnCorrelatedPairFunction);
void ProcessEvent(int bin_number, partA_iterator iter_A, partB_iterator iter_B, TreatCorPairFunc TreatCorPair, TreatNCorPairFunc TreatNCorPair)
Event-by-event processing function.

User-provided iterators for each particle type

The 'iterators' IterA and IterB are not in fact strictly iterators: they are any expression which can be used on the right-hand side of a range-for loop, i.e. 'X' in the following pseudo-code:

for(auto& p : X){ ... }
#define X(type, name)

For example, if the particles in your event are stored in a std::vector, X would be a reference to the vector:

std::vector<KVNucleus> my_event;
for(auto& n : my_event){ ... loop over particles in event ... }
event_mixer.ProcessEvent(bin, my_event, ...);

KaliVeda provides a wide range of pseudo-iterators for KVEvent to be used in range-for loops, which are commonly used in KVEventSelector analysis classes:

for(auto& n : EventOKIterator(GetEvent())) { ... loop over 'OK' particles of event, 'n' is a KVNucleus& ... }
for(auto& p : ReconEventIterator(GetEvent(),
{"alpha",[](const KVReconstructedNucleus* x){ return x->IsAMeasured() && x->IsIsotope(2,4); }})
)
{ ... loop over isotopically-identified alpha particles of event, 'p' is a KVReconstructedNucleus& ... }
Class for iterating over "OK" nuclei in events accessed through base pointer/reference.
Wrapper class for iterating over nuclei in KVReconstructedEvent accessed through base pointer or refe...
Double_t x[n]
TArc a

Therefore to use KVEventMixer in a KVEventSelector analysis class, IterA and IterB will probably be one of these pseudo-iterators, as in this (incomplete) example for correlating \(\alpha\)-particles and deuterons:

event_mixer.ProcessEvent(bin,
ReconEventIterator(GetEvent(),
{"alpha",[](const KVReconstructedNucleus* x){ return x->IsAMeasured() && x->IsIsotope(2,4); }}),
ReconEventIterator(GetEvent(),
{"deuton",[](const KVReconstructedNucleus* x){ return x->IsAMeasured() && x->IsIsotope(1,2); }}),
...);

User-provided functions for correlated and uncorrelated pairs of particles

Finally you have to provide two functions in order to treat each pair of correlated and uncorrelated particles. This is conveniently done using lambda functions. Each function takes three arguments, the 'bin' corresponding to the event class (this is the value passed as first argumet to ProcessEvent) and then one argument for each particle in the pair, with a slight signature difference between correlated and uncorrelated pairs:

  • correlated pair signature: void(int, IteratorReferenceType, IteratorReferenceType)
  • uncorrelated pair signature: void(int, IteratorReferenceType, ParticleInfoStruct&)

where IteratorReferenceType refers to the type of reference to the particles in the event returned by the pseudo-iterators given as 2nd and 3rd argument:

and ParticleInfoStruct is the structure you provided to store the informations on particles from different events for the decorrelation.

The correlated pair function CorrelatedPairFunction will be called for every pair of particles (A,B) generated by coupling every A particle in the event with every B particle. If A and B particles are in fact of the same type, it is the user's responsibility to avoid correlating each particle with itself in the given CorrelatedPairFunction (e.g. by comparing the memory address of each particle object).

The uncorrelated pair function UnCorrelatedPairFunction will be called for every pair generated by coupling:

  • every A particle of the current event with every B particle of the previous \(N\) events stored in buffers;
  • every B particle of the current event with every A particle of the previous \(N\) events stored in buffers;

Full example of event mixing correlation study

All will hopefully become clear with a full example. Let us assume that in your experimental data analysis you want to calculate azimuthal correlations for alpha particles for different bins in centrality, using the azimuthal_cor structure we presented above to store relevant informations ( \(\phi\) angle and detector index for each \(\alpha\)-particle).

Declaration and initialisation of the structure and the event mixing object as member variables of the analysis class (we only specify the parts of the code concerning the event mixing):

class MyCorrelationAnalysis : public KVReconEventSelector {
// ... some other stuff here ...
struct azimuthal_cor
{
double phi;
int det_index;
azimuthal_cor() = default;
azimuthal_cor(azimuthal_cor&&) = default;
azimuthal_cor(const azimuthal_cor&) = default;
azimuthal_cor(const KVReconstructedNucleus& nuc)
: phi{nuc.GetPhi()}, det_index{nuc.GetStoppingDetector()->GetIndex()} {}
};
// 5 centrality bins in this example
// ... lots more stuff here ...
};
Base class for user analysis of reconstructed data.

Next, the use of the event mixing object in the event by event analysis:

Bool_t MyCorrelationAnalysis::Analysis()
{
// ... lots of other analysis stuff, such as determining the 'bin' this event belongs to ...
event_mixer.ProcessEvent(bin,
ReconEventIterator(GetEvent(), // iterator for first alpha
{"alpha",[](const KVReconstructedNucleus* n){return n->IsOK() && n->IsAMeasured() && n->IsIsotope(2,4);}}),
ReconEventIterator(GetEvent(), // iterator for second alpha
{"alpha",[](const KVReconstructedNucleus* n){return n->IsOK() && n->IsAMeasured() && n->IsIsotope(2,4);}}),
[](int bin, KVNucleus& alpha, KVNucleus& other_alpha) // function to treat correlated pairs
{
// treat pair from same event
// avoid same alpha!!
if(&alpha == &other_alpha) return; // function will be called with same alpha as both arguments - warning!
auto dphi = pos.GetAzimuthalWidth(alpha.GetPhi(),other_alpha.GetPhi());
if(dphi>180) dphi=360-dphi;
// ... probably fill a histogram or something here ...
},
[](int bin, KVReconstructedNucleus& alpha, azimuthal_cor& other_alpha) // function to treat uncorrelated pairs from mixed events
{
// treat pair from different events
if(other_alpha.det_index != alpha.GetStoppingDetector()->GetIndex()) // here we avoid 'pairs' hitting same detector - impossible experimentally
{
auto dphi = pos.GetAzimuthalWidth(alpha.GetPhi(),other_alpha.phi);
if(dphi>180) dphi=360-dphi;
// ... probably fill a histogram or something here ...
}
}
}
bool Bool_t
Double_t GetPhi() const
Definition KVParticle.h:688
Base class used for handling geometry in a multidetector array.
Definition KVPosition.h:91
Double_t GetAzimuthalWidth(Double_t phmin=-1., Double_t phimax=-1.) const

Things to note in this example:

  1. The correlated pair function uses KVNucleus& arguments although the provided iterators return KVReconstructedNucleus references. As KVNucleus is a base class of KVReconstructedNucleus and the only method used in the function (KVParticle::GetPhi()) is defined for KVNucleus, this is not a problem. KVReconstructedNucleus& arguments could be used equally well, without any other change to the code.
  2. We avoid correlating each alpha particle with itself (which would give a huge peak at \(\Delta\phi=0^{o}\)) by comparing the memory addresses of the two KVNucleus objects passed to the function (if(&alpha == &other_alpha) return;)
  3. In the uncorrelated pair function we use a KVReconstructedNucleus& argument for the particle from the current event, as we need to acces the KVReconstructedNucleus::GetStoppingDetector() method in order to avoid generating uncorrelated pairs which could not exist experimentally (particles hitting the same detector).
Examples
ExampleCorrelationAnalysis.cpp.

Definition at line 264 of file KVEventMixer.h.

#include <KVEventMixer.h>

Classes

struct  bin_data
 
struct  event
 

Public Member Functions

 KVEventMixer (typename std::deque< event >::size_type number_of_events_to_mix=10)
 
template<typename TreatCorPairFunc , typename TreatNCorPairFunc , typename partA_iterator , typename partB_iterator >
void ProcessEvent (int bin_number, partA_iterator iter_A, partB_iterator iter_B, TreatCorPairFunc TreatCorPair, TreatNCorPairFunc TreatNCorPair)
 Event-by-event processing function.
 

Private Attributes

bin_data bins [NumBins]
 
std::deque< event >::size_type decor_events = 10
 

Constructor & Destructor Documentation

◆ KVEventMixer()

template<typename ParticleInfoStruct , int NumBins = 1>
KVEventMixer< ParticleInfoStruct, NumBins >::KVEventMixer ( typename std::deque< event >::size_type  number_of_events_to_mix = 10)
inline
Template Parameters
ParticleInfoStructuser-provided structure which will be used to store any required information on each particle to be used in the event mixing
NumBinsnumber of different event classes to be treated separately in the event mixing [default:1]
Parameters
number_of_events_to_mixnumber of previous events to store in order to do event mixing [default:10]

Definition at line 296 of file KVEventMixer.h.

Member Function Documentation

◆ ProcessEvent()

template<typename ParticleInfoStruct , int NumBins = 1>
template<typename TreatCorPairFunc , typename TreatNCorPairFunc , typename partA_iterator , typename partB_iterator >
void KVEventMixer< ParticleInfoStruct, NumBins >::ProcessEvent ( int  bin_number,
partA_iterator  iter_A,
partB_iterator  iter_B,
TreatCorPairFunc  TreatCorPair,
TreatNCorPairFunc  TreatNCorPair 
)
inline

Event-by-event processing function.

Parameters
bin_numberuser-defined 'event class number'; particles from different classes are not mixed
iter_Aiterator over particle type A
iter_Biterator over particle type B
TreatCorPairfunction called for each pair of (A,B) particles from the same event
TreatNCorPairfunction called for each pair of (A,B) particles built from different events

In your event by event analysis, you need to call the ProcessEvent() method, providing the required iterators and functions to treat each pair of particles, along with the 'bin' number corresponding to the class of the current event (e.g. events could be classed according to total multiplicity into bins of centrality - it is up to you how you sort your events):

event_mixer.ProcessEvent(bin,IterA,IterB,CorrelatedPairFunction,UnCorrelatedPairFunction);

User-provided iterators for each particle type

The 'iterators' IterA and IterB are not in fact strictly iterators: they are any expression which can be used on the right-hand side of a range-for loop, i.e. 'X' in the following pseudo-code:

for(auto& p : X){ ... }

For example, if the particles in your event are stored in a std::vector, X would be a reference to the vector:

std::vector<KVNucleus> my_event;
for(auto& n : my_event){ ... loop over particles in event ... }
event_mixer.ProcessEvent(bin, my_event, ...);

KaliVeda provides a wide range of pseudo-iterators for KVEvent to be used in range-for loops, which are commonly used in KVEventSelector analysis classes:

for(auto& n : EventOKIterator(GetEvent())) { ... loop over 'OK' particles of event, 'n' is a KVNucleus& ... }
for(auto& p : ReconEventIterator(GetEvent(),
{"alpha",[](const KVReconstructedNucleus* x){ return x->IsAMeasured() && x->IsIsotope(2,4); }})
)
{ ... loop over isotopically-identified alpha particles of event, 'p' is a KVReconstructedNucleus& ... }

Therefore to use KVEventMixer in a KVEventSelector analysis class, IterA and IterB will probably be one of these pseudo-iterators, as in this (incomplete) example for correlating \(\alpha\)-particles and deuterons:

event_mixer.ProcessEvent(bin,
ReconEventIterator(GetEvent(),
{"alpha",[](const KVReconstructedNucleus* x){ return x->IsAMeasured() && x->IsIsotope(2,4); }}),
ReconEventIterator(GetEvent(),
{"deuton",[](const KVReconstructedNucleus* x){ return x->IsAMeasured() && x->IsIsotope(1,2); }}),
...);

User-provided functions for correlated and uncorrelated pairs of particles

Finally you have to provide two functions in order to treat each pair of correlated and uncorrelated particles. This is conveniently done using lambda functions. Each function takes three arguments, the 'bin' corresponding to the event class (this is the value passed as first argumet to ProcessEvent) and then one argument for each particle in the pair, with a slight signature difference between correlated and uncorrelated pairs:

  • correlated pair signature: void(int, IteratorReferenceType, IteratorReferenceType)
  • uncorrelated pair signature: void(int, IteratorReferenceType, ParticleInfoStruct&)

where IteratorReferenceType refers to the type of reference to the particles in the event returned by the pseudo-iterators given as 2nd and 3rd argument:

and ParticleInfoStruct is the structure you provided to store the informations on particles from different events for the decorrelation.

The correlated pair function CorrelatedPairFunction will be called for every pair of particles (A,B) generated by coupling every A particle in the event with every B particle. If A and B particles are in fact of the same type, it is the user's responsibility to avoid correlating each particle with itself in the given CorrelatedPairFunction (e.g. by comparing the memory address of each particle object).

The uncorrelated pair function UnCorrelatedPairFunction will be called for every pair generated by coupling:

  • every A particle of the current event with every B particle of the previous \(N\) events stored in buffers;
  • every B particle of the current event with every A particle of the previous \(N\) events stored in buffers;

Definition at line 379 of file KVEventMixer.h.

Member Data Documentation

◆ bins

template<typename ParticleInfoStruct , int NumBins = 1>
bin_data KVEventMixer< ParticleInfoStruct, NumBins >::bins[NumBins]
private

Definition at line 285 of file KVEventMixer.h.

◆ decor_events

template<typename ParticleInfoStruct , int NumBins = 1>
std::deque<event>::size_type KVEventMixer< ParticleInfoStruct, NumBins >::decor_events = 10
private

Definition at line 288 of file KVEventMixer.h.