cogsworth 0.0__tar.gz
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.
- cogsworth-0.0/LICENSE +21 -0
- cogsworth-0.0/PKG-INFO +24 -0
- cogsworth-0.0/README.md +10 -0
- cogsworth-0.0/cogsworth/__init__.py +2 -0
- cogsworth-0.0/cogsworth/_version.py +1 -0
- cogsworth-0.0/cogsworth/classify.py +369 -0
- cogsworth-0.0/cogsworth/events.py +90 -0
- cogsworth-0.0/cogsworth/galaxy.py +557 -0
- cogsworth-0.0/cogsworth/kicks.py +182 -0
- cogsworth-0.0/cogsworth/observables.py +336 -0
- cogsworth-0.0/cogsworth/pop.py +999 -0
- cogsworth-0.0/cogsworth/tests/__init__.py +0 -0
- cogsworth-0.0/cogsworth/tests/prep_data.py +14 -0
- cogsworth-0.0/cogsworth/tests/test_classify.py +54 -0
- cogsworth-0.0/cogsworth/tests/test_events.py +48 -0
- cogsworth-0.0/cogsworth/tests/test_galaxy.py +189 -0
- cogsworth-0.0/cogsworth/tests/test_observables.py +35 -0
- cogsworth-0.0/cogsworth/tests/test_pop.py +254 -0
- cogsworth-0.0/cogsworth.egg-info/PKG-INFO +24 -0
- cogsworth-0.0/cogsworth.egg-info/SOURCES.txt +24 -0
- cogsworth-0.0/cogsworth.egg-info/dependency_links.txt +1 -0
- cogsworth-0.0/cogsworth.egg-info/requires.txt +22 -0
- cogsworth-0.0/cogsworth.egg-info/top_level.txt +1 -0
- cogsworth-0.0/setup.cfg +67 -0
- cogsworth-0.0/setup.py +14 -0
cogsworth-0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2022 Tom Wagg
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
cogsworth-0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: cogsworth
|
|
3
|
+
Version: 0.0
|
|
4
|
+
Summary: A framework for performing self-consistent population synthesis and orbital integration
|
|
5
|
+
Home-page: https://github.com/TomWagg/cogsworth
|
|
6
|
+
Author: Tom Wagg
|
|
7
|
+
Author-email: tomjwagg@gmail.com
|
|
8
|
+
License: MIT
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
Provides-Extra: test
|
|
12
|
+
Provides-Extra: docs
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
<h1 align="center">Cogsworth</h1>
|
|
17
|
+
<p align="center">A Python package for self-consistently performing population synthesis and orbital integration</p>
|
|
18
|
+
<p align="center">
|
|
19
|
+
<a href='https://cogsworth.readthedocs.io/en/latest/?badge=latest'>
|
|
20
|
+
<img src='https://readthedocs.org/projects/cogsworth/badge/?version=latest' alt='Documentation Status' />
|
|
21
|
+
</a>
|
|
22
|
+
<br>
|
|
23
|
+
<img width=500 src="https://user-images.githubusercontent.com/21990332/194442782-49afc013-2c53-4638-bcb7-c0970319c2bd.png">
|
|
24
|
+
</p>
|
cogsworth-0.0/README.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
|
|
2
|
+
<h1 align="center">Cogsworth</h1>
|
|
3
|
+
<p align="center">A Python package for self-consistently performing population synthesis and orbital integration</p>
|
|
4
|
+
<p align="center">
|
|
5
|
+
<a href='https://cogsworth.readthedocs.io/en/latest/?badge=latest'>
|
|
6
|
+
<img src='https://readthedocs.org/projects/cogsworth/badge/?version=latest' alt='Documentation Status' />
|
|
7
|
+
</a>
|
|
8
|
+
<br>
|
|
9
|
+
<img width=500 src="https://user-images.githubusercontent.com/21990332/194442782-49afc013-2c53-4638-bcb7-c0970319c2bd.png">
|
|
10
|
+
</p>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.0"
|
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
import numpy as np
|
|
3
|
+
import astropy.units as u
|
|
4
|
+
import astropy.constants as const
|
|
5
|
+
import gala.dynamics as gd
|
|
6
|
+
|
|
7
|
+
__all__ = ["determine_final_classes", "list_classes", "get_eddington_rate", "get_eddington_lum",
|
|
8
|
+
"get_schwarzchild_radius", "get_x_ray_lum"]
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def determine_final_classes(population=None, bpp=None, bcm=None, kick_info=None, orbits=None, potential=None):
|
|
12
|
+
"""Determine the classes of each member of a population at the last point in the evolution (usually
|
|
13
|
+
present day).
|
|
14
|
+
|
|
15
|
+
Either supply a Population class or each individual table separately
|
|
16
|
+
|
|
17
|
+
Parameters
|
|
18
|
+
----------
|
|
19
|
+
population : :class:`~cogsworth.pop.Population`, optional
|
|
20
|
+
A full population class created from the pop module, by default None
|
|
21
|
+
bpp : :class:`~pandas.DataFrame`
|
|
22
|
+
Evolutionary history of each binary
|
|
23
|
+
bcm : :class:`~pandas.DataFrame`
|
|
24
|
+
Final state of each binary
|
|
25
|
+
initC : :class:`~pandas.DataFrame`
|
|
26
|
+
Initial conditions for each binary
|
|
27
|
+
kick_info : :class:`~pandas.DataFrame`
|
|
28
|
+
Information about the kicks that occur for each binary
|
|
29
|
+
orbits : `list` of :class:`~gala.dynamics.Orbit`
|
|
30
|
+
The orbits of each binary within the galaxy. Disrupted binaries should have two entries
|
|
31
|
+
(for both stars).
|
|
32
|
+
galactic_potential : :class:`~gala.potential.potential.PotentialBase`, optional
|
|
33
|
+
Galactic potential to use for evolving the orbits of binaries
|
|
34
|
+
|
|
35
|
+
Returns
|
|
36
|
+
-------
|
|
37
|
+
classes : :class:`~pandas.DataFrame`
|
|
38
|
+
A DataFrame with a boolean column for each class (see :meth:`list_classes`) and a row for each binary
|
|
39
|
+
|
|
40
|
+
Raises
|
|
41
|
+
------
|
|
42
|
+
ValueError
|
|
43
|
+
If either `population` is None OR any another parameter is None
|
|
44
|
+
"""
|
|
45
|
+
# ensure that there's enough input
|
|
46
|
+
if population is None and (bpp is None or bcm is None or kick_info is None
|
|
47
|
+
or orbits is None or potential is None):
|
|
48
|
+
raise ValueError("Either `population` must be supplied or all other parameters")
|
|
49
|
+
|
|
50
|
+
# split up the input so that I can use a single interface
|
|
51
|
+
if population is not None:
|
|
52
|
+
bpp, bcm, kick_info, orbits, potential = population.bpp, population.bcm, population.kick_info,\
|
|
53
|
+
population.orbits, population.galactic_potential
|
|
54
|
+
|
|
55
|
+
# get the binary indices and also reduce the tables to just the final row in each
|
|
56
|
+
final_bpp = bpp[~bpp.index.duplicated(keep="last")]
|
|
57
|
+
final_bcm = bcm[~bcm.index.duplicated(keep="last")]
|
|
58
|
+
final_kick_info = kick_info.sort_values(["bin_num", "star"]).drop_duplicates(subset="bin_num",
|
|
59
|
+
keep="last")
|
|
60
|
+
|
|
61
|
+
# set up an empty dataframe
|
|
62
|
+
columns = ["dco", "co-1", "co-2", "xrb", "walkaway-t-1", "walkaway-t-2", "runaway-t-1", "runaway-t-2",
|
|
63
|
+
"walkaway-o-1", "walkaway-o-2", "runaway-o-1", "runaway-o-2", "widow-1", "widow-2",
|
|
64
|
+
"stellar-merger-co-1", "stellar-merger-co-2", "pisn-1", "pisn-2"]
|
|
65
|
+
data = np.zeros(shape=(len(final_bpp), len(columns))).astype(bool)
|
|
66
|
+
classes = pd.DataFrame(data=data, columns=columns)
|
|
67
|
+
|
|
68
|
+
# match the index to the final bpp bin_nums
|
|
69
|
+
classes.index = final_bpp["bin_num"].index
|
|
70
|
+
|
|
71
|
+
# check the state of the binary
|
|
72
|
+
bound = final_bpp["sep"] > 0.0
|
|
73
|
+
merger = final_bpp["sep"] == 0.0
|
|
74
|
+
|
|
75
|
+
# HACK: three different markers and only assume it is disrupted if ALL are True
|
|
76
|
+
disrupted = (final_bpp["bin_num"].isin(kick_info[kick_info["disrupted"] == 1.0]["bin_num"].unique())
|
|
77
|
+
& (final_bpp["sep"] < 0.0)
|
|
78
|
+
& final_bpp["bin_num"].isin(bpp[bpp["evol_type"] == 11.0]["bin_num"]))
|
|
79
|
+
|
|
80
|
+
# set some flags for the end conditions of each component
|
|
81
|
+
primary_is_star = final_bpp["kstar_1"] <= 9
|
|
82
|
+
secondary_is_star = final_bpp["kstar_2"] <= 9
|
|
83
|
+
primary_is_bh_ns = final_bpp["kstar_1"].isin([13, 14])
|
|
84
|
+
secondary_is_bh_ns = final_bpp["kstar_2"].isin([13, 14])
|
|
85
|
+
|
|
86
|
+
# check if there was ever a bound CO
|
|
87
|
+
primary_ever_bound_bh_ns = final_bpp["bin_num"].isin(bpp[bpp["kstar_1"].isin([13, 14])
|
|
88
|
+
& bpp["sep"] > 0.0]["bin_num"].unique())
|
|
89
|
+
secondary_ever_bound_bh_ns = final_bpp["bin_num"].isin(bpp[bpp["kstar_2"].isin([13, 14])
|
|
90
|
+
& bpp["sep"] > 0.0]["bin_num"].unique())
|
|
91
|
+
|
|
92
|
+
# create masks based on conditions listed in `list_classes`
|
|
93
|
+
classes["dco"] = bound & primary_is_bh_ns & secondary_is_bh_ns
|
|
94
|
+
classes["co-1"] = ~merger & primary_is_bh_ns
|
|
95
|
+
classes["co-2"] = ~merger & secondary_is_bh_ns
|
|
96
|
+
classes["stellar-merger-co-1"] = merger & primary_is_bh_ns
|
|
97
|
+
classes["stellar-merger-co-2"] = merger & secondary_is_bh_ns
|
|
98
|
+
classes["xrb"] = bound & ((primary_is_bh_ns & secondary_is_star) | (secondary_is_bh_ns & primary_is_star))
|
|
99
|
+
|
|
100
|
+
classes["widow-1"] = ~merger & primary_is_star & secondary_ever_bound_bh_ns
|
|
101
|
+
classes["widow-2"] = ~merger & secondary_is_star & primary_ever_bound_bh_ns
|
|
102
|
+
|
|
103
|
+
classes["pisn-1"] = final_bcm["SN_1"].isin([6, 7])
|
|
104
|
+
classes["pisn-2"] = final_bcm["SN_2"].isin([6, 7])
|
|
105
|
+
|
|
106
|
+
classes["walkaway-t-1"] = disrupted & (final_kick_info["vsys_1_total"] < 30.0) & primary_is_star
|
|
107
|
+
classes["walkaway-t-2"] = disrupted & (final_kick_info["vsys_2_total"] < 30.0) & secondary_is_star
|
|
108
|
+
classes["runaway-t-1"] = disrupted & (final_kick_info["vsys_1_total"] >= 30.0) & primary_is_star
|
|
109
|
+
classes["runaway-t-2"] = disrupted & (final_kick_info["vsys_2_total"] >= 30.0) & secondary_is_star
|
|
110
|
+
|
|
111
|
+
# calculate relative speeds for observed walk/runaways
|
|
112
|
+
if disrupted.any():
|
|
113
|
+
rel_speed_1 = _get_rel_speed(orbits=orbits[disrupted], potential=potential, which_star=0)
|
|
114
|
+
rel_speed_2 = _get_rel_speed(orbits=orbits[disrupted], potential=potential, which_star=1)
|
|
115
|
+
|
|
116
|
+
# set the classes based on the relative speeds (non-disrupted as all left as False by default)
|
|
117
|
+
classes.loc[disrupted, "walkaway-o-1"] = (rel_speed_1 < 30.0) & primary_is_star[disrupted]
|
|
118
|
+
classes.loc[disrupted, "walkaway-o-2"] = (rel_speed_2 < 30.0) & secondary_is_star[disrupted]
|
|
119
|
+
classes.loc[disrupted, "runaway-o-1"] = (rel_speed_1 >= 30.0) & primary_is_star[disrupted]
|
|
120
|
+
classes.loc[disrupted, "runaway-o-2"] = (rel_speed_2 >= 30.0) & secondary_is_star[disrupted]
|
|
121
|
+
|
|
122
|
+
return classes
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _get_rel_speed(orbits, potential, which_star):
|
|
126
|
+
"""Calculate the relative speed of a set of stars at the end of their orbits compared to the circular
|
|
127
|
+
velocity at their final positions (given a galactic potential)
|
|
128
|
+
|
|
129
|
+
Parameters
|
|
130
|
+
----------
|
|
131
|
+
orbits : `list`
|
|
132
|
+
List of gala Orbits
|
|
133
|
+
potential : :class:`gala.potential.potential.PotentialBase`
|
|
134
|
+
The galactic potential used for finding the circular velocity
|
|
135
|
+
which_star : `int`
|
|
136
|
+
Index of which star to consider, either 0 or 1
|
|
137
|
+
|
|
138
|
+
Returns
|
|
139
|
+
-------
|
|
140
|
+
rel_speed : `float`
|
|
141
|
+
Relative speed in km / s
|
|
142
|
+
"""
|
|
143
|
+
# get final positions and velocities
|
|
144
|
+
posf = [None for _ in range(len(orbits))]
|
|
145
|
+
velf = [None for _ in range(len(orbits))]
|
|
146
|
+
for i, orbit in enumerate(orbits):
|
|
147
|
+
posf[i] = orbit[which_star][-1].pos.xyz.to(u.kpc).value
|
|
148
|
+
velf[i] = orbit[which_star][-1].vel.d_xyz.to(u.km / u.s)
|
|
149
|
+
posf *= u.kpc
|
|
150
|
+
velf *= u.km / u.s
|
|
151
|
+
|
|
152
|
+
# create gala phase space positions based on them
|
|
153
|
+
wf = gd.PhaseSpacePosition(pos=posf.T, vel=velf.T)
|
|
154
|
+
|
|
155
|
+
# calculate the circular velocities at those locations
|
|
156
|
+
v_circ = potential.circular_velocity(q=wf.pos.xyz)
|
|
157
|
+
|
|
158
|
+
# get the cylindrical velocities and calculate the relative speeds compared to the circular velocity
|
|
159
|
+
v_R = wf.represent_as("cylindrical").vel.d_rho
|
|
160
|
+
with u.set_enabled_equivalencies(u.dimensionless_angles()):
|
|
161
|
+
v_T = wf.represent_as("cylindrical").vel.d_phi.to(1/u.Myr) * wf.represent_as("cylindrical").rho
|
|
162
|
+
v_z = wf.represent_as("cylindrical").vel.d_z
|
|
163
|
+
rel_speed = (((v_R - v_circ)**2 + v_T**2 + v_z**2)**(0.5)).to(u.km / u.s).value
|
|
164
|
+
|
|
165
|
+
return rel_speed
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def list_classes():
|
|
169
|
+
"""List out the available classes that are used in other functions"""
|
|
170
|
+
classes = [
|
|
171
|
+
{
|
|
172
|
+
"name": "runaway-t",
|
|
173
|
+
"full_name": "Theory Runaway",
|
|
174
|
+
"condition": ("Any star from a disrupted binary that has an instantaneous velocity > 30 km/s in "
|
|
175
|
+
"the frame of the binary")
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
"name": "runaway-o",
|
|
179
|
+
"full_name": "Observation runaway",
|
|
180
|
+
"condition": ("Any star from a disrupted binary that is moving with a Galactocentric velocity "
|
|
181
|
+
"> 30km/s relative to the local circular velocity at its location")
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
"name": "walkaway-t",
|
|
185
|
+
"full_name": "Theory Runaway",
|
|
186
|
+
"condition": ("Any star from a disrupted binary that has an instantaneous velocity < 30 km/s in "
|
|
187
|
+
"the frame of the binary")
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
"name": "walkaway-o",
|
|
191
|
+
"full_name": "Observation walkaway",
|
|
192
|
+
"condition": ("Any star from a disrupted binary that is moving with a Galactocentric velocity "
|
|
193
|
+
"< 30km/s relative to the local circular velocity at its location")
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
"name": "widow",
|
|
197
|
+
"full_name": "Widowed Star",
|
|
198
|
+
"condition": ("Any star, or binary containing a star, that is/was a companion to a compact "
|
|
199
|
+
"object")
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
"name": "xrb",
|
|
203
|
+
"full_name": "X-ray binary",
|
|
204
|
+
"condition": ("Any binary with a star that is a companion to a compact object")
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
"name": "co",
|
|
208
|
+
"full_name": "Compact object",
|
|
209
|
+
"condition": ("Any compact object or binary containing a compact object")
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
"name": "stellar-merger-co",
|
|
213
|
+
"full_name": "Compact object from merger",
|
|
214
|
+
"condition": ("Any compact object resulting from a stellar merger")
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
"name": "dco",
|
|
218
|
+
"full_name": "Double compact object",
|
|
219
|
+
"condition": ("Any bound binary of two compact objects")
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
"name": "sdIa",
|
|
223
|
+
"full_name": "Single degenerate type Ia",
|
|
224
|
+
"condition": ("Any disrupted binary that contains a massless remnant that was once a white dwarf")
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
"name": "pisn",
|
|
228
|
+
"full_name": "Pair Instability Supernova",
|
|
229
|
+
"condition": ("Any binary that had a star with a pair instability supernova")
|
|
230
|
+
},
|
|
231
|
+
]
|
|
232
|
+
|
|
233
|
+
print("Any class with a suffix '-1' or '-2' applies to only the primary or secondary")
|
|
234
|
+
print("Available classes")
|
|
235
|
+
print("-----------------")
|
|
236
|
+
for c in classes:
|
|
237
|
+
print(f'{c["full_name"]} ({c["name"]})')
|
|
238
|
+
print(f' {c["condition"]}\n')
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def get_eddington_rate(mass, radiative_efficiency=None, radius=None):
|
|
242
|
+
"""Get the eddington accretion rate
|
|
243
|
+
|
|
244
|
+
One of either radiative efficiency or radius must be provided.
|
|
245
|
+
|
|
246
|
+
Parameters
|
|
247
|
+
----------
|
|
248
|
+
mass : :class:`~astropy.units.Quantity` [mass]
|
|
249
|
+
Mass
|
|
250
|
+
radiative_efficiency : :class:`~numpy.ndarray`, optional
|
|
251
|
+
Fraction of radiated mass that is accreted, by default None
|
|
252
|
+
radius : :class:`~astropy.units.Quantity` [length], optional
|
|
253
|
+
Radius, by default None
|
|
254
|
+
|
|
255
|
+
Returns
|
|
256
|
+
-------
|
|
257
|
+
eddington_rate : :class:`~astropy.units.Quantity` [mass / time]
|
|
258
|
+
Eddington accretion rate
|
|
259
|
+
|
|
260
|
+
Raises
|
|
261
|
+
------
|
|
262
|
+
ValueError
|
|
263
|
+
If neither radiative efficiency or radius are provided
|
|
264
|
+
"""
|
|
265
|
+
if radiative_efficiency is None and radius is None:
|
|
266
|
+
raise ValueError("Either `radiative_efficiency` or `radius` must be provided")
|
|
267
|
+
elif radiative_efficiency is None:
|
|
268
|
+
radiative_efficiency = (const.G * mass / (radius * const.c**2)).decompose()
|
|
269
|
+
return 4 * np.pi * const.G * mass * const.m_p / (radiative_efficiency * const.c * const.sigma_T)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def get_eddington_lum(mass):
|
|
273
|
+
"""Get the eddington luminosity for a given mass
|
|
274
|
+
|
|
275
|
+
Parameters
|
|
276
|
+
----------
|
|
277
|
+
mass : :class:`~astropy.units.Quantity` [mass]
|
|
278
|
+
Mass
|
|
279
|
+
|
|
280
|
+
Returns
|
|
281
|
+
-------
|
|
282
|
+
eddington_lum : :class:`~astropy.units.Quantity` [energy / time]
|
|
283
|
+
Eddington luminosity
|
|
284
|
+
"""
|
|
285
|
+
return 4 * np.pi * const.G * mass * const.c * const.m_p / const.sigma_T
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def get_schwarzchild_radius(mass):
|
|
289
|
+
"""Get the Schwarzchild radius for a given black hole mass
|
|
290
|
+
|
|
291
|
+
Parameters
|
|
292
|
+
----------
|
|
293
|
+
mass : :class:`~astropy.units.Quantity` [mass]
|
|
294
|
+
Mass
|
|
295
|
+
|
|
296
|
+
Returns
|
|
297
|
+
-------
|
|
298
|
+
r_s
|
|
299
|
+
Schwarzchild radius
|
|
300
|
+
"""
|
|
301
|
+
return 2 * const.G * mass / const.c**2
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def get_x_ray_lum(m_acc, r_acc, m_acc_dot, porb, kstar, m_don, RRLO_don):
|
|
305
|
+
"""Estimate the X-ray luminosity for a given binary
|
|
306
|
+
|
|
307
|
+
We use the prescription from
|
|
308
|
+
`Misra+2022 <https://ui.adsabs.harvard.edu/abs/2022arXiv220905505M/abstract>`_ to estimate the X-ray
|
|
309
|
+
luminosity for both RLOF/wind-fed XRBs and Be-XRBs.
|
|
310
|
+
|
|
311
|
+
Parameters
|
|
312
|
+
----------
|
|
313
|
+
m_acc : :class:`~astropy.units.Quantity` [mass]
|
|
314
|
+
Accretor mass
|
|
315
|
+
r_acc : :class:`~astropy.units.Quantity` [length]
|
|
316
|
+
Accretor radius
|
|
317
|
+
m_acc_dot : :class:`~astropy.units.Quantity` [mass / time]
|
|
318
|
+
Accretion rate
|
|
319
|
+
porb : :class:`~astropy.units.Quantity` [time]
|
|
320
|
+
Orbital period
|
|
321
|
+
kstar : `int`
|
|
322
|
+
Stellar type
|
|
323
|
+
m_don : :class:`~astropy.units.Quantity` [mass]
|
|
324
|
+
Donor mass
|
|
325
|
+
RRLO_don : :class:`~numpy.ndarray`
|
|
326
|
+
Donor radius in units of Roche Lobe radius
|
|
327
|
+
|
|
328
|
+
Returns
|
|
329
|
+
-------
|
|
330
|
+
rlof_wind_fed : :class:`~astropy.units.Quantity` [erg / s]
|
|
331
|
+
Luminosity for a RLOF/wind fed x-ray binary
|
|
332
|
+
be_xrb : :class:`~astropy.units.Quantity` [erg / s]
|
|
333
|
+
Luminosity for Be-XRB
|
|
334
|
+
"""
|
|
335
|
+
# use 3 R_S as the radius for black holes
|
|
336
|
+
r_acc[kstar == 14] = 3 * get_schwarzchild_radius(m_acc[kstar == 14])
|
|
337
|
+
|
|
338
|
+
# compute radiative efficiency and eddington limit for masses
|
|
339
|
+
radiative_efficiency = (const.G * m_acc / (r_acc * const.c**2)).decompose()
|
|
340
|
+
eddington_rate = get_eddington_rate(m_acc, radiative_efficiency=radiative_efficiency)
|
|
341
|
+
eddington_ratio = (m_acc_dot / eddington_rate).decompose()
|
|
342
|
+
|
|
343
|
+
# first calculate for RLOF/wind fed XRBs
|
|
344
|
+
rlof_wind_fed = np.zeros(len(m_acc)) * u.erg / u.s
|
|
345
|
+
|
|
346
|
+
# handle sub eddington accretion
|
|
347
|
+
regular = eddington_ratio < 1.0
|
|
348
|
+
rlof_wind_fed[regular] = radiative_efficiency[regular] * m_acc_dot[regular] * const.c**2
|
|
349
|
+
|
|
350
|
+
# handle super Eddington
|
|
351
|
+
super_edd = (eddington_ratio >= 1.0) & (eddington_ratio < 8.5)
|
|
352
|
+
rlof_wind_fed[super_edd] = get_eddington_lum(m_acc[super_edd]) * (1 + np.log(eddington_ratio[super_edd]))
|
|
353
|
+
|
|
354
|
+
# account for beaming effect in extreme super Eddington
|
|
355
|
+
super_duper_edd = eddington_ratio >= 8.5
|
|
356
|
+
b = 73 / eddington_ratio[super_duper_edd]**2
|
|
357
|
+
b[b < 3.2e-3] = 3.2e-3
|
|
358
|
+
rlof_wind_fed[super_duper_edd] = get_eddington_lum(m_acc[super_duper_edd])\
|
|
359
|
+
* (1 + np.log(eddington_ratio[super_duper_edd])) / b
|
|
360
|
+
|
|
361
|
+
# next calculate for Be=XRBs
|
|
362
|
+
be_xrb = np.zeros(len(m_acc)) * u.erg / u.s
|
|
363
|
+
|
|
364
|
+
# we only want to consider certain systems as potential Be-XRBs based on Misra+2022
|
|
365
|
+
be_xrb_mask = (porb > 10 * u.day) & (porb < 300 * u.day) & (m_don >= 6 * u.Msun)\
|
|
366
|
+
& (RRLO_don * 100 > 1.0) & (kstar == 13)
|
|
367
|
+
be_xrb[be_xrb_mask] = 10**(4.53 - 1.5 * np.log10(porb[be_xrb_mask].to(u.day).value)) * 1e35 * u.erg / u.s
|
|
368
|
+
|
|
369
|
+
return rlof_wind_fed, be_xrb
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import astropy.units as u
|
|
2
|
+
|
|
3
|
+
__all__ = ["identify_events"]
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def identify_events(full_bpp, full_kick_info):
|
|
7
|
+
"""Identify any events that occur in the stellar evolution that would affect the galactic evolution
|
|
8
|
+
|
|
9
|
+
.. note::
|
|
10
|
+
This function currently only considers supernovae when identifying events
|
|
11
|
+
|
|
12
|
+
Parameters
|
|
13
|
+
----------
|
|
14
|
+
full_bpp : :class:`~pandas.DataFrame`
|
|
15
|
+
Table of evolution phase events from COSMIC
|
|
16
|
+
full_kick_info : :class:`~pandas.DataFrame`
|
|
17
|
+
Table of information about kicks from COSMIC
|
|
18
|
+
|
|
19
|
+
Returns
|
|
20
|
+
-------
|
|
21
|
+
events : `list`
|
|
22
|
+
A list of events for each binary. `None` is returned if there are no pertinent events, a list of
|
|
23
|
+
dicts for a bound system and two lists for a disrupted system (to track each component)
|
|
24
|
+
"""
|
|
25
|
+
bin_nums = full_bpp["bin_num"].unique()
|
|
26
|
+
# remove anything that doesn't get a kick
|
|
27
|
+
full_kick_info = full_kick_info[full_kick_info["star"] > 0.0]
|
|
28
|
+
|
|
29
|
+
# mask for the rows that contain supernova events
|
|
30
|
+
full_bpp = full_bpp[full_bpp["evol_type"].isin([15, 16])]
|
|
31
|
+
|
|
32
|
+
# reduce to just the supernova rows and ensure we have the same length in each table
|
|
33
|
+
assert len(full_kick_info) == len(full_bpp)
|
|
34
|
+
|
|
35
|
+
events_list = [None for _ in range(len(bin_nums))]
|
|
36
|
+
for i, bin_num in enumerate(bin_nums):
|
|
37
|
+
if bin_num not in full_bpp.index:
|
|
38
|
+
continue
|
|
39
|
+
|
|
40
|
+
bpp = full_bpp.loc[[bin_num]]
|
|
41
|
+
kick_info = full_kick_info.loc[[bin_num]]
|
|
42
|
+
|
|
43
|
+
# check if the the binary is going to disrupt at any point
|
|
44
|
+
it_will_disrupt = (kick_info["disrupted"] == 1.0).any()
|
|
45
|
+
|
|
46
|
+
# iterate over the kick file and store the relevant information (for both if disruption will occur)
|
|
47
|
+
if it_will_disrupt:
|
|
48
|
+
events_1, events_2 = [], []
|
|
49
|
+
for j in range(len(kick_info)):
|
|
50
|
+
default_event = {
|
|
51
|
+
"time": bpp.iloc[j]["tphys"] * u.Myr,
|
|
52
|
+
"m_1": bpp.iloc[j]["mass_1"] * u.Msun,
|
|
53
|
+
"m_2": bpp.iloc[j]["mass_2"] * u.Msun,
|
|
54
|
+
"a": bpp.iloc[j]["sep"] * u.Rsun,
|
|
55
|
+
"ecc": bpp.iloc[j]["ecc"],
|
|
56
|
+
"delta_v_sys_xyz": [kick_info.iloc[j]["delta_vsysx_1"],
|
|
57
|
+
kick_info.iloc[j]["delta_vsysy_1"],
|
|
58
|
+
kick_info.iloc[j]["delta_vsysz_1"]] * u.km / u.s
|
|
59
|
+
}
|
|
60
|
+
events_1.append(default_event)
|
|
61
|
+
|
|
62
|
+
# for rows including the disruption or after it then the secondary component needs editing
|
|
63
|
+
if kick_info.iloc[j]["disrupted"] == 1.0:
|
|
64
|
+
events_2.append({
|
|
65
|
+
"time": bpp.iloc[j]["tphys"] * u.Myr,
|
|
66
|
+
"m_1": bpp.iloc[j]["mass_1"] * u.Msun,
|
|
67
|
+
"m_2": bpp.iloc[j]["mass_2"] * u.Msun,
|
|
68
|
+
"a": bpp.iloc[j]["sep"] * u.Rsun,
|
|
69
|
+
"ecc": bpp.iloc[j]["ecc"],
|
|
70
|
+
"delta_v_sys_xyz": [kick_info.iloc[j]["delta_vsysx_2"],
|
|
71
|
+
kick_info.iloc[j]["delta_vsysy_2"],
|
|
72
|
+
kick_info.iloc[j]["delta_vsysz_2"]] * u.km / u.s
|
|
73
|
+
})
|
|
74
|
+
else:
|
|
75
|
+
# otherwise just append the regular stuff
|
|
76
|
+
events_2.append(default_event)
|
|
77
|
+
events_list[i] = [events_1, events_2]
|
|
78
|
+
else:
|
|
79
|
+
# for bound binaries we just need a single list
|
|
80
|
+
events_list[i] = [{
|
|
81
|
+
"time": bpp.iloc[j]["tphys"] * u.Myr,
|
|
82
|
+
"m_1": bpp.iloc[j]["mass_1"] * u.Msun,
|
|
83
|
+
"m_2": bpp.iloc[j]["mass_2"] * u.Msun,
|
|
84
|
+
"a": bpp.iloc[j]["sep"] * u.Rsun,
|
|
85
|
+
"ecc": bpp.iloc[j]["ecc"],
|
|
86
|
+
"delta_v_sys_xyz": [kick_info.iloc[j]["delta_vsysx_1"],
|
|
87
|
+
kick_info.iloc[j]["delta_vsysy_1"],
|
|
88
|
+
kick_info.iloc[j]["delta_vsysz_1"]] * u.km / u.s
|
|
89
|
+
} for j in range(len(kick_info))]
|
|
90
|
+
return events_list
|