"""Collapse the profile of a star object into a BH.
This script is based on the physics explained in Appendix D of Bavera+2020.
"""
import numpy as np
from scipy import integrate
import posydon.utils.constants as const
from posydon.utils.gridutils import find_index_nearest_neighbour
from posydon.utils.limits_thresholds import NEUTRINO_MASS_LOSS_UPPER_LIMIT
__authors__ = [
"Simone Bavera <Simone.Bavera@unige.ch>",
"Emmanouil Zapartas <ezapartas@gmail.com>",
"Scott Coughlin <scottcoughlin2014@u.northwestern.edu>",
"Devina Misra <devina.misra@unige.ch>",
"Matthias Kruckow <Matthias.Kruckow@unige.ch>",
]
__credits__ = [
'Aldo Batta <aldobatta@gmail.com>',
]
[docs]
def get_ejecta_element_mass_at_collapse(star, compact_object_mass, verbose):
"""Calculate the masses of H1, He4, and O16 in the ejecta.
Parameters
----------
star : object
Star object of a collapsing star containing the MESA profile.
compact_object_mass : float
The mass of the compact object (in Msun), hence all above this mass will be ejected.
verbose : bool
If `True`, it prints some informations.
Returns
-------
h1_mass_ej : float
Hydrogen mass in the ejecta. (in Msun)
he4_mass_ej : float
Helium mass in the ejecta. (in Msun)
"""
# read star quantities
profile_mass_all = star.profile['mass'][::-1] # cell outer total mass in Msun
# shell's mass
dm_all = profile_mass_all[1:] - profile_mass_all[:-1]
if 'x_mass_fraction_H' in star.profile.dtype.names:
XH_all = star.profile['x_mass_fraction_H'][::-1] # h1 mass fraction
if 'y_mass_fraction_He' in star.profile.dtype.names:
YHe_all = star.profile['y_mass_fraction_He'][::-1] # he4 mass fraction
#print("profile_mass_all[-1], compact_object_mass", profile_mass_all[-1], compact_object_mass)
if profile_mass_all[-1] <= compact_object_mass:
# This catches the case that all the star's profile is collapsed.
h1_mass_ej = 0.0
he4_mass_ej = 0.0
else:
# Find the index where the profile mass exceeds the compact object mass
i_rem = np.argmax(profile_mass_all > compact_object_mass)
#print("i_rem, len(dm_all)", i_rem, len(dm_all))
#print("mass coordinate above which it is ejected", profile_mass_all[i_rem])
# Ensure the index is within bounds
if i_rem < len(dm_all):
# Calculate the ejected mass of H1 and He4
dm_ejected = dm_all[i_rem:]
XH_ejected = XH_all[i_rem + 1:] # +1 because dm_all has one less element than profile_mass_all
YHe_ejected = YHe_all[i_rem + 1:]
# Calculate the ejected masses only if the lengths match
if len(dm_ejected) == len(XH_ejected) == len(YHe_ejected):
h1_mass_ej = np.sum(dm_ejected * XH_ejected)
he4_mass_ej = np.sum(dm_ejected * YHe_ejected)
else:
h1_mass_ej = 0.0
he4_mass_ej = 0.0
print("Warning: Mismatch in array lengths, cannot calculate ejected masses accurately.")
else:
h1_mass_ej = 0.0
he4_mass_ej = 0.0
print("Warning: Index out of bounds, cannot calculate ejected masses.")
#print(h1_mass_ej, he4_mass_ej, np.sum(dm_ejected))
return h1_mass_ej, he4_mass_ej
[docs]
def get_initial_BH_properties(star, mass_collapsing, mass_central_BH,
neutrino_mass_loss, max_neutrino_mass_loss,
verbose):
"""Collapse directly the center of the star and return useful quantities.
Parameters
----------
star : object
Star object of a collapsing star containing the MESA profile.
mass_collapsing : float
Remnant barionic mass in M_sun collapsing to form the BH. This is the
mass left to collapse after applying a supernova prescriptions,
see e.g., rapid and delayed mechanisms of Fryer et al. (2012).
mass_central_BH : float
Mass of the central stellar layers (in M_sun) collasping directly to
form a proto BH.
neutrino_mass_loss : float
Mass (in M_sun) lost through neutrinos in the formation of the
central BH.
max_neutrino_mass_loss : float
Maximum mass (in M_sun) lost thorugh neutrinos.
verbose : bool
If `True`, it prints some informations.
Returns
-------
mass_initial_BH : float
Mass of the initial BH in units of g.
a_initial_BH : float
Dimensionless spin of the initial BH.
J_initial_BH : float
Angular momentum of the initial BH in g*cm^2/s.
angular_frequency_i : array floats
Shell's angular frequencies in s^-1 collapsing onto the
initially-formed BH.
enclosed_mass_i : array floats
Shell's enclosed masses in g collapsing onto the initially formed BH.
radius_i : array floats
Shell's radii in cm collapsing onto the initially formed BH.
density_i : array floats
Shell's densities in g/cm^3 collapsing onto the initially formed BH.
dm_i : array floats
Shell's masses in g collapsing onto the initially formed BH.
dm_i : array floats
Shell's width in cm collapsing onto the initially formed BH.
"""
if neutrino_mass_loss < 0.:
raise ValueError(
'Something went wrong, neutrino_mass_loss must be positive!')
if neutrino_mass_loss > max_neutrino_mass_loss:
raise ValueError(
'Something went wrong, ',
'max_neutrino_mass_loss = {:2.2f} Msun'.format(
max_neutrino_mass_loss),
'while neutrino_mass_loss = {:2.2f} Msun'.format(
neutrino_mass_loss), 'was passed!')
# load units and convert to CGS units
G = const.standard_cgrav
c = const.clight
Mo = const.Msun
Ro = const.Rsun
mass_central_BH *= Mo
neutrino_mass_loss *= Mo
# read star quantities
enclosed_mass_all = star.profile['mass'][::-1]*Mo # cell outer total mass
radius_all = star.profile['radius'][::-1] * Ro # cell outer radius
density_all = 10**(star.profile['logRho'][::-1]) # cell density
angular_frequency_all = star.profile['omega'][::-1] # cell angular freq.
if 'he4' in star.profile.dtype.names:
# he3_all = star.profile['he3'][::-1] # he4 mass fraction
he4_all = star.profile['he4'][::-1] # he4 mass fraction
he3_all = np.zeros(len(enclosed_mass_all)) # ENHANCEMENT support he3
else:
he4_all = np.zeros(len(enclosed_mass_all))
he3_all = np.zeros(len(enclosed_mass_all))
# cut the ejected layers of the profile due to the SN event:
# index of the layers up to m_rembar from Fryer prescription
# (+1 for the range)
if enclosed_mass_all[-1] / Mo <= mass_collapsing:
# This catches the case that all the star's profile is callapsed.
# Note that the 'mass' of the MESA profile is the enclosed mass of that
# shell; the mass of the whole star is then
# star.profile['mass'][::-1][-1] + dm,
# where dm is the mass of the last shell.
i_rem = len(enclosed_mass_all)
else:
i_rem = np.argmax(enclosed_mass_all/Mo > mass_collapsing) + 1
enclosed_mass = enclosed_mass_all[:i_rem]
radius = radius_all[:i_rem]
density = density_all[:i_rem]
angular_frequency = angular_frequency_all[:i_rem]
he3 = he3_all[:i_rem]
he4 = he4_all[:i_rem]
# max he mass ejected in the SN
dm_SN = enclosed_mass_all[i_rem+1:] - enclosed_mass_all[i_rem:-1]
max_he_mass_ejected_SN = sum((he3_all[i_rem:-1] + he4_all[i_rem:-1])*dm_SN)
# find index containing the mass MBH_0 (in CGS)
index_initial_BH = find_index_nearest_neighbour(enclosed_mass,
mass_central_BH)
# mass of the initial BH collapsing directy assuming that
# neutrino_mass_loss is lost thorugh neutrinos in the formation of
# the central BH, note: this neutrinos are carring away angular
# momentum proportional to the neutrino_mass_loss/mass_central_BH
mass_initial_BH = enclosed_mass[index_initial_BH] - neutrino_mass_loss
# Cut input arrays to consider only the shells AFTER the formation of the
# initial BH note add 1 to get the range after the value index_initial_BH
angular_frequency_i = angular_frequency[index_initial_BH + 1:]
enclosed_mass_i = enclosed_mass[index_initial_BH + 1:]
radius_i = radius[index_initial_BH + 1:]
density_i = density[index_initial_BH + 1:]
he3_i = he3[index_initial_BH + 1:]
he4_i = he4[index_initial_BH + 1:]
# shell's mass
dm = enclosed_mass[1:] - enclosed_mass[:-1]
# shell's width
dr = radius[1:] - radius[:-1]
# dm has len(enclosed_mass_initial_BH) = len(dM_initial_BH)-1
dm_i = dm[index_initial_BH:]
dr_i = dr[index_initial_BH:]
# Compute the angular momentum of the initial BH by solving eq. 1 of
# Batta & Ramirez 2019:
# J_i_BH = int Omega(r) r^2 sin^2(t) dm dr
# = iint 2pi Omega(r) r^4 sin^3(t) rho(r) dt dr
# = 2pi int_0^pi sin^3(t) dt int_0^M_core Omega(r) r^4 rho(r) dr
# =: 2pi * temp1 * temp2
def f_temp1(t):
return np.sin(t)**3
temp1 = integrate.quad(f_temp1, 0, np.pi)[0]
# f_nu_AM is a rescaling the J_initial_BH. This account for the
# angular momentum lost thorugh neutrinos, which under the assumption
# of efficient AM transport is really low.
f_nu_AM = mass_initial_BH / enclosed_mass[index_initial_BH]
f_temp2 = (f_nu_AM*density[:index_initial_BH+1]
* angular_frequency[:index_initial_BH+1]
* radius[:index_initial_BH+1]**4)
temp2 = integrate.simpson(f_temp2, x=radius[:index_initial_BH + 1])
J_initial_BH = 2 * np.pi * temp1 * temp2
# dimensless spinn of the initial BH
a_initial_BH = J_initial_BH * c / (G * mass_initial_BH**2)
if verbose:
print('')
print('Initializing the BH properties for 1D collapse.')
print('')
print('The BH formed from the ',
round(mass_central_BH / Mo,
2), 'innermost solar masses has mass',
round(mass_initial_BH / Mo,
2), 'M_sun (', neutrino_mass_loss / Mo,
'M_sun were lost thorugh neutrinos) and a = Jc/GM^2 =',
round(a_initial_BH, 4))
return [
mass_initial_BH, a_initial_BH, J_initial_BH, angular_frequency_i,
enclosed_mass_i, radius_i, density_i, dm_i, dr_i,
he3_i, he4_i, max_he_mass_ejected_SN
]
[docs]
def compute_isco_properties(a, m_BH):
"""Compute the BH innermost stable circular orbit (ISCO) parameters.
Parameters
----------
a : float
Dimnesionless BH spin.
m_BH : float
Mass of the BH in g.
Returns
-------
r_isco : float
Radius of the ISCO in CGS units (cm).
j_isco : float
Specific angular momentum at ISCO in CGS units (cm^2/s).
efficiency : float
Orbital energy efficiency at ISCO.
"""
# load units
G = const.standard_cgrav
c = const.clight
# eq. 3/4 in Batta & Ramirez-Ruiz (2019)
z1 = 1 + (((1 - a**2)**(1. / 3.))
* ((1 + a)**(1. / 3.) + (1 - a)**(1. / 3.)))
z2 = (3 * a**2 + z1**2)**(1. / 2.)
r_isco = (3 + z2 - np.sqrt((3 - z1) * (3 + z1 + 2 * z2)))
# note that eq. 5 is the equivalent of what is below expet for the fact
# that what is below is not defined for a=1
j_isco = (
(G * m_BH / c) * (r_isco**2 - 2 * a * r_isco**(0.5) + a**2)
/ (r_isco**(3./4.) * (r_isco**(3./2.) - 3 * r_isco**0.5 + 2 * a)**0.5)
)
# fraction of disk's mass accreted by BH
# (1-efficiency) of the rest mass is "radiated" away
efficiency = np.sqrt(1. - 2. / (3 * r_isco))
# assign CGS units
r_isco = (G * m_BH / c**2) * r_isco
return r_isco, j_isco, efficiency
[docs]
def do_core_collapse_BH(star,
mass_collapsing,
mass_central_BH=2.51,
neutrino_mass_loss=None,
max_neutrino_mass_loss=NEUTRINO_MASS_LOSS_UPPER_LIMIT,
verbose=False):
"""Do the core collapse of a star object with MESA profile provided.
Parameters
----------
star : object
Star object of a collapsing star containing the MESA profile.
mass_collapsing : float
Remnant barionic mass in M_sun collapsing to form the BH. This is the
mass left to collapse after applying a supernova prescriptions,
see e.g. rapid and delayed mechanisms of Fryer et al. (2012).
mass_central_BH : float
Mass of the central stellar layers (in M_sun) collasping directly to
form a proto BH.
neutrino_mass_loss : float
Mass (in M_sun) lost thorugh neutrinos in the formation of the central
BH.
max_neutrino_mass_loss : float
Maximum mass (in M_sun) lost thorugh neutrinos.
verbose : bool
If `True`, it prints some informations.
Returns
-------
M_BH_total : float
Mass of the final BH in M_sun.
a_BH_total : float
Dimensionless spin of the final BH.
M_BH_array : array floats
BH mass evelution in g.
a_BH_array : array floats
Dimensionless spin evolution.
J_accreted_array : array floats
Angular momentum accreted from a given shell by the BH in CGS units.
J_total_array : array floats
Total angular momentum in accreted shells plus BH's initial angular
momentum in CGS units.
J_disk_shell_array : array floats
Angular momentum accreted from the shell's part collapsing to form a
disk in CGS units.
radiation_eff_array : array floats
Fraction of accretion disk radiated away, this is one minus accretion
efficiency.
r_isco_array : array floats
Radius of the innermost stable circular orbit in cm.
j_isco_array : array floats
Specific angular momentum at the innermost stable circular orbit in
CGS.
M_direct_collapse_array : array floats
Cumulative mass accreted through direct collapse in g.
M_disk_array : array floats
Cumulative mass accreted thorugh the disk in g.
dm_direct_array : array floats
Shell's mass accreted through direct collapse in g.
dm_disk_array : array floats
Shell's mass accreted thorugh the disk in g.
j_shell_array : array floats
Shell's specific angular momentum in CGS.
M_total_array : array floats
Cumulative mass of shells and initial BH in g.
a_star_array : array floats
Dimensionless spin parameter of the star.
"""
# convert to CGS units
G = const.standard_cgrav
c = const.clight
Mo = const.Msun
# ===========================================================
# Assumes a BH is already formed with a certain spin and mass
# its spin parameter is a = Jc/GM^2 and could be > 1
# ===========================================================
# Extract results from Get_InitialBHProperties()
(M_BH, a_BH, J_BH, Omega_shells, enclosed_mass, radius_shells, rho_shells,
dm, dr, he3, he4, max_he_mass_ejected) = get_initial_BH_properties(
star, mass_collapsing, mass_central_BH, neutrino_mass_loss,
max_neutrino_mass_loss, verbose)
# check that there is matter falling onto the BH
if len(enclosed_mass) == 0:
arr = np.array([np.nan])
return [
M_BH / Mo, a_BH, np.nan, np.nan
#M_BH / Mo, a_BH, arr, arr, arr, arr, arr, arr,
#arr, arr, arr, np.array([0.]), arr, arr, arr, arr,
#arr, arr
]
# shell's specific angular momentum at equator
j_shells = Omega_shells * radius_shells**2
if a_BH > 0.99:
a_BH = 0.99
J_BH = a_BH * G * M_BH**2 / c
# get the initial r_isco, j_isco and orbital energy convertion efficiency
r_isco, j_isco, eff = compute_isco_properties(a_BH, M_BH)
# Initialize integrated quantities
M_direct_collapse = M_BH
M_disk = 0.
M_total = M_BH
dm_disk = 0.
dm_direct = 0.
J_total = J_BH
# =======================================================
# Initialize lists that will contain the BH's properties
# as a function of the collapsed shells and information
# of mass fraction of material with low j and high j
J_accreted_array = [J_BH] # angular momentum accreted by BH
J_total_array = [
J_total
] # total angular momentum in accreted shells + BH's initial J
M_BH_array = [M_BH] # BH mass
a_BH_array = [a_BH] # BH's spin
radiation_eff_array = [1 - eff] # fraction of accretion disk radiated away
r_isco_array = [r_isco] # radius of ISCO
j_isco_array = [j_isco
] # specific angular momentum at ISCO (prograde orbits)
M_direct_collapse_array = [
M_direct_collapse
] # integrated mass accreted through direct collapse
M_disk_array = [M_disk] # integrated mass accreted through disk
dm_disk_array = [dm_disk] # mass in shell with j > j_isco (forms a disk)
dm_direct_array = [dm_direct
] # mass in shell with j < j_isco (direct collapse)
M_total_array = [M_total] # integrated mass (shells + initial BH)
j_shell_array = [j_shells[0]] # shell's specific angular momentum
a_star_array = [a_BH] # star's spin parameter
J_disk_shell_array = [0.] # angular momentum of the shell's disk
# compute BH properties as each shell collapses
for i, value in enumerate(dm):
# get r_isco, j_isco, and orbital energy at isco
r_isco, j_isco, eff = compute_isco_properties(a_BH, M_BH)
# shell properties
j_shell = j_shells[i]
Omega_shell = Omega_shells[i]
r_shell = radius_shells[i]
dr_shell = dr[i]
dm_shell = dm[i]
rho_shell = rho_shells[i]
he3_shell = he3[i]
he4_shell = he4[i]
# Determine if the specific angular momentum of the shell can form
# a disk or not and update BH's properties
# All mass collapses directly to the BH
if j_shell < j_isco:
# eq. 9 of Batta & Ramirez-Ruiz (2019) for theta<theta_disk
# J_shell = 2 int Omega(r) sin^3(t) rho r^4 dr dt dphi
# = 4 pi Omega_r rho_r int_0^pi/2 sin^3(t) dt int_r-dr^r r^4 dr
# = 4 pi Omega_r rho_r temp1 temp2
def f_temp1(x):
return np.sin(x)**3
temp1 = integrate.quad(f_temp1, 0, np.pi / 2)[0]
def f_temp2(x):
return x**4
temp2 = integrate.quad(f_temp2, r_shell - dr_shell, r_shell)[0]
# angular momentum of entire shell: J_shell=J_direct
J_direct = 4 * np.pi * (Omega_shell * rho_shell) * temp1 * temp2
# Update BH's angular momentum content and mass
J_BH = J_BH + J_direct
M_BH = M_BH + dm_shell
# mass accreted through direct collapse
dm_direct = dm_shell
# mass forming a disk
dm_disk = 0.0
# Integrated mass from direct collapse
M_direct_collapse = M_direct_collapse + dm_direct
# angular momentum accreted from the disk
J_disk = 0. # There is no disk
# Portions of the shells with theta>theta_disk are forming a disk
else:
# theta_disk is the angle within which
# j_shell = Omega_shell * r^2 * sin(t))^2 < j_isco
# contains all material that will collapse directly to the BH
# eq. 7 of Batta & Ramirez-Ruiz (2019)
theta_disk = np.arcsin(np.sqrt(j_isco / (Omega_shell*r_shell**2)))
# eq. 9 of Batta & Ramirez-Ruiz (2019) for theta<theta_disk
# J_shell = J_direct + J_disk
def f_temp1(x):
return np.sin(x)**3
temp1 = integrate.quad(f_temp1, 0, theta_disk)[0]
def f_temp2(x):
return x**4
temp2 = integrate.quad(f_temp2, r_shell - dr_shell, r_shell)[0]
J_direct = 4 * np.pi * (Omega_shell * rho_shell) * temp1 * temp2
# shell's mass fraction that will collapse directly
# eq. 8 of Batta & Ramirez-Ruiz (2019)
# (typo corrected, see Bavera et al. (2020))
dm_direct = (1.0 - np.cos(theta_disk)) * dm_shell
# shell's mass fraction that will form an accretion disk
# note that we assume that only mass collapsing directy loses
# neutrinos
dm_disk = np.cos(theta_disk) * dm_shell
# angular momentum accreted thorugh the disk
# eq. 8 of Batta & Ramirez-Ruiz (2019)
J_disk = j_isco * dm_disk
# Update BH's angular momentum content and mass
J_BH = J_BH + J_disk + J_direct
M_BH = M_BH + dm_disk * eff + dm_direct
# Integrated mass from direct collapse
M_direct_collapse = M_direct_collapse + dm_direct
# Integrated mass from accretion disk
# eq. 8 of Batta & Ramirez-Ruiz (2019): only fraction eff is
# accreted, see Thorne (1974)
M_disk = M_disk + dm_disk * eff
# max He mass tht can be ejected during the disk formation
max_he_mass_ejected += dm_shell * (he3_shell + he4_shell)
# Update BH's spin parameter
a_BH = J_BH * c / (G * M_BH**2.)
# Shell's angular momentum (same as J_direct if j < j_isco ) assuming
J_shell = 8 * np.pi / 3. * temp2 * Omega_shell * rho_shell
M_total = M_total + dm_direct + dm_disk
J_total = J_total + J_shell
a_star = J_total * c / (G * M_total**2)
# Check if a > 1: this should not happen!
if a_BH > 1:
raise ValueError(
"We got a={:.5g} from shell {} containing {:.5g} M_sun".format(
a_BH, i, dm_shell / Mo))
# Append all quantities to the arrays
J_accreted_array.append(J_BH)
M_BH_array.append(M_BH)
a_BH_array.append(a_BH)
radiation_eff_array.append(1. - eff)
r_isco_array.append(r_isco)
j_isco_array.append(j_isco)
M_direct_collapse_array.append(M_direct_collapse)
M_disk_array.append(M_disk)
dm_disk_array.append(dm_disk)
dm_direct_array.append(dm_direct)
j_shell_array.append(j_shell)
M_total_array.append(M_total)
J_total_array.append(J_total)
a_star_array.append(a_star)
if j_shell < 1.00 * j_isco:
J_disk_shell_array.append(0.)
else:
J_disk_shell_array.append((J_shell - J_direct) / dm_disk)
# BH mass from the collapse of the entire star in Msun
M_BH_total = M_BH_array[-1] / Mo
# BH spin from the collapse of the entire star
a_BH_total = a_BH_array[-1]
# BH disk mass accreted in Msun
m_disk_accreted = M_disk_array[-1] / Mo
# BH disk mass radiate in Msun
m_disk_radiated = sum(np.array(dm_disk_array)*np.array(radiation_eff_array))/Mo
# max He mass tht can be ejected during the disk formation
max_he_mass_ejected /= Mo
if verbose:
print('')
print('Evolving BH properties in 1D collapse')
print('')
print('The BH formed from the collapse of the entire star with mass',
round(enclosed_mass[-1] / Mo, 2), 'M_sun has mass',
round(M_BH_total, 2), 'M_sun', 'and a = ', round(a_BH_total, 3),
'.')
print('A disk of total mass', round(M_disk_array[-1] / Mo, 2), 'M_sun',
'was formed around the BH.')
print('')
return [
M_BH_total,
a_BH_total,
m_disk_accreted,
m_disk_radiated,
# np.array(M_BH_array),
# np.array(a_BH_array),
# np.array(J_accreted_array),
# np.array(J_total_array),
# np.array(J_disk_shell_array),
# np.array(radiation_eff_array),
# np.array(r_isco_array),
# np.array(j_isco_array),
# np.array(M_direct_collapse_array),
# np.array(M_disk_array),
# np.array(dm_direct_array),
# np.array(dm_disk_array),
# np.array(j_shell_array),
# np.array(M_total_array),
# np.array(a_star_array),
# max_he_mass_ejected,
]