From c9d0254f243b67ef0c2a9ae5ed8d99ad11b3c69a Mon Sep 17 00:00:00 2001
From: Aaron LI <aly@aaronly.me>
Date: Thu, 4 Jan 2018 22:07:06 +0800
Subject: clusters/halo: Add preliminary RadioHaloAM based on RadioHalo

The RadioHaloAM class is intended to account for all merger events, and there
are a lot of methods to be implemented.
---
 fg21sim/extragalactic/clusters/halo.py | 103 +++++++++++++++++++++++++++++++++
 fg21sim/extragalactic/clusters/main.py |  24 ++++----
 2 files changed, 115 insertions(+), 12 deletions(-)

(limited to 'fg21sim/extragalactic/clusters')

diff --git a/fg21sim/extragalactic/clusters/halo.py b/fg21sim/extragalactic/clusters/halo.py
index 365aeda..1481899 100644
--- a/fg21sim/extragalactic/clusters/halo.py
+++ b/fg21sim/extragalactic/clusters/halo.py
@@ -720,3 +720,106 @@ class RadioHalo:
         z = COSMO.redshift(t)
         loss = -4.32e-4 * gamma**2 * ((B/3.25)**2 + (1+z)**4)
         return loss
+
+
+class RadioHaloAM(RadioHalo):
+    """
+    Simulate the diffuse (giant) radio halo for a galaxy cluster
+    with all its on-going/recent merger events taken into account,
+    while the above ``RadioHalo`` class only considers the most
+    recent major/maximum merger event that is specified.
+
+    Parameters
+    ----------
+    M_obs : float
+        Cluster virial mass at the observation (simulation end) time.
+        Unit: [Msun]
+    z_obs : float
+        Redshift of the observation (simulation end) time.
+    M_main, M_sub : list[float]
+        List of main and sub cluster masses at each merger event,
+        from current to earlier time.
+        Unit: [Msun]
+    z_merger : list[float]
+        The redshifts at each merger event, from small to large.
+    merger_num : int
+        Number of merger events traced for the cluster.
+    """
+    def __init__(self, M_obs, z_obs, M_main, M_sub, z_merger,
+                 merger_num, configs=CONFIGS):
+        self.merger_num = merger_num
+        M_main = np.asarray(M_main[:merger_num])
+        M_sub = np.asarray(M_sub[:merger_num])
+        z_merger = np.asarray(z_merger[:merger_num])
+        super().__init__(M_obs=M_obs, z_obs=z_obs,
+                         M_main=M_main, M_sub=M_sub,
+                         z_merger=z_merger, configs=configs)
+
+    @property
+    def age_begin(self):
+        """
+        The cosmic time when the merger begins, i.e., the earliest merger.
+        Unit: [Gyr]
+        """
+        return self.age_merger[-1]
+
+    def _merger_idx(self, t):
+        """
+        Determine the index of the merger event within which the given
+        time is located, i.e.:
+            age_merger[idx-1] >= t > age_merger[idx]
+        """
+        return (self.age_merger > t).sum()
+
+    def _merger(self, idx):
+        """
+        Return the properties of the idx-th merger event.
+        """
+        return {
+            "M_main": self.M_main[idx],
+            "M_sub": self.M_sub[idx],
+            "z": self.z_merger[idx],
+            "age": self.age_merger[idx],
+        }
+
+    def mass_merged(self, t):
+        """
+        The mass of merged cluster at the given (cosmic) time.
+        Unit: [Msun]
+        """
+        if t >= self.age_obs:
+            return self.M_obs
+        else:
+            idx = self._merger_idx(t)
+            merger = self._merger(idx)
+            return (merger["M_main"] + merger["M_sub"])
+
+    def mass_main(self, t):
+        """
+        Calculate the main cluster mass at the given (cosmic) time.
+
+        Parameters
+        ----------
+        t : float
+            The (cosmic) time/age.
+            Unit: [Gyr]
+
+        Returns
+        -------
+        mass : float
+            The mass of the main cluster.
+            Unit: [Msun]
+        """
+        idx = self._merger_idx(t)
+        merger1 = self._merger(idx)
+        mass1 = merger1["M_main"]
+        t1 = merger1["age"]
+        if idx == 0:
+            mass0 = self.M_obs
+            t0 = self.age_obs
+        else:
+            merger0 = self._merger(idx-1)
+            mass0 = merger0["M_main"]
+            t0 = merger0["age"]
+        rate = (mass0 - mass1) / (t0 - t1)
+        return (mass1 + rate * (t - t1))
diff --git a/fg21sim/extragalactic/clusters/main.py b/fg21sim/extragalactic/clusters/main.py
index 04ed52d..b2ed530 100644
--- a/fg21sim/extragalactic/clusters/main.py
+++ b/fg21sim/extragalactic/clusters/main.py
@@ -20,7 +20,7 @@ import numpy as np
 
 from .psformalism import PSFormalism
 from .formation import ClusterFormation
-from .halo import RadioHalo
+from .halo import RadioHaloAM
 from .emission import HaloEmission
 from ...share import CONFIGS, COSMO
 from ...utils.io import dataframe_to_csv, pickle_dump, pickle_load
@@ -241,9 +241,9 @@ class GalaxyClusters:
         """
         # Select out the clusters with recent mergers
         idx_rmm = [idx for idx, cdict in enumerate(self.catalog)
-                   if cdict["rmm_z"] is not None]
+                   if cdict["merger_num"] > 0]
         num = len(idx_rmm)
-        logger.info("Simulating halos for %d merging clusters ..." % num)
+        logger.info("Simulating halos for %d clusters with mergers ..." % num)
         self.halos = []
         for i, idx in enumerate(idx_rmm):
             ii = i + 1
@@ -252,15 +252,15 @@ class GalaxyClusters:
             cdict = self.catalog[idx]
             z_obs = cdict["z"]
             M_obs = cdict["mass"]
-            z_merger = cdict["rmm_z"]
-            M_main = cdict["rmm_mass1"]
-            M_sub = cdict["rmm_mass2"]
-            logger.info("[%d/%d] " % (ii, num) +
-                        "M1[%.2e] & M2[%.2e] @ z[%.3f] -> M[%.2e] @ z[%.3f]" %
-                        (M_main, M_sub, z_merger, M_obs, z_obs))
-            halo = RadioHalo(M_obs=M_obs, z_obs=z_obs,
-                             M_main=M_main, M_sub=M_sub,
-                             z_merger=z_merger, configs=self.configs)
+            merger_num = cdict["merger_num"]
+            logger.info("[%d/%d] M[%.2e] @ z[%.3f] with %d mergers" %
+                        (ii, num, M_obs, z_obs, merger_num))
+            halo = RadioHaloAM(M_obs=M_obs, z_obs=z_obs,
+                               M_main=cdict["merger_mass1"],
+                               M_sub=cdict["merger_mass2"],
+                               z_merger=cdict["merger_z"],
+                               merger_num=merger_num,
+                               configs=self.configs)
             n_e = halo.calc_electron_spectrum()
             data = OrderedDict([
                 ("z0", halo.z_obs),
-- 
cgit v1.2.2