Event Abstraction
The Event class is designed to encapsulate the information of an event entry from a ROOT TTree, facilitating access
to it by abstracting into collections of Particles instances and data members representing event metadata
(such as an index, event number, etc.).
Thanks to the design of the Particle class, the Event class can dynamically build particles
based on specifications from a configuration file as described in the Particle Abstraction.
This file should contain information about the types of particles and how to build them, allowing for flexible and customizable event processing.
The dynamic particle-building feature searches in the config file for the map with key particle_types.
This should list each type of particle to be created with its respective specifications (see Particle Abstraction for a specific example).
Example particle_types structure in run_config.yaml
particle_types:
# ...
particle_name:
class: 'path.to.particle_class' # Optional: custom class, defaults to 'dtpr.particles.Particle'
amount: (int, TBranch<int> or TBranch<vector>) # How many particles to build per event
attributes:
attr_name:
branch: 'branch_name' # Directly from a TTree branch
expr: 'expression' # Python expression using other attributes
src: 'path.to.function' # Callable, receives the particle as input
filter: 'expression' # Optional: filter particles using 'p' (particle) and 'ev' (event)
sorter:
by: 'expression' # Sorting key, can use 'p' and 'ev'
reverse: True/False # Optional: reverse sorting order
The Event class creates n instances of the Particle class, where n is determined by the amount key.
Attributes are defined using the branch (direct mapping from TTree branches), expr (computed values using expressions), or src (callable methods).
The callable methods must accept the particle instance as an argument, and the expression can access the particle
instance attributes for any computation.
The particle_types map can also manage the following keys:
class: Specifies the class path to be used for the particle. If not specified, it defaults to theParticleclass.filter: Allows filtering of particles based on boolean conditions. These conditions can use the particle instance (referred to asp) and the root event entry (referred to asev).sorter: Sorts particles using Python’ssorted(). Thebykey specifies the sorting expression (which can also usepandev), and thereversekey (default:False) can reverse the order.
Internally, all particle collections are stored in a dictionary (self._particles) within the Event object. But, since
the __getattr__ and __setattr__ methods are overridden, you can access collections like event.digis directly.
Example usage
To iterate over a TTree and create event instances with particles specified in the
configuration file, you can use the following code:
from ROOT import TFile
from dtpr.base.config import RUN_CONFIG
# update the configuration file -> if it is not set, it will use the default one 'run_config.yaml'
RUN_CONFIG.change_config_file(config_path=cf_path)
# first, create a TFile object to read the dt ntuple (this is not necessary by using NTuple Class)
with TFile(input_file, "read") as ntuple:
tree = ntuple["dtNtupleProducer/DTTREE;1"]
for iev, ev in enumerate(tree):
# use_config=True to use the configuration file to build the particles
event = Event(index=iev, ev=ev, use_config=True)
# Print the event summary
print(event)
break # break after the first event
Output
>> ------ Event 39956 info ------
+ Index: 0
+ Digis
* Number of digis: 81
+ Segments
* Number of segments: 8
+ Tps
* Number of tps: 21
+ Genmuons
* Number of genmuons: 2
* GenMuon 0 info -->
--> Pt: 258.0812683105469, Eta: -1.9664770364761353, Phi: 0.5708979964256287, Charge: -1, Matched_segments_stations: [], Showered: False
* GenMuon 1 info -->
--> Pt: 190.72511291503906, Eta: -0.2504693865776062, Phi: -2.558511257171631, Charge: 1, Matched_segments_stations: [], Showered: False
+ Emushowers
* Number of emushowers: 0
+ Simhits
* Number of simhits: 50
How this Event is Built
The process of building the Event and populating it with Particle objects can be visualized as follows:
sequenceDiagram
participant User
participant Event
participant ROOTEntry
participant RUN_CONFIG
participant Particle
User->>Event: Create Event(index=0, ev=ROOTEntry, use_config=True)
Event->>RUN_CONFIG: Request particle_types configuration
Note over RUN_CONFIG: (e.g., defines "digis", "segments", "genmuons")
loop For each configured particle type (e.g., "digis")
Event->>ROOTEntry: Get number of instances (e.g., "digi_nDigis")
Note over ROOTEntry: Returns N for "digis"
loop For each instance (e.g., digi 0 to N-1)
Event->>Particle: new Particle(index=i, ev=ROOTEntry, attributes={...})
Note over Particle: Particle reads its attributes from ROOTEntry based on config
Particle-->>Event: Return created Particle object
end
Event->>Event: Store list of built particles (e.g., self.digis = [...])
end
Event-->>User: Return fully populated Event
Flow Explanation:
User Initiates Event: The user creates an
Eventobject, providing it with a raw data entry from the NTuple anduse_config=True.Event Consults Configuration: The
Eventlooks to theRUN_CONFIGto determine which particle types to build and how to build them.Dynamic Particle Creation: For each particle type defined in the configuration: - The
Eventdetermines how many particles of this type exist in the current raw data entry. - For each particle, theEventcreates a newParticle(or custom class) instance, which reads its attributes from the raw data or computes them as specified. - All created particles are collected into a list and stored as an attribute of theEvent(e.g.,event.digis).Event Ready: After all particle types are built, the
Eventobject is ready for analysis.
On the other hand, the Event class is highly flexible and not restricted to TTree data. As demonstrated above, you can
define any type of attribute mapping in the configuration file or even create an empty event instance and then add attributes manually as needed.
Example: manual event creation
event = Event(index=1) # initialize an empty event with index 1
print(event)
# It is possible to manually add particles or other attributes
from dtpr.base import Particle
showers = [
Particle(index=i, wh=1, sc=1, st=1, name="Shower") for i in range(5)
] # create 5 showers
event.showers = showers # add them to the event
print(event)
print(event.showers[-1])
Output
>> ------ Event 1 info ------
+ Index: 1
>> ------ Event 1 info ------
+ Index: 1
+ Showers
* Number of showers: 5
>> Shower 4 info -->
+ Wh: 1, Sc: 1, St: 1