def acceptance(self, track): # return False pt = track.pt eta = abs(track.p3.Eta()) if eta < 1.35 and pt>0.5: return random.uniform(0,1)<0.95 elif eta < 2.5 and pt>0.5: return random.uniform(0,1)<0.9 else: return False
def process(self, event): '''Process the event. This method creates: - event.var_random: the random variable, between 0 and 1. ''' event.var_random = random.uniform(0,1)
def particles(nptcs, pdgid, thetamin, thetamax, emin, emax, vertex=None): ngenerated = 0 mass, charge = particle_data[pdgid] while ngenerated < nptcs: theta = random.uniform(thetamin, thetamax) phi = random.uniform(-math.pi, math.pi) energy = random.uniform(emin, emax) if vertex is None: vertex = Point(0, 0, 0) momentum = math.sqrt(energy ** 2 - mass ** 2) costheta = math.cos(theta) sintheta = math.sin(theta) cosphi = math.cos(phi) sinphi = math.sin(phi) p4 = LorentzVector(momentum * sintheta * cosphi, momentum * sintheta * sinphi, momentum * costheta, energy) ngenerated += 1 yield Particle(p4, vertex, charge, pdgid)
def simulate_hadron(self, ptc): """Simulate a hadron, neutral or charged. ptc should behave as pfobjects.Particle. """ pdebugger.info("Simulating Hadron") # implement beam pipe scattering ecal = self.detector.elements["ecal"] hcal = self.detector.elements["hcal"] beampipe = self.detector.elements["beampipe"] frac_ecal = 0.0 propagator(ptc.q()).propagate_one(ptc, beampipe.volume.inner, self.detector.elements["field"].magnitude) propagator(ptc.q()).propagate_one(ptc, beampipe.volume.outer, self.detector.elements["field"].magnitude) mscat.multiple_scattering(ptc, beampipe, self.detector.elements["field"].magnitude) # re-propagate after multiple scattering in the beam pipe # indeed, multiple scattering is applied within the beam pipe, # so the extrapolation points to the beam pipe entrance and exit # change after multiple scattering. propagator(ptc.q()).propagate_one(ptc, beampipe.volume.inner, self.detector.elements["field"].magnitude) propagator(ptc.q()).propagate_one(ptc, beampipe.volume.outer, self.detector.elements["field"].magnitude) propagator(ptc.q()).propagate_one(ptc, ecal.volume.inner, self.detector.elements["field"].magnitude) # these lines moved earlier in order to match cpp logic if ptc.q() != 0: pdebugger.info(" ".join(("Made", ptc.track.__str__()))) smeared_track = self.smear_track(ptc.track, self.detector.elements["tracker"]) if smeared_track: ptc.track_smeared = smeared_track if "ecal_in" in ptc.path.points: # doesn't have to be the case (long-lived particles) path_length = ecal.material.path_length(ptc) if path_length < sys.float_info.max: # ecal path length can be infinite in case the ecal # has lambda_I = 0 (fully transparent to hadrons) time_ecal_inner = ptc.path.time_at_z(ptc.points["ecal_in"].Z()) deltat = ptc.path.deltat(path_length) time_decay = time_ecal_inner + deltat point_decay = ptc.path.point_at_time(time_decay) ptc.points["ecal_decay"] = point_decay if ecal.volume.contains(point_decay): frac_ecal = random.uniform(0.0, 0.7) cluster = self.make_cluster(ptc, "ecal", frac_ecal) # For now, using the hcal resolution and acceptance # for hadronic cluster # in the ECAL. That's not a bug! smeared = self.smear_cluster(cluster, hcal, acceptance=ecal) if smeared: ptc.clusters_smeared[smeared.layer] = smeared cluster = self.make_cluster(ptc, "hcal", 1 - frac_ecal) smeared = self.smear_cluster(cluster, hcal) if smeared: ptc.clusters_smeared[smeared.layer] = smeared
def is_tagged(self, is_signal): '''Return tagging value. @return: result of the tagging (boolean) @param is_b: specifies whether the object of interest is signal or background ''' eff = self.eff if is_signal else self.fake_rate return random.uniform(0, 1) < eff
def electron_acceptance(self, track): '''returns True if electron is seen. No information, cooking something up. ''' if track.p3().Mag() > 5 and \ abs(track.theta()) < Tracker.theta_max: return random.uniform(0, 1) < 0.95 else: return False
def acceptance(self, cluster): energy = cluster.energy eta = abs(cluster.position.Eta()) if eta < self.eta_crack : if energy>1.: return random.uniform(0,1)<(1/(1+math.exp((energy-1.93816)/(-1.75330)))) else: return False elif eta < 3. : if energy>1.1: if energy<10.: return random.uniform(0,1)<(1.05634-1.66943e-01*energy+1.05997e-02*(energy**2)) else: return random.uniform(0,1)<(8.09522e-01/(1+math.exp((energy-9.90855)/-5.30366))) else: return False elif eta < 5.: return energy>7. else: return False
def particle(pdgid, thetamin, thetamax, ptmin, ptmax, flat_pt=False, papas = False, phimin=-math.pi, phimax=math.pi): '''Create and return a particle in a given phase space @param pdgid: the pdg ID code @param thetamin: min theta @param thetamax: max theta @param ptmin: min pt @param ptmax: max pt @param flat_pt: False by default, indicating that the pt of the particle should be chosen from a uniform distribution between ptmin and ptmax. if set to true, the energy of the particle is drawn from a uniform distribution between ptmin and ptmax (then considered as Emin and Emax). ''' mass, charge = particle_data[pdgid] theta = random.uniform(thetamin, thetamax) phi = random.uniform(phimin, phimax) energy = random.uniform(ptmin, ptmax) costheta = math.cos(math.pi/2. - theta) sintheta = math.sin(math.pi/2. - theta) tantheta = sintheta / costheta cosphi = math.cos(phi) sinphi = math.sin(phi) vertex = TVector3(0,0,0) if flat_pt: pt = energy momentum = pt / sintheta energy = math.sqrt(momentum**2 + mass**2) else: energy = max([energy, mass]) momentum = math.sqrt(energy**2 - mass**2) tlv = TLorentzVector(momentum*sintheta*cosphi, momentum*sintheta*sinphi, momentum*costheta, energy) if papas: return PapasParticle(tlv, vertex, charge, pdgid, subtype ='g') #pfobjects has a uniqueid else: return TlvParticle(pdgid, charge, tlv) #pfobjects has a uniqueid
def monojet(pdgids, theta, phi, pstar, jetenergy, vertex=None): particles = [] if vertex is None: vertex = TVector3(0.0, 0.0, 0.0) jetp4star = TLorentzVector() for pdgid in pdgids[:-1]: mass, charge = particle_data[pdgid] phistar = random.uniform(-math.pi, math.pi) thetastar = random.uniform(-math.pi, math.pi) sint = math.sin(thetastar) cost = math.cos(thetastar) sinp = math.sin(phistar) cosp = math.cos(phistar) pz = pstar * cost px = pstar * sint * cosp py = pstar * sint * sinp p4 = TLorentzVector() p4.SetXYZM(px, py, pz, mass) jetp4star += p4 particles.append(Particle(p4, vertex, charge, pdgid)) pdgid = pdgids[-1] mass, charge = particle_data[pdgid] p4 = TLorentzVector() p4.SetVectM(-jetp4star.Vect(), mass) particles.append(Particle(p4, vertex, charge, pdgid)) jetp4star += p4 # boosting to lab gamma = jetenergy / jetp4star.M() beta = math.sqrt(1 - 1 / gamma ** 2) boostvec = TVector3(math.sin(theta) * math.cos(phi), math.sin(theta) * math.sin(phi), math.cos(theta)) boostvec *= beta boosted_particles = [] jetp4 = LorentzVector() for ptc in particles: bp4 = LorentzVector(ptc.p4()) bp4.Boost(boostvec) jetp4 += bp4 boosted_particles.append(Particle(bp4, ptc.vertex, ptc.q(), ptc.pdgid())) # print jetp4.M(), jetp4.E() return boosted_particles
def particle(pdgid, thetamin, thetamax, ptmin, ptmax, flat_pt=False): mass, charge = particle_data[pdgid] theta = random.uniform(thetamin, thetamax) phi = random.uniform(-math.pi, math.pi) energy = random.uniform(ptmin, ptmax) costheta = math.cos(math.pi/2. - theta) sintheta = math.sin(math.pi/2. - theta) tantheta = sintheta / costheta cosphi = math.cos(phi) sinphi = math.sin(phi) if flat_pt: pt = energy momentum = pt / sintheta energy = math.sqrt(momentum**2 + mass**2) else: energy = max([energy, mass]) momentum = math.sqrt(energy**2 - mass**2) tlv = TLorentzVector(momentum*sintheta*cosphi, momentum*sintheta*sinphi, momentum*costheta, energy) return Particle(pdgid, charge, tlv)
def muon_acceptance(self, track): """Delphes parametrization https://github.com/delphes/delphes/blob/master/cards/delphes_card_CMS.tcl 96d6bcf """ rnd = random.uniform(0, 1) eta = abs(track.p3().Eta()) pt = track.p3().Perp() if pt < 10.: return False elif eta < 2.4: return rnd < 0.95 else: return False
def acceptance(self, cluster): energy = cluster.energy eta = abs(cluster.position.Eta()) if eta < self.eta_crack: if energy > 1.: return random.uniform(0, 1) < (1 / (1 + math.exp( (energy - 1.93816) / (-1.75330)))) else: return False elif eta < 3.: if energy > 1.1: if energy < 10.: return random.uniform( 0, 1) < (1.05634 - 1.66943e-01 * energy + 1.05997e-02 * (energy**2)) else: return random.uniform(0, 1) < (8.09522e-01 / (1 + math.exp( (energy - 9.90855) / -5.30366))) else: return False elif eta < 5.: return energy > 7. else: return False
def simulate_hadron(self, ptc): '''Simulate a hadron, neutral or charged. ptc should behave as pfobjects.Particle. ''' pdebugger.info("Simulating Hadron") #implement beam pipe scattering ecal = self.detector.elements['ecal'] hcal = self.detector.elements['hcal'] frac_ecal = 0. if ptc.q() != 0 : #track is now made outside of the particle and then the particle is told where the track is track = self.make_and_store_track(ptc) tracker = self.detector.elements['tracker'] smeared_track = self.make_and_store_smeared_track( ptc, track, tracker.resolution, tracker.acceptance ) propagator(ptc.q()).propagate_one(ptc, ecal.volume.inner, self.detector.elements['field'].magnitude) if 'ecal_in' in ptc.path.points: # doesn't have to be the case (long-lived particles) path_length = ecal.material.path_length(ptc) if path_length < sys.float_info.max: # ecal path length can be infinite in case the ecal # has lambda_I = 0 (fully transparent to hadrons) time_ecal_inner = ptc.path.time_at_z(ptc.points['ecal_in'].Z()) deltat = ptc.path.deltat(path_length) time_decay = time_ecal_inner + deltat point_decay = ptc.path.point_at_time(time_decay) ptc.points['ecal_decay'] = point_decay if ecal.volume.contains(point_decay): frac_ecal = random.uniform(0., 0.7) cluster = self.make_and_store_cluster(ptc, 'ecal', frac_ecal) # For now, using the hcal resolution and acceptance # for hadronic cluster # in the ECAL. That's not a bug! smeared = self.make_and_store_smeared_cluster(cluster, hcal, acceptance=ecal) if smeared: ptc.clusters_smeared[smeared.layer] = smeared cluster = self.make_and_store_cluster(ptc, 'hcal', 1-frac_ecal) smeared = self.make_and_store_smeared_cluster(cluster, hcal) if smeared: ptc.clusters_smeared[smeared.layer] = smeared
def electron_acceptance(self, ptc): """Delphes parametrization https://github.com/delphes/delphes/blob/master/cards/delphes_card_CMS.tcl 96d6bcf """ rnd = random.uniform(0, 1) if ptc.pt() < 10.: return False else: eta = abs(ptc.eta()) if eta < 1.5: return rnd < 0.95 elif eta < 2.5: return rnd < 0.85 else: return False
def electron_acceptance(self, track): """Delphes parametrization https://github.com/delphes/delphes/blob/master/cards/delphes_card_CMS.tcl 96d6bcf """ rnd = random.uniform(0, 1) pt = track.p3().Perp() if pt < 10.: return False else: eta = abs(track.p3().Eta()) if eta < 1.5: return rnd < 0.95 elif eta < 2.5: return rnd < 0.85 else: return False
def muon_resolution(self, ptc): """Delphes parametrization # resolution formula for muons set ResolutionFormula { (abs(eta) <= 0.5) * (pt > 0.1) * sqrt(0.01^2 + pt^2*1.0e-4^2) + (abs(eta) > 0.5 && abs(eta) <= 1.5) * (pt > 0.1) * sqrt(0.015^2 + pt^2*1.5e-4^2) + (abs(eta) > 1.5 && abs(eta) <= 2.5) * (pt > 0.1) * sqrt(0.025^2 + pt^2*3.5e-4^2)} """ rnd = random.uniform(0, 1) eta = abs(ptc.eta()) cstt = None vart = None if eta < 0.5: cstt, vart = 0.01, 1e-4 elif eta < 1.5: cstt, vart = 0.015, 1.5e-4 else: cstt, vart = 0.025, 3.5e-4 res = math.sqrt(cstt**2 + vart**2) return res
def muon_resolution(self, track): """Delphes parametrization # resolution formula for muons set ResolutionFormula { (abs(eta) <= 0.5) * (pt > 0.1) * sqrt(0.01^2 + pt^2*1.0e-4^2) + (abs(eta) > 0.5 && abs(eta) <= 1.5) * (pt > 0.1) * sqrt(0.015^2 + pt^2*1.5e-4^2) + (abs(eta) > 1.5 && abs(eta) <= 2.5) * (pt > 0.1) * sqrt(0.025^2 + pt^2*3.5e-4^2)} """ rnd = random.uniform(0, 1) eta = abs(track.p3().Eta()) pt = track.p3().Perp() cstt = None vart = None if eta < 0.5: cstt, vart = 0.01, 1e-4 elif eta < 1.5: cstt, vart = 0.015, 1.5e-4 else: cstt, vart = 0.025, 3.5e-4 res = math.sqrt(cstt**2 + (pt * vart)**2) return res
def acceptance(self, track): '''Returns True if the track is seen. Currently taken from https://indico.cern.ch/event/650053/contributions/2672772/attachments/1501093/2338117/FCCee_MDI_Jul30.pdf Original values for CLIC: Acceptance from the CLIC CDF p107, Fig. 5.12 without background. The tracker is taken to be efficient up to theta = 80 degrees. ''' rnd = random.uniform(0, 1) pt = track.p3().Pt() theta = abs(track.theta()) if theta < self.theta_max: if pt < 0.1: return False elif pt < 0.3: return rnd < 0.9 elif pt < 1: return rnd < 0.95 else: return rnd < 0.99 return False
def acceptance(self, track): '''Returns True if the track is seen. Currently taken from https://indico.cern.ch/event/650053/contributions/2672772/attachments/1501093/2338117/FCCee_MDI_Jul30.pdf Original values for CLIC: Acceptance from the CLIC CDF p107, Fig. 5.12 without background. The tracker is taken to be efficient up to theta = 80 degrees. ''' rnd = random.uniform(0,1) pt = track.p3().Pt() theta = abs(track.theta()) if theta < self.__class__.theta_max: if pt < 0.1: return False elif pt < 0.3: return rnd < 0.9 elif pt < 1: return rnd < 0.95 else: return rnd < 0.99 return False
def simulate_hadron(self, ptc): '''Simulate a hadron, neutral or charged. ptc should behave as pfobjects.Particle. ''' pdebugger.info("Simulating Hadron") #implement beam pipe scattering ecal = self.detector.elements['ecal'] hcal = self.detector.elements['hcal'] beampipe = self.detector.elements['beampipe'] frac_ecal = 0. propagator(ptc.q()).propagate_one( ptc, beampipe.volume.inner, self.detector.elements['field'].magnitude) propagator(ptc.q()).propagate_one( ptc, beampipe.volume.outer, self.detector.elements['field'].magnitude) mscat.multiple_scattering(ptc, beampipe, self.detector.elements['field'].magnitude) #re-propagate after multiple scattering in the beam pipe #indeed, multiple scattering is applied within the beam pipe, #so the extrapolation points to the beam pipe entrance and exit #change after multiple scattering. propagator(ptc.q()).propagate_one( ptc, beampipe.volume.inner, self.detector.elements['field'].magnitude) propagator(ptc.q()).propagate_one( ptc, beampipe.volume.outer, self.detector.elements['field'].magnitude) propagator(ptc.q()).propagate_one( ptc, ecal.volume.inner, self.detector.elements['field'].magnitude) # these lines moved earlier in order to match cpp logic if ptc.q() != 0: pdebugger.info(" ".join(("Made", ptc.track.__str__()))) smeared_track = self.smear_track(ptc.track, self.detector.elements['tracker']) if smeared_track: ptc.track_smeared = smeared_track if 'ecal_in' in ptc.path.points: # doesn't have to be the case (long-lived particles) path_length = ecal.material.path_length(ptc) if path_length < sys.float_info.max: # ecal path length can be infinite in case the ecal # has lambda_I = 0 (fully transparent to hadrons) time_ecal_inner = ptc.path.time_at_z(ptc.points['ecal_in'].Z()) deltat = ptc.path.deltat(path_length) time_decay = time_ecal_inner + deltat point_decay = ptc.path.point_at_time(time_decay) ptc.points['ecal_decay'] = point_decay if ecal.volume.contains(point_decay): frac_ecal = random.uniform(0., 0.7) cluster = self.make_cluster(ptc, 'ecal', frac_ecal) # For now, using the hcal resolution and acceptance # for hadronic cluster # in the ECAL. That's not a bug! smeared = self.smear_cluster(cluster, hcal, acceptance=ecal) if smeared: ptc.clusters_smeared[smeared.layer] = smeared cluster = self.make_cluster(ptc, 'hcal', 1 - frac_ecal) smeared = self.smear_cluster(cluster, hcal) if smeared: ptc.clusters_smeared[smeared.layer] = smeared
def simulate_hadron(self, ptc): '''Simulate a hadron, neutral or charged. ptc should behave as pfobjects.Particle. ''' pdebugger.info("Simulating Hadron") #implement beam pipe scattering ecal = self.detector.elements['ecal'] hcal = self.detector.elements['hcal'] beampipe = self.detector.elements['beampipe'] frac_ecal = 0. if ptc.q() != 0: #track is now made outside of the particle and then the particle is told where the track is track = self.make_and_store_track(ptc) resolution = self.detector.elements['tracker'].pt_resolution(track) smeared_track = self.make_smeared_track(track, resolution) if self.detector.elements['tracker'].acceptance(smeared_track): self.smeared_tracks[smeared_track.uniqueid] = smeared_track self.update_history(track.uniqueid, smeared_track.uniqueid) ptc.track_smeared = smeared_track else: pdebugger.info(str('Rejected {}'.format(smeared_track))) propagator(ptc.q()).propagate_one( ptc, beampipe.volume.inner, self.detector.elements['field'].magnitude) propagator(ptc.q()).propagate_one( ptc, beampipe.volume.outer, self.detector.elements['field'].magnitude) #pdebug next line must be editted out to match cpp #mscat.multiple_scattering(ptc, beampipe, self.detector.elements['field'].magnitude) #re-propagate after multiple scattering in the beam pipe #indeed, multiple scattering is applied within the beam pipe, #so the extrapolation points to the beam pipe entrance and exit #change after multiple scattering. propagator(ptc.q()).propagate_one( ptc, beampipe.volume.inner, self.detector.elements['field'].magnitude) propagator(ptc.q()).propagate_one( ptc, beampipe.volume.outer, self.detector.elements['field'].magnitude) propagator(ptc.q()).propagate_one( ptc, ecal.volume.inner, self.detector.elements['field'].magnitude) if 'ecal_in' in ptc.path.points: # doesn't have to be the case (long-lived particles) path_length = ecal.material.path_length(ptc) if path_length < sys.float_info.max: # ecal path length can be infinite in case the ecal # has lambda_I = 0 (fully transparent to hadrons) time_ecal_inner = ptc.path.time_at_z(ptc.points['ecal_in'].Z()) deltat = ptc.path.deltat(path_length) time_decay = time_ecal_inner + deltat point_decay = ptc.path.point_at_time(time_decay) ptc.points['ecal_decay'] = point_decay if ecal.volume.contains(point_decay): frac_ecal = random.uniform(0., 0.7) cluster = self.make_and_store_cluster( ptc, 'ecal', frac_ecal) # For now, using the hcal resolution and acceptance # for hadronic cluster # in the ECAL. That's not a bug! smeared = self.make_and_store_smeared_cluster( cluster, hcal, acceptance=ecal) if smeared: ptc.clusters_smeared[smeared.layer] = smeared cluster = self.make_and_store_cluster(ptc, 'hcal', 1 - frac_ecal) smeared = self.make_and_store_smeared_cluster(cluster, hcal) if smeared: ptc.clusters_smeared[smeared.layer] = smeared
def process(self, event): event.var_random = random.uniform(0,1)
def multiple_scattering( particle, detector_element, field ): '''This function computes the scattering of a particle while propagating through the detector. As described in the pdg booklet, Passage of particles through matter, multiple scattering through small angles. the direction of a charged particle is modified. This function takes a particle (that has been propagated until the detector element where it will be scattered) and the detector element responsible for the scattering. The magnetic field has to be specified in order to create the new trajectory. Then this function computes the new direction, randomly choosen according to Moliere's theory of multiple scattering (see pdg booklet) and replaces the initial path of the particle by this new scattered path. The particle can now be propagated in the next part of the detector. ''' if not particle.q(): return # reject particles that could not be extrapolated to detector element # (particle created too late, out of the detector element) surface_in = '{}_in'.format(detector_element.name) surface_out = '{}_out'.format(detector_element.name) if not surface_in in particle.path.points or \ not surface_out in particle.path.points: return #TODOCOLIN : check usage of private attributes in_point = particle.path.points[surface_in] out_point = particle.path.points[surface_out] phi_in = particle.path.phi( in_point.X(), in_point.Y()) phi_out = particle.path.phi( out_point.X(), out_point.Y()) t_scat = particle.path.time_at_phi((phi_in+phi_out)*0.5) # compute p4_t = p4 at t_scat : p4_0 = particle.path.p4.Clone() p4tx = p4_0.X()*math.cos(particle.path.omega*t_scat)\ + p4_0.Y()*math.sin(particle.path.omega*t_scat) p4ty =-p4_0.X()*math.sin(particle.path.omega*t_scat)\ + p4_0.Y()*math.cos(particle.path.omega*t_scat) p4tz = p4_0.Z() p4tt = p4_0.T() p4_t = TLorentzVector(p4tx, p4ty, p4tz, p4tt) # now, p4t will be modified with respect to the multiple scattering # first one has to determine theta_0 the width of the gaussian : P = p4_t.Vect().Dot(p4_t.Vect().Unit()) deltat = particle.path.time_at_phi(phi_out)-particle.path.time_at_phi(phi_in) x = abs(particle.path.path_length(deltat)) X_0 = detector_element.material.x0 theta_0 = 1.0*13.6e-3/(1.0*particle.path.speed/constants.c*P)*abs(particle.path.charge) theta_0 *= (1.0*x/X_0)**(1.0/2)*(1+0.038*math.log(1.0*x/X_0)) # now, make p4_t change due to scattering : theta_space = random.gauss(0, theta_0*2.0**(1.0/2)) psi = constants.pi*random.uniform(0,1) #double checked p3i = p4_t.Vect().Clone() e_z = TVector3(0,0,1) #first rotation : theta, in the xy plane a = p3i.Cross(e_z) #this may change the sign, but randomly, as the sign of theta already is p4_t.Rotate(theta_space,a) #second rotation : psi (isotropic around initial direction) p4_t.Rotate(psi,p3i.Unit()) # creating new helix, ref at scattering point : helix_new_t = Helix(field, particle.path.charge, p4_t, particle.path.point_at_time(t_scat)) # now, back to t=0 p4sx = p4_t.X()*math.cos(-particle.path.omega*t_scat)\ + p4_t.Y()*math.sin(-particle.path.omega*t_scat) p4sy =-p4_t.X()*math.sin(-particle.path.omega*t_scat)\ + p4_t.Y()*math.cos(-particle.path.omega*t_scat) p4sz = p4_t.Z() p4st = p4_t.T() p4_scat = TLorentzVector(p4sx, p4sy, p4sz, p4st) # creating new helix, ref at new t0 point : helix_new_0 = Helix(field, particle.path.charge, p4_scat, helix_new_t.point_at_time(-t_scat)) # replacing the particle's path with the scatterd one : particle.set_path(helix_new_0, option = 'w')
def is_b_tagged(self, is_b): eff = self.eff if is_b else self.fake_rate return random.uniform(0, 1) < eff
def multiple_scattering(particle, detector_element, field): '''This function computes the scattering of a particle while propagating through the detector. As described in the pdg booklet, Passage of particles through matter, multiple scattering through small angles. the direction of a charged particle is modified. This function takes a particle (that has been propagated until the detector element where it will be scattered) and the detector element responsible for the scattering. The magnetic field has to be specified in order to create the new trajectory. Then this function computes the new direction, randomly choosen according to Moliere's theory of multiple scattering (see pdg booklet) and replaces the initial path of the particle by this new scattered path. The particle can now be propagated in the next part of the detector. ''' if not particle.q(): return # reject particles that could not be extrapolated to detector element # (particle created too late, out of the detector element) surface_in = '{}_in'.format(detector_element.name) surface_out = '{}_out'.format(detector_element.name) if not surface_in in particle.path.points or \ not surface_out in particle.path.points: return #TODOCOLIN : check usage of private attributes in_point = particle.path.points[surface_in] out_point = particle.path.points[surface_out] phi_in = particle.path.phi(in_point.X(), in_point.Y()) phi_out = particle.path.phi(out_point.X(), out_point.Y()) t_scat = particle.path.time_at_phi((phi_in + phi_out) * 0.5) # compute p4_t = p4 at t_scat : p4_0 = particle.path.p4.Clone() p4tx = p4_0.X()*math.cos(particle.path.omega*t_scat)\ + p4_0.Y()*math.sin(particle.path.omega*t_scat) p4ty =-p4_0.X()*math.sin(particle.path.omega*t_scat)\ + p4_0.Y()*math.cos(particle.path.omega*t_scat) p4tz = p4_0.Z() p4tt = p4_0.T() p4_t = TLorentzVector(p4tx, p4ty, p4tz, p4tt) # now, p4t will be modified with respect to the multiple scattering # first one has to determine theta_0 the width of the gaussian : P = p4_t.Vect().Dot(p4_t.Vect().Unit()) deltat = particle.path.time_at_phi(phi_out) - particle.path.time_at_phi( phi_in) x = abs(particle.path.path_length(deltat)) X_0 = detector_element.material.x0 theta_0 = 1.0 * 13.6e-3 / (1.0 * particle.path.speed / constants.c * P) * abs(particle.path.charge) theta_0 *= (1.0 * x / X_0)**(1.0 / 2) * (1 + 0.038 * math.log(1.0 * x / X_0)) # now, make p4_t change due to scattering : theta_space = random.gauss(0, theta_0 * 2.0**(1.0 / 2)) psi = constants.pi * random.uniform(0, 1) #double checked p3i = p4_t.Vect().Clone() e_z = TVector3(0, 0, 1) #first rotation : theta, in the xy plane a = p3i.Cross(e_z) #this may change the sign, but randomly, as the sign of theta already is p4_t.Rotate(theta_space, a) #second rotation : psi (isotropic around initial direction) p4_t.Rotate(psi, p3i.Unit()) # creating new helix, ref at scattering point : helix_new_t = Helix(field, particle.path.charge, p4_t, particle.path.point_at_time(t_scat)) # now, back to t=0 p4sx = p4_t.X()*math.cos(-particle.path.omega*t_scat)\ + p4_t.Y()*math.sin(-particle.path.omega*t_scat) p4sy =-p4_t.X()*math.sin(-particle.path.omega*t_scat)\ + p4_t.Y()*math.cos(-particle.path.omega*t_scat) p4sz = p4_t.Z() p4st = p4_t.T() p4_scat = TLorentzVector(p4sx, p4sy, p4sz, p4st) # creating new helix, ref at new t0 point : helix_new_0 = Helix(field, particle.path.charge, p4_scat, helix_new_t.point_at_time(-t_scat)) # replacing the particle's path with the scatterd one : particle.set_path(helix_new_0, option='w')
def process(self, event): event.var_random = random.uniform(0, 1)
def simulate_hadron(self, ptc): '''Simulate a hadron, neutral or charged. ptc should behave as pfobjects.Particle. ''' pdebugger.info("Simulating Hadron") #implement beam pipe scattering ecal = self.detector.elements['ecal'] hcal = self.detector.elements['hcal'] beampipe = self.detector.elements['beampipe'] frac_ecal = 0. if ptc.q() != 0 : #track is now made outside of the particle and then the particle is told where the track is track = self.make_and_store_track(ptc) resolution = self.detector.elements['tracker'].pt_resolution(track) smeared_track = self.make_smeared_track(track, resolution) if self.detector.elements['tracker'].acceptance(smeared_track): self.smeared_tracks[smeared_track.uniqueid] = smeared_track self.update_history(track.uniqueid, smeared_track.uniqueid ) ptc.track_smeared = smeared_track else: pdebugger.info(str('Rejected {}'.format(smeared_track))) propagator(ptc.q()).propagate_one(ptc, beampipe.volume.inner, self.detector.elements['field'].magnitude) propagator(ptc.q()).propagate_one(ptc, beampipe.volume.outer, self.detector.elements['field'].magnitude) #pdebug next line must be editted out to match cpp #mscat.multiple_scattering(ptc, beampipe, self.detector.elements['field'].magnitude) #re-propagate after multiple scattering in the beam pipe #indeed, multiple scattering is applied within the beam pipe, #so the extrapolation points to the beam pipe entrance and exit #change after multiple scattering. propagator(ptc.q()).propagate_one(ptc, beampipe.volume.inner, self.detector.elements['field'].magnitude) propagator(ptc.q()).propagate_one(ptc, beampipe.volume.outer, self.detector.elements['field'].magnitude) propagator(ptc.q()).propagate_one(ptc, ecal.volume.inner, self.detector.elements['field'].magnitude) if 'ecal_in' in ptc.path.points: # doesn't have to be the case (long-lived particles) path_length = ecal.material.path_length(ptc) if path_length < sys.float_info.max: # ecal path length can be infinite in case the ecal # has lambda_I = 0 (fully transparent to hadrons) time_ecal_inner = ptc.path.time_at_z(ptc.points['ecal_in'].Z()) deltat = ptc.path.deltat(path_length) time_decay = time_ecal_inner + deltat point_decay = ptc.path.point_at_time(time_decay) ptc.points['ecal_decay'] = point_decay if ecal.volume.contains(point_decay): frac_ecal = random.uniform(0., 0.7) cluster = self.make_and_store_cluster(ptc, 'ecal', frac_ecal) # For now, using the hcal resolution and acceptance # for hadronic cluster # in the ECAL. That's not a bug! smeared = self.make_and_store_smeared_cluster(cluster, hcal, acceptance=ecal) if smeared: ptc.clusters_smeared[smeared.layer] = smeared cluster = self.make_and_store_cluster(ptc, 'hcal', 1-frac_ecal) smeared = self.make_and_store_smeared_cluster(cluster, hcal) if smeared: ptc.clusters_smeared[smeared.layer] = smeared