diff --git a/km3buu/geometry.py b/km3buu/geometry.py new file mode 100644 index 0000000000000000000000000000000000000000..34149557470422cc51610b4b5e9fe409d49b4e4f --- /dev/null +++ b/km3buu/geometry.py @@ -0,0 +1,145 @@ +# Filename: geometry.py +""" +Detector geometry related functionalities +""" + +__author__ = "Johannes Schumann" +__copyright__ = "Copyright 2021, Johannes Schumann and the KM3NeT collaboration." +__credits__ = [] +__license__ = "MIT" +__maintainer__ = "Johannes Schumann" +__email__ = "jschumann@km3net.de" +__status__ = "Development" + +import numpy as np +from abc import ABC, abstractmethod + + +class DetectorVolume(ABC): + """ + Detector geometry class + """ + def __init__(self): + self._volume = -1.0 + self._coord_origin = (0., 0., 0.) + + @abstractmethod + def random_pos(self): + """ + Generate a random position in the detector volume based on a uniform + event distribution + + Returns + ------- + tuple [m] (x, y, z) + """ + pass + + @abstractmethod + def header_entry(self): + """ + Returns the header information for the detector volume geometry + + Returns + ------- + tuple (header_key, header_information) + """ + pass + + @property + def volume(self): + """ + Detector volume + + Returns + ------- + float [m^3] + """ + return self._volume + + @property + def coord_origin(self): + """ + Coordinate origin + + Returns + ------- + tuple [m] (x, y, z) + """ + return self._coord_origin + + +class CanVolume(DetectorVolume): + """ + Cylindrical detector geometry + + Parameters + ---------- + radius: float [m] (default: 403.5) + Cylinder radius given in metres + zmin: float [m] (default: 0.0) + Cylinder bottom z position + zmax: float [m] (default: 476.5) + Cylinder top z position + """ + def __init__(self, radius=403.4, zmin=0.0, zmax=476.5): + super().__init__() + self._radius = radius + self._zmin = zmin + self._zmax = zmax + self._volume = self._calc_volume() + + def _calc_volume(self): + return np.pi * (self._zmax - self._zmin) * np.power(self._radius, 2) + + def random_pos(self): + r = self._radius * np.sqrt(np.random.uniform(0, 1)) + phi = np.random.uniform(0, 2 * np.pi) + pos_x = r * np.cos(phi) + pos_y = r * np.sin(phi) + pos_z = np.random.uniform(self._zmin, self._zmax) + return (pos_x, pos_y, pos_z) + + def header_entry(self): + key = "can" + value = "{} {} {}".format(self._zmin, self._zmax, self._radius) + return key, value + + +class SphericalVolume(DetectorVolume): + """ + Spherical detector geometry + + Parameters + ---------- + radius: float [m] + The radius of the sphere + center: tuple [m] + Coordinate center of the sphere + (x, y, z) + """ + def __init__(self, radius, coord_origin=(0, 0, 0)): + super().__init__() + self._radius = radius + self._coord_origin = coord_origin + self._volume = self._calc_volume() + + def _calc_volume(self): + return 4 / 3 * np.pi * np.power(self._radius, 3) + + def random_pos(self): + r = np.power(self._radius, 1 / 3) + phi = np.random.uniform(0, 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 header_entry(self): + key = "sphere" + value = "radius: {} center_x: {} center_y: {} center_z: {}".format( + self._radius, self._coord_origin[0], self._coord_origin[1], + self._coord_origin[2]) + return key, value diff --git a/km3buu/output.py b/km3buu/output.py index 507d5d4dba77e726df06c6e06abf784cd1ac42f2..23a2b772a1bc3d729ba77517e83e4ccc9e46960d 100644 --- a/km3buu/output.py +++ b/km3buu/output.py @@ -27,6 +27,7 @@ import mendeleev from datetime import datetime from .jobcard import Jobcard, read_jobcard, PDGID_LOOKUP +from .geometry import DetectorVolume, CanVolume from .config import Config, read_default_media_compositions from .__version__ import version @@ -91,29 +92,16 @@ ROOTTUPLE_KEY = "RootTuple" EMPTY_KM3NET_HEADER_DICT = { "start_run": "0", - "XSecFile": "", "drawing": "volume", - "detector": "", "depth": "2475.0", - "muon_desc_file": "", "target": "", - "cut_primary": "0 0 0 0", - "cut_seamuon": "0 0 0 0", - "cut_in": "0 0 0 0", "cut_nu": "0 0 0 0", "spectrum": "0", "can": "0 0 0", "flux": "0 0 0", - "fixedcan": "0 0 0 0 0", - "genvol": "0 0 0 0 0", "coord_origin": "0 0 0", - "genhencut": "0 0", "norma": "0 0", - "livetime": "0 0", - "seabottom": "0", - "DAQ": "0", "tgen": "0", - "primary": "0", "simul": "" } @@ -360,7 +348,7 @@ class GiBUUOutput: def write_detector_file(gibuu_output, ofile="gibuu.offline.root", - can=(0, 476.5, 403.4), + geometry=CanVolume(), livetime=3.156e7, propagate_tau=True): # pragma: no cover """ @@ -372,12 +360,13 @@ def write_detector_file(gibuu_output, Output object which wraps the information from the GiBUU output files ofile: str Output filename - can: tuple - The can dimensions which are used to distribute the events - (z_min, z_max, radius) + geometry: DetectorVolume + The detector geometry which should be used livetime: float The data livetime """ + if not isinstance(geometry, DetectorVolume): + raise TypeError("Geometry needs to be a DetectorVolume") evt = ROOT.Evt() outfile = ROOT.TFile.Open(ofile, "RECREATE") @@ -439,28 +428,29 @@ def write_detector_file(gibuu_output, media = read_default_media_compositions() density = media["SeaWater"]["density"] - symbol = mendeleev.element(gibuu_output.Z).symbol - target = media["SeaWater"]["elements"][symbol] + element = mendeleev.element(gibuu_output.Z) + target = media["SeaWater"]["elements"][element.symbol] target_density = 1e3 * density * target[1] targets_per_volume = target_density * (1e3 * constants.Avogadro / target[0].atomic_weight) - can_volume = np.pi * (can[1] - can[0]) * np.power(can[2], 2) - w2 = gibuu_output.w2weights(can_volume, targets_per_volume, 4 * np.pi) + w2 = gibuu_output.w2weights(geometry.volume, targets_per_volume, 4 * np.pi) head = ROOT.Head() header_dct = EMPTY_KM3NET_HEADER_DICT.copy() - timestamp = datetime.now() - header_dct["simul"] = "KM3BUU {} {}".format( - version, timestamp.strftime("%Y%m%d %H%M%S")) - header_dct["can"] = "{:.1f} {:.1f} {:.1f}".format(*can) - header_dct["tgen"] = "{:.1f}".format(livetime) + header_dct["target"] = element.name + key, value = geometry.header_entry() + header_dct[key] = value + header_dct["coord_origin"] = "{} {} {}".format(*geometry.coord_origin) header_dct["flux"] = "{:d} 0 0".format(nu_type) - header_dct["genvol"] = "0 {:.1f} {:.1f} {:.1f} {:d}".format( - can[1], can[2], can_volume, gibuu_output.generated_events) header_dct["cut_nu"] = "{:.2f} {:.2f} -1 1".format(gibuu_output.energy_min, gibuu_output.energy_max) + header_dct["tgen"] = "{:.1f}".format(livetime) + header_dct["norma"] = "0 {}".format(gibuu_output.generated_events) + timestamp = datetime.now() + header_dct["simul"] = "KM3BUU {} {}".format( + version, timestamp.strftime("%Y%m%d %H%M%S")) for k, v in header_dct.items(): head.set_line(k, v) @@ -471,16 +461,11 @@ def write_detector_file(gibuu_output, evt.id = mc_event_id evt.mc_run_id = mc_event_id # Weights - evt.w.push_back(can_volume) #w1 (can volume) + evt.w.push_back(geometry.volume) #w1 (can volume) evt.w.push_back(w2[mc_event_id]) #w2 evt.w.push_back(-1.0) #w3 (= w2*flux) # Vertex Position - r = can[2] * np.sqrt(np.random.uniform(0, 1)) - phi = np.random.uniform(0, 2 * np.pi) - pos_x = r * np.cos(phi) - pos_y = r * np.sin(phi) - pos_z = np.random.uniform(can[0], can[1]) - vtx_pos = np.array([pos_x, pos_y, pos_z]) + vtx_pos = np.array(geometry.random_pos()) # Direction phi = np.random.uniform(0, 2 * np.pi) cos_theta = np.random.uniform(-1, 1) diff --git a/km3buu/tests/test_geometry.py b/km3buu/tests/test_geometry.py new file mode 100644 index 0000000000000000000000000000000000000000..9894c4732948623ea94bd059b871d766902f0299 --- /dev/null +++ b/km3buu/tests/test_geometry.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python +# coding=utf-8 +# Filename: test_output.py + +__author__ = "Johannes Schumann" +__copyright__ = "Copyright 2020, Johannes Schumann and the KM3NeT collaboration." +__credits__ = [] +__license__ = "MIT" +__maintainer__ = "Johannes Schumann" +__email__ = "jschumann@km3net.de" +__status__ = "Development" + +import unittest + +from km3buu.geometry import * + + +class TestGeneralGeometry(unittest.TestCase): + def test_abstract_init(self): + with self.assertRaises(TypeError) as ctx: + d = DetectorVolume() + + +class TestSphere(unittest.TestCase): + def setUp(self): + self.detector_geometry = SphericalVolume(20, (2, 2, 2)) + + def test_volume(self): + volume = self.detector_geometry.volume + self.assertAlmostEqual(volume, 33510.32, 2) + + def test_random_pos(self): + for i in range(50): + pos = self.detector_geometry.random_pos() + assert pos[0] > -18.0 + assert pos[1] > -18.0 + assert pos[2] > -18.0 + assert pos[0] < 22.0 + assert pos[1] < 22.0 + assert pos[2] < 22.0 + + +class TestCan(unittest.TestCase): + def setUp(self): + self.detector_geometry = CanVolume() + + def test_volume(self): + volume = self.detector_geometry.volume + self.assertAlmostEqual(volume, 243604084.28, 2)