Source code for posydon.binary_evol.CE.step_CEE

"""Common envelope evolution step.

Calculates the evolution of a star in a BinaryStar object. It uses
the alpha-perscription and caclulates how much the orbit has to shrink in
order to expel its envelope. If in that separation, one of the star fills its
RL, then the system is considered a merger. Else the common envelope is lost
and we are left with a binary system with the core of the donor star (which
initiated the unstable CEE) and the core of the copmanion. For now it works
with the donor star being either a type of H_giant (with a He core) or
He_giant (with a CO core) and the companion star being either a MS or compact
object star, not contribyting to the CE.

If profiles exist we are able to calculate on the spot the lambda parameter
for the donor star, else we use the default values

Parameters
----------
binary : BinaryStar (An object of class BinaryStar defined in POSYDON)
verbose : Boolean
    In case we want information about the CEE  (the default is False).

"""


__authors__ = [
    "Emmanouil Zapartas <ezapartas@gmail.com>",
    "Devina Misra <devina.misra@unige.ch>",
    "Jaime Roman Garza <Jaime.Roman@etu.unige.ch>",
    "Simone Bavera <Simone.Bavera@unige.ch>",
    "Konstantinos Kovlakas <Konstantinos.Kovlakas@unige.ch>",
    "Jeffrey Andrews <jeffrey.andrews@northwestern.edu>",
    "Tassos Fragos <Anastasios.Fragkos@unige.ch>",
    "Matthias Kruckow <Matthias.Kruckow@unige.ch>",
]


import numpy as np
import pandas as pd
from posydon.utils import common_functions as cf
from posydon.utils import constants as const
from posydon.binary_evol.binarystar import BINARYPROPERTIES
from posydon.binary_evol.singlestar import STARPROPERTIES
from posydon.utils.common_functions import PATH_TO_POSYDON
from posydon.utils.common_functions import check_state_of_star
from posydon.utils.common_functions import calculate_lambda_from_profile, calculate_Mejected_for_integrated_binding_energy
from posydon.utils.posydonwarning import Pwarn


MODEL = {"prescription": 'alpha-lambda',
         "common_envelope_efficiency": 1.0,
         "common_envelope_lambda_default": 0.5,
         "common_envelope_option_for_lambda": 'lambda_from_grid_final_values',
         "common_envelope_option_for_HG_star": "optimistic",
         "common_envelope_alpha_thermal": 1.0,
         "core_definition_H_fraction": 0.1,     # with 0.01 no CE BBHs
         "core_definition_He_fraction": 0.1,
         "CEE_tolerance_err": 0.001,
         "verbose": False,
         "common_envelope_option_after_succ_CEE": 'core_not_replaced_noMT',
         "mass_loss_during_CEE_merged": False # If False, then no mass loss from this step for a merged star
                                              # If True, then we remove mass according to the alpha-lambda prescription
                                              # assuming a final separation where the inner core RLOF starts.
         # "core_replaced_noMT" for core_definition_H_fraction=0.01

         }


# common_envelope_option_for_lambda:
# 1) 'default_lambda': using for lambda the constant value of
# common_envelope_lambda_default parameter
# 2) 'lambda_from_grid_final_values': using lambda parameter from MESA history
# which was calulated ni the same way as method (5) below
# 3) 'lambda_from_profile_gravitational': calculating the lambda parameter
# from the donor's profile by using the gravitational binding energy from the
# surface to the core (needing "mass", and "radius" as columns in the profile)
# 4) 'lambda_from_profile_gravitational_plus_internal': as above but taking
# into account a factor of common_envelope_alpha_thermal * internal energy too
# in the binding energy (needing also "energy" as column in the profile)
# 5) 'lambda_from_profile_gravitational_plus_internal_minus_recombination':
# as above but not taking into account the recombination energy in the internal
# energy (needing also "y_mass_fraction_He", "x_mass_fraction_H",
# "neutral_fraction_H", "neutral_fraction_He", and "avg_charge_He" as column
# in the profile)
# the mass fraction of an element which is used as threshold to define a core,


STAR_STATE_POST_MS = [
    "H-rich_Core_H_burning",
    "H-rich_Shell_H_burning",
    "H-rich_Core_He_burning",
    "H-rich_Central_He_depleted",
    "H-rich_Central_C_depletion",
    "H-rich_non_burning",
    "accreted_He_non_burning"
]


STAR_STATE_POST_HeMS = [
    'accreted_He_Core_He_burning',
    'stripped_He_Core_He_burning',
    'stripped_He_Central_He_depleted',
    'stripped_He_Central_C_depletion',
    'stripped_He_non_burning'
]


[docs] class StepCEE(object): """Compute supernova final remnant mass, fallback fraction & stellar state. This consider the nearest neighboor of the He core mass of the star, previous to the collapse. Considering a set of data for which the He core mass of the compact object projenitos previous the collapse, the final remnant mass and final stellar state of the compact object is known. Parameters ---------- verbose : bool If True, the messages will be prited in the console. Keyword Arguments ----------------- prescription : str Prescription to use for computing the prediction of common enevelope evolution. Available options are: * 'alpha-lambda' : Considers the the alpha-lambda prescription described in [1]_ and [2]_ to predict the outcome of the common envelope evolution. If the profile of the donor star is available then it is used to compute the value of lambda. References ---------- .. [1] Webbink, R. F. (1984). Double white dwarfs as progenitors of R Coronae Borealis stars and Type I supernovae. The Astrophysical Journal, 277, 355-360. .. [2] De Kool, M. (1990). Common envelope evolution and double cores of planetary nebulae. The Astrophysical Journal, 358, 189-195. """ def __init__( self, prescription=MODEL['prescription'], common_envelope_efficiency=MODEL['common_envelope_efficiency'], common_envelope_lambda_default=MODEL[ 'common_envelope_lambda_default'], common_envelope_option_for_lambda=MODEL[ 'common_envelope_option_for_lambda'], common_envelope_option_for_HG_star=MODEL[ 'common_envelope_option_for_HG_star'], common_envelope_option_after_succ_CEE=MODEL[ 'common_envelope_option_after_succ_CEE'], common_envelope_alpha_thermal=MODEL[ 'common_envelope_alpha_thermal'], core_definition_H_fraction=MODEL[ 'core_definition_H_fraction'], core_definition_He_fraction=MODEL[ 'core_definition_He_fraction'], CEE_tolerance_err=MODEL['CEE_tolerance_err'], mass_loss_during_CEE_merged=MODEL['mass_loss_during_CEE_merged'], verbose=MODEL['verbose'], **kwargs): """Initialize a StepCEE instance.""" # read kwargs to initialize the class if kwargs: for key in kwargs: if key not in MODEL: raise ValueError(key + " is not a valid parameter name!") for varname in MODEL: default_value = MODEL[varname] setattr(self, varname, kwargs.get(varname, default_value)) else: self.prescription = prescription self.common_envelope_efficiency = common_envelope_efficiency self.common_envelope_lambda_default = \ common_envelope_lambda_default self.common_envelope_option_for_lambda = \ common_envelope_option_for_lambda self.common_envelope_option_for_HG_star = \ common_envelope_option_for_HG_star self.common_envelope_alpha_thermal = common_envelope_alpha_thermal self.core_definition_H_fraction = core_definition_H_fraction self.core_definition_He_fraction = core_definition_He_fraction self.CEE_tolerance_err = CEE_tolerance_err self.common_envelope_option_after_succ_CEE = \ common_envelope_option_after_succ_CEE self.mass_loss_during_CEE_merged = mass_loss_during_CEE_merged self.verbose = verbose self.path_to_posydon = PATH_TO_POSYDON def __call__(self, binary): """Perform the CEE step for a BinaryStar object.""" # Determine which star is the donor and which is the companion if binary.event in ["oCE1", "oDoubleCE1"]: donor_star = binary.star_1 comp_star = binary.star_2 star_to_merge = "1" elif binary.event in ["oCE2", "oDoubleCE2"]: donor_star = binary.star_2 comp_star = binary.star_1 star_to_merge = "2" else: raise ValueError("CEE does not apply if `event` is not " "`oCE1`, 'oDoubleCE1' or `oCE2`, 'oDoubleCE1'") # Check for double CE double_CE = binary.event in ["oDoubleCE1", "oDoubleCE2"] if self.verbose: print("binary.event : ", binary.event) print("common_envelope_efficiency : ", self.common_envelope_efficiency) print("common_envelope_option_for_lambda : ", self.common_envelope_option_for_lambda) # Check to make sure binary can go through a CE mergeable_donor = (donor_star.state in [ 'H-rich_Core_H_burning', 'stripped_He_Core_He_burning', 'accreted_He_Core_He_burning']) mergeable_HG_donor = ( self.common_envelope_option_for_HG_star == "pessimistic" and donor_star.state in ['H-rich_Shell_H_burning']) if mergeable_donor or mergeable_HG_donor: # system merges binary.state = 'merged' binary.event = "oMerging" + star_to_merge return # Calculate binary's evolution if self.prescription == 'alpha-lambda': # Determine lambda and associated CE parameters lambda1_CE, mc1_i, rc1_i, donor_type = self.calculate_lambda_CE( donor_star, verbose=self.verbose) if double_CE: lambda2_CE, mc2_i, rc2_i, comp_type = self.calculate_lambda_CE( comp_star, verbose=self.verbose) else: lambda2_CE = np.nan mc2_i = comp_star.mass rc2_i = 10**comp_star.log_R comp_type = "not_giant_companion" # Evolve binary self.CEE_simple_alpha_prescription( binary, donor_star, comp_star, lambda1_CE, mc1_i, rc1_i, donor_type, lambda2_CE, mc2_i, rc2_i, comp_type, double_CE=double_CE, verbose=self.verbose, common_envelope_option_after_succ_CEE=( self.common_envelope_option_after_succ_CEE), core_definition_H_fraction=self.core_definition_H_fraction, core_definition_He_fraction=self.core_definition_He_fraction, mass_loss_during_CEE_merged=self.mass_loss_during_CEE_merged) else: raise ValueError("Invalid common envelope prescription given.")
[docs] def calculate_lambda_CE(self, donor, verbose=False): """Calculate lambda_CE from an assumed constant value or from profile. If lambda_CE is calculated from donor's profile, we also pass a more accurate calculation of the donor core mass for the purposes of CEE. Parameters ---------- donor : instance of SingleStar The donor star to calculate pre-CE quantities. verbose : bool In case we want information about the CEE (the default is False). Returns ------- lambda_CE: float lambda_CE calculated either as an assumed constant value or calculated from profile mc1_i: float If lambda_CE is calculated from donor's profile, we also pass a more accurate calculation of the donor core mass for the purposes of CEE. rc1_i: float The radius of the donor's core """ m1_i = donor.mass radius1 = 10. ** donor.log_R if donor.state in STAR_STATE_POST_MS: # "H_Giant": donor_type = 'He_core' core_element_fraction_definition = self.core_definition_H_fraction if core_element_fraction_definition not in [0.01, 0.1, 0.3]: raise ValueError("He-core defintion should always be " "set to a H abundance of 1%, 10%, or 30%") elif donor.state in STAR_STATE_POST_HeMS: # "He_Giant": donor_type = 'CO_core' core_element_fraction_definition = self.core_definition_He_fraction if core_element_fraction_definition != 0.1: raise ValueError("CO-core should always be " "set to a He abudance of 10%") else: raise ValueError("type = %s of donor of CEE not recognized" % donor.state) if self.common_envelope_option_for_lambda == "default_lambda": lambda_CE = self.common_envelope_lambda_default if donor_type == 'He_core': mc1_i = donor.he_core_mass rc1_i = donor.he_core_radius elif donor_type == "CO_core": mc1_i = donor.co_core_mass rc1_i = donor.co_core_radius else: raise ValueError("type = %s of donor of CEE not recognized" % donor.state) elif (self.common_envelope_option_for_lambda == "lambda_from_grid_final_values"): if donor_type == 'CO_core': if core_element_fraction_definition == 0.1: lambda_CE = donor.lambda_CE_pure_He_star_10cent mc1_i = donor.m_core_CE_pure_He_star_10cent rc1_i = donor.r_core_CE_pure_He_star_10cent else: raise ValueError("An impossible core_definition was " "provided in calculate lambda function") elif donor_type == 'He_core': if core_element_fraction_definition == 0.01: lambda_CE = donor.lambda_CE_1cent mc1_i = donor.m_core_CE_1cent rc1_i = donor.r_core_CE_1cent elif core_element_fraction_definition == 0.1: lambda_CE = donor.lambda_CE_10cent mc1_i = donor.m_core_CE_10cent rc1_i = donor.r_core_CE_10cent elif core_element_fraction_definition == 0.3: lambda_CE = donor.lambda_CE_30cent mc1_i = donor.m_core_CE_30cent rc1_i = donor.r_core_CE_30cent else: raise ValueError("An impossible core_definition was " "provided in calculate lambda function") else: raise ValueError("An impossible CE donor-star type was " "provided in calculate lambda function") if verbose: print("verbose for lambda_from_grid_final_values") print("core_element_fraction_definition :", core_element_fraction_definition) print("lambda_CE : ", lambda_CE) print("donor state: ", donor.state) print("donor_type: ", donor_type) print("donor R, rc1_i (for CEE calculation), he_core_radius, " "co_core_radius = ", radius1, rc1_i, donor.he_core_radius, donor.co_core_radius) print("donor M, mc1_i (for CEE calculation), he_core_mass, " "co_core_mass = ", m1_i, mc1_i, donor.he_core_mass, donor.co_core_mass) elif donor.profile is None: Pwarn("Donor profile does not exist -- proceeding with " "default_lambda alpha-CE prescription", "ApproximationWarning") # like in the "default_lambda" option lambda_CE = self.common_envelope_lambda_default if donor_type == 'He_core': mc1_i = donor.he_core_mass rc1_i = donor.he_core_radius elif donor_type == "CO_core": mc1_i = donor.co_core_mass rc1_i = donor.co_core_radius else: raise ValueError("type = %s of donor of CEE not recognized" % donor.state) else: valid_options = ["lambda_from_profile_gravitational", "lambda_from_profile_gravitational_plus_internal", "lambda_from_profile_gravitational_plus_" + "internal_minus_recombination"] if self.common_envelope_option_for_lambda not in valid_options: raise ValueError("common_envelope_option_for_lambda not a " "valid option - Should never occur") ''' lambda_CE, mc1_i, rc1_i = \ self.calculate_lambda_from_profile_duringstepCEE( donor, core_element_fraction_definition, verbose) ''' lambda_CE, mc1_i, rc1_i = calculate_lambda_from_profile( profile=donor.profile, donor_star_state=donor.state, m1_i=m1_i, radius1=radius1, common_envelope_option_for_lambda=( self.common_envelope_option_for_lambda), core_element_fraction_definition=( core_element_fraction_definition), ind_core=None, common_envelope_alpha_thermal=( self.common_envelope_alpha_thermal), tolerance=self.CEE_tolerance_err, verbose=verbose) return lambda_CE, mc1_i, rc1_i, donor_type
[docs] def CEE_simple_alpha_prescription( self, binary, donor, comp_star, lambda1_CE, mc1_i, rc1_i, donor_type, lambda2_CE, mc2_i, rc2_i, comp_type, double_CE=False, verbose=False, common_envelope_option_after_succ_CEE=MODEL[ 'common_envelope_option_after_succ_CEE'], core_definition_H_fraction=MODEL['core_definition_H_fraction'], core_definition_He_fraction=MODEL['core_definition_He_fraction'], mass_loss_during_CEE_merged=MODEL['mass_loss_during_CEE_merged']): """Apply the alpha-lambda common-envelope prescription. It uses energetics to calculate the shrinakge of the orbit (and possible merger) of the two components, as well as upadate their new masses, during a common-envelope phase. Parameters ---------- binary : BinaryStar object Instance of the binary to evolve. donor : SingleStar object The donor star comp_star : SingleStar object The companion star lambda1_CE : float Lambda previously calculated for the donor's envelope binding energy. mc1_i : float core mass of the donor rc1_i : float core radius of the donor donor_type : string String dictating whether the donor has a H-envelope or He-envelope lambda2_CE : float Lambda previously calculated for the companion's envelope binding energy. mc2_i : float core mass of the companion rc2_i : float core radius of the companion comp_type : string String dictating whether the companion has a H-envelope or He-envelope. double_CE : bool In case we have a double CE situation. verbose : bool In case we want information about the CEE. common_envelope_option_after_succ_CEE: str Options are: 1) "core_replaced_noMT" he_core_mass/radius (or co_core_mass/radius for CEE of stripped_He*) are replaced according to the new core boundary used for CEE (based on core_definition_H/He_fraction) but no other change in period after succesful ejection at alpha-lambda prescription. 2) "core_not_replaced_noMT" he_core_mass/radius (or co_core_mass/radius for CEE of stripped_He*) staying as preCEE and no other change in period after succesful ejection at alpha-lambda prescription. 3) "core_not_replaced_stableMT" he_core_mass/radius (or co_core_mass/radius for CEE of stripped_He*) staying as preCEE and after succesful ejection at alpha-lambda prescription, we assume an instantaneous stableMT phase (non-conservative, with mass lost from accretor) from the donor (or from donor and simultaneously the accretor at double_CE), taking away the extra "core" mass as defined by the core boundary used for CEE (based on core_definition_H/He_fraction). 4) "core_not_replaced_windloss" he_core_mass/radius (or co_core_mass/radius for CEE of stripped_He*) staying as preCEE and after succesful ejection at alpha-lambda prescription, we assume a instantaneous windloss phase from the donor (or from donor and simultaneously the accretor at double_CE), taking away the extra "core" mass as defined by the core boundary used for CEE (based on core_definition_H/He_fraction). core_definition_H_fraction: float The value of the H abundance to define the envelope-core boundary in He_cores. core_definition_He_fraction: float The value of the He abundance to define the envelope-core boundary in CO_cores. mass_loss_during_CEE_merged: Boolean If False, then no mass loss from this step for a merged CEE If True, then we remove mass according to the alpha-lambda prescription assuming a final separation where the inner core(s) RLOF starts, and the same lambda(s) """ # Get star properties m1_i = donor.mass radius1 = 10**donor.log_R state1_i = donor.state m2_i = comp_star.mass radius2 = 10**comp_star.log_R if verbose: print("Pre CE") print("binary event :", binary.event) print("double CEE : ", double_CE) print("donor state: ", state1_i) print("donor mass / core mass = ", m1_i, mc1_i) print("companion state: ", comp_star.state) print("companion mass / core mass = ", m2_i, mc2_i) # Get binary parameters period_i = binary.orbital_period alpha_CE = self.common_envelope_efficiency # calculate evolution of the orbit if pd.isna(lambda1_CE): ebind_i = 0.0 else: ebind_i = (-const.standard_cgrav / lambda1_CE * (m1_i * const.Msun * (m1_i - mc1_i) * const.Msun) / (radius1 * const.Rsun)) if (double_CE and (not pd.isna(lambda2_CE))): ebind_i += (-const.standard_cgrav / lambda2_CE * (m2_i * const.Msun * (m2_i - mc2_i) * const.Msun) / (radius2 * const.Rsun)) separation_i = const.Rsun * cf.orbital_separation_from_period( period_i, m1_i, m2_i) # in cgs units eorb_i = (-0.5 * const.standard_cgrav * m1_i * const.Msun * m2_i * const.Msun / separation_i) # TODO use core masses or total masses? eorb_postCEE = eorb_i + ebind_i/alpha_CE separation_postCEE = (-0.5 * const.standard_cgrav * mc1_i * const.Msun * mc2_i * const.Msun / eorb_postCEE) # Check to make sure final orbital separation is positive if not (separation_postCEE > -self.CEE_tolerance_err): raise ValueError("CEE problem, negative postCEE separation") if verbose: print("CEE alpha-lambda prescription") print("ebind_i", ebind_i) print("eorb_i", eorb_i) print("eorb_postCEE", eorb_postCEE) print("DEorb", eorb_postCEE - eorb_i) print("separation_i in Rsun", separation_i/const.Rsun) print("separation_postCEE in Rsun", separation_postCEE/const.Rsun) # now we check if the roche Lobe of any of the cores that spiralled-in # will be filled if reached this final separation RL1 = cf.roche_lobe_radius(mc1_i, mc2_i, separation_postCEE/const.Rsun) RL2 = cf.roche_lobe_radius(mc2_i, mc1_i, separation_postCEE/const.Rsun) if verbose: print("donor radius / core radius / RL1:", radius1, rc1_i, RL1) print("companion radius / core radius / RL2:", radius2, rc2_i, RL2) # Check if binary merges or survives if ((rc1_i - RL1) < self.CEE_tolerance_err and (rc2_i - RL2) < self.CEE_tolerance_err): # system survives CEE, the whole CE is ejected # and the new orbital separation for the cores is returned if verbose: print("system survives CEE, the whole CE is ejected and the " "new orbital separation for the cores is returned") # possible change of core masses if common_envelope_option_after_succ_CEE in ["core_replaced_noMT"]: mc1_f = mc1_i rc1_f = rc1_i mc2_f = mc2_i rc2_f = rc2_i if verbose: print("m1 core mass pre CEE (He,CO) / to after CEE : ", donor.he_core_mass, donor.co_core_mass, mc1_f) print("m1 core radius pre CEE (He,CO) / to after CEE : ", donor.he_core_radius, donor.co_core_radius, rc1_f) print("m2 core mass pre CEE (He,CO) / to after CEE : ", comp_star.he_core_mass, comp_star.co_core_mass, mc2_f ) print("m2 core radius pre CEE (He,CO) / to after CEE : ", comp_star.he_core_radius, comp_star.co_core_radius, rc2_f) elif common_envelope_option_after_succ_CEE in [ "core_not_replaced_noMT", "core_not_replaced_stableMT", "core_not_replaced_windloss"]: if donor_type == 'He_core': mc1_f = donor.he_core_mass rc1_f = donor.he_core_radius elif donor_type == "CO_core": mc1_f = donor.co_core_mass rc1_f = donor.co_core_radius if mc1_f > mc1_i: mc1_f = mc1_i Pwarn( "The final donor core mass (even after stable, postCEE MT)" " is higher than the postCEE core mass. Now equalizing to " "postCEE mass", "ApproximationWarning") if not double_CE: mc2_f = mc2_i rc2_f = rc2_i else: if comp_type == 'He_core': mc2_f = comp_star.he_core_mass rc2_f = donor.he_core_radius elif comp_type == "CO_core": mc2_f = comp_star.co_core_mass rc2_f = donor.co_core_radius elif comp_type == "not_giant_companion": mc2_f = comp_star.mass rc2_f = 10.**(comp_star.log_R) if mc2_f > mc2_i: mc2_f = mc2_i Pwarn("The accretor's final core mass (even after " "non-conservative stable, postCEE MT) is " "higher that postCEE core mass. " "Now equalizing to postCEE mass", "ApproximationWarning") if verbose: print("difference between m1 core mass defined by CEE step" " / to the final one as pre CEE : ", mc1_f, mc1_i) print("difference between r1 core mass defined by CEE step" " / to the final one as pre CEE : ", rc1_f, rc1_i) print("difference between m2 core mass defined by CEE step" " / to the final one as pre CEE : ", mc2_f, mc2_i) print("difference between r2 core mass defined by CEE step" " / to the final one as pre CEE : ", rc2_f, rc2_i) else: raise ValueError( "Not accepted option in common_envelope_option_after_succ_" "CEE = {}, dont know how to proceed". format(common_envelope_option_after_succ_CEE)) # possible change of orbital period after CEE orbital_period_postCEE = cf.orbital_period_from_separation( separation_postCEE / const.Rsun, mc1_i, mc2_i) if common_envelope_option_after_succ_CEE in [ "core_replaced_noMT", "core_not_replaced_noMT"]: orbital_period_f = orbital_period_postCEE elif common_envelope_option_after_succ_CEE in [ "core_not_replaced_stableMT"]: # An assumed stable mass transfer case after postCEE with # fully non-conservative MT and mass lost from the vicinity # of the accretor: orbital_period_f = cf.period_change_stabe_MT( orbital_period_postCEE, Mdon_i=mc1_i, Mdon_f=mc1_f, Macc_i=mc2_i, alpha=0.0, beta=1.0) if double_CE: # a reverse stable MT assumed to be happening # at the same time orbital_period_f = cf.period_change_stabe_MT( orbital_period_f, Mdon_i=mc2_i, Mdon_f=mc2_f, Macc_i=mc1_i, alpha=0.0, beta=1.0) if verbose: print("during the assumed stable MT phase after postCE") print("with 'common_envelope_option_after_succ_CEE' :", common_envelope_option_after_succ_CEE) print("the orbit cahnged from postCEE : ", orbital_period_postCEE) print("to : ", orbital_period_f) elif common_envelope_option_after_succ_CEE in [ "core_not_replaced_windloss"]: # An assumed wind loss after postCEE with mass lost # from the vicinity of the donor orbital_period_f = cf.period_change_stabe_MT( orbital_period_postCEE, Mdon_i=mc1_i, Mdon_f=mc1_f, Macc_i=mc2_i, alpha=1.0, beta=0.0) if double_CE: # a wind mass loss from the 2nd star assumed to be # happening at the same time orbital_period_f = cf.period_change_stabe_MT( orbital_period_f, Mdon_i=mc2_i, Mdon_f=mc2_f, Macc_i=mc1_i, alpha=1.0, beta=0.0) if verbose: print("during the assumed windloss phase after postCE") print("with 'common_envelope_option_after_succ_CEE' :", common_envelope_option_after_succ_CEE) print("the orbit cahnged from postCEE : ", orbital_period_postCEE) print("to : ", orbital_period_f) separation_f = cf.orbital_separation_from_period(orbital_period_f, mc1_f, mc2_f) if state1_i == 'stripped_He_Central_He_depleted': if donor == binary.star_1: binary.event = 'CC1' elif donor == binary.star_2: binary.event = 'CC2' else: binary.event = None # Adjust binary properties binary.separation = separation_f binary.orbital_period = orbital_period_f binary.eccentricity = 0.0 binary.state = 'detached' # binary.event = None for key in BINARYPROPERTIES: # the binary attributes that are changed in the CE step if key not in ["separation", "orbital_period", "eccentricity", "state", "event"]: # the binary attributes that keep the same value from the # previous step if key not in ["time", "V_sys", "mass_transfer_case", "nearest_neighbour_distance"]: setattr(binary, key, np.nan) # the rest become np.nan stars = [donor] star_types = [donor_type] core_masses = [mc1_f] core_radii = [rc1_f] if verbose: print("CEE succesfully ejected") print("new orbital period = ", binary.orbital_period) print("binary event : ", binary.event) print("double CEE : ", double_CE) if double_CE: stars.append(comp_star) star_types.append(comp_type) core_masses.append(mc2_f) core_radii.append(rc2_f) # Adjust donor star properties for star, star_type, core_mass, core_radius in zip( stars, star_types, core_masses, core_radii): star.mass = core_mass star.log_R = np.log10(core_radius) attributes_changing = [ "mass", "log_R", "state" ] if star_type == 'He_core': if common_envelope_option_after_succ_CEE in [ "core_replaced_noMT"]: star.surface_h1 = core_definition_H_fraction else: star.surface_h1 = 0.01 star.he_core_mass = core_mass star.he_core_radius = core_radius if star.metallicity is None: star.surface_he4 = 1 - star.surface_h1 - 0.0142 else: star.surface_he4 = 1.0-star.surface_h1-star.metallicity star.log_LH = -1e99 attributes_changing.extend([ "surface_h1", "surface_he4", "log_LH", "log_LHe", 'he_core_mass', 'he_core_radius' ]) elif star_type == 'CO_core': star.he_core_mass = core_mass star.he_core_radius = core_radius star.co_core_mass = core_mass star.co_core_radius = core_radius star.surface_h1 = 0.0 if common_envelope_option_after_succ_CEE in [ "core_replaced_noMT"]: star.surface_he4 = core_definition_He_fraction else: star.surface_he4 = 0.1 star.log_LH = -1e99 star.log_LHe = -1e99 attributes_changing.extend([ "surface_h1", "surface_he4", "log_LH", "log_LHe", 'he_core_mass', 'he_core_radius', 'co_core_mass', 'co_core_radius' ]) elif star_type == "not_giant_companion": continue else: raise ValueError("Unrecognized star type:", star_type) state_old = star.state star.state = check_state_of_star(star) if verbose: print("star state of before/ after CE : ", state_old, star.state) for key in STARPROPERTIES: if key not in attributes_changing: # the singlestar attributes that are changed # in the CE step # the singlestar attributes that keep the same value # from previous step if not in attributes_changing if key not in [ "surface_h1", "surface_he4", "log_LH", "log_LHe", "he_core_mass", "he_core_radius", "co_core_mass", "co_core_radius", "center_h1", "center_he4", "center_c12", "center_o16", "center_n14", "metallicity", "log_LZ", "log_Lnuc", "c12_c12", "center_gamma", "avg_c_in_c_core"]: setattr(star, key, np.nan) # the rest become NaN's else: # system merges binary.state = 'merged' if binary.event in ["oCE1", "oDoubleCE1"]: binary.event = "oMerging1" if binary.event in ["oCE2", "oDoubleCE2"]: binary.event = "oMerging2" if verbose: print("system merges due to one of the two star's core filling" "its RL") print("Rdonor core vs RLdonor core = ", rc1_i, RL1) print("Rcompanion vs RLcompanion= ", rc2_i, RL2) Mejected_donor = 0.0 Mejected_comp = 0.0 if mass_loss_during_CEE_merged: # we calculate the ejected mass from part of the commone envelope, using # a_f = separation_postCEE so that one of the cores (or MS star) is filling its inner Roche lobe, # and assuming that lambda(Menvelope) ~ lamda(Mejected) although # Mejected < Menvelope (e.g. see Fig1 of Dewi+Tauris2000) if not double_CE and donor.profile is None: Mejected_donor = 0.0 Mejected_comp = 0.0 Pwarn("mass_loss_during_CEE_merged == True, but no profile found " "for the donor star. Proceeding with no partial mass ejection.", "ApproximationWarning") elif double_CE and (donor.profile is None or comp_star.profile is None): Mejected_comp = 0.0 Mejected_comp = 0.0 Pwarn("mass_loss_during_CEE_merged == True, but not profile found " "for the donor or companion star in double_CE. Proceeding with no partial mass ejection.", "ApproximationWarning") else: separation_for_inner_RLO1 = rc1_i / cf.roche_lobe_radius(mc1_i, mc2_i, a_orb=1) separation_for_inner_RLO2 = rc2_i / cf.roche_lobe_radius(mc2_i, mc1_i, a_orb=1) separation_before_merger = max( separation_for_inner_RLO1, separation_for_inner_RLO2 ) * const.Rsun if verbose: print("separation_before_merger (for the calculation of Mejected for the merger): ",separation_before_merger/ const.Rsun , "in Rsun") print("which is the max of RLO1 or RLO2 of the inner cores: ", separation_for_inner_RLO1 , separation_for_inner_RLO2, "in Rsun") E_orb_used_up_to_inner_RLOF = alpha_CE * ( - const.standard_cgrav * m1_i * const.Msun * m2_i * const.Msun/(2. * separation_i) + \ const.standard_cgrav * mc1_i * const.Msun * mc2_i * const.Msun/(2. * separation_before_merger) ) # We assume "lambda_from_profile_gravitational_plus_internal_minus_recombination" if not double_CE: Mejected_donor = calculate_Mejected_for_integrated_binding_energy(donor.profile, E_orb_used_up_to_inner_RLOF, mc1_i, rc1_i, m1_i, radius1) else: # in double_CE # Assuming that the ratio of orbital energy used for the partial ejection of each (common) envelope # is the same as the ratio of the initial envelope masses: WF = (m1_i - mc1_i)/ (m2_i - mc2_i + m1_i - mc1_i) # weight factor for 1: # = Mdonor,envelope / Mcomp,envelope Eorb_for_partial_ej_1 = E_orb_used_up_to_inner_RLOF * WF Mejected_donor = calculate_Mejected_for_integrated_binding_energy(donor.profile, Eorb_for_partial_ej_1, mc1_i, rc1_i, m1_i, radius1) Eorb_for_partial_ej_2 = E_orb_used_up_to_inner_RLOF * (1. - WF) Mejected_comp = calculate_Mejected_for_integrated_binding_energy(comp_star.profile, Eorb_for_partial_ej_2, mc2_i, rc2_i, m2_i, radius2) if verbose: print("Mejecta_donor = ", Mejected_donor, "in Msun compared to its initial envelope =", m1_i - mc1_i) print("Mejecta_comp = ", Mejected_comp, "in Msun compared to its initial envelope =", m2_i - mc2_i) ''' if not ( (Mejected_donor <= m1_i - mc1_i) and (Mejected_comp <= m2_i - mc2_i) ): raise Exception("Mejected_donor in double CEE with mass_loss_during_CEE_merged is found more than Menvelope") if not (Mejected_donor >= 0.): raise Exception("The root of the equation in double CEE with mass_loss_during_CEE_merged is found negative") ''' if (Mejected_donor > m1_i - mc1_i) or (Mejected_comp > m2_i - mc2_i): Mejected_donor = (m1_i - mc1_i) -0.01 # at least this value of envelope is left. Mejected_comp = (m2_i - mc2_i) -0.01 Pwarn("M_ejected of at least one star in double CEE is found to be more than the initial envelope. " "Reducing both to their initial_envelope - 0.01 Msun", "ApproximationWarning") donor.mass = m1_i - Mejected_donor donor.log_R = np.nan comp_star.mass = m2_i - Mejected_comp comp_star.log_R = np.nan if donor_type == 'CO_core': donor.he_core_mass = m1_i - Mejected_donor donor.he_core_radius = np.nan comp_star.he_core_mass = m2_i - Mejected_comp comp_star.he_core_radius = np.nan if verbose: print("The mass loss during merging_CEE is: ", mass_loss_during_CEE_merged) print("so m1_i , m1_f = ", m1_i, donor.mass) print("so m2_i , m2_f = ", m2_i, comp_star.mass) return