3 import ROOT, math, random
4 from ParticleGun.histsampling
import TH1
12 "Base class for all samplers"
15 return RuntimeError(
"Can't sample from an abstract sampler object.")
18 """This is the call method that will actually be used (so that normal
19 functions can also be passed in as samplers)."""
26 "A special-case sampler which just returns one value rather than sampling."
35 return "ConstSampler[%s]" %
str(self.
val)
41 "Base class for samplers from continuous distributions."
46 "Uniformly sample in the range [low,high)."
54 return random.uniform(self.
low, self.
high)
58 "Uniformly sample in the modulus range (-high,low]+[low,high)."
61 assert(low == abs(low)
and high == abs(high))
67 val = random.uniform(self.
low, self.
high)
68 if random.random() > 0.5:
74 "Uniformly sample from a set of disjoint intervals."
78 The ranges variable can either be a list of increasing numbers or a
79 list of pairs of numbers.
81 The former case will be treated as
82 defining alternating on/off ranges for sampling, starting with an active
83 one (i.e. it's a list of bin edges). The latter way specifically lists
84 the 'on' regions only, with their start and end values in the pairs.
86 The behaviour is undefined if the numbers are not ordered or overlap --
87 i.e. it might work but hasn't been designed that way and might change in
88 future. Don't rely on this behaviour!
91 raise Exception(
"You must supply at least one non-null sampling range")
92 if hasattr(ranges[0],
"__len__"):
93 assert all(len(x) == 2
for x
in ranges)
96 assert len(ranges) > 1
97 lows = [x
for x
in ranges[:-1]]
98 highs = [x
for x
in ranges[1:]]
100 for i, pair
in enumerate(zip(lows, highs)):
102 myranges.append(pair)
103 assert len(myranges) == len(ranges) // 2
118 runningwidth +=
float(r[1] - r[0])
124 ranges = property(_getRanges, _setRanges)
127 assert(x >= 0
and x <= 1)
136 raise ValueError(
"No matching division found in unit interval! How?")
141 rand = random.random()
147 "Randomly sample from an exponential distribution (i.e. uniformly on a log scale)."
154 rand = random.random()
155 logval = rand * math.log(self.
high) + (1 - rand) * math.log(self.
low)
156 val = math.exp(logval)
161 "Randomly sample from a 1D Gaussian distribution."
168 return random.gauss(self.
mean, self.
sigma)
172 "Randomly sample from a 1/x distribution."
179 invx = random.uniform(1/self.
high, 1/self.
low)
187 "Randomly sample from a 1D ROOT histogram."
192 raise Exception(
"Histogram %s is EMPTY! Cannot sample" % self.
hist.GetName())
195 return self.
hist.GetRandom()
204 "Base class for samplers from lists of discrete values"
208 class RandomSeqSampler(DiscreteSampler):
209 "Uniformly random sample from a list of values."
220 RndmSeq = RandomSeqSampler
224 "Sequentially sample from a list of values, returning to the beginning once exhausted."
237 Sequence = CyclicSeqSampler
247 Automatically cast the provided object to a sampler type. This is used
248 extensively inside the particle and position samplers, so that the user
249 can pass in a primitive type like a number or list and it will be
250 treated as if the more verbose sampler constructors had been called.
253 - if x can be called, i.e. x() is valid, we just return x;
254 - a Python list (square brackets) will be converted to a continuous
255 UniformSampler or DisjointUniformSampler;
256 - a Python tuple (round brackets/parentheses) will be treated
257 as a discrete CyclicSeqSampler;
258 - a Python set (curly brackets/braces) will be treated
259 as a discrete RandomSeqSampler;
260 - otherwise a ConstSampler will be created from x, so that x is
261 returned when the sampler is called.
265 elif type(x)
is list:
267 if len(x) == 2
and type(x[0])
in (int,float)
and type(x[1])
in (int,float):
269 elif len(x) > 2
or (len(x) > 0
and type(x[0])
not in (int,float)):
272 raise Exception(
"Supplied list could not be converted to a continuous sampler")
273 elif type(x)
is tuple:
288 Sampler of position 3-vectors, for modelling a beamspot.
335 return ROOT.TLorentzVector(x, y, z, t)
345 Base class for four-momentum sampling.
347 There are many ways to unambiguously specify four-momenta. Not all are sensible/useful,
348 though. The following are implemented here:
357 Possibly the following (not yet implemented) could be useful: let us know if you
366 "A momentum sampler which just returns a null vector with the given mass."
380 v4 = ROOT.TLorentzVector(0, 0, 0, self.
mass)
385 "Create a 4-momentum vector from mass, px, py, pz distributions/samplers."
430 e = math.sqrt(px**2 + py**2 + pz**2 + m**2)
431 v4 = ROOT.TLorentzVector(px, py, pz, e)
436 "Create a 4-momentum vector from E, eta, m and phi distributions/samplers."
440 def __init__(self, energy, eta, mass=0.0, phi=[0, TWOPI]):
456 "Pseudorapidity sampler"
472 "Azimuthal angle sampler"
480 eta = - ln(tan(theta/2)) / 2
481 => theta = 2 atan( exp(-eta) )
484 theta = 2 * math.atan(math.exp(-eta))
487 p = math.sqrt( e**2 - m**2 )
488 pz = p * math.cos(theta)
489 pt = p * math.sin(theta)
491 px = pt * math.cos(phi)
492 py = pt * math.sin(phi)
493 v4 = ROOT.TLorentzVector(px, py, pz, e)
498 "Create a 4-momentum vector from E, y, m and phi distributions."
502 def __init__(self, energy, rap, mass=0.0, phi=[0, TWOPI]):
534 "Azimuthal angle sampler"
542 y = 0.5 * ln((E+pz)/(E-pz))
543 -> (E^2 - pz^2) exp(2y) = (E+pz)^2
544 & (E^2 - pz^2) exp(-2y) = (E-pz)^2
545 -> E = sqrt(pt^2 + m^2) cosh(y)
546 -> pz = sqrt(pt^2 + m^2) sinh(y)
547 -> sqrt(pt^2 + m^2) = E / cosh(y)
551 sqrt_pt2_m2 = e / math.cosh(y)
552 pz = sqrt_pt2_m2 * math.sinh(y)
554 pt = math.sqrt( sqrt_pt2_m2**2 - m**2 )
556 px = pt * math.cos(phi)
557 py = pt * math.sin(phi)
558 v4 = ROOT.TLorentzVector(px, py, pz, e)
563 "Create a 4-momentum vector from E, theta, m and phi distributions/samplers."
567 def __init__(self, energy, theta, mass=0.0, phi=[0, TWOPI]):
583 "Polar angle sampler"
599 "Azimuthal angle sampler"
613 p = math.sqrt( e**2 - m**2 )
615 pz = p * math.cos(theta)
616 pt = p * math.sin(theta)
618 px = pt * math.cos(phi)
619 py = pt * math.sin(phi)
620 v4 = ROOT.TLorentzVector(px, py, pz, e)
625 "Create a 4-momentum vector from pt, eta, m and phi distributions/samplers."
627 def __init__(self, pt, eta, mass=0.0, phi=[0, TWOPI]):
635 "Transverse momentum sampler"
643 "Pseudorapidity sampler"
659 "Azimuthal angle sampler"
667 eta = - ln(tan(theta/2)) / 2
668 => theta = 2 atan( exp(-eta) )
671 theta = 2 * math.atan(math.exp(-eta))
673 p = pt / math.sin(theta)
675 px = pt * math.cos(phi)
676 py = pt * math.sin(phi)
677 pz = p * math.cos(theta)
679 e = math.sqrt( p**2 + m**2 )
680 v4 = ROOT.TLorentzVector(px, py, pz, e)
685 "Create a 4-momentum vector from pt, y, m and phi distributions/samplers."
687 def __init__(self, pt, rap, mass=0.0, phi=[0, TWOPI]):
695 "Transverse momentum sampler"
719 "Azimuthal angle sampler"
727 y = 0.5 * ln((E+pz)/(E-pz))
728 -> (E^2 - pz^2) exp(2y) = (E+pz)^2
729 & (E^2 - pz^2) exp(-2y) = (E-pz)^2
730 -> E = sqrt(pt^2 + m^2) cosh(y)
731 -> pz = sqrt(pt^2 + m^2) sinh(y)
732 -> sqrt(pt^2 + m^2) = E / cosh(y)
738 sqrt_pt2_m2 = math.sqrt( pt**2 + m**2 )
740 e = sqrt_pt2_m2 * math.cosh(y)
741 pz = sqrt_pt2_m2 * math.sinh(y)
743 px = pt * math.cos(phi)
744 py = pt * math.sin(phi)
745 v4 = ROOT.TLorentzVector(px, py, pz, e)
750 "Create a 4-momentum vector from pt, theta, m and phi distributions/samplers."
752 def __init__(self, pt, theta, mass=0.0, phi=[0, TWOPI]):
760 "Transverse momentum sampler"
768 "Polar angle sampler"
784 "Azimuthal angle sampler"
792 p = pt / math.sin(theta)
799 p = pt / math.sin(theta)
801 px = pt * math.cos(phi)
802 py = pt * math.sin(phi)
803 pz = p * math.cos(theta)
805 e = math.sqrt( p**2 + m**2 )
806 v4 = ROOT.TLorentzVector(px, py, pz, e)
839 A particle object for use as a return value from the particle samplers.
843 Constructor/initializer: PID is the (int) PDG particle ID code
844 of this particle, mom is its momentum 4-vector, and pos is
845 the vertex 4-position (both as ROOT.TLorentzVector, in MeV).
848 self.
mom = mom
or ROOT.TLorentzVector(0,0,0,0)
849 self.
pos = pos
or ROOT.TLorentzVector(0,0,0,0)
855 A simple N-independent-particle sampler.
859 mom=NullMomSampler(),
871 "Particle ID code sampler"
879 "Particle number sampler"
886 "Return a vector of sampled particles"
887 numparticles = self.
n()
889 for i
in range(numparticles):