diff --git a/km3buu/jobcard.py b/km3buu/jobcard.py index 0e5ddaaf7e16f0df9ae39803ec9a4a7935c4961b..18a44e47042be52e9d15e6fe5e08467991fa01b6 100644 --- a/km3buu/jobcard.py +++ b/km3buu/jobcard.py @@ -111,8 +111,8 @@ def generate_neutrino_jobcard(events, Interaction channel ["CC", "NC", "antiCC", "antiNC"] flavour: str Flavour ["electron", "muon", "tau"] - energy: tuple - Initial energy range of the neutrino in GeV + energy: float, tuple + Initial energy or energy range (emin, emax) of the primary neutrino in GeV target: (int, int) (Z, A) describing the target nucleon write_pert: boolean (default: True) @@ -146,8 +146,11 @@ def generate_neutrino_jobcard(events, jc["input"]["numEnsembles"] = run_events jc["input"]["num_runs_SameEnergy"] = runs # ENERGY - jc["nl_neutrino_energyflux"]["eflux_min"] = energy[0] - jc["nl_neutrino_energyflux"]["eflux_max"] = energy[1] + if isinstance(energy, tuple): + jc["nl_neutrino_energyflux"]["eflux_min"] = energy[0] + jc["nl_neutrino_energyflux"]["eflux_max"] = energy[1] + else: + jc["nl_sigmamc"]["enu"] = energy # DECAY if do_decay: for i in DECAYED_HADRONS: @@ -155,7 +158,7 @@ def generate_neutrino_jobcard(events, jc["ModifyParticles"][key] = 4 jc["pythia"]["MDCY(102,1)"] = 1 # FLUX - if fluxfile is not None: + if fluxfile is not None and isinstance(energy, tuple): if not isfile(fluxfile): raise IOError("Fluxfile {} not found!") jc["neutrino_induced"]["nuexp"] = 99 diff --git a/km3buu/output.py b/km3buu/output.py index 6bcade60a33e93b3377831a1e0f4f7c7e7bfc005..cc975cff58d79c4d998524bd773e1e0cb979662d 100644 --- a/km3buu/output.py +++ b/km3buu/output.py @@ -216,9 +216,18 @@ class GiBUUOutput: ] self._read_xsection_file() self._read_root_output() - self._read_flux_file() self._read_jobcard() + self.flux_data = None + self._min_energy = np.nan + self._max_energy = np.nan + self._generated_events = -1 + + try: + self._read_flux_file() + except OSError: + self._read_single_energy() + def _read_root_output(self): root_pert_regex = re.compile(ROOT_PERT_FILENAME) self.root_pert_files = list( @@ -247,46 +256,64 @@ class GiBUUOutput: else: self.jobcard = None + def _read_single_energy(self): + root_tupledata = self.arrays + energies = np.array(root_tupledata.lepIn_E) + if np.std(energies) > 1e-10: + raise NotImplementedError( + "Energy not constant; run data cannot be interpreted") + self._min_energy = np.mean(energies) + self._max_energy = self._max_energy + num_ensembles = int(self.jobcard["input"]["numensembles"]) + num_runs = int(self.jobcard["input"]["num_runs_sameenergy"]) + self._generated_events = num_ensembles * num_runs + def _read_flux_file(self): fpath = join(self._data_path, FLUXDESCR_FILENAME) self.flux_data = np.loadtxt(fpath, dtype=FLUX_INFORMATION_DTYPE) self.flux_interpolation = UnivariateSpline(self.flux_data["energy"], self.flux_data["events"]) + self._energy_min = np.min(self.flux_data["energy"]) + self._energy_max = np.max(self.flux_data["energy"]) + self._generated_events = int(np.sum(self.flux_data["events"])) def _event_xsec(self, root_tupledata): weights = np.array(root_tupledata.weight) - total_events = np.sum(self.flux_data["events"]) + total_events = self._generated_events n_files = len(self.root_pert_files) xsec = np.divide(total_events * weights, n_files) return xsec @property def mean_xsec(self): - root_tupledata = self.arrays - energies = np.array(root_tupledata.lepIn_E) - weights = self._event_xsec(root_tupledata) - Emin = np.min(energies) - Emax = np.max(energies) - xsec, energy_bins = np.histogram(energies, - weights=weights, - bins=np.logspace( - np.log10(Emin), np.log10(Emax), - 15)) - deltaE = np.mean(self.flux_data["energy"][1:] - - self.flux_data["energy"][:-1]) - bin_events = np.array([ - self.flux_interpolation.integral(energy_bins[i], - energy_bins[i + 1]) / deltaE - for i in range(len(energy_bins) - 1) - ]) - x = (energy_bins[1:] + energy_bins[:-1]) / 2 - y = xsec / bin_events / x - xsec_interp = interp1d(x, - y, - kind="linear", - fill_value=(y[0], y[-1]), - bounds_error=False) - return lambda e: xsec_interp(e) * e + if self.flux_data is None: + return lambda energy: self.xsection["sum"] + else: + root_tupledata = self.arrays + energies = np.array(root_tupledata.lepIn_E) + weights = self._event_xsec(root_tupledata) + Emin = np.min(energies) + Emax = np.max(energies) + xsec, energy_bins = np.histogram(energies, + weights=weights, + bins=np.logspace( + np.log10(Emin), + np.log10(Emax), 15)) + deltaE = np.mean(self.flux_data["energy"][1:] - + self.flux_data["energy"][:-1]) + bin_events = np.array([ + self.flux_interpolation.integral(energy_bins[i], + energy_bins[i + 1]) / deltaE + for i in range(len(energy_bins) - 1) + ]) + x = (energy_bins[1:] + energy_bins[:-1]) / 2 + y = xsec / bin_events / x + xsec_interp = interp1d(x, + y, + kind="linear", + fill_value=(y[0], y[-1]), + bounds_error=False) + return lambda e: xsec_interp(e) * e def w2weights(self, volume, target_density, solid_angle): """ @@ -303,18 +330,19 @@ class GiBUUOutput: """ root_tupledata = self.arrays - energy_min = np.min(self.flux_data["energy"]) - energy_max = np.max(self.flux_data["energy"]) - energy_phase_space = self.flux_interpolation.integral( - energy_min, energy_max) xsec = self._event_xsec( root_tupledata ) * self.A #xsec_per_nucleon * no_nucleons in the core - inv_gen_flux = np.power( - self.flux_interpolation(root_tupledata.lepIn_E), -1) - phase_space = solid_angle * energy_phase_space + if self.flux_data is not None: + inv_gen_flux = np.power( + self.flux_interpolation(root_tupledata.lepIn_E), -1) + energy_phase_space = self.flux_interpolation.integral( + self._energy_min, self._energy_max) + energy_factor = energy_phase_space * inv_gen_flux + else: + energy_factor = 1 env_factor = volume * SECONDS_PER_YEAR - retval = env_factor * phase_space * inv_gen_flux * xsec * 10**-42 * target_density + retval = env_factor * solid_angle * energy_factor * xsec * 10**-42 * target_density return retval @staticmethod @@ -415,15 +443,15 @@ class GiBUUOutput: @property def energy_min(self): - return np.min(self.flux_data["energy"]) + return self._min_energy @property def energy_max(self): - return np.max(self.flux_data["energy"]) + return self._max_energy @property def generated_events(self): - return int(np.sum(self.flux_data["events"])) + return self._generated_events def write_detector_file(gibuu_output, @@ -591,7 +619,8 @@ def write_detector_file(gibuu_output, 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) + add_particles(event_tau_sec, vtx_pos, R, mc_trk_id, timestamp, + PARTICLE_MC_STATUS["StableFinalState"]) mc_trk_id += len(event_tau_sec.E) else: lep_out_trk = ROOT.Trk() diff --git a/km3buu/tests/test_jobcard.py b/km3buu/tests/test_jobcard.py index ff5c04d69b6b7688ca64ff32657e1f2aa93faa4e..66b1162161db75a41876da0375604cdee621821f 100644 --- a/km3buu/tests/test_jobcard.py +++ b/km3buu/tests/test_jobcard.py @@ -45,7 +45,7 @@ class TestJobcard(unittest.TestCase): assert ctnt.find(expected_line) == -1 -class TestNeutrinoJobcard(unittest.TestCase): +class TestNeutrinoEnergyRangeJobcard(unittest.TestCase): def setUp(self): self.test_fluxfile = TemporaryFile() self.test_Z = np.random.randint(1, 100) @@ -93,3 +93,37 @@ class TestNeutrinoJobcard(unittest.TestCase): def test_photon_propagation_flag(self): self.assertEqual(self.test_jobcard["insertion"]["propagateNoPhoton"], not self.photon_propagation_flag) + + +class TestNeutrinoSingleEnergyJobcard(unittest.TestCase): + def setUp(self): + self.test_fluxfile = TemporaryFile() + self.test_Z = np.random.randint(1, 100) + self.test_A = np.random.randint(self.test_Z, 100) + self.test_energy = np.random.uniform(0.0, 100.0) + self.photon_propagation_flag = np.random.choice([True, False]) + self.do_decay = np.random.choice([True, False]) + self.test_jobcard = generate_neutrino_jobcard( + 1000, + "CC", + "electron", + self.test_energy, (self.test_Z, self.test_A), + do_decay=self.do_decay, + photon_propagation=self.photon_propagation_flag, + fluxfile=self.test_fluxfile.name, + input_path="/test") + + def test_input_path(self): + self.assertEqual("/test", self.test_jobcard["input"]["path_to_input"]) + + def test_target(self): + self.assertEqual(self.test_Z, self.test_jobcard["target"]["target_Z"]) + self.assertEqual(self.test_A, self.test_jobcard["target"]["target_A"]) + + def test_energy(self): + self.assertAlmostEqual(self.test_energy, + self.test_jobcard["nl_sigmamc"]["enu"]) + + def test_photon_propagation_flag(self): + self.assertEqual(self.test_jobcard["insertion"]["propagateNoPhoton"], + not self.photon_propagation_flag)