From 38a74e23bb8b27080eb7491e1bd1a88def606702 Mon Sep 17 00:00:00 2001
From: Johannes Schumann <johannes.schumann@fau.de>
Date: Sat, 1 Apr 2023 07:35:43 +0200
Subject: [PATCH] Make CAN only geometry able to decay taus

---
 km3buu/geometry.py    | 89 ++++++++++++++++++++++++++++++++++---------
 km3buu/propagation.py |  2 +-
 2 files changed, 73 insertions(+), 18 deletions(-)

diff --git a/km3buu/geometry.py b/km3buu/geometry.py
index e94ad97..d30bbaa 100644
--- a/km3buu/geometry.py
+++ b/km3buu/geometry.py
@@ -159,6 +159,13 @@ class NoVolume(DetectorVolume):
         else:
             return np.concatenate([np.zeros(n), np.ones(n)]).reshape((2, -1)).T
 
+    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, None
+
 
 class CanVolume(DetectorVolume):
     """
@@ -176,6 +183,8 @@ class CanVolume(DetectorVolume):
         Detector center position in the xy-plane
     zenith: float [1] (default: (-1.0, 1.0) )
         Zenith range given as cos(θ)
+    taupropagation: bool (default True)
+        Do secondary tau lepton propagation / decay
     """
 
     def __init__(self,
@@ -183,7 +192,8 @@ class CanVolume(DetectorVolume):
                  zmin=0.0,
                  zmax=476.5,
                  detector_center=(0., 0.),
-                 zenith=(-1, 1)):
+                 zenith=(-1, 1),
+                 taupropagation=True):
         super().__init__()
         self._radius = radius
         self._zmin = zmin
@@ -193,22 +203,55 @@ class CanVolume(DetectorVolume):
         self._cosZmin = zenith[0]
         self._cosZmax = zenith[1]
         self._solid_angle = 2 * np.pi * (self._cosZmax - self._cosZmin)
+        self._propagator = None
+        if taupropagation:
+            self._pp_geometry = self.make_proposal_geometries()
+            self._propagator = Propagator([15, -15], self._pp_geometry)
+
+    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._zmin + self._zmax) / 2 * M_TO_CM
+        geometry_can = pp.geometry.Cylinder(
+            pp.Cartesian3D(center_x, center_y, can_zpos),
+            (self._zmax - self._zmin) * M_TO_CM, self._radius * M_TO_CM, 0.)
+        geometry_can.hierarchy = PROPOSAL_CAN_HIERARCHY_LEVEL
+        geometries["can"] = geometry_can
+        return geometries
 
     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)
+    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._zmin, self._zmax)
-        return (pos_x, pos_y, pos_z)
+        pos_z = np.random.uniform(self._zmin, self._zmax, 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 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_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()
@@ -217,16 +260,28 @@ class CanVolume(DetectorVolume):
                                         self._volume, nevents)
         retdct[key] = value
         key = "fixedcan"
-        value = "{} {} {} {} {}".format(detector_center[0], detector_center[1],
-                                        self._zmin, self._zmax, self._radius)
+        value = "{} {} {} {} {}".format(self._detector_center[0],
+                                        self._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()
+        vtx_angles = self.random_dir()
         weight = 1
         evts = None
-        return vtx_pos, vtx_dir, weight, evts
+
+        if evt.flavor_ID == 3 and abs(evt.process_ID) == 2:
+            charged_lepton_type = np.sign(
+                evt.process_ID) * (2 * evt.flavor_ID + 9)
+            lepout_dir = np.array(
+                [evt.lepOut_Px, evt.lepOut_Py, evt.lepOut_Pz])
+            R = Rotation.from_euler("yz", vtx_angles)
+            evts = self._propagator.propagate(charged_lepton_type,
+                                              evt.lepOut_E, vtx_pos,
+                                              R.apply(lepout_dir))
+
+        return vtx_pos, vtx_angles, weight, evts
 
 
 class CylindricalVolume(DetectorVolume):
@@ -438,9 +493,9 @@ class CylindricalVolume(DetectorVolume):
             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))
+            particles = self._propagator.propagate(charged_lepton_type,
+                                                   evt.lepOut_E, vtx_pos,
+                                                   R.apply(lepout_dir))
             if not particles is None:
                 return vtx_pos, vtx_angles, samples, particles
 
diff --git a/km3buu/propagation.py b/km3buu/propagation.py
index b1874ba..f94d256 100644
--- a/km3buu/propagation.py
+++ b/km3buu/propagation.py
@@ -127,7 +127,7 @@ class Propagator(object):
         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):
+    def propagate(self, lep_pdgid, lep_E, lep_pos, lep_dir):
         """
         Lepton propagation to can based on PROPOSAL
 
-- 
GitLab