-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathevent_classes.py
More file actions
205 lines (162 loc) · 6.44 KB
/
event_classes.py
File metadata and controls
205 lines (162 loc) · 6.44 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
"""Classes to describe event & physical particles."""
from __future__ import absolute_import, division
import math
import networkx as nx
from pythiaplotter.utils.logging_config import get_logger
from pythiaplotter.utils.common import generate_repr_str, get_terminal_width
from pythiaplotter.utils.pdgid_converter import pdgid_to_string
from functools import total_ordering
log = get_logger(__name__)
class Event(object):
"""Hold event info"""
def __init__(self, event_num=0, source=None, title=None, **kwargs):
self.event_num = int(event_num)
self.source = source or ""
self.title = title or ""
self.graph = None # to hold NetworkX graph
self.__dict__.update(**kwargs)
def __repr__(self):
ignore = ["graph"]
return generate_repr_str(self, ignore)
def __str__(self):
"""Print event info in format suitable for use on graph or printout"""
ignore = ["graph"]
info = [k + ": " + str(v) + "\n" for k, v in self.__dict__.items() if k not in ignore]
return "Event:\n{0}".format("".join(info))
def print_stats(self):
"""Print some basic statistics about the event"""
log.info("Some statistics:")
log.info("----------------")
log.info(nx.info(self.graph))
log.info("Graph density {}".format(nx.density(self.graph)))
log.info("Histogram of node degree:")
# Deal with bin contents larger than the terminal width by scaling bins
bin_contents = nx.degree_histogram(self.graph)
tw = get_terminal_width()
offset = 5 # for bin labels, etc
if max(bin_contents) > (tw - offset):
scale = (tw - offset) / max(bin_contents)
bin_contents = [int(round(b*scale)) for b in bin_contents]
for i, h in enumerate(bin_contents):
log.info("{:2d} {}".format(i, "#"*h))
@total_ordering
class Particle(object):
def __init__(self, barcode, pdgid=0, status=0,
initial_state=False, final_state=False, **kwargs):
"""Hold information about a particle in an event.
Parameters
----------
barcode : int
Unique integer for this particle to identify it in an event.
pdgid : int, optional
PDGID code for this particle, see PDG
status : int, optional
Status code for the particle. NB conventions differ between generators
initial_state : bool, optional
Flag initial state particle (no parents)
final_state : bool, optional
Flag final state particle (no children)
kwargs : dict
Store any other particle attributes, such as px/py/pz/pt/energy/mass
Attributes
----------
name : str
pt : float
eta : float
phi : float
px : float
py : float
pz : float
energy : float
mass : float
"""
self.barcode = int(barcode)
self.pdgid = int(pdgid)
self.name = pdgid_to_string(self.pdgid)
self.status = int(status)
self.final_state = final_state
self.initial_state = initial_state
# some default fields
for k in ['pt', 'eta', 'phi', 'px', 'py', 'pz', 'energy', 'mass']:
self.__dict__[k] = 0.0
self.__dict__.update(**kwargs)
if all([k in kwargs for k in ['px', 'py', 'pz']]):
pt, eta, phi = convert_px_py_pz(float(self.px), float(self.py), float(self.pz))
self.__dict__['pt'] = pt
self.__dict__['eta'] = eta
self.__dict__['phi'] = phi
def __repr__(self):
return generate_repr_str(self)
def __str__(self):
# Properties to print out - we don't want all of them!
return "Particle {0}, PDGID {1}".format(self.barcode, self.pdgid)
def __eq__(self, other):
return self.barcode == other.barcode
def __lt__(self, other):
return self.barcode < other.barcode
def convert_px_py_pz(px, py, pz):
"""Convert cartesian momentum components :math:`p_x, p_y, p_z` into :math:`p_T, \eta, \phi`
Notes
-----
* pt (:math:`p_T`) is the momentum in the transverse (x-y) plane
* eta (:math:`\eta`) is the pseudorapidity, :math:`\eta = -\ln(\\tanh(\\theta/2))`
where :math:`\\theta` is the angle of the 3-momentum in the x-z plane relative to the z axis
* phi (:math:`\phi`) is the angle of the 3-momentum in the x-y plane relative to the x axis
Relationships between :math:`p_T, \eta, \phi` and :math:`p_x, p_y, p_z`:
.. math::
p_x &= p_T * \cos(\phi)
p_y &= p_T * \sin(\phi)
p_z &= p_T * \sinh (\eta) = p * \cos(\\theta)
Note that if :math:`p_T = 0`, :math:`\eta = \mathrm{sign}(p_z) * \infty`.
Parameters
----------
px, py, pz : float
Cartesian component of momentum along x, y, z axis, respectively
Returns
-------
pt, eta, phi : float
Transverse momentum, pseudorapidity, and azimuthal angle (in radians).
"""
# transverse momentum
pt = math.sqrt(math.fsum([math.pow(px, 2), math.pow(py, 2)]))
# total momentum
p = math.sqrt(math.fsum([math.pow(pt, 2), math.pow(pz, 2)]))
if pt != 0:
eta = math.asinh(pz / pt)
phi = math.atan2(py, px)
else:
eta = math.copysign(float('inf'), pz)
phi = 0
return pt, eta, phi
class NodeParticle(object):
def __init__(self, particle, parent_barcodes):
"""Class to hold info when particle is represented by a node.
Parameters
----------
particle : Particle
The particle of interest
parent_barcodes : list[int]
List of parent barcodes
"""
self.particle = particle
self.parent_barcodes = parent_barcodes
def __repr__(self):
return generate_repr_str(self)
@property
def barcode(self):
return self.particle.barcode
class EdgeParticle(object):
"""Class to hold info when particle is represented by an edge.
This contains the physical Particle object, and associated info that
is edge-specific, such as incoming/outgoing vertex barcodes.
Vertex barcodes are ints.
"""
def __init__(self, particle, vtx_in_barcode, vtx_out_barcode):
self.particle = particle
self.vtx_in_barcode = int(vtx_in_barcode)
self.vtx_out_barcode = int(vtx_out_barcode)
@property
def barcode(self):
return self.particle.barcode
def __repr__(self):
return generate_repr_str(self)