diff --git a/km3buu/geometry.py b/km3buu/geometry.py index 27c685ec2d2bdbad50b47a787d644b43e2568d10..e94ad97f14cef7a5e8a88e7e6356cd35726b4b13 100644 --- a/km3buu/geometry.py +++ b/km3buu/geometry.py @@ -13,6 +13,14 @@ __status__ = "Development" import numpy as np from abc import ABC, abstractmethod +from collections import defaultdict +from scipy.spatial.transform import Rotation +from particle import Particle + +import proposal as pp +from .propagation import * + +M_TO_CM = 1e2 class DetectorVolume(ABC): @@ -26,10 +34,15 @@ class DetectorVolume(ABC): self._solid_angle = 1 @abstractmethod - def random_dir(self): + def random_dir(self, n=1): """ Generate a random direction for the interaction + Parameter + --------- + n: int (default: 1) + Number of vertex positions to sample + Return ------ tuple [rad, 1] (phi, cos(theta)) @@ -37,17 +50,41 @@ class DetectorVolume(ABC): pass @abstractmethod - def random_pos(self): + def random_pos(self, n=1): """ Generate a random position in the detector volume based on a uniform event distribution + Parameter + --------- + n: int (default: 1) + Number of vertex directions to sample + Returns ------- tuple [m] (x, y, z) """ pass + def distribute_event(self, *pargs, **kwargs): + """ + Integrated event distribution method which also handles propagation + + Returns + ------- + vtx_pos: tuple + Position of the vertex in the interaction volume + vtx_dir: tuple + Direction of the vertex in the interaction volume + weight_correction: float + If the sampling requires a correction to the weight this factor is + unequal 1 + additional_events: awkward.highlevel.Array ( + E, Px, Py, Pz, x, y, z, pdgid) + Propagated / Additional Particles from decays (None if no propagation is done) + """ + return self.random_pos(), self.random_dir(), 1.0, None + @abstractmethod def header_entries(self): """ @@ -77,7 +114,7 @@ class DetectorVolume(ABC): Returns ------- - float [m^3] + float [m^3] """ return self._volume @@ -95,7 +132,7 @@ class DetectorVolume(ABC): class NoVolume(DetectorVolume): """ - Dummy volume to write out data w/o geometry + Dummy volume to write out data w/o geometry """ def __init__(self): @@ -110,16 +147,22 @@ class NoVolume(DetectorVolume): retdct[key] = value return retdct - def random_pos(self): - return (.0, .0, .0) + def random_pos(self, n=1): + if n == 1: + return np.zeros(3) + else: + return np.zeros((n, 3)) - def random_dir(self): - return (0, 1) + def random_dir(self, n=1): + if n == 1: + return (0, 1) + else: + return np.concatenate([np.zeros(n), np.ones(n)]).reshape((2, -1)).T class CanVolume(DetectorVolume): """ - Cylindrical detector geometry + Cylindrical detector geometry, only CAN / no propagation Parameters ---------- @@ -174,9 +217,233 @@ class CanVolume(DetectorVolume): self._volume, nevents) retdct[key] = value key = "fixedcan" - value = "0 0 {} {} {}".format(self._zmin, self._zmax, self._radius) + value = "{} {} {} {} {}".format(detector_center[0], detector_center[1], + self._zmin, self._zmax, self._radius) return retdct + def distribute_event(self, evt): + vtx_pos = self.random_pos() + vtx_dir = self.random_dir() + weight = 1 + evts = None + return vtx_pos, vtx_dir, weight, evts + + +class CylindricalVolume(DetectorVolume): + """ + Cylindrical detector geometry + + Parameters + ---------- + radius: float [m] (default: 403.5) + Radius of the interaction volume + sw_height: float [m] (default: xxx) + Height of the seawater in the interaction volume + sr_height: float [m] (default: xxx) + Height of the seabottom rock in the interaction volume + can_radius: float [m] (default: 0.0) + Cylinder bottom z position + can_zmin: float [m] (default: 0.0) + Cylinder bottom z position + can_zmax: float [m] (default: 476.5) + Cylinder top z position + detector_center: tuple [m] (default: (0.0, 0.0) ) + Detector center position in the xy-plane + zenith: float [1] (default: (-1.0, 1.0) ) + Zenith range given as cos(θ) + """ + + def __init__(self, + radius=500.0, + sw_height=650.0, + sr_height=100.0, + can_radius=200.4, + can_zmin=0.0, + can_zmax=350, + detector_center=(0., 0.), + zenith=(-1, 1)): + super().__init__() + self._detector_center = detector_center + # Interaction Volume + self._radius = radius + self._water_height = sw_height + self._rock_height = sr_height + # Can Volume + self._canradius = can_radius + self._canzmin = can_zmin + self._canzmax = can_zmax + # Direction + self._cosZmin = zenith[0] + self._cosZmax = zenith[1] + # Properties + self._solid_angle = 2 * np.pi * (self._cosZmax - self._cosZmin) + self._volume = self._calc_volume() + # PROPOSAL + self._pp_geometry = None + self._propagator = None + + def _calc_volume(self): + return np.pi * (self._water_height + self._rock_height) * np.power( + self._radius, 2) + + def random_pos(self, n=1): + r = self._radius * np.sqrt(np.random.uniform(0, 1, n)) + phi = np.random.uniform(0, 2 * np.pi, n) + pos_x = r * np.cos(phi) + self._detector_center[0] + pos_y = r * np.sin(phi) + self._detector_center[1] + pos_z = np.random.uniform(-self._rock_height, self._water_height, n) + pos = np.concatenate([pos_x, pos_y, pos_z]).reshape((3, -1)).T + if pos.shape[0] == 1: + return pos[0, :] + else: + return pos + + def in_can(self, pos): + """ + Check if position is inside the CAN + + Parameters + ---------- + pos: np.array + The positions which should be checked + + Return + ------ + boolean / np.array + """ + if type(pos) is tuple or pos.ndim == 1: + pos = np.reshape(pos, (-1, 3)) + zmask = (pos[:, 2] >= self._canzmin) & (pos[:, 2] <= self._canzmax) + r2 = (pos[:, 0] - self._coord_origin[0])**2 + \ + (pos[:, 1] - self._coord_origin[1])**2 + rmask = r2 < (self._canradius**2) + mask = zmask & rmask + if len(mask) == 1: + return mask[0] + else: + return mask + + def random_dir(self, n=1): + phi = np.random.uniform(0, 2 * np.pi, n) + cos_theta = np.random.uniform(self._cosZmin, self._cosZmax, n) + direction = np.concatenate([phi, cos_theta]).reshape((2, -1)).T + if direction.shape[0] == 1: + return direction[0, :] + else: + return direction + + def header_entries(self, nevents=0): + retdct = dict() + key = "genvol" + value = "{} {} {} {} {}".format(-self._rock_height, self._water_height, + self._radius, self._volume, nevents) + retdct[key] = value + key = "fixedcan" + value = "{} {} {} {} {}".format(self._detector_center[0], + self._detector_center[1], + self._canzmin, self._canzmax, + self._canradius) + return retdct + + def make_proposal_geometries(self): + """ + Setup the geometries for the propagation using PROPOSAL + """ + geometries = dict() + # General + center_x = self._detector_center[0] * M_TO_CM + center_y = self._detector_center[1] * M_TO_CM + # StopVolume + geometry_stop = pp.geometry.Sphere(pp.Cartesian3D(), 1e20) + geometry_stop.hierarchy = PROPOSAL_STOP_HIERARCHY_LEVEL + geometries["stop"] = geometry_stop + # CAN + can_zpos = (self._canzmin + self._canzmax) / 2 * M_TO_CM + geometry_can = pp.geometry.Cylinder( + pp.Cartesian3D(center_x, center_y, can_zpos), + (self._canzmax - self._canzmin) * M_TO_CM, + self._canradius * M_TO_CM, 0.) + geometry_can.hierarchy = PROPOSAL_CAN_HIERARCHY_LEVEL + geometries["can"] = geometry_can + # Interaction Volume + geometry_mantle = pp.geometry.Cylinder( + pp.Cartesian3D(center_x, center_y, can_zpos), + (self._canzmax - self._canzmin) * M_TO_CM, self._radius * M_TO_CM, + self._canradius * M_TO_CM) + geometry_mantle.hierarchy = PROPOSAL_PROPAGATION_HIERARCHY_LEVEL + geometries["can_mantle"] = geometry_mantle + if self._canzmin > 0: + floor_zpos = self._canzmin / 2 * M_TO_CM + geometry_floor = pp.geometry.Cylinder( + pp.Cartesian3D(center_x, center_y, floor_zpos), + (self._canzmax - self._canzmin) * M_TO_CM, + self._radius * M_TO_CM, 0.) + geometry_floor.hierarchy = PROPOSAL_PROPAGATION_HIERARCHY_LEVEL + geometries["can_floor"] = geometry_floor + if self._water_height > self._canzmax: + ceil_zpos = (self._water_height + self._canzmax) / 2 * M_TO_CM + geometry_ceil = pp.geometry.Cylinder( + pp.Cartesian3D(center_x, center_y, ceil_zpos), + (self._water_height - self._canzmax) * M_TO_CM, + self._radius * M_TO_CM, 0.) + geometry_ceil.hierarchy = PROPOSAL_PROPAGATION_HIERARCHY_LEVEL + geometries["can_ceiling"] = geometry_ceil + if self._rock_height > 0.: + sr_zpos = -self._rock_height / 2 * M_TO_CM + geometry_sr = pp.geometry.Cylinder( + pp.Cartesian3D(center_x, center_y, sr_zpos), + self._rock_height * M_TO_CM, self._radius * M_TO_CM, 0) + geometry_sr.hierarchy = PROPOSAL_PROPAGATION_HIERARCHY_LEVEL + geometries["sr"] = geometry_sr + return geometries + + @staticmethod + def _addparticles(dct, particle_infos): + for prtcl in particle_infos: + dct['barcode'].append(prtcl.type) + dct['E'].append(prtcl.energy) + dct['x'].append(prtcl.position.x / 100) + dct['y'].append(prtcl.position.y / 100) + dct['z'].append(prtcl.position.z / 100) + dct['Px'].append(prtcl.direction.x * prtcl.momentum / 1e3) + dct['Py'].append(prtcl.direction.y * prtcl.momentum / 1e3) + dct['Pz'].append(prtcl.direction.z * prtcl.momentum / 1e3) + dct['deltaT'].append(prtcl.time) + + def distribute_event(self, evt): + # NC -> doesn't require any propagation + if abs(evt.process_ID) == 3 or evt.flavor_ID == 1: + vtx_pos = self.random_pos() + vtx_dir = self.random_dir() + weight = 1 + evts = None + return vtx_pos, vtx_dir, weight, evts + + if not self._pp_geometry: + self._pp_geometry = self.make_proposal_geometries() + + if not self._propagator: + self._propagator = Propagator([13, -13, 15, -15], + self._pp_geometry) + + charged_lepton_type = np.sign(evt.process_ID) * (2 * evt.flavor_ID + 9) + + samples = 0 + lepout_dir = np.array([evt.lepOut_Px, evt.lepOut_Py, evt.lepOut_Pz]) + + while True: + samples += 1 + vtx_pos = self.random_pos() + vtx_angles = self.random_dir() + if self.in_can(vtx_pos) and evt.flavor_ID == 2: + return vtx_pos, vtx_angles, samples, None + R = Rotation.from_euler("yz", vtx_angles) + particles = self._propagator.propagate_to_can( + charged_lepton_type, evt.lepOut_E, vtx_pos, + R.apply(lepout_dir)) + if not particles is None: + return vtx_pos, vtx_angles, samples, particles + class SphericalVolume(DetectorVolume): """ @@ -205,20 +472,29 @@ class SphericalVolume(DetectorVolume): def _calc_volume(self): return 4 / 3 * np.pi * np.power(self._radius, 3) - def random_pos(self): - r = self._radius * np.power(np.random.random(), 1 / 3) - phi = np.random.uniform(0, 2 * np.pi) - cosTheta = np.random.uniform(-1, 1) - pos_x = r * np.cos(phi) * np.sqrt(1 - np.power(cosTheta, 2)) - pos_y = r * np.sin(phi) * np.sqrt(1 - np.power(cosTheta, 2)) - pos_z = r * cosTheta - pos = (pos_x, pos_y, pos_z) - return tuple(np.add(self._coord_origin, pos)) - - def random_dir(self): - phi = np.random.uniform(0, 2 * np.pi) - cos_theta = np.random.uniform(self._cosZmin, self._cosZmax) - return (phi, cos_theta) + def random_pos(self, n=1): + r = self._radius * np.power(np.random.random(n), 1 / 3) + phi = np.random.uniform(0, 2 * np.pi, n) + cosTheta = np.random.uniform(-1, 1, n) + pos_x = r * np.cos(phi) * np.sqrt( + 1 - np.power(cosTheta, 2)) + self._coord_origin[0] + pos_y = r * np.sin(phi) * np.sqrt( + 1 - np.power(cosTheta, 2)) + self._coord_origin[1] + pos_z = r * cosTheta + self._coord_origin[2] + pos = np.concatenate([pos_x, pos_y, pos_z]).reshape((3, -1)).T + if pos.shape[0] == 1: + return pos[0, :] + else: + return pos + + def random_dir(self, n=1): + phi = np.random.uniform(0, 2 * np.pi, n) + cos_theta = np.random.uniform(self._cosZmin, self._cosZmax, n) + direction = np.concatenate([phi, cos_theta]).reshape((2, -1)).T + if direction.shape[0] == 1: + return direction[0, :] + else: + return direction def header_entries(self, nevents=0): retdct = dict() diff --git a/km3buu/output.py b/km3buu/output.py index 357cc10014e5f0c8f31bbaae3adef5baea8aa30f..109098616a69e7c5e0a5cc0abe0e945de1d2f7b3 100644 --- a/km3buu/output.py +++ b/km3buu/output.py @@ -30,7 +30,7 @@ import km3io from .physics import visible_energy_fraction from .jobcard import Jobcard, read_jobcard, PDGID_LOOKUP -from .geometry import DetectorVolume, CanVolume +from .geometry import * from .config import Config, read_default_media_compositions from .__version__ import version @@ -540,7 +540,6 @@ class GiBUUOutput: self.flux_data["energy"][mask], self.flux_data["flux"][mask], p0=[1, -1]) - self._flux_index = popt[1] @property @@ -552,7 +551,7 @@ def write_detector_file(gibuu_output, ofile="gibuu.offline.root", no_files=1, run_number=1, - geometry=CanVolume(), + geometry=CylindricalVolume(), livetime=3.156e7, propagate_tau=True): # pragma: no cover """ @@ -576,8 +575,8 @@ def write_detector_file(gibuu_output, if not isinstance(geometry, DetectorVolume): raise TypeError("Geometry needs to be a DetectorVolume") - def add_particles(particle_data, pos_offset, rotation, start_mc_trk_id, - timestamp, status): + def add_particles(particle_data, start_mc_trk_id, timestamp, status, + mother_id): nonlocal evt for i in range(len(particle_data.E)): trk = ROOT.Trk() @@ -585,19 +584,13 @@ def write_detector_file(gibuu_output, mom = np.array([ particle_data.Px[i], particle_data.Py[i], particle_data.Pz[i] ]) - p_dir = rotation.apply(mom / np.linalg.norm(mom)) - try: - prtcl_pos = np.array([ - particle_data.x[i], particle_data.y[i], particle_data.z[i] - ]) - prtcl_pos = rotation.apply(prtcl_pos) - trk.pos.set(*np.add(pos_offset, prtcl_pos)) - trk.t = timestamp + particle_data.deltaT[i] * 1e9 - except AttributeError: - trk.pos.set(*pos_offset) - trk.t = timestamp + p_dir = mom / np.linalg.norm(mom) trk.dir.set(*p_dir) - trk.mother_id = 0 + prtcl_pos = np.array( + [particle_data.x[i], particle_data.y[i], particle_data.z[i]]) + trk.pos.set(*prtcl_pos) + trk.t = timestamp + particle_data.deltaT[i] + trk.mother_id = mother_id trk.type = int(particle_data.barcode[i]) trk.E = particle_data.E[i] trk.status = status @@ -627,11 +620,6 @@ def write_detector_file(gibuu_output, bjorkenx = event_data.Bx bjorkeny = event_data.By - tau_secondaries = None - if propagate_tau and abs(nu_type) == 16 and ichan == 2: - from .propagation import propagate_lepton - tau_secondaries = propagate_lepton(event_data, np.sign(nu_type) * 15) - media = read_default_media_compositions() density = media["SeaWater"]["density"] # [g/cm^3] element = mendeleev.element(gibuu_output.Z) @@ -663,6 +651,8 @@ def write_detector_file(gibuu_output, header_dct["primary"] = "{:d}".format(nu_type) header_dct["start_run"] = str(run_number) + event_times = np.random.uniform(0, livetime, len(event_data)) + for i in range(no_files): start_id = 0 stop_id = len(event_data) @@ -677,15 +667,20 @@ def write_detector_file(gibuu_output, outfile = ROOT.TFile.Open(tmp_filename, "RECREATE") tree = ROOT.TTree("E", "KM3NeT Evt Tree") tree.Branch("Evt", evt, 32000, 4) - mc_trk_id = 0 - - for mc_event_id, event in enumerate(event_data[start_id:stop_id]): + from tqdm import tqdm + for mc_event_id, event in tqdm(enumerate( + event_data[start_id:stop_id])): + mc_trk_id = 0 + total_id = start_id + mc_event_id evt.clear() evt.id = mc_event_id evt.mc_run_id = mc_event_id + # Vertex Positioning & Propagation + vtx_pos, vtx_angles, samples, prop_particles = geometry.distribute_event( + event) # Weights evt.w.push_back(geometry.volume) # w1 (can volume) - evt.w.push_back(w2[start_id + mc_event_id]) # w2 + evt.w.push_back(w2[total_id] / samples) # w2 evt.w.push_back(-1.0) # w3 (= w2*flux) # Event Information (w2list) evt.w2list.resize(W2LIST_LENGTH) @@ -711,10 +706,9 @@ def write_detector_file(gibuu_output, evt.w2list[km3io.definitions.w2list_km3buu["W2LIST_KM3BUU_P_EARTH"]] = np.nan evt.w2list[km3io.definitions.w2list_km3buu["W2LIST_KM3BUU_WATER_INT_LEN"]] = np.nan - # Vertex Position - vtx_pos = np.array(geometry.random_pos()) + timestamp = event_times[total_id] # Direction - phi, cos_theta = geometry.random_dir() + phi, cos_theta = vtx_angles sin_theta = np.sqrt(1 - cos_theta**2) dir_x = np.cos(phi) * sin_theta @@ -725,8 +719,6 @@ def write_detector_file(gibuu_output, theta = np.arccos(cos_theta) R = Rotation.from_euler("yz", [theta, phi]) - timestamp = np.random.uniform(0, livetime) - nu_in_trk = ROOT.Trk() nu_in_trk.id = mc_trk_id mc_trk_id += 1 @@ -736,7 +728,8 @@ def write_detector_file(gibuu_output, nu_in_trk.dir.set(*direction) nu_in_trk.E = event.lepIn_E nu_in_trk.t = timestamp - nu_in_trk.status = km3io.definitions.trkmembers["TRK_ST_PRIMARYNEUTRINO"] + nu_in_trk.status = km3io.definitions.trkmembers[ + "TRK_ST_PRIMARYNEUTRINO"] evt.mc_trks.push_back(nu_in_trk) lep_out_trk = ROOT.Trk() @@ -751,21 +744,40 @@ def write_detector_file(gibuu_output, lep_out_trk.E = event.lepOut_E lep_out_trk.t = timestamp - if tau_secondaries is not None: - lep_out_trk.status = km3io.definitions.trkmembers["TRK_ST_UNDEFINED"] + if prop_particles is not None: + lep_out_trk.status = km3io.definitions.trkmembers[ + "TRK_ST_PROPLEPTON"] + generator_particle_state = km3io.definitions.trkmembers[ + "TRK_ST_UNDEFINED"] else: - lep_out_trk.status = km3io.definitions.trkmembers["TRK_ST_FINALSTATE"] + lep_out_trk.status = km3io.definitions.trkmembers[ + "TRK_ST_FINALSTATE"] + generator_particle_state = km3io.definitions.trkmembers[ + "TRK_ST_FINALSTATE"] evt.mc_trks.push_back(lep_out_trk) - if tau_secondaries is not None: - event_tau_sec = tau_secondaries[mc_event_id] - add_particles(event_tau_sec, vtx_pos, R, mc_trk_id, timestamp, - km3io.definitions.trkmembers["TRK_ST_FINALSTATE"]) - mc_trk_id += len(event_tau_sec.E) + event.x = np.ones(len(event.E)) * vtx_pos[0] + event.y = np.ones(len(event.E)) * vtx_pos[1] + event.z = np.ones(len(event.E)) * vtx_pos[2] + + directions = R.apply(np.array([event.Px, event.Py, event.Pz]).T) + event.Px = directions[:, 0] + event.Py = directions[:, 1] + event.Pz = directions[:, 2] + + event.deltaT = np.zeros(len(event.E)) + + add_particles(event, mc_trk_id, timestamp, + generator_particle_state, 0) + mc_trk_id += len(event.E) + + if prop_particles is not None: + add_particles( + prop_particles, mc_trk_id, timestamp, + km3io.definitions.trkmembers["TRK_ST_FINALSTATE"], 1) + mc_trk_id += len(prop_particles.E) - add_particles(event, vtx_pos, R, mc_trk_id, timestamp, - km3io.definitions.trkmembers["TRK_ST_FINALSTATE"]) tree.Fill() for k, v in geometry.header_entries(mc_event_id + 1).items(): diff --git a/km3buu/propagation.py b/km3buu/propagation.py index 61b59a9cab0137a0ec79af8a2dfc3f9a0aa669a3..b1874ba1a198abb72399ad9506c74a992959c64a 100644 --- a/km3buu/propagation.py +++ b/km3buu/propagation.py @@ -22,6 +22,12 @@ import pathlib from .config import Config +M_TO_CM = 1e2 + +itp_table_path = Config().proposal_itp_tables +pathlib.Path(itp_table_path).mkdir(parents=True, exist_ok=True) +pp.InterpolationSettings.tables_path = itp_table_path + PROPOSAL_LEPTON_DEFINITIONS = { 11: pp.particle.EMinusDef, -11: pp.particle.EPlusDef, @@ -37,16 +43,21 @@ PROPOSAL_LEPTON_DEFINITIONS = { -16: pp.particle.NuTauBarDef } -PROPOSAL_TARGET = pp.medium.AntaresWater() +PROPOSAL_TARGET_WATER = pp.medium.AntaresWater() +PROPOSAL_TARGET_ROCK = pp.medium.StandardRock() + +PROPOSAL_STOP_HIERARCHY_LEVEL = 0 +PROPOSAL_PROPAGATION_HIERARCHY_LEVEL = 4 +PROPOSAL_CAN_HIERARCHY_LEVEL = 2 +PROPOSAL_MUON_STOP_CONDITION = 3 +PROPOSAL_TAU_STOP_CONDITION = 1 -def _setup_propagator(max_distance, particle_definition): - itp_table_path = Config().proposal_itp_tables - pathlib.Path(itp_table_path).mkdir(parents=True, exist_ok=True) - pp.InterpolationSettings.tables_path = itp_table_path + +def _setup_utility(particle_definition, target): crosssection = pp.crosssection.make_std_crosssection( particle_def=particle_definition, - target=PROPOSAL_TARGET, + target=target, interpolate=True, cuts=pp.EnergyCutSettings(np.inf, 0.05, False)) collection = pp.PropagationUtilityCollection() @@ -54,73 +65,120 @@ def _setup_propagator(max_distance, particle_definition): collection.interaction = pp.make_interaction(crosssection, True) collection.decay = pp.make_decay(crosssection, particle_definition, True) collection.time = pp.make_time(crosssection, particle_definition, True) + return pp.PropagationUtility(collection=collection) - utility = pp.PropagationUtility(collection=collection) - geometry = pp.geometry.Sphere(pp.Cartesian3D(), max_distance) - density_distr = pp.density_distribution.density_homogeneous( - PROPOSAL_TARGET.mass_density) +def setup_propagator(particle_definition, proposal_geometries): + utility_sw = _setup_utility(particle_definition, PROPOSAL_TARGET_WATER) + utility_sr = _setup_utility( + particle_definition, + PROPOSAL_TARGET_ROCK) if "sr" in proposal_geometries.keys() else None - propagator = pp.Propagator(particle_definition, - [(geometry, utility, density_distr)]) + density_sw = pp.density_distribution.density_homogeneous( + PROPOSAL_TARGET_WATER.mass_density) + density_sr = pp.density_distribution.density_homogeneous( + PROPOSAL_TARGET_ROCK.mass_density) - return propagator + sectors = [(geometry, utility_sw, density_sw) + for k, geometry in proposal_geometries.items() if k != "sr"] + if "sr" in proposal_geometries.keys(): + sectors.append((proposal_geometries["sr"], utility_sr, density_sr)) + propagator = pp.Propagator(particle_definition, sectors) -def propagate_lepton(lepout_data, pdgid): - """ - Lepton propagation based on PROPOSAL - - Parameters - ---------- - lepout_data: awkward.highlevel.Array - Lepton data in the GiBUU output shape containing the fields - 'lepOut_E, lepOut_Px, lepOut_Py, lepOut_Pz' - pdgid: - The pdgid of the propagated lepton - - Returns - ------- - awkward.highlevel.Array (E, Px, Py, Pz, x, y, z) - """ - lepton_info = Particle.from_pdgid(pdgid) - prop_range = const.c * lepton_info.lifetime * 1e11 * np.max( - lepout_data.lepOut_E) / lepton_info.mass #[cm] - - lepton_def = PROPOSAL_LEPTON_DEFINITIONS[pdgid]() + return propagator - propagator = _setup_propagator(10 * prop_range, lepton_def) - propagated_data = defaultdict(list) +class Propagator(object): + + def __init__(self, pdgids, geometry): + self._geometry = geometry + self._pdgids = pdgids + self._pp_propagators = dict() + self._setup() + + def _setup(self): + for p in self._pdgids: + self._pp_propagators[p] = setup_propagator( + PROPOSAL_LEPTON_DEFINITIONS[p](), self._geometry) + + # @geometry.setter + # def geometry(self, geodct): + # self._geometry = geodct + # # Update propagators + # self._setup() + + @staticmethod + def _addparticles(dct, particle_infos): + for prtcl in particle_infos: + dct['barcode'].append(prtcl.type) + dct['E'].append(prtcl.energy) + dct['x'].append(prtcl.position.x / M_TO_CM) + dct['y'].append(prtcl.position.y / M_TO_CM) + dct['z'].append(prtcl.position.z / M_TO_CM) + dct['Px'].append(prtcl.direction.x * prtcl.momentum / 1e3) + dct['Py'].append(prtcl.direction.y * prtcl.momentum / 1e3) + dct['Pz'].append(prtcl.direction.z * prtcl.momentum / 1e3) + dct['deltaT'].append(prtcl.time * 1e-9) + + def propagate_particle(self, prtcl_state): + stop_condition = PROPOSAL_MUON_STOP_CONDITION if abs( + prtcl_state.type) == 13 else PROPOSAL_TAU_STOP_CONDITION + return self._pp_propagators[prtcl_state.type].propagate( + prtcl_state, hierarchy_condition=stop_condition) + + def propagate_to_can(self, lep_pdgid, lep_E, lep_pos, lep_dir): + """ + Lepton propagation to can based on PROPOSAL + + Parameters + ---------- + vtx_pos: np.array + Vertex positions of the given events + lep_E: np.array + Outgoing lepton energy + lep_dir: np.array + Lepton direction positions of the given events + pdgid: + The pdgid of the propagated lepton + + Returns + ------- + awkward.highlevel.Array (barcode, deltaT, E, Px, Py, Pz, x, y, z) + """ + if not lep_pdgid in self._pdgids: + return None - for entry in lepout_data: init_state = pp.particle.ParticleState() - init_state.energy = entry.lepOut_E * 1e3 - init_state.position = pp.Cartesian3D(0, 0, 0) - init_state.direction = pp.Cartesian3D(entry.lepOut_Px, entry.lepOut_Py, - entry.lepOut_Pz) + init_state.type = lep_pdgid + init_state.energy = lep_E * 1e3 + init_state.direction = pp.Cartesian3D(lep_dir) init_state.direction.normalize() - track = propagator.propagate(init_state, 5 * prop_range) - secondaries = track.decay_products() - - E = [s.energy / 1e3 for s in secondaries] - pdgid = [p.type for p in secondaries] - Px = [p.direction.x * p.momentum / 1e3 for p in secondaries] - Py = [p.direction.y * p.momentum / 1e3 for p in secondaries] - Pz = [p.direction.z * p.momentum / 1e3 for p in secondaries] - x = [p.position.x / 100 for p in secondaries] - y = [p.position.y / 100 for p in secondaries] - z = [p.position.z / 100 for p in secondaries] - dt = [p.time for p in secondaries] - - propagated_data["E"].append(E) - propagated_data["Px"].append(Px) - propagated_data["Py"].append(Py) - propagated_data["Pz"].append(Pz) - propagated_data["barcode"].append(pdgid) - propagated_data["x"].append(x) - propagated_data["y"].append(y) - propagated_data["z"].append(z) - propagated_data["deltaT"].append(dt) - - return ak.Array(propagated_data) + init_state.position = pp.Cartesian3D(*(lep_pos * M_TO_CM)) + + particle_dct = defaultdict(list) + + track = self.propagate_particle(init_state) + fsi = track.final_state() + decay = track.decay_products() + + if self._geometry['can'].is_inside(fsi.position, fsi.direction): + if len(decay) > 0: + self._addparticles(particle_dct, decay) + else: + self._addparticles(particle_dct, [fsi]) + else: + for d in decay: + if d.type in self._pp_propagators.keys(): + track = self.propagate_particle(d) + fsi = track.final_state() + decay = track.decay_products() + if self._geometry['can'].is_inside(fsi.position, + fsi.direction): + if len(decay) > 0: + self._addparticles(particle_dct, decay) + else: + self._addparticles(particle_dct, [fsi]) + if len(particle_dct["E"]) == 0: + return None + return ak.Array(particle_dct)