cbhbd 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cbhbd/__init__.py +8 -0
- cbhbd/cbhbd.py +159 -0
- cbhbd/cluster.py +1825 -0
- cbhbd/cluster_prescriptions.py +520 -0
- cbhbd/funcs.py +177 -0
- cbhbd/mergers.py +444 -0
- cbhbd/remnant.py +205 -0
- cbhbd-1.0.0.dist-info/METADATA +98 -0
- cbhbd-1.0.0.dist-info/RECORD +10 -0
- cbhbd-1.0.0.dist-info/WHEEL +4 -0
cbhbd/__init__.py
ADDED
cbhbd/cbhbd.py
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from . import cluster
|
|
3
|
+
from . import mergers
|
|
4
|
+
from . import remnant
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class MergerOutcome:
|
|
8
|
+
# Define all possible merger types
|
|
9
|
+
|
|
10
|
+
InClusterInspiral = "incluster_inspiral"
|
|
11
|
+
GWCaptureBS = "gw_capture_bs"
|
|
12
|
+
GWCaptureBB = "gw_capture_bb"
|
|
13
|
+
Ejected = "ejected"
|
|
14
|
+
GWCaptureDirect = "gw_capture_direct"
|
|
15
|
+
|
|
16
|
+
@staticmethod
|
|
17
|
+
def get_outcomes():
|
|
18
|
+
return [MergerOutcome.InClusterInspiral,
|
|
19
|
+
MergerOutcome.GWCaptureBS,
|
|
20
|
+
MergerOutcome.GWCaptureBB,
|
|
21
|
+
MergerOutcome.Ejected,
|
|
22
|
+
MergerOutcome.GWCaptureDirect]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class CBHBD:
|
|
26
|
+
remnant_models = {k: None for k in remnant.available_remnant_models.keys()}
|
|
27
|
+
|
|
28
|
+
def __init__(self, **kwargs):
|
|
29
|
+
self._run_mergers = mergers._run_mergers
|
|
30
|
+
self._get_IMBH_data = mergers._get_IMBH_data
|
|
31
|
+
|
|
32
|
+
self.tend = 13.8e3 # [Myr] Requested final time for integration. Actual final time can be smaller if e.g. the cluster dissolves
|
|
33
|
+
self.M0 = None # [Msun] Initial mass of the cluster.
|
|
34
|
+
self.N = None # Initial number of stars in the cluster
|
|
35
|
+
self.rh0 = None # [pc] Initial half-mass radius
|
|
36
|
+
self.rhoh0 = None # [Msun/pc^3] Initial density within the half-mass radius
|
|
37
|
+
self.rg = 8 # [kpc] Galactocentric distance of the cluster. For eccentric orbits, consider it as a(1 - e) where a is the semi-major axis, e is eccentricity.
|
|
38
|
+
self.Z = None # Metallicity of the cluster, where Z=0.02 is solar metallicity
|
|
39
|
+
self.FeH = None # Metallicity of the cluster, where FeH=0 is solar metallicity
|
|
40
|
+
|
|
41
|
+
self.Zsolar = 0.02 # Solar metallicity.
|
|
42
|
+
|
|
43
|
+
self.verbose = True # If True, print extra output.
|
|
44
|
+
self.seed = None # Seed for random number generator. Use None to get a random seed.
|
|
45
|
+
self.remnant_model = "RemnantModelVarma19Islam23" # GW remnant model, see remnant.py for available options.
|
|
46
|
+
self.compute_mergers = True # If true, compute the mergers. If false, only compute the cluster model.
|
|
47
|
+
self.ifmr = "sevn-rapid" # IFMR relation to be used if ssp is selected.
|
|
48
|
+
|
|
49
|
+
self.dtout = None # [Myr] Time step for integration. If None, computations are faster.
|
|
50
|
+
self.dense_output = True # Condition for solve_ivp to give continuous solutions. Facilitates interpolation. Can be used instead of specifying t_eval. Necessary if compute_mergers==True.
|
|
51
|
+
self.BH = True # Condition to work with BHs in clusterBH.
|
|
52
|
+
self.ssp = True # Condition to use the SSP tools to extract the BHMF at any moment. Default option uses such tools.
|
|
53
|
+
self.kick = True # Condition to include natal kicks. Affects the BH population obtained. Default option considers kicks.
|
|
54
|
+
|
|
55
|
+
self.output_mergers = False # A Boolean parameter to save the mergers to a file
|
|
56
|
+
self.outfile_mergers = "mergers.txt" # File to save the mergers, if needed.
|
|
57
|
+
|
|
58
|
+
# The user can either specify the initial average mass beforehand, or it is extracted from the IMF.
|
|
59
|
+
# If m0 is not None, the IMF must be well defined so that it matches.
|
|
60
|
+
self.m0 = None # [Msun] Average mass obtained for this particular IMF.
|
|
61
|
+
|
|
62
|
+
# Pass in lists for a_slopes, m_breaks and nbins. Any number of IMF intervals, based on size of lists. m_breaks must be size N + 1.
|
|
63
|
+
|
|
64
|
+
# Slopes of mass function.
|
|
65
|
+
self.a_slopes = [-1.3, -2.3, -2.3]
|
|
66
|
+
self.a_kroupa = self.a_slopes.copy() # In case the user inserts a different set of slopes (and mass breaks), the stellar mass loss rate ν (and time tsev) is different. If ssp is True, this is acoounted, if not, bottom-light IMFs are studied with sev_tune=True.
|
|
67
|
+
|
|
68
|
+
# Mass function break masses.
|
|
69
|
+
self.m_breaks = [0.08, 0.5, 1, 150]
|
|
70
|
+
self.m_kroupa = self.m_breaks.copy() # The user can insert different masses. The slopes and mass breaks of the Kroupa should not change.
|
|
71
|
+
|
|
72
|
+
# Number of bins per interval of the mass function.
|
|
73
|
+
self.nbins = [5, 5, 20]
|
|
74
|
+
|
|
75
|
+
# Valid input parameters. Do not define more input params after this line.
|
|
76
|
+
self.cbhbd_params = [attr for attr in self.__dict__.keys() if attr[0] != "_"]
|
|
77
|
+
|
|
78
|
+
# Read the user's input parameters
|
|
79
|
+
if kwargs is not None:
|
|
80
|
+
for key, value in kwargs.items():
|
|
81
|
+
setattr(self, key, value)
|
|
82
|
+
|
|
83
|
+
assert self.remnant_model in remnant.available_remnant_models, f"Remnant model {self.remnant_model} not available, please choose from {remnant.available_remnant_models.keys()}"
|
|
84
|
+
self._construct_input_params()
|
|
85
|
+
|
|
86
|
+
# Add the unchanged defaults to kwargs, to then pass it to ClusterBH
|
|
87
|
+
for attr, value in self.__dict__.items():
|
|
88
|
+
if attr[0] != "_":
|
|
89
|
+
kwargs[attr] = value
|
|
90
|
+
|
|
91
|
+
# Derived variables
|
|
92
|
+
self.mmin = self.m_breaks[0]
|
|
93
|
+
self.mmax = self.m_breaks[-1]
|
|
94
|
+
|
|
95
|
+
# Output
|
|
96
|
+
self.cluster, self.mergers = None, None
|
|
97
|
+
self.mIMBH, self.chiIMBH, self.genIMBH = None, None, None # Properties of the most massive BH in the cluster.
|
|
98
|
+
self.mIMBHej, self.chiIMBHej, self.genIMBHej = None, None, None # Properties of the most massive BH ejected from the cluster.
|
|
99
|
+
self.bhv = None
|
|
100
|
+
|
|
101
|
+
self._run(**kwargs)
|
|
102
|
+
|
|
103
|
+
def _construct_input_params(self):
|
|
104
|
+
# Construct the mass and IMF
|
|
105
|
+
if (self.M0 is None) == (self.N is None):
|
|
106
|
+
raise ValueError("Please provide either M0 or N. "
|
|
107
|
+
"To change the mean mass, modify the IMF breaks and limits (m_break) or slopes (a_slope)")
|
|
108
|
+
if self.m0 is None:
|
|
109
|
+
self.m0 = cluster.initial_average_mass(self.a_slopes, self.m_breaks)
|
|
110
|
+
if self.M0 is None:
|
|
111
|
+
self.M0 = self.m0 * self.N
|
|
112
|
+
else:
|
|
113
|
+
self.N = self.M0 / self.m0
|
|
114
|
+
|
|
115
|
+
# Construct the half-mass radius and density
|
|
116
|
+
if (self.rh0 is None) == (self.rhoh0 is None):
|
|
117
|
+
raise ValueError("Please provide either rh0 or rhoh0.")
|
|
118
|
+
if self.rhoh0 is None:
|
|
119
|
+
self.rhoh0 = 3 * self.M0 / (8 * np.pi * self.rh0 ** 3)
|
|
120
|
+
else:
|
|
121
|
+
self.rh0 = (3 * self.M0 / (8 * np.pi * self.rhoh0)) ** (1 / 3)
|
|
122
|
+
|
|
123
|
+
# Construct the metallicity
|
|
124
|
+
if (self.Z is None) == (self.FeH is None):
|
|
125
|
+
raise ValueError("Please provide either Z or FeH.")
|
|
126
|
+
if self.Z is None:
|
|
127
|
+
self.Z = self.Zsolar * 10 ** self.FeH
|
|
128
|
+
else:
|
|
129
|
+
self.FeH = np.log10(self.Z / self.Zsolar)
|
|
130
|
+
|
|
131
|
+
# General sanity checks
|
|
132
|
+
assert self.rh0 > 0
|
|
133
|
+
assert self.rhoh0 > 0
|
|
134
|
+
assert self.M0 > 0
|
|
135
|
+
assert self.N > 0
|
|
136
|
+
if self.compute_mergers:
|
|
137
|
+
assert self.ssp, "Mergers are only available when SSP is enabled"
|
|
138
|
+
assert self.BH, "Mergers are only available when BHs are enabled"
|
|
139
|
+
assert self.dense_output, "Mergers are only available when dense_output is enabled"
|
|
140
|
+
|
|
141
|
+
def _run(self, **kwargs):
|
|
142
|
+
self.cluster = cluster.Cluster(**kwargs)
|
|
143
|
+
|
|
144
|
+
if self.compute_mergers:
|
|
145
|
+
try:
|
|
146
|
+
self.mergers = self._run_mergers(self)
|
|
147
|
+
self._get_IMBH_data(self)
|
|
148
|
+
if self.output_mergers:
|
|
149
|
+
self.mergers.to_csv(self.outfile_mergers, header=True, index=False)
|
|
150
|
+
except Exception as err:
|
|
151
|
+
print("Error in computing the mergers in model with", flush=True)
|
|
152
|
+
print(f"\t Mass = {self.M0} M_sun", flush=True)
|
|
153
|
+
print(f"\t Metallicity = {self.Z}", flush=True)
|
|
154
|
+
print(f"\t Final time = {self.tend} Myr", flush=True)
|
|
155
|
+
print(f"\t Initial density = {self.rhoh0:.3g} M_sun/pc^3", flush=True)
|
|
156
|
+
print(f"\t IFMR model = {self.ifmr}", flush=True)
|
|
157
|
+
print(f"\t Seed = {self.seed}", flush=True)
|
|
158
|
+
print(err, flush=True)
|
|
159
|
+
raise err
|