def simulate(self, ptcs): self.reset() self.ptcs = [] #newsort # import pdb; pdb.set_trace() for gen_ptc in sorted(ptcs, key=lambda ptc: ptc.uniqueid): pdebugger.info(str('{}'.format(gen_ptc))) for gen_ptc in ptcs: ptc = pfsimparticle(gen_ptc) if ptc.pdgid() == 22: self.simulate_photon(ptc) elif abs(ptc.pdgid()) == 11: #check with colin self.propagate_electron(ptc) #smeared_ptc = self.smear_electron(ptc) #smeared.append(smeared_ptc) # self.simulate_electron(ptc) elif abs(ptc.pdgid()) == 13: #check with colin self.propagate_muon(ptc) #smeared_ptc = self.smear_muon(ptc) #smeared.append(smeared_ptc) # self.simulate_muon(ptc) elif abs(ptc.pdgid()) in [12, 14, 16]: self.simulate_neutrino(ptc) elif abs(ptc.pdgid()) > 100: #TODO make sure this is ok if ptc.q() and ptc.pt() < 0.2: # to avoid numerical problems in propagation continue self.simulate_hadron(ptc) self.ptcs.append(ptc) self.pfinput = PFInput( self.ptcs ) #collect up tracks, clusters etc ready for merging/reconstruction_muon(otc)
def make_cluster(self, ptc, detname, fraction=1., size=None): '''adds a cluster in a given detector, with a given fraction of the particle energy.''' detector = self.detector.elements[detname] propagator(ptc.q()).propagate_one( ptc, detector.volume.inner, self.detector.elements['field'].magnitude) if size is None: size = detector.cluster_size(ptc) cylname = detector.volume.inner.name if not cylname in ptc.points: # TODO Colin particle was not extrapolated here... # issue must be solved! errormsg = ''' SimulationError : cannot make cluster for particle: particle: {ptc} with vertex rho={rho:5.2f}, z={zed:5.2f} cannot be extrapolated to : {det}\n'''.format(ptc=ptc, rho=ptc.vertex.Perp(), zed=ptc.vertex.Z(), det=detector.volume.inner) self.logger.warning(errormsg) raise SimulationError( 'Particle not extrapolated to the detector, so cannot make a cluster there. No worries for now, problem will be solved :-)' ) cluster = Cluster(ptc.p4().E() * fraction, ptc.points[cylname], size, cylname, ptc) ptc.clusters[cylname] = cluster pdebugger.info(" ".join(("Made", cluster.__str__()))) return cluster
def make_and_store_smeared_cluster(self, cluster, detector, accept=False, acceptance=None): '''Returns a copy of cluster, after a gaussian smearing of the energy. The smeared cluster is stored for further processing. @param cluster: the cluster to be smeared. @param detector: detector object from which the energy resolution, energy response and acceptance parametrizations are taken. @param accept: if set to true, always accept the cluster after smearing @param acceptance: optional detedctor object for acceptance. if provided, and if accept is False, used in place of detector.acceptance ''' eres = detector.energy_resolution(cluster.energy, cluster.position.Eta()) response = detector.energy_response(cluster.energy, cluster.position.Eta()) energy = cluster.energy * random.gauss(response, eres) clusters = self.cluster_collection(cluster.layer, smeared=True) smeared_cluster = SmearedCluster(cluster, energy, cluster.position, cluster.size(), cluster.layer, len(clusters), cluster.particle) pdebugger.info(str('Made {}'.format(smeared_cluster))) det = acceptance if acceptance else detector if det.acceptance(smeared_cluster) or accept: clusters[smeared_cluster.uniqueid] = smeared_cluster self.update_history(cluster.uniqueid, smeared_cluster.uniqueid) return smeared_cluster else: pdebugger.info(str('Rejected {}'.format(smeared_cluster))) return None
def reconstruct_cluster(self, cluster, layer, energy = None, vertex = None): '''construct a photon if it is an ecal construct a neutral hadron if it is an hcal ''' if vertex is None: vertex = TVector3() pdg_id = None if layer=='ecal_in': pdg_id = 22 #photon elif layer=='hcal_in': pdg_id = 130 #K0 else: raise ValueError('layer must be equal to ecal_in or hcal_in') assert(pdg_id) mass, charge = particle_data[pdg_id] if energy is None: energy = cluster.energy if energy < mass: return None if (mass==0): momentum= energy #avoid sqrt for zero mass else: momentum = math.sqrt(energy**2 - mass**2) p3 = cluster.position.Unit() * momentum p4 = TLorentzVector(p3.Px(), p3.Py(), p3.Pz(), energy) #mass is not accurate here particle = Particle(p4, vertex, charge, pdg_id, Identifier.PFOBJECTTYPE.RECPARTICLE) path = StraightLine(p4, vertex) path.points[layer] = cluster.position #alice: this may be a bit strange because we can make a photon with a path where the point is actually that of the hcal? # nb this only is problem if the cluster and the assigned layer are different particle.set_path(path) particle.clusters[layer] = cluster # not sure about this either when hcal is used to make an ecal cluster? self.locked[cluster.uniqueid] = True #just OK but not nice if hcal used to make ecal. pdebugger.info(str('Made {} from {}'.format(particle, cluster))) return particle
def make_cluster(self, ptc, detname, fraction=1.0, size=None): """adds a cluster in a given detector, with a given fraction of the particle energy.""" detector = self.detector.elements[detname] propagator(ptc.q()).propagate_one(ptc, detector.volume.inner, self.detector.elements["field"].magnitude) if size is None: size = detector.cluster_size(ptc) cylname = detector.volume.inner.name if not cylname in ptc.points: # TODO Colin particle was not extrapolated here... # issue must be solved! errormsg = """ SimulationError : cannot make cluster for particle: particle: {ptc} with vertex rho={rho:5.2f}, z={zed:5.2f} cannot be extrapolated to : {det}\n""".format( ptc=ptc, rho=ptc.vertex.Perp(), zed=ptc.vertex.Z(), det=detector.volume.inner ) self.logger.warning(errormsg) raise SimulationError( "Particle not extrapolated to the detector, so cannot make a cluster there. No worries for now, problem will be solved :-)" ) cluster = Cluster(ptc.p4().E() * fraction, ptc.points[cylname], size, cylname, ptc) ptc.clusters[cylname] = cluster pdebugger.info(" ".join(("Made", cluster.__str__()))) return cluster
def make_and_store_cluster(self, ptc, detname, fraction=1., size=None): '''adds a cluster in a given detector, with a given fraction of the particle energy. Stores the cluster in the appropriate collection and records cluster in the history''' detector = self.detector.elements[detname] propagator(ptc.q()).propagate_one(ptc, detector.volume.inner, self.detector.elements['field'].magnitude) if size is None: size = detector.cluster_size(ptc) cylname = detector.volume.inner.name if not cylname in ptc.points: # TODO Colin particle was not extrapolated here... # issue must be solved! errormsg = ''' SimulationError : cannot make cluster for particle: particle: {ptc} with vertex rho={rho:5.2f}, z={zed:5.2f} cannot be extrapolated to : {det}\n'''.format(ptc=ptc, rho=ptc.vertex.Perp(), zed=ptc.vertex.Z(), det=detector.volume.inner) self.logger.warning(errormsg) raise SimulationError('Particle not extrapolated to the detector, so cannot make a cluster there. No worries for now, problem will be solved :-)') clusters = self.cluster_collection(cylname) cluster = Cluster(ptc.p4().E()*fraction, ptc.points[cylname], size, cylname, len(clusters), ptc) #update collections and history ptc.clusters[cylname] = cluster clusters[cluster.uniqueid] = cluster self.update_history(ptc.uniqueid, cluster.uniqueid,) pdebugger.info(" ".join(("Made", cluster.__str__()))) return cluster
def simulate(self, ptcs, history): self.reset() self.history = history # import pdb; pdb.set_trace() for ptc in ptcs: if ptc.q() and ptc.pt() < 0.2 and abs(ptc.pdgid()) >= 100: # to avoid numerical problems in propagation (and avoid making a particle that is not used) continue pdebugger.info(str('Simulating {}'.format(ptc))) # ptc = pfsimparticle(gen_ptc, len(self.simulated_particles)) self.history[ptc.uniqueid] = Node(ptc.uniqueid) if ptc.pdgid() == 22: self.simulate_photon(ptc) elif abs(ptc.pdgid()) == 11: #check with colin # self.propagate_electron(ptc) self.simulate_electron(ptc) elif abs(ptc.pdgid()) == 13: #check with colin # self.propagate_muon(ptc) self.simulate_muon(ptc) elif abs(ptc.pdgid()) in [12, 14, 16]: self.simulate_neutrino(ptc) elif abs(ptc.pdgid()) > 100: #TODO make sure this is ok self.simulate_hadron(ptc) self.ptcs.append(ptc) self.simulated_particles[ptc.uniqueid] = ptc
def simulate_muon(self, ptc): pdebugger.info("Simulating Muon") self.propagate(ptc) smeared_track = self.smear_track(ptc.track, self.detector.elements['tracker']) if smeared_track: ptc.track_smeared = smeared_track
def smear_muon(self, ptc): pdebugger.info("Smearing Muon") self.propagate(ptc) if ptc.q() != 0: pdebugger.info(" ".join(("Made", ptc.track.__str__()))) smeared = copy.deepcopy(ptc) return smeared
def simulate(self, ptcs): self.reset() self.ptcs = [] # newsort # import pdb; pdb.set_trace() for gen_ptc in sorted(ptcs, key=lambda ptc: ptc.uniqueid): pdebugger.info(str("{}".format(gen_ptc))) for gen_ptc in ptcs: ptc = pfsimparticle(gen_ptc) if ptc.pdgid() == 22: self.simulate_photon(ptc) elif abs(ptc.pdgid()) == 11: # check with colin self.propagate_electron(ptc) # smeared_ptc = self.smear_electron(ptc) # smeared.append(smeared_ptc) # self.simulate_electron(ptc) elif abs(ptc.pdgid()) == 13: # check with colin self.propagate_muon(ptc) # smeared_ptc = self.smear_muon(ptc) # smeared.append(smeared_ptc) # self.simulate_muon(ptc) elif abs(ptc.pdgid()) in [12, 14, 16]: self.simulate_neutrino(ptc) elif abs(ptc.pdgid()) > 100: # TODO make sure this is ok if ptc.q() and ptc.pt() < 0.2: # to avoid numerical problems in propagation continue self.simulate_hadron(ptc) self.ptcs.append(ptc) self.pfinput = PFInput(self.ptcs) # collect up tracks, clusters etc ready for merging/reconstruction_muon(otc)
def make_and_store_smeared_cluster(self, cluster, detector, accept=False, acceptance=None): '''Returns a copy of self with a smeared energy. If accept is False (default), returns None if the smeared cluster is not in the detector acceptance. ''' eres = detector.energy_resolution(cluster.energy, cluster.position.Eta()) response = detector.energy_response(cluster.energy, cluster.position.Eta()) energy = cluster.energy * random.gauss(response, eres) clusters = self.smeared_cluster_collection(cluster.layer) smeared_cluster = SmearedCluster(cluster, energy, cluster.position, cluster.size(), cluster.layer, len(clusters), cluster.particle) pdebugger.info(str('Made {}'.format(smeared_cluster))) det = acceptance if acceptance else detector if det.acceptance(smeared_cluster) or accept: clusters[smeared_cluster.uniqueid] = smeared_cluster self.update_history(cluster.uniqueid, smeared_cluster.uniqueid) return smeared_cluster else: pdebugger.info(str('Rejected {}'.format(smeared_cluster))) return None
def propagate_electron(self, ptc): pdebugger.info("Propogate Electron") ecal = self.detector.elements['ecal'] propagator(ptc.q()).propagate_one(ptc, ecal.volume.inner, self.detector.elements['field'].magnitude) return
def reconstruct_track( self, track, pdgid, parent_ids, clusters=None ): # cluster argument does not ever seem to be used at present '''construct a charged hadron from the track ''' if self.locked[track.uniqueid]: return vertex = track.path.points['vertex'] pdgid = pdgid * track.charge mass, charge = particle_data[pdgid] p4 = TLorentzVector() p4.SetVectM(track.p3(), mass) particle = Particle(p4, vertex, charge, pdgid, len(self.particles), subtype='r') #todo fix this so it picks up smeared track points (need to propagagte smeared track) particle.set_track( track) #refer to existing track rather than make a new one self.locked[track.uniqueid] = True pdebugger.info(str('Made {} from {}'.format(particle, track))) self.insert_particle(parent_ids, particle) return particle
def simulate_electron(self, ptc): '''Simulate an electron corresponding to gen particle ptc. Uses the methods detector.electron_energy_resolution and detector.electron_acceptance to smear the electron track. Later on, the particle flow algorithm will use the tracks coming from an electron to reconstruct electrons. This method does not simulate an electron energy deposit in the ECAL. ''' pdebugger.info("Simulating Electron") ecal = self.detector.elements['ecal'] track = self.make_and_store_track(ptc) propagator(ptc.q()).propagate_one( ptc, ecal.volume.inner, self.detector.elements['field'].magnitude ) eres = self.detector.electron_energy_resolution(ptc) smeared_track = self.make_smeared_track(track, eres) if self.detector.electron_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)))
def reconstruct(self, papasevent, block_type_and_subtype): '''papasevent: PapasEvent containing collections of particle flow objects block_type_and_subtype: which blocks collection to use''' self.unused = [] self.papasevent = papasevent self.history_helper = HistoryHelper(papasevent) self.particles = dict() self.splitblocks = dict() blocks = papasevent.get_collection(block_type_and_subtype) # simplify the blocks by editing the links so that each track will end up linked to at most one hcal # then recalculate the blocks for blockid in sorted(blocks.keys(), reverse=True): #big blocks come first pdebugger.info(str('Splitting {}'.format(blocks[blockid]))) newblocks = self.simplify_blocks(blocks[blockid], self.papasevent.history) self.splitblocks.update(newblocks) #reconstruct each of the resulting blocks for b in sorted(self.splitblocks.keys(), reverse=True): #put big interesting blocks first sblock = self.splitblocks[b] pdebugger.info('Processing {}'.format(sblock)) self.reconstruct_block(sblock) #check if anything is unused if len(self.unused): self.log.warning(str(self.unused)) self.log.info("Particles:") self.log.info(str(self))
def smear_electron(self, ptc): pdebugger.info("Smearing Electron") ecal = self.detector.elements["ecal"] propagator(ptc.q()).propagate_one(ptc, ecal.volume.inner, self.detector.elements["field"].magnitude) if ptc.q() != 0: pdebugger.info(" ".join(("Made", ptc.track.__str__()))) smeared = copy.deepcopy(ptc) return 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.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 reconstruct_cluster(self, cluster, layer, parent_ids, energy=None, vertex=None): '''construct a photon if it is an ecal construct a neutral hadron if it is an hcal ''' if self.locked[cluster.uniqueid]: return if vertex is None: vertex = TVector3() pdg_id = None propagate_to = None if layer == 'ecal_in': pdg_id = 22 #photon propagate_to = [self.detector.elements['ecal'].volume.inner] elif layer == 'hcal_in': pdg_id = 130 #K0 propagate_to = [ self.detector.elements['ecal'].volume.inner, self.detector.elements['hcal'].volume.inner ] else: raise ValueError('layer must be equal to ecal_in or hcal_in') assert (pdg_id) mass, charge = particle_data[pdg_id] if energy is None: energy = cluster.energy if energy < mass: return None if mass == 0: momentum = energy #avoid sqrt for zero mass else: momentum = math.sqrt(energy**2 - mass**2) p3 = cluster.position.Unit() * momentum p4 = TLorentzVector(p3.Px(), p3.Py(), p3.Pz(), energy) #mass is not accurate here particle = Particle(p4, vertex, charge, pdg_id, len(self.particles), subtype='r') # alice: this may be a bit strange because we can make a photon # with a path where the point is actually that of the hcal? # nb this only is problem if the cluster and the assigned layer # are different propagator(charge).propagate([particle], propagate_to) #merge Nov 10th 2016 not sure about following line (was commented out in papasevent branch) particle.clusters[ layer] = cluster # not sure about this either when hcal is used to make an ecal cluster? self.locked[ cluster. uniqueid] = True #just OK but not nice if hcal used to make ecal. pdebugger.info(str('Made {} from {}'.format(particle, cluster))) self.insert_particle(parent_ids, particle)
def make_and_store_track(self, ptc): '''creates a new track, adds it into the true_tracks collection and updates the history information''' track = Track(ptc.p3(), ptc.q(), ptc.path, index=len(self.true_tracks)) pdebugger.info(" ".join(("Made", track.__str__()))) self.true_tracks[track.uniqueid] = track self.update_history(ptc.uniqueid, track.uniqueid) ptc.set_track(track) return track
def simulate_photon(self, ptc): pdebugger.info("Simulating Photon") detname = 'ecal' ecal = self.detector.elements[detname] propagator(ptc.q()).propagate_one(ptc, ecal.volume.inner) cluster = self.make_and_store_cluster(ptc, detname) smeared = self.make_and_store_smeared_cluster(cluster, ecal) if smeared: ptc.clusters_smeared[smeared.layer] = smeared
def smear_electron(self, ptc): pdebugger.info("Smearing Electron") ecal = self.detector.elements['ecal'] propagator(ptc.q()).propagate_one( ptc, ecal.volume.inner, self.detector.elements['field'].magnitude) if ptc.q() != 0: pdebugger.info(" ".join(("Made", ptc.track.__str__()))) smeared = copy.deepcopy(ptc) return smeared
def simulate_photon(self, ptc): pdebugger.info("Simulating Photon") detname = "ecal" ecal = self.detector.elements[detname] propagator(ptc.q()).propagate_one(ptc, ecal.volume.inner) cluster = self.make_cluster(ptc, detname) smeared = self.smear_cluster(cluster, ecal) if smeared: ptc.clusters_smeared[smeared.layer] = smeared
def simulate_electron(self, ptc): pdebugger.info("Simulating Electron") ecal = self.detector.elements["ecal"] propagator(ptc.q()).propagate_one(ptc, ecal.volume.inner, self.detector.elements["field"].magnitude) cluster = self.make_cluster(ptc, "ecal") smeared_cluster = self.smear_cluster(cluster, ecal) if smeared_cluster: ptc.clusters_smeared[smeared_cluster.layer] = smeared_cluster smeared_track = self.smear_track(ptc.track, self.detector.elements["tracker"]) if smeared_track: ptc.track_smeared = smeared_track
def simparticle(ptc, index): '''Create a sim particle to be used in papas from an input particle. ''' tp4 = ptc.p4() vertex = ptc.start_vertex().position() charge = ptc.q() pid = ptc.pdgid() simptc = Particle(tp4, vertex, charge, index, pid) pdebugger.info(" ".join(("Made", simptc.__str__()))) simptc.gen_ptc = ptc return simptc
def make_smeared_track(self, track, resolution): '''create a new smeared track''' #TODO smearing depends on particle type! scale_factor = random.gauss(1, resolution) smeared_track = SmearedTrack(track, track.p3 * scale_factor, track.charge, track.path, index = len(self.smeared_tracks)) pdebugger.info(" ".join(("Made", smeared_track.__str__()))) return smeared_track
def smear_track(self, track, detector, accept=False): # TODO smearing depends on particle type! ptres = detector.pt_resolution(track) scale_factor = random.gauss(1, ptres) smeared_track = SmearedTrack(track, track.p3 * scale_factor, track.charge, track.path) pdebugger.info(" ".join(("Made", smeared_track.__str__()))) if detector.acceptance(smeared_track) or accept: return smeared_track else: pdebugger.info(str("Rejected {}".format(smeared_track))) return None
def make_smeared_track(self, track, resolution): '''create a new smeared track''' #TODO smearing depends on particle type! scale_factor = random.gauss(1, resolution) smeared_track = SmearedTrack(track, track.p3 * scale_factor, track.charge, track.path, index=len(self.smeared_tracks)) pdebugger.info(" ".join(("Made", smeared_track.__str__()))) return smeared_track
def pfsimparticle(ptc): '''Create a PFSimParticle from a particle. The PFSimParticle will have the same p4, vertex, charge, pdg ID. ''' tp4 = ptc.p4() vertex = ptc.start_vertex().position() charge = ptc.q() pid = ptc.pdgid() simptc = PFSimParticle(tp4, vertex, charge, pid) pdebugger.info(" ".join(("Made", simptc.__str__()))) simptc.gen_ptc = ptc return simptc
def smear_track(self, track, detector, accept=False): #TODO smearing depends on particle type! ptres = detector.pt_resolution(track) scale_factor = random.gauss(1, ptres) smeared_track = SmearedTrack(track, track.p3 * scale_factor, track.charge, track.path) pdebugger.info(" ".join(("Made", smeared_track.__str__()))) if detector.acceptance(smeared_track) or accept: return smeared_track else: pdebugger.info(str('Rejected {}'.format(smeared_track))) return None
def pfsimparticle(ptc): """Create a PFSimParticle from a particle. The PFSimParticle will have the same p4, vertex, charge, pdg ID. """ tp4 = ptc.p4() vertex = ptc.start_vertex().position() charge = ptc.q() pid = ptc.pdgid() simptc = PFSimParticle(tp4, vertex, charge, pid) pdebugger.info(" ".join(("Made", simptc.__str__()))) simptc.gen_ptc = ptc return simptc
def simulate_electron(self, ptc): pdebugger.info("Simulating Electron") ecal = self.detector.elements['ecal'] propagator(ptc.q()).propagate_one( ptc, ecal.volume.inner, self.detector.elements['field'].magnitude) cluster = self.make_cluster(ptc, 'ecal') smeared_cluster = self.smear_cluster(cluster, ecal) if smeared_cluster: ptc.clusters_smeared[smeared_cluster.layer] = smeared_cluster smeared_track = self.smear_track(ptc.track, self.detector.elements['tracker']) if smeared_track: ptc.track_smeared = smeared_track
def reconstruct_track(self, track, clusters=None): # cluster argument does not ever seem to be used at present """construct a charged hadron from the track """ vertex = track.path.points["vertex"] pdg_id = 211 * track.charge mass, charge = particle_data[pdg_id] p4 = TLorentzVector() p4.SetVectM(track.p3, mass) particle = Particle(p4, vertex, charge, pdg_id, Identifier.PFOBJECTTYPE.RECPARTICLE) particle.set_path(track.path) particle.clusters = clusters self.locked[track.uniqueid] = True pdebugger.info(str("Made {} from {}".format(particle, track))) return particle
def reconstruct_track(self, track, clusters = None): # cluster argument does not ever seem to be used at present '''construct a charged hadron from the track ''' vertex = track.path.points['vertex'] pdg_id = 211 * track.charge mass, charge = particle_data[pdg_id] p4 = TLorentzVector() p4.SetVectM(track.p3, mass) particle = Particle(p4, vertex, charge, pdg_id, Identifier.PFOBJECTTYPE.RECPARTICLE) particle.set_path(track.path) particle.clusters = clusters self.locked[track.uniqueid] = True pdebugger.info(str('Made {} from {}'.format(particle, track))) return particle
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 simparticle(ptc, index): '''Create a sim particle to be used in papas from an input particle. ''' tp4 = ptc.p4() vertex = ptc.start_vertex().position() charge = ptc.q() pid = ptc.pdgid() simptc = Particle(tp4, vertex, charge, pid) simptc.set_dagid(IdCoder.make_id(IdCoder.PFOBJECTTYPE.PARTICLE, index, 's', simptc.idvalue)) pdebugger.info(" ".join(("Made", simptc.__str__()))) #simptc.gen_ptc = ptc #record that sim particle derives from gen particle child = papasevent.history.setdefault(simptc.dagid(), Node(simptc.dagid())) #creates a new node if it is not there already parent = papasevent.history.setdefault(ptc.dagid(), Node(ptc.dagid())) parent.add_child(child) return simptc
def simulate_muon(self, ptc): '''Simulate a muon corresponding to gen particle ptc Uses the methods detector.muon_energy_resolution and detector.muon_acceptance to smear the muon track. Later on, the particle flow algorithm will use the tracks coming from a muon to reconstruct muons. This method does not simulate energy deposits in the calorimeters ''' pdebugger.info("Simulating Muon") track = self.make_and_store_track(ptc) self.propagate(ptc) smeared_track = self.make_and_store_smeared_track( ptc, track, self.detector.muon_resolution, self.detector.muon_acceptance)
def reconstruct_cluster(self, cluster, layer, parent_ids, energy=None, vertex=None): '''construct a photon if it is an ecal construct a neutral hadron if it is an hcal ''' if self.locked[cluster.uniqueid]: return if vertex is None: vertex = TVector3() pdg_id = None propagate_to = None if layer=='ecal_in': pdg_id = 22 #photon propagate_to = [ self.detector.elements['ecal'].volume.inner ] elif layer=='hcal_in': pdg_id = 130 #K0 propagate_to = [ self.detector.elements['ecal'].volume.inner, self.detector.elements['hcal'].volume.inner ] else: raise ValueError('layer must be equal to ecal_in or hcal_in') assert(pdg_id) mass, charge = particle_data[pdg_id] if energy is None: energy = cluster.energy if energy < mass: return None if mass == 0: momentum = energy #avoid sqrt for zero mass else: momentum = math.sqrt(energy**2 - mass**2) p3 = cluster.position.Unit() * momentum p4 = TLorentzVector(p3.Px(), p3.Py(), p3.Pz(), energy) #mass is not accurate here particle = Particle(p4, vertex, charge, pdg_id) particle.set_dagid(IdCoder.make_id(IdCoder.PFOBJECTTYPE.PARTICLE, len(self.particles), 'r', particle.idvalue)) # alice: this may be a bit strange because we can make a photon # with a path where the point is actually that of the hcal? # nb this only is problem if the cluster and the assigned layer # are different propagator(charge).propagate([particle], propagate_to) #merge Nov 10th 2016 not sure about following line (was commented out in papasevent branch) particle.clusters[layer] = cluster # not sure about this either when hcal is used to make an ecal cluster? self.locked[cluster.uniqueid] = True #just OK but not nice if hcal used to make ecal. pdebugger.info(str('Made {} from {}'.format(particle, cluster))) self.insert_particle(parent_ids, particle)
def _make_and_store_merged_clusters(self): ''' This takes the subgraphs of connected clusters that are to be merged, and makes a new MergedCluster. It stores the new MergedCluser into the self.merged_clusters collection. It then updates the history to record the links between the clusters and the merged cluster. ''' for subgraph in self.subgraphs: # TODO may want to order subgraphs from largest to smallest at some point subgraph.sort(reverse=True) #start with highest E or pT clusters overlapping_clusters = [self.clusters[node_id] for node_id in subgraph] supercluster = MergedCluster(overlapping_clusters, len(self.merged_clusters)) self.merged_clusters[supercluster.uniqueid] = supercluster if self.history_nodes: snode = Node(supercluster.uniqueid) self.history_nodes[supercluster.uniqueid] = snode for node_id in subgraph: self.history_nodes[node_id].add_child(snode) pdebugger.info(str('Made {}'.format(supercluster)))
def reconstruct_track(self, track, pdgid, parent_ids, clusters=None): # cluster argument does not ever seem to be used at present '''construct a charged hadron from the track ''' if self.locked[track.uniqueid]: return vertex = track.path.points['vertex'] pdgid = pdgid * track.charge mass, charge = particle_data[pdgid] p4 = TLorentzVector() p4.SetVectM(track.p3, mass) particle = Particle(p4, vertex, charge, len(self.particles), pdgid, subtype='r') #todo fix this so it picks up smeared track points (need to propagagte smeared track) particle.set_track(track) #refer to existing track rather than make a new one self.locked[track.uniqueid] = True pdebugger.info(str('Made {} from {}'.format(particle, track))) self.insert_particle(parent_ids, particle) return particle
def simulate_electron(self, ptc): '''Simulate an electron corresponding to gen particle ptc. Uses the methods detector.electron_energy_resolution and detector.electron_acceptance to smear the electron track. Later on, the particle flow algorithm will use the tracks coming from an electron to reconstruct electrons. This method does not simulate an electron energy deposit in the ECAL. ''' pdebugger.info("Simulating Electron") ecal = self.detector.elements['ecal'] track = self.make_and_store_track(ptc) propagator(ptc.q()).propagate_one( ptc, ecal.volume.inner, self.detector.elements['field'].magnitude) smeared_track = self.make_and_store_smeared_track( ptc, track, self.detector.electron_resolution, self.detector.electron_acceptance)
def smear_cluster(self, cluster, detector, accept=False, acceptance=None): """Returns a copy of self with a smeared energy. If accept is False (default), returns None if the smeared cluster is not in the detector acceptance. """ eres = detector.energy_resolution(cluster.energy, cluster.position.Eta()) response = detector.energy_response(cluster.energy, cluster.position.Eta()) energy = cluster.energy * random.gauss(response, eres) smeared_cluster = SmearedCluster( cluster, energy, cluster.position, cluster.size(), cluster.layer, cluster.particle ) pdebugger.info(str("Made {}".format(smeared_cluster))) det = acceptance if acceptance else detector if det.acceptance(smeared_cluster) or accept: return smeared_cluster else: pdebugger.info(str("Rejected {}".format(smeared_cluster))) return None
def simulate_muon(self, ptc): '''Simulate a muon corresponding to gen particle ptc Uses the methods detector.muon_energy_resolution and detector.muon_acceptance to smear the muon track. Later on, the particle flow algorithm will use the tracks coming from a muon to reconstruct muons. This method does not simulate energy deposits in the calorimeters ''' pdebugger.info("Simulating Muon") track = self.make_and_store_track(ptc) self.propagate(ptc) smeared_track = self.make_and_store_smeared_track( ptc, track, self.detector.muon_resolution, self.detector.muon_acceptance )
def _make_blocks (self) : ''' uses the DAGfloodfill algorithm in connection with the BlockBuilder nodes to work out which elements are connected Each set of connected elements will be used to make a new PFBlock ''' for subgraph in self.subgraphs: #make the block block = PFBlock(subgraph, self.edges, self.startindex + len(self.blocks), subtype=self.subtype) pdebugger.info("Made {}".format(block)) #put the block in the dict of blocks self.blocks[block.uniqueid] = block #make a node for the block and add into the history Nodes if (self.history != None): blocknode = Node(block.uniqueid) self.history[block.uniqueid] = blocknode #now add in the links between the block elements and the block into the history for elemid in block.element_uniqueids: self.history[elemid].add_child(blocknode)
def reconstruct(self, event, blocksname, historyname): '''arguments event: should contain blocks and optionally history_nodes''' self.blocks = getattr(event, blocksname) self.unused = [] self.particles = dict() # history nodes will be used to connect reconstructed particles into the history # its optional at the moment if hasattr(event, historyname): self.history_nodes = event.history_nodes else : self.history_nodes = None # simplify the blocks by editing the links so that each track will end up linked to at most one hcal # then recalculate the blocks splitblocks=dict() for block in self._sorted_block_keys(): #put big interesting blocks first #print "block: ", len(self.blocks[block]), self.blocks[block].short_name(); newblocks=self.simplify_blocks(self.blocks[block], self.history_nodes) if newblocks != None: splitblocks.update( newblocks) if len(splitblocks): self.blocks.update(splitblocks) #reconstruct each of the resulting blocks for b in self._sorted_block_keys(): #put big interesting blocks first block=self.blocks[b] if block.is_active: # when blocks are split the original gets deactivated #ALICE debugging #if len(block.element_uniqueids)<6: # continue pdebugger.info('Processing {}'.format(block)) self.reconstruct_block(block) self.unused.extend( [id for id in block.element_uniqueids if not self.locked[id]]) #check if anything is unused if len(self.unused): self.log.warning(str(self.unused)) self.log.info("Particles:") self.log.info(str(self))
def make_and_store_smeared_track(self, ptc, track, detector_resolution, detector_acceptance): '''create a new smeared track''' #TODO smearing depends on particle type! resolution = detector_resolution(track) scale_factor = random.gauss(1, resolution) smeared_track = SmearedTrack(track, track._p3 * scale_factor, track.charge, track.path, index = len(self.smeared_tracks)) pdebugger.info(" ".join(("Made", smeared_track.__str__()))) if detector_acceptance(track): self.smeared_tracks[smeared_track.uniqueid] = smeared_track self.update_history(track.uniqueid, smeared_track.uniqueid) ptc.track_smeared = smeared_track return smeared_track else: pdebugger.info(str('Rejected {}'.format(smeared_track))) return None
def make_and_store_smeared_track(self, ptc, track, detector_resolution, detector_acceptance): '''create a new smeared track''' #TODO smearing depends on particle type! resolution = detector_resolution(ptc) scale_factor = random.gauss(1, resolution) smeared_track = SmearedTrack(track, track.p3 * scale_factor, track.charge, track.path, index = len(self.smeared_tracks)) pdebugger.info(" ".join(("Made", smeared_track.__str__()))) if detector_acceptance(smeared_track): self.smeared_tracks[smeared_track.uniqueid] = smeared_track self.update_history(track.uniqueid, smeared_track.uniqueid ) ptc.track_smeared = smeared_track return smeared_track else: pdebugger.info(str('Rejected {}'.format(smeared_track))) return None
def _make_and_store_merged_clusters(self): ''' This takes the subgraphs of connected clusters that are to be merged, and makes a new MergedCluster. It stores the new MergedCluser into the self.merged_clusters collection. It then updates the history to record the links between the clusters and the merged cluster. ''' for subgraph in self.subgraphs: # TODO may want to order subgraphs from largest to smallest at some point subgraph.sort(reverse=True) #start with highest E or pT clusters overlapping_clusters = [ self.clusters[node_id] for node_id in subgraph ] supercluster = MergedCluster(overlapping_clusters, len(self.merged_clusters)) self.merged_clusters[supercluster.uniqueid] = supercluster if self.history_nodes: snode = Node(supercluster.uniqueid) self.history_nodes[supercluster.uniqueid] = snode for node_id in subgraph: self.history_nodes[node_id].add_child(snode) pdebugger.info(str('Made {}'.format(supercluster)))
def smear_cluster(self, cluster, detector, accept=False, acceptance=None): '''Returns a copy of self with a smeared energy. If accept is False (default), returns None if the smeared cluster is not in the detector acceptance. ''' eres = detector.energy_resolution(cluster.energy, cluster.position.Eta()) response = detector.energy_response(cluster.energy, cluster.position.Eta()) energy = cluster.energy * random.gauss(response, eres) smeared_cluster = SmearedCluster(cluster, energy, cluster.position, cluster.size(), cluster.layer, cluster.particle) pdebugger.info(str('Made {}'.format(smeared_cluster))) det = acceptance if acceptance else detector if det.acceptance(smeared_cluster) or accept: return smeared_cluster else: pdebugger.info(str('Rejected {}'.format(smeared_cluster))) return None
def reconstruct_cluster(self, cluster, layer, energy=None, vertex=None): """construct a photon if it is an ecal construct a neutral hadron if it is an hcal """ if vertex is None: vertex = TVector3() pdg_id = None propagate_to = None if layer == "ecal_in": pdg_id = 22 # photon propagate_to = [self.detector.elements["ecal"].volume.inner] elif layer == "hcal_in": pdg_id = 130 # K0 propagate_to = [self.detector.elements["ecal"].volume.inner, self.detector.elements["hcal"].volume.inner] else: raise ValueError("layer must be equal to ecal_in or hcal_in") assert pdg_id mass, charge = particle_data[pdg_id] if energy is None: energy = cluster.energy if energy < mass: return None if mass == 0: momentum = energy # avoid sqrt for zero mass else: momentum = math.sqrt(energy ** 2 - mass ** 2) p3 = cluster.position.Unit() * momentum p4 = TLorentzVector(p3.Px(), p3.Py(), p3.Pz(), energy) # mass is not accurate here particle = Particle(p4, vertex, charge, pdg_id, Identifier.PFOBJECTTYPE.RECPARTICLE) # path = StraightLine(p4, vertex) # path.points[layer] = cluster.position # alice: this may be a bit strange because we can make a photon # with a path where the point is actually that of the hcal? # nb this only is problem if the cluster and the assigned layer # are different # particle.set_path(path) propagator(charge).propagate([particle], propagate_to) particle.clusters[layer] = cluster # not sure about this either when hcal is used to make an ecal cluster? self.locked[cluster.uniqueid] = True # just OK but not nice if hcal used to make ecal. pdebugger.info(str("Made {} from {}".format(particle, cluster))) return particle
def reconstruct(self, event, blocksname, historyname): """arguments event: should contain blocks and optionally history_nodes""" self.blocks = getattr(event, blocksname) self.unused = [] self.particles = dict() # history nodes will be used to connect reconstructed particles into the history # its optional at the moment if hasattr(event, historyname): self.history_nodes = event.history_nodes else: self.history_nodes = None # simplify the blocks by editing the links so that each track will end up linked to at most one hcal # then recalculate the blocks splitblocks = dict() for block in self._sorted_block_keys(): # put big interesting blocks first # print "block: ", len(self.blocks[block]), self.blocks[block].short_name(); newblocks = self.simplify_blocks(self.blocks[block], self.history_nodes) if newblocks != None: splitblocks.update(newblocks) if len(splitblocks): self.blocks.update(splitblocks) # reconstruct each of the resulting blocks for b in self._sorted_block_keys(): # put big interesting blocks first block = self.blocks[b] if block.is_active: # when blocks are split the original gets deactivated # ALICE debugging # if len(block.element_uniqueids)<6: # continue pdebugger.info("Processing {}".format(block)) self.reconstruct_block(block) self.unused.extend([id for id in block.element_uniqueids if not self.locked[id]]) # check if anything is unused if len(self.unused): self.log.warning(str(self.unused)) self.log.info("Particles:") self.log.info(str(self))
def simulate_muon(self, ptc): '''Simulate a muon corresponding to gen particle ptc Uses the methods detector.muon_energy_resolution and detector.muon_acceptance to smear the muon track. Later on, the particle flow algorithm will use the tracks coming from a muon to reconstruct muons. This method does not simulate energy deposits in the calorimeters ''' pdebugger.info("Simulating Muon") track = self.make_and_store_track(ptc) self.propagate(ptc) ptres = self.detector.muon_pt_resolution(ptc) smeared_track = self.make_smeared_track(track, ptres) if self.detector.muon_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)))
def simparticle(ptc, index): '''Create a sim particle to be used in papas from an input particle. ''' tp4 = ptc.p4() vertex = ptc.start_vertex().position() charge = ptc.q() pid = ptc.pdgid() simptc = Particle(tp4, vertex, charge, pid) simptc.set_dagid( IdCoder.make_id(IdCoder.PFOBJECTTYPE.PARTICLE, index, 's', simptc.idvalue)) pdebugger.info(" ".join(("Made", simptc.__str__()))) #simptc.gen_ptc = ptc #record that sim particle derives from gen particle child = papasevent.history.setdefault( simptc.dagid(), Node(simptc.dagid( ))) #creates a new node if it is not there already parent = papasevent.history.setdefault(ptc.dagid(), Node(ptc.dagid())) parent.add_child(child) return simptc
def _make_merged_clusters(self): #carry out the merging of linked clusters for subgraphids in self.subgraphs: subgraphids.sort() first = None supercluster =None snode = None for elemid in subgraphids : if not first: first = elemid supercluster = MergedCluster(self.clusters[elemid]) self.merged[supercluster.uniqueid] = supercluster; if (self.history_nodes) : snode = Node(supercluster.uniqueid) self.history_nodes[supercluster.uniqueid] = snode else: thing = self.clusters[elemid] supercluster += thing if (self.history_nodes) : self.history_nodes[elemid].add_child(snode) pdebugger.info('Merged Cluster from {}\n'.format(self.clusters[elemid])) pdebugger.info(str('Made {}\n'.format(supercluster)))