From 8b371f3605051dd3056e8b974fb470be847d720a Mon Sep 17 00:00:00 2001 From: Yuzhe Song Date: Wed, 28 Feb 2024 09:11:30 +1100 Subject: [PATCH 01/19] Attempted updates to NS.cpp to solve the issues with NS recycling --- src/NS.cpp | 276 ++++++++++++++++++++++++++++++++++++++---------- src/NS.h | 11 +- src/constants.h | 1 + 3 files changed, 232 insertions(+), 56 deletions(-) diff --git a/src/NS.cpp b/src/NS.cpp index fe73b8386..d12038905 100755 --- a/src/NS.cpp +++ b/src/NS.cpp @@ -350,7 +350,7 @@ void NS::CalculateAndSetPulsarParameters() { m_PulsarDetails.spinPeriod = CalculatePulsarBirthSpinPeriod(); // spin period in ms m_PulsarDetails.spinFrequency = _2_PI / (m_PulsarDetails.spinPeriod * SECONDS_IN_MS); m_PulsarDetails.birthPeriod = m_PulsarDetails.spinPeriod * SECONDS_IN_MS; // convert from ms to s - + std::cout << "BP: " << m_PulsarDetails.spinPeriod << " BB: " << m_PulsarDetails.magneticField << std::endl; m_MomentOfInertia_CGS = CalculateMomentOfInertiaCGS(); // in CGS g cm^2 // Note we convert neutronStarMomentOfInertia from CGS to SI here @@ -403,7 +403,7 @@ void NS::SpinDownIsolatedPulsar(const double p_Stepsize) { double term1 = magFieldLowerLimit_G * magFieldLowerLimit_G * p_Stepsize; double term2 = tau * magFieldLowerLimit_G * ( m_PulsarDetails.magneticField * TESLA_TO_GAUSS - initialMagField_G); double term3 = (tau / 2.0) * (TESLA_TO_GAUSS * TESLA_TO_GAUSS * (m_PulsarDetails.magneticField * m_PulsarDetails.magneticField) - (initialMagField_G * initialMagField_G)); - double Psquared = 2.0 * constant2 * (term1 - term2 - term3) + (initialSpinPeriod * initialSpinPeriod); + double Psquared = constant2 * (term1 - term2 - term3) + (initialSpinPeriod * initialSpinPeriod); double P_f = std::sqrt(Psquared); m_PulsarDetails.spinFrequency = _2_PI / P_f; // pulsar spin frequency @@ -413,11 +413,123 @@ void NS::SpinDownIsolatedPulsar(const double p_Stepsize) { double pDotTop = constant2 * TESLA_TO_GAUSS * TESLA_TO_GAUSS * m_PulsarDetails.magneticField * m_PulsarDetails.magneticField; double pDot = pDotTop / P_f; m_PulsarDetails.spinDownRate = -_2_PI * pDot / (P_f * P_f); - + //std::cout << "spindown fdot1, " << m_PulsarDetails.spinDownRate << " spindown fdot2, " << -_2_PI * pDot / (P_f * P_f) << std::endl; + //std::cout << "constant2: " << constant2 << " term1: " << term1 << " term2: " << term2 << " term3: " << term3 << " IP: " << initialSpinPeriod << std::endl; + //std::cout << "Timestep: "<< p_Stepsize << " radius^6: " << NSradius_6 << "B: " << m_PulsarDetails.magneticField << " m: " << m_Mass << " MOI: " << m_MomentOfInertia_CGS << "period: " << P_f << std::endl; m_AngularMomentum_CGS = m_PulsarDetails.spinFrequency * m_MomentOfInertia_CGS; // angular momentum of star in CGS } +/* + * Update the magnetic field and spins of neutron stars when it's accreting mass from companion. + * We carry out the calculations in this function using cgs units. + * This function is called in multiple situations in the NS::UpdateMagneticFieldAndSpin() function + * + * Modifies the following class member variables: + * + * m_Mass + * m_AngularMomentum_CGS + * m_PulsarDetails.spinFrequency + * m_PulsarDetails.magneticField + * m_PulsarDetails.spinDownRate + * + * + * void PulsarAccretion(const double p_Stepsize, const double p_MassGainPerTimeStep, const double p_Epsilon) + * @param [IN] p_Stepsize Timestep size for integration (in seconds) + * @param [IN] p_MassGainPerTimeStep Mass loss from the secondary for each iteration (in g) + * @param [IN] kappa Mass loss from the secondary for each iteration (in g) + * @param [IN] p_Epsilon Uncertainty due to mass loss + */ +DBL_DBL_DBL_DBL NS::PulsarAccretion(const double p_Stepsize, const double p_MassGainPerTimeStep, const double kappa, const double p_Epsilon) { + double initialMagField = m_PulsarDetails.magneticField; + double initialMagField_G = initialMagField * TESLA_TO_GAUSS; // (in G) + // (in G) + double magFieldLowerLimit_G = PPOW(10.0, OPTIONS->PulsarLog10MinimumMagneticField()) ; + //double kappa = OPTIONS->PulsarMagneticFieldDecayMassscale() * MSOL_TO_G; + //m_Mass = m_Mass + (p_MassGainPerTimeStep / MSOL_TO_G); + // This part of the code does pulsar recycling through accretion + // recycling happens for pulsar with spin period larger than 1 ms and in a binary system with mass transfer + // the pulsar being recycled is either in a common envolope, or should have started the recycling process in previous time steps. + double mass_g = m_Mass * MSOL_TO_G; // in g + double r_cm = m_Radius * RSOL_TO_KM * KM_TO_CM; // in cm + m_MomentOfInertia_CGS = CalculateMomentOfInertiaCGS() ; + double MoI = m_MomentOfInertia_CGS;// * CGS_SI; + double angularMomentum = m_AngularMomentum_CGS;// * CGS_SI; + double newPulsarMagneticField = (initialMagField_G - magFieldLowerLimit_G) * exp(-p_MassGainPerTimeStep / kappa) + magFieldLowerLimit_G; + std::cout << "IB: " << initialMagField_G << " MT: " << p_MassGainPerTimeStep << " kappa: " << kappa << " BMIN: " << magFieldLowerLimit_G << std::endl; + + // calculate the Alfven radius for an accreting neutron star + // see Equation 8 in arXiv:0903.3538v2 + double mDot = p_MassGainPerTimeStep / p_Stepsize; + double R_CM_6 = r_cm * r_cm * r_cm * r_cm * r_cm * r_cm; + double B_4 = newPulsarMagneticField * newPulsarMagneticField * newPulsarMagneticField * newPulsarMagneticField; + double R_a_top = 8.0 * R_CM_6 * R_CM_6 * B_4; + double R_a_bot = mass_g * mDot * mDot * (G * 1000.0); + double alfvenRadius = PPOW(R_a_top / R_a_bot, 1.0 / 7.0) / 2.0; + + // calculate the difference in the keplerian angular velocity and surface angular velocity of the neutron star in m + // see Equation 2 in 1994MNRAS.269..455J + double keplerianVelocityAtAlfvenRadius = std::sqrt(2.0 * (G * 1000.0) * mass_g / alfvenRadius); + double keplerianAngularVelocityAtAlfvenRadius = 4.0 * M_PI * keplerianVelocityAtAlfvenRadius / alfvenRadius; + double velocityDifference = keplerianAngularVelocityAtAlfvenRadius - m_PulsarDetails.spinFrequency; + + // calculate the change in angular momentum due to accretion + // see Equation 12 in arXiv:0805.0059/ Equation 8 in arxiv:1912.02415 + double Jdot = p_Epsilon * velocityDifference * alfvenRadius * alfvenRadius * mDot; + angularMomentum = angularMomentum + Jdot * p_Stepsize; + std::cout << "MT: " << p_MassGainPerTimeStep <<" B: "<< newPulsarMagneticField << " f: " << angularMomentum / MoI << " Ra: " << alfvenRadius << std::endl; + //std::cout << "accretion fdot1, " << CalculateSpinDownRate(m_PulsarDetails.spinFrequency, m_MomentOfInertia_CGS, m_PulsarDetails.magneticField, m_Radius * RSOL_TO_KM) << " accretion fdot2, " << Jdot/MoI << std::endl; + // if (utils::Compare(angularMomentum / MoI, 0.0) > 0) { + // std::cout<< "updated" <PulsarLog10MinimumMagneticField()) ; + double mass_g = m_Mass * MSOL_TO_G; // in g + double r_cm = m_Radius * RSOL_TO_KM * KM_TO_CM; + double MoI = m_MomentOfInertia_CGS;// * CGS_SI; + double angularMomentum = initial_am;// * CGS_SI; + double newPulsarMagneticField = (initialMagField_G - magFieldLowerLimit_G) * exp(-p_MassGainPerTimeStep / kappa) + magFieldLowerLimit_G; + //std::cout << "IB: " << initialMagField_G << " MT: " << p_MassGainPerTimeStep << " kappa: " << kappa << " BMIN: " << magFieldLowerLimit_G << std::endl; + + // calculate the Alfven radius for an accreting neutron star + // see Equation 8 in arXiv:0903.3538v2 + double mDot = p_MassGainPerTimeStep / p_Stepsize; + double R_CM_6 = r_cm * r_cm * r_cm * r_cm * r_cm * r_cm; + double B_4 = newPulsarMagneticField * newPulsarMagneticField * newPulsarMagneticField * newPulsarMagneticField; + double R_a_top = 8.0 * R_CM_6 * R_CM_6 * B_4; + double R_a_bot = mass_g * mDot * mDot * (G * 1000.0); + double alfvenRadius = PPOW(R_a_top / R_a_bot, 1.0 / 7.0) / 2.0; + + // calculate the difference in the keplerian angular velocity and surface angular velocity of the neutron star in m + // see Equation 2 in 1994MNRAS.269..455J + double keplerianVelocityAtAlfvenRadius = std::sqrt(2.0 * (G * 1000.0) * mass_g / alfvenRadius); + double keplerianAngularVelocityAtAlfvenRadius = 4.0 * M_PI * keplerianVelocityAtAlfvenRadius / alfvenRadius; + double velocityDifference = keplerianAngularVelocityAtAlfvenRadius - initial_f; + + // calculate the change in angular momentum due to accretion + // see Equation 12 in arXiv:0805.0059/ Equation 8 in arxiv:1912.02415 + double Jdot = p_Epsilon * velocityDifference * alfvenRadius * alfvenRadius * mDot; + angularMomentum = angularMomentum + Jdot * p_Stepsize; + //std::cout << "MT: " << p_MassGainPerTimeStep <<" B: "<< newPulsarMagneticField << " f: " << angularMomentum / MoI << " Ra: " << alfvenRadius << std::endl; + //std::cout << "accretion fdot1, " << CalculateSpinDownRate(m_PulsarDetails.spinFrequency, m_MomentOfInertia_CGS, m_PulsarDetails.magneticField, m_Radius * RSOL_TO_KM) << " accretion fdot2, " << Jdot/MoI << std::endl; + return std::make_tuple(newPulsarMagneticField * GAUSS_TO_TESLA, angularMomentum / MoI, Jdot / MoI, angularMomentum); +} + + /* * Update the magnetic field and spins of neutron stars in the following situations: * 1). @@ -444,60 +556,114 @@ void NS::SpinDownIsolatedPulsar(const double p_Stepsize) { */ void NS::UpdateMagneticFieldAndSpin(const bool p_CommonEnvelope, const bool p_RecycledNS, const double p_Stepsize, const double p_MassGainPerTimeStep, const double p_Epsilon) { - double initialMagField = m_PulsarDetails.magneticField; // (in T) - double magFieldLowerLimit = PPOW(10.0, OPTIONS->PulsarLog10MinimumMagneticField()) * GAUSS_TO_TESLA; - double kappa = OPTIONS->PulsarMagneticFieldDecayMassscale() * MSOL_TO_KG; + // double initialMagField = m_PulsarDetails.magneticField; // (in T) + // double magFieldLowerLimit = PPOW(10.0, OPTIONS->PulsarLog10MinimumMagneticField()) * GAUSS_TO_TESLA; + double kappa = OPTIONS->PulsarMagneticFieldDecayMassscale() * MSOL_TO_G; if ((!p_RecycledNS && !p_CommonEnvelope) || (!p_RecycledNS && utils::Compare(p_MassGainPerTimeStep, 0.0) == 0 )) { // these are the ''classical'' isolated pulsars + //std::cout << "start spinning down. The initial spin period at the timestep is " << _2_PI / m_PulsarDetails.spinFrequency << std::endl; SpinDownIsolatedPulsar(p_Stepsize); + //std::cout << "end of spinning down step. period is " << _2_PI / m_PulsarDetails.spinFrequency << std::endl; + } - else if (utils::Compare(m_PulsarDetails.spinFrequency, _2_PI * 1000.0) < 0 && - (p_RecycledNS || p_CommonEnvelope) && utils::Compare(p_MassGainPerTimeStep, 0.0) > 0) { - - // This part of the code does pulsar recycling through accretion - // recycling happens for pulsar with spin period larger than 1 ms and in a binary system with mass transfer - // the pulsar being recycled is either in a common envolope, or should have started the recycling process in previous time steps. - double mass_kg = m_Mass * MSOL_TO_KG; // in kg - double r_m = m_Radius * RSOL_TO_KM * KM_TO_M; // in metres - - double MoI_SI = m_MomentOfInertia_CGS * CGS_SI; - double angularMomentum_SI = m_AngularMomentum_CGS * CGS_SI; - - double newPulsarMagneticField = (initialMagField - magFieldLowerLimit) * exp(-p_MassGainPerTimeStep / 1000.0 / kappa) + magFieldLowerLimit; - - // calculate the Alfven radius for an accreting neutron star - // see Equation 8 in arXiv:0903.3538v2 - double mDot = p_MassGainPerTimeStep / 1000.0 / p_Stepsize; - double R_M_6 = r_m * r_m * r_m * r_m * r_m * r_m; - double B_4 = newPulsarMagneticField * newPulsarMagneticField * newPulsarMagneticField * newPulsarMagneticField; - double R_a_top = 8.0 * R_M_6 * R_M_6 * B_4; - double R_a_bot = mass_kg * mDot * mDot * G; - double alfvenRadius = PPOW(R_a_top / R_a_bot, 1.0 / 7.0); - - // calculate the difference in the keplerian angular velocity and surface angular velocity of the neutron star in m - // see Equation 2 in 1994MNRAS.269..455J - double keplerianVelocityAtAlfvenRadius = std::sqrt(2.0 * G * mass_kg / alfvenRadius); - double keplerianAngularVelocityAtAlfvenRadius = 4.0 * M_PI * keplerianVelocityAtAlfvenRadius / alfvenRadius; - double velocityDifference = keplerianAngularVelocityAtAlfvenRadius - m_PulsarDetails.spinFrequency; - - // calculate the change in angular momentum due to accretion - // see Equation 12 in arXiv:0805.0059/ Equation 8 in arxiv:1912.02415 - double Jdot = p_Epsilon * velocityDifference * alfvenRadius * alfvenRadius * mDot; - angularMomentum_SI = angularMomentum_SI + Jdot * p_Stepsize; - - if (utils::Compare(angularMomentum_SI / MoI_SI, 0.0) > 0) { - m_PulsarDetails.magneticField = newPulsarMagneticField; - m_PulsarDetails.spinFrequency = angularMomentum_SI / MoI_SI; - m_PulsarDetails.spinDownRate = Jdot / MoI_SI; - m_AngularMomentum_CGS = angularMomentum_SI / CGS_SI; - } - else { - SpinDownIsolatedPulsar(p_Stepsize); - } - } - else { - //In all other conditions, treat the pulsar as isolated. - SpinDownIsolatedPulsar(p_Stepsize); - } -} \ No newline at end of file + // Not checking spin period any more (utils::Compare(m_PulsarDetails.spinFrequency, _2_PI * 1000.0) < 0 ) + //as propeller effect should be included in the calculation + else if ((!p_CommonEnvelope) && utils::Compare(p_MassGainPerTimeStep, 0.0) > 0) { + //(p_RecycledNS || p_CommonEnvelope) + //if (!p_CommonEnvelope) { + //first try to do the spin up for RLOF systems. + std::cout << "RLOF Evolution" << std::endl; + std::cout << "start accreting " << p_MassGainPerTimeStep * 1000.0 << " "<< kappa << std::endl; + std::tuple Accretion_Results; + std::tuple Next_Accretion_Results; + + double divide_timestep_by = 100.0; + bool do_not_stop_looping = true; + while (do_not_stop_looping) { + int count = 0; + double new_timestep_size = p_Stepsize / divide_timestep_by; + double new_massGain = p_MassGainPerTimeStep * 1000.0 / divide_timestep_by ; + Accretion_Results = PulsarAccretion(new_timestep_size, new_massGain, kappa, p_Epsilon); + + for (int n = 1; n<= divide_timestep_by; n++){ + double last_B = 0; + double last_f = 0; + double last_fdot = 0; + double last_am = 0; + if (n==1) { + last_B = std::get<0>(Accretion_Results); + last_f = std::get<1>(Accretion_Results); + last_fdot = std::get<2>(Accretion_Results); + last_am = std::get<3>(Accretion_Results); + } + else { + last_B = std::get<0>(Next_Accretion_Results); + last_f = std::get<1>(Next_Accretion_Results); + last_fdot = std::get<2>(Next_Accretion_Results); + last_am = std::get<3>(Next_Accretion_Results); + } + Next_Accretion_Results = PulsarAccretion_V2(last_B, last_f, last_am, new_timestep_size, new_massGain, kappa, p_Epsilon); + //std::cout << std::get<1>(Next_Accretion_Results) << std::endl; + if (utils::Compare(std::get<1>(Next_Accretion_Results), 0.0) < 0) { + Next_Accretion_Results = Accretion_Results ; + break;} + else {count ++;} + } + if (count == divide_timestep_by){ + do_not_stop_looping = false; + } + else { + divide_timestep_by = divide_timestep_by * 2.0; + } + } + std::cout << "Total timestep for the accretion: " << divide_timestep_by << std::endl; + double new_timestep_size = p_Stepsize / divide_timestep_by; + double new_massGain = p_MassGainPerTimeStep * 1000.0 / divide_timestep_by ; + for (int n = 1; n<= divide_timestep_by; n++){ + double last_B = std::get<0>(Accretion_Results); + double last_f = std::get<1>(Accretion_Results); + double last_fdot = std::get<2>(Accretion_Results); + double last_am = std::get<3>(Accretion_Results); + + Accretion_Results = PulsarAccretion_V2(last_B, last_f, last_am, new_timestep_size, new_massGain, kappa, p_Epsilon); + + } + + + m_PulsarDetails.magneticField = std::get<0>(Accretion_Results); + m_PulsarDetails.spinFrequency = std::get<1>(Accretion_Results); + m_PulsarDetails.spinDownRate = std::get<2>(Accretion_Results); + m_AngularMomentum_CGS = std::get<3>(Accretion_Results); + std::cout << "at end of accretion, period is " << _2_PI/m_PulsarDetails.spinFrequency << std::endl; + } + + else if ((p_CommonEnvelope) && utils::Compare(p_MassGainPerTimeStep, 0.0) > 0) { + //Now we consider the common envelope casees + std::cout << "CE Evolution, starting period is " << _2_PI / m_PulsarDetails.spinFrequency << std::endl; + double initialMagField = m_PulsarDetails.magneticField; + double initialMagField_G = initialMagField * TESLA_TO_GAUSS; + double magFieldLowerLimit_G = PPOW(10.0, OPTIONS->PulsarLog10MinimumMagneticField()) ; + + double mass_g = m_Mass * MSOL_TO_G; // in g + double r_cm = m_Radius * RSOL_TO_KM * KM_TO_CM; // in cm + m_MomentOfInertia_CGS = CalculateMomentOfInertiaCGS() ; + double MoI = m_MomentOfInertia_CGS; + double angularMomentum = m_AngularMomentum_CGS; + double newPulsarMagneticField = (initialMagField_G - magFieldLowerLimit_G) * exp(-p_MassGainPerTimeStep / kappa) + magFieldLowerLimit_G; + double r_cm_3 = r_cm * r_cm * r_cm ; + double Jacc = MoI * PPOW(G * 1000.0 * mass_g /r_cm_3, 0.5) * p_MassGainPerTimeStep / mass_g ; //MoI * PPOW(G * 1000.0 * p_MassGainPerTimeStep / r_cm_3, 0.5) ; + double newAngularMomentum = angularMomentum + Jacc ; + m_PulsarDetails.magneticField = newPulsarMagneticField * GAUSS_TO_TESLA; + m_PulsarDetails.spinFrequency = newAngularMomentum / MoI ; + m_PulsarDetails.spinDownRate = (Jacc / p_Stepsize) / MoI; + m_AngularMomentum_CGS = newAngularMomentum ; + std::cout << "CE Accretion - f: " << newAngularMomentum / MoI << " and fdot: " << (Jacc / p_Stepsize) / MoI << std::endl; + std::cout << "CE Accretion - dm: " << p_MassGainPerTimeStep << " p: " << _2_PI / m_PulsarDetails.spinFrequency << std::endl; + } + + // else { + // //In all other conditions, treat the pulsar as isolated. + // SpinDownIsolatedPulsar(p_Stepsize); + // } +} diff --git a/src/NS.h b/src/NS.h index 9d5f0b2e8..4a61cbda7 100755 --- a/src/NS.h +++ b/src/NS.h @@ -86,6 +86,15 @@ class NS: virtual public BaseStar, public Remnants { bool ShouldEvolveOnPhase() const { return (m_Mass <= OPTIONS->MaximumNeutronStarMass()); } // Evolve as a neutron star unless mass > maximum neutron star mass (e.g. through accretion) void SpinDownIsolatedPulsar(const double p_Stepsize); + DBL_DBL_DBL_DBL PulsarAccretion(const double p_Stepsize, + const double p_MassGainPerTimeStep, + const double kappa, + const double p_Epsilon); + DBL_DBL_DBL_DBL PulsarAccretion_V2(const double initial_b, const double initial_p, const double initial_am, + const double p_Stepsize, + const double p_MassGainPerTimeStep, + const double kappa, + const double p_Epsilon); void UpdateMagneticFieldAndSpin(const bool p_CommonEnvelope, const bool p_RecycledNS, const double p_Stepsize, @@ -94,4 +103,4 @@ class NS: virtual public BaseStar, public Remnants { }; -#endif // __NS_h__ \ No newline at end of file +#endif // __NS_h__ diff --git a/src/constants.h b/src/constants.h index 064776f03..11c2094be 100755 --- a/src/constants.h +++ b/src/constants.h @@ -38,6 +38,7 @@ typedef unsigned long int OBJECT_I typedef std::vector DBL_VECTOR; typedef std::tuple DBL_DBL; typedef std::tuple DBL_DBL_DBL; +typedef std::tuple DBL_DBL_DBL_DBL; typedef std::tuple STR_STR_STR_STR; // Hash for Enum Class From 3e88fd3eb04f8bfa70060dd8e1b97794faec3a5d Mon Sep 17 00:00:00 2001 From: Yuzhe Song Date: Wed, 28 Feb 2024 16:50:47 +1100 Subject: [PATCH 02/19] Corrected P-dot calculation to use the CalculateSpinDownRate() function for all cases --- src/NS.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/NS.cpp b/src/NS.cpp index d12038905..8fd040da7 100755 --- a/src/NS.cpp +++ b/src/NS.cpp @@ -412,7 +412,7 @@ void NS::SpinDownIsolatedPulsar(const double p_Stepsize) { // see Equation 4 in arXiv:0903.3538v2 (Our version is in cgs) double pDotTop = constant2 * TESLA_TO_GAUSS * TESLA_TO_GAUSS * m_PulsarDetails.magneticField * m_PulsarDetails.magneticField; double pDot = pDotTop / P_f; - m_PulsarDetails.spinDownRate = -_2_PI * pDot / (P_f * P_f); + m_PulsarDetails.spinDownRate = CalculateSpinDownRate(m_PulsarDetails.spinFrequency,m_MomentOfInertia_CGS , m_PulsarDetails.magneticField, NSradius_IN_CM / KM_TO_CM);//-_2_PI * pDot / (P_f * P_f); //std::cout << "spindown fdot1, " << m_PulsarDetails.spinDownRate << " spindown fdot2, " << -_2_PI * pDot / (P_f * P_f) << std::endl; //std::cout << "constant2: " << constant2 << " term1: " << term1 << " term2: " << term2 << " term3: " << term3 << " IP: " << initialSpinPeriod << std::endl; //std::cout << "Timestep: "<< p_Stepsize << " radius^6: " << NSradius_6 << "B: " << m_PulsarDetails.magneticField << " m: " << m_Mass << " MOI: " << m_MomentOfInertia_CGS << "period: " << P_f << std::endl; @@ -633,8 +633,11 @@ void NS::UpdateMagneticFieldAndSpin(const bool p_CommonEnvelope, const bool p_Re m_PulsarDetails.magneticField = std::get<0>(Accretion_Results); m_PulsarDetails.spinFrequency = std::get<1>(Accretion_Results); - m_PulsarDetails.spinDownRate = std::get<2>(Accretion_Results); + //m_PulsarDetails.spinDownRate = std::get<2>(Accretion_Results); m_AngularMomentum_CGS = std::get<3>(Accretion_Results); + + m_PulsarDetails.spinDownRate = CalculateSpinDownRate(m_PulsarDetails.spinFrequency,m_MomentOfInertia_CGS , m_PulsarDetails.magneticField, m_Radius * RSOL_TO_KM );//-_2_PI * pDot / (P_f * P_f); + std::cout << "at end of accretion, period is " << _2_PI/m_PulsarDetails.spinFrequency << std::endl; } @@ -656,8 +659,10 @@ void NS::UpdateMagneticFieldAndSpin(const bool p_CommonEnvelope, const bool p_Re double newAngularMomentum = angularMomentum + Jacc ; m_PulsarDetails.magneticField = newPulsarMagneticField * GAUSS_TO_TESLA; m_PulsarDetails.spinFrequency = newAngularMomentum / MoI ; - m_PulsarDetails.spinDownRate = (Jacc / p_Stepsize) / MoI; + //m_PulsarDetails.spinDownRate = (Jacc / p_Stepsize) / MoI; m_AngularMomentum_CGS = newAngularMomentum ; + m_PulsarDetails.spinDownRate = CalculateSpinDownRate(m_PulsarDetails.spinFrequency,m_MomentOfInertia_CGS , m_PulsarDetails.magneticField, m_Radius * RSOL_TO_KM );//-_2_PI * pDot / (P_f * P_f); + std::cout << "CE Accretion - f: " << newAngularMomentum / MoI << " and fdot: " << (Jacc / p_Stepsize) / MoI << std::endl; std::cout << "CE Accretion - dm: " << p_MassGainPerTimeStep << " p: " << _2_PI / m_PulsarDetails.spinFrequency << std::endl; } From b3a0dd2ed968f34c008fcf69e951acaf7b3cf94f Mon Sep 17 00:00:00 2001 From: Yuzhe Song Date: Wed, 6 Mar 2024 18:22:47 +1100 Subject: [PATCH 03/19] Added an option for how NS would accrete in commone envelope phase --- src/NS.cpp | 246 ++++++++++++++++++++++++++---------------------- src/NS.h | 12 ++- src/Options.cpp | 16 +++- src/Options.h | 6 ++ src/constants.h | 11 +++ src/yaml.h | 1 + 6 files changed, 176 insertions(+), 116 deletions(-) diff --git a/src/NS.cpp b/src/NS.cpp index 8fd040da7..5e6e9df45 100755 --- a/src/NS.cpp +++ b/src/NS.cpp @@ -410,8 +410,8 @@ void NS::SpinDownIsolatedPulsar(const double p_Stepsize) { // calculate the spin down rate for isolated neutron stars // see Equation 4 in arXiv:0903.3538v2 (Our version is in cgs) - double pDotTop = constant2 * TESLA_TO_GAUSS * TESLA_TO_GAUSS * m_PulsarDetails.magneticField * m_PulsarDetails.magneticField; - double pDot = pDotTop / P_f; + // double pDotTop = constant2 * TESLA_TO_GAUSS * TESLA_TO_GAUSS * m_PulsarDetails.magneticField * m_PulsarDetails.magneticField; + // double pDot = pDotTop / P_f; m_PulsarDetails.spinDownRate = CalculateSpinDownRate(m_PulsarDetails.spinFrequency,m_MomentOfInertia_CGS , m_PulsarDetails.magneticField, NSradius_IN_CM / KM_TO_CM);//-_2_PI * pDot / (P_f * P_f); //std::cout << "spindown fdot1, " << m_PulsarDetails.spinDownRate << " spindown fdot2, " << -_2_PI * pDot / (P_f * P_f) << std::endl; //std::cout << "constant2: " << constant2 << " term1: " << term1 << " term2: " << term2 << " term3: " << term3 << " IP: " << initialSpinPeriod << std::endl; @@ -420,95 +420,95 @@ void NS::SpinDownIsolatedPulsar(const double p_Stepsize) { } -/* - * Update the magnetic field and spins of neutron stars when it's accreting mass from companion. - * We carry out the calculations in this function using cgs units. - * This function is called in multiple situations in the NS::UpdateMagneticFieldAndSpin() function - * - * Modifies the following class member variables: - * - * m_Mass - * m_AngularMomentum_CGS - * m_PulsarDetails.spinFrequency - * m_PulsarDetails.magneticField - * m_PulsarDetails.spinDownRate - * - * - * void PulsarAccretion(const double p_Stepsize, const double p_MassGainPerTimeStep, const double p_Epsilon) - * @param [IN] p_Stepsize Timestep size for integration (in seconds) - * @param [IN] p_MassGainPerTimeStep Mass loss from the secondary for each iteration (in g) - * @param [IN] kappa Mass loss from the secondary for each iteration (in g) - * @param [IN] p_Epsilon Uncertainty due to mass loss - */ -DBL_DBL_DBL_DBL NS::PulsarAccretion(const double p_Stepsize, const double p_MassGainPerTimeStep, const double kappa, const double p_Epsilon) { - double initialMagField = m_PulsarDetails.magneticField; - double initialMagField_G = initialMagField * TESLA_TO_GAUSS; // (in G) - // (in G) - double magFieldLowerLimit_G = PPOW(10.0, OPTIONS->PulsarLog10MinimumMagneticField()) ; - //double kappa = OPTIONS->PulsarMagneticFieldDecayMassscale() * MSOL_TO_G; - //m_Mass = m_Mass + (p_MassGainPerTimeStep / MSOL_TO_G); - // This part of the code does pulsar recycling through accretion - // recycling happens for pulsar with spin period larger than 1 ms and in a binary system with mass transfer - // the pulsar being recycled is either in a common envolope, or should have started the recycling process in previous time steps. - double mass_g = m_Mass * MSOL_TO_G; // in g - double r_cm = m_Radius * RSOL_TO_KM * KM_TO_CM; // in cm - m_MomentOfInertia_CGS = CalculateMomentOfInertiaCGS() ; - double MoI = m_MomentOfInertia_CGS;// * CGS_SI; - double angularMomentum = m_AngularMomentum_CGS;// * CGS_SI; - double newPulsarMagneticField = (initialMagField_G - magFieldLowerLimit_G) * exp(-p_MassGainPerTimeStep / kappa) + magFieldLowerLimit_G; - std::cout << "IB: " << initialMagField_G << " MT: " << p_MassGainPerTimeStep << " kappa: " << kappa << " BMIN: " << magFieldLowerLimit_G << std::endl; +// /* +// * Update the magnetic field and spins of neutron stars when it's accreting mass from companion. +// * We carry out the calculations in this function using cgs units. +// * This function is called in multiple situations in the NS::UpdateMagneticFieldAndSpin() function +// * +// * Modifies the following class member variables: +// * +// * m_Mass +// * m_AngularMomentum_CGS +// * m_PulsarDetails.spinFrequency +// * m_PulsarDetails.magneticField +// * m_PulsarDetails.spinDownRate +// * +// * +// * void PulsarAccretion(const double p_Stepsize, const double p_MassGainPerTimeStep, const double p_Epsilon) +// * @param [IN] p_Stepsize Timestep size for integration (in seconds) +// * @param [IN] p_MassGainPerTimeStep Mass loss from the secondary for each iteration (in g) +// * @param [IN] kappa Mass loss from the secondary for each iteration (in g) +// * @param [IN] p_Epsilon Uncertainty due to mass loss +// */ +// DBL_DBL_DBL_DBL NS::PulsarAccretion(const double p_Stepsize, const double p_MassGainPerTimeStep, const double kappa, const double p_Epsilon) { +// double initialMagField = m_PulsarDetails.magneticField; +// double initialMagField_G = initialMagField * TESLA_TO_GAUSS; // (in G) +// // (in G) +// double magFieldLowerLimit_G = PPOW(10.0, OPTIONS->PulsarLog10MinimumMagneticField()) ; +// //double kappa = OPTIONS->PulsarMagneticFieldDecayMassscale() * MSOL_TO_G; +// //m_Mass = m_Mass + (p_MassGainPerTimeStep / MSOL_TO_G); +// // This part of the code does pulsar recycling through accretion +// // recycling happens for pulsar with spin period larger than 1 ms and in a binary system with mass transfer +// // the pulsar being recycled is either in a common envolope, or should have started the recycling process in previous time steps. +// double mass_g = m_Mass * MSOL_TO_G; // in g +// double r_cm = m_Radius * RSOL_TO_KM * KM_TO_CM; // in cm +// m_MomentOfInertia_CGS = CalculateMomentOfInertiaCGS() ; +// double MoI = m_MomentOfInertia_CGS;// * CGS_SI; +// double angularMomentum = m_AngularMomentum_CGS;// * CGS_SI; +// double newPulsarMagneticField = (initialMagField_G - magFieldLowerLimit_G) * exp(-p_MassGainPerTimeStep / kappa) + magFieldLowerLimit_G; +// std::cout << "IB: " << initialMagField_G << " MT: " << p_MassGainPerTimeStep << " kappa: " << kappa << " BMIN: " << magFieldLowerLimit_G << std::endl; - // calculate the Alfven radius for an accreting neutron star - // see Equation 8 in arXiv:0903.3538v2 - double mDot = p_MassGainPerTimeStep / p_Stepsize; - double R_CM_6 = r_cm * r_cm * r_cm * r_cm * r_cm * r_cm; - double B_4 = newPulsarMagneticField * newPulsarMagneticField * newPulsarMagneticField * newPulsarMagneticField; - double R_a_top = 8.0 * R_CM_6 * R_CM_6 * B_4; - double R_a_bot = mass_g * mDot * mDot * (G * 1000.0); - double alfvenRadius = PPOW(R_a_top / R_a_bot, 1.0 / 7.0) / 2.0; +// // calculate the Alfven radius for an accreting neutron star +// // see Equation 8 in arXiv:0903.3538v2 +// double mDot = p_MassGainPerTimeStep / p_Stepsize; +// double R_CM_6 = r_cm * r_cm * r_cm * r_cm * r_cm * r_cm; +// double B_4 = newPulsarMagneticField * newPulsarMagneticField * newPulsarMagneticField * newPulsarMagneticField; +// double R_a_top = 8.0 * R_CM_6 * R_CM_6 * B_4; +// double R_a_bot = mass_g * mDot * mDot * (G * 1000.0); +// double alfvenRadius = PPOW(R_a_top / R_a_bot, 1.0 / 7.0) / 2.0; - // calculate the difference in the keplerian angular velocity and surface angular velocity of the neutron star in m - // see Equation 2 in 1994MNRAS.269..455J - double keplerianVelocityAtAlfvenRadius = std::sqrt(2.0 * (G * 1000.0) * mass_g / alfvenRadius); - double keplerianAngularVelocityAtAlfvenRadius = 4.0 * M_PI * keplerianVelocityAtAlfvenRadius / alfvenRadius; - double velocityDifference = keplerianAngularVelocityAtAlfvenRadius - m_PulsarDetails.spinFrequency; - - // calculate the change in angular momentum due to accretion - // see Equation 12 in arXiv:0805.0059/ Equation 8 in arxiv:1912.02415 - double Jdot = p_Epsilon * velocityDifference * alfvenRadius * alfvenRadius * mDot; - angularMomentum = angularMomentum + Jdot * p_Stepsize; - std::cout << "MT: " << p_MassGainPerTimeStep <<" B: "<< newPulsarMagneticField << " f: " << angularMomentum / MoI << " Ra: " << alfvenRadius << std::endl; - //std::cout << "accretion fdot1, " << CalculateSpinDownRate(m_PulsarDetails.spinFrequency, m_MomentOfInertia_CGS, m_PulsarDetails.magneticField, m_Radius * RSOL_TO_KM) << " accretion fdot2, " << Jdot/MoI << std::endl; - // if (utils::Compare(angularMomentum / MoI, 0.0) > 0) { - // std::cout<< "updated" < 0) { +// // std::cout<< "updated" <PulsarLog10MinimumMagneticField()) ; - double mass_g = m_Mass * MSOL_TO_G; // in g - double r_cm = m_Radius * RSOL_TO_KM * KM_TO_CM; - double MoI = m_MomentOfInertia_CGS;// * CGS_SI; - double angularMomentum = initial_am;// * CGS_SI; +// // m_AngularMomentum_CGS = angularMomentum ; +// // } +// // else { +// // std::cout<< "spindown?" <PulsarLog10MinimumMagneticField()) ; + double mass_g = m_Mass * MSOL_TO_G; // in g + double r_cm = m_Radius * RSOL_TO_KM * KM_TO_CM; + double MoI = m_MomentOfInertia_CGS;// * CGS_SI; + double angularMomentum = p_AngularMomentum;// * CGS_SI; double newPulsarMagneticField = (initialMagField_G - magFieldLowerLimit_G) * exp(-p_MassGainPerTimeStep / kappa) + magFieldLowerLimit_G; - //std::cout << "IB: " << initialMagField_G << " MT: " << p_MassGainPerTimeStep << " kappa: " << kappa << " BMIN: " << magFieldLowerLimit_G << std::endl; + std::cout << "IB: " << initialMagField_G << " MT: " << p_MassGainPerTimeStep << " kappa: " << kappa << " BMIN: " << magFieldLowerLimit_G << std::endl; // calculate the Alfven radius for an accreting neutron star // see Equation 8 in arXiv:0903.3538v2 double mDot = p_MassGainPerTimeStep / p_Stepsize; - double R_CM_6 = r_cm * r_cm * r_cm * r_cm * r_cm * r_cm; + double R_CM_6 = r_cm * r_cm * r_cm * r_cm * r_cm * r_cm; double B_4 = newPulsarMagneticField * newPulsarMagneticField * newPulsarMagneticField * newPulsarMagneticField; double R_a_top = 8.0 * R_CM_6 * R_CM_6 * B_4; double R_a_bot = mass_g * mDot * mDot * (G * 1000.0); @@ -518,13 +518,13 @@ DBL_DBL_DBL_DBL NS::PulsarAccretion_V2(const double initial_b, const double init // see Equation 2 in 1994MNRAS.269..455J double keplerianVelocityAtAlfvenRadius = std::sqrt(2.0 * (G * 1000.0) * mass_g / alfvenRadius); double keplerianAngularVelocityAtAlfvenRadius = 4.0 * M_PI * keplerianVelocityAtAlfvenRadius / alfvenRadius; - double velocityDifference = keplerianAngularVelocityAtAlfvenRadius - initial_f; + double velocityDifference = keplerianAngularVelocityAtAlfvenRadius - p_SpinFrequency; // calculate the change in angular momentum due to accretion // see Equation 12 in arXiv:0805.0059/ Equation 8 in arxiv:1912.02415 double Jdot = p_Epsilon * velocityDifference * alfvenRadius * alfvenRadius * mDot; angularMomentum = angularMomentum + Jdot * p_Stepsize; - //std::cout << "MT: " << p_MassGainPerTimeStep <<" B: "<< newPulsarMagneticField << " f: " << angularMomentum / MoI << " Ra: " << alfvenRadius << std::endl; + std::cout << "MT: " << p_MassGainPerTimeStep <<" B: "<< newPulsarMagneticField << " f: " << angularMomentum / MoI << " Ra: " << alfvenRadius << std::endl; //std::cout << "accretion fdot1, " << CalculateSpinDownRate(m_PulsarDetails.spinFrequency, m_MomentOfInertia_CGS, m_PulsarDetails.magneticField, m_Radius * RSOL_TO_KM) << " accretion fdot2, " << Jdot/MoI << std::endl; return std::make_tuple(newPulsarMagneticField * GAUSS_TO_TESLA, angularMomentum / MoI, Jdot / MoI, angularMomentum); } @@ -556,10 +556,32 @@ DBL_DBL_DBL_DBL NS::PulsarAccretion_V2(const double initial_b, const double init */ void NS::UpdateMagneticFieldAndSpin(const bool p_CommonEnvelope, const bool p_RecycledNS, const double p_Stepsize, const double p_MassGainPerTimeStep, const double p_Epsilon) { - // double initialMagField = m_PulsarDetails.magneticField; // (in T) - // double magFieldLowerLimit = PPOW(10.0, OPTIONS->PulsarLog10MinimumMagneticField()) * GAUSS_TO_TESLA; + int NSCE = -1; + + switch (OPTIONS->NeutronStarAccretionInCE()) { // which distribution? + + case NS_ACCRETION_IN_CE::ZERO: // ZERO + NSCE = 0; + break; + + case NS_ACCRETION_IN_CE::DISK: // DISK , same as a RLOF case + NSCE = 1; + break; + + case NS_ACCRETION_IN_CE::SURFACE: // SURFACE, mass are accreted onto the surface of the neutron star. + NSCE = 2; + break; + + default: // unknown distribution + SHOW_WARN_STATIC(ERROR::UNKNOWN_NS_ACCRETION_IN_CE, // show warning + "Using zero accretion", + OBJECT_TYPE::BASE_BINARY_STAR, + STELLAR_TYPE::NEUTRON_STAR); + NSCE = 0; + } + double kappa = OPTIONS->PulsarMagneticFieldDecayMassscale() * MSOL_TO_G; - + if ((!p_RecycledNS && !p_CommonEnvelope) || (!p_RecycledNS && utils::Compare(p_MassGainPerTimeStep, 0.0) == 0 )) { // these are the ''classical'' isolated pulsars //std::cout << "start spinning down. The initial spin period at the timestep is " << _2_PI / m_PulsarDetails.spinFrequency << std::endl; @@ -569,24 +591,24 @@ void NS::UpdateMagneticFieldAndSpin(const bool p_CommonEnvelope, const bool p_Re } // Not checking spin period any more (utils::Compare(m_PulsarDetails.spinFrequency, _2_PI * 1000.0) < 0 ) //as propeller effect should be included in the calculation - else if ((!p_CommonEnvelope) && utils::Compare(p_MassGainPerTimeStep, 0.0) > 0) { + else if ( utils::Compare(p_MassGainPerTimeStep, 0.0) > 0) { //(p_RecycledNS || p_CommonEnvelope) - //if (!p_CommonEnvelope) { + if ((!p_CommonEnvelope) || (p_CommonEnvelope && (NSCE == 1))){ //first try to do the spin up for RLOF systems. std::cout << "RLOF Evolution" << std::endl; std::cout << "start accreting " << p_MassGainPerTimeStep * 1000.0 << " "<< kappa << std::endl; std::tuple Accretion_Results; std::tuple Next_Accretion_Results; - double divide_timestep_by = 100.0; + double divide_timestep_by = 200.0; bool do_not_stop_looping = true; while (do_not_stop_looping) { int count = 0; double new_timestep_size = p_Stepsize / divide_timestep_by; double new_massGain = p_MassGainPerTimeStep * 1000.0 / divide_timestep_by ; - Accretion_Results = PulsarAccretion(new_timestep_size, new_massGain, kappa, p_Epsilon); + Accretion_Results = PulsarAccretion(m_PulsarDetails.magneticField, m_PulsarDetails.spinFrequency, m_AngularMomentum_CGS, new_timestep_size, new_massGain, kappa, p_Epsilon); - for (int n = 1; n<= divide_timestep_by; n++){ + for (int n = 1; n<= int(divide_timestep_by); n++){ double last_B = 0; double last_f = 0; double last_fdot = 0; @@ -603,14 +625,14 @@ void NS::UpdateMagneticFieldAndSpin(const bool p_CommonEnvelope, const bool p_Re last_fdot = std::get<2>(Next_Accretion_Results); last_am = std::get<3>(Next_Accretion_Results); } - Next_Accretion_Results = PulsarAccretion_V2(last_B, last_f, last_am, new_timestep_size, new_massGain, kappa, p_Epsilon); + Next_Accretion_Results = PulsarAccretion(last_B, last_f, last_am, new_timestep_size, new_massGain, kappa, p_Epsilon); //std::cout << std::get<1>(Next_Accretion_Results) << std::endl; if (utils::Compare(std::get<1>(Next_Accretion_Results), 0.0) < 0) { Next_Accretion_Results = Accretion_Results ; break;} else {count ++;} } - if (count == divide_timestep_by){ + if (count == int(divide_timestep_by)){ do_not_stop_looping = false; } else { @@ -620,13 +642,13 @@ void NS::UpdateMagneticFieldAndSpin(const bool p_CommonEnvelope, const bool p_Re std::cout << "Total timestep for the accretion: " << divide_timestep_by << std::endl; double new_timestep_size = p_Stepsize / divide_timestep_by; double new_massGain = p_MassGainPerTimeStep * 1000.0 / divide_timestep_by ; - for (int n = 1; n<= divide_timestep_by; n++){ - double last_B = std::get<0>(Accretion_Results); - double last_f = std::get<1>(Accretion_Results); - double last_fdot = std::get<2>(Accretion_Results); - double last_am = std::get<3>(Accretion_Results); + for (int n = 1; n<= int(divide_timestep_by); n++){ + double last_B_2 = std::get<0>(Accretion_Results); + double last_f_2 = std::get<1>(Accretion_Results); + double last_fdot_2 = std::get<2>(Accretion_Results); + double last_am_2 = std::get<3>(Accretion_Results); - Accretion_Results = PulsarAccretion_V2(last_B, last_f, last_am, new_timestep_size, new_massGain, kappa, p_Epsilon); + Accretion_Results = PulsarAccretion(last_B_2, last_f_2, last_am_2, new_timestep_size, new_massGain, kappa, p_Epsilon); } @@ -641,9 +663,11 @@ void NS::UpdateMagneticFieldAndSpin(const bool p_CommonEnvelope, const bool p_Re std::cout << "at end of accretion, period is " << _2_PI/m_PulsarDetails.spinFrequency << std::endl; } - else if ((p_CommonEnvelope) && utils::Compare(p_MassGainPerTimeStep, 0.0) > 0) { + else if ((p_CommonEnvelope) && (NSCE == 2) && utils::Compare(p_MassGainPerTimeStep, 0.0) > 0) { //Now we consider the common envelope casees std::cout << "CE Evolution, starting period is " << _2_PI / m_PulsarDetails.spinFrequency << std::endl; + + double initialMagField = m_PulsarDetails.magneticField; double initialMagField_G = initialMagField * TESLA_TO_GAUSS; double magFieldLowerLimit_G = PPOW(10.0, OPTIONS->PulsarLog10MinimumMagneticField()) ; @@ -656,19 +680,21 @@ void NS::UpdateMagneticFieldAndSpin(const bool p_CommonEnvelope, const bool p_Re double newPulsarMagneticField = (initialMagField_G - magFieldLowerLimit_G) * exp(-p_MassGainPerTimeStep / kappa) + magFieldLowerLimit_G; double r_cm_3 = r_cm * r_cm * r_cm ; double Jacc = MoI * PPOW(G * 1000.0 * mass_g /r_cm_3, 0.5) * p_MassGainPerTimeStep / mass_g ; //MoI * PPOW(G * 1000.0 * p_MassGainPerTimeStep / r_cm_3, 0.5) ; - double newAngularMomentum = angularMomentum + Jacc ; + //double newAngularMomentum = angularMomentum + Jacc ; + m_AngularMomentum_CGS = angularMomentum + Jacc ; m_PulsarDetails.magneticField = newPulsarMagneticField * GAUSS_TO_TESLA; - m_PulsarDetails.spinFrequency = newAngularMomentum / MoI ; + m_PulsarDetails.spinFrequency = m_AngularMomentum_CGS / MoI ; //m_PulsarDetails.spinDownRate = (Jacc / p_Stepsize) / MoI; - m_AngularMomentum_CGS = newAngularMomentum ; + m_PulsarDetails.spinDownRate = CalculateSpinDownRate(m_PulsarDetails.spinFrequency,m_MomentOfInertia_CGS , m_PulsarDetails.magneticField, m_Radius * RSOL_TO_KM );//-_2_PI * pDot / (P_f * P_f); - std::cout << "CE Accretion - f: " << newAngularMomentum / MoI << " and fdot: " << (Jacc / p_Stepsize) / MoI << std::endl; + std::cout << "CE Accretion - f: " << m_AngularMomentum_CGS / MoI << " and fdot: " << (Jacc / p_Stepsize) / MoI << std::endl; std::cout << "CE Accretion - dm: " << p_MassGainPerTimeStep << " p: " << _2_PI / m_PulsarDetails.spinFrequency << std::endl; } - // else { - // //In all other conditions, treat the pulsar as isolated. - // SpinDownIsolatedPulsar(p_Stepsize); - // } + else if ((p_CommonEnvelope) && (NSCE == 0)) { + //In all other conditions, treat the pulsar as isolated and should be spun down. + SpinDownIsolatedPulsar(p_Stepsize); + } + } } diff --git a/src/NS.h b/src/NS.h index 4a61cbda7..818e57ea0 100755 --- a/src/NS.h +++ b/src/NS.h @@ -86,11 +86,13 @@ class NS: virtual public BaseStar, public Remnants { bool ShouldEvolveOnPhase() const { return (m_Mass <= OPTIONS->MaximumNeutronStarMass()); } // Evolve as a neutron star unless mass > maximum neutron star mass (e.g. through accretion) void SpinDownIsolatedPulsar(const double p_Stepsize); - DBL_DBL_DBL_DBL PulsarAccretion(const double p_Stepsize, - const double p_MassGainPerTimeStep, - const double kappa, - const double p_Epsilon); - DBL_DBL_DBL_DBL PulsarAccretion_V2(const double initial_b, const double initial_p, const double initial_am, + // DBL_DBL_DBL_DBL PulsarAccretion(const double p_Stepsize, + // const double p_MassGainPerTimeStep, + // const double kappa, + // const double p_Epsilon); + DBL_DBL_DBL_DBL PulsarAccretion(const double p_MagField, + const double p_SpinPeriod, + const double p_AngularMomentum, const double p_Stepsize, const double p_MassGainPerTimeStep, const double kappa, diff --git a/src/Options.cpp b/src/Options.cpp index 2b3267487..7d0c78ff4 100644 --- a/src/Options.cpp +++ b/src/Options.cpp @@ -511,6 +511,9 @@ void Options::OptionValues::Initialise() { m_NeutronStarEquationOfState.type = NS_EOS::SSE; m_NeutronStarEquationOfState.typeString = NS_EOSLabel.at(m_NeutronStarEquationOfState.type); + // Neutron star accretion in common envelope + m_NSAccretionInCE.type = NS_ACCRETION_IN_CE::ZERO; + m_NSAccretionInCE.typeString = NS_ACCRETION_IN_CELabel.at(m_NSAccretionInCE.type) ; // Pulsar birth magnetic field distribution m_PulsarBirthMagneticFieldDistribution.type = PULSAR_BIRTH_MAGNETIC_FIELD_DISTRIBUTION::ZERO; @@ -1739,7 +1742,11 @@ bool Options::AddOptions(OptionValues *p_Options, po::options_description *p_Opt po::value(&p_Options->m_NeutronStarEquationOfState.typeString)->default_value(p_Options->m_NeutronStarEquationOfState.typeString), ("Neutron star equation of state to use (" + AllowedOptionValuesFormatted("neutron-star-equation-of-state") + ", default = '" + p_Options->m_NeutronStarEquationOfState.typeString + "')").c_str() ) - + ( + "neutron-star-accretion-in-ce", + po::value(&p_Options->m_NSAccretionInCE.typeString)->default_value(p_Options->m_NSAccretionInCE.typeString), + ("Neutron star accretion in CE to use (" + AllowedOptionValuesFormatted("neutron-star-accretion-in-ce") + ", default = '" + p_Options->m_NSAccretionInCE.typeString + "')").c_str() + ) ( "OB-mass-loss", po::value(&p_Options->m_OBMassLoss.typeString)->default_value(p_Options->m_OBMassLoss.typeString), @@ -2169,6 +2176,11 @@ std::string Options::OptionValues::CheckAndSetOptions() { COMPLAIN_IF(!found, "Unknown Neutron Star Equation of State"); } + if (!DEFAULTED("neutron-star-accretion-in-ce")) { // neutron star accretion in ce + std::tie(found, m_NSAccretionInCE.type) = utils::GetMapKey(m_NSAccretionInCE.typeString, NS_ACCRETION_IN_CELabel, m_NSAccretionInCE.type); + COMPLAIN_IF(!found, "Unknown Neutron Star Accretion in CE"); + } + if (!DEFAULTED("OB-mass-loss")) { // OB (main sequence) loss prescription std::tie(found, m_OBMassLoss.type) = utils::GetMapKey(m_OBMassLoss.typeString, OB_MASS_LOSS_LABEL, m_OBMassLoss.type); COMPLAIN_IF(!found, "Unknown OB Mass Loss Prescription"); @@ -2469,6 +2481,7 @@ std::vector Options::AllowedOptionValues(const std::string p_Option case _("mode") : POPULATE_RET(EVOLUTION_MODE_LABEL); break; case _("neutrino-mass-loss-BH-formation") : POPULATE_RET(NEUTRINO_MASS_LOSS_PRESCRIPTION_LABEL); break; case _("neutron-star-equation-of-state") : POPULATE_RET(NS_EOSLabel); break; + case _("neutron-star-accretion-in-ce") : POPULATE_RET(NS_ACCRETION_IN_CELabel); break; case _("OB-mass-loss") : POPULATE_RET(OB_MASS_LOSS_LABEL); break; case _("orbital-period-distribution") : POPULATE_RET(ORBITAL_PERIOD_DISTRIBUTION_LABEL); break; case _("pulsar-birth-magnetic-field-distribution") : POPULATE_RET(PULSAR_BIRTH_MAGNETIC_FIELD_DISTRIBUTION_LABEL); break; @@ -4544,6 +4557,7 @@ COMPAS_VARIABLE Options::OptionValue(const T_ANY_PROPERTY p_Property) const { case PROGRAM_OPTION::NOTES : value = Notes(); break; case PROGRAM_OPTION::NS_EOS : value = static_cast(NeutronStarEquationOfState()); break; + case PROGRAM_OPTION::NS_ACCRETION_IN_CE : value = static_cast(NeutronStarAccretionInCE()); break; case PROGRAM_OPTION::ORBITAL_PERIOD : value = OrbitalPeriod(); break; case PROGRAM_OPTION::ORBITAL_PERIOD_DISTRIBUTION : value = static_cast(OrbitalPeriodDistribution()); break; diff --git a/src/Options.h b/src/Options.h index e17b1f034..a5063ae70 100755 --- a/src/Options.h +++ b/src/Options.h @@ -379,6 +379,8 @@ class Options { "maximum-mass-donor-nandez-ivanova", "minimum-secondary-mass", + "neutron-star-in-accretion-in-ce", + "orbital-period", "orbital-period-distribution", "orbital-period-max", @@ -501,6 +503,7 @@ class Options { "notes-hdrs", "neutrino-mass-loss-BH-formation", "neutron-star-equation-of-state", + "neutron-star-accretion-in-CE", "OB-mass-loss", "orbital-period-distribution", @@ -932,6 +935,8 @@ class Options { // Neutron star equation of state ENUM_OPT m_NeutronStarEquationOfState; // NS EOS + // Neutron star accretion in common envelope + ENUM_OPT m_NSAccretionInCE; // NS Accretion In CE // Pulsar birth magnetic field distribution string ENUM_OPT m_PulsarBirthMagneticFieldDistribution; // Birth magnetic field distribution for pulsars @@ -1388,6 +1393,7 @@ class Options { double NeutrinoMassLossValueBH() const { return OPT_VALUE("neutrino-mass-loss-BH-formation-value", m_NeutrinoMassLossValueBH, true); } NS_EOS NeutronStarEquationOfState() const { return OPT_VALUE("neutron-star-equation-of-state", m_NeutronStarEquationOfState.type, true); } + NS_ACCRETION_IN_CE NeutronStarAccretionInCE() const { return OPT_VALUE("neutron-star-accretion-in-ce", m_NSAccretionInCE.type, true); } std::string Notes(const size_t p_Idx) const { return OPT_VALUE("notes", m_Notes[p_Idx], true); } std::vector Notes() const { return OPT_VALUE("notes", m_Notes, true); } diff --git a/src/constants.h b/src/constants.h index 11c2094be..898c00ffa 100755 --- a/src/constants.h +++ b/src/constants.h @@ -642,6 +642,7 @@ enum class ERROR: int { UNKNOWN_MT_THERMALLY_LIMITED_VARIATION, // unknown mass transfer thermally limited variation UNKNOWN_NEUTRINO_MASS_LOSS_PRESCRIPTION, // unknown neutrino mass loss prescription UNKNOWN_NS_EOS, // unknown NS equation-of-state + UNKNOWN_NS_ACCRETION_IN_CE, // unknown NS accretion-in-ce UNKNOWN_PPI_PRESCRIPTION, // unknown pulsational pair instability prescription UNKNOWN_PROGRAM_OPTION, // unknown program option UNKNOWN_PROPERTY_TYPE, // unknown property type @@ -784,6 +785,7 @@ const COMPASUnorderedMap> ERROR_CATA { ERROR::UNKNOWN_MASS_LOSS_PRESCRIPTION, { ERROR_SCOPE::ALWAYS, "Unknown mass loss prescription" }}, { ERROR::UNKNOWN_NEUTRINO_MASS_LOSS_PRESCRIPTION, { ERROR_SCOPE::ALWAYS, "Unknown neutrino mass loss prescription" }}, { ERROR::UNKNOWN_NS_EOS, { ERROR_SCOPE::ALWAYS, "Unknown NS equation-of-state" }}, + { ERROR::UNKNOWN_NS_ACCRETION_IN_CE, { ERROR_SCOPE::ALWAYS, "Unknown NS accretion-in-ce" }}, { ERROR::UNKNOWN_PPI_PRESCRIPTION, { ERROR_SCOPE::ALWAYS, "Unknown pulsational pair instability prescription" }}, { ERROR::UNKNOWN_PROGRAM_OPTION, { ERROR_SCOPE::ALWAYS, "Unknown program option - property details not found" }}, { ERROR::UNKNOWN_PROPERTY_TYPE, { ERROR_SCOPE::ALWAYS, "Unknown property type - property details not found" }}, @@ -1217,6 +1219,14 @@ const COMPASUnorderedMap NS_EOSLabel = { { NS_EOS::ARP3, "ARP3" } }; +// Neutron Star accretion in CE +enum class NS_ACCRETION_IN_CE: int { ZERO, DISK, SURFACE }; +const COMPASUnorderedMap NS_ACCRETION_IN_CELabel = { + { NS_ACCRETION_IN_CE::ZERO, "ZERO" }, + { NS_ACCRETION_IN_CE::DISK, "DISK" }, + { NS_ACCRETION_IN_CE::SURFACE, "SURFACE" } +}; + // Orbital Period Distributions enum class ORBITAL_PERIOD_DISTRIBUTION: int { FLATINLOG }; @@ -2509,6 +2519,7 @@ enum class PROGRAM_OPTION: int { NOTES, NS_EOS, + NS_ACCRETION_IN_CE, ORBITAL_PERIOD, ORBITAL_PERIOD_DISTRIBUTION, diff --git a/src/yaml.h b/src/yaml.h index 83a7def68..b6e9b3ed5 100644 --- a/src/yaml.h +++ b/src/yaml.h @@ -317,6 +317,7 @@ namespace yaml { " --kick-magnitude-distribution", " --kick-direction", " --neutron-star-equation-of-state", + " --neutron-star-accretion-in-ce", " --neutrino-mass-loss-BH-formation", " --pulsar-birth-magnetic-field-distribution", " --pulsar-birth-spin-period-distribution", From 2806c6f248faa82d6a2ffac1caba56501ef5c329 Mon Sep 17 00:00:00 2001 From: Yuzhe Song Date: Sun, 31 Mar 2024 22:13:19 +1100 Subject: [PATCH 04/19] code clean-up --- src/NS.cpp | 151 ++++++++++++++++------------------------------------- src/NS.h | 4 -- 2 files changed, 44 insertions(+), 111 deletions(-) diff --git a/src/NS.cpp b/src/NS.cpp index 5e6e9df45..0f5b5d4bd 100755 --- a/src/NS.cpp +++ b/src/NS.cpp @@ -350,7 +350,6 @@ void NS::CalculateAndSetPulsarParameters() { m_PulsarDetails.spinPeriod = CalculatePulsarBirthSpinPeriod(); // spin period in ms m_PulsarDetails.spinFrequency = _2_PI / (m_PulsarDetails.spinPeriod * SECONDS_IN_MS); m_PulsarDetails.birthPeriod = m_PulsarDetails.spinPeriod * SECONDS_IN_MS; // convert from ms to s - std::cout << "BP: " << m_PulsarDetails.spinPeriod << " BB: " << m_PulsarDetails.magneticField << std::endl; m_MomentOfInertia_CGS = CalculateMomentOfInertiaCGS(); // in CGS g cm^2 // Note we convert neutronStarMomentOfInertia from CGS to SI here @@ -410,91 +409,33 @@ void NS::SpinDownIsolatedPulsar(const double p_Stepsize) { // calculate the spin down rate for isolated neutron stars // see Equation 4 in arXiv:0903.3538v2 (Our version is in cgs) - // double pDotTop = constant2 * TESLA_TO_GAUSS * TESLA_TO_GAUSS * m_PulsarDetails.magneticField * m_PulsarDetails.magneticField; - // double pDot = pDotTop / P_f; - m_PulsarDetails.spinDownRate = CalculateSpinDownRate(m_PulsarDetails.spinFrequency,m_MomentOfInertia_CGS , m_PulsarDetails.magneticField, NSradius_IN_CM / KM_TO_CM);//-_2_PI * pDot / (P_f * P_f); - //std::cout << "spindown fdot1, " << m_PulsarDetails.spinDownRate << " spindown fdot2, " << -_2_PI * pDot / (P_f * P_f) << std::endl; - //std::cout << "constant2: " << constant2 << " term1: " << term1 << " term2: " << term2 << " term3: " << term3 << " IP: " << initialSpinPeriod << std::endl; - //std::cout << "Timestep: "<< p_Stepsize << " radius^6: " << NSradius_6 << "B: " << m_PulsarDetails.magneticField << " m: " << m_Mass << " MOI: " << m_MomentOfInertia_CGS << "period: " << P_f << std::endl; + m_PulsarDetails.spinDownRate = CalculateSpinDownRate(m_PulsarDetails.spinFrequency,m_MomentOfInertia_CGS , m_PulsarDetails.magneticField, NSradius_IN_CM / KM_TO_CM); m_AngularMomentum_CGS = m_PulsarDetails.spinFrequency * m_MomentOfInertia_CGS; // angular momentum of star in CGS } -// /* -// * Update the magnetic field and spins of neutron stars when it's accreting mass from companion. -// * We carry out the calculations in this function using cgs units. -// * This function is called in multiple situations in the NS::UpdateMagneticFieldAndSpin() function -// * -// * Modifies the following class member variables: -// * -// * m_Mass -// * m_AngularMomentum_CGS -// * m_PulsarDetails.spinFrequency -// * m_PulsarDetails.magneticField -// * m_PulsarDetails.spinDownRate -// * -// * -// * void PulsarAccretion(const double p_Stepsize, const double p_MassGainPerTimeStep, const double p_Epsilon) -// * @param [IN] p_Stepsize Timestep size for integration (in seconds) -// * @param [IN] p_MassGainPerTimeStep Mass loss from the secondary for each iteration (in g) -// * @param [IN] kappa Mass loss from the secondary for each iteration (in g) -// * @param [IN] p_Epsilon Uncertainty due to mass loss -// */ -// DBL_DBL_DBL_DBL NS::PulsarAccretion(const double p_Stepsize, const double p_MassGainPerTimeStep, const double kappa, const double p_Epsilon) { -// double initialMagField = m_PulsarDetails.magneticField; -// double initialMagField_G = initialMagField * TESLA_TO_GAUSS; // (in G) -// // (in G) -// double magFieldLowerLimit_G = PPOW(10.0, OPTIONS->PulsarLog10MinimumMagneticField()) ; -// //double kappa = OPTIONS->PulsarMagneticFieldDecayMassscale() * MSOL_TO_G; -// //m_Mass = m_Mass + (p_MassGainPerTimeStep / MSOL_TO_G); -// // This part of the code does pulsar recycling through accretion -// // recycling happens for pulsar with spin period larger than 1 ms and in a binary system with mass transfer -// // the pulsar being recycled is either in a common envolope, or should have started the recycling process in previous time steps. -// double mass_g = m_Mass * MSOL_TO_G; // in g -// double r_cm = m_Radius * RSOL_TO_KM * KM_TO_CM; // in cm -// m_MomentOfInertia_CGS = CalculateMomentOfInertiaCGS() ; -// double MoI = m_MomentOfInertia_CGS;// * CGS_SI; -// double angularMomentum = m_AngularMomentum_CGS;// * CGS_SI; -// double newPulsarMagneticField = (initialMagField_G - magFieldLowerLimit_G) * exp(-p_MassGainPerTimeStep / kappa) + magFieldLowerLimit_G; -// std::cout << "IB: " << initialMagField_G << " MT: " << p_MassGainPerTimeStep << " kappa: " << kappa << " BMIN: " << magFieldLowerLimit_G << std::endl; - -// // calculate the Alfven radius for an accreting neutron star -// // see Equation 8 in arXiv:0903.3538v2 -// double mDot = p_MassGainPerTimeStep / p_Stepsize; -// double R_CM_6 = r_cm * r_cm * r_cm * r_cm * r_cm * r_cm; -// double B_4 = newPulsarMagneticField * newPulsarMagneticField * newPulsarMagneticField * newPulsarMagneticField; -// double R_a_top = 8.0 * R_CM_6 * R_CM_6 * B_4; -// double R_a_bot = mass_g * mDot * mDot * (G * 1000.0); -// double alfvenRadius = PPOW(R_a_top / R_a_bot, 1.0 / 7.0) / 2.0; - -// // calculate the difference in the keplerian angular velocity and surface angular velocity of the neutron star in m -// // see Equation 2 in 1994MNRAS.269..455J -// double keplerianVelocityAtAlfvenRadius = std::sqrt(2.0 * (G * 1000.0) * mass_g / alfvenRadius); -// double keplerianAngularVelocityAtAlfvenRadius = 4.0 * M_PI * keplerianVelocityAtAlfvenRadius / alfvenRadius; -// double velocityDifference = keplerianAngularVelocityAtAlfvenRadius - m_PulsarDetails.spinFrequency; - -// // calculate the change in angular momentum due to accretion -// // see Equation 12 in arXiv:0805.0059/ Equation 8 in arxiv:1912.02415 -// double Jdot = p_Epsilon * velocityDifference * alfvenRadius * alfvenRadius * mDot; -// angularMomentum = angularMomentum + Jdot * p_Stepsize; -// std::cout << "MT: " << p_MassGainPerTimeStep <<" B: "<< newPulsarMagneticField << " f: " << angularMomentum / MoI << " Ra: " << alfvenRadius << std::endl; -// //std::cout << "accretion fdot1, " << CalculateSpinDownRate(m_PulsarDetails.spinFrequency, m_MomentOfInertia_CGS, m_PulsarDetails.magneticField, m_Radius * RSOL_TO_KM) << " accretion fdot2, " << Jdot/MoI << std::endl; -// // if (utils::Compare(angularMomentum / MoI, 0.0) > 0) { -// // std::cout<< "updated" <PulsarLog10MinimumMagneticField()) ; @@ -525,14 +466,12 @@ DBL_DBL_DBL_DBL NS::PulsarAccretion(const double p_MagField, const double p_Spin double Jdot = p_Epsilon * velocityDifference * alfvenRadius * alfvenRadius * mDot; angularMomentum = angularMomentum + Jdot * p_Stepsize; std::cout << "MT: " << p_MassGainPerTimeStep <<" B: "<< newPulsarMagneticField << " f: " << angularMomentum / MoI << " Ra: " << alfvenRadius << std::endl; - //std::cout << "accretion fdot1, " << CalculateSpinDownRate(m_PulsarDetails.spinFrequency, m_MomentOfInertia_CGS, m_PulsarDetails.magneticField, m_Radius * RSOL_TO_KM) << " accretion fdot2, " << Jdot/MoI << std::endl; return std::make_tuple(newPulsarMagneticField * GAUSS_TO_TESLA, angularMomentum / MoI, Jdot / MoI, angularMomentum); } /* - * Update the magnetic field and spins of neutron stars in the following situations: - * 1). + * Update the magnetic field and spins of neutron stars. * Modifies the following class member variables: * * m_AngularMomentum_CGS @@ -558,13 +497,13 @@ void NS::UpdateMagneticFieldAndSpin(const bool p_CommonEnvelope, const bool p_Re int NSCE = -1; - switch (OPTIONS->NeutronStarAccretionInCE()) { // which distribution? + switch (OPTIONS->NeutronStarAccretionInCE()) { // which mode of CE accretion to use? - case NS_ACCRETION_IN_CE::ZERO: // ZERO + case NS_ACCRETION_IN_CE::ZERO: // ZERO, no effect from CE NSCE = 0; break; - case NS_ACCRETION_IN_CE::DISK: // DISK , same as a RLOF case + case NS_ACCRETION_IN_CE::DISK: // DISK , CE effect same as a RLOF case NSCE = 1; break; @@ -572,8 +511,8 @@ void NS::UpdateMagneticFieldAndSpin(const bool p_CommonEnvelope, const bool p_Re NSCE = 2; break; - default: // unknown distribution - SHOW_WARN_STATIC(ERROR::UNKNOWN_NS_ACCRETION_IN_CE, // show warning + default: // unknown distribution + SHOW_WARN_STATIC(ERROR::UNKNOWN_NS_ACCRETION_IN_CE, // show warning "Using zero accretion", OBJECT_TYPE::BASE_BINARY_STAR, STELLAR_TYPE::NEUTRON_STAR); @@ -583,18 +522,21 @@ void NS::UpdateMagneticFieldAndSpin(const bool p_CommonEnvelope, const bool p_Re double kappa = OPTIONS->PulsarMagneticFieldDecayMassscale() * MSOL_TO_G; if ((!p_RecycledNS && !p_CommonEnvelope) || (!p_RecycledNS && utils::Compare(p_MassGainPerTimeStep, 0.0) == 0 )) { - // these are the ''classical'' isolated pulsars - //std::cout << "start spinning down. The initial spin period at the timestep is " << _2_PI / m_PulsarDetails.spinFrequency << std::endl; + // These are the ''classical'' isolated pulsars. They experience spin-down. SpinDownIsolatedPulsar(p_Stepsize); - //std::cout << "end of spinning down step. period is " << _2_PI / m_PulsarDetails.spinFrequency << std::endl; - } - // Not checking spin period any more (utils::Compare(m_PulsarDetails.spinFrequency, _2_PI * 1000.0) < 0 ) - //as propeller effect should be included in the calculation + else if ( utils::Compare(p_MassGainPerTimeStep, 0.0) > 0) { - //(p_RecycledNS || p_CommonEnvelope) + // Next, we update pulsar that goes through mass transfer. + // Note that we do not need to set hard lower limit on the spin period, + // as propeller effect should be included in the calculation, + // which means pulsars will start spinning down when the AM is no longer transferred along mass transfer. if ((!p_CommonEnvelope) || (p_CommonEnvelope && (NSCE == 1))){ - //first try to do the spin up for RLOF systems. + // Pulsar evolution during stable mass transfer (RLOF), or if using the 'DISK' option + // in the CE accretion option + // To prevent in some cases where the numerical calculations return negative spin period/frequency, + // We divide the step of mass transfer into sufficiently small ones where in each step, + // no negative spin period is seen. std::cout << "RLOF Evolution" << std::endl; std::cout << "start accreting " << p_MassGainPerTimeStep * 1000.0 << " "<< kappa << std::endl; std::tuple Accretion_Results; @@ -626,7 +568,6 @@ void NS::UpdateMagneticFieldAndSpin(const bool p_CommonEnvelope, const bool p_Re last_am = std::get<3>(Next_Accretion_Results); } Next_Accretion_Results = PulsarAccretion(last_B, last_f, last_am, new_timestep_size, new_massGain, kappa, p_Epsilon); - //std::cout << std::get<1>(Next_Accretion_Results) << std::endl; if (utils::Compare(std::get<1>(Next_Accretion_Results), 0.0) < 0) { Next_Accretion_Results = Accretion_Results ; break;} @@ -649,13 +590,10 @@ void NS::UpdateMagneticFieldAndSpin(const bool p_CommonEnvelope, const bool p_Re double last_am_2 = std::get<3>(Accretion_Results); Accretion_Results = PulsarAccretion(last_B_2, last_f_2, last_am_2, new_timestep_size, new_massGain, kappa, p_Epsilon); - } - m_PulsarDetails.magneticField = std::get<0>(Accretion_Results); m_PulsarDetails.spinFrequency = std::get<1>(Accretion_Results); - //m_PulsarDetails.spinDownRate = std::get<2>(Accretion_Results); m_AngularMomentum_CGS = std::get<3>(Accretion_Results); m_PulsarDetails.spinDownRate = CalculateSpinDownRate(m_PulsarDetails.spinFrequency,m_MomentOfInertia_CGS , m_PulsarDetails.magneticField, m_Radius * RSOL_TO_KM );//-_2_PI * pDot / (P_f * P_f); @@ -664,10 +602,9 @@ void NS::UpdateMagneticFieldAndSpin(const bool p_CommonEnvelope, const bool p_Re } else if ((p_CommonEnvelope) && (NSCE == 2) && utils::Compare(p_MassGainPerTimeStep, 0.0) > 0) { - //Now we consider the common envelope casees + // Mass transfer through CE when accretion happens at the surface of the NS std::cout << "CE Evolution, starting period is " << _2_PI / m_PulsarDetails.spinFrequency << std::endl; - double initialMagField = m_PulsarDetails.magneticField; double initialMagField_G = initialMagField * TESLA_TO_GAUSS; double magFieldLowerLimit_G = PPOW(10.0, OPTIONS->PulsarLog10MinimumMagneticField()) ; @@ -680,11 +617,10 @@ void NS::UpdateMagneticFieldAndSpin(const bool p_CommonEnvelope, const bool p_Re double newPulsarMagneticField = (initialMagField_G - magFieldLowerLimit_G) * exp(-p_MassGainPerTimeStep / kappa) + magFieldLowerLimit_G; double r_cm_3 = r_cm * r_cm * r_cm ; double Jacc = MoI * PPOW(G * 1000.0 * mass_g /r_cm_3, 0.5) * p_MassGainPerTimeStep / mass_g ; //MoI * PPOW(G * 1000.0 * p_MassGainPerTimeStep / r_cm_3, 0.5) ; - //double newAngularMomentum = angularMomentum + Jacc ; + m_AngularMomentum_CGS = angularMomentum + Jacc ; m_PulsarDetails.magneticField = newPulsarMagneticField * GAUSS_TO_TESLA; m_PulsarDetails.spinFrequency = m_AngularMomentum_CGS / MoI ; - //m_PulsarDetails.spinDownRate = (Jacc / p_Stepsize) / MoI; m_PulsarDetails.spinDownRate = CalculateSpinDownRate(m_PulsarDetails.spinFrequency,m_MomentOfInertia_CGS , m_PulsarDetails.magneticField, m_Radius * RSOL_TO_KM );//-_2_PI * pDot / (P_f * P_f); @@ -693,7 +629,8 @@ void NS::UpdateMagneticFieldAndSpin(const bool p_CommonEnvelope, const bool p_Re } else if ((p_CommonEnvelope) && (NSCE == 0)) { - //In all other conditions, treat the pulsar as isolated and should be spun down. + // If mass transfer is happening through CE and chosen 'ZERO' in CE accretion mode, + // treat the pulsar as isolated and should be spun down. SpinDownIsolatedPulsar(p_Stepsize); } } diff --git a/src/NS.h b/src/NS.h index 818e57ea0..d4cb73b75 100755 --- a/src/NS.h +++ b/src/NS.h @@ -86,10 +86,6 @@ class NS: virtual public BaseStar, public Remnants { bool ShouldEvolveOnPhase() const { return (m_Mass <= OPTIONS->MaximumNeutronStarMass()); } // Evolve as a neutron star unless mass > maximum neutron star mass (e.g. through accretion) void SpinDownIsolatedPulsar(const double p_Stepsize); - // DBL_DBL_DBL_DBL PulsarAccretion(const double p_Stepsize, - // const double p_MassGainPerTimeStep, - // const double kappa, - // const double p_Epsilon); DBL_DBL_DBL_DBL PulsarAccretion(const double p_MagField, const double p_SpinPeriod, const double p_AngularMomentum, From c59a3b7d1c9f9e92f50abd86c77085402080ab66 Mon Sep 17 00:00:00 2001 From: Yuzhe Song Date: Sun, 31 Mar 2024 22:32:00 +1100 Subject: [PATCH 05/19] update changelog.h --- src/changelog.h | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/changelog.h b/src/changelog.h index 262acc8bf..a7cafebe4 100644 --- a/src/changelog.h +++ b/src/changelog.h @@ -1113,7 +1113,13 @@ // - Defect repair : Added explicit definition `bool isUnstable = false` to avoid confusion in BaseBinaryStar.cpp // - Defect repair : Fixed erroneous core mass values in ResolveSNIa in WhiteDwarfs.cpp. Was previously 0 for all core masses. // - Enhancement: Added output parameter TZAMS for internal variable m_TZAMS +// 02.42.03 YS - Mar 31, 2024 - Update to neutron star accretion treatments: +// - Fixes to MSP formation/NS in mass transfer treatments: +// 1). Created a new function NS::PulsarAccretion() to calculate the pulsar evolution in stable mass transfer. +// 2). In UpdateMagneticFieldAndSpin(), splitting stable mass transfer into smaller steps so that no negative spin period is present. +// 3). Adding a new programing option "NS_ACCRETION_IN_CE" for different treatment of how neutron star would behave when in CE. -const std::string VERSION_STRING = "02.42.02"; + +const std::string VERSION_STRING = "02.42.03"; # endif // __changelog_h__ From 084173595f54e0bea4ce44028ff1d84ce17dbc01 Mon Sep 17 00:00:00 2001 From: Yuzhe Song Date: Wed, 3 Apr 2024 18:28:17 +1100 Subject: [PATCH 06/19] Code clean-up in response to reviews --- .../program-options-list-defaults.rst | 5 + src/NS.cpp | 135 ++++++++---------- src/NS.h | 2 +- src/changelog.h | 6 +- src/compasConfigDefault.yaml | 1 + 5 files changed, 66 insertions(+), 83 deletions(-) diff --git a/online-docs/pages/User guide/Program options/program-options-list-defaults.rst b/online-docs/pages/User guide/Program options/program-options-list-defaults.rst index a01d3f55e..b922a371d 100644 --- a/online-docs/pages/User guide/Program options/program-options-list-defaults.rst +++ b/online-docs/pages/User guide/Program options/program-options-list-defaults.rst @@ -911,6 +911,11 @@ Default = FIXED_MASS Amount of mass lost in neutrinos during BH formation (either as fraction or in solar masses, depending on the value of ``--neutrino-mass-loss-bh-formation``). |br| Default = 0.1 +**--neutron-star-accretion-in-ce** |br| +Neutron star accretion behavior in common envelope. |br| +Options: { ZERO, DISK, SURFACE } |br| +Default = ZERO + **--neutron-star-equation-of-state** |br| Neutron star equation of state. |br| Options: { SSE, ARP3 } |br| diff --git a/src/NS.cpp b/src/NS.cpp index 0f5b5d4bd..320d2c095 100755 --- a/src/NS.cpp +++ b/src/NS.cpp @@ -433,18 +433,18 @@ void NS::SpinDownIsolatedPulsar(const double p_Stepsize) { * @param [IN] p_AngularMomentum Angular momentum of the NS at the beginning of accretion (g cm-2 / s) * @param [IN] p_Stepsize Timestep size for integration (in seconds) * @param [IN] p_MassGainPerTimeStep Mass loss from the secondary for each iteration (in g) - * @param [IN] kappa Mass loss from the secondary for each iteration (in g) + * @param [IN] p_Kappa Mass loss from the secondary for each iteration (in g) * @param [IN] p_Epsilon Uncertainty due to mass loss + * @return Tuple containing the updated magnetic field strength, spin frequency, angular momentum of neutron star */ -DBL_DBL_DBL_DBL NS::PulsarAccretion(const double p_MagField, const double p_SpinFrequency, const double p_AngularMomentum, const double p_Stepsize, const double p_MassGainPerTimeStep, const double kappa, const double p_Epsilon) { +DBL_DBL_DBL_DBL NS::PulsarAccretion(const double p_MagField, const double p_SpinFrequency, const double p_AngularMomentum, const double p_Stepsize, const double p_MassGainPerTimeStep, const double p_Kappa, const double p_Epsilon) { double initialMagField_G = p_MagField * TESLA_TO_GAUSS; double magFieldLowerLimit_G = PPOW(10.0, OPTIONS->PulsarLog10MinimumMagneticField()) ; double mass_g = m_Mass * MSOL_TO_G; // in g double r_cm = m_Radius * RSOL_TO_KM * KM_TO_CM; double MoI = m_MomentOfInertia_CGS;// * CGS_SI; double angularMomentum = p_AngularMomentum;// * CGS_SI; - double newPulsarMagneticField = (initialMagField_G - magFieldLowerLimit_G) * exp(-p_MassGainPerTimeStep / kappa) + magFieldLowerLimit_G; - std::cout << "IB: " << initialMagField_G << " MT: " << p_MassGainPerTimeStep << " kappa: " << kappa << " BMIN: " << magFieldLowerLimit_G << std::endl; + double newPulsarMagneticField = (initialMagField_G - magFieldLowerLimit_G) * exp(-p_MassGainPerTimeStep / p_Kappa) + magFieldLowerLimit_G; // calculate the Alfven radius for an accreting neutron star // see Equation 8 in arXiv:0903.3538v2 @@ -465,7 +465,6 @@ DBL_DBL_DBL_DBL NS::PulsarAccretion(const double p_MagField, const double p_Spin // see Equation 12 in arXiv:0805.0059/ Equation 8 in arxiv:1912.02415 double Jdot = p_Epsilon * velocityDifference * alfvenRadius * alfvenRadius * mDot; angularMomentum = angularMomentum + Jdot * p_Stepsize; - std::cout << "MT: " << p_MassGainPerTimeStep <<" B: "<< newPulsarMagneticField << " f: " << angularMomentum / MoI << " Ra: " << alfvenRadius << std::endl; return std::make_tuple(newPulsarMagneticField * GAUSS_TO_TESLA, angularMomentum / MoI, Jdot / MoI, angularMomentum); } @@ -491,7 +490,6 @@ DBL_DBL_DBL_DBL NS::PulsarAccretion(const double p_MagField, const double p_Spin * @param [IN] p_Stepsize Timestep size for integration (in seconds) * @param [IN] p_MassGainPerTimeStep Mass loss from the secondary for each iteration (in kg) * @param [IN] p_Epsilon Uncertainty due to mass loss - * @return Tuple containing the Maximum Mass Acceptance Rate and the Accretion Efficiency Parameter */ void NS::UpdateMagneticFieldAndSpin(const bool p_CommonEnvelope, const bool p_RecycledNS, const double p_Stepsize, const double p_MassGainPerTimeStep, const double p_Epsilon) { @@ -537,101 +535,80 @@ void NS::UpdateMagneticFieldAndSpin(const bool p_CommonEnvelope, const bool p_Re // To prevent in some cases where the numerical calculations return negative spin period/frequency, // We divide the step of mass transfer into sufficiently small ones where in each step, // no negative spin period is seen. - std::cout << "RLOF Evolution" << std::endl; - std::cout << "start accreting " << p_MassGainPerTimeStep * 1000.0 << " "<< kappa << std::endl; - std::tuple Accretion_Results; - std::tuple Next_Accretion_Results; - - double divide_timestep_by = 200.0; - bool do_not_stop_looping = true; - while (do_not_stop_looping) { + std::tuple accretionResults; + + int divideTimestepBy = 100; + bool done = false; + while (!done && utils::Compare(divideTimestepBy, 1000000) > 0.0) { + + divideTimestepBy *= 2; + + double thisTimestepSize = p_Stepsize / divideTimestepBy; + double thisMassGain = p_MassGainPerTimeStep * 1000.0 / divideTimestepBy; + double B ; + double f ; + double fdot ; + double am ; + + std::tie(B, f, fdot, am) = PulsarAccretion(m_PulsarDetails.magneticField, m_PulsarDetails.spinFrequency, + m_AngularMomentum_CGS, thisTimestepSize, thisMassGain, kappa, p_Epsilon); + int count = 0; - double new_timestep_size = p_Stepsize / divide_timestep_by; - double new_massGain = p_MassGainPerTimeStep * 1000.0 / divide_timestep_by ; - Accretion_Results = PulsarAccretion(m_PulsarDetails.magneticField, m_PulsarDetails.spinFrequency, m_AngularMomentum_CGS, new_timestep_size, new_massGain, kappa, p_Epsilon); - - for (int n = 1; n<= int(divide_timestep_by); n++){ - double last_B = 0; - double last_f = 0; - double last_fdot = 0; - double last_am = 0; - if (n==1) { - last_B = std::get<0>(Accretion_Results); - last_f = std::get<1>(Accretion_Results); - last_fdot = std::get<2>(Accretion_Results); - last_am = std::get<3>(Accretion_Results); - } - else { - last_B = std::get<0>(Next_Accretion_Results); - last_f = std::get<1>(Next_Accretion_Results); - last_fdot = std::get<2>(Next_Accretion_Results); - last_am = std::get<3>(Next_Accretion_Results); - } - Next_Accretion_Results = PulsarAccretion(last_B, last_f, last_am, new_timestep_size, new_massGain, kappa, p_Epsilon); - if (utils::Compare(std::get<1>(Next_Accretion_Results), 0.0) < 0) { - Next_Accretion_Results = Accretion_Results ; - break;} - else {count ++;} + while (!done) { + std::tie(B, f, fdot, am) = PulsarAccretion(B, f, am, thisTimestepSize, thisMassGain, kappa, p_Epsilon); + + if (utils::Compare(f, 0.0) < 0) break; + if (++count >= divideTimestepBy) done = true; } - if (count == int(divide_timestep_by)){ - do_not_stop_looping = false; - } - else { - divide_timestep_by = divide_timestep_by * 2.0; - } } - std::cout << "Total timestep for the accretion: " << divide_timestep_by << std::endl; - double new_timestep_size = p_Stepsize / divide_timestep_by; - double new_massGain = p_MassGainPerTimeStep * 1000.0 / divide_timestep_by ; - for (int n = 1; n<= int(divide_timestep_by); n++){ - double last_B_2 = std::get<0>(Accretion_Results); - double last_f_2 = std::get<1>(Accretion_Results); - double last_fdot_2 = std::get<2>(Accretion_Results); - double last_am_2 = std::get<3>(Accretion_Results); + + double newTimeStepSize = p_Stepsize / divideTimestepBy; + double newMassGain = p_MassGainPerTimeStep * 1000.0 / divideTimestepBy ; + accretionResults = PulsarAccretion(m_PulsarDetails.magneticField, m_PulsarDetails.spinFrequency, + m_AngularMomentum_CGS, newTimeStepSize, newMassGain, kappa, p_Epsilon); + for (int n = 1; n<= int(divideTimestepBy); n++){ - Accretion_Results = PulsarAccretion(last_B_2, last_f_2, last_am_2, new_timestep_size, new_massGain, kappa, p_Epsilon); + double last_B_2 = std::get<0>(accretionResults); + double last_f_2 = std::get<1>(accretionResults); + double last_am_2 = std::get<3>(accretionResults); + + accretionResults = PulsarAccretion(last_B_2, last_f_2, last_am_2, newTimeStepSize, newMassGain, kappa, p_Epsilon); } - m_PulsarDetails.magneticField = std::get<0>(Accretion_Results); - m_PulsarDetails.spinFrequency = std::get<1>(Accretion_Results); - m_AngularMomentum_CGS = std::get<3>(Accretion_Results); - - m_PulsarDetails.spinDownRate = CalculateSpinDownRate(m_PulsarDetails.spinFrequency,m_MomentOfInertia_CGS , m_PulsarDetails.magneticField, m_Radius * RSOL_TO_KM );//-_2_PI * pDot / (P_f * P_f); + m_PulsarDetails.magneticField = std::get<0>(accretionResults); + m_PulsarDetails.spinFrequency = std::get<1>(accretionResults); + m_AngularMomentum_CGS = std::get<2>(accretionResults); - std::cout << "at end of accretion, period is " << _2_PI/m_PulsarDetails.spinFrequency << std::endl; + m_PulsarDetails.spinDownRate = CalculateSpinDownRate(m_PulsarDetails.spinFrequency,m_MomentOfInertia_CGS , m_PulsarDetails.magneticField, m_Radius * RSOL_TO_KM ); } - + else if ((p_CommonEnvelope) && (NSCE == 2) && utils::Compare(p_MassGainPerTimeStep, 0.0) > 0) { // Mass transfer through CE when accretion happens at the surface of the NS - std::cout << "CE Evolution, starting period is " << _2_PI / m_PulsarDetails.spinFrequency << std::endl; - double initialMagField = m_PulsarDetails.magneticField; - double initialMagField_G = initialMagField * TESLA_TO_GAUSS; - double magFieldLowerLimit_G = PPOW(10.0, OPTIONS->PulsarLog10MinimumMagneticField()) ; + double initialMagField = m_PulsarDetails.magneticField; + double initialMagField_G = initialMagField * TESLA_TO_GAUSS; + double magFieldLowerLimit_G = PPOW(10.0, OPTIONS->PulsarLog10MinimumMagneticField()) ; - double mass_g = m_Mass * MSOL_TO_G; // in g - double r_cm = m_Radius * RSOL_TO_KM * KM_TO_CM; // in cm - m_MomentOfInertia_CGS = CalculateMomentOfInertiaCGS() ; - double MoI = m_MomentOfInertia_CGS; - double angularMomentum = m_AngularMomentum_CGS; + double mass_g = m_Mass * MSOL_TO_G; // in g + double r_cm = m_Radius * RSOL_TO_KM * KM_TO_CM; // in cm + m_MomentOfInertia_CGS = CalculateMomentOfInertiaCGS() ; + double MoI = m_MomentOfInertia_CGS; + double angularMomentum = m_AngularMomentum_CGS; double newPulsarMagneticField = (initialMagField_G - magFieldLowerLimit_G) * exp(-p_MassGainPerTimeStep / kappa) + magFieldLowerLimit_G; - double r_cm_3 = r_cm * r_cm * r_cm ; - double Jacc = MoI * PPOW(G * 1000.0 * mass_g /r_cm_3, 0.5) * p_MassGainPerTimeStep / mass_g ; //MoI * PPOW(G * 1000.0 * p_MassGainPerTimeStep / r_cm_3, 0.5) ; + double r_cm_3 = r_cm * r_cm * r_cm ; + double Jacc = MoI * PPOW(G * 1000.0 * mass_g /r_cm_3, 0.5) * p_MassGainPerTimeStep / mass_g ; - m_AngularMomentum_CGS = angularMomentum + Jacc ; + m_AngularMomentum_CGS = angularMomentum + Jacc ; m_PulsarDetails.magneticField = newPulsarMagneticField * GAUSS_TO_TESLA; m_PulsarDetails.spinFrequency = m_AngularMomentum_CGS / MoI ; - m_PulsarDetails.spinDownRate = CalculateSpinDownRate(m_PulsarDetails.spinFrequency,m_MomentOfInertia_CGS , m_PulsarDetails.magneticField, m_Radius * RSOL_TO_KM );//-_2_PI * pDot / (P_f * P_f); - - std::cout << "CE Accretion - f: " << m_AngularMomentum_CGS / MoI << " and fdot: " << (Jacc / p_Stepsize) / MoI << std::endl; - std::cout << "CE Accretion - dm: " << p_MassGainPerTimeStep << " p: " << _2_PI / m_PulsarDetails.spinFrequency << std::endl; + m_PulsarDetails.spinDownRate = CalculateSpinDownRate(m_PulsarDetails.spinFrequency,m_MomentOfInertia_CGS , m_PulsarDetails.magneticField, m_Radius * RSOL_TO_KM ); } else if ((p_CommonEnvelope) && (NSCE == 0)) { // If mass transfer is happening through CE and chosen 'ZERO' in CE accretion mode, // treat the pulsar as isolated and should be spun down. SpinDownIsolatedPulsar(p_Stepsize); - } + } } } diff --git a/src/NS.h b/src/NS.h index d4cb73b75..293eb5fd2 100755 --- a/src/NS.h +++ b/src/NS.h @@ -86,7 +86,7 @@ class NS: virtual public BaseStar, public Remnants { bool ShouldEvolveOnPhase() const { return (m_Mass <= OPTIONS->MaximumNeutronStarMass()); } // Evolve as a neutron star unless mass > maximum neutron star mass (e.g. through accretion) void SpinDownIsolatedPulsar(const double p_Stepsize); - DBL_DBL_DBL_DBL PulsarAccretion(const double p_MagField, + DBL_DBL_DBL_DBL PulsarAccretion(const double p_MagField, const double p_SpinPeriod, const double p_AngularMomentum, const double p_Stepsize, diff --git a/src/changelog.h b/src/changelog.h index a7cafebe4..e13f073f9 100644 --- a/src/changelog.h +++ b/src/changelog.h @@ -1113,13 +1113,13 @@ // - Defect repair : Added explicit definition `bool isUnstable = false` to avoid confusion in BaseBinaryStar.cpp // - Defect repair : Fixed erroneous core mass values in ResolveSNIa in WhiteDwarfs.cpp. Was previously 0 for all core masses. // - Enhancement: Added output parameter TZAMS for internal variable m_TZAMS -// 02.42.03 YS - Mar 31, 2024 - Update to neutron star accretion treatments: +// 02.43.00 YS - Mar 31, 2024 - Update to neutron star accretion treatments: // - Fixes to MSP formation/NS in mass transfer treatments: // 1). Created a new function NS::PulsarAccretion() to calculate the pulsar evolution in stable mass transfer. // 2). In UpdateMagneticFieldAndSpin(), splitting stable mass transfer into smaller steps so that no negative spin period is present. -// 3). Adding a new programing option "NS_ACCRETION_IN_CE" for different treatment of how neutron star would behave when in CE. +// 3). Adding a new programing option "NS-ACCRETION-IN-CE" for different treatment of how neutron star would behave when in CE. -const std::string VERSION_STRING = "02.42.03"; +const std::string VERSION_STRING = "02.43.00"; # endif // __changelog_h__ diff --git a/src/compasConfigDefault.yaml b/src/compasConfigDefault.yaml index 0907aa81b..ceee44fcf 100644 --- a/src/compasConfigDefault.yaml +++ b/src/compasConfigDefault.yaml @@ -247,6 +247,7 @@ stringChoices: # --fryer-supernova-engine: 'DELAYED' # Default: 'DELAYED' # Options: ['DELAYED','RAPID'] # --kick-magnitude-distribution: 'MULLERMANDEL' # Default: 'MULLERMANDEL' # Options: ['MULLERMANDEL','MULLER2016MAXWELLIAN','MULLER2016','BRAYELDRIDGE','MAXWELLIAN','FLAT','FIXED','ZERO'] # --kick-direction: 'ISOTROPIC' # Default: 'ISOTROPIC' # Options: ['POLES','WEDGE','POWERLAW','PERPENDICULAR','INPLANE','ISOTROPIC'] +# --neutron-star-accretion-in-ce: 'ZERO' # Default: 'ZERO' # Options: ['ZERO', 'DISK', 'SURFACE'] # --neutron-star-equation-of-state: 'SSE' # Default: 'SSE' # Options: ['ARP3','SSE'] # --neutrino-mass-loss-BH-formation: 'FIXED_MASS' # Default: 'FIXED_MASS' # Options: ['FIXED_MASS','FIXED_FRACTION'] # --pulsar-birth-magnetic-field-distribution: 'ZERO' # Default: 'ZERO' # Options: ['LOGNORMAL','UNIFORM','FLATINLOG','FIXED','ZERO'] From 24d15edeb8923d289a5e2384e6272d964d8e57ec Mon Sep 17 00:00:00 2001 From: Yuzhe Song Date: Fri, 19 Apr 2024 17:14:44 +1000 Subject: [PATCH 07/19] Updates to NS.cpp per reviews and BaseB^CaryStar.cpp for issue #1076 --- src/BaseBinaryStar.cpp | 19 +- src/NS.cpp | 84 +++--- src/NSapr9.cpp | 627 +++++++++++++++++++++++++++++++++++++++++ src/constants.h | 2 +- 4 files changed, 685 insertions(+), 47 deletions(-) create mode 100755 src/NSapr9.cpp diff --git a/src/BaseBinaryStar.cpp b/src/BaseBinaryStar.cpp index 01e93a127..731cbb925 100644 --- a/src/BaseBinaryStar.cpp +++ b/src/BaseBinaryStar.cpp @@ -387,11 +387,11 @@ void BaseBinaryStar::SetRemainingValues() { m_Flags.mergesInHubbleTime = false; m_Unbound = false; - m_SystemicVelocity = Vector3d(); - m_NormalizedOrbitalAngularMomentumVector = Vector3d(); - m_ThetaE = DEFAULT_INITIAL_DOUBLE_VALUE; - m_PhiE = DEFAULT_INITIAL_DOUBLE_VALUE; - m_PsiE = DEFAULT_INITIAL_DOUBLE_VALUE; + m_SystemicVelocity = Vector3d(); + m_NormalizedOrbitalAngularMomentumVector = Vector3d(); + m_ThetaE = DEFAULT_INITIAL_DOUBLE_VALUE; + m_PhiE = DEFAULT_INITIAL_DOUBLE_VALUE; + m_PsiE = DEFAULT_INITIAL_DOUBLE_VALUE; m_SynchronizationTimescale = DEFAULT_INITIAL_DOUBLE_VALUE; m_CircularizationTimescale = DEFAULT_INITIAL_DOUBLE_VALUE; @@ -1273,7 +1273,7 @@ bool BaseBinaryStar::ResolveSupernova() { Vector3d relativeVelocityVector = relativeVelocityVectorPrev + (natalKickVector - companionRecoilVector); // km/s - PostSN relative velocity vector Vector3d orbitalAngularMomentumVector = cross(separationVectorPrev, relativeVelocityVector); // km^2 s^-1 - PostSN specific orbital angular momentum vector - double orbitalAngularMomentum = orbitalAngularMomentumVector.mag; // km^2 s^-1 - PostSN specific orbital angular momentum + double orbitalAngularMomentum = orbitalAngularMomentumVector.mag; // km^2 s^-1 - PostSN specific orbital angular momentum m_NormalizedOrbitalAngularMomentumVector = orbitalAngularMomentumVector/orbitalAngularMomentum; // set unit vector here to make printing out the inclination vector easier Vector3d eccentricityVector = cross(relativeVelocityVector, orbitalAngularMomentumVector) / @@ -1988,6 +1988,13 @@ void BaseBinaryStar::CalculateMassTransfer(const double p_Dt) { } } + // If the current timestep is smaller than the donor's thermal timescale, only a fraction of the mass + // that needs to be donated has time to be donated + double donorThermalTimescale = m_Donor->CalculateThermalTimescale(); + if (p_Dt < donorThermalTimescale) { + massDiffDonor = (p_Dt / donorThermalTimescale) * massDiffDonor; + } + if (!m_CEDetails.CEEnow) { // CE flagged? // no double massGainAccretor = -massDiffDonor * m_FractionAccreted; // set accretor mass gain to mass loss * conservativeness diff --git a/src/NS.cpp b/src/NS.cpp index 320d2c095..2e596da37 100755 --- a/src/NS.cpp +++ b/src/NS.cpp @@ -395,7 +395,7 @@ void NS::SpinDownIsolatedPulsar(const double p_Stepsize) { // see Equation 6 in arXiv:0903.3538v2 m_PulsarDetails.magneticField = magFieldLowerLimit + (initialMagField - magFieldLowerLimit) * exp(-p_Stepsize / tau); // update pulsar magnetic field in SI. - // calculate the spin down rate for isolated neutron stars + // calculate the spin frequency for isolated neutron stars // see Equation 6 in arxiv:1912.02415 // The rest of the calculations are carried out in cgs. double constant2 = (_8_PI_2 * NSradius_6) / (_3_C_3 * m_MomentOfInertia_CGS); @@ -416,9 +416,10 @@ void NS::SpinDownIsolatedPulsar(const double p_Stepsize) { /* * Calculate the magnetic field, spin, spin-down rate and angular momentum of a neutron star, - * when it's accreting mass from companion. + * when it's accreting mass from companion through stable mass transfer. + * The calculations used in this function follows closely Sec. 2.2.1 in arxiv:1912.02415 * We carry out the calculations in this function using cgs units. - * This function is called in multiple situations in the NS::UpdateMagneticFieldAndSpin() function + * This function is called in the NS::UpdateMagneticFieldAndSpin() function * * Returns, in a tuple, these four quantities of a neutron star: * @@ -432,39 +433,44 @@ void NS::SpinDownIsolatedPulsar(const double p_Stepsize) { * @param [IN] p_SpinFrequency Spin frequency for the NS at the beginning of accretion (in Hz) * @param [IN] p_AngularMomentum Angular momentum of the NS at the beginning of accretion (g cm-2 / s) * @param [IN] p_Stepsize Timestep size for integration (in seconds) - * @param [IN] p_MassGainPerTimeStep Mass loss from the secondary for each iteration (in g) - * @param [IN] p_Kappa Mass loss from the secondary for each iteration (in g) - * @param [IN] p_Epsilon Uncertainty due to mass loss - * @return Tuple containing the updated magnetic field strength, spin frequency, angular momentum of neutron star + * @param [IN] p_MassGainPerTimeStep Mass transferred from the secondary for each iteration (in g) + * @param [IN] p_Kappa Magnetic field mass decay scale (in g) + * @param [IN] p_Epsilon Efficiency factor allowing for uncertainties of coupling magnetic field and matter. + * @return Tuple containing the updated magnetic field strength, spin frequency, spin-down rate and angular momentum of neutron star */ DBL_DBL_DBL_DBL NS::PulsarAccretion(const double p_MagField, const double p_SpinFrequency, const double p_AngularMomentum, const double p_Stepsize, const double p_MassGainPerTimeStep, const double p_Kappa, const double p_Epsilon) { double initialMagField_G = p_MagField * TESLA_TO_GAUSS; - double magFieldLowerLimit_G = PPOW(10.0, OPTIONS->PulsarLog10MinimumMagneticField()) ; + double magFieldLowerLimit_G = PPOW(10.0, OPTIONS->PulsarLog10MinimumMagneticField()) ; // convert to Gauss double mass_g = m_Mass * MSOL_TO_G; // in g - double r_cm = m_Radius * RSOL_TO_KM * KM_TO_CM; - double MoI = m_MomentOfInertia_CGS;// * CGS_SI; - double angularMomentum = p_AngularMomentum;// * CGS_SI; + double r_cm = m_Radius * RSOL_TO_KM * KM_TO_CM; // NS radius in cm + double MoI = m_MomentOfInertia_CGS; + double angularMomentum = p_AngularMomentum; + + //Magnetic field decay due to mass transfer. + //Follows Eq. 12 in arxiv:1912.02415 double newPulsarMagneticField = (initialMagField_G - magFieldLowerLimit_G) * exp(-p_MassGainPerTimeStep / p_Kappa) + magFieldLowerLimit_G; // calculate the Alfven radius for an accreting neutron star - // see Equation 8 in arXiv:0903.3538v2 + // see Equation 10 in arxiv:1912.02415 double mDot = p_MassGainPerTimeStep / p_Stepsize; double R_CM_6 = r_cm * r_cm * r_cm * r_cm * r_cm * r_cm; - double B_4 = newPulsarMagneticField * newPulsarMagneticField * newPulsarMagneticField * newPulsarMagneticField; - double R_a_top = 8.0 * R_CM_6 * R_CM_6 * B_4; - double R_a_bot = mass_g * mDot * mDot * (G * 1000.0); - double alfvenRadius = PPOW(R_a_top / R_a_bot, 1.0 / 7.0) / 2.0; + double p = R_CM_6 * R_CM_6 / (mass_g * mass_g * mDot); + double q = PPOW(p, 1.0/7.0); + double alfvenConst = PPOW(2 * PI_2 / G_CGS, 1.0/7.0) ; + double alfvenRadius = alfvenConst * q * PPOW(initialMagField_G, 4.0/7.0); - // calculate the difference in the keplerian angular velocity and surface angular velocity of the neutron star in m - // see Equation 2 in 1994MNRAS.269..455J - double keplerianVelocityAtAlfvenRadius = std::sqrt(2.0 * (G * 1000.0) * mass_g / alfvenRadius); - double keplerianAngularVelocityAtAlfvenRadius = 4.0 * M_PI * keplerianVelocityAtAlfvenRadius / alfvenRadius; - double velocityDifference = keplerianAngularVelocityAtAlfvenRadius - p_SpinFrequency; + // calculate the difference in the keplerian angular velocity at the magnetic radius + // and surface angular velocity of the neutron star + // magnetic radius is half of alfven radius + // see Equation 2 in 1994MNRAS.269..455J / Equation 8 in arxiv:1912.02415 + double keplerianVelocityAtMagneticRadius = std::sqrt(4.0 * (G_CGS) * mass_g / alfvenRadius); + double keplerianAngularVelocityAtMagneticRadius = 2.0 * keplerianVelocityAtMagneticRadius / alfvenRadius; + double omegaDifference = keplerianAngularVelocityAtMagneticRadius - p_SpinFrequency; // calculate the change in angular momentum due to accretion // see Equation 12 in arXiv:0805.0059/ Equation 8 in arxiv:1912.02415 - double Jdot = p_Epsilon * velocityDifference * alfvenRadius * alfvenRadius * mDot; - angularMomentum = angularMomentum + Jdot * p_Stepsize; + double Jdot = p_Epsilon * omegaDifference * alfvenRadius * alfvenRadius * mDot / 4.0; + angularMomentum = angularMomentum + Jdot * p_Stepsize; return std::make_tuple(newPulsarMagneticField * GAUSS_TO_TESLA, angularMomentum / MoI, Jdot / MoI, angularMomentum); } @@ -488,8 +494,8 @@ DBL_DBL_DBL_DBL NS::PulsarAccretion(const double p_MagField, const double p_Spin * @param [IN] p_CommonEnvelope Indicates whether there there is a common envelope - true or false * @param [IN] p_RecycledNS Indicates whether this star is/was a recycled neutron star - true or false * @param [IN] p_Stepsize Timestep size for integration (in seconds) - * @param [IN] p_MassGainPerTimeStep Mass loss from the secondary for each iteration (in kg) - * @param [IN] p_Epsilon Uncertainty due to mass loss + * @param [IN] p_MassGainPerTimeStep Mass trasnferred from the secondary for each iteration (in kg) + * @param [IN] p_Epsilon Efficiency factor allowing for uncertainties of coupling magnetic field and matter. */ void NS::UpdateMagneticFieldAndSpin(const bool p_CommonEnvelope, const bool p_RecycledNS, const double p_Stepsize, const double p_MassGainPerTimeStep, const double p_Epsilon) { @@ -519,9 +525,9 @@ void NS::UpdateMagneticFieldAndSpin(const bool p_CommonEnvelope, const bool p_Re double kappa = OPTIONS->PulsarMagneticFieldDecayMassscale() * MSOL_TO_G; - if ((!p_RecycledNS && !p_CommonEnvelope) || (!p_RecycledNS && utils::Compare(p_MassGainPerTimeStep, 0.0) == 0 )) { + if ((!p_RecycledNS && !p_CommonEnvelope) || (p_RecycledNS && utils::Compare(p_MassGainPerTimeStep, 0.0) == 0 )) { // These are the ''classical'' isolated pulsars. They experience spin-down. - SpinDownIsolatedPulsar(p_Stepsize); + SpinDownIsolatedPulsar(p_Stepsize); } else if ( utils::Compare(p_MassGainPerTimeStep, 0.0) > 0) { @@ -532,11 +538,8 @@ void NS::UpdateMagneticFieldAndSpin(const bool p_CommonEnvelope, const bool p_Re if ((!p_CommonEnvelope) || (p_CommonEnvelope && (NSCE == 1))){ // Pulsar evolution during stable mass transfer (RLOF), or if using the 'DISK' option // in the CE accretion option - // To prevent in some cases where the numerical calculations return negative spin period/frequency, - // We divide the step of mass transfer into sufficiently small ones where in each step, - // no negative spin period is seen. - std::tuple accretionResults; - + std::tuple accretionResults = PulsarAccretion(m_PulsarDetails.magneticField, m_PulsarDetails.spinFrequency, m_AngularMomentum_CGS, p_Stepsize, p_MassGainPerTimeStep * 1000.0, kappa, p_Epsilon); + int divideTimestepBy = 100; bool done = false; while (!done && utils::Compare(divideTimestepBy, 1000000) > 0.0) { @@ -573,18 +576,19 @@ void NS::UpdateMagneticFieldAndSpin(const bool p_CommonEnvelope, const bool p_Re double last_am_2 = std::get<3>(accretionResults); accretionResults = PulsarAccretion(last_B_2, last_f_2, last_am_2, newTimeStepSize, newMassGain, kappa, p_Epsilon); - } m_PulsarDetails.magneticField = std::get<0>(accretionResults); m_PulsarDetails.spinFrequency = std::get<1>(accretionResults); - m_AngularMomentum_CGS = std::get<2>(accretionResults); + m_AngularMomentum_CGS = std::get<3>(accretionResults); + + m_PulsarDetails.spinDownRate = CalculateSpinDownRate(m_PulsarDetails.spinFrequency,m_MomentOfInertia_CGS , m_PulsarDetails.magneticField, m_Radius * RSOL_TO_KM ); - m_PulsarDetails.spinDownRate = CalculateSpinDownRate(m_PulsarDetails.spinFrequency,m_MomentOfInertia_CGS , m_PulsarDetails.magneticField, m_Radius * RSOL_TO_KM ); + } } - else if ((p_CommonEnvelope) && (NSCE == 2) && utils::Compare(p_MassGainPerTimeStep, 0.0) > 0) { + + else if ((p_CommonEnvelope) && (NSCE == 2)) { // Mass transfer through CE when accretion happens at the surface of the NS - double initialMagField = m_PulsarDetails.magneticField; double initialMagField_G = initialMagField * TESLA_TO_GAUSS; double magFieldLowerLimit_G = PPOW(10.0, OPTIONS->PulsarLog10MinimumMagneticField()) ; @@ -594,9 +598,9 @@ void NS::UpdateMagneticFieldAndSpin(const bool p_CommonEnvelope, const bool p_Re m_MomentOfInertia_CGS = CalculateMomentOfInertiaCGS() ; double MoI = m_MomentOfInertia_CGS; double angularMomentum = m_AngularMomentum_CGS; - double newPulsarMagneticField = (initialMagField_G - magFieldLowerLimit_G) * exp(-p_MassGainPerTimeStep / kappa) + magFieldLowerLimit_G; + double newPulsarMagneticField = (initialMagField_G - magFieldLowerLimit_G) * exp(-p_MassGainPerTimeStep * 1000.0 / kappa) + magFieldLowerLimit_G; double r_cm_3 = r_cm * r_cm * r_cm ; - double Jacc = MoI * PPOW(G * 1000.0 * mass_g /r_cm_3, 0.5) * p_MassGainPerTimeStep / mass_g ; + double Jacc = MoI * PPOW(G_CGS * mass_g /r_cm_3, 0.5) * p_MassGainPerTimeStep * 1000.0 / mass_g ; m_AngularMomentum_CGS = angularMomentum + Jacc ; m_PulsarDetails.magneticField = newPulsarMagneticField * GAUSS_TO_TESLA; @@ -609,6 +613,6 @@ void NS::UpdateMagneticFieldAndSpin(const bool p_CommonEnvelope, const bool p_Re // If mass transfer is happening through CE and chosen 'ZERO' in CE accretion mode, // treat the pulsar as isolated and should be spun down. SpinDownIsolatedPulsar(p_Stepsize); - } } } +} \ No newline at end of file diff --git a/src/NSapr9.cpp b/src/NSapr9.cpp new file mode 100755 index 000000000..43819b33b --- /dev/null +++ b/src/NSapr9.cpp @@ -0,0 +1,627 @@ +#include "Rand.h" +#include "NS.h" + + +/* + * Calculate the luminosity of a Neutron Star + * + * Hurley et al. 2000, eq 93 + * + * Called (indirectly) from GiantBranch, so must be static. + * + * + * double CalculateLuminosityOnPhase_Static(const double p_Mass, const double p_Time) + * + * @param [IN] p_Mass Mass in Msol + * @param [IN] p_Time Time since formation of the object in Myr + * @return Luminosity of the Neutron Star in Lsol + */ +double NS::CalculateLuminosityOnPhase_Static(const double p_Mass, const double p_Time) { + double t = std::max(p_Time, 0.1); + return 0.02 * PPOW(p_Mass, 2.0 / 3.0) / (t * t); +} + + +/* + * Choose timestep for Pulsar Evolution + * + * Pulsars evolve very fast when they are first born, and evolve slower as they age. + * Hence, timestep is chosen to be small when pulsar is young, and is slowly increased + * as the pulsar ages. + * + * double ChooseTimestep(const double p_Time) + * + * @param [IN] p_Time Current age of star in Myr + * @return Suggested timestep (dt) + */ +double NS::ChooseTimestep(const double p_Time) const { + double result = 500.0; // default value + + if (p_Time < 0.01) { + result = 0.001; + } + else if (p_Time < 0.1) { + result = 0.01; + } + else if (p_Time < 1.0) { + result = 0.1; + } + else if (p_Time < 10.0) { + result = 1.0; + } + else if (p_Time < 500.0) { + double slope = 1.58859191006; // 1.58859191006 = log10(500.0) / (log10(500.0) - 1.0) + double log10_step = slope * (log10(p_Time) - 1.0); + result = PPOW(10.0, log10_step); + } + + return result; +} + + +/* + * Calculate Neutron Star radius according to selected equation of state (by commandline option) + * + * + * double CalculateRadiusOnPhaseInKM_Static(const double p_Mass) + * + * @param [IN] p_Mass Mass in Msol + * @return Radius of Neutron Star in km + */ +double NS::CalculateRadiusOnPhaseInKM_Static(const double p_Mass) { + + double radius; + + switch (OPTIONS->NeutronStarEquationOfState()) { // which equation-of-state? + + case NS_EOS::SSE: // SSE + radius = 10.0; + break; + + case NS_EOS::ARP3: { // ARP3 + + // We don't extrapolate so masses outside table just set to extreme values + + std::map::const_iterator iter; + + iter = ARP3MassRadiusRelation.begin(); + double ARP3MinimumMass = iter->first; + double ARP3RadiusAtMinimumMass = iter->second; + + iter = ARP3MassRadiusRelation.end(); + double ARP3MaximumMass = (--iter)->first; + double ARP3RadiusAtMaximumMass = iter->second; + + if (utils::Compare(p_Mass, ARP3MinimumMass) < 0) { + radius = ARP3RadiusAtMinimumMass; + } + else if (utils::Compare(p_Mass, ARP3MaximumMass) > 0) { + radius = ARP3RadiusAtMaximumMass; + } + else{ + radius = utils::SampleFromTabulatedCDF(p_Mass, ARP3MassRadiusRelation); + } + } break; + + default: // unknown equation-of-state + SHOW_WARN_STATIC(ERROR::UNKNOWN_NS_EOS, // show warning + "Using default NS radius = 10.0", + OBJECT_TYPE::BASE_STAR, + STELLAR_TYPE::NEUTRON_STAR); + radius = 10.0; + } + + return radius; +} + + +/* + * Calculate core collapse Supernova parameters + * + * Called from GiantBranch, so must be static. + * + * + * DBL_DBL_DBL CalculateCoreCollapseSNParams_Static(const double p_Mass) + * + * @param [IN] p_Mass Mass in Msol + * @return Tuple containing Luminosity, Radius and Temperature of Neutron Star + */ +DBL_DBL_DBL NS::CalculateCoreCollapseSNParams_Static(const double p_Mass) { + double luminosity = CalculateLuminosityOnPhase_Static(p_Mass, 0.0); // Luminosity of Neutron Star as it cools + double radius = CalculateRadiusOnPhase_Static(p_Mass); // Radius of Neutron Star in Rsol + double temperature = BaseStar::CalculateTemperatureOnPhase_Static(luminosity, radius); // Temperature of NS + + return std::make_tuple(luminosity, radius, temperature); +} + + +/* + * Calculate the spin period of a Pulsar at birth according to selected distribution (by commandline option) + * + * + * double CalculatePulsarBirthSpinPeriod() + * + * @return Birth spin period of Pulsar in ms + */ +double NS::CalculatePulsarBirthSpinPeriod() { + + double pSpin; + + switch (OPTIONS->PulsarBirthSpinPeriodDistribution()) { // which distribution? + + case PULSAR_BIRTH_SPIN_PERIOD_DISTRIBUTION::ZERO: // ZERO + pSpin = 0.0; + break; + + case PULSAR_BIRTH_SPIN_PERIOD_DISTRIBUTION::FIXED: // FIXED constant value as used in default model in Oslowski et al 2011 https://arxiv.org/abs/0903.3538 + SHOW_WARN_STATIC(ERROR::UNSUPPORTED_PULSAR_BIRTH_SPIN_PERIOD_DISTRIBUTION, // show warning + "Using spin = 0.0", + OBJECT_TYPE::BASE_STAR, + STELLAR_TYPE::NEUTRON_STAR); + pSpin = 0.0; + break; + + case PULSAR_BIRTH_SPIN_PERIOD_DISTRIBUTION::UNIFORM: { // UNIFORM distribution between minimum and maximum value as in Oslowski et al 2011 https://arxiv.org/abs/0903.3538 (default Pmin = and Pmax = ) + // and also Kiel et al 2008 https://arxiv.org/abs/0805.0059 (default Pmin = 10 ms and Pmax 100 ms, section 3.4) + + double maximum = OPTIONS->PulsarBirthSpinPeriodDistributionMax(); + double minimum = OPTIONS->PulsarBirthSpinPeriodDistributionMin(); + + pSpin = minimum + (RAND->Random() * (maximum - minimum)); + } break; + + case PULSAR_BIRTH_SPIN_PERIOD_DISTRIBUTION::NORMAL: { // NORMAL distribution from Faucher-Giguere and Kaspi 2006 https://arxiv.org/abs/astro-ph/0512585 + + // Values hard-coded for now, can make them options if necessary + // pulsarBirthSpinPeriodDistributionFaucherGiguereKaspi2006Mean = 300.0; + // pulsarBirthSpinPeriodDistributionFaucherGiguereKaspi2006Std = 150.0; + + double mean = 300.0; + double sigma = 150.0; + + do { pSpin = RAND->RandomGaussian(sigma) + mean;} while (utils::Compare(pSpin, 0.0) < 0); + + } break; + + default: // unknown distribution + SHOW_WARN_STATIC(ERROR::UNKNOWN_PULSAR_BIRTH_SPIN_PERIOD_DISTRIBUTION, // show warning + "Using spin = 0.0", + OBJECT_TYPE::BASE_STAR, + STELLAR_TYPE::NEUTRON_STAR); + pSpin = 0.0; + } + + return pSpin; +} + + +/* + * Calculate (log10 of) the magnetic field (in G) for a Pulsar at birth + * according to selected distribution (by commandline option) + * + * + * double CalculatePulsarBirthMagneticField() + * + * @return log10 of the birth magnetic field in G + */ +double NS::CalculatePulsarBirthMagneticField() { + + double log10B; + + switch (OPTIONS->PulsarBirthMagneticFieldDistribution()) { // which distribution? + + case PULSAR_BIRTH_MAGNETIC_FIELD_DISTRIBUTION::ZERO: // ZERO + log10B = 0.0; + break; + + case PULSAR_BIRTH_MAGNETIC_FIELD_DISTRIBUTION::FIXED: // FIXED - set to a fixed constant value + SHOW_WARN_STATIC(ERROR::UNSUPPORTED_PULSAR_BIRTH_MAGNETIC_FIELD_DISTRIBUTION, // show warning + "Using 0.0", + OBJECT_TYPE::BASE_STAR, + STELLAR_TYPE::NEUTRON_STAR); + log10B = 0.0; + break; + + case PULSAR_BIRTH_MAGNETIC_FIELD_DISTRIBUTION::FLATINLOG: { // FLAT IN LOG distribution from Oslowski et al 2011 https://arxiv.org/abs/0903.3538 (log10B0min = , log10B0max = ) + + double maximum = OPTIONS->PulsarBirthMagneticFieldDistributionMax(); + double minimum = OPTIONS->PulsarBirthMagneticFieldDistributionMin(); + + log10B = minimum + (RAND->Random() * (maximum - minimum)); + + } break; + + case PULSAR_BIRTH_MAGNETIC_FIELD_DISTRIBUTION::UNIFORM: { // UNIFORM flat distribution used in Kiel et al 2008 https://arxiv.org/abs/0805.0059 (log10B0min = 11, log10B0max = 13.5 see section 3.4 and Table 1.) + + + double maximum = PPOW(10.0, OPTIONS->PulsarBirthMagneticFieldDistributionMax()); + double minimum = PPOW(10.0, OPTIONS->PulsarBirthMagneticFieldDistributionMin()); + + log10B = log10(minimum + (RAND->Random() * (maximum - minimum))); + } break; + + case PULSAR_BIRTH_MAGNETIC_FIELD_DISTRIBUTION::LOGNORMAL: { // LOG NORMAL distribution from Faucher-Giguere and Kaspi 2006 https://arxiv.org/abs/astro-ph/0512585 + + // Values hard-coded for now, can make them options if necessary + // pulsarBirthMagneticFieldDistributionFaucherGiguereKaspi2006Mean = 12.65 + // pulsarBirthMagneticFieldDistributionFaucherGiguereKaspi2006Std = 0.55 + + double mean = 12.65; + double sigma = 0.55; + + log10B = RAND->RandomGaussian(sigma) + mean; + } break; + + default: // unknown distribution + SHOW_WARN_STATIC(ERROR::UNKNOWN_PULSAR_BIRTH_MAGNETIC_FIELD_DISTRIBUTION, // show warning + "Using 0.0", + OBJECT_TYPE::BASE_STAR, + STELLAR_TYPE::NEUTRON_STAR); + log10B = 0.0; + } + + return log10B; +} + + +/* + * Calculate the moment of inertia for a Neutron Star using a model independent relation between + * the moment of inertia, mass and radius of a neutron star - return MoI in CGS. + * + * Uses m_Mass and m_Radius to calculate moment of inertia. + * + * Raithel et al. 2016, eq 8 in https://arxiv.org/abs/1603.06594 + * https://tap.arizona.edu/sites/tap.arizona.edu/files/Raithel_2015_manuscript.pdf + * + * + * double CalculateMomentOfInertiaCGS() + * + * @return Moment of inertia in g cm^2 + */ +double NS::CalculateMomentOfInertiaCGS() const { + + // pow() is slow - use multiplication + + double r_km = m_Radius * RSOL_TO_KM; + double m_r = m_Mass / r_km; + double m_r_4 = m_r * m_r * m_r * m_r; + + double r_cm = m_Radius * RSOL_TO_CM; + double r_cm_2 = r_cm * r_cm; + + return 0.237 * m_Mass * MSOL_TO_G * r_cm_2 * (1.0 + (4.2 * m_r) + 90.0 * m_r_4); +} + + +/* + * Calculate the spin down rate for isolated Neutron Stars in cgs + * + * See Equation 2 in https://arxiv.org/pdf/1912.02415.pdf + * + * This is changed to the form of calculating spindown with P and Pdot, then convert to OmegaDot and to be recorded in the output file. + * Evolution of the inclination between pulsar magnetic and rotational axes will be considered in a future version. + * + * double CalculateSpinDownRate(const double p_Omega, const double p_MomentOfInteria, const double p_MagField, const double p_Radius) + * + * @param [IN] p_Omega Pulsar spin frequency. + * @param [IN] p_MomentOfInteria Moment of Interia of the Neutron Star in kg m^2 + * @param [IN] p_MagField Magnetic field in Tesla + * @param [IN] p_Radius Radius of the Neutron Star in kilometres + * @return Spin down rate (spin frequency derivative) of an isolated Neutron Star in s^(-2) + */ +double NS::CalculateSpinDownRate(const double p_Omega, const double p_MomentOfInteria, const double p_MagField, const double p_Radius) const { + + // pow() is slow - use multiplication + + double period = _2_PI / p_Omega; // convert frequency to period + double cgsRadius = p_Radius * KM_TO_CM; // radius in cm + double radius_6 = cgsRadius * cgsRadius * cgsRadius * cgsRadius * cgsRadius * cgsRadius; + double cgsMagField = p_MagField * TESLA_TO_GAUSS; // B field in G + double magField_2 = cgsMagField * cgsMagField; + constexpr double _8_PI_2 = 8.0 * PI_2; + constexpr double _3_C_3 = 3.0E6 * C * C * C; // 3.0 * (C * 100.0) * (C * 100.0) * (C * 100.0) + double pDotTop = _8_PI_2 * radius_6 * magField_2; + double pDotBottom = _3_C_3 * p_MomentOfInteria * period; + double pDot = pDotTop / pDotBottom; // period derivative + + return(-pDot * p_Omega / period); // convert period derivative to frequency derivative, which is what is recorded in the output +} + + +/* + * Calculates and sets pulsar parameters at birth of pulsar + * + * Modifies the following class member variables: + * + * m_AngularMomentum_CGS + * m_MomentOfInertia_CGS + * m_PulsarDetails.birthPeriod + * m_PulsarDetails.birthSpinDownRate + * m_PulsarDetails.magneticField + * m_PulsarDetails.spinDownRate + * m_PulsarDetails.spinFrequency + * + * + * void CalculateAndSetPulsarParameters() + */ +void NS::CalculateAndSetPulsarParameters() { + + m_PulsarDetails.magneticField = PPOW(10.0, CalculatePulsarBirthMagneticField()) * GAUSS_TO_TESLA; // magnetic field in Gauss -> convert to Tesla + m_PulsarDetails.spinPeriod = CalculatePulsarBirthSpinPeriod(); // spin period in ms + m_PulsarDetails.spinFrequency = _2_PI / (m_PulsarDetails.spinPeriod * SECONDS_IN_MS); + m_PulsarDetails.birthPeriod = m_PulsarDetails.spinPeriod * SECONDS_IN_MS; // convert from ms to s + m_MomentOfInertia_CGS = CalculateMomentOfInertiaCGS(); // in CGS g cm^2 + + // Note we convert neutronStarMomentOfInertia from CGS to SI here + m_PulsarDetails.spinDownRate = CalculateSpinDownRate(m_PulsarDetails.spinFrequency, m_MomentOfInertia_CGS, m_PulsarDetails.magneticField, m_Radius * RSOL_TO_KM); + m_PulsarDetails.birthSpinDownRate = m_PulsarDetails.spinDownRate; + m_AngularMomentum_CGS = m_MomentOfInertia_CGS * m_PulsarDetails.spinFrequency; // in CGS g cm^2 s^-1 +} + + +/* + * Update the magnetic field and spins of neutron stars when it's deemed as an isolated pulsar. + * + * This function is called in multiple situations in the NS::UpdateMagneticFieldAndSpin() function + * + * Modifies the following class member variables: + * + * m_AngularMomentum_CGS + * m_PulsarDetails.spinFrequency + * m_PulsarDetails.magneticField + * m_PulsarDetails.spinDownRate + * + * + * void SpinDownIsolatedPulsar(const double p_Stepsize) + * + * @param [IN] p_Stepsize Timestep size for integration (in seconds) + */ +void NS::SpinDownIsolatedPulsar(const double p_Stepsize) { + + double NSradius_IN_CM = m_Radius * RSOL_TO_KM * KM_TO_CM; + double NSradius_3 = NSradius_IN_CM * NSradius_IN_CM * NSradius_IN_CM; + double NSradius_6 = NSradius_3 * NSradius_3; + constexpr double _8_PI_2 = 8.0 * PI_2; + constexpr double _3_C_3 = 3.0 * C * C * C * 1000000.0; + + double initialMagField = m_PulsarDetails.magneticField; // (in T) + double initialMagField_G = initialMagField * TESLA_TO_GAUSS; + double initialSpinPeriod = _2_PI / m_PulsarDetails.spinFrequency; + double magFieldLowerLimit = PPOW(10.0, OPTIONS->PulsarLog10MinimumMagneticField()) * GAUSS_TO_TESLA; + double magFieldLowerLimit_G = magFieldLowerLimit * TESLA_TO_GAUSS; + double tau = OPTIONS->PulsarMagneticFieldDecayTimescale() * MYR_TO_YEAR * SECONDS_IN_YEAR; + + // calculate isolated decay of the magnetic field for a neutron star + // see Equation 6 in arXiv:0903.3538v2 + m_PulsarDetails.magneticField = magFieldLowerLimit + (initialMagField - magFieldLowerLimit) * exp(-p_Stepsize / tau); // update pulsar magnetic field in SI. + + // calculate the spin frequency for isolated neutron stars + // see Equation 6 in arxiv:1912.02415 + // The rest of the calculations are carried out in cgs. + double constant2 = (_8_PI_2 * NSradius_6) / (_3_C_3 * m_MomentOfInertia_CGS); + double term1 = magFieldLowerLimit_G * magFieldLowerLimit_G * p_Stepsize; + double term2 = tau * magFieldLowerLimit_G * ( m_PulsarDetails.magneticField * TESLA_TO_GAUSS - initialMagField_G); + double term3 = (tau / 2.0) * (TESLA_TO_GAUSS * TESLA_TO_GAUSS * (m_PulsarDetails.magneticField * m_PulsarDetails.magneticField) - (initialMagField_G * initialMagField_G)); + double Psquared = constant2 * (term1 - term2 - term3) + (initialSpinPeriod * initialSpinPeriod); + + double P_f = std::sqrt(Psquared); + m_PulsarDetails.spinFrequency = _2_PI / P_f; // pulsar spin frequency + + // calculate the spin down rate for isolated neutron stars + // see Equation 4 in arXiv:0903.3538v2 (Our version is in cgs) + m_PulsarDetails.spinDownRate = CalculateSpinDownRate(m_PulsarDetails.spinFrequency,m_MomentOfInertia_CGS , m_PulsarDetails.magneticField, NSradius_IN_CM / KM_TO_CM); + m_AngularMomentum_CGS = m_PulsarDetails.spinFrequency * m_MomentOfInertia_CGS; // angular momentum of star in CGS +} + + +/* + * Calculate the magnetic field, spin, spin-down rate and angular momentum of a neutron star, + * when it's accreting mass from companion. + * The calculations used in this function follows closely Sec. 2.2.1 in arxiv:1912.02415 + * We carry out the calculations in this function using cgs units. + * This function is called in the NS::UpdateMagneticFieldAndSpin() function + * + * Returns, in a tuple, these four quantities of a neutron star: + * + * magnetic field strength + * spin frequency + * spin-down rate + * angular momentum + * + * DBL_DBL_DBL_DBL PulsarAccretion(const double p_MagField, const double p_SpinFrequency, const double p_AngularMomentum, const double p_Stepsize, const double p_MassGainPerTimeStep, const double kappa, const double p_Epsilon) + * @param [IN] p_MagField NS magnetic field strength at the beginning of accretion (in Tesla) + * @param [IN] p_SpinFrequency Spin frequency for the NS at the beginning of accretion (in Hz) + * @param [IN] p_AngularMomentum Angular momentum of the NS at the beginning of accretion (g cm-2 / s) + * @param [IN] p_Stepsize Timestep size for integration (in seconds) + * @param [IN] p_MassGainPerTimeStep Mass transferred from the secondary for each iteration (in g) + * @param [IN] p_Kappa Magnetic field mass decay scale (in g) + * @param [IN] p_Epsilon Efficiency factor allowing for uncertainties of coupling magnetic field and matter. + * @return Tuple containing the updated magnetic field strength, spin frequency, spin-down rate and angular momentum of neutron star + */ +DBL_DBL_DBL_DBL NS::PulsarAccretion(const double p_MagField, const double p_SpinFrequency, const double p_AngularMomentum, const double p_Stepsize, const double p_MassGainPerTimeStep, const double p_Kappa, const double p_Epsilon) { + double initialMagField_G = p_MagField * TESLA_TO_GAUSS; + double magFieldLowerLimit_G = PPOW(10.0, OPTIONS->PulsarLog10MinimumMagneticField()) ; + double mass_g = m_Mass * MSOL_TO_G; // in g + double r_cm = m_Radius * RSOL_TO_KM * KM_TO_CM; + double MoI = m_MomentOfInertia_CGS;// * CGS_SI; + double angularMomentum = p_AngularMomentum;// * CGS_SI; + double newPulsarMagneticField = (initialMagField_G - magFieldLowerLimit_G) * exp(-p_MassGainPerTimeStep / p_Kappa) + magFieldLowerLimit_G; + std::cout << "IB: " << initialMagField_G << " FB: " << newPulsarMagneticField << " MT: " << p_MassGainPerTimeStep << " kappa: " << p_Kappa << " BMIN: " << magFieldLowerLimit_G << " step: " << p_Stepsize <NeutronStarAccretionInCE()) { // which mode of CE accretion to use? + + case NS_ACCRETION_IN_CE::ZERO: // ZERO, no effect from CE + NSCE = 0; + break; + + case NS_ACCRETION_IN_CE::DISK: // DISK , CE effect same as a RLOF case + NSCE = 1; + break; + + case NS_ACCRETION_IN_CE::SURFACE: // SURFACE, mass are accreted onto the surface of the neutron star. + NSCE = 2; + break; + + default: // unknown distribution + SHOW_WARN_STATIC(ERROR::UNKNOWN_NS_ACCRETION_IN_CE, // show warning + "Using zero accretion", + OBJECT_TYPE::BASE_BINARY_STAR, + STELLAR_TYPE::NEUTRON_STAR); + NSCE = 0; + } + + double kappa = OPTIONS->PulsarMagneticFieldDecayMassscale() * MSOL_TO_G; + + if ((!p_RecycledNS && !p_CommonEnvelope) || (!p_RecycledNS && utils::Compare(p_MassGainPerTimeStep, 0.0) == 0 )) { + // These are the ''classical'' isolated pulsars. They experience spin-down. + SpinDownIsolatedPulsar(p_Stepsize); + } + + else if ( utils::Compare(p_MassGainPerTimeStep, 0.0) > 0) { + // Next, we update pulsar that goes through mass transfer. + // Note that we do not need to set hard lower limit on the spin period, + // as propeller effect should be included in the calculation, + // which means pulsars will start spinning down when the AM is no longer transferred along mass transfer. + if ((!p_CommonEnvelope) || (p_CommonEnvelope && (NSCE == 1))){ + // Pulsar evolution during stable mass transfer (RLOF), or if using the 'DISK' option + // in the CE accretion option + + //----to be deleted--- + // To prevent in some cases where the numerical calculations return negative spin period/frequency, + // We divide the step of mass transfer into sufficiently small ones where in each step, + // no negative spin period is seen. + //----to be deleted--- + std::tuple accretionResults = PulsarAccretion(m_PulsarDetails.magneticField, m_PulsarDetails.spinFrequency, m_AngularMomentum_CGS, p_Stepsize, p_MassGainPerTimeStep * 1000.0, kappa, p_Epsilon); + + //----to be deleted--- + // int divideTimestepBy = 100; + // bool done = false; + // while (!done && utils::Compare(divideTimestepBy, 1000000) > 0.0) { + + // divideTimestepBy *= 2; + + // double thisTimestepSize = p_Stepsize / divideTimestepBy; + // double thisMassGain = p_MassGainPerTimeStep * 1000.0 / divideTimestepBy; + // double B ; + // double f ; + // double fdot ; + // double am ; + + // std::tie(B, f, fdot, am) = PulsarAccretion(m_PulsarDetails.magneticField, m_PulsarDetails.spinFrequency, + // m_AngularMomentum_CGS, thisTimestepSize, thisMassGain, kappa, p_Epsilon); + + // int count = 0; + // while (!done) { + // std::tie(B, f, fdot, am) = PulsarAccretion(B, f, am, thisTimestepSize, thisMassGain, kappa, p_Epsilon); + + // if (utils::Compare(f, 0.0) < 0) break; + // if (++count >= divideTimestepBy) done = true; + // } + // } + + // double newTimeStepSize = p_Stepsize / divideTimestepBy; + // double newMassGain = p_MassGainPerTimeStep * 1000.0 / divideTimestepBy ; + // accretionResults = PulsarAccretion(m_PulsarDetails.magneticField, m_PulsarDetails.spinFrequency, + // m_AngularMomentum_CGS, newTimeStepSize, newMassGain, kappa, p_Epsilon); + // for (int n = 1; n<= int(divideTimestepBy); n++){ + + // double last_B_2 = std::get<0>(accretionResults); + // double last_f_2 = std::get<1>(accretionResults); + // double last_am_2 = std::get<3>(accretionResults); + + // accretionResults = PulsarAccretion(last_B_2, last_f_2, last_am_2, newTimeStepSize, newMassGain, kappa, p_Epsilon); + //----to be deleted--- + + m_PulsarDetails.magneticField = std::get<0>(accretionResults); + m_PulsarDetails.spinFrequency = std::get<1>(accretionResults); + m_AngularMomentum_CGS = std::get<2>(accretionResults); + + m_PulsarDetails.spinDownRate = CalculateSpinDownRate(m_PulsarDetails.spinFrequency,m_MomentOfInertia_CGS , m_PulsarDetails.magneticField, m_Radius * RSOL_TO_KM ); + } + + + else if ((p_CommonEnvelope) && (NSCE == 2) && utils::Compare(p_MassGainPerTimeStep, 0.0) > 0) { + // Mass transfer through CE when accretion happens at the surface of the NS + + double initialMagField = m_PulsarDetails.magneticField; + double initialMagField_G = initialMagField * TESLA_TO_GAUSS; + double magFieldLowerLimit_G = PPOW(10.0, OPTIONS->PulsarLog10MinimumMagneticField()) ; + + double mass_g = m_Mass * MSOL_TO_G; // in g + double r_cm = m_Radius * RSOL_TO_KM * KM_TO_CM; // in cm + m_MomentOfInertia_CGS = CalculateMomentOfInertiaCGS() ; + double MoI = m_MomentOfInertia_CGS; + double angularMomentum = m_AngularMomentum_CGS; + double newPulsarMagneticField = (initialMagField_G - magFieldLowerLimit_G) * exp(-p_MassGainPerTimeStep * 1000.0 / kappa) + magFieldLowerLimit_G; + double r_cm_3 = r_cm * r_cm * r_cm ; + double Jacc = MoI * PPOW(G_CGS * mass_g /r_cm_3, 0.5) * p_MassGainPerTimeStep * 1000.0 / mass_g ; + + m_AngularMomentum_CGS = angularMomentum + Jacc ; + m_PulsarDetails.magneticField = newPulsarMagneticField * GAUSS_TO_TESLA; + m_PulsarDetails.spinFrequency = m_AngularMomentum_CGS / MoI ; + + m_PulsarDetails.spinDownRate = CalculateSpinDownRate(m_PulsarDetails.spinFrequency,m_MomentOfInertia_CGS , m_PulsarDetails.magneticField, m_Radius * RSOL_TO_KM ); + } + + else if ((p_CommonEnvelope) && (NSCE == 0)) { + // If mass transfer is happening through CE and chosen 'ZERO' in CE accretion mode, + // treat the pulsar as isolated and should be spun down. + SpinDownIsolatedPulsar(p_Stepsize); + } +} +} \ No newline at end of file diff --git a/src/constants.h b/src/constants.h index 54281ea62..56d76f75f 100755 --- a/src/constants.h +++ b/src/constants.h @@ -183,7 +183,7 @@ extern OBJECT_ID globalObjectId; constexpr double FLOAT_TOLERANCE_ABSOLUTE = 0.0000005; // absolute tolerance for floating-point comparisons if COMPARE_GLOBAL_TOLERANCE is defined constexpr double FLOAT_TOLERANCE_RELATIVE = 0.0000005; // relative tolerance for floating-point comparisons if COMPARE_GLOBAL_TOLERANCE is defined -constexpr double ROOT_ABS_TOLERANCE = 1.0E-10; // absolute tolerance for root finder +constexpr double ROOT_ABS_TOLERANCE = 1.0E-9; // absolute tolerance for root finder constexpr double ROOT_REL_TOLERANCE = 1.0E-6; // relative tolerance for root finder From c820189260f5bb51f232c0e1839008f24c9b787e Mon Sep 17 00:00:00 2001 From: Yuzhe Song Date: Fri, 19 Apr 2024 17:15:59 +1000 Subject: [PATCH 08/19] Revert "Updates to NS.cpp per reviews and BaseB^CaryStar.cpp for issue #1076" This reverts commit 24d15edeb8923d289a5e2384e6272d964d8e57ec. --- src/BaseBinaryStar.cpp | 19 +- src/NS.cpp | 84 +++--- src/NSapr9.cpp | 627 ----------------------------------------- src/constants.h | 2 +- 4 files changed, 47 insertions(+), 685 deletions(-) delete mode 100755 src/NSapr9.cpp diff --git a/src/BaseBinaryStar.cpp b/src/BaseBinaryStar.cpp index 731cbb925..01e93a127 100644 --- a/src/BaseBinaryStar.cpp +++ b/src/BaseBinaryStar.cpp @@ -387,11 +387,11 @@ void BaseBinaryStar::SetRemainingValues() { m_Flags.mergesInHubbleTime = false; m_Unbound = false; - m_SystemicVelocity = Vector3d(); - m_NormalizedOrbitalAngularMomentumVector = Vector3d(); - m_ThetaE = DEFAULT_INITIAL_DOUBLE_VALUE; - m_PhiE = DEFAULT_INITIAL_DOUBLE_VALUE; - m_PsiE = DEFAULT_INITIAL_DOUBLE_VALUE; + m_SystemicVelocity = Vector3d(); + m_NormalizedOrbitalAngularMomentumVector = Vector3d(); + m_ThetaE = DEFAULT_INITIAL_DOUBLE_VALUE; + m_PhiE = DEFAULT_INITIAL_DOUBLE_VALUE; + m_PsiE = DEFAULT_INITIAL_DOUBLE_VALUE; m_SynchronizationTimescale = DEFAULT_INITIAL_DOUBLE_VALUE; m_CircularizationTimescale = DEFAULT_INITIAL_DOUBLE_VALUE; @@ -1273,7 +1273,7 @@ bool BaseBinaryStar::ResolveSupernova() { Vector3d relativeVelocityVector = relativeVelocityVectorPrev + (natalKickVector - companionRecoilVector); // km/s - PostSN relative velocity vector Vector3d orbitalAngularMomentumVector = cross(separationVectorPrev, relativeVelocityVector); // km^2 s^-1 - PostSN specific orbital angular momentum vector - double orbitalAngularMomentum = orbitalAngularMomentumVector.mag; // km^2 s^-1 - PostSN specific orbital angular momentum + double orbitalAngularMomentum = orbitalAngularMomentumVector.mag; // km^2 s^-1 - PostSN specific orbital angular momentum m_NormalizedOrbitalAngularMomentumVector = orbitalAngularMomentumVector/orbitalAngularMomentum; // set unit vector here to make printing out the inclination vector easier Vector3d eccentricityVector = cross(relativeVelocityVector, orbitalAngularMomentumVector) / @@ -1988,13 +1988,6 @@ void BaseBinaryStar::CalculateMassTransfer(const double p_Dt) { } } - // If the current timestep is smaller than the donor's thermal timescale, only a fraction of the mass - // that needs to be donated has time to be donated - double donorThermalTimescale = m_Donor->CalculateThermalTimescale(); - if (p_Dt < donorThermalTimescale) { - massDiffDonor = (p_Dt / donorThermalTimescale) * massDiffDonor; - } - if (!m_CEDetails.CEEnow) { // CE flagged? // no double massGainAccretor = -massDiffDonor * m_FractionAccreted; // set accretor mass gain to mass loss * conservativeness diff --git a/src/NS.cpp b/src/NS.cpp index 2e596da37..320d2c095 100755 --- a/src/NS.cpp +++ b/src/NS.cpp @@ -395,7 +395,7 @@ void NS::SpinDownIsolatedPulsar(const double p_Stepsize) { // see Equation 6 in arXiv:0903.3538v2 m_PulsarDetails.magneticField = magFieldLowerLimit + (initialMagField - magFieldLowerLimit) * exp(-p_Stepsize / tau); // update pulsar magnetic field in SI. - // calculate the spin frequency for isolated neutron stars + // calculate the spin down rate for isolated neutron stars // see Equation 6 in arxiv:1912.02415 // The rest of the calculations are carried out in cgs. double constant2 = (_8_PI_2 * NSradius_6) / (_3_C_3 * m_MomentOfInertia_CGS); @@ -416,10 +416,9 @@ void NS::SpinDownIsolatedPulsar(const double p_Stepsize) { /* * Calculate the magnetic field, spin, spin-down rate and angular momentum of a neutron star, - * when it's accreting mass from companion through stable mass transfer. - * The calculations used in this function follows closely Sec. 2.2.1 in arxiv:1912.02415 + * when it's accreting mass from companion. * We carry out the calculations in this function using cgs units. - * This function is called in the NS::UpdateMagneticFieldAndSpin() function + * This function is called in multiple situations in the NS::UpdateMagneticFieldAndSpin() function * * Returns, in a tuple, these four quantities of a neutron star: * @@ -433,44 +432,39 @@ void NS::SpinDownIsolatedPulsar(const double p_Stepsize) { * @param [IN] p_SpinFrequency Spin frequency for the NS at the beginning of accretion (in Hz) * @param [IN] p_AngularMomentum Angular momentum of the NS at the beginning of accretion (g cm-2 / s) * @param [IN] p_Stepsize Timestep size for integration (in seconds) - * @param [IN] p_MassGainPerTimeStep Mass transferred from the secondary for each iteration (in g) - * @param [IN] p_Kappa Magnetic field mass decay scale (in g) - * @param [IN] p_Epsilon Efficiency factor allowing for uncertainties of coupling magnetic field and matter. - * @return Tuple containing the updated magnetic field strength, spin frequency, spin-down rate and angular momentum of neutron star + * @param [IN] p_MassGainPerTimeStep Mass loss from the secondary for each iteration (in g) + * @param [IN] p_Kappa Mass loss from the secondary for each iteration (in g) + * @param [IN] p_Epsilon Uncertainty due to mass loss + * @return Tuple containing the updated magnetic field strength, spin frequency, angular momentum of neutron star */ DBL_DBL_DBL_DBL NS::PulsarAccretion(const double p_MagField, const double p_SpinFrequency, const double p_AngularMomentum, const double p_Stepsize, const double p_MassGainPerTimeStep, const double p_Kappa, const double p_Epsilon) { double initialMagField_G = p_MagField * TESLA_TO_GAUSS; - double magFieldLowerLimit_G = PPOW(10.0, OPTIONS->PulsarLog10MinimumMagneticField()) ; // convert to Gauss + double magFieldLowerLimit_G = PPOW(10.0, OPTIONS->PulsarLog10MinimumMagneticField()) ; double mass_g = m_Mass * MSOL_TO_G; // in g - double r_cm = m_Radius * RSOL_TO_KM * KM_TO_CM; // NS radius in cm - double MoI = m_MomentOfInertia_CGS; - double angularMomentum = p_AngularMomentum; - - //Magnetic field decay due to mass transfer. - //Follows Eq. 12 in arxiv:1912.02415 + double r_cm = m_Radius * RSOL_TO_KM * KM_TO_CM; + double MoI = m_MomentOfInertia_CGS;// * CGS_SI; + double angularMomentum = p_AngularMomentum;// * CGS_SI; double newPulsarMagneticField = (initialMagField_G - magFieldLowerLimit_G) * exp(-p_MassGainPerTimeStep / p_Kappa) + magFieldLowerLimit_G; // calculate the Alfven radius for an accreting neutron star - // see Equation 10 in arxiv:1912.02415 + // see Equation 8 in arXiv:0903.3538v2 double mDot = p_MassGainPerTimeStep / p_Stepsize; double R_CM_6 = r_cm * r_cm * r_cm * r_cm * r_cm * r_cm; - double p = R_CM_6 * R_CM_6 / (mass_g * mass_g * mDot); - double q = PPOW(p, 1.0/7.0); - double alfvenConst = PPOW(2 * PI_2 / G_CGS, 1.0/7.0) ; - double alfvenRadius = alfvenConst * q * PPOW(initialMagField_G, 4.0/7.0); + double B_4 = newPulsarMagneticField * newPulsarMagneticField * newPulsarMagneticField * newPulsarMagneticField; + double R_a_top = 8.0 * R_CM_6 * R_CM_6 * B_4; + double R_a_bot = mass_g * mDot * mDot * (G * 1000.0); + double alfvenRadius = PPOW(R_a_top / R_a_bot, 1.0 / 7.0) / 2.0; - // calculate the difference in the keplerian angular velocity at the magnetic radius - // and surface angular velocity of the neutron star - // magnetic radius is half of alfven radius - // see Equation 2 in 1994MNRAS.269..455J / Equation 8 in arxiv:1912.02415 - double keplerianVelocityAtMagneticRadius = std::sqrt(4.0 * (G_CGS) * mass_g / alfvenRadius); - double keplerianAngularVelocityAtMagneticRadius = 2.0 * keplerianVelocityAtMagneticRadius / alfvenRadius; - double omegaDifference = keplerianAngularVelocityAtMagneticRadius - p_SpinFrequency; + // calculate the difference in the keplerian angular velocity and surface angular velocity of the neutron star in m + // see Equation 2 in 1994MNRAS.269..455J + double keplerianVelocityAtAlfvenRadius = std::sqrt(2.0 * (G * 1000.0) * mass_g / alfvenRadius); + double keplerianAngularVelocityAtAlfvenRadius = 4.0 * M_PI * keplerianVelocityAtAlfvenRadius / alfvenRadius; + double velocityDifference = keplerianAngularVelocityAtAlfvenRadius - p_SpinFrequency; // calculate the change in angular momentum due to accretion // see Equation 12 in arXiv:0805.0059/ Equation 8 in arxiv:1912.02415 - double Jdot = p_Epsilon * omegaDifference * alfvenRadius * alfvenRadius * mDot / 4.0; - angularMomentum = angularMomentum + Jdot * p_Stepsize; + double Jdot = p_Epsilon * velocityDifference * alfvenRadius * alfvenRadius * mDot; + angularMomentum = angularMomentum + Jdot * p_Stepsize; return std::make_tuple(newPulsarMagneticField * GAUSS_TO_TESLA, angularMomentum / MoI, Jdot / MoI, angularMomentum); } @@ -494,8 +488,8 @@ DBL_DBL_DBL_DBL NS::PulsarAccretion(const double p_MagField, const double p_Spin * @param [IN] p_CommonEnvelope Indicates whether there there is a common envelope - true or false * @param [IN] p_RecycledNS Indicates whether this star is/was a recycled neutron star - true or false * @param [IN] p_Stepsize Timestep size for integration (in seconds) - * @param [IN] p_MassGainPerTimeStep Mass trasnferred from the secondary for each iteration (in kg) - * @param [IN] p_Epsilon Efficiency factor allowing for uncertainties of coupling magnetic field and matter. + * @param [IN] p_MassGainPerTimeStep Mass loss from the secondary for each iteration (in kg) + * @param [IN] p_Epsilon Uncertainty due to mass loss */ void NS::UpdateMagneticFieldAndSpin(const bool p_CommonEnvelope, const bool p_RecycledNS, const double p_Stepsize, const double p_MassGainPerTimeStep, const double p_Epsilon) { @@ -525,9 +519,9 @@ void NS::UpdateMagneticFieldAndSpin(const bool p_CommonEnvelope, const bool p_Re double kappa = OPTIONS->PulsarMagneticFieldDecayMassscale() * MSOL_TO_G; - if ((!p_RecycledNS && !p_CommonEnvelope) || (p_RecycledNS && utils::Compare(p_MassGainPerTimeStep, 0.0) == 0 )) { + if ((!p_RecycledNS && !p_CommonEnvelope) || (!p_RecycledNS && utils::Compare(p_MassGainPerTimeStep, 0.0) == 0 )) { // These are the ''classical'' isolated pulsars. They experience spin-down. - SpinDownIsolatedPulsar(p_Stepsize); + SpinDownIsolatedPulsar(p_Stepsize); } else if ( utils::Compare(p_MassGainPerTimeStep, 0.0) > 0) { @@ -538,8 +532,11 @@ void NS::UpdateMagneticFieldAndSpin(const bool p_CommonEnvelope, const bool p_Re if ((!p_CommonEnvelope) || (p_CommonEnvelope && (NSCE == 1))){ // Pulsar evolution during stable mass transfer (RLOF), or if using the 'DISK' option // in the CE accretion option - std::tuple accretionResults = PulsarAccretion(m_PulsarDetails.magneticField, m_PulsarDetails.spinFrequency, m_AngularMomentum_CGS, p_Stepsize, p_MassGainPerTimeStep * 1000.0, kappa, p_Epsilon); - + // To prevent in some cases where the numerical calculations return negative spin period/frequency, + // We divide the step of mass transfer into sufficiently small ones where in each step, + // no negative spin period is seen. + std::tuple accretionResults; + int divideTimestepBy = 100; bool done = false; while (!done && utils::Compare(divideTimestepBy, 1000000) > 0.0) { @@ -576,19 +573,18 @@ void NS::UpdateMagneticFieldAndSpin(const bool p_CommonEnvelope, const bool p_Re double last_am_2 = std::get<3>(accretionResults); accretionResults = PulsarAccretion(last_B_2, last_f_2, last_am_2, newTimeStepSize, newMassGain, kappa, p_Epsilon); + } m_PulsarDetails.magneticField = std::get<0>(accretionResults); m_PulsarDetails.spinFrequency = std::get<1>(accretionResults); - m_AngularMomentum_CGS = std::get<3>(accretionResults); - - m_PulsarDetails.spinDownRate = CalculateSpinDownRate(m_PulsarDetails.spinFrequency,m_MomentOfInertia_CGS , m_PulsarDetails.magneticField, m_Radius * RSOL_TO_KM ); + m_AngularMomentum_CGS = std::get<2>(accretionResults); - } + m_PulsarDetails.spinDownRate = CalculateSpinDownRate(m_PulsarDetails.spinFrequency,m_MomentOfInertia_CGS , m_PulsarDetails.magneticField, m_Radius * RSOL_TO_KM ); } - - else if ((p_CommonEnvelope) && (NSCE == 2)) { + else if ((p_CommonEnvelope) && (NSCE == 2) && utils::Compare(p_MassGainPerTimeStep, 0.0) > 0) { // Mass transfer through CE when accretion happens at the surface of the NS + double initialMagField = m_PulsarDetails.magneticField; double initialMagField_G = initialMagField * TESLA_TO_GAUSS; double magFieldLowerLimit_G = PPOW(10.0, OPTIONS->PulsarLog10MinimumMagneticField()) ; @@ -598,9 +594,9 @@ void NS::UpdateMagneticFieldAndSpin(const bool p_CommonEnvelope, const bool p_Re m_MomentOfInertia_CGS = CalculateMomentOfInertiaCGS() ; double MoI = m_MomentOfInertia_CGS; double angularMomentum = m_AngularMomentum_CGS; - double newPulsarMagneticField = (initialMagField_G - magFieldLowerLimit_G) * exp(-p_MassGainPerTimeStep * 1000.0 / kappa) + magFieldLowerLimit_G; + double newPulsarMagneticField = (initialMagField_G - magFieldLowerLimit_G) * exp(-p_MassGainPerTimeStep / kappa) + magFieldLowerLimit_G; double r_cm_3 = r_cm * r_cm * r_cm ; - double Jacc = MoI * PPOW(G_CGS * mass_g /r_cm_3, 0.5) * p_MassGainPerTimeStep * 1000.0 / mass_g ; + double Jacc = MoI * PPOW(G * 1000.0 * mass_g /r_cm_3, 0.5) * p_MassGainPerTimeStep / mass_g ; m_AngularMomentum_CGS = angularMomentum + Jacc ; m_PulsarDetails.magneticField = newPulsarMagneticField * GAUSS_TO_TESLA; @@ -613,6 +609,6 @@ void NS::UpdateMagneticFieldAndSpin(const bool p_CommonEnvelope, const bool p_Re // If mass transfer is happening through CE and chosen 'ZERO' in CE accretion mode, // treat the pulsar as isolated and should be spun down. SpinDownIsolatedPulsar(p_Stepsize); + } } } -} \ No newline at end of file diff --git a/src/NSapr9.cpp b/src/NSapr9.cpp deleted file mode 100755 index 43819b33b..000000000 --- a/src/NSapr9.cpp +++ /dev/null @@ -1,627 +0,0 @@ -#include "Rand.h" -#include "NS.h" - - -/* - * Calculate the luminosity of a Neutron Star - * - * Hurley et al. 2000, eq 93 - * - * Called (indirectly) from GiantBranch, so must be static. - * - * - * double CalculateLuminosityOnPhase_Static(const double p_Mass, const double p_Time) - * - * @param [IN] p_Mass Mass in Msol - * @param [IN] p_Time Time since formation of the object in Myr - * @return Luminosity of the Neutron Star in Lsol - */ -double NS::CalculateLuminosityOnPhase_Static(const double p_Mass, const double p_Time) { - double t = std::max(p_Time, 0.1); - return 0.02 * PPOW(p_Mass, 2.0 / 3.0) / (t * t); -} - - -/* - * Choose timestep for Pulsar Evolution - * - * Pulsars evolve very fast when they are first born, and evolve slower as they age. - * Hence, timestep is chosen to be small when pulsar is young, and is slowly increased - * as the pulsar ages. - * - * double ChooseTimestep(const double p_Time) - * - * @param [IN] p_Time Current age of star in Myr - * @return Suggested timestep (dt) - */ -double NS::ChooseTimestep(const double p_Time) const { - double result = 500.0; // default value - - if (p_Time < 0.01) { - result = 0.001; - } - else if (p_Time < 0.1) { - result = 0.01; - } - else if (p_Time < 1.0) { - result = 0.1; - } - else if (p_Time < 10.0) { - result = 1.0; - } - else if (p_Time < 500.0) { - double slope = 1.58859191006; // 1.58859191006 = log10(500.0) / (log10(500.0) - 1.0) - double log10_step = slope * (log10(p_Time) - 1.0); - result = PPOW(10.0, log10_step); - } - - return result; -} - - -/* - * Calculate Neutron Star radius according to selected equation of state (by commandline option) - * - * - * double CalculateRadiusOnPhaseInKM_Static(const double p_Mass) - * - * @param [IN] p_Mass Mass in Msol - * @return Radius of Neutron Star in km - */ -double NS::CalculateRadiusOnPhaseInKM_Static(const double p_Mass) { - - double radius; - - switch (OPTIONS->NeutronStarEquationOfState()) { // which equation-of-state? - - case NS_EOS::SSE: // SSE - radius = 10.0; - break; - - case NS_EOS::ARP3: { // ARP3 - - // We don't extrapolate so masses outside table just set to extreme values - - std::map::const_iterator iter; - - iter = ARP3MassRadiusRelation.begin(); - double ARP3MinimumMass = iter->first; - double ARP3RadiusAtMinimumMass = iter->second; - - iter = ARP3MassRadiusRelation.end(); - double ARP3MaximumMass = (--iter)->first; - double ARP3RadiusAtMaximumMass = iter->second; - - if (utils::Compare(p_Mass, ARP3MinimumMass) < 0) { - radius = ARP3RadiusAtMinimumMass; - } - else if (utils::Compare(p_Mass, ARP3MaximumMass) > 0) { - radius = ARP3RadiusAtMaximumMass; - } - else{ - radius = utils::SampleFromTabulatedCDF(p_Mass, ARP3MassRadiusRelation); - } - } break; - - default: // unknown equation-of-state - SHOW_WARN_STATIC(ERROR::UNKNOWN_NS_EOS, // show warning - "Using default NS radius = 10.0", - OBJECT_TYPE::BASE_STAR, - STELLAR_TYPE::NEUTRON_STAR); - radius = 10.0; - } - - return radius; -} - - -/* - * Calculate core collapse Supernova parameters - * - * Called from GiantBranch, so must be static. - * - * - * DBL_DBL_DBL CalculateCoreCollapseSNParams_Static(const double p_Mass) - * - * @param [IN] p_Mass Mass in Msol - * @return Tuple containing Luminosity, Radius and Temperature of Neutron Star - */ -DBL_DBL_DBL NS::CalculateCoreCollapseSNParams_Static(const double p_Mass) { - double luminosity = CalculateLuminosityOnPhase_Static(p_Mass, 0.0); // Luminosity of Neutron Star as it cools - double radius = CalculateRadiusOnPhase_Static(p_Mass); // Radius of Neutron Star in Rsol - double temperature = BaseStar::CalculateTemperatureOnPhase_Static(luminosity, radius); // Temperature of NS - - return std::make_tuple(luminosity, radius, temperature); -} - - -/* - * Calculate the spin period of a Pulsar at birth according to selected distribution (by commandline option) - * - * - * double CalculatePulsarBirthSpinPeriod() - * - * @return Birth spin period of Pulsar in ms - */ -double NS::CalculatePulsarBirthSpinPeriod() { - - double pSpin; - - switch (OPTIONS->PulsarBirthSpinPeriodDistribution()) { // which distribution? - - case PULSAR_BIRTH_SPIN_PERIOD_DISTRIBUTION::ZERO: // ZERO - pSpin = 0.0; - break; - - case PULSAR_BIRTH_SPIN_PERIOD_DISTRIBUTION::FIXED: // FIXED constant value as used in default model in Oslowski et al 2011 https://arxiv.org/abs/0903.3538 - SHOW_WARN_STATIC(ERROR::UNSUPPORTED_PULSAR_BIRTH_SPIN_PERIOD_DISTRIBUTION, // show warning - "Using spin = 0.0", - OBJECT_TYPE::BASE_STAR, - STELLAR_TYPE::NEUTRON_STAR); - pSpin = 0.0; - break; - - case PULSAR_BIRTH_SPIN_PERIOD_DISTRIBUTION::UNIFORM: { // UNIFORM distribution between minimum and maximum value as in Oslowski et al 2011 https://arxiv.org/abs/0903.3538 (default Pmin = and Pmax = ) - // and also Kiel et al 2008 https://arxiv.org/abs/0805.0059 (default Pmin = 10 ms and Pmax 100 ms, section 3.4) - - double maximum = OPTIONS->PulsarBirthSpinPeriodDistributionMax(); - double minimum = OPTIONS->PulsarBirthSpinPeriodDistributionMin(); - - pSpin = minimum + (RAND->Random() * (maximum - minimum)); - } break; - - case PULSAR_BIRTH_SPIN_PERIOD_DISTRIBUTION::NORMAL: { // NORMAL distribution from Faucher-Giguere and Kaspi 2006 https://arxiv.org/abs/astro-ph/0512585 - - // Values hard-coded for now, can make them options if necessary - // pulsarBirthSpinPeriodDistributionFaucherGiguereKaspi2006Mean = 300.0; - // pulsarBirthSpinPeriodDistributionFaucherGiguereKaspi2006Std = 150.0; - - double mean = 300.0; - double sigma = 150.0; - - do { pSpin = RAND->RandomGaussian(sigma) + mean;} while (utils::Compare(pSpin, 0.0) < 0); - - } break; - - default: // unknown distribution - SHOW_WARN_STATIC(ERROR::UNKNOWN_PULSAR_BIRTH_SPIN_PERIOD_DISTRIBUTION, // show warning - "Using spin = 0.0", - OBJECT_TYPE::BASE_STAR, - STELLAR_TYPE::NEUTRON_STAR); - pSpin = 0.0; - } - - return pSpin; -} - - -/* - * Calculate (log10 of) the magnetic field (in G) for a Pulsar at birth - * according to selected distribution (by commandline option) - * - * - * double CalculatePulsarBirthMagneticField() - * - * @return log10 of the birth magnetic field in G - */ -double NS::CalculatePulsarBirthMagneticField() { - - double log10B; - - switch (OPTIONS->PulsarBirthMagneticFieldDistribution()) { // which distribution? - - case PULSAR_BIRTH_MAGNETIC_FIELD_DISTRIBUTION::ZERO: // ZERO - log10B = 0.0; - break; - - case PULSAR_BIRTH_MAGNETIC_FIELD_DISTRIBUTION::FIXED: // FIXED - set to a fixed constant value - SHOW_WARN_STATIC(ERROR::UNSUPPORTED_PULSAR_BIRTH_MAGNETIC_FIELD_DISTRIBUTION, // show warning - "Using 0.0", - OBJECT_TYPE::BASE_STAR, - STELLAR_TYPE::NEUTRON_STAR); - log10B = 0.0; - break; - - case PULSAR_BIRTH_MAGNETIC_FIELD_DISTRIBUTION::FLATINLOG: { // FLAT IN LOG distribution from Oslowski et al 2011 https://arxiv.org/abs/0903.3538 (log10B0min = , log10B0max = ) - - double maximum = OPTIONS->PulsarBirthMagneticFieldDistributionMax(); - double minimum = OPTIONS->PulsarBirthMagneticFieldDistributionMin(); - - log10B = minimum + (RAND->Random() * (maximum - minimum)); - - } break; - - case PULSAR_BIRTH_MAGNETIC_FIELD_DISTRIBUTION::UNIFORM: { // UNIFORM flat distribution used in Kiel et al 2008 https://arxiv.org/abs/0805.0059 (log10B0min = 11, log10B0max = 13.5 see section 3.4 and Table 1.) - - - double maximum = PPOW(10.0, OPTIONS->PulsarBirthMagneticFieldDistributionMax()); - double minimum = PPOW(10.0, OPTIONS->PulsarBirthMagneticFieldDistributionMin()); - - log10B = log10(minimum + (RAND->Random() * (maximum - minimum))); - } break; - - case PULSAR_BIRTH_MAGNETIC_FIELD_DISTRIBUTION::LOGNORMAL: { // LOG NORMAL distribution from Faucher-Giguere and Kaspi 2006 https://arxiv.org/abs/astro-ph/0512585 - - // Values hard-coded for now, can make them options if necessary - // pulsarBirthMagneticFieldDistributionFaucherGiguereKaspi2006Mean = 12.65 - // pulsarBirthMagneticFieldDistributionFaucherGiguereKaspi2006Std = 0.55 - - double mean = 12.65; - double sigma = 0.55; - - log10B = RAND->RandomGaussian(sigma) + mean; - } break; - - default: // unknown distribution - SHOW_WARN_STATIC(ERROR::UNKNOWN_PULSAR_BIRTH_MAGNETIC_FIELD_DISTRIBUTION, // show warning - "Using 0.0", - OBJECT_TYPE::BASE_STAR, - STELLAR_TYPE::NEUTRON_STAR); - log10B = 0.0; - } - - return log10B; -} - - -/* - * Calculate the moment of inertia for a Neutron Star using a model independent relation between - * the moment of inertia, mass and radius of a neutron star - return MoI in CGS. - * - * Uses m_Mass and m_Radius to calculate moment of inertia. - * - * Raithel et al. 2016, eq 8 in https://arxiv.org/abs/1603.06594 - * https://tap.arizona.edu/sites/tap.arizona.edu/files/Raithel_2015_manuscript.pdf - * - * - * double CalculateMomentOfInertiaCGS() - * - * @return Moment of inertia in g cm^2 - */ -double NS::CalculateMomentOfInertiaCGS() const { - - // pow() is slow - use multiplication - - double r_km = m_Radius * RSOL_TO_KM; - double m_r = m_Mass / r_km; - double m_r_4 = m_r * m_r * m_r * m_r; - - double r_cm = m_Radius * RSOL_TO_CM; - double r_cm_2 = r_cm * r_cm; - - return 0.237 * m_Mass * MSOL_TO_G * r_cm_2 * (1.0 + (4.2 * m_r) + 90.0 * m_r_4); -} - - -/* - * Calculate the spin down rate for isolated Neutron Stars in cgs - * - * See Equation 2 in https://arxiv.org/pdf/1912.02415.pdf - * - * This is changed to the form of calculating spindown with P and Pdot, then convert to OmegaDot and to be recorded in the output file. - * Evolution of the inclination between pulsar magnetic and rotational axes will be considered in a future version. - * - * double CalculateSpinDownRate(const double p_Omega, const double p_MomentOfInteria, const double p_MagField, const double p_Radius) - * - * @param [IN] p_Omega Pulsar spin frequency. - * @param [IN] p_MomentOfInteria Moment of Interia of the Neutron Star in kg m^2 - * @param [IN] p_MagField Magnetic field in Tesla - * @param [IN] p_Radius Radius of the Neutron Star in kilometres - * @return Spin down rate (spin frequency derivative) of an isolated Neutron Star in s^(-2) - */ -double NS::CalculateSpinDownRate(const double p_Omega, const double p_MomentOfInteria, const double p_MagField, const double p_Radius) const { - - // pow() is slow - use multiplication - - double period = _2_PI / p_Omega; // convert frequency to period - double cgsRadius = p_Radius * KM_TO_CM; // radius in cm - double radius_6 = cgsRadius * cgsRadius * cgsRadius * cgsRadius * cgsRadius * cgsRadius; - double cgsMagField = p_MagField * TESLA_TO_GAUSS; // B field in G - double magField_2 = cgsMagField * cgsMagField; - constexpr double _8_PI_2 = 8.0 * PI_2; - constexpr double _3_C_3 = 3.0E6 * C * C * C; // 3.0 * (C * 100.0) * (C * 100.0) * (C * 100.0) - double pDotTop = _8_PI_2 * radius_6 * magField_2; - double pDotBottom = _3_C_3 * p_MomentOfInteria * period; - double pDot = pDotTop / pDotBottom; // period derivative - - return(-pDot * p_Omega / period); // convert period derivative to frequency derivative, which is what is recorded in the output -} - - -/* - * Calculates and sets pulsar parameters at birth of pulsar - * - * Modifies the following class member variables: - * - * m_AngularMomentum_CGS - * m_MomentOfInertia_CGS - * m_PulsarDetails.birthPeriod - * m_PulsarDetails.birthSpinDownRate - * m_PulsarDetails.magneticField - * m_PulsarDetails.spinDownRate - * m_PulsarDetails.spinFrequency - * - * - * void CalculateAndSetPulsarParameters() - */ -void NS::CalculateAndSetPulsarParameters() { - - m_PulsarDetails.magneticField = PPOW(10.0, CalculatePulsarBirthMagneticField()) * GAUSS_TO_TESLA; // magnetic field in Gauss -> convert to Tesla - m_PulsarDetails.spinPeriod = CalculatePulsarBirthSpinPeriod(); // spin period in ms - m_PulsarDetails.spinFrequency = _2_PI / (m_PulsarDetails.spinPeriod * SECONDS_IN_MS); - m_PulsarDetails.birthPeriod = m_PulsarDetails.spinPeriod * SECONDS_IN_MS; // convert from ms to s - m_MomentOfInertia_CGS = CalculateMomentOfInertiaCGS(); // in CGS g cm^2 - - // Note we convert neutronStarMomentOfInertia from CGS to SI here - m_PulsarDetails.spinDownRate = CalculateSpinDownRate(m_PulsarDetails.spinFrequency, m_MomentOfInertia_CGS, m_PulsarDetails.magneticField, m_Radius * RSOL_TO_KM); - m_PulsarDetails.birthSpinDownRate = m_PulsarDetails.spinDownRate; - m_AngularMomentum_CGS = m_MomentOfInertia_CGS * m_PulsarDetails.spinFrequency; // in CGS g cm^2 s^-1 -} - - -/* - * Update the magnetic field and spins of neutron stars when it's deemed as an isolated pulsar. - * - * This function is called in multiple situations in the NS::UpdateMagneticFieldAndSpin() function - * - * Modifies the following class member variables: - * - * m_AngularMomentum_CGS - * m_PulsarDetails.spinFrequency - * m_PulsarDetails.magneticField - * m_PulsarDetails.spinDownRate - * - * - * void SpinDownIsolatedPulsar(const double p_Stepsize) - * - * @param [IN] p_Stepsize Timestep size for integration (in seconds) - */ -void NS::SpinDownIsolatedPulsar(const double p_Stepsize) { - - double NSradius_IN_CM = m_Radius * RSOL_TO_KM * KM_TO_CM; - double NSradius_3 = NSradius_IN_CM * NSradius_IN_CM * NSradius_IN_CM; - double NSradius_6 = NSradius_3 * NSradius_3; - constexpr double _8_PI_2 = 8.0 * PI_2; - constexpr double _3_C_3 = 3.0 * C * C * C * 1000000.0; - - double initialMagField = m_PulsarDetails.magneticField; // (in T) - double initialMagField_G = initialMagField * TESLA_TO_GAUSS; - double initialSpinPeriod = _2_PI / m_PulsarDetails.spinFrequency; - double magFieldLowerLimit = PPOW(10.0, OPTIONS->PulsarLog10MinimumMagneticField()) * GAUSS_TO_TESLA; - double magFieldLowerLimit_G = magFieldLowerLimit * TESLA_TO_GAUSS; - double tau = OPTIONS->PulsarMagneticFieldDecayTimescale() * MYR_TO_YEAR * SECONDS_IN_YEAR; - - // calculate isolated decay of the magnetic field for a neutron star - // see Equation 6 in arXiv:0903.3538v2 - m_PulsarDetails.magneticField = magFieldLowerLimit + (initialMagField - magFieldLowerLimit) * exp(-p_Stepsize / tau); // update pulsar magnetic field in SI. - - // calculate the spin frequency for isolated neutron stars - // see Equation 6 in arxiv:1912.02415 - // The rest of the calculations are carried out in cgs. - double constant2 = (_8_PI_2 * NSradius_6) / (_3_C_3 * m_MomentOfInertia_CGS); - double term1 = magFieldLowerLimit_G * magFieldLowerLimit_G * p_Stepsize; - double term2 = tau * magFieldLowerLimit_G * ( m_PulsarDetails.magneticField * TESLA_TO_GAUSS - initialMagField_G); - double term3 = (tau / 2.0) * (TESLA_TO_GAUSS * TESLA_TO_GAUSS * (m_PulsarDetails.magneticField * m_PulsarDetails.magneticField) - (initialMagField_G * initialMagField_G)); - double Psquared = constant2 * (term1 - term2 - term3) + (initialSpinPeriod * initialSpinPeriod); - - double P_f = std::sqrt(Psquared); - m_PulsarDetails.spinFrequency = _2_PI / P_f; // pulsar spin frequency - - // calculate the spin down rate for isolated neutron stars - // see Equation 4 in arXiv:0903.3538v2 (Our version is in cgs) - m_PulsarDetails.spinDownRate = CalculateSpinDownRate(m_PulsarDetails.spinFrequency,m_MomentOfInertia_CGS , m_PulsarDetails.magneticField, NSradius_IN_CM / KM_TO_CM); - m_AngularMomentum_CGS = m_PulsarDetails.spinFrequency * m_MomentOfInertia_CGS; // angular momentum of star in CGS -} - - -/* - * Calculate the magnetic field, spin, spin-down rate and angular momentum of a neutron star, - * when it's accreting mass from companion. - * The calculations used in this function follows closely Sec. 2.2.1 in arxiv:1912.02415 - * We carry out the calculations in this function using cgs units. - * This function is called in the NS::UpdateMagneticFieldAndSpin() function - * - * Returns, in a tuple, these four quantities of a neutron star: - * - * magnetic field strength - * spin frequency - * spin-down rate - * angular momentum - * - * DBL_DBL_DBL_DBL PulsarAccretion(const double p_MagField, const double p_SpinFrequency, const double p_AngularMomentum, const double p_Stepsize, const double p_MassGainPerTimeStep, const double kappa, const double p_Epsilon) - * @param [IN] p_MagField NS magnetic field strength at the beginning of accretion (in Tesla) - * @param [IN] p_SpinFrequency Spin frequency for the NS at the beginning of accretion (in Hz) - * @param [IN] p_AngularMomentum Angular momentum of the NS at the beginning of accretion (g cm-2 / s) - * @param [IN] p_Stepsize Timestep size for integration (in seconds) - * @param [IN] p_MassGainPerTimeStep Mass transferred from the secondary for each iteration (in g) - * @param [IN] p_Kappa Magnetic field mass decay scale (in g) - * @param [IN] p_Epsilon Efficiency factor allowing for uncertainties of coupling magnetic field and matter. - * @return Tuple containing the updated magnetic field strength, spin frequency, spin-down rate and angular momentum of neutron star - */ -DBL_DBL_DBL_DBL NS::PulsarAccretion(const double p_MagField, const double p_SpinFrequency, const double p_AngularMomentum, const double p_Stepsize, const double p_MassGainPerTimeStep, const double p_Kappa, const double p_Epsilon) { - double initialMagField_G = p_MagField * TESLA_TO_GAUSS; - double magFieldLowerLimit_G = PPOW(10.0, OPTIONS->PulsarLog10MinimumMagneticField()) ; - double mass_g = m_Mass * MSOL_TO_G; // in g - double r_cm = m_Radius * RSOL_TO_KM * KM_TO_CM; - double MoI = m_MomentOfInertia_CGS;// * CGS_SI; - double angularMomentum = p_AngularMomentum;// * CGS_SI; - double newPulsarMagneticField = (initialMagField_G - magFieldLowerLimit_G) * exp(-p_MassGainPerTimeStep / p_Kappa) + magFieldLowerLimit_G; - std::cout << "IB: " << initialMagField_G << " FB: " << newPulsarMagneticField << " MT: " << p_MassGainPerTimeStep << " kappa: " << p_Kappa << " BMIN: " << magFieldLowerLimit_G << " step: " << p_Stepsize <NeutronStarAccretionInCE()) { // which mode of CE accretion to use? - - case NS_ACCRETION_IN_CE::ZERO: // ZERO, no effect from CE - NSCE = 0; - break; - - case NS_ACCRETION_IN_CE::DISK: // DISK , CE effect same as a RLOF case - NSCE = 1; - break; - - case NS_ACCRETION_IN_CE::SURFACE: // SURFACE, mass are accreted onto the surface of the neutron star. - NSCE = 2; - break; - - default: // unknown distribution - SHOW_WARN_STATIC(ERROR::UNKNOWN_NS_ACCRETION_IN_CE, // show warning - "Using zero accretion", - OBJECT_TYPE::BASE_BINARY_STAR, - STELLAR_TYPE::NEUTRON_STAR); - NSCE = 0; - } - - double kappa = OPTIONS->PulsarMagneticFieldDecayMassscale() * MSOL_TO_G; - - if ((!p_RecycledNS && !p_CommonEnvelope) || (!p_RecycledNS && utils::Compare(p_MassGainPerTimeStep, 0.0) == 0 )) { - // These are the ''classical'' isolated pulsars. They experience spin-down. - SpinDownIsolatedPulsar(p_Stepsize); - } - - else if ( utils::Compare(p_MassGainPerTimeStep, 0.0) > 0) { - // Next, we update pulsar that goes through mass transfer. - // Note that we do not need to set hard lower limit on the spin period, - // as propeller effect should be included in the calculation, - // which means pulsars will start spinning down when the AM is no longer transferred along mass transfer. - if ((!p_CommonEnvelope) || (p_CommonEnvelope && (NSCE == 1))){ - // Pulsar evolution during stable mass transfer (RLOF), or if using the 'DISK' option - // in the CE accretion option - - //----to be deleted--- - // To prevent in some cases where the numerical calculations return negative spin period/frequency, - // We divide the step of mass transfer into sufficiently small ones where in each step, - // no negative spin period is seen. - //----to be deleted--- - std::tuple accretionResults = PulsarAccretion(m_PulsarDetails.magneticField, m_PulsarDetails.spinFrequency, m_AngularMomentum_CGS, p_Stepsize, p_MassGainPerTimeStep * 1000.0, kappa, p_Epsilon); - - //----to be deleted--- - // int divideTimestepBy = 100; - // bool done = false; - // while (!done && utils::Compare(divideTimestepBy, 1000000) > 0.0) { - - // divideTimestepBy *= 2; - - // double thisTimestepSize = p_Stepsize / divideTimestepBy; - // double thisMassGain = p_MassGainPerTimeStep * 1000.0 / divideTimestepBy; - // double B ; - // double f ; - // double fdot ; - // double am ; - - // std::tie(B, f, fdot, am) = PulsarAccretion(m_PulsarDetails.magneticField, m_PulsarDetails.spinFrequency, - // m_AngularMomentum_CGS, thisTimestepSize, thisMassGain, kappa, p_Epsilon); - - // int count = 0; - // while (!done) { - // std::tie(B, f, fdot, am) = PulsarAccretion(B, f, am, thisTimestepSize, thisMassGain, kappa, p_Epsilon); - - // if (utils::Compare(f, 0.0) < 0) break; - // if (++count >= divideTimestepBy) done = true; - // } - // } - - // double newTimeStepSize = p_Stepsize / divideTimestepBy; - // double newMassGain = p_MassGainPerTimeStep * 1000.0 / divideTimestepBy ; - // accretionResults = PulsarAccretion(m_PulsarDetails.magneticField, m_PulsarDetails.spinFrequency, - // m_AngularMomentum_CGS, newTimeStepSize, newMassGain, kappa, p_Epsilon); - // for (int n = 1; n<= int(divideTimestepBy); n++){ - - // double last_B_2 = std::get<0>(accretionResults); - // double last_f_2 = std::get<1>(accretionResults); - // double last_am_2 = std::get<3>(accretionResults); - - // accretionResults = PulsarAccretion(last_B_2, last_f_2, last_am_2, newTimeStepSize, newMassGain, kappa, p_Epsilon); - //----to be deleted--- - - m_PulsarDetails.magneticField = std::get<0>(accretionResults); - m_PulsarDetails.spinFrequency = std::get<1>(accretionResults); - m_AngularMomentum_CGS = std::get<2>(accretionResults); - - m_PulsarDetails.spinDownRate = CalculateSpinDownRate(m_PulsarDetails.spinFrequency,m_MomentOfInertia_CGS , m_PulsarDetails.magneticField, m_Radius * RSOL_TO_KM ); - } - - - else if ((p_CommonEnvelope) && (NSCE == 2) && utils::Compare(p_MassGainPerTimeStep, 0.0) > 0) { - // Mass transfer through CE when accretion happens at the surface of the NS - - double initialMagField = m_PulsarDetails.magneticField; - double initialMagField_G = initialMagField * TESLA_TO_GAUSS; - double magFieldLowerLimit_G = PPOW(10.0, OPTIONS->PulsarLog10MinimumMagneticField()) ; - - double mass_g = m_Mass * MSOL_TO_G; // in g - double r_cm = m_Radius * RSOL_TO_KM * KM_TO_CM; // in cm - m_MomentOfInertia_CGS = CalculateMomentOfInertiaCGS() ; - double MoI = m_MomentOfInertia_CGS; - double angularMomentum = m_AngularMomentum_CGS; - double newPulsarMagneticField = (initialMagField_G - magFieldLowerLimit_G) * exp(-p_MassGainPerTimeStep * 1000.0 / kappa) + magFieldLowerLimit_G; - double r_cm_3 = r_cm * r_cm * r_cm ; - double Jacc = MoI * PPOW(G_CGS * mass_g /r_cm_3, 0.5) * p_MassGainPerTimeStep * 1000.0 / mass_g ; - - m_AngularMomentum_CGS = angularMomentum + Jacc ; - m_PulsarDetails.magneticField = newPulsarMagneticField * GAUSS_TO_TESLA; - m_PulsarDetails.spinFrequency = m_AngularMomentum_CGS / MoI ; - - m_PulsarDetails.spinDownRate = CalculateSpinDownRate(m_PulsarDetails.spinFrequency,m_MomentOfInertia_CGS , m_PulsarDetails.magneticField, m_Radius * RSOL_TO_KM ); - } - - else if ((p_CommonEnvelope) && (NSCE == 0)) { - // If mass transfer is happening through CE and chosen 'ZERO' in CE accretion mode, - // treat the pulsar as isolated and should be spun down. - SpinDownIsolatedPulsar(p_Stepsize); - } -} -} \ No newline at end of file diff --git a/src/constants.h b/src/constants.h index 56d76f75f..54281ea62 100755 --- a/src/constants.h +++ b/src/constants.h @@ -183,7 +183,7 @@ extern OBJECT_ID globalObjectId; constexpr double FLOAT_TOLERANCE_ABSOLUTE = 0.0000005; // absolute tolerance for floating-point comparisons if COMPARE_GLOBAL_TOLERANCE is defined constexpr double FLOAT_TOLERANCE_RELATIVE = 0.0000005; // relative tolerance for floating-point comparisons if COMPARE_GLOBAL_TOLERANCE is defined -constexpr double ROOT_ABS_TOLERANCE = 1.0E-9; // absolute tolerance for root finder +constexpr double ROOT_ABS_TOLERANCE = 1.0E-10; // absolute tolerance for root finder constexpr double ROOT_REL_TOLERANCE = 1.0E-6; // relative tolerance for root finder From 744d90d5e6e70b50af180c2ff1958b9e17b7d0a8 Mon Sep 17 00:00:00 2001 From: Yuzhe Song Date: Fri, 19 Apr 2024 17:20:02 +1000 Subject: [PATCH 09/19] Revert "Revert "Updates to NS.cpp per reviews and BaseB^CaryStar.cpp for issue #1076"" This reverts commit c820189260f5bb51f232c0e1839008f24c9b787e. --- src/BaseBinaryStar.cpp | 19 +- src/NS.cpp | 84 +++--- src/NSapr9.cpp | 627 +++++++++++++++++++++++++++++++++++++++++ src/constants.h | 2 +- 4 files changed, 685 insertions(+), 47 deletions(-) create mode 100755 src/NSapr9.cpp diff --git a/src/BaseBinaryStar.cpp b/src/BaseBinaryStar.cpp index 01e93a127..731cbb925 100644 --- a/src/BaseBinaryStar.cpp +++ b/src/BaseBinaryStar.cpp @@ -387,11 +387,11 @@ void BaseBinaryStar::SetRemainingValues() { m_Flags.mergesInHubbleTime = false; m_Unbound = false; - m_SystemicVelocity = Vector3d(); - m_NormalizedOrbitalAngularMomentumVector = Vector3d(); - m_ThetaE = DEFAULT_INITIAL_DOUBLE_VALUE; - m_PhiE = DEFAULT_INITIAL_DOUBLE_VALUE; - m_PsiE = DEFAULT_INITIAL_DOUBLE_VALUE; + m_SystemicVelocity = Vector3d(); + m_NormalizedOrbitalAngularMomentumVector = Vector3d(); + m_ThetaE = DEFAULT_INITIAL_DOUBLE_VALUE; + m_PhiE = DEFAULT_INITIAL_DOUBLE_VALUE; + m_PsiE = DEFAULT_INITIAL_DOUBLE_VALUE; m_SynchronizationTimescale = DEFAULT_INITIAL_DOUBLE_VALUE; m_CircularizationTimescale = DEFAULT_INITIAL_DOUBLE_VALUE; @@ -1273,7 +1273,7 @@ bool BaseBinaryStar::ResolveSupernova() { Vector3d relativeVelocityVector = relativeVelocityVectorPrev + (natalKickVector - companionRecoilVector); // km/s - PostSN relative velocity vector Vector3d orbitalAngularMomentumVector = cross(separationVectorPrev, relativeVelocityVector); // km^2 s^-1 - PostSN specific orbital angular momentum vector - double orbitalAngularMomentum = orbitalAngularMomentumVector.mag; // km^2 s^-1 - PostSN specific orbital angular momentum + double orbitalAngularMomentum = orbitalAngularMomentumVector.mag; // km^2 s^-1 - PostSN specific orbital angular momentum m_NormalizedOrbitalAngularMomentumVector = orbitalAngularMomentumVector/orbitalAngularMomentum; // set unit vector here to make printing out the inclination vector easier Vector3d eccentricityVector = cross(relativeVelocityVector, orbitalAngularMomentumVector) / @@ -1988,6 +1988,13 @@ void BaseBinaryStar::CalculateMassTransfer(const double p_Dt) { } } + // If the current timestep is smaller than the donor's thermal timescale, only a fraction of the mass + // that needs to be donated has time to be donated + double donorThermalTimescale = m_Donor->CalculateThermalTimescale(); + if (p_Dt < donorThermalTimescale) { + massDiffDonor = (p_Dt / donorThermalTimescale) * massDiffDonor; + } + if (!m_CEDetails.CEEnow) { // CE flagged? // no double massGainAccretor = -massDiffDonor * m_FractionAccreted; // set accretor mass gain to mass loss * conservativeness diff --git a/src/NS.cpp b/src/NS.cpp index 320d2c095..2e596da37 100755 --- a/src/NS.cpp +++ b/src/NS.cpp @@ -395,7 +395,7 @@ void NS::SpinDownIsolatedPulsar(const double p_Stepsize) { // see Equation 6 in arXiv:0903.3538v2 m_PulsarDetails.magneticField = magFieldLowerLimit + (initialMagField - magFieldLowerLimit) * exp(-p_Stepsize / tau); // update pulsar magnetic field in SI. - // calculate the spin down rate for isolated neutron stars + // calculate the spin frequency for isolated neutron stars // see Equation 6 in arxiv:1912.02415 // The rest of the calculations are carried out in cgs. double constant2 = (_8_PI_2 * NSradius_6) / (_3_C_3 * m_MomentOfInertia_CGS); @@ -416,9 +416,10 @@ void NS::SpinDownIsolatedPulsar(const double p_Stepsize) { /* * Calculate the magnetic field, spin, spin-down rate and angular momentum of a neutron star, - * when it's accreting mass from companion. + * when it's accreting mass from companion through stable mass transfer. + * The calculations used in this function follows closely Sec. 2.2.1 in arxiv:1912.02415 * We carry out the calculations in this function using cgs units. - * This function is called in multiple situations in the NS::UpdateMagneticFieldAndSpin() function + * This function is called in the NS::UpdateMagneticFieldAndSpin() function * * Returns, in a tuple, these four quantities of a neutron star: * @@ -432,39 +433,44 @@ void NS::SpinDownIsolatedPulsar(const double p_Stepsize) { * @param [IN] p_SpinFrequency Spin frequency for the NS at the beginning of accretion (in Hz) * @param [IN] p_AngularMomentum Angular momentum of the NS at the beginning of accretion (g cm-2 / s) * @param [IN] p_Stepsize Timestep size for integration (in seconds) - * @param [IN] p_MassGainPerTimeStep Mass loss from the secondary for each iteration (in g) - * @param [IN] p_Kappa Mass loss from the secondary for each iteration (in g) - * @param [IN] p_Epsilon Uncertainty due to mass loss - * @return Tuple containing the updated magnetic field strength, spin frequency, angular momentum of neutron star + * @param [IN] p_MassGainPerTimeStep Mass transferred from the secondary for each iteration (in g) + * @param [IN] p_Kappa Magnetic field mass decay scale (in g) + * @param [IN] p_Epsilon Efficiency factor allowing for uncertainties of coupling magnetic field and matter. + * @return Tuple containing the updated magnetic field strength, spin frequency, spin-down rate and angular momentum of neutron star */ DBL_DBL_DBL_DBL NS::PulsarAccretion(const double p_MagField, const double p_SpinFrequency, const double p_AngularMomentum, const double p_Stepsize, const double p_MassGainPerTimeStep, const double p_Kappa, const double p_Epsilon) { double initialMagField_G = p_MagField * TESLA_TO_GAUSS; - double magFieldLowerLimit_G = PPOW(10.0, OPTIONS->PulsarLog10MinimumMagneticField()) ; + double magFieldLowerLimit_G = PPOW(10.0, OPTIONS->PulsarLog10MinimumMagneticField()) ; // convert to Gauss double mass_g = m_Mass * MSOL_TO_G; // in g - double r_cm = m_Radius * RSOL_TO_KM * KM_TO_CM; - double MoI = m_MomentOfInertia_CGS;// * CGS_SI; - double angularMomentum = p_AngularMomentum;// * CGS_SI; + double r_cm = m_Radius * RSOL_TO_KM * KM_TO_CM; // NS radius in cm + double MoI = m_MomentOfInertia_CGS; + double angularMomentum = p_AngularMomentum; + + //Magnetic field decay due to mass transfer. + //Follows Eq. 12 in arxiv:1912.02415 double newPulsarMagneticField = (initialMagField_G - magFieldLowerLimit_G) * exp(-p_MassGainPerTimeStep / p_Kappa) + magFieldLowerLimit_G; // calculate the Alfven radius for an accreting neutron star - // see Equation 8 in arXiv:0903.3538v2 + // see Equation 10 in arxiv:1912.02415 double mDot = p_MassGainPerTimeStep / p_Stepsize; double R_CM_6 = r_cm * r_cm * r_cm * r_cm * r_cm * r_cm; - double B_4 = newPulsarMagneticField * newPulsarMagneticField * newPulsarMagneticField * newPulsarMagneticField; - double R_a_top = 8.0 * R_CM_6 * R_CM_6 * B_4; - double R_a_bot = mass_g * mDot * mDot * (G * 1000.0); - double alfvenRadius = PPOW(R_a_top / R_a_bot, 1.0 / 7.0) / 2.0; + double p = R_CM_6 * R_CM_6 / (mass_g * mass_g * mDot); + double q = PPOW(p, 1.0/7.0); + double alfvenConst = PPOW(2 * PI_2 / G_CGS, 1.0/7.0) ; + double alfvenRadius = alfvenConst * q * PPOW(initialMagField_G, 4.0/7.0); - // calculate the difference in the keplerian angular velocity and surface angular velocity of the neutron star in m - // see Equation 2 in 1994MNRAS.269..455J - double keplerianVelocityAtAlfvenRadius = std::sqrt(2.0 * (G * 1000.0) * mass_g / alfvenRadius); - double keplerianAngularVelocityAtAlfvenRadius = 4.0 * M_PI * keplerianVelocityAtAlfvenRadius / alfvenRadius; - double velocityDifference = keplerianAngularVelocityAtAlfvenRadius - p_SpinFrequency; + // calculate the difference in the keplerian angular velocity at the magnetic radius + // and surface angular velocity of the neutron star + // magnetic radius is half of alfven radius + // see Equation 2 in 1994MNRAS.269..455J / Equation 8 in arxiv:1912.02415 + double keplerianVelocityAtMagneticRadius = std::sqrt(4.0 * (G_CGS) * mass_g / alfvenRadius); + double keplerianAngularVelocityAtMagneticRadius = 2.0 * keplerianVelocityAtMagneticRadius / alfvenRadius; + double omegaDifference = keplerianAngularVelocityAtMagneticRadius - p_SpinFrequency; // calculate the change in angular momentum due to accretion // see Equation 12 in arXiv:0805.0059/ Equation 8 in arxiv:1912.02415 - double Jdot = p_Epsilon * velocityDifference * alfvenRadius * alfvenRadius * mDot; - angularMomentum = angularMomentum + Jdot * p_Stepsize; + double Jdot = p_Epsilon * omegaDifference * alfvenRadius * alfvenRadius * mDot / 4.0; + angularMomentum = angularMomentum + Jdot * p_Stepsize; return std::make_tuple(newPulsarMagneticField * GAUSS_TO_TESLA, angularMomentum / MoI, Jdot / MoI, angularMomentum); } @@ -488,8 +494,8 @@ DBL_DBL_DBL_DBL NS::PulsarAccretion(const double p_MagField, const double p_Spin * @param [IN] p_CommonEnvelope Indicates whether there there is a common envelope - true or false * @param [IN] p_RecycledNS Indicates whether this star is/was a recycled neutron star - true or false * @param [IN] p_Stepsize Timestep size for integration (in seconds) - * @param [IN] p_MassGainPerTimeStep Mass loss from the secondary for each iteration (in kg) - * @param [IN] p_Epsilon Uncertainty due to mass loss + * @param [IN] p_MassGainPerTimeStep Mass trasnferred from the secondary for each iteration (in kg) + * @param [IN] p_Epsilon Efficiency factor allowing for uncertainties of coupling magnetic field and matter. */ void NS::UpdateMagneticFieldAndSpin(const bool p_CommonEnvelope, const bool p_RecycledNS, const double p_Stepsize, const double p_MassGainPerTimeStep, const double p_Epsilon) { @@ -519,9 +525,9 @@ void NS::UpdateMagneticFieldAndSpin(const bool p_CommonEnvelope, const bool p_Re double kappa = OPTIONS->PulsarMagneticFieldDecayMassscale() * MSOL_TO_G; - if ((!p_RecycledNS && !p_CommonEnvelope) || (!p_RecycledNS && utils::Compare(p_MassGainPerTimeStep, 0.0) == 0 )) { + if ((!p_RecycledNS && !p_CommonEnvelope) || (p_RecycledNS && utils::Compare(p_MassGainPerTimeStep, 0.0) == 0 )) { // These are the ''classical'' isolated pulsars. They experience spin-down. - SpinDownIsolatedPulsar(p_Stepsize); + SpinDownIsolatedPulsar(p_Stepsize); } else if ( utils::Compare(p_MassGainPerTimeStep, 0.0) > 0) { @@ -532,11 +538,8 @@ void NS::UpdateMagneticFieldAndSpin(const bool p_CommonEnvelope, const bool p_Re if ((!p_CommonEnvelope) || (p_CommonEnvelope && (NSCE == 1))){ // Pulsar evolution during stable mass transfer (RLOF), or if using the 'DISK' option // in the CE accretion option - // To prevent in some cases where the numerical calculations return negative spin period/frequency, - // We divide the step of mass transfer into sufficiently small ones where in each step, - // no negative spin period is seen. - std::tuple accretionResults; - + std::tuple accretionResults = PulsarAccretion(m_PulsarDetails.magneticField, m_PulsarDetails.spinFrequency, m_AngularMomentum_CGS, p_Stepsize, p_MassGainPerTimeStep * 1000.0, kappa, p_Epsilon); + int divideTimestepBy = 100; bool done = false; while (!done && utils::Compare(divideTimestepBy, 1000000) > 0.0) { @@ -573,18 +576,19 @@ void NS::UpdateMagneticFieldAndSpin(const bool p_CommonEnvelope, const bool p_Re double last_am_2 = std::get<3>(accretionResults); accretionResults = PulsarAccretion(last_B_2, last_f_2, last_am_2, newTimeStepSize, newMassGain, kappa, p_Epsilon); - } m_PulsarDetails.magneticField = std::get<0>(accretionResults); m_PulsarDetails.spinFrequency = std::get<1>(accretionResults); - m_AngularMomentum_CGS = std::get<2>(accretionResults); + m_AngularMomentum_CGS = std::get<3>(accretionResults); + + m_PulsarDetails.spinDownRate = CalculateSpinDownRate(m_PulsarDetails.spinFrequency,m_MomentOfInertia_CGS , m_PulsarDetails.magneticField, m_Radius * RSOL_TO_KM ); - m_PulsarDetails.spinDownRate = CalculateSpinDownRate(m_PulsarDetails.spinFrequency,m_MomentOfInertia_CGS , m_PulsarDetails.magneticField, m_Radius * RSOL_TO_KM ); + } } - else if ((p_CommonEnvelope) && (NSCE == 2) && utils::Compare(p_MassGainPerTimeStep, 0.0) > 0) { + + else if ((p_CommonEnvelope) && (NSCE == 2)) { // Mass transfer through CE when accretion happens at the surface of the NS - double initialMagField = m_PulsarDetails.magneticField; double initialMagField_G = initialMagField * TESLA_TO_GAUSS; double magFieldLowerLimit_G = PPOW(10.0, OPTIONS->PulsarLog10MinimumMagneticField()) ; @@ -594,9 +598,9 @@ void NS::UpdateMagneticFieldAndSpin(const bool p_CommonEnvelope, const bool p_Re m_MomentOfInertia_CGS = CalculateMomentOfInertiaCGS() ; double MoI = m_MomentOfInertia_CGS; double angularMomentum = m_AngularMomentum_CGS; - double newPulsarMagneticField = (initialMagField_G - magFieldLowerLimit_G) * exp(-p_MassGainPerTimeStep / kappa) + magFieldLowerLimit_G; + double newPulsarMagneticField = (initialMagField_G - magFieldLowerLimit_G) * exp(-p_MassGainPerTimeStep * 1000.0 / kappa) + magFieldLowerLimit_G; double r_cm_3 = r_cm * r_cm * r_cm ; - double Jacc = MoI * PPOW(G * 1000.0 * mass_g /r_cm_3, 0.5) * p_MassGainPerTimeStep / mass_g ; + double Jacc = MoI * PPOW(G_CGS * mass_g /r_cm_3, 0.5) * p_MassGainPerTimeStep * 1000.0 / mass_g ; m_AngularMomentum_CGS = angularMomentum + Jacc ; m_PulsarDetails.magneticField = newPulsarMagneticField * GAUSS_TO_TESLA; @@ -609,6 +613,6 @@ void NS::UpdateMagneticFieldAndSpin(const bool p_CommonEnvelope, const bool p_Re // If mass transfer is happening through CE and chosen 'ZERO' in CE accretion mode, // treat the pulsar as isolated and should be spun down. SpinDownIsolatedPulsar(p_Stepsize); - } } } +} \ No newline at end of file diff --git a/src/NSapr9.cpp b/src/NSapr9.cpp new file mode 100755 index 000000000..43819b33b --- /dev/null +++ b/src/NSapr9.cpp @@ -0,0 +1,627 @@ +#include "Rand.h" +#include "NS.h" + + +/* + * Calculate the luminosity of a Neutron Star + * + * Hurley et al. 2000, eq 93 + * + * Called (indirectly) from GiantBranch, so must be static. + * + * + * double CalculateLuminosityOnPhase_Static(const double p_Mass, const double p_Time) + * + * @param [IN] p_Mass Mass in Msol + * @param [IN] p_Time Time since formation of the object in Myr + * @return Luminosity of the Neutron Star in Lsol + */ +double NS::CalculateLuminosityOnPhase_Static(const double p_Mass, const double p_Time) { + double t = std::max(p_Time, 0.1); + return 0.02 * PPOW(p_Mass, 2.0 / 3.0) / (t * t); +} + + +/* + * Choose timestep for Pulsar Evolution + * + * Pulsars evolve very fast when they are first born, and evolve slower as they age. + * Hence, timestep is chosen to be small when pulsar is young, and is slowly increased + * as the pulsar ages. + * + * double ChooseTimestep(const double p_Time) + * + * @param [IN] p_Time Current age of star in Myr + * @return Suggested timestep (dt) + */ +double NS::ChooseTimestep(const double p_Time) const { + double result = 500.0; // default value + + if (p_Time < 0.01) { + result = 0.001; + } + else if (p_Time < 0.1) { + result = 0.01; + } + else if (p_Time < 1.0) { + result = 0.1; + } + else if (p_Time < 10.0) { + result = 1.0; + } + else if (p_Time < 500.0) { + double slope = 1.58859191006; // 1.58859191006 = log10(500.0) / (log10(500.0) - 1.0) + double log10_step = slope * (log10(p_Time) - 1.0); + result = PPOW(10.0, log10_step); + } + + return result; +} + + +/* + * Calculate Neutron Star radius according to selected equation of state (by commandline option) + * + * + * double CalculateRadiusOnPhaseInKM_Static(const double p_Mass) + * + * @param [IN] p_Mass Mass in Msol + * @return Radius of Neutron Star in km + */ +double NS::CalculateRadiusOnPhaseInKM_Static(const double p_Mass) { + + double radius; + + switch (OPTIONS->NeutronStarEquationOfState()) { // which equation-of-state? + + case NS_EOS::SSE: // SSE + radius = 10.0; + break; + + case NS_EOS::ARP3: { // ARP3 + + // We don't extrapolate so masses outside table just set to extreme values + + std::map::const_iterator iter; + + iter = ARP3MassRadiusRelation.begin(); + double ARP3MinimumMass = iter->first; + double ARP3RadiusAtMinimumMass = iter->second; + + iter = ARP3MassRadiusRelation.end(); + double ARP3MaximumMass = (--iter)->first; + double ARP3RadiusAtMaximumMass = iter->second; + + if (utils::Compare(p_Mass, ARP3MinimumMass) < 0) { + radius = ARP3RadiusAtMinimumMass; + } + else if (utils::Compare(p_Mass, ARP3MaximumMass) > 0) { + radius = ARP3RadiusAtMaximumMass; + } + else{ + radius = utils::SampleFromTabulatedCDF(p_Mass, ARP3MassRadiusRelation); + } + } break; + + default: // unknown equation-of-state + SHOW_WARN_STATIC(ERROR::UNKNOWN_NS_EOS, // show warning + "Using default NS radius = 10.0", + OBJECT_TYPE::BASE_STAR, + STELLAR_TYPE::NEUTRON_STAR); + radius = 10.0; + } + + return radius; +} + + +/* + * Calculate core collapse Supernova parameters + * + * Called from GiantBranch, so must be static. + * + * + * DBL_DBL_DBL CalculateCoreCollapseSNParams_Static(const double p_Mass) + * + * @param [IN] p_Mass Mass in Msol + * @return Tuple containing Luminosity, Radius and Temperature of Neutron Star + */ +DBL_DBL_DBL NS::CalculateCoreCollapseSNParams_Static(const double p_Mass) { + double luminosity = CalculateLuminosityOnPhase_Static(p_Mass, 0.0); // Luminosity of Neutron Star as it cools + double radius = CalculateRadiusOnPhase_Static(p_Mass); // Radius of Neutron Star in Rsol + double temperature = BaseStar::CalculateTemperatureOnPhase_Static(luminosity, radius); // Temperature of NS + + return std::make_tuple(luminosity, radius, temperature); +} + + +/* + * Calculate the spin period of a Pulsar at birth according to selected distribution (by commandline option) + * + * + * double CalculatePulsarBirthSpinPeriod() + * + * @return Birth spin period of Pulsar in ms + */ +double NS::CalculatePulsarBirthSpinPeriod() { + + double pSpin; + + switch (OPTIONS->PulsarBirthSpinPeriodDistribution()) { // which distribution? + + case PULSAR_BIRTH_SPIN_PERIOD_DISTRIBUTION::ZERO: // ZERO + pSpin = 0.0; + break; + + case PULSAR_BIRTH_SPIN_PERIOD_DISTRIBUTION::FIXED: // FIXED constant value as used in default model in Oslowski et al 2011 https://arxiv.org/abs/0903.3538 + SHOW_WARN_STATIC(ERROR::UNSUPPORTED_PULSAR_BIRTH_SPIN_PERIOD_DISTRIBUTION, // show warning + "Using spin = 0.0", + OBJECT_TYPE::BASE_STAR, + STELLAR_TYPE::NEUTRON_STAR); + pSpin = 0.0; + break; + + case PULSAR_BIRTH_SPIN_PERIOD_DISTRIBUTION::UNIFORM: { // UNIFORM distribution between minimum and maximum value as in Oslowski et al 2011 https://arxiv.org/abs/0903.3538 (default Pmin = and Pmax = ) + // and also Kiel et al 2008 https://arxiv.org/abs/0805.0059 (default Pmin = 10 ms and Pmax 100 ms, section 3.4) + + double maximum = OPTIONS->PulsarBirthSpinPeriodDistributionMax(); + double minimum = OPTIONS->PulsarBirthSpinPeriodDistributionMin(); + + pSpin = minimum + (RAND->Random() * (maximum - minimum)); + } break; + + case PULSAR_BIRTH_SPIN_PERIOD_DISTRIBUTION::NORMAL: { // NORMAL distribution from Faucher-Giguere and Kaspi 2006 https://arxiv.org/abs/astro-ph/0512585 + + // Values hard-coded for now, can make them options if necessary + // pulsarBirthSpinPeriodDistributionFaucherGiguereKaspi2006Mean = 300.0; + // pulsarBirthSpinPeriodDistributionFaucherGiguereKaspi2006Std = 150.0; + + double mean = 300.0; + double sigma = 150.0; + + do { pSpin = RAND->RandomGaussian(sigma) + mean;} while (utils::Compare(pSpin, 0.0) < 0); + + } break; + + default: // unknown distribution + SHOW_WARN_STATIC(ERROR::UNKNOWN_PULSAR_BIRTH_SPIN_PERIOD_DISTRIBUTION, // show warning + "Using spin = 0.0", + OBJECT_TYPE::BASE_STAR, + STELLAR_TYPE::NEUTRON_STAR); + pSpin = 0.0; + } + + return pSpin; +} + + +/* + * Calculate (log10 of) the magnetic field (in G) for a Pulsar at birth + * according to selected distribution (by commandline option) + * + * + * double CalculatePulsarBirthMagneticField() + * + * @return log10 of the birth magnetic field in G + */ +double NS::CalculatePulsarBirthMagneticField() { + + double log10B; + + switch (OPTIONS->PulsarBirthMagneticFieldDistribution()) { // which distribution? + + case PULSAR_BIRTH_MAGNETIC_FIELD_DISTRIBUTION::ZERO: // ZERO + log10B = 0.0; + break; + + case PULSAR_BIRTH_MAGNETIC_FIELD_DISTRIBUTION::FIXED: // FIXED - set to a fixed constant value + SHOW_WARN_STATIC(ERROR::UNSUPPORTED_PULSAR_BIRTH_MAGNETIC_FIELD_DISTRIBUTION, // show warning + "Using 0.0", + OBJECT_TYPE::BASE_STAR, + STELLAR_TYPE::NEUTRON_STAR); + log10B = 0.0; + break; + + case PULSAR_BIRTH_MAGNETIC_FIELD_DISTRIBUTION::FLATINLOG: { // FLAT IN LOG distribution from Oslowski et al 2011 https://arxiv.org/abs/0903.3538 (log10B0min = , log10B0max = ) + + double maximum = OPTIONS->PulsarBirthMagneticFieldDistributionMax(); + double minimum = OPTIONS->PulsarBirthMagneticFieldDistributionMin(); + + log10B = minimum + (RAND->Random() * (maximum - minimum)); + + } break; + + case PULSAR_BIRTH_MAGNETIC_FIELD_DISTRIBUTION::UNIFORM: { // UNIFORM flat distribution used in Kiel et al 2008 https://arxiv.org/abs/0805.0059 (log10B0min = 11, log10B0max = 13.5 see section 3.4 and Table 1.) + + + double maximum = PPOW(10.0, OPTIONS->PulsarBirthMagneticFieldDistributionMax()); + double minimum = PPOW(10.0, OPTIONS->PulsarBirthMagneticFieldDistributionMin()); + + log10B = log10(minimum + (RAND->Random() * (maximum - minimum))); + } break; + + case PULSAR_BIRTH_MAGNETIC_FIELD_DISTRIBUTION::LOGNORMAL: { // LOG NORMAL distribution from Faucher-Giguere and Kaspi 2006 https://arxiv.org/abs/astro-ph/0512585 + + // Values hard-coded for now, can make them options if necessary + // pulsarBirthMagneticFieldDistributionFaucherGiguereKaspi2006Mean = 12.65 + // pulsarBirthMagneticFieldDistributionFaucherGiguereKaspi2006Std = 0.55 + + double mean = 12.65; + double sigma = 0.55; + + log10B = RAND->RandomGaussian(sigma) + mean; + } break; + + default: // unknown distribution + SHOW_WARN_STATIC(ERROR::UNKNOWN_PULSAR_BIRTH_MAGNETIC_FIELD_DISTRIBUTION, // show warning + "Using 0.0", + OBJECT_TYPE::BASE_STAR, + STELLAR_TYPE::NEUTRON_STAR); + log10B = 0.0; + } + + return log10B; +} + + +/* + * Calculate the moment of inertia for a Neutron Star using a model independent relation between + * the moment of inertia, mass and radius of a neutron star - return MoI in CGS. + * + * Uses m_Mass and m_Radius to calculate moment of inertia. + * + * Raithel et al. 2016, eq 8 in https://arxiv.org/abs/1603.06594 + * https://tap.arizona.edu/sites/tap.arizona.edu/files/Raithel_2015_manuscript.pdf + * + * + * double CalculateMomentOfInertiaCGS() + * + * @return Moment of inertia in g cm^2 + */ +double NS::CalculateMomentOfInertiaCGS() const { + + // pow() is slow - use multiplication + + double r_km = m_Radius * RSOL_TO_KM; + double m_r = m_Mass / r_km; + double m_r_4 = m_r * m_r * m_r * m_r; + + double r_cm = m_Radius * RSOL_TO_CM; + double r_cm_2 = r_cm * r_cm; + + return 0.237 * m_Mass * MSOL_TO_G * r_cm_2 * (1.0 + (4.2 * m_r) + 90.0 * m_r_4); +} + + +/* + * Calculate the spin down rate for isolated Neutron Stars in cgs + * + * See Equation 2 in https://arxiv.org/pdf/1912.02415.pdf + * + * This is changed to the form of calculating spindown with P and Pdot, then convert to OmegaDot and to be recorded in the output file. + * Evolution of the inclination between pulsar magnetic and rotational axes will be considered in a future version. + * + * double CalculateSpinDownRate(const double p_Omega, const double p_MomentOfInteria, const double p_MagField, const double p_Radius) + * + * @param [IN] p_Omega Pulsar spin frequency. + * @param [IN] p_MomentOfInteria Moment of Interia of the Neutron Star in kg m^2 + * @param [IN] p_MagField Magnetic field in Tesla + * @param [IN] p_Radius Radius of the Neutron Star in kilometres + * @return Spin down rate (spin frequency derivative) of an isolated Neutron Star in s^(-2) + */ +double NS::CalculateSpinDownRate(const double p_Omega, const double p_MomentOfInteria, const double p_MagField, const double p_Radius) const { + + // pow() is slow - use multiplication + + double period = _2_PI / p_Omega; // convert frequency to period + double cgsRadius = p_Radius * KM_TO_CM; // radius in cm + double radius_6 = cgsRadius * cgsRadius * cgsRadius * cgsRadius * cgsRadius * cgsRadius; + double cgsMagField = p_MagField * TESLA_TO_GAUSS; // B field in G + double magField_2 = cgsMagField * cgsMagField; + constexpr double _8_PI_2 = 8.0 * PI_2; + constexpr double _3_C_3 = 3.0E6 * C * C * C; // 3.0 * (C * 100.0) * (C * 100.0) * (C * 100.0) + double pDotTop = _8_PI_2 * radius_6 * magField_2; + double pDotBottom = _3_C_3 * p_MomentOfInteria * period; + double pDot = pDotTop / pDotBottom; // period derivative + + return(-pDot * p_Omega / period); // convert period derivative to frequency derivative, which is what is recorded in the output +} + + +/* + * Calculates and sets pulsar parameters at birth of pulsar + * + * Modifies the following class member variables: + * + * m_AngularMomentum_CGS + * m_MomentOfInertia_CGS + * m_PulsarDetails.birthPeriod + * m_PulsarDetails.birthSpinDownRate + * m_PulsarDetails.magneticField + * m_PulsarDetails.spinDownRate + * m_PulsarDetails.spinFrequency + * + * + * void CalculateAndSetPulsarParameters() + */ +void NS::CalculateAndSetPulsarParameters() { + + m_PulsarDetails.magneticField = PPOW(10.0, CalculatePulsarBirthMagneticField()) * GAUSS_TO_TESLA; // magnetic field in Gauss -> convert to Tesla + m_PulsarDetails.spinPeriod = CalculatePulsarBirthSpinPeriod(); // spin period in ms + m_PulsarDetails.spinFrequency = _2_PI / (m_PulsarDetails.spinPeriod * SECONDS_IN_MS); + m_PulsarDetails.birthPeriod = m_PulsarDetails.spinPeriod * SECONDS_IN_MS; // convert from ms to s + m_MomentOfInertia_CGS = CalculateMomentOfInertiaCGS(); // in CGS g cm^2 + + // Note we convert neutronStarMomentOfInertia from CGS to SI here + m_PulsarDetails.spinDownRate = CalculateSpinDownRate(m_PulsarDetails.spinFrequency, m_MomentOfInertia_CGS, m_PulsarDetails.magneticField, m_Radius * RSOL_TO_KM); + m_PulsarDetails.birthSpinDownRate = m_PulsarDetails.spinDownRate; + m_AngularMomentum_CGS = m_MomentOfInertia_CGS * m_PulsarDetails.spinFrequency; // in CGS g cm^2 s^-1 +} + + +/* + * Update the magnetic field and spins of neutron stars when it's deemed as an isolated pulsar. + * + * This function is called in multiple situations in the NS::UpdateMagneticFieldAndSpin() function + * + * Modifies the following class member variables: + * + * m_AngularMomentum_CGS + * m_PulsarDetails.spinFrequency + * m_PulsarDetails.magneticField + * m_PulsarDetails.spinDownRate + * + * + * void SpinDownIsolatedPulsar(const double p_Stepsize) + * + * @param [IN] p_Stepsize Timestep size for integration (in seconds) + */ +void NS::SpinDownIsolatedPulsar(const double p_Stepsize) { + + double NSradius_IN_CM = m_Radius * RSOL_TO_KM * KM_TO_CM; + double NSradius_3 = NSradius_IN_CM * NSradius_IN_CM * NSradius_IN_CM; + double NSradius_6 = NSradius_3 * NSradius_3; + constexpr double _8_PI_2 = 8.0 * PI_2; + constexpr double _3_C_3 = 3.0 * C * C * C * 1000000.0; + + double initialMagField = m_PulsarDetails.magneticField; // (in T) + double initialMagField_G = initialMagField * TESLA_TO_GAUSS; + double initialSpinPeriod = _2_PI / m_PulsarDetails.spinFrequency; + double magFieldLowerLimit = PPOW(10.0, OPTIONS->PulsarLog10MinimumMagneticField()) * GAUSS_TO_TESLA; + double magFieldLowerLimit_G = magFieldLowerLimit * TESLA_TO_GAUSS; + double tau = OPTIONS->PulsarMagneticFieldDecayTimescale() * MYR_TO_YEAR * SECONDS_IN_YEAR; + + // calculate isolated decay of the magnetic field for a neutron star + // see Equation 6 in arXiv:0903.3538v2 + m_PulsarDetails.magneticField = magFieldLowerLimit + (initialMagField - magFieldLowerLimit) * exp(-p_Stepsize / tau); // update pulsar magnetic field in SI. + + // calculate the spin frequency for isolated neutron stars + // see Equation 6 in arxiv:1912.02415 + // The rest of the calculations are carried out in cgs. + double constant2 = (_8_PI_2 * NSradius_6) / (_3_C_3 * m_MomentOfInertia_CGS); + double term1 = magFieldLowerLimit_G * magFieldLowerLimit_G * p_Stepsize; + double term2 = tau * magFieldLowerLimit_G * ( m_PulsarDetails.magneticField * TESLA_TO_GAUSS - initialMagField_G); + double term3 = (tau / 2.0) * (TESLA_TO_GAUSS * TESLA_TO_GAUSS * (m_PulsarDetails.magneticField * m_PulsarDetails.magneticField) - (initialMagField_G * initialMagField_G)); + double Psquared = constant2 * (term1 - term2 - term3) + (initialSpinPeriod * initialSpinPeriod); + + double P_f = std::sqrt(Psquared); + m_PulsarDetails.spinFrequency = _2_PI / P_f; // pulsar spin frequency + + // calculate the spin down rate for isolated neutron stars + // see Equation 4 in arXiv:0903.3538v2 (Our version is in cgs) + m_PulsarDetails.spinDownRate = CalculateSpinDownRate(m_PulsarDetails.spinFrequency,m_MomentOfInertia_CGS , m_PulsarDetails.magneticField, NSradius_IN_CM / KM_TO_CM); + m_AngularMomentum_CGS = m_PulsarDetails.spinFrequency * m_MomentOfInertia_CGS; // angular momentum of star in CGS +} + + +/* + * Calculate the magnetic field, spin, spin-down rate and angular momentum of a neutron star, + * when it's accreting mass from companion. + * The calculations used in this function follows closely Sec. 2.2.1 in arxiv:1912.02415 + * We carry out the calculations in this function using cgs units. + * This function is called in the NS::UpdateMagneticFieldAndSpin() function + * + * Returns, in a tuple, these four quantities of a neutron star: + * + * magnetic field strength + * spin frequency + * spin-down rate + * angular momentum + * + * DBL_DBL_DBL_DBL PulsarAccretion(const double p_MagField, const double p_SpinFrequency, const double p_AngularMomentum, const double p_Stepsize, const double p_MassGainPerTimeStep, const double kappa, const double p_Epsilon) + * @param [IN] p_MagField NS magnetic field strength at the beginning of accretion (in Tesla) + * @param [IN] p_SpinFrequency Spin frequency for the NS at the beginning of accretion (in Hz) + * @param [IN] p_AngularMomentum Angular momentum of the NS at the beginning of accretion (g cm-2 / s) + * @param [IN] p_Stepsize Timestep size for integration (in seconds) + * @param [IN] p_MassGainPerTimeStep Mass transferred from the secondary for each iteration (in g) + * @param [IN] p_Kappa Magnetic field mass decay scale (in g) + * @param [IN] p_Epsilon Efficiency factor allowing for uncertainties of coupling magnetic field and matter. + * @return Tuple containing the updated magnetic field strength, spin frequency, spin-down rate and angular momentum of neutron star + */ +DBL_DBL_DBL_DBL NS::PulsarAccretion(const double p_MagField, const double p_SpinFrequency, const double p_AngularMomentum, const double p_Stepsize, const double p_MassGainPerTimeStep, const double p_Kappa, const double p_Epsilon) { + double initialMagField_G = p_MagField * TESLA_TO_GAUSS; + double magFieldLowerLimit_G = PPOW(10.0, OPTIONS->PulsarLog10MinimumMagneticField()) ; + double mass_g = m_Mass * MSOL_TO_G; // in g + double r_cm = m_Radius * RSOL_TO_KM * KM_TO_CM; + double MoI = m_MomentOfInertia_CGS;// * CGS_SI; + double angularMomentum = p_AngularMomentum;// * CGS_SI; + double newPulsarMagneticField = (initialMagField_G - magFieldLowerLimit_G) * exp(-p_MassGainPerTimeStep / p_Kappa) + magFieldLowerLimit_G; + std::cout << "IB: " << initialMagField_G << " FB: " << newPulsarMagneticField << " MT: " << p_MassGainPerTimeStep << " kappa: " << p_Kappa << " BMIN: " << magFieldLowerLimit_G << " step: " << p_Stepsize <NeutronStarAccretionInCE()) { // which mode of CE accretion to use? + + case NS_ACCRETION_IN_CE::ZERO: // ZERO, no effect from CE + NSCE = 0; + break; + + case NS_ACCRETION_IN_CE::DISK: // DISK , CE effect same as a RLOF case + NSCE = 1; + break; + + case NS_ACCRETION_IN_CE::SURFACE: // SURFACE, mass are accreted onto the surface of the neutron star. + NSCE = 2; + break; + + default: // unknown distribution + SHOW_WARN_STATIC(ERROR::UNKNOWN_NS_ACCRETION_IN_CE, // show warning + "Using zero accretion", + OBJECT_TYPE::BASE_BINARY_STAR, + STELLAR_TYPE::NEUTRON_STAR); + NSCE = 0; + } + + double kappa = OPTIONS->PulsarMagneticFieldDecayMassscale() * MSOL_TO_G; + + if ((!p_RecycledNS && !p_CommonEnvelope) || (!p_RecycledNS && utils::Compare(p_MassGainPerTimeStep, 0.0) == 0 )) { + // These are the ''classical'' isolated pulsars. They experience spin-down. + SpinDownIsolatedPulsar(p_Stepsize); + } + + else if ( utils::Compare(p_MassGainPerTimeStep, 0.0) > 0) { + // Next, we update pulsar that goes through mass transfer. + // Note that we do not need to set hard lower limit on the spin period, + // as propeller effect should be included in the calculation, + // which means pulsars will start spinning down when the AM is no longer transferred along mass transfer. + if ((!p_CommonEnvelope) || (p_CommonEnvelope && (NSCE == 1))){ + // Pulsar evolution during stable mass transfer (RLOF), or if using the 'DISK' option + // in the CE accretion option + + //----to be deleted--- + // To prevent in some cases where the numerical calculations return negative spin period/frequency, + // We divide the step of mass transfer into sufficiently small ones where in each step, + // no negative spin period is seen. + //----to be deleted--- + std::tuple accretionResults = PulsarAccretion(m_PulsarDetails.magneticField, m_PulsarDetails.spinFrequency, m_AngularMomentum_CGS, p_Stepsize, p_MassGainPerTimeStep * 1000.0, kappa, p_Epsilon); + + //----to be deleted--- + // int divideTimestepBy = 100; + // bool done = false; + // while (!done && utils::Compare(divideTimestepBy, 1000000) > 0.0) { + + // divideTimestepBy *= 2; + + // double thisTimestepSize = p_Stepsize / divideTimestepBy; + // double thisMassGain = p_MassGainPerTimeStep * 1000.0 / divideTimestepBy; + // double B ; + // double f ; + // double fdot ; + // double am ; + + // std::tie(B, f, fdot, am) = PulsarAccretion(m_PulsarDetails.magneticField, m_PulsarDetails.spinFrequency, + // m_AngularMomentum_CGS, thisTimestepSize, thisMassGain, kappa, p_Epsilon); + + // int count = 0; + // while (!done) { + // std::tie(B, f, fdot, am) = PulsarAccretion(B, f, am, thisTimestepSize, thisMassGain, kappa, p_Epsilon); + + // if (utils::Compare(f, 0.0) < 0) break; + // if (++count >= divideTimestepBy) done = true; + // } + // } + + // double newTimeStepSize = p_Stepsize / divideTimestepBy; + // double newMassGain = p_MassGainPerTimeStep * 1000.0 / divideTimestepBy ; + // accretionResults = PulsarAccretion(m_PulsarDetails.magneticField, m_PulsarDetails.spinFrequency, + // m_AngularMomentum_CGS, newTimeStepSize, newMassGain, kappa, p_Epsilon); + // for (int n = 1; n<= int(divideTimestepBy); n++){ + + // double last_B_2 = std::get<0>(accretionResults); + // double last_f_2 = std::get<1>(accretionResults); + // double last_am_2 = std::get<3>(accretionResults); + + // accretionResults = PulsarAccretion(last_B_2, last_f_2, last_am_2, newTimeStepSize, newMassGain, kappa, p_Epsilon); + //----to be deleted--- + + m_PulsarDetails.magneticField = std::get<0>(accretionResults); + m_PulsarDetails.spinFrequency = std::get<1>(accretionResults); + m_AngularMomentum_CGS = std::get<2>(accretionResults); + + m_PulsarDetails.spinDownRate = CalculateSpinDownRate(m_PulsarDetails.spinFrequency,m_MomentOfInertia_CGS , m_PulsarDetails.magneticField, m_Radius * RSOL_TO_KM ); + } + + + else if ((p_CommonEnvelope) && (NSCE == 2) && utils::Compare(p_MassGainPerTimeStep, 0.0) > 0) { + // Mass transfer through CE when accretion happens at the surface of the NS + + double initialMagField = m_PulsarDetails.magneticField; + double initialMagField_G = initialMagField * TESLA_TO_GAUSS; + double magFieldLowerLimit_G = PPOW(10.0, OPTIONS->PulsarLog10MinimumMagneticField()) ; + + double mass_g = m_Mass * MSOL_TO_G; // in g + double r_cm = m_Radius * RSOL_TO_KM * KM_TO_CM; // in cm + m_MomentOfInertia_CGS = CalculateMomentOfInertiaCGS() ; + double MoI = m_MomentOfInertia_CGS; + double angularMomentum = m_AngularMomentum_CGS; + double newPulsarMagneticField = (initialMagField_G - magFieldLowerLimit_G) * exp(-p_MassGainPerTimeStep * 1000.0 / kappa) + magFieldLowerLimit_G; + double r_cm_3 = r_cm * r_cm * r_cm ; + double Jacc = MoI * PPOW(G_CGS * mass_g /r_cm_3, 0.5) * p_MassGainPerTimeStep * 1000.0 / mass_g ; + + m_AngularMomentum_CGS = angularMomentum + Jacc ; + m_PulsarDetails.magneticField = newPulsarMagneticField * GAUSS_TO_TESLA; + m_PulsarDetails.spinFrequency = m_AngularMomentum_CGS / MoI ; + + m_PulsarDetails.spinDownRate = CalculateSpinDownRate(m_PulsarDetails.spinFrequency,m_MomentOfInertia_CGS , m_PulsarDetails.magneticField, m_Radius * RSOL_TO_KM ); + } + + else if ((p_CommonEnvelope) && (NSCE == 0)) { + // If mass transfer is happening through CE and chosen 'ZERO' in CE accretion mode, + // treat the pulsar as isolated and should be spun down. + SpinDownIsolatedPulsar(p_Stepsize); + } +} +} \ No newline at end of file diff --git a/src/constants.h b/src/constants.h index 54281ea62..56d76f75f 100755 --- a/src/constants.h +++ b/src/constants.h @@ -183,7 +183,7 @@ extern OBJECT_ID globalObjectId; constexpr double FLOAT_TOLERANCE_ABSOLUTE = 0.0000005; // absolute tolerance for floating-point comparisons if COMPARE_GLOBAL_TOLERANCE is defined constexpr double FLOAT_TOLERANCE_RELATIVE = 0.0000005; // relative tolerance for floating-point comparisons if COMPARE_GLOBAL_TOLERANCE is defined -constexpr double ROOT_ABS_TOLERANCE = 1.0E-10; // absolute tolerance for root finder +constexpr double ROOT_ABS_TOLERANCE = 1.0E-9; // absolute tolerance for root finder constexpr double ROOT_REL_TOLERANCE = 1.0E-6; // relative tolerance for root finder From 0f68c81b09f7a5a8bcefbace61357ca09e52f012 Mon Sep 17 00:00:00 2001 From: Yuzhe Song Date: Fri, 19 Apr 2024 17:25:22 +1000 Subject: [PATCH 10/19] remove extra file --- src/NSapr9.cpp | 627 ------------------------------------------------- 1 file changed, 627 deletions(-) delete mode 100755 src/NSapr9.cpp diff --git a/src/NSapr9.cpp b/src/NSapr9.cpp deleted file mode 100755 index 43819b33b..000000000 --- a/src/NSapr9.cpp +++ /dev/null @@ -1,627 +0,0 @@ -#include "Rand.h" -#include "NS.h" - - -/* - * Calculate the luminosity of a Neutron Star - * - * Hurley et al. 2000, eq 93 - * - * Called (indirectly) from GiantBranch, so must be static. - * - * - * double CalculateLuminosityOnPhase_Static(const double p_Mass, const double p_Time) - * - * @param [IN] p_Mass Mass in Msol - * @param [IN] p_Time Time since formation of the object in Myr - * @return Luminosity of the Neutron Star in Lsol - */ -double NS::CalculateLuminosityOnPhase_Static(const double p_Mass, const double p_Time) { - double t = std::max(p_Time, 0.1); - return 0.02 * PPOW(p_Mass, 2.0 / 3.0) / (t * t); -} - - -/* - * Choose timestep for Pulsar Evolution - * - * Pulsars evolve very fast when they are first born, and evolve slower as they age. - * Hence, timestep is chosen to be small when pulsar is young, and is slowly increased - * as the pulsar ages. - * - * double ChooseTimestep(const double p_Time) - * - * @param [IN] p_Time Current age of star in Myr - * @return Suggested timestep (dt) - */ -double NS::ChooseTimestep(const double p_Time) const { - double result = 500.0; // default value - - if (p_Time < 0.01) { - result = 0.001; - } - else if (p_Time < 0.1) { - result = 0.01; - } - else if (p_Time < 1.0) { - result = 0.1; - } - else if (p_Time < 10.0) { - result = 1.0; - } - else if (p_Time < 500.0) { - double slope = 1.58859191006; // 1.58859191006 = log10(500.0) / (log10(500.0) - 1.0) - double log10_step = slope * (log10(p_Time) - 1.0); - result = PPOW(10.0, log10_step); - } - - return result; -} - - -/* - * Calculate Neutron Star radius according to selected equation of state (by commandline option) - * - * - * double CalculateRadiusOnPhaseInKM_Static(const double p_Mass) - * - * @param [IN] p_Mass Mass in Msol - * @return Radius of Neutron Star in km - */ -double NS::CalculateRadiusOnPhaseInKM_Static(const double p_Mass) { - - double radius; - - switch (OPTIONS->NeutronStarEquationOfState()) { // which equation-of-state? - - case NS_EOS::SSE: // SSE - radius = 10.0; - break; - - case NS_EOS::ARP3: { // ARP3 - - // We don't extrapolate so masses outside table just set to extreme values - - std::map::const_iterator iter; - - iter = ARP3MassRadiusRelation.begin(); - double ARP3MinimumMass = iter->first; - double ARP3RadiusAtMinimumMass = iter->second; - - iter = ARP3MassRadiusRelation.end(); - double ARP3MaximumMass = (--iter)->first; - double ARP3RadiusAtMaximumMass = iter->second; - - if (utils::Compare(p_Mass, ARP3MinimumMass) < 0) { - radius = ARP3RadiusAtMinimumMass; - } - else if (utils::Compare(p_Mass, ARP3MaximumMass) > 0) { - radius = ARP3RadiusAtMaximumMass; - } - else{ - radius = utils::SampleFromTabulatedCDF(p_Mass, ARP3MassRadiusRelation); - } - } break; - - default: // unknown equation-of-state - SHOW_WARN_STATIC(ERROR::UNKNOWN_NS_EOS, // show warning - "Using default NS radius = 10.0", - OBJECT_TYPE::BASE_STAR, - STELLAR_TYPE::NEUTRON_STAR); - radius = 10.0; - } - - return radius; -} - - -/* - * Calculate core collapse Supernova parameters - * - * Called from GiantBranch, so must be static. - * - * - * DBL_DBL_DBL CalculateCoreCollapseSNParams_Static(const double p_Mass) - * - * @param [IN] p_Mass Mass in Msol - * @return Tuple containing Luminosity, Radius and Temperature of Neutron Star - */ -DBL_DBL_DBL NS::CalculateCoreCollapseSNParams_Static(const double p_Mass) { - double luminosity = CalculateLuminosityOnPhase_Static(p_Mass, 0.0); // Luminosity of Neutron Star as it cools - double radius = CalculateRadiusOnPhase_Static(p_Mass); // Radius of Neutron Star in Rsol - double temperature = BaseStar::CalculateTemperatureOnPhase_Static(luminosity, radius); // Temperature of NS - - return std::make_tuple(luminosity, radius, temperature); -} - - -/* - * Calculate the spin period of a Pulsar at birth according to selected distribution (by commandline option) - * - * - * double CalculatePulsarBirthSpinPeriod() - * - * @return Birth spin period of Pulsar in ms - */ -double NS::CalculatePulsarBirthSpinPeriod() { - - double pSpin; - - switch (OPTIONS->PulsarBirthSpinPeriodDistribution()) { // which distribution? - - case PULSAR_BIRTH_SPIN_PERIOD_DISTRIBUTION::ZERO: // ZERO - pSpin = 0.0; - break; - - case PULSAR_BIRTH_SPIN_PERIOD_DISTRIBUTION::FIXED: // FIXED constant value as used in default model in Oslowski et al 2011 https://arxiv.org/abs/0903.3538 - SHOW_WARN_STATIC(ERROR::UNSUPPORTED_PULSAR_BIRTH_SPIN_PERIOD_DISTRIBUTION, // show warning - "Using spin = 0.0", - OBJECT_TYPE::BASE_STAR, - STELLAR_TYPE::NEUTRON_STAR); - pSpin = 0.0; - break; - - case PULSAR_BIRTH_SPIN_PERIOD_DISTRIBUTION::UNIFORM: { // UNIFORM distribution between minimum and maximum value as in Oslowski et al 2011 https://arxiv.org/abs/0903.3538 (default Pmin = and Pmax = ) - // and also Kiel et al 2008 https://arxiv.org/abs/0805.0059 (default Pmin = 10 ms and Pmax 100 ms, section 3.4) - - double maximum = OPTIONS->PulsarBirthSpinPeriodDistributionMax(); - double minimum = OPTIONS->PulsarBirthSpinPeriodDistributionMin(); - - pSpin = minimum + (RAND->Random() * (maximum - minimum)); - } break; - - case PULSAR_BIRTH_SPIN_PERIOD_DISTRIBUTION::NORMAL: { // NORMAL distribution from Faucher-Giguere and Kaspi 2006 https://arxiv.org/abs/astro-ph/0512585 - - // Values hard-coded for now, can make them options if necessary - // pulsarBirthSpinPeriodDistributionFaucherGiguereKaspi2006Mean = 300.0; - // pulsarBirthSpinPeriodDistributionFaucherGiguereKaspi2006Std = 150.0; - - double mean = 300.0; - double sigma = 150.0; - - do { pSpin = RAND->RandomGaussian(sigma) + mean;} while (utils::Compare(pSpin, 0.0) < 0); - - } break; - - default: // unknown distribution - SHOW_WARN_STATIC(ERROR::UNKNOWN_PULSAR_BIRTH_SPIN_PERIOD_DISTRIBUTION, // show warning - "Using spin = 0.0", - OBJECT_TYPE::BASE_STAR, - STELLAR_TYPE::NEUTRON_STAR); - pSpin = 0.0; - } - - return pSpin; -} - - -/* - * Calculate (log10 of) the magnetic field (in G) for a Pulsar at birth - * according to selected distribution (by commandline option) - * - * - * double CalculatePulsarBirthMagneticField() - * - * @return log10 of the birth magnetic field in G - */ -double NS::CalculatePulsarBirthMagneticField() { - - double log10B; - - switch (OPTIONS->PulsarBirthMagneticFieldDistribution()) { // which distribution? - - case PULSAR_BIRTH_MAGNETIC_FIELD_DISTRIBUTION::ZERO: // ZERO - log10B = 0.0; - break; - - case PULSAR_BIRTH_MAGNETIC_FIELD_DISTRIBUTION::FIXED: // FIXED - set to a fixed constant value - SHOW_WARN_STATIC(ERROR::UNSUPPORTED_PULSAR_BIRTH_MAGNETIC_FIELD_DISTRIBUTION, // show warning - "Using 0.0", - OBJECT_TYPE::BASE_STAR, - STELLAR_TYPE::NEUTRON_STAR); - log10B = 0.0; - break; - - case PULSAR_BIRTH_MAGNETIC_FIELD_DISTRIBUTION::FLATINLOG: { // FLAT IN LOG distribution from Oslowski et al 2011 https://arxiv.org/abs/0903.3538 (log10B0min = , log10B0max = ) - - double maximum = OPTIONS->PulsarBirthMagneticFieldDistributionMax(); - double minimum = OPTIONS->PulsarBirthMagneticFieldDistributionMin(); - - log10B = minimum + (RAND->Random() * (maximum - minimum)); - - } break; - - case PULSAR_BIRTH_MAGNETIC_FIELD_DISTRIBUTION::UNIFORM: { // UNIFORM flat distribution used in Kiel et al 2008 https://arxiv.org/abs/0805.0059 (log10B0min = 11, log10B0max = 13.5 see section 3.4 and Table 1.) - - - double maximum = PPOW(10.0, OPTIONS->PulsarBirthMagneticFieldDistributionMax()); - double minimum = PPOW(10.0, OPTIONS->PulsarBirthMagneticFieldDistributionMin()); - - log10B = log10(minimum + (RAND->Random() * (maximum - minimum))); - } break; - - case PULSAR_BIRTH_MAGNETIC_FIELD_DISTRIBUTION::LOGNORMAL: { // LOG NORMAL distribution from Faucher-Giguere and Kaspi 2006 https://arxiv.org/abs/astro-ph/0512585 - - // Values hard-coded for now, can make them options if necessary - // pulsarBirthMagneticFieldDistributionFaucherGiguereKaspi2006Mean = 12.65 - // pulsarBirthMagneticFieldDistributionFaucherGiguereKaspi2006Std = 0.55 - - double mean = 12.65; - double sigma = 0.55; - - log10B = RAND->RandomGaussian(sigma) + mean; - } break; - - default: // unknown distribution - SHOW_WARN_STATIC(ERROR::UNKNOWN_PULSAR_BIRTH_MAGNETIC_FIELD_DISTRIBUTION, // show warning - "Using 0.0", - OBJECT_TYPE::BASE_STAR, - STELLAR_TYPE::NEUTRON_STAR); - log10B = 0.0; - } - - return log10B; -} - - -/* - * Calculate the moment of inertia for a Neutron Star using a model independent relation between - * the moment of inertia, mass and radius of a neutron star - return MoI in CGS. - * - * Uses m_Mass and m_Radius to calculate moment of inertia. - * - * Raithel et al. 2016, eq 8 in https://arxiv.org/abs/1603.06594 - * https://tap.arizona.edu/sites/tap.arizona.edu/files/Raithel_2015_manuscript.pdf - * - * - * double CalculateMomentOfInertiaCGS() - * - * @return Moment of inertia in g cm^2 - */ -double NS::CalculateMomentOfInertiaCGS() const { - - // pow() is slow - use multiplication - - double r_km = m_Radius * RSOL_TO_KM; - double m_r = m_Mass / r_km; - double m_r_4 = m_r * m_r * m_r * m_r; - - double r_cm = m_Radius * RSOL_TO_CM; - double r_cm_2 = r_cm * r_cm; - - return 0.237 * m_Mass * MSOL_TO_G * r_cm_2 * (1.0 + (4.2 * m_r) + 90.0 * m_r_4); -} - - -/* - * Calculate the spin down rate for isolated Neutron Stars in cgs - * - * See Equation 2 in https://arxiv.org/pdf/1912.02415.pdf - * - * This is changed to the form of calculating spindown with P and Pdot, then convert to OmegaDot and to be recorded in the output file. - * Evolution of the inclination between pulsar magnetic and rotational axes will be considered in a future version. - * - * double CalculateSpinDownRate(const double p_Omega, const double p_MomentOfInteria, const double p_MagField, const double p_Radius) - * - * @param [IN] p_Omega Pulsar spin frequency. - * @param [IN] p_MomentOfInteria Moment of Interia of the Neutron Star in kg m^2 - * @param [IN] p_MagField Magnetic field in Tesla - * @param [IN] p_Radius Radius of the Neutron Star in kilometres - * @return Spin down rate (spin frequency derivative) of an isolated Neutron Star in s^(-2) - */ -double NS::CalculateSpinDownRate(const double p_Omega, const double p_MomentOfInteria, const double p_MagField, const double p_Radius) const { - - // pow() is slow - use multiplication - - double period = _2_PI / p_Omega; // convert frequency to period - double cgsRadius = p_Radius * KM_TO_CM; // radius in cm - double radius_6 = cgsRadius * cgsRadius * cgsRadius * cgsRadius * cgsRadius * cgsRadius; - double cgsMagField = p_MagField * TESLA_TO_GAUSS; // B field in G - double magField_2 = cgsMagField * cgsMagField; - constexpr double _8_PI_2 = 8.0 * PI_2; - constexpr double _3_C_3 = 3.0E6 * C * C * C; // 3.0 * (C * 100.0) * (C * 100.0) * (C * 100.0) - double pDotTop = _8_PI_2 * radius_6 * magField_2; - double pDotBottom = _3_C_3 * p_MomentOfInteria * period; - double pDot = pDotTop / pDotBottom; // period derivative - - return(-pDot * p_Omega / period); // convert period derivative to frequency derivative, which is what is recorded in the output -} - - -/* - * Calculates and sets pulsar parameters at birth of pulsar - * - * Modifies the following class member variables: - * - * m_AngularMomentum_CGS - * m_MomentOfInertia_CGS - * m_PulsarDetails.birthPeriod - * m_PulsarDetails.birthSpinDownRate - * m_PulsarDetails.magneticField - * m_PulsarDetails.spinDownRate - * m_PulsarDetails.spinFrequency - * - * - * void CalculateAndSetPulsarParameters() - */ -void NS::CalculateAndSetPulsarParameters() { - - m_PulsarDetails.magneticField = PPOW(10.0, CalculatePulsarBirthMagneticField()) * GAUSS_TO_TESLA; // magnetic field in Gauss -> convert to Tesla - m_PulsarDetails.spinPeriod = CalculatePulsarBirthSpinPeriod(); // spin period in ms - m_PulsarDetails.spinFrequency = _2_PI / (m_PulsarDetails.spinPeriod * SECONDS_IN_MS); - m_PulsarDetails.birthPeriod = m_PulsarDetails.spinPeriod * SECONDS_IN_MS; // convert from ms to s - m_MomentOfInertia_CGS = CalculateMomentOfInertiaCGS(); // in CGS g cm^2 - - // Note we convert neutronStarMomentOfInertia from CGS to SI here - m_PulsarDetails.spinDownRate = CalculateSpinDownRate(m_PulsarDetails.spinFrequency, m_MomentOfInertia_CGS, m_PulsarDetails.magneticField, m_Radius * RSOL_TO_KM); - m_PulsarDetails.birthSpinDownRate = m_PulsarDetails.spinDownRate; - m_AngularMomentum_CGS = m_MomentOfInertia_CGS * m_PulsarDetails.spinFrequency; // in CGS g cm^2 s^-1 -} - - -/* - * Update the magnetic field and spins of neutron stars when it's deemed as an isolated pulsar. - * - * This function is called in multiple situations in the NS::UpdateMagneticFieldAndSpin() function - * - * Modifies the following class member variables: - * - * m_AngularMomentum_CGS - * m_PulsarDetails.spinFrequency - * m_PulsarDetails.magneticField - * m_PulsarDetails.spinDownRate - * - * - * void SpinDownIsolatedPulsar(const double p_Stepsize) - * - * @param [IN] p_Stepsize Timestep size for integration (in seconds) - */ -void NS::SpinDownIsolatedPulsar(const double p_Stepsize) { - - double NSradius_IN_CM = m_Radius * RSOL_TO_KM * KM_TO_CM; - double NSradius_3 = NSradius_IN_CM * NSradius_IN_CM * NSradius_IN_CM; - double NSradius_6 = NSradius_3 * NSradius_3; - constexpr double _8_PI_2 = 8.0 * PI_2; - constexpr double _3_C_3 = 3.0 * C * C * C * 1000000.0; - - double initialMagField = m_PulsarDetails.magneticField; // (in T) - double initialMagField_G = initialMagField * TESLA_TO_GAUSS; - double initialSpinPeriod = _2_PI / m_PulsarDetails.spinFrequency; - double magFieldLowerLimit = PPOW(10.0, OPTIONS->PulsarLog10MinimumMagneticField()) * GAUSS_TO_TESLA; - double magFieldLowerLimit_G = magFieldLowerLimit * TESLA_TO_GAUSS; - double tau = OPTIONS->PulsarMagneticFieldDecayTimescale() * MYR_TO_YEAR * SECONDS_IN_YEAR; - - // calculate isolated decay of the magnetic field for a neutron star - // see Equation 6 in arXiv:0903.3538v2 - m_PulsarDetails.magneticField = magFieldLowerLimit + (initialMagField - magFieldLowerLimit) * exp(-p_Stepsize / tau); // update pulsar magnetic field in SI. - - // calculate the spin frequency for isolated neutron stars - // see Equation 6 in arxiv:1912.02415 - // The rest of the calculations are carried out in cgs. - double constant2 = (_8_PI_2 * NSradius_6) / (_3_C_3 * m_MomentOfInertia_CGS); - double term1 = magFieldLowerLimit_G * magFieldLowerLimit_G * p_Stepsize; - double term2 = tau * magFieldLowerLimit_G * ( m_PulsarDetails.magneticField * TESLA_TO_GAUSS - initialMagField_G); - double term3 = (tau / 2.0) * (TESLA_TO_GAUSS * TESLA_TO_GAUSS * (m_PulsarDetails.magneticField * m_PulsarDetails.magneticField) - (initialMagField_G * initialMagField_G)); - double Psquared = constant2 * (term1 - term2 - term3) + (initialSpinPeriod * initialSpinPeriod); - - double P_f = std::sqrt(Psquared); - m_PulsarDetails.spinFrequency = _2_PI / P_f; // pulsar spin frequency - - // calculate the spin down rate for isolated neutron stars - // see Equation 4 in arXiv:0903.3538v2 (Our version is in cgs) - m_PulsarDetails.spinDownRate = CalculateSpinDownRate(m_PulsarDetails.spinFrequency,m_MomentOfInertia_CGS , m_PulsarDetails.magneticField, NSradius_IN_CM / KM_TO_CM); - m_AngularMomentum_CGS = m_PulsarDetails.spinFrequency * m_MomentOfInertia_CGS; // angular momentum of star in CGS -} - - -/* - * Calculate the magnetic field, spin, spin-down rate and angular momentum of a neutron star, - * when it's accreting mass from companion. - * The calculations used in this function follows closely Sec. 2.2.1 in arxiv:1912.02415 - * We carry out the calculations in this function using cgs units. - * This function is called in the NS::UpdateMagneticFieldAndSpin() function - * - * Returns, in a tuple, these four quantities of a neutron star: - * - * magnetic field strength - * spin frequency - * spin-down rate - * angular momentum - * - * DBL_DBL_DBL_DBL PulsarAccretion(const double p_MagField, const double p_SpinFrequency, const double p_AngularMomentum, const double p_Stepsize, const double p_MassGainPerTimeStep, const double kappa, const double p_Epsilon) - * @param [IN] p_MagField NS magnetic field strength at the beginning of accretion (in Tesla) - * @param [IN] p_SpinFrequency Spin frequency for the NS at the beginning of accretion (in Hz) - * @param [IN] p_AngularMomentum Angular momentum of the NS at the beginning of accretion (g cm-2 / s) - * @param [IN] p_Stepsize Timestep size for integration (in seconds) - * @param [IN] p_MassGainPerTimeStep Mass transferred from the secondary for each iteration (in g) - * @param [IN] p_Kappa Magnetic field mass decay scale (in g) - * @param [IN] p_Epsilon Efficiency factor allowing for uncertainties of coupling magnetic field and matter. - * @return Tuple containing the updated magnetic field strength, spin frequency, spin-down rate and angular momentum of neutron star - */ -DBL_DBL_DBL_DBL NS::PulsarAccretion(const double p_MagField, const double p_SpinFrequency, const double p_AngularMomentum, const double p_Stepsize, const double p_MassGainPerTimeStep, const double p_Kappa, const double p_Epsilon) { - double initialMagField_G = p_MagField * TESLA_TO_GAUSS; - double magFieldLowerLimit_G = PPOW(10.0, OPTIONS->PulsarLog10MinimumMagneticField()) ; - double mass_g = m_Mass * MSOL_TO_G; // in g - double r_cm = m_Radius * RSOL_TO_KM * KM_TO_CM; - double MoI = m_MomentOfInertia_CGS;// * CGS_SI; - double angularMomentum = p_AngularMomentum;// * CGS_SI; - double newPulsarMagneticField = (initialMagField_G - magFieldLowerLimit_G) * exp(-p_MassGainPerTimeStep / p_Kappa) + magFieldLowerLimit_G; - std::cout << "IB: " << initialMagField_G << " FB: " << newPulsarMagneticField << " MT: " << p_MassGainPerTimeStep << " kappa: " << p_Kappa << " BMIN: " << magFieldLowerLimit_G << " step: " << p_Stepsize <NeutronStarAccretionInCE()) { // which mode of CE accretion to use? - - case NS_ACCRETION_IN_CE::ZERO: // ZERO, no effect from CE - NSCE = 0; - break; - - case NS_ACCRETION_IN_CE::DISK: // DISK , CE effect same as a RLOF case - NSCE = 1; - break; - - case NS_ACCRETION_IN_CE::SURFACE: // SURFACE, mass are accreted onto the surface of the neutron star. - NSCE = 2; - break; - - default: // unknown distribution - SHOW_WARN_STATIC(ERROR::UNKNOWN_NS_ACCRETION_IN_CE, // show warning - "Using zero accretion", - OBJECT_TYPE::BASE_BINARY_STAR, - STELLAR_TYPE::NEUTRON_STAR); - NSCE = 0; - } - - double kappa = OPTIONS->PulsarMagneticFieldDecayMassscale() * MSOL_TO_G; - - if ((!p_RecycledNS && !p_CommonEnvelope) || (!p_RecycledNS && utils::Compare(p_MassGainPerTimeStep, 0.0) == 0 )) { - // These are the ''classical'' isolated pulsars. They experience spin-down. - SpinDownIsolatedPulsar(p_Stepsize); - } - - else if ( utils::Compare(p_MassGainPerTimeStep, 0.0) > 0) { - // Next, we update pulsar that goes through mass transfer. - // Note that we do not need to set hard lower limit on the spin period, - // as propeller effect should be included in the calculation, - // which means pulsars will start spinning down when the AM is no longer transferred along mass transfer. - if ((!p_CommonEnvelope) || (p_CommonEnvelope && (NSCE == 1))){ - // Pulsar evolution during stable mass transfer (RLOF), or if using the 'DISK' option - // in the CE accretion option - - //----to be deleted--- - // To prevent in some cases where the numerical calculations return negative spin period/frequency, - // We divide the step of mass transfer into sufficiently small ones where in each step, - // no negative spin period is seen. - //----to be deleted--- - std::tuple accretionResults = PulsarAccretion(m_PulsarDetails.magneticField, m_PulsarDetails.spinFrequency, m_AngularMomentum_CGS, p_Stepsize, p_MassGainPerTimeStep * 1000.0, kappa, p_Epsilon); - - //----to be deleted--- - // int divideTimestepBy = 100; - // bool done = false; - // while (!done && utils::Compare(divideTimestepBy, 1000000) > 0.0) { - - // divideTimestepBy *= 2; - - // double thisTimestepSize = p_Stepsize / divideTimestepBy; - // double thisMassGain = p_MassGainPerTimeStep * 1000.0 / divideTimestepBy; - // double B ; - // double f ; - // double fdot ; - // double am ; - - // std::tie(B, f, fdot, am) = PulsarAccretion(m_PulsarDetails.magneticField, m_PulsarDetails.spinFrequency, - // m_AngularMomentum_CGS, thisTimestepSize, thisMassGain, kappa, p_Epsilon); - - // int count = 0; - // while (!done) { - // std::tie(B, f, fdot, am) = PulsarAccretion(B, f, am, thisTimestepSize, thisMassGain, kappa, p_Epsilon); - - // if (utils::Compare(f, 0.0) < 0) break; - // if (++count >= divideTimestepBy) done = true; - // } - // } - - // double newTimeStepSize = p_Stepsize / divideTimestepBy; - // double newMassGain = p_MassGainPerTimeStep * 1000.0 / divideTimestepBy ; - // accretionResults = PulsarAccretion(m_PulsarDetails.magneticField, m_PulsarDetails.spinFrequency, - // m_AngularMomentum_CGS, newTimeStepSize, newMassGain, kappa, p_Epsilon); - // for (int n = 1; n<= int(divideTimestepBy); n++){ - - // double last_B_2 = std::get<0>(accretionResults); - // double last_f_2 = std::get<1>(accretionResults); - // double last_am_2 = std::get<3>(accretionResults); - - // accretionResults = PulsarAccretion(last_B_2, last_f_2, last_am_2, newTimeStepSize, newMassGain, kappa, p_Epsilon); - //----to be deleted--- - - m_PulsarDetails.magneticField = std::get<0>(accretionResults); - m_PulsarDetails.spinFrequency = std::get<1>(accretionResults); - m_AngularMomentum_CGS = std::get<2>(accretionResults); - - m_PulsarDetails.spinDownRate = CalculateSpinDownRate(m_PulsarDetails.spinFrequency,m_MomentOfInertia_CGS , m_PulsarDetails.magneticField, m_Radius * RSOL_TO_KM ); - } - - - else if ((p_CommonEnvelope) && (NSCE == 2) && utils::Compare(p_MassGainPerTimeStep, 0.0) > 0) { - // Mass transfer through CE when accretion happens at the surface of the NS - - double initialMagField = m_PulsarDetails.magneticField; - double initialMagField_G = initialMagField * TESLA_TO_GAUSS; - double magFieldLowerLimit_G = PPOW(10.0, OPTIONS->PulsarLog10MinimumMagneticField()) ; - - double mass_g = m_Mass * MSOL_TO_G; // in g - double r_cm = m_Radius * RSOL_TO_KM * KM_TO_CM; // in cm - m_MomentOfInertia_CGS = CalculateMomentOfInertiaCGS() ; - double MoI = m_MomentOfInertia_CGS; - double angularMomentum = m_AngularMomentum_CGS; - double newPulsarMagneticField = (initialMagField_G - magFieldLowerLimit_G) * exp(-p_MassGainPerTimeStep * 1000.0 / kappa) + magFieldLowerLimit_G; - double r_cm_3 = r_cm * r_cm * r_cm ; - double Jacc = MoI * PPOW(G_CGS * mass_g /r_cm_3, 0.5) * p_MassGainPerTimeStep * 1000.0 / mass_g ; - - m_AngularMomentum_CGS = angularMomentum + Jacc ; - m_PulsarDetails.magneticField = newPulsarMagneticField * GAUSS_TO_TESLA; - m_PulsarDetails.spinFrequency = m_AngularMomentum_CGS / MoI ; - - m_PulsarDetails.spinDownRate = CalculateSpinDownRate(m_PulsarDetails.spinFrequency,m_MomentOfInertia_CGS , m_PulsarDetails.magneticField, m_Radius * RSOL_TO_KM ); - } - - else if ((p_CommonEnvelope) && (NSCE == 0)) { - // If mass transfer is happening through CE and chosen 'ZERO' in CE accretion mode, - // treat the pulsar as isolated and should be spun down. - SpinDownIsolatedPulsar(p_Stepsize); - } -} -} \ No newline at end of file From ed897f25b9c07b59a3dd09fdca5a348b09213568 Mon Sep 17 00:00:00 2001 From: Yuzhe Song Date: Mon, 22 Apr 2024 08:54:31 +1000 Subject: [PATCH 11/19] fixing typos --- src/NS.cpp | 4 ++-- src/changelog.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/NS.cpp b/src/NS.cpp index 2e596da37..4af62212c 100755 --- a/src/NS.cpp +++ b/src/NS.cpp @@ -494,7 +494,7 @@ DBL_DBL_DBL_DBL NS::PulsarAccretion(const double p_MagField, const double p_Spin * @param [IN] p_CommonEnvelope Indicates whether there there is a common envelope - true or false * @param [IN] p_RecycledNS Indicates whether this star is/was a recycled neutron star - true or false * @param [IN] p_Stepsize Timestep size for integration (in seconds) - * @param [IN] p_MassGainPerTimeStep Mass trasnferred from the secondary for each iteration (in kg) + * @param [IN] p_MassGainPerTimeStep Mass transferred from the secondary for each iteration (in kg) * @param [IN] p_Epsilon Efficiency factor allowing for uncertainties of coupling magnetic field and matter. */ void NS::UpdateMagneticFieldAndSpin(const bool p_CommonEnvelope, const bool p_RecycledNS, const double p_Stepsize, const double p_MassGainPerTimeStep, const double p_Epsilon) { @@ -615,4 +615,4 @@ void NS::UpdateMagneticFieldAndSpin(const bool p_CommonEnvelope, const bool p_Re SpinDownIsolatedPulsar(p_Stepsize); } } -} \ No newline at end of file +} diff --git a/src/changelog.h b/src/changelog.h index 1a3756238..297e7dd16 100644 --- a/src/changelog.h +++ b/src/changelog.h @@ -1134,7 +1134,7 @@ // - Fixes to MSP formation/NS in mass transfer treatments: // 1). Created a new function NS::PulsarAccretion() to calculate the pulsar evolution in stable mass transfer. // 2). In UpdateMagneticFieldAndSpin(), splitting stable mass transfer into smaller steps so that no negative spin period is present. -// 3). Adding a new programing option "NS-ACCRETION-IN-CE" for different treatment of how neutron star would behave when in CE. +// 3). Adding a new programming option "NS-ACCRETION-IN-CE" for different treatment of how neutron star would behave when in CE. // - Includes SS's fix for issue #1076 const std::string VERSION_STRING = "02.44.00"; From cd62aa04012735e3b1734023cb41d8a3f1dc1d57 Mon Sep 17 00:00:00 2001 From: Yuzhe Song Date: Thu, 16 May 2024 14:58:45 +1000 Subject: [PATCH 12/19] changes to BaseBinaryStar.cpp --- src/BaseBinaryStar.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/BaseBinaryStar.cpp b/src/BaseBinaryStar.cpp index b77ecd2df..9a456d728 100644 --- a/src/BaseBinaryStar.cpp +++ b/src/BaseBinaryStar.cpp @@ -2021,7 +2021,8 @@ void BaseBinaryStar::CalculateMassTransfer(const double p_Dt) { } // Check for recycled pulsars. Not considering CEE as a way of recycling NSs. - if (!m_CEDetails.CEEnow && m_Accretor->IsOneOf({ STELLAR_TYPE::NEUTRON_STAR })) { // accretor is a neutron star + //if (!m_CEDetails.CEEnow && m_Accretor->IsOneOf({ STELLAR_TYPE::NEUTRON_STAR })) { // accretor is a neutron star + if ((!m_CEDetails.CEEnow || OPTIONS->NeutronStarAccretionInCE() != NS_ACCRETION_IN_CE::ZERO) && m_Accretor->IsOneOf({ STELLAR_TYPE::NEUTRON_STAR })) { m_Donor->SetRLOFOntoNS(); // donor donated mass to a neutron star m_Accretor->SetRecycledNS(); // accretor is (was) a recycled NS } From 67c0f12599efdddffec8c4558a456f3c22f68872 Mon Sep 17 00:00:00 2001 From: Yuzhe Song Date: Thu, 16 May 2024 15:06:38 +1000 Subject: [PATCH 13/19] changes to changelog.h to avoid conflict --- src/changelog.h | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/changelog.h b/src/changelog.h index 297e7dd16..eb92464d7 100644 --- a/src/changelog.h +++ b/src/changelog.h @@ -1130,13 +1130,7 @@ // - Some Code cleanup // 02.43.05 JR - Apr 21, 2024 - Defect repair, some code cleanup: // - Last piece of no logging for clones - this prevents ephemeral clones from writing to or clearing the SSE SN stash. -// 02.44.00 YS - Apr 22, 2024 - Update to neutron star accretion treatments: -// - Fixes to MSP formation/NS in mass transfer treatments: -// 1). Created a new function NS::PulsarAccretion() to calculate the pulsar evolution in stable mass transfer. -// 2). In UpdateMagneticFieldAndSpin(), splitting stable mass transfer into smaller steps so that no negative spin period is present. -// 3). Adding a new programming option "NS-ACCRETION-IN-CE" for different treatment of how neutron star would behave when in CE. -// - Includes SS's fix for issue #1076 -const std::string VERSION_STRING = "02.44.00"; +const std::string VERSION_STRING = "02.43.05"; # endif // __changelog_h__ From 8102751aecfc0d8afe7c435ee36c3fc80f03aeb9 Mon Sep 17 00:00:00 2001 From: Yuzhe Song Date: Tue, 21 May 2024 13:52:44 +1000 Subject: [PATCH 14/19] commenting out Simon's MT treatments --- src/BaseBinaryStar.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/BaseBinaryStar.cpp b/src/BaseBinaryStar.cpp index 1fb537fd6..4462d5ee9 100644 --- a/src/BaseBinaryStar.cpp +++ b/src/BaseBinaryStar.cpp @@ -2109,10 +2109,10 @@ void BaseBinaryStar::CalculateMassTransfer(const double p_Dt) { // If the current timestep is smaller than the donor's thermal timescale, only a fraction of the mass // that needs to be donated has time to be donated - double donorThermalTimescale = m_Donor->CalculateThermalTimescale(); - if (p_Dt < donorThermalTimescale) { - massDiffDonor = (p_Dt / donorThermalTimescale) * massDiffDonor; - } + // double donorThermalTimescale = m_Donor->CalculateThermalTimescale(); + // if (p_Dt < donorThermalTimescale) { + // massDiffDonor = (p_Dt / donorThermalTimescale) * massDiffDonor; + // } if (!m_CEDetails.CEEnow) { // CE flagged? // no From 781d84d740914b81c7dacd7491a585b4ea97f144 Mon Sep 17 00:00:00 2001 From: Yuzhe Song Date: Mon, 24 Jun 2024 15:40:52 +1000 Subject: [PATCH 15/19] Update to Pdot calculations in NS.cpp --- src/NS.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/NS.cpp b/src/NS.cpp index f1302a0f9..268be3470 100755 --- a/src/NS.cpp +++ b/src/NS.cpp @@ -580,8 +580,8 @@ void NS::UpdateMagneticFieldAndSpin(const bool p_CommonEnvelope, const bool p_Re m_PulsarDetails.magneticField = std::get<0>(accretionResults); m_PulsarDetails.spinFrequency = std::get<1>(accretionResults); m_AngularMomentum_CGS = std::get<3>(accretionResults); - - m_PulsarDetails.spinDownRate = CalculateSpinDownRate(m_PulsarDetails.spinFrequency,m_MomentOfInertia_CGS , m_PulsarDetails.magneticField, m_Radius * RSOL_TO_KM ); + m_PulsarDetails.spinDownRate = std::get<2>(accretionResults); + //m_PulsarDetails.spinDownRate = CalculateSpinDownRate(m_PulsarDetails.spinFrequency,m_MomentOfInertia_CGS , m_PulsarDetails.magneticField, m_Radius * RSOL_TO_KM ); } } @@ -605,8 +605,8 @@ void NS::UpdateMagneticFieldAndSpin(const bool p_CommonEnvelope, const bool p_Re m_AngularMomentum_CGS = angularMomentum + Jacc ; m_PulsarDetails.magneticField = newPulsarMagneticField * GAUSS_TO_TESLA; m_PulsarDetails.spinFrequency = m_AngularMomentum_CGS / MoI ; - - m_PulsarDetails.spinDownRate = CalculateSpinDownRate(m_PulsarDetails.spinFrequency,m_MomentOfInertia_CGS , m_PulsarDetails.magneticField, m_Radius * RSOL_TO_KM ); + m_PulsarDetails.spinDownRate = Jacc/ p_Stepsize /MoI; + // m_PulsarDetails.spinDownRate = CalculateSpinDownRate(m_PulsarDetails.spinFrequency,m_MomentOfInertia_CGS , m_PulsarDetails.magneticField, m_Radius * RSOL_TO_KM ); } else if ((p_CommonEnvelope) && (NSCE == 0)) { From 27dc8cc55846b3fd64665fda3be5873544dbec3d Mon Sep 17 00:00:00 2001 From: Yuzhe Song Date: Thu, 27 Jun 2024 17:58:59 +1000 Subject: [PATCH 16/19] incorporating latest mass transfer fixes --- src/NS.cpp | 3 --- src/changelog.h | 7 ++++++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/NS.cpp b/src/NS.cpp index 268be3470..1d1510c91 100755 --- a/src/NS.cpp +++ b/src/NS.cpp @@ -581,8 +581,6 @@ void NS::UpdateMagneticFieldAndSpin(const bool p_CommonEnvelope, const bool p_Re m_PulsarDetails.spinFrequency = std::get<1>(accretionResults); m_AngularMomentum_CGS = std::get<3>(accretionResults); m_PulsarDetails.spinDownRate = std::get<2>(accretionResults); - //m_PulsarDetails.spinDownRate = CalculateSpinDownRate(m_PulsarDetails.spinFrequency,m_MomentOfInertia_CGS , m_PulsarDetails.magneticField, m_Radius * RSOL_TO_KM ); - } } @@ -606,7 +604,6 @@ void NS::UpdateMagneticFieldAndSpin(const bool p_CommonEnvelope, const bool p_Re m_PulsarDetails.magneticField = newPulsarMagneticField * GAUSS_TO_TESLA; m_PulsarDetails.spinFrequency = m_AngularMomentum_CGS / MoI ; m_PulsarDetails.spinDownRate = Jacc/ p_Stepsize /MoI; - // m_PulsarDetails.spinDownRate = CalculateSpinDownRate(m_PulsarDetails.spinFrequency,m_MomentOfInertia_CGS , m_PulsarDetails.magneticField, m_Radius * RSOL_TO_KM ); } else if ((p_CommonEnvelope) && (NSCE == 0)) { diff --git a/src/changelog.h b/src/changelog.h index 665a59f19..c27432d56 100644 --- a/src/changelog.h +++ b/src/changelog.h @@ -1218,9 +1218,14 @@ // - Code cleanup // 02.49.05 IM - June 22, 2024 - Enhancement: // - Replaced fixed-step, first-order integrator for orbital change after mass transfer with an adaptive-step, higher-order ODE integrator for improved speed and accuracy +// 02.50.00 YS - Jun 27, 2024 - Update to neutron star accretion treatments: +// - Fixes to MSP formation/NS in mass transfer treatments: +// 1). Created a new function NS::PulsarAccretion() to calculate the pulsar evolution in stable mass transfer. +// 2). In UpdateMagneticFieldAndSpin(), splitting stable mass transfer into smaller steps so that no negative spin period is present. +// 3). Adding a new programming option "NS-ACCRETION-IN-CE" for different treatment of how neutron star would behave when in CE. -const std::string VERSION_STRING = "02.49.05"; +const std::string VERSION_STRING = "02.50.00"; From d72d3a7875e8955a0671042e6bf2b0436ed21e04 Mon Sep 17 00:00:00 2001 From: Yuzhe Song Date: Thu, 29 Aug 2024 16:55:20 +1000 Subject: [PATCH 17/19] changes to MSP branch before switching branches. Will continue to work on it --- src/BaseBinaryStar.cpp | 10 +- src/Mode | 495 +++++++++++++++++++++++++++++++++++++++++ src/NS.cpp | 171 +++++++------- src/Options.cpp | 6 +- src/Options.h | 2 +- 5 files changed, 581 insertions(+), 103 deletions(-) create mode 100644 src/Mode diff --git a/src/BaseBinaryStar.cpp b/src/BaseBinaryStar.cpp index 270ef0e57..dc30d8b52 100644 --- a/src/BaseBinaryStar.cpp +++ b/src/BaseBinaryStar.cpp @@ -2142,13 +2142,6 @@ void BaseBinaryStar::CalculateMassTransfer(const double p_Dt) { } } - // If the current timestep is smaller than the donor's thermal timescale, only a fraction of the mass - // that needs to be donated has time to be donated - // double donorThermalTimescale = m_Donor->CalculateThermalTimescale(); - // if (p_Dt < donorThermalTimescale) { - // massDiffDonor = (p_Dt / donorThermalTimescale) * massDiffDonor; - // } - if (!m_CEDetails.CEEnow) { // CE flagged? // no double massGainAccretor = -massDiffDonor * m_FractionAccreted; // set accretor mass gain to mass loss * conservativeness @@ -2173,8 +2166,7 @@ void BaseBinaryStar::CalculateMassTransfer(const double p_Dt) { } } - // Check for recycled pulsars. Not considering CEE as a way of recycling NSs. - //if (!m_CEDetails.CEEnow && m_Accretor->IsOneOf({ STELLAR_TYPE::NEUTRON_STAR })) { // accretor is a neutron star + // Check for recycled pulsars. if ((!m_CEDetails.CEEnow || OPTIONS->NeutronStarAccretionInCE() != NS_ACCRETION_IN_CE::ZERO) && m_Accretor->IsOneOf({ STELLAR_TYPE::NEUTRON_STAR })) { m_Donor->SetRLOFOntoNS(); // donor donated mass to a neutron star m_Accretor->SetRecycledNS(); // accretor is (was) a recycled NS diff --git a/src/Mode b/src/Mode new file mode 100644 index 000000000..fbbdd8f76 --- /dev/null +++ b/src/Mode @@ -0,0 +1,495 @@ +BaseBinaryStar.cpp: bool sampled = OPTIONS->OptionSpecified("initial-mass-1") == 0 || +BaseBinaryStar.cpp: OPTIONS->OptionSpecified("initial-mass-2") == 0 || +BaseBinaryStar.cpp: (OPTIONS->OptionSpecified("metallicity") == 0 && OPTIONS->MetallicityDistribution() != METALLICITY_DISTRIBUTION::ZSOLAR) || +BaseBinaryStar.cpp: (OPTIONS->OptionSpecified("semi-major-axis") == 0 && OPTIONS->OptionSpecified("orbital-period") == 0) || +BaseBinaryStar.cpp: (OPTIONS->OptionSpecified("eccentricity") == 0 && OPTIONS->EccentricityDistribution() != ECCENTRICITY_DISTRIBUTION::ZERO); +BaseBinaryStar.cpp: kickParameters1.magnitudeRandomSpecified = OPTIONS->OptionSpecified("kick-magnitude-random-1") == 1; +BaseBinaryStar.cpp: kickParameters1.magnitudeRandom = OPTIONS->KickMagnitudeRandom1(); +BaseBinaryStar.cpp: kickParameters1.magnitudeSpecified = OPTIONS->OptionSpecified("kick-magnitude-1") == 1; +BaseBinaryStar.cpp: kickParameters1.magnitude = OPTIONS->KickMagnitude1(); +BaseBinaryStar.cpp: kickParameters1.phiSpecified = OPTIONS->OptionSpecified("kick-phi-1") == 1; +BaseBinaryStar.cpp: kickParameters1.phi = OPTIONS->SN_Phi1(); +BaseBinaryStar.cpp: kickParameters1.thetaSpecified = OPTIONS->OptionSpecified("kick-theta-1") == 1; +BaseBinaryStar.cpp: kickParameters1.theta = OPTIONS->SN_Theta1(); +BaseBinaryStar.cpp: kickParameters1.meanAnomalySpecified = OPTIONS->OptionSpecified("kick-mean-anomaly-1") == 1; +BaseBinaryStar.cpp: kickParameters1.meanAnomaly = OPTIONS->SN_MeanAnomaly1(); +BaseBinaryStar.cpp: kickParameters2.magnitudeRandomSpecified = OPTIONS->OptionSpecified("kick-magnitude-random-2") == 1; +BaseBinaryStar.cpp: kickParameters2.magnitudeRandom = OPTIONS->KickMagnitudeRandom2(); +BaseBinaryStar.cpp: kickParameters2.magnitudeSpecified = OPTIONS->OptionSpecified("kick-magnitude-2") == 1; +BaseBinaryStar.cpp: kickParameters2.magnitude = OPTIONS->KickMagnitude2(); +BaseBinaryStar.cpp: kickParameters2.phiSpecified = OPTIONS->OptionSpecified("kick-phi-2") == 1; +BaseBinaryStar.cpp: kickParameters2.phi = OPTIONS->SN_Phi2(); +BaseBinaryStar.cpp: kickParameters2.thetaSpecified = OPTIONS->OptionSpecified("kick-theta-2") == 1; +BaseBinaryStar.cpp: kickParameters2.theta = OPTIONS->SN_Theta2(); +BaseBinaryStar.cpp: kickParameters2.meanAnomalySpecified = OPTIONS->OptionSpecified("kick-mean-anomaly-2") == 1; +BaseBinaryStar.cpp: kickParameters2.meanAnomaly = OPTIONS->SN_MeanAnomaly2(); +BaseBinaryStar.cpp: double mass1 = OPTIONS->OptionSpecified("initial-mass-1") == 1 // user specified primary mass? +BaseBinaryStar.cpp: ? OPTIONS->InitialMass1() // yes, use it +BaseBinaryStar.cpp: : utils::SampleInitialMass(OPTIONS->InitialMassFunction(), +BaseBinaryStar.cpp: OPTIONS->InitialMassFunctionMax(), +BaseBinaryStar.cpp: OPTIONS->InitialMassFunctionMin(), +BaseBinaryStar.cpp: OPTIONS->InitialMassFunctionPower()); // no - asmple it +BaseBinaryStar.cpp: if (OPTIONS->OptionSpecified("initial-mass-2") == 1) { // user specified secondary mass? +BaseBinaryStar.cpp: mass2 = OPTIONS->InitialMass2(); // yes, use it +BaseBinaryStar.cpp: double q = OPTIONS->OptionSpecified("mass-ratio") == 1 // user specified mass ratio? +BaseBinaryStar.cpp: ? OPTIONS->MassRatio() // yes, use it +BaseBinaryStar.cpp: : utils::SampleMassRatio(OPTIONS->MassRatioDistribution(), +BaseBinaryStar.cpp: OPTIONS->MassRatioDistributionMax(), +BaseBinaryStar.cpp: OPTIONS->MassRatioDistributionMin()); // no - sample it +BaseBinaryStar.cpp: double metallicity = OPTIONS->OptionSpecified("metallicity") == 1 // user specified metallicity? +BaseBinaryStar.cpp: ? OPTIONS->Metallicity() // yes, use it +BaseBinaryStar.cpp: : utils::SampleMetallicity(OPTIONS->MetallicityDistribution(), +BaseBinaryStar.cpp: OPTIONS->MetallicityDistributionMax(), +BaseBinaryStar.cpp: OPTIONS->MetallicityDistributionMin()); // no, sample it +BaseBinaryStar.cpp: if (OPTIONS->OptionSpecified("semi-major-axis") == 1) { // user specified semi-major axis? +BaseBinaryStar.cpp: m_SemiMajorAxis = OPTIONS->SemiMajorAxis(); // yes, use it +BaseBinaryStar.cpp: if (OPTIONS->OptionSpecified("orbital-period") == 1) { // user specified orbital period? +BaseBinaryStar.cpp: m_SemiMajorAxis = utils::ConvertPeriodInDaysToSemiMajorAxisInAU(mass1, mass2, OPTIONS->OrbitalPeriod()); // yes - calculate semi-major axis from period +BaseBinaryStar.cpp: if (OPTIONS->OptionSpecified("semi-major-axis-distribution") == 1 || // user specified semi-major axis distribution, or +BaseBinaryStar.cpp: OPTIONS->OptionSpecified("orbital-period-distribution" ) == 0) { // user did not specify oprbital period distribution +BaseBinaryStar.cpp: m_SemiMajorAxis = utils::SampleSemiMajorAxis(OPTIONS->SemiMajorAxisDistribution(), +BaseBinaryStar.cpp: OPTIONS->SemiMajorAxisDistributionMax(), +BaseBinaryStar.cpp: OPTIONS->SemiMajorAxisDistributionMin(), +BaseBinaryStar.cpp: OPTIONS->SemiMajorAxisDistributionPower(), +BaseBinaryStar.cpp: OPTIONS->OrbitalPeriodDistributionMax(), +BaseBinaryStar.cpp: OPTIONS->OrbitalPeriodDistributionMin(), +BaseBinaryStar.cpp: double orbitalPeriod = utils::SampleOrbitalPeriod(OPTIONS->OrbitalPeriodDistribution(), +BaseBinaryStar.cpp: OPTIONS->OrbitalPeriodDistributionMax(), +BaseBinaryStar.cpp: OPTIONS->OrbitalPeriodDistributionMin()); +BaseBinaryStar.cpp: m_Eccentricity = OPTIONS->OptionSpecified("eccentricity") == 1 // user specified eccentricity? +BaseBinaryStar.cpp: ? OPTIONS->Eccentricity() // yes, use it +BaseBinaryStar.cpp: : utils::SampleEccentricity(OPTIONS->EccentricityDistribution(), +BaseBinaryStar.cpp: OPTIONS->EccentricityDistributionMax(), +BaseBinaryStar.cpp: OPTIONS->EccentricityDistributionMin()); // no, sample it +BaseBinaryStar.cpp: m_Star1 = OPTIONS->OptionSpecified("rotational-frequency-1") == 1 // user specified primary rotational frequency? +BaseBinaryStar.cpp: ? new BinaryConstituentStar(m_RandomSeed, mass1, metallicity, kickParameters1, OPTIONS->RotationalFrequency1() * SECONDS_IN_YEAR) // yes - use it (convert from Hz to cycles per year - see BaseStar::CalculateZAMSAngularFrequency()) +BaseBinaryStar.cpp: m_Star2 = OPTIONS->OptionSpecified("rotational-frequency-2") == 1 // user specified secondary rotational frequency? +BaseBinaryStar.cpp: ? new BinaryConstituentStar(m_RandomSeed, mass2, metallicity, kickParameters2, OPTIONS->RotationalFrequency2() * SECONDS_IN_YEAR) // yes - use it (convert from Hz to cycles per year - see BaseStar::CalculateZAMSAngularFrequency()) +BaseBinaryStar.cpp: if (rlof && OPTIONS->AllowRLOFAtBirth()) { // over-contact binaries at birth allowed? +BaseBinaryStar.cpp: m_Star1 = OPTIONS->OptionSpecified("rotational-frequency-1") == 1 // user specified primary rotational frequency? +BaseBinaryStar.cpp: ? new BinaryConstituentStar(m_RandomSeed, mass1, metallicity, kickParameters1, OPTIONS->RotationalFrequency1() * SECONDS_IN_YEAR) // yes - use it (convert from Hz to cycles per year - see BaseStar::CalculateZAMSAngularFrequency()) +BaseBinaryStar.cpp: m_Star2 = OPTIONS->OptionSpecified("rotational-frequency-2") == 1 // user specified secondary rotational frequency? +BaseBinaryStar.cpp: ? new BinaryConstituentStar(m_RandomSeed, mass2, metallicity, kickParameters2, OPTIONS->RotationalFrequency2() * SECONDS_IN_YEAR) // yes - use it (convert from Hz to cycles per year - see BaseStar::CalculateZAMSAngularFrequency()) +BaseBinaryStar.cpp: secondarySmallerThanMinimumMass = utils::Compare(mass2, OPTIONS->MinimumMassSecondary()) < 0; +BaseBinaryStar.cpp: bool ok = !((!OPTIONS->AllowRLOFAtBirth() && rlof) || (!OPTIONS->AllowTouchingAtBirth() && merger) || secondarySmallerThanMinimumMass); +BaseBinaryStar.cpp: if (OPTIONS->PopulationDataPrinting()) { // user wants to see details of binary? +BaseBinaryStar.cpp: if (OPTIONS->CHEMode() != CHE_MODE::NONE) { // CHE enabled? +BaseBinaryStar.cpp: m_JLoss = OPTIONS->MassTransferJloss(); +BaseBinaryStar.cpp: m_FractionAccreted = OPTIONS->MassTransferFractionAccreted(); +BaseBinaryStar.cpp: std::tie(ok, value) = OPTIONS->OptionValue(p_Property); // get the value +BaseBinaryStar.cpp: if (!OPTIONS->RLOFPrinting()) return ok; // do not print if printing option off +BaseBinaryStar.cpp: if (OPTIONS->HMXRBinaries()) { +BaseBinaryStar.cpp: if (!OPTIONS->BeBinaries()) return true; // do not print if printing option off +BaseBinaryStar.cpp: if (!OPTIONS->RLOFPrinting()) return; // nothing to do +BaseBinaryStar.cpp: if (!OPTIONS->BeBinaries() || !IsBeBinary()) return; // nothing to do; +BaseBinaryStar.cpp: double alphaCE = OPTIONS->CommonEnvelopeAlpha(); // CE efficiency parameter +BaseBinaryStar.cpp: if (OPTIONS->AllowMainSequenceStarToSurviveCommonEnvelope()) { // allow main sequence stars to survive CEE? +BaseBinaryStar.cpp: if (OPTIONS->CommonEnvelopeFormalism() == CE_FORMALISM::ENERGY) { +BaseBinaryStar.cpp: else if ( OPTIONS->CommonEnvelopeFormalism() == CE_FORMALISM::TWO_STAGE ) { +BaseBinaryStar.cpp: if (!OPTIONS->AllowRadiativeEnvelopeStarToSurviveCommonEnvelope()) { // stellar merger +BaseBinaryStar.cpp: if (OPTIONS->CHEMode() != CHE_MODE::NONE) m_Star1->SetOmega(omega); +BaseBinaryStar.cpp: if (OPTIONS->CHEMode() != CHE_MODE::NONE) m_Star2->SetOmega(omega); +BaseBinaryStar.cpp: if (m_RLOFDetails.immediateRLOFPostCEE == true && !OPTIONS->AllowImmediateRLOFpostCEToSurviveCommonEnvelope()) { // is there immediate post-CE RLOF which is not allowed? +BaseBinaryStar.cpp: if (!(m_Star1->IsOneOf(MAIN_SEQUENCE) && m_Star2->IsOneOf(MAIN_SEQUENCE) && OPTIONS->EvolveMainSequenceMergers())) +BaseBinaryStar.cpp: switch (OPTIONS->MassTransferAngularMomentumLossPrescription()) { // which precription? +BaseBinaryStar.cpp: ? OPTIONS->MassTransferJlossMacLeodLinearFractionDegen() +BaseBinaryStar.cpp: : OPTIONS->MassTransferJlossMacLeodLinearFractionNonDegen(); +BaseBinaryStar.cpp: case MT_ANGULAR_MOMENTUM_LOSS_PRESCRIPTION::ARBITRARY : gamma = OPTIONS->MassTransferJloss(); break; +BaseBinaryStar.cpp: if (OPTIONS->UseMassTransfer() && m_MassTransfer) { +BaseBinaryStar.cpp: if (OPTIONS->UseMassLoss()) { // mass loss enabled? +BaseBinaryStar.cpp: if (!OPTIONS->UseMassTransfer()) return; // mass transfer not enabled - nothing to do +BaseBinaryStar.cpp: if (OPTIONS->CHEMode() != CHE_MODE::NONE && HasTwoOf({STELLAR_TYPE::CHEMICALLY_HOMOGENEOUS}) && HasStarsTouching()) { // CHE enabled and both stars CH? +BaseBinaryStar.cpp: if (OPTIONS->MassTransferAngularMomentumLossPrescription() != MT_ANGULAR_MOMENTUM_LOSS_PRESCRIPTION::ARBITRARY) { // arbitrary angular momentum loss prescription? +BaseBinaryStar.cpp: bool caseBBAlwaysStable = OPTIONS->CaseBBStabilityPrescription() == CASE_BB_STABILITY_PRESCRIPTION::ALWAYS_STABLE; +BaseBinaryStar.cpp: bool caseBBAlwaysUnstable = OPTIONS->CaseBBStabilityPrescription() == CASE_BB_STABILITY_PRESCRIPTION::ALWAYS_UNSTABLE; +BaseBinaryStar.cpp: bool caseBBAlwaysUnstableOntoNSBH = OPTIONS->CaseBBStabilityPrescription() == CASE_BB_STABILITY_PRESCRIPTION::ALWAYS_STABLE_ONTO_NSBH; +BaseBinaryStar.cpp: else if (OPTIONS->QCritPrescription() != QCRIT_PRESCRIPTION::NONE) { // Determine stability based on critical mass ratios +BaseBinaryStar.cpp: if ((!m_CEDetails.CEEnow || OPTIONS->NeutronStarAccretionInCE() != NS_ACCRETION_IN_CE::ZERO) && m_Accretor->IsOneOf({ STELLAR_TYPE::NEUTRON_STAR })) { +BaseBinaryStar.cpp: if (OPTIONS->CHEMode() != CHE_MODE::NONE && HasTwoOf({STELLAR_TYPE::CHEMICALLY_HOMOGENEOUS})) { // CHE enabled and both stars CH? +BaseBinaryStar.cpp: if (OPTIONS->CirculariseBinaryDuringMassTransfer()) { // circularise binary to the periapsis separation? +BaseBinaryStar.cpp: m_SemiMajorAxis *= OPTIONS->AngularMomentumConservationDuringCircularisation() // yes - conserve angular momentum? +BaseBinaryStar.cpp: if (OPTIONS->CHEMode() != CHE_MODE::NONE) m_Star1->SetOmega(omega); +BaseBinaryStar.cpp: if (OPTIONS->CHEMode() != CHE_MODE::NONE) m_Star2->SetOmega(omega); +BaseBinaryStar.cpp: !(OPTIONS->CHEMode() != CHE_MODE::NONE && HasTwoOf({STELLAR_TYPE::CHEMICALLY_HOMOGENEOUS}))) { // yes - avoid CEE if CH+CH +BaseBinaryStar.cpp: if (!m_Unbound && OPTIONS->TidesPrescription() != TIDES_PRESCRIPTION::NONE) { +BaseBinaryStar.cpp: if (OPTIONS->TidesPrescription() == TIDES_PRESCRIPTION::KAPIL2024) { +BaseBinaryStar.cpp: if (OPTIONS->PopulationDataPrinting()) { +BaseBinaryStar.cpp: if (!OPTIONS->TimestepsFileName().empty()) { // have timesteps filename? +BaseBinaryStar.cpp: std::tie(error, timesteps) = utils::ReadTimesteps(OPTIONS->TimestepsFileName()); // read timesteps from file +BaseBinaryStar.cpp: dt = std::min(m_Star1->CalculateTimestep(), m_Star2->CalculateTimestep()) * OPTIONS->TimestepMultiplier() / 1000.0; // calculate timestep - make first step small +BaseBinaryStar.cpp: else if (!OPTIONS->EvolveDoubleWhiteDwarfs() && IsWDandWD()) { // double WD and their evolution is not enabled? +BaseBinaryStar.cpp: else if (HasOneOf({ STELLAR_TYPE::MASSLESS_REMNANT }) && !OPTIONS->EvolveMainSequenceMergers()) { // at least one massless remnant and not evolving MS merger products? +BaseBinaryStar.cpp: if (m_Star1->IsOneOf(MAIN_SEQUENCE) && m_Star2->IsOneOf(MAIN_SEQUENCE) && OPTIONS->EvolveMainSequenceMergers()) // yes - both MS and evolving MS merger products? +BaseBinaryStar.cpp: else if (IsUnbound() && !OPTIONS->EvolveUnboundSystems()) { // binary is unbound and we don't want unbound systems? +BaseBinaryStar.cpp: if (OPTIONS->RLOFPrinting()) StashRLOFProperties(MASS_TRANSFER_TIMING::PRE_MT); // stash properties immediately pre-Mass Transfer +BaseBinaryStar.cpp: if (m_Star1->IsOneOf(MAIN_SEQUENCE) && m_Star2->IsOneOf(MAIN_SEQUENCE) && OPTIONS->EvolveMainSequenceMergers()) // yes - both MS and evolving MS merger products? +BaseBinaryStar.cpp: if (!OPTIONS->EvolveUnboundSystems() || IsDCO()) { // should we evolve unbound systems? +BaseBinaryStar.cpp: if (!(OPTIONS->EvolvePulsars() && HasOneOf({ STELLAR_TYPE::NEUTRON_STAR }))) { // evolve pulsar? +BaseBinaryStar.cpp: else if (m_Time > OPTIONS->MaxEvolutionTime()) { // evolution time exceeds maximum? +BaseBinaryStar.cpp: else if (!OPTIONS->EvolveDoubleWhiteDwarfs() && IsWDandWD()) { // double WD and their evolution is not enabled? +BaseBinaryStar.cpp: else if (HasOneOf({ STELLAR_TYPE::MASSLESS_REMNANT }) && !OPTIONS->EvolveMainSequenceMergers()) { // at least one massless remnant and not evolving MS merger products? +BaseBinaryStar.cpp: if (m_Star1->IsOneOf(MAIN_SEQUENCE) && m_Star2->IsOneOf(MAIN_SEQUENCE) && OPTIONS->EvolveMainSequenceMergers()) // yes - both MS and evolving MS merger products? +BaseBinaryStar.cpp: else if (IsUnbound() && !OPTIONS->EvolveUnboundSystems()) { // binary is unbound and we don't want unbound systems? +BaseBinaryStar.cpp: if (stepNum >= OPTIONS->MaxNumberOfTimestepIterations()) evolutionStatus = EVOLUTION_STATUS::STEPS_UP; // number of timesteps for evolution exceeds maximum +BaseBinaryStar.cpp: dt = std::min(m_Star1->CalculateTimestep(), m_Star2->CalculateTimestep()) * OPTIONS->TimestepMultiplier(); // calculate new timestep +BaseBinaryStar.h: return OPTIONS->SwitchLog() ? // switch logging enabled? +BaseBinaryStar.h: return OPTIONS->DetailedOutput() ? LOGGING->LogBSEDetailedOutput(this, p_Id, p_RecordType) : true; +BaseBinaryStar.h: return OPTIONS->EvolvePulsars() ? LOGGING->LogBSEPulsarEvolutionParameters(this, p_RecordType) : true; +BaseBinaryStar.h: return (OPTIONS->RocketKickMagnitude1() > 0) || (OPTIONS->RocketKickMagnitude2() > 0); +BaseStar.cpp: m_BaryonicMassOfMaximumNeutronStarMass = (0.075 * OPTIONS->MaximumNeutronStarMass() * OPTIONS->MaximumNeutronStarMass()) + OPTIONS->MaximumNeutronStarMass(); +BaseStar.cpp: std::tie(ok, value) = OPTIONS->OptionValue(p_Property); +BaseStar.cpp: if (OPTIONS->CommonEnvelopeLambdaNanjingUseRejuvenatedMass()) mass = m_Mass0; // Use rejuvenated mass to calculate lambda instead of true birth mass +BaseStar.cpp: if (OPTIONS->CommonEnvelopeLambdaNanjingEnhanced()) { // If using enhanced Nanjing lambdas +BaseStar.cpp: if (OPTIONS->CommonEnvelopeLambdaNanjingInterpolateInMass()) { +BaseStar.cpp: if (OPTIONS->CommonEnvelopeLambdaNanjingInterpolateInMetallicity()) { +BaseStar.cpp: if (OPTIONS->CommonEnvelopeLambdaNanjingInterpolateInMetallicity()) { +BaseStar.cpp: ZETA_PRESCRIPTION zetaPrescription = OPTIONS->StellarZetaPrescription(); +BaseStar.cpp: QCRIT_PRESCRIPTION qCritPrescription = OPTIONS->QCritPrescription(); +BaseStar.cpp: m_Lambdas.fixed = OPTIONS->CommonEnvelopeLambda(); +BaseStar.cpp: m_Lambdas.kruckow = CalculateLambdaKruckow(m_Radius, OPTIONS->CommonEnvelopeSlopeKruckow()); +BaseStar.cpp: switch (OPTIONS->MassTransferRejuvenationPrescription()) { // which prescription +BaseStar.cpp: return OPTIONS->LuminousBlueVariableFactor() * 1.0E-4; +BaseStar.cpp: rate = OPTIONS->WolfRayetFactor() * 1.0E-13 * PPOW(m_Luminosity, 1.5) * PPOW(m_Metallicity / ZSOL, 0.86) * (1.0 - p_Mu); +BaseStar.cpp: Mdot = PPOW(10.0, logMdot) * OPTIONS->WolfRayetFactor(); +BaseStar.cpp: double LBVRate = CalculateMassLossRateLBV(OPTIONS->LuminousBlueVariablePrescription()); // start with LBV winds (can be, and is often, 0.0) +BaseStar.cpp: OPTIONS->LuminousBlueVariablePrescription() == LBV_PRESCRIPTION::HURLEY_ADD ) { // check whether we should add other winds to the LBV winds (always for HURLEY_ADD prescription, only if not in LBV regime for others) +BaseStar.cpp: otherWindsRate = CalculateMassLossRateHurley() * OPTIONS->CoolWindMassLossMultiplier(); // Apply cool wind mass loss multiplier +BaseStar.cpp: double LBVRate = CalculateMassLossRateLBV(OPTIONS->LuminousBlueVariablePrescription()); // start with LBV winds (can be, and is often, 0.0) +BaseStar.cpp: OPTIONS->LuminousBlueVariablePrescription() == LBV_PRESCRIPTION::HURLEY_ADD ) { // check whether we should add other winds to the LBV winds (always for HURLEY_ADD prescription, only if not in LBV regime for others) +BaseStar.cpp: otherWindsRate = CalculateMassLossRateRSG(OPTIONS->RSGMassLoss()); +BaseStar.cpp: otherWindsRate = CalculateMassLossRateHurley() * OPTIONS->CoolWindMassLossMultiplier(); // apply cool wind mass loss multiplier +BaseStar.cpp: otherWindsRate = CalculateMassLossRateVMS(OPTIONS->VMSMassLoss()); +BaseStar.cpp: otherWindsRate = CalculateMassLossRateOB(OPTIONS->OBMassLoss()); +BaseStar.cpp: * Calls relevant mass loss function based on mass loss prescription given in program options (OPTIONS->massLossPrescription) +BaseStar.cpp: if (OPTIONS->UseMassLoss()) { +BaseStar.cpp: switch (OPTIONS->MassLossPrescription()) { // which prescription? +BaseStar.cpp: mDot = mDot * OPTIONS->OverallWindMassLossMultiplier(); // apply overall wind mass loss multiplier +BaseStar.cpp: if (OPTIONS->UseMassLoss()) { // only if using mass loss (program option) +BaseStar.cpp: if (OPTIONS->CheckPhotonTiringLimit()) { +BaseStar.cpp: if (OPTIONS->UseMassLoss()) { +BaseStar.cpp: switch (OPTIONS->MassTransferAccretionEfficiencyPrescription()) { +BaseStar.cpp: acceptanceRate = min(OPTIONS->MassTransferCParameter() * p_AccretorMassRate, p_DonorMassRate); +BaseStar.cpp: fractionAccreted = OPTIONS->MassTransferFractionAccreted(); +BaseStar.cpp: switch( OPTIONS->MassTransferThermallyLimitedVariation() ) { +BaseStar.cpp: switch (OPTIONS->RotationalVelocityDistribution()) { // which prescription? +BaseStar.cpp: if (utils::Compare(p_RemnantMass, OPTIONS->MaximumNeutronStarMass()) < 0) { +BaseStar.cpp: muKick = max(OPTIONS->MullerMandelKickMultiplierNS() * (p_COCoreMass - p_RemnantMass) / p_RemnantMass, 0.0); +BaseStar.cpp: muKick = max(OPTIONS->MullerMandelKickMultiplierBH() * (p_COCoreMass - p_RemnantMass) / p_RemnantMass, 0.0); +BaseStar.cpp: remnantKick = muKick * (1.0 + gsl_cdf_gaussian_Pinv(rand, OPTIONS->MullerMandelSigmaKick())); +BaseStar.cpp: switch (OPTIONS->KickMagnitudeDistribution()) { // which distribution +BaseStar.cpp: kickMagnitude = DrawKickMagnitudeDistributionFlat(OPTIONS->KickMagnitudeDistributionMaximum(), p_Rand); +BaseStar.cpp: return kickMagnitude / OPTIONS->KickScalingFactor(); +BaseStar.cpp: sigma = OPTIONS->KickMagnitudeDistributionSigmaForECSN(); +BaseStar.cpp: sigma = OPTIONS->KickMagnitudeDistributionSigmaForUSSN(); +BaseStar.cpp: if( utils::SNEventType(m_SupernovaDetails.events.current) == SN_EVENT::PPISN && !OPTIONS->NatalKickForPPISN() ) { +BaseStar.cpp: sigma = OPTIONS->KickMagnitudeDistributionSigmaCCSN_NS(); +BaseStar.cpp: sigma = OPTIONS->KickMagnitudeDistributionSigmaCCSN_BH(); +BaseStar.cpp: if( OPTIONS->RadialChangeFraction()!=0 && radialExpansionTimescale > 0.0 ) // if radial expansion timescale was computed +BaseStar.cpp: dt = min(dt, OPTIONS->RadialChangeFraction()*radialExpansionTimescale); +BaseStar.cpp: if( OPTIONS->MassChangeFraction()!=0 && massChangeTimescale > 0.0 ) // if mass change timescale was computed +BaseStar.cpp: dt = min(dt, OPTIONS->MassChangeFraction()*massChangeTimescale); +BaseStar.h: return OPTIONS->DetailedOutput() ? LOGGING->LogSSEDetailedOutput(this, p_Id, p_RecordType) : true; // Write record to SSE Detailed Output log file +BaseStar.h: return OPTIONS->SwitchLog() ? (LOGGING->ObjectSwitchingPersistence() == OBJECT_PERSISTENCE::PERMANENT ? LOGGING->LogSSESwitchLog(this) : true) : true; // Write record to SSE Switchlog log file +BaseStar.h: virtual double CalculateEddingtonCriticalRate() const { return 2.08E-3 / 1.7 * m_Radius * MYR_TO_YEAR * OPTIONS->EddingtonAccretionFactor() ; } // Hurley+, 2002, Eq. (67) +BH.cpp: switch (OPTIONS->NeutrinoMassLossAssumptionBH()) { // which assumption? +BH.cpp: gravitationalMass = p_BaryonicMass * (1.0 - OPTIONS->NeutrinoMassLossValueBH()); +BH.cpp: gravitationalMass = p_BaryonicMass - OPTIONS->NeutrinoMassLossValueBH(); +BH.cpp: switch (OPTIONS->BlackHoleKicks()) { // which BH kicks option specified? +BinaryConstituentStar.cpp: switch (OPTIONS->CommonEnvelopeMassAccretionPrescription()) { // which prescription? +BinaryConstituentStar.cpp: deltaMass = OPTIONS->CommonEnvelopeMassAccretionConstant(); // use program option +BinaryConstituentStar.cpp: deltaMass = RAND->Random(OPTIONS->CommonEnvelopeMassAccretionMin(), OPTIONS->CommonEnvelopeMassAccretionMax()); // uniform random distribution - Oslowski+ (2011) +BinaryConstituentStar.cpp: deltaMass = std::min(OPTIONS->CommonEnvelopeMassAccretionMax(), std::max(OPTIONS->CommonEnvelopeMassAccretionMin(), m * p_CompanionRadius + c)); +BinaryConstituentStar.cpp: switch (OPTIONS->CommonEnvelopeLambdaPrescription()) { // which common envelope lambda prescription? +BinaryConstituentStar.cpp: m_CEDetails.lambda *= OPTIONS->CommonEnvelopeLambdaMultiplier(); // multiply by constant (program option, default = 1.0) +BinaryConstituentStar.cpp: if ( OPTIONS->CommonEnvelopeFormalism() == CE_FORMALISM::TWO_STAGE ) +changelog.h:// - removed redundant OPTIONS->MassTransferCriticalMassRatioHeliumGiant() from qcritflag if statement in BaseBinaryStar::CalculateMassTransfer() +changelog.h:// - fixed OPTIONS->FixedMetallicity() - always returned true, now returns actual value +changelog.h:// - fixed OPTIONS->OutputPathString() - was always returning raw option instead of fully qualified path +changelog.h:// (a) m_JLoss = OPTIONS->MassTransferJloss(); +changelog.h:// (b) m_FractionAccreted = OPTIONS->MassTransferFractionAccreted(); +changelog.h:// - added OPTIONS->ZetaAdiabaticArbitrary() - option existed, but Options code had no function to retrieve value +changelog.h:// - added OPTIONS->MassTransferFractionAccreted() to options - erroneously not ported from legacy code +changelog.h:// - added ProgramOptionDetails() to Options.cpp and OPTIONS->OptionsDetails() in preparation for change in output functionality +changelog.h:// - fixed issue #162 OPTIONS->UseFixedUK() always returns FALSE. Now returns TRUE if user supplies a fixed kick velocity via --fix-dimensionless-kick-velocity command line option +changelog.h:// - OPTIONS->UseFixedUK() returns TRUE when user supplies -ve value via --fix-dimensionless-kick-velocity. Now return TRUE iff the user supplies a value >=0 via --fix-dimensionless-kick-velocity +changelog.h:// - Removed m_LBVfactor variable from BaseStar - use OPTIONS->LuminousBlueVariableFactor() +changelog.h:// - Removed m_LBVfactor variable from BaseStar - use OPTIONS->WolfRayetFactor() +changelog.h:// - Removed variable 'alpha' from BinaryCEDetails struct - use OPTIONS->CommonEnvelopeAlpha() +changelog.h:// - removed the fixed constant MULLERMANDEL_MAXNS; instead, OPTIONS->MaximumNeutronStarMass() is used for consistency (see issue #1114) +CHeB.cpp: return (OPTIONS->CommonEnvelopeAlphaThermal() * lambdaBG[0]) + ((1.0 - OPTIONS->CommonEnvelopeAlphaThermal()) * lambdaBG[1]); +CHeB.cpp: return (OPTIONS->CommonEnvelopeAlphaThermal() * lambdaBG[0]) + ((1.0 - OPTIONS->CommonEnvelopeAlphaThermal()) * lambdaBG[1]); +CHeB.cpp: switch (OPTIONS->EnvelopeStatePrescription()) { // which envelope prescription? +CHeB.cpp: envelope = utils::Compare(Temperature() * TSOL, OPTIONS->ConvectiveEnvelopeTemperatureThreshold()) > 0 ? ENVELOPE::RADIATIVE : ENVELOPE::CONVECTIVE; // Envelope is radiative if temperature exceeds fixed threshold, otherwise convective +CHeB.h: bool ShouldEnvelopeBeExpelledByPulsations() const { return ( OPTIONS->ExpelConvectiveEnvelopeAboveLuminosityThreshold() && DetermineEnvelopeType() == ENVELOPE::CONVECTIVE && utils::Compare( log10(m_Luminosity/m_Mass), OPTIONS->LuminosityToMassThreshold() ) >= 0 ) ; } // Envelope of convective star with luminosity to mass ratio beyond threshold should be expelled +CH.h: bool ShouldEvolveOnPhase() const { return m_Age < m_Timescales[static_cast(TIMESCALE::tMS)] && (OPTIONS->OptimisticCHE() || m_Omega >= m_OmegaCHE); } // Evolve on CHE phase if age in MS timescale and spinning at least as fast as CHE threshold +EAGB.cpp: return (OPTIONS->CommonEnvelopeAlphaThermal() * lambdaBG[0]) + ((1.0 - OPTIONS->CommonEnvelopeAlphaThermal()) * lambdaBG[1]); +EAGB.cpp: return (OPTIONS->CommonEnvelopeAlphaThermal() * lambdaBG[0]) + ((1.0 - OPTIONS->CommonEnvelopeAlphaThermal()) * lambdaBG[1]); +Errors.cpp: if (p_Prefix == WARNING_PREFIX && !OPTIONS->EnableWarnings()) return false; // do nothing +GiantBranch.cpp: qCrit = OPTIONS->MassTransferCriticalMassRatioGiantDegenerateAccretor(); +GiantBranch.cpp: qCrit = OPTIONS->MassTransferCriticalMassRatioGiantNonDegenerateAccretor(); +GiantBranch.cpp: zeta = OPTIONS->ZetaAdiabaticArbitrary(); +GiantBranch.cpp: zeta = OPTIONS->ZetaRadiativeEnvelopeGiant(); +GiantBranch.cpp: if (utils::Compare(p_COCoreMass, MULLERMANDEL_M1) < 0 || utils::Compare(p_HeCoreMass, OPTIONS->MaximumNeutronStarMass()) <= 0 ) { +GiantBranch.cpp: while (remnantMassMaximumNeutronStarMass() || remnantMass > p_HeCoreMass) { +GiantBranch.cpp: while (remnantMass < MULLERMANDEL_MINNS || remnantMass > OPTIONS->MaximumNeutronStarMass() || remnantMass > p_HeCoreMass) { +GiantBranch.cpp: while (remnantMass < MULLERMANDEL_MINNS || remnantMass > OPTIONS->MaximumNeutronStarMass() || remnantMass > p_HeCoreMass) { +GiantBranch.cpp: while (remnantMass < MULLERMANDEL_MINNS || remnantMass > OPTIONS->MaximumNeutronStarMass() || remnantMass > p_HeCoreMass) { +GiantBranch.cpp: switch (OPTIONS->FryerSupernovaEngine()) { // which SN_ENGINE? +GiantBranch.cpp: baryonicRemnantMass = 1.2 + 0.05 * OPTIONS->Fryer22fmix() + 0.01 * pow( (p_COCoreMass/OPTIONS->Fryer22fmix()), 2.0) + exp( OPTIONS->Fryer22fmix() * (p_COCoreMass - OPTIONS->Fryer22Mcrit()) ) ; // equation 5. +GiantBranch.cpp: switch (OPTIONS->FryerSupernovaEngine()) { // which SN_ENGINE? +GiantBranch.cpp: switch (OPTIONS->RemnantMassPrescription()) { // which prescription? +GiantBranch.cpp: if (OPTIONS->RemnantMassPrescription() == REMNANT_MASS_PRESCRIPTION::MULLER2016) { +GiantBranch.cpp: else if (OPTIONS->RemnantMassPrescription() == REMNANT_MASS_PRESCRIPTION::MULLERMANDEL) { +GiantBranch.cpp: if (utils::Compare(m_Mass, OPTIONS->MaximumNeutronStarMass() ) > 0) +GiantBranch.cpp: else if (OPTIONS->RemnantMassPrescription() == REMNANT_MASS_PRESCRIPTION::HURLEY2000) { +GiantBranch.cpp: else if (utils::Compare(m_Mass, OPTIONS->MaximumNeutronStarMass()) > 0) { +GiantBranch.cpp: if (!m_MassTransferDonorHistory.empty() || (OPTIONS->AllowNonStrippedECSN())) { // If progenitor has never been a MT donor, is it allowed to ECSN? +GiantBranch.cpp: switch (OPTIONS->PulsationalPairInstabilityPrescription()) { // which prescription? +GiantBranch.cpp: if ( OPTIONS->UsePulsationalPairInstability() && +GiantBranch.cpp: utils::Compare(m_HeCoreMass, OPTIONS->PulsationalPairInstabilityLowerLimit()) >= 0 && +GiantBranch.cpp: utils::Compare(m_HeCoreMass, OPTIONS->PulsationalPairInstabilityUpperLimit()) <= 0) { // Pulsational Pair Instability Supernova +GiantBranch.cpp: else if ( OPTIONS->UsePairInstabilitySupernovae() && +GiantBranch.cpp: utils::Compare(m_HeCoreMass, OPTIONS->PairInstabilityLowerLimit()) >= 0 && +GiantBranch.cpp: utils::Compare(m_HeCoreMass, OPTIONS->PairInstabilityUpperLimit()) <= 0) { // Pair Instability Supernova +GiantBranch.cpp: if (OPTIONS->EvolutionMode() == EVOLUTION_MODE::SSE && m_ObjectPersistence == OBJECT_PERSISTENCE::PERMANENT) { +HeGB.cpp: ? OPTIONS->MassTransferCriticalMassRatioHeliumGiantDegenerateAccretor() // degenerate accretor +HeGB.cpp: : OPTIONS->MassTransferCriticalMassRatioHeliumGiantNonDegenerateAccretor(); // non-degenerate accretor +HeHG.cpp: switch (OPTIONS->MassTransferRejuvenationPrescription()) { // which rejuvenation prescription? +HeHG.cpp: switch (OPTIONS->EnvelopeStatePrescription()) { // which envelope prescription? +HeHG.cpp: envelope = utils::Compare(Temperature() * TSOL, OPTIONS->ConvectiveEnvelopeTemperatureThreshold()) > 0 ? ENVELOPE::RADIATIVE : ENVELOPE::CONVECTIVE; // Envelope is radiative if temperature exceeds fixed threshold, otherwise convective +HeHG.cpp: ? OPTIONS->MassTransferCriticalMassRatioHeliumHGDegenerateAccretor() // degenerate accretor +HeHG.cpp: : OPTIONS->MassTransferCriticalMassRatioHeliumHGNonDegenerateAccretor(); // non-degenerate accretor +HeHG.cpp: stellarType = (utils::Compare(m_COCoreMass, OPTIONS->MCBUR1() ) < 0) ? STELLAR_TYPE::CARBON_OXYGEN_WHITE_DWARF : STELLAR_TYPE::OXYGEN_NEON_WHITE_DWARF; +HeMS.cpp: switch (OPTIONS->MassTransferRejuvenationPrescription()) { +HeMS.cpp: if (OPTIONS->WRMassLoss() == WR_MASS_LOSS::SANDERVINK2023) { +HeMS.cpp: else if (OPTIONS->WRMassLoss() == WR_MASS_LOSS::SHENAR2019) { +HeMS.cpp: ? OPTIONS->MassTransferCriticalMassRatioHeliumMSDegenerateAccretor() // degenerate accretor +HeMS.cpp: : OPTIONS->MassTransferCriticalMassRatioHeliumMSNonDegenerateAccretor(); // non-degenerate accretor +HeMS.h: double CalculateZetaConstantsByEnvelope(ZETA_PRESCRIPTION p_ZetaPrescription) { return OPTIONS->ZetaMainSequence(); } // A HeMS star is treated as any other MS star for Zeta calculation purposes +HG.cpp: return (OPTIONS->CommonEnvelopeAlphaThermal() * lambdaBG[0]) + ((1.0 - OPTIONS->CommonEnvelopeAlphaThermal()) * lambdaBG[1]); +HG.cpp: return (OPTIONS->CommonEnvelopeAlphaThermal() * lambdaBG[0]) + ((1.0 - OPTIONS->CommonEnvelopeAlphaThermal()) * lambdaBG[1]); +HG.cpp: switch (OPTIONS->MassTransferRejuvenationPrescription()) { // which rejuvenation prescription? +HG.cpp: ? OPTIONS->MassTransferCriticalMassRatioHGDegenerateAccretor() // degenerate accretor +HG.cpp: : OPTIONS->MassTransferCriticalMassRatioHGNonDegenerateAccretor(); // non-degenerate accretor +HG.cpp: switch (OPTIONS->EnvelopeStatePrescription()) { // which envelope prescription? +HG.cpp: envelope = utils::Compare(Temperature() * TSOL, OPTIONS->ConvectiveEnvelopeTemperatureThreshold()) > 0 ? ENVELOPE::RADIATIVE : ENVELOPE::CONVECTIVE; // Envelope is radiative if temperature exceeds fixed threshold, otherwise convective +Log.cpp: size_t IOBufSize = OPTIONS->HDF5BufferSize() * chunkSize; // IO buffer size +Log.cpp: m_OptionDetails = OPTIONS->CmdLineOptionsDetails(); // get commandline option details +Log.cpp: string fileExt = "." + LOGFILETYPEFileExt.at(OPTIONS->LogfileType()); // file extension for HDF5 files +Log.cpp: if (OPTIONS->StoreInputFiles()) { // user wants input files stored in output container? +Log.cpp: if (!OPTIONS->GridFilename().empty()) { // user specified a grid file? +Log.cpp: boost::filesystem::path srcPath(OPTIONS->GridFilename()); // grid file fully-qualified name +Log.cpp: (void)boost::filesystem::copy_file(OPTIONS->GridFilename(), dstFn, BOOST_OVERWRITE_EXISTING); // copy grid file - overwrite any existing file (shouldn't be one, but just in case we want this one) +Log.cpp: Squawk("ERROR: Unable to copy grid file " + OPTIONS->GridFilename() + " to output container " + dstPath); // announce error +Log.cpp: if (m_Enabled && !OPTIONS->LogfileDefinitionsFilename().empty()) { // user specified a logfile-definitions file? +Log.cpp: boost::filesystem::path srcPath(OPTIONS->LogfileDefinitionsFilename()); // logfile-definitions file fully-qualified name +Log.cpp: (void)boost::filesystem::copy_file(OPTIONS->LogfileDefinitionsFilename(), dstFn, BOOST_OVERWRITE_EXISTING); // copy logfile-definitions file - overwrite any existing file (shouldn't be one, but just in case we want this one) +Log.cpp: Squawk("ERROR: Unable to copy logfile-definitions file " + OPTIONS->LogfileDefinitionsFilename() + " to output container " + dstPath); // announce error +Log.cpp: unsigned long int actualRandomSeed = OPTIONS->FixedRandomSeedCmdLine() ? OPTIONS->RandomSeedCmdLine() : RAND->DefaultSeed(); // actual random seed used +Log.cpp: if (objectsRequested >= 0 && (int)OPTIONS->nObjectsToEvolve() == objectsRequested) derivation = "USER_SUPPLIED"; // should be right most of the time (not critical) +Log.cpp: if (OPTIONS->PrintBoolAsString()) +Log.cpp: if (OPTIONS->EvolutionMode() == EVOLUTION_MODE::SSE) +Log.cpp: if (OPTIONS->EvolutionMode() == EVOLUTION_MODE::SSE) { +Log.cpp: string fileext = LOGFILETYPEFileExt.at(OPTIONS->LogfileType()); // file extension +Log.cpp: details = std::make_tuple(TYPENAME::STRING, OPTIONS->NotesHdrs(p_Idx), "-", 0, 1); // yes - construct details +Log.cpp: if ((OPTIONS->AddOptionsToSysParms() == ADD_OPTIONS_TO_SYSPARMS::ALWAYS) || // always add option columns? +Log.cpp: ((OPTIONS->AddOptionsToSysParms() == ADD_OPTIONS_TO_SYSPARMS::GRID) && // add for grids? +Log.cpp: (!OPTIONS->GridFilename().empty() || OPTIONS->CommandLineGrid()))) { // have grid file or ranges/sets? +Log.cpp: if ((OPTIONS->AddOptionsToSysParms() == ADD_OPTIONS_TO_SYSPARMS::ALWAYS) || // always add option columns? +Log.cpp: ((OPTIONS->AddOptionsToSysParms() == ADD_OPTIONS_TO_SYSPARMS::GRID) && // add for grids? +Log.cpp: (!OPTIONS->GridFilename().empty() || OPTIONS->CommandLineGrid()))) { // have grid file or ranges/sets? +Log.cpp: if (OPTIONS->PrintBoolAsString()) { // print bool values as strings "TRUE" or "FALSE"? +Log.cpp: fileDetails.filename = OPTIONS->LogfileBeBinaries(); +Log.cpp: fileDetails.recordTypes = OPTIONS->LogfileBeBinariesRecordTypes(); +Log.cpp: fileDetails.filename = OPTIONS->LogfileCommonEnvelopes(); +Log.cpp: fileDetails.recordTypes = OPTIONS->LogfileCommonEnvelopesRecordTypes(); +Log.cpp: fileDetails.filename = OPTIONS->LogfileDoubleCompactObjects(); +Log.cpp: fileDetails.recordTypes = OPTIONS->LogfileDoubleCompactObjectsRecordTypes(); +Log.cpp: fileDetails.filename = OPTIONS->LogfilePulsarEvolution(); +Log.cpp: fileDetails.recordTypes = OPTIONS->LogfilePulsarEvolutionRecordTypes(); +Log.cpp: fileDetails.filename = OPTIONS->LogfileRLOFParameters(); +Log.cpp: fileDetails.recordTypes = OPTIONS->LogfileRLOFParametersRecordTypes(); +Log.cpp: fileDetails.filename = OPTIONS->LogfileSupernovae(); +Log.cpp: fileDetails.recordTypes = OPTIONS->LogfileSupernovaeRecordTypes(); +Log.cpp: fileDetails.filename = OPTIONS->LogfileSwitchLog(); +Log.cpp: fileDetails.filename = OPTIONS->LogfileSystemParameters(); +Log.cpp: fileDetails.recordTypes = OPTIONS->LogfileSystemParametersRecordTypes(); +Log.cpp: if ((OPTIONS->AddOptionsToSysParms() == ADD_OPTIONS_TO_SYSPARMS::ALWAYS) || // always add option columns? +Log.cpp: ((OPTIONS->AddOptionsToSysParms() == ADD_OPTIONS_TO_SYSPARMS::GRID) && // add for grids? +Log.cpp: (!OPTIONS->GridFilename().empty() || OPTIONS->CommandLineGrid()))) { // have grid file or ranges/sets? +Log.cpp: fileDetails.filename = OPTIONS->LogfileSupernovae(); +Log.cpp: fileDetails.recordTypes = OPTIONS->LogfileSupernovaeRecordTypes(); +Log.cpp: fileDetails.filename = OPTIONS->LogfileSwitchLog(); +Log.cpp: fileDetails.filename = OPTIONS->LogfileSystemParameters(); +Log.cpp: fileDetails.recordTypes = OPTIONS->LogfileSystemParametersRecordTypes(); +Log.cpp: if ((OPTIONS->AddOptionsToSysParms() == ADD_OPTIONS_TO_SYSPARMS::ALWAYS) || // always add option columns? +Log.cpp: ((OPTIONS->AddOptionsToSysParms() == ADD_OPTIONS_TO_SYSPARMS::GRID) && // add for grids? +Log.cpp: (!OPTIONS->GridFilename().empty() || OPTIONS->CommandLineGrid()))) { // have grid file or ranges/sets? +Log.cpp: fileDetails.filename = DETAILED_OUTPUT_DIRECTORY_NAME + "/" + OPTIONS->LogfileDetailedOutput(); // logfile filename with directory +Log.cpp: fileDetails.recordTypes = OPTIONS->LogfileDetailedOutputRecordTypes(); // record types +Log.cpp: string fileExt = "." + LOGFILETYPEFileExt.at(OPTIONS->LogfileType()); // file extension for HDF5 files +Log.cpp: // program option NOTES is special - the property is actually a vector of strings (OPTIONS->Notes()), +Log.cpp: if (OPTIONS->LogfileType() == LOGFILETYPE::HDF5) { // logging to HDF5 files? +Log.cpp: size_t chunkSize = OPTIONS->nObjectsToEvolve() < HDF5_MINIMUM_CHUNK_SIZE || +Log.cpp: p_Logfile == LOGFILE::BSE_DETAILED_OUTPUT ? HDF5_MINIMUM_CHUNK_SIZE : OPTIONS->HDF5ChunkSize(); // chunk size +Log.cpp: size_t IOBufSize = OPTIONS->HDF5BufferSize() * chunkSize; // IO buffer size +Log.cpp: string filename = OPTIONS->LogfileDefinitionsFilename(); // get user-specified definitions file +Log.cpp: std::vector addNotes = std::vector(OPTIONS->NotesHdrs().size(), false); // annotations (notes) user wants added to the base properties +Log.cpp: std::vector subtractNotes = std::vector(OPTIONS->NotesHdrs().size(), false); // annotations (notes) user wants subtracted from the base properties +Log.cpp: addNotes = std::vector(OPTIONS->NotesHdrs().size(), false); // start with no annotations to be added +Log.cpp: subtractNotes = std::vector(OPTIONS->NotesHdrs().size(), false); // start with no annotations to be subtracted +Log.cpp: if (notesIdx < 1 or notesIdx > (int)OPTIONS->NotesHdrs().size()) { // index in valid range? +Log.h: string fmt = OPTIONS->PrintBoolAsString() ? "%5s" : "%1s"; +Log.h: string vS = OPTIONS->PrintBoolAsString() ? (v ? "TRUE " : "FALSE") : (v ? "1" : "0"); +Log.h: string fmt = OPTIONS->PrintBoolAsString() ? "%5s" : "%1s"; +Log.h: string vS = OPTIONS->PrintBoolAsString() ? (v ? "TRUE " : "FALSE") : (v ? "1" : "0"); +Log.h: // user-specified option '--notes-hdrs' (OPTIONS->NotesHdrs()) +Log.h: std::vector m_BSE_BE_Binaries_Notes = std::vector(OPTIONS->NotesHdrs().size(), false); +Log.h: std::vector m_BSE_CEE_Notes = std::vector(OPTIONS->NotesHdrs().size(), false); +Log.h: std::vector m_BSE_DCO_Notes = std::vector(OPTIONS->NotesHdrs().size(), false); +Log.h: std::vector m_BSE_Detailed_Notes = std::vector(OPTIONS->NotesHdrs().size(), false); +Log.h: std::vector m_BSE_Pulsars_Notes = std::vector(OPTIONS->NotesHdrs().size(), false); +Log.h: std::vector m_BSE_RLOF_Notes = std::vector(OPTIONS->NotesHdrs().size(), false); +Log.h: std::vector m_BSE_SNE_Notes = std::vector(OPTIONS->NotesHdrs().size(), false); +Log.h: std::vector m_BSE_Switch_Notes = std::vector(OPTIONS->NotesHdrs().size(), false); +Log.h: std::vector m_BSE_SysParms_Notes = std::vector(OPTIONS->NotesHdrs().size(), false); +Log.h: std::vector m_SSE_Detailed_Notes = std::vector(OPTIONS->NotesHdrs().size(), false); +Log.h: std::vector m_SSE_SNE_Notes = std::vector(OPTIONS->NotesHdrs().size(), false); +Log.h: std::vector m_SSE_Switch_Notes = std::vector(OPTIONS->NotesHdrs().size(), false); +Log.h: std::vector m_SSE_SysParms_Notes = std::vector(OPTIONS->NotesHdrs().size(), false); +Log.h: bool hdf5 = OPTIONS->LogfileType() == LOGFILETYPE::HDF5; // logging to hdf5 file? +Log.h: switch (OPTIONS->LogfileType()) { +Log.h: value = boost::variant(OPTIONS->Notes(idx)); // yes - get value +Log.h: if (OPTIONS->LogfileType() == LOGFILETYPE::HDF5) // logging to HDF5 file? +Log.h: if (OPTIONS->LogfileType() == LOGFILETYPE::HDF5) { // logging to HDF5 file? +main.cpp: * The signal is raised in the Star::SwitchTo() function if OPTIONS->BSESwitchLog() +main.cpp: if (evolvingBinaryStarValid && OPTIONS->SwitchLog()) { // yes - do we have a valid binary star, and are we logging switches? +main.cpp: bool usingGrid = !OPTIONS->GridFilename().empty(); // using grid file? +main.cpp: // OPTIONS->AdvanceCmdLineOptionValues(), called at the end of the loop, advances the +main.cpp: int gridResult = OPTIONS->ApplyNextGridLine(); // set options according to specified values in grid file +main.cpp: SHOW_ERROR(error, "Accessing grid file '" + OPTIONS->GridFilename() + "'"); // show error +main.cpp: ERROR error = OPTIONS->RewindGridFile(); // ready for next commandline options variation +main.cpp: SHOW_ERROR(error, "Accessing grid file '" + OPTIONS->GridFilename() + "'"); // no - show error (should never happen here - should be picked up at file open) +main.cpp: SHOW_ERROR(ERROR::ERROR, "Accessing grid file '" + OPTIONS->GridFilename() + "'"); // unexpected error - show error +main.cpp: // (by OPTIONS->ApplyNextGridLine()). +main.cpp: // OPTIONS->AdvanceGridLineOptionValues(), called at the end of the loop, advances the +main.cpp: if (OPTIONS->FixedRandomSeedGridLine()) { // user specified a random seed in the grid file for this binary? +main.cpp: randomSeed = OPTIONS->RandomSeedGridLine() + (unsigned long int)gridLineVariation; // random seed +main.cpp: if (OPTIONS->SetRandomSeed(randomSeed, optsOrigin) < 0) { // ok? +main.cpp: else if (OPTIONS->FixedRandomSeedCmdLine()) { // no - user specified a random seed on the commandline? +main.cpp: randomSeed = OPTIONS->RandomSeedCmdLine() + (unsigned long int)index; // random seed +main.cpp: if (OPTIONS->SetRandomSeed(randomSeed, optsOrigin) < 0) { // ok? +main.cpp: if (OPTIONS->SetRandomSeed(randomSeed, optsOrigin) < 0) { // ok? +main.cpp: double initialMass = OPTIONS->OptionSpecified("initial-mass") == 1 // user specified mass? +main.cpp: ? OPTIONS->InitialMass() // yes, use it +main.cpp: : utils::SampleInitialMass(OPTIONS->InitialMassFunction(), // no, sample it +main.cpp: OPTIONS->InitialMassFunctionMax(), +main.cpp: OPTIONS->InitialMassFunctionMin(), +main.cpp: OPTIONS->InitialMassFunctionPower()); +main.cpp: double metallicity = OPTIONS->OptionSpecified("metallicity") == 1 // user specified metallicity? +main.cpp: ? OPTIONS->Metallicity() // yes, use it +main.cpp: : utils::SampleMetallicity(OPTIONS->MetallicityDistribution(), +main.cpp: OPTIONS->MetallicityDistributionMax(), +main.cpp: OPTIONS->MetallicityDistributionMin()); // no, sample it +main.cpp: kickParameters.magnitudeRandomSpecified = OPTIONS->OptionSpecified("kick-magnitude-random") == 1; +main.cpp: kickParameters.magnitudeRandom = OPTIONS->KickMagnitudeRandom(); +main.cpp: kickParameters.magnitudeSpecified = OPTIONS->OptionSpecified("kick-magnitude") == 1; +main.cpp: kickParameters.magnitude = OPTIONS->KickMagnitude(); +main.cpp: star = OPTIONS->OptionSpecified("rotational-frequency") == 1 // user specified rotational frequency? +main.cpp: ? new Star(randomSeed, initialMass, metallicity, kickParameters, OPTIONS->RotationalFrequency() * SECONDS_IN_YEAR) // yes - use it (convert from Hz to cycles per year - see BaseStar::CalculateZAMSAngularFrequency()) +main.cpp: if (!OPTIONS->Quiet()) { // quiet mode? +main.cpp: int optionsStatus = OPTIONS->AdvanceGridLineOptionValues(); // apply next grid file options (ranges/sets) +main.cpp: int optionsStatus = OPTIONS->AdvanceCmdLineOptionValues(); // yes - apply next commandline options (ranges/sets) +main.cpp: if (usingGrid || OPTIONS->CommandLineGrid() || (!usingGrid && index >= OPTIONS->nObjectsToEvolve())) { // created required number of stars? +main.cpp: if (!OPTIONS->Quiet()) { +main.cpp: bool usingGrid = !OPTIONS->GridFilename().empty(); // using grid file? +main.cpp: // OPTIONS->AdvanceCmdLineOptionValues(), called at the end of the loop, advances the +main.cpp: int gridResult = OPTIONS->ApplyNextGridLine(); // yes - set options according to specified values in grid file +main.cpp: SHOW_ERROR(error, "Accessing grid file '" + OPTIONS->GridFilename() + "'"); // show error +main.cpp: ERROR error = OPTIONS->RewindGridFile(); // ready for next commandline options variation +main.cpp: SHOW_ERROR(error, "Accessing grid file '" + OPTIONS->GridFilename() + "'"); // no - show error (should never happen here - should be picked up at file open) +main.cpp: SHOW_ERROR(ERROR::ERROR, "Accessing grid file '" + OPTIONS->GridFilename() + "'"); // unexpected error - show error +main.cpp: // (by OPTIONS->ApplyNextGridLine()). +main.cpp: // OPTIONS->AdvanceGridLineOptionValues(), called at the end of the loop, advances the +main.cpp: if (OPTIONS->FixedRandomSeedGridLine()) { // user specified a random seed in the grid file for this binary? +main.cpp: randomSeed = OPTIONS->RandomSeedGridLine() + (unsigned long int)gridLineVariation; // random seed +main.cpp: if (OPTIONS->SetRandomSeed(randomSeed, optsOrigin) < 0) { // ok? +main.cpp: else if (OPTIONS->FixedRandomSeedCmdLine()) { // no - user specified a random seed on the commandline? +main.cpp: randomSeed = OPTIONS->RandomSeedCmdLine() + (unsigned long int)index + (unsigned long int)gridLineVariation; // random seed +main.cpp: if (OPTIONS->SetRandomSeed(randomSeed, optsOrigin) < 0) { // ok? +main.cpp: if (OPTIONS->SetRandomSeed(randomSeed, optsOrigin) < 0) { // ok? +main.cpp: if (!OPTIONS->Quiet()) { // quiet mode? +main.cpp: if (OPTIONS->CHEMode() == CHE_MODE::NONE) { // CHE enabled? +main.cpp: int optionsStatus = OPTIONS->AdvanceGridLineOptionValues(); // apply next grid file options (ranges/sets) +main.cpp: int optionsStatus = OPTIONS->AdvanceCmdLineOptionValues(); // apply next commandline options (ranges/sets) +main.cpp: if (usingGrid || OPTIONS->CommandLineGrid() || (!usingGrid && index >= OPTIONS->nObjectsToEvolve())) { // created required number of stars? +main.cpp: if (!OPTIONS->Quiet()) { +main.cpp: bool ok = OPTIONS->Initialise(argc, argv); // get the program options from the commandline +main.cpp: if (OPTIONS->RequestedHelp()) { // user requested help? +main.cpp: OPTIONS->ShowHelp(); // show help +main.cpp: else if (OPTIONS->RequestedVersion()) { // user requested version? +main.cpp: else if (!OPTIONS->YAMLfilename().empty()) { // user requested YAML file creation? +main.cpp: yaml::MakeYAMLfile(OPTIONS->YAMLfilename(), OPTIONS->YAMLtemplate()); // create YAML file +main.cpp: LOGGING->Start(OPTIONS->OutputPathString(), // location of logfiles +main.cpp: OPTIONS->OutputContainerName(), // directory to be created for logfiles +main.cpp: OPTIONS->LogfileNamePrefix(), // prefix for logfile names +main.cpp: OPTIONS->LogLevel(), // log level - determines (in part) what is written to log file +main.cpp: OPTIONS->LogClasses(), // log classes - determines (in part) what is written to log file +main.cpp: OPTIONS->DebugLevel(), // debug level - determines (in part) what debug information is displayed +main.cpp: OPTIONS->DebugClasses(), // debug classes - determines (in part) what debug information is displayed +main.cpp: OPTIONS->DebugToFile(), // should debug statements also be written to logfile? +main.cpp: OPTIONS->ErrorsToFile(), // should error messages also be written to logfile? +main.cpp: OPTIONS->LogfileType()); // log file type +main.cpp: if (!OPTIONS->GridFilename().empty()) { // have grid filename? +main.cpp: ERROR error = OPTIONS->OpenGridFile(OPTIONS->GridFilename()); // yes - open grid file +main.cpp: SHOW_ERROR(error, "Accessing grid file '" + OPTIONS->GridFilename() + "'"); // no - show error +main.cpp: if (OPTIONS->EvolutionMode() == EVOLUTION_MODE::SSE) { // SSE? +main.cpp: if (!OPTIONS->GridFilename().empty()) { // have grid filename? +main.cpp: OPTIONS->CloseGridFile(); // yes - close it if it's open +MainSequence.cpp: if (OPTIONS->RetainCoreMassDuringCaseAMassTransfer()) { +MainSequence.h: double CalculateCoreMassAtPhaseEnd() const { return OPTIONS->RetainCoreMassDuringCaseAMassTransfer() ? MinimumCoreMass() : 0.0; } // Accounts for minimal core mass built up prior to mass loss through mass transfer +MainSequence.h: double CalculateZetaConstantsByEnvelope(ZETA_PRESCRIPTION p_ZetaPrescription) { return OPTIONS->ZetaMainSequence(); } +MS_gt_07.cpp: switch (OPTIONS->MassTransferRejuvenationPrescription()) { // which prescription? +MS_gt_07.cpp: ? OPTIONS->MassTransferCriticalMassRatioMSHighMassDegenerateAccretor() // degenerate accretor +MS_gt_07.cpp: : OPTIONS->MassTransferCriticalMassRatioMSHighMassNonDegenerateAccretor(); // non-degenerate accretor +MS_gt_07.cpp: switch (OPTIONS->EnvelopeStatePrescription()) { // which envelope prescription? +MS_gt_07.cpp: envelope = utils::Compare(Temperature() * TSOL, OPTIONS->ConvectiveEnvelopeTemperatureThreshold()) > 0 ? ENVELOPE::RADIATIVE : ENVELOPE::CONVECTIVE; // Envelope is radiative if temperature exceeds fixed threshold, otherwise convective +MS_lte_07.cpp: switch (OPTIONS->MassTransferRejuvenationPrescription()) { // which prescription? +MS_lte_07.cpp: ? OPTIONS->MassTransferCriticalMassRatioMSLowMassDegenerateAccretor() // degenerate accretor +MS_lte_07.cpp: : OPTIONS->MassTransferCriticalMassRatioMSLowMassNonDegenerateAccretor(); // non-degenerate accretor +NS.cpp: switch (OPTIONS->NeutronStarEquationOfState()) { // which equation-of-state? +NS.cpp: switch (OPTIONS->PulsarBirthSpinPeriodDistribution()) { // which distribution? +NS.cpp: double maximum = OPTIONS->PulsarBirthSpinPeriodDistributionMax(); +NS.cpp: double minimum = OPTIONS->PulsarBirthSpinPeriodDistributionMin(); +NS.cpp: switch (OPTIONS->PulsarBirthMagneticFieldDistribution()) { // which distribution? +NS.cpp: double maximum = OPTIONS->PulsarBirthMagneticFieldDistributionMax(); +NS.cpp: double minimum = OPTIONS->PulsarBirthMagneticFieldDistributionMin(); +NS.cpp: double maximum = PPOW(10.0, OPTIONS->PulsarBirthMagneticFieldDistributionMax()); +NS.cpp: double minimum = PPOW(10.0, OPTIONS->PulsarBirthMagneticFieldDistributionMin()); +NS.cpp: double magFieldLowerLimit = PPOW(10.0, OPTIONS->PulsarLog10MinimumMagneticField()) * GAUSS_TO_TESLA; +NS.cpp: double tau = OPTIONS->PulsarMagneticFieldDecayTimescale() * MYR_TO_YEAR * SECONDS_IN_YEAR; +NS.cpp: double magFieldLowerLimit_G = PPOW(10.0, OPTIONS->PulsarLog10MinimumMagneticField()) ; // convert to Gauss +NS.cpp: switch (OPTIONS->NeutronStarAccretionInCE()) { // which mode of CE accretion to use? +NS.cpp: double kappa = OPTIONS->PulsarMagneticFieldDecayMassscale() * MSOL_TO_G; +NS.cpp: double magFieldLowerLimit_G = PPOW(10.0, OPTIONS->PulsarLog10MinimumMagneticField()) ; +NS.h: bool ShouldEvolveOnPhase() const { return (m_Mass <= OPTIONS->MaximumNeutronStarMass()); } // Evolve as a neutron star unless mass > maximum neutron star mass (e.g. through accretion) +Options.cpp: if (OPTIONS->OptionSpecified("grid-lines-to-process") == 1 && // user specified number of grid lines to process? +Options.cpp: m_Gridfile.startLine = OPTIONS->GridStartLine(); // set first line to process (0-based) +Options.cpp: m_Gridfile.linesToProcess = OPTIONS->GridLinesToProcess(); // set number of lines to process (-1 = process to EOF) +Star.cpp: if (OPTIONS->CHEMode() != CHE_MODE::NONE && utils::Compare(m_Star->Omega(), m_Star->OmegaCHE()) >= 0) { // CHE? +Star.cpp: if (utils::IsOneOf(stellarTypePrev, EVOLVABLE_TYPES) && OPTIONS->SwitchLog()) { // star should be evolving from one of the evolvable types (We don't want the initial switch from Star->MS. Not necessary for BSE (handled differently), but no harm) +Star.cpp: if (OPTIONS->EvolutionMode() == EVOLUTION_MODE::BSE) { // BSE? +Star.cpp: if (!OPTIONS->TimestepsFileName().empty()) { // have timesteps filename? +Star.cpp: std::tie(error, timesteps) = utils::ReadTimesteps(OPTIONS->TimestepsFileName()); // read timesteps from file +Star.cpp: if (m_Star->Time() > OPTIONS->MaxEvolutionTime()) { +Star.cpp: else if (stepNum >= OPTIONS->MaxNumberOfTimestepIterations()) { +Star.cpp: dt = m_Star->CalculateTimestep() * OPTIONS->TimestepMultiplier(); // calculate new timestep +TPAGB.cpp: return (OPTIONS->CommonEnvelopeAlphaThermal() * lambdaBG[0]) + ((1.0 - OPTIONS->CommonEnvelopeAlphaThermal()) * lambdaBG[1]); +TPAGB.cpp: return (OPTIONS->CommonEnvelopeAlphaThermal() * lambdaBG[0]) + ((1.0 - OPTIONS->CommonEnvelopeAlphaThermal()) * lambdaBG[1]); +TPAGB.cpp: stellarType = (utils::Compare(gbParams(McBAGB), OPTIONS->MCBUR1() ) < 0) ? STELLAR_TYPE::CARBON_OXYGEN_WHITE_DWARF : STELLAR_TYPE::OXYGEN_NEON_WHITE_DWARF; +TPAGB.h: bool IsSupernova() const { return (utils::Compare(m_COCoreMass, m_GBParams[static_cast(GBP::McSN)]) >= 0 && utils::Compare(CalculateInitialSupernovaMass(), OPTIONS->MCBUR1()) >= 0 && utils::Compare(m_COCoreMass, m_Mass) < 0); } // Going supernova if still has envelope and core mass large enough +typedefs.h: std::vector annotations; // print flags for each annotation specified by the user (e.g. OPTIONS->NotesHdrs()) +typedefs.h: double fixed; // Set to OPTIONS->commonEnvelopeLambda +typedefs.h: double kruckow; // Calculated using m_Radius and OPTIONS->commonEnvelopeSlopeKruckow +typedefs.h: double fixed; // Calculated using lambda = OPTIONS->commonEnvelopeLambda +yaml.cpp: std::vector optionDetails = OPTIONS->CmdLineOptionsDetails(); diff --git a/src/NS.cpp b/src/NS.cpp index 1d1510c91..9ff270af3 100755 --- a/src/NS.cpp +++ b/src/NS.cpp @@ -417,7 +417,7 @@ void NS::SpinDownIsolatedPulsar(const double p_Stepsize) { /* * Calculate the magnetic field, spin, spin-down rate and angular momentum of a neutron star, * when it's accreting mass from companion through stable mass transfer. - * The calculations used in this function follows closely Sec. 2.2.1 in arxiv:1912.02415 + * The calculations used in this function follow closely Sec. 2.2.1 in arxiv:1912.02415 * We carry out the calculations in this function using cgs units. * This function is called in the NS::UpdateMagneticFieldAndSpin() function * @@ -442,8 +442,7 @@ DBL_DBL_DBL_DBL NS::PulsarAccretion(const double p_MagField, const double p_Spin double initialMagField_G = p_MagField * TESLA_TO_GAUSS; double magFieldLowerLimit_G = PPOW(10.0, OPTIONS->PulsarLog10MinimumMagneticField()) ; // convert to Gauss double mass_g = m_Mass * MSOL_TO_G; // in g - double r_cm = m_Radius * RSOL_TO_KM * KM_TO_CM; // NS radius in cm - double MoI = m_MomentOfInertia_CGS; + double r_cm = m_Radius * RSOL_TO_KM * KM_TO_CM; double angularMomentum = p_AngularMomentum; //Magnetic field decay due to mass transfer. @@ -452,26 +451,26 @@ DBL_DBL_DBL_DBL NS::PulsarAccretion(const double p_MagField, const double p_Spin // calculate the Alfven radius for an accreting neutron star // see Equation 10 in arxiv:1912.02415 - double mDot = p_MassGainPerTimeStep / p_Stepsize; - double R_CM_6 = r_cm * r_cm * r_cm * r_cm * r_cm * r_cm; - double p = R_CM_6 * R_CM_6 / (mass_g * mass_g * mDot); - double q = PPOW(p, 1.0/7.0); - double alfvenConst = PPOW(2 * PI_2 / G_CGS, 1.0/7.0) ; - double alfvenRadius = alfvenConst * q * PPOW(initialMagField_G, 4.0/7.0); - + double mDot = p_MassGainPerTimeStep / p_Stepsize; + double R_CM_6 = r_cm * r_cm * r_cm * r_cm * r_cm * r_cm; + double p = R_CM_6 * R_CM_6 / (mass_g * mass_g * mDot); + double q = PPOW(p, 1.0/7.0); + double alfvenConst = PPOW(2 * PI_2 / G_CGS, 1.0/7.0) ; + double alfvenRadius = alfvenConst * q * PPOW(initialMagField_G, 4.0/7.0); + double magneticRadius = alfvenRadius / 2.0; // calculate the difference in the keplerian angular velocity at the magnetic radius // and surface angular velocity of the neutron star // magnetic radius is half of alfven radius - // see Equation 2 in 1994MNRAS.269..455J / Equation 8 in arxiv:1912.02415 - double keplerianVelocityAtMagneticRadius = std::sqrt(4.0 * (G_CGS) * mass_g / alfvenRadius); - double keplerianAngularVelocityAtMagneticRadius = 2.0 * keplerianVelocityAtMagneticRadius / alfvenRadius; + // see Equation 2 in 1994MNRAS.269..455J / Equation 9 in arxiv:1912.02415 + double keplerianVelocityAtMagneticRadius = std::sqrt((G_CGS) * mass_g / magneticRadius); + double keplerianAngularVelocityAtMagneticRadius = keplerianVelocityAtMagneticRadius / magneticRadius; double omegaDifference = keplerianAngularVelocityAtMagneticRadius - p_SpinFrequency; // calculate the change in angular momentum due to accretion // see Equation 12 in arXiv:0805.0059/ Equation 8 in arxiv:1912.02415 - double Jdot = p_Epsilon * omegaDifference * alfvenRadius * alfvenRadius * mDot / 4.0; + double Jdot = p_Epsilon * omegaDifference * magneticRadius * magneticRadius * mDot; angularMomentum = angularMomentum + Jdot * p_Stepsize; - return std::make_tuple(newPulsarMagneticField * GAUSS_TO_TESLA, angularMomentum / MoI, Jdot / MoI, angularMomentum); + return std::make_tuple(newPulsarMagneticField * GAUSS_TO_TESLA, angularMomentum / m_MomentOfInertia_CGS, Jdot / m_MomentOfInertia_CGS, angularMomentum); } @@ -498,31 +497,6 @@ DBL_DBL_DBL_DBL NS::PulsarAccretion(const double p_MagField, const double p_Spin * @param [IN] p_Epsilon Efficiency factor allowing for uncertainties of coupling magnetic field and matter. */ void NS::UpdateMagneticFieldAndSpin(const bool p_CommonEnvelope, const bool p_RecycledNS, const double p_Stepsize, const double p_MassGainPerTimeStep, const double p_Epsilon) { - - int NSCE = -1; - - switch (OPTIONS->NeutronStarAccretionInCE()) { // which mode of CE accretion to use? - - case NS_ACCRETION_IN_CE::ZERO: // ZERO, no effect from CE - NSCE = 0; - break; - - case NS_ACCRETION_IN_CE::DISK: // DISK , CE effect same as a RLOF case - NSCE = 1; - break; - - case NS_ACCRETION_IN_CE::SURFACE: // SURFACE, mass are accreted onto the surface of the neutron star. - NSCE = 2; - break; - - default: // unknown distribution - SHOW_WARN_STATIC(ERROR::UNKNOWN_NS_ACCRETION_IN_CE, // show warning - "Using zero accretion", - OBJECT_TYPE::BASE_BINARY_STAR, - STELLAR_TYPE::NEUTRON_STAR); - NSCE = 0; - } - double kappa = OPTIONS->PulsarMagneticFieldDecayMassscale() * MSOL_TO_G; if ((!p_RecycledNS && !p_CommonEnvelope) || (p_RecycledNS && utils::Compare(p_MassGainPerTimeStep, 0.0) == 0 )) { @@ -535,57 +509,75 @@ void NS::UpdateMagneticFieldAndSpin(const bool p_CommonEnvelope, const bool p_Re // Note that we do not need to set hard lower limit on the spin period, // as propeller effect should be included in the calculation, // which means pulsars will start spinning down when the AM is no longer transferred along mass transfer. - if ((!p_CommonEnvelope) || (p_CommonEnvelope && (NSCE == 1))){ + if ((!p_CommonEnvelope) || (p_CommonEnvelope && OPTIONS->NeutronStarAccretionInCE() == NS_ACCRETION_IN_CE::DISK)){ // Pulsar evolution during stable mass transfer (RLOF), or if using the 'DISK' option // in the CE accretion option - std::tuple accretionResults = PulsarAccretion(m_PulsarDetails.magneticField, m_PulsarDetails.spinFrequency, m_AngularMomentum_CGS, p_Stepsize, p_MassGainPerTimeStep * 1000.0, kappa, p_Epsilon); + + double final_b=; + double final_p=; + double final_am=; + double final_pdot=; + x[0] = final_b; + x[1] = final_p; + x[2] = final_am; + x[3] = final_pdot; + + double divideTimestepBy = 100.0; + + controlled_stepper_type controlled_stepper; + state_type x(4); + + controlled_stepper.do_step(, x, t_start, dt); + + + // std::tuple accretionResults = PulsarAccretion(m_PulsarDetails.magneticField, m_PulsarDetails.spinFrequency, m_AngularMomentum_CGS, p_Stepsize, p_MassGainPerTimeStep / G_TO_KG, kappa, p_Epsilon); - int divideTimestepBy = 100; - bool done = false; - while (!done && utils::Compare(divideTimestepBy, 1000000) > 0.0) { + // double divideTimestepBy = 100.0; + // bool done = false; + // while (!done && utils::Compare(divideTimestepBy, 1000000) > 0.0) { - divideTimestepBy *= 2; + // divideTimestepBy *= 2.0; - double thisTimestepSize = p_Stepsize / divideTimestepBy; - double thisMassGain = p_MassGainPerTimeStep * 1000.0 / divideTimestepBy; - double B ; - double f ; - double fdot ; - double am ; - - std::tie(B, f, fdot, am) = PulsarAccretion(m_PulsarDetails.magneticField, m_PulsarDetails.spinFrequency, - m_AngularMomentum_CGS, thisTimestepSize, thisMassGain, kappa, p_Epsilon); - - int count = 0; - while (!done) { - std::tie(B, f, fdot, am) = PulsarAccretion(B, f, am, thisTimestepSize, thisMassGain, kappa, p_Epsilon); + // double thisTimestepSize = p_Stepsize / divideTimestepBy; + // double thisMassGain = p_MassGainPerTimeStep / G_TO_KG / divideTimestepBy; + // double B ; + // double f ; + // double fdot ; + // double am ; + + // std::tie(B, f, fdot, am) = PulsarAccretion(m_PulsarDetails.magneticField, m_PulsarDetails.spinFrequency, + // m_AngularMomentum_CGS, thisTimestepSize, thisMassGain, kappa, p_Epsilon); + + // int count = 0; + // while (!done) { + // std::tie(B, f, fdot, am) = PulsarAccretion(B, f, am, thisTimestepSize, thisMassGain, kappa, p_Epsilon); - if (utils::Compare(f, 0.0) < 0) break; - if (++count >= divideTimestepBy) done = true; - } - } + // if (utils::Compare(f, 0.0) < 0) break; + // if (++count >= divideTimestepBy) done = true; + // } + // } - double newTimeStepSize = p_Stepsize / divideTimestepBy; - double newMassGain = p_MassGainPerTimeStep * 1000.0 / divideTimestepBy ; - accretionResults = PulsarAccretion(m_PulsarDetails.magneticField, m_PulsarDetails.spinFrequency, - m_AngularMomentum_CGS, newTimeStepSize, newMassGain, kappa, p_Epsilon); - for (int n = 1; n<= int(divideTimestepBy); n++){ + // double newTimeStepSize = p_Stepsize / divideTimestepBy; + // double newMassGain = p_MassGainPerTimeStep / G_TO_KG / divideTimestepBy ; + // accretionResults = PulsarAccretion(m_PulsarDetails.magneticField, m_PulsarDetails.spinFrequency, + // m_AngularMomentum_CGS, newTimeStepSize, newMassGain, kappa, p_Epsilon); + // for (int n = 1; n<= int(divideTimestepBy); n++){ - double last_B_2 = std::get<0>(accretionResults); - double last_f_2 = std::get<1>(accretionResults); - double last_am_2 = std::get<3>(accretionResults); + // double last_B_2 = std::get<0>(accretionResults); + // double last_f_2 = std::get<1>(accretionResults); + // double last_am_2 = std::get<3>(accretionResults); - accretionResults = PulsarAccretion(last_B_2, last_f_2, last_am_2, newTimeStepSize, newMassGain, kappa, p_Epsilon); + // accretionResults = PulsarAccretion(last_B_2, last_f_2, last_am_2, newTimeStepSize, newMassGain, kappa, p_Epsilon); - m_PulsarDetails.magneticField = std::get<0>(accretionResults); - m_PulsarDetails.spinFrequency = std::get<1>(accretionResults); - m_AngularMomentum_CGS = std::get<3>(accretionResults); - m_PulsarDetails.spinDownRate = std::get<2>(accretionResults); - } + // m_PulsarDetails.magneticField = std::get<0>(accretionResults); + // m_PulsarDetails.spinFrequency = std::get<1>(accretionResults); + // m_AngularMomentum_CGS = std::get<3>(accretionResults); + // m_PulsarDetails.spinDownRate = std::get<2>(accretionResults); + // } } - else if ((p_CommonEnvelope) && (NSCE == 2)) { + else if (p_CommonEnvelope && OPTIONS->NeutronStarAccretionInCE() == NS_ACCRETION_IN_CE::SURFACE) { // Mass transfer through CE when accretion happens at the surface of the NS double initialMagField = m_PulsarDetails.magneticField; double initialMagField_G = initialMagField * TESLA_TO_GAUSS; @@ -594,22 +586,21 @@ void NS::UpdateMagneticFieldAndSpin(const bool p_CommonEnvelope, const bool p_Re double mass_g = m_Mass * MSOL_TO_G; // in g double r_cm = m_Radius * RSOL_TO_KM * KM_TO_CM; // in cm m_MomentOfInertia_CGS = CalculateMomentOfInertiaCGS() ; - double MoI = m_MomentOfInertia_CGS; - double angularMomentum = m_AngularMomentum_CGS; - double newPulsarMagneticField = (initialMagField_G - magFieldLowerLimit_G) * exp(-p_MassGainPerTimeStep * 1000.0 / kappa) + magFieldLowerLimit_G; + double newPulsarMagneticField = (initialMagField_G - magFieldLowerLimit_G) * exp(-p_MassGainPerTimeStep / G_TO_KG / kappa) + magFieldLowerLimit_G; double r_cm_3 = r_cm * r_cm * r_cm ; - double Jacc = MoI * PPOW(G_CGS * mass_g /r_cm_3, 0.5) * p_MassGainPerTimeStep * 1000.0 / mass_g ; + // angular momentum of the accreted materials as they fall onto the surface of the NS. + double Jacc = m_MomentOfInertia_CGS * PPOW(G_CGS * mass_g /r_cm_3, 0.5) * p_MassGainPerTimeStep / G_TO_KG / mass_g ; - m_AngularMomentum_CGS = angularMomentum + Jacc ; + m_AngularMomentum_CGS = m_AngularMomentum_CGS + Jacc ; m_PulsarDetails.magneticField = newPulsarMagneticField * GAUSS_TO_TESLA; - m_PulsarDetails.spinFrequency = m_AngularMomentum_CGS / MoI ; - m_PulsarDetails.spinDownRate = Jacc/ p_Stepsize /MoI; + m_PulsarDetails.spinFrequency = m_AngularMomentum_CGS / m_MomentOfInertia_CGS ; + m_PulsarDetails.spinDownRate = Jacc / p_Stepsize / m_MomentOfInertia_CGS; } - else if ((p_CommonEnvelope) && (NSCE == 0)) { - // If mass transfer is happening through CE and chosen 'ZERO' in CE accretion mode, - // treat the pulsar as isolated and should be spun down. - SpinDownIsolatedPulsar(p_Stepsize); + else if (p_CommonEnvelope && OPTIONS->NeutronStarAccretionInCE() == NS_ACCRETION_IN_CE::ZERO) { + // If mass transfer is happening through CE and chosen 'ZERO' in CE accretion mode, + // treat the pulsar as isolated and should be spun down. + SpinDownIsolatedPulsar(p_Stepsize); + } } } -} diff --git a/src/Options.cpp b/src/Options.cpp index 038aa9709..5a6d35da8 100644 --- a/src/Options.cpp +++ b/src/Options.cpp @@ -1811,9 +1811,9 @@ bool Options::AddOptions(OptionValues *p_Options, po::options_description *p_Opt ("Neutron star equation of state to use (" + AllowedOptionValuesFormatted("neutron-star-equation-of-state") + ", default = '" + p_Options->m_NeutronStarEquationOfState.typeString + "')").c_str() ) ( - "neutron-star-accretion-in-ce", + "neutron-star-accretion-in-CE", po::value(&p_Options->m_NSAccretionInCE.typeString)->default_value(p_Options->m_NSAccretionInCE.typeString), - ("Neutron star accretion in CE to use (" + AllowedOptionValuesFormatted("neutron-star-accretion-in-ce") + ", default = '" + p_Options->m_NSAccretionInCE.typeString + "')").c_str() + ("Neutron star accretion in CE to use (" + AllowedOptionValuesFormatted("neutron-star-accretion-in-CE") + ", default = '" + p_Options->m_NSAccretionInCE.typeString + "')").c_str() ) ( "OB-mass-loss", @@ -2558,7 +2558,7 @@ std::vector Options::AllowedOptionValues(const std::string p_Option case _("mode") : POPULATE_RET(EVOLUTION_MODE_LABEL); break; case _("neutrino-mass-loss-BH-formation") : POPULATE_RET(NEUTRINO_MASS_LOSS_PRESCRIPTION_LABEL); break; case _("neutron-star-equation-of-state") : POPULATE_RET(NS_EOSLabel); break; - case _("neutron-star-accretion-in-ce") : POPULATE_RET(NS_ACCRETION_IN_CELabel); break; + case _("neutron-star-accretion-in-CE") : POPULATE_RET(NS_ACCRETION_IN_CELabel); break; case _("OB-mass-loss") : POPULATE_RET(OB_MASS_LOSS_LABEL); break; case _("orbital-period-distribution") : POPULATE_RET(ORBITAL_PERIOD_DISTRIBUTION_LABEL); break; case _("pulsar-birth-magnetic-field-distribution") : POPULATE_RET(PULSAR_BIRTH_MAGNETIC_FIELD_DISTRIBUTION_LABEL); break; diff --git a/src/Options.h b/src/Options.h index fa405f5fb..550170da8 100755 --- a/src/Options.h +++ b/src/Options.h @@ -380,7 +380,7 @@ class Options { "maximum-mass-donor-nandez-ivanova", "minimum-secondary-mass", - "neutron-star-in-accretion-in-ce", + "neutron-star-accretion-in-ce", "orbital-period", "orbital-period-distribution", From c29472430e827983f44a17b245b01c02f34ae21a Mon Sep 17 00:00:00 2001 From: Yuzhe Song Date: Sat, 28 Sep 2024 16:02:15 +1000 Subject: [PATCH 18/19] trial for using BOOST ODE integrator in NS accretion --- src/NS.cpp | 174 ++++++++++++++++++++++++++++++++----------------- src/NS.h | 20 ++++-- src/typedefs.h | 1 + 3 files changed, 129 insertions(+), 66 deletions(-) diff --git a/src/NS.cpp b/src/NS.cpp index 9ff270af3..348231a89 100755 --- a/src/NS.cpp +++ b/src/NS.cpp @@ -1,6 +1,7 @@ #include "Rand.h" #include "NS.h" +#include "gsl/gsl_poly.h" /* * Calculate the luminosity of a Neutron Star @@ -457,7 +458,7 @@ DBL_DBL_DBL_DBL NS::PulsarAccretion(const double p_MagField, const double p_Spin double q = PPOW(p, 1.0/7.0); double alfvenConst = PPOW(2 * PI_2 / G_CGS, 1.0/7.0) ; double alfvenRadius = alfvenConst * q * PPOW(initialMagField_G, 4.0/7.0); - double magneticRadius = alfvenRadius / 2.0; + double magneticRadius = alfvenRadius / 2.0; // calculate the difference in the keplerian angular velocity at the magnetic radius // and surface angular velocity of the neutron star // magnetic radius is half of alfven radius @@ -474,6 +475,62 @@ DBL_DBL_DBL_DBL NS::PulsarAccretion(const double p_MagField, const double p_Spin } + +/* + * Calculate the magnetic field, spin, spin-down rate and angular momentum of a neutron star, + * when it's accreting mass from companion through stable mass transfer. + * The calculations used in this function follow closely Sec. 2.2.1 in arxiv:1912.02415 + * We carry out the calculations in this function using cgs units. + * This function is called in the NS::UpdateMagneticFieldAndSpin() function + * + * Returns the updated: + * dJ/dm: change in angular momentum per mass transferred. + * + * DBL_DBL_DBL_DBL PulsarAccretion(const double p_MagField, const double p_SpinFrequency, const double p_AngularMomentum, const double p_Stepsize, const double p_MassGainPerTimeStep, const double kappa, const double p_Epsilon) + * @param [IN] p_MagField NS magnetic field strength at the beginning of accretion (in Tesla) + * @param [IN] p_SpinFrequency Spin frequency for the NS at the beginning of accretion (in Hz) + * @param [IN] p_AngularMomentum Angular momentum of the NS at the beginning of accretion (g cm-2 / s) + * @param [IN] p_Stepsize Timestep size for integration (in seconds) + * @param [IN] p_MassGainPerTimeStep Mass transferred from the secondary for each iteration (in g) + * @param [IN] p_Kappa Magnetic field mass decay scale (in g) + * @param [IN] p_Epsilon Efficiency factor allowing for uncertainties of coupling magnetic field and matter. + * @return Tuple containing the updated magnetic field strength, spin frequency, spin-down rate and angular momentum of neutron star + */ +double NS::deltaAngularMomentumByPulsarAccretion(const double p_MassGainPerTimeStep, const double p_Mass, const double p_Radius, const double p_MagField, const double p_SpinFrequency, const double p_AngularMomentum, const double p_Stepsize, const double p_Kappa, const double p_Epsilon) { + double initialMagField_G = p_MagField * TESLA_TO_GAUSS; + double magFieldLowerLimit_G = PPOW(10.0, OPTIONS->PulsarLog10MinimumMagneticField()) ; // convert to Gauss + double mass_g = p_Mass * MSOL_TO_G; // in g + double r_cm = p_Radius * RSOL_TO_KM * KM_TO_CM; + double angularMomentum = p_AngularMomentum; + + //Magnetic field decay due to mass transfer. + //Follows Eq. 12 in arxiv:1912.02415 + double newPulsarMagneticField = (initialMagField_G - magFieldLowerLimit_G) * exp(-p_MassGainPerTimeStep / p_Kappa) + magFieldLowerLimit_G; + + // calculate the Alfven radius for an accreting neutron star + // see Equation 10 in arxiv:1912.02415 + double mDot = p_MassGainPerTimeStep / p_Stepsize; + double R_CM_6 = r_cm * r_cm * r_cm * r_cm * r_cm * r_cm; + double p = R_CM_6 * R_CM_6 / (mass_g * mass_g * mDot); + double q = PPOW(p, 1.0/7.0); + double alfvenConst = PPOW(2 * PI_2 / G_CGS, 1.0/7.0) ; + double alfvenRadius = alfvenConst * q * PPOW(initialMagField_G, 4.0/7.0); + double magneticRadius = alfvenRadius / 2.0; + // calculate the difference in the keplerian angular velocity at the magnetic radius + // and surface angular velocity of the neutron star + // magnetic radius is half of alfven radius + // see Equation 2 in 1994MNRAS.269..455J / Equation 9 in arxiv:1912.02415 + double keplerianVelocityAtMagneticRadius = std::sqrt((G_CGS) * mass_g / magneticRadius); + double keplerianAngularVelocityAtMagneticRadius = keplerianVelocityAtMagneticRadius / magneticRadius; + double omegaDifference = keplerianAngularVelocityAtMagneticRadius - p_SpinFrequency; + + // calculate the change in angular momentum due to accretion + // see Equation 12 in arXiv:0805.0059/ Equation 8 in arxiv:1912.02415 + double deltaJ = p_Epsilon * omegaDifference * magneticRadius * magneticRadius ; + return(deltaJ); +} + + /* * Update the magnetic field and spins of neutron stars. * Modifies the following class member variables: @@ -497,8 +554,10 @@ DBL_DBL_DBL_DBL NS::PulsarAccretion(const double p_MagField, const double p_Spin * @param [IN] p_Epsilon Efficiency factor allowing for uncertainties of coupling magnetic field and matter. */ void NS::UpdateMagneticFieldAndSpin(const bool p_CommonEnvelope, const bool p_RecycledNS, const double p_Stepsize, const double p_MassGainPerTimeStep, const double p_Epsilon) { - double kappa = OPTIONS->PulsarMagneticFieldDecayMassscale() * MSOL_TO_G; - + double kappa = OPTIONS->PulsarMagneticFieldDecayMassscale() * MSOL_TO_G; + double magFieldLowerLimit_G = PPOW(10.0, OPTIONS->PulsarLog10MinimumMagneticField()) ; + double massNS = m_Mass; + double radiusNS = m_Radius; if ((!p_RecycledNS && !p_CommonEnvelope) || (p_RecycledNS && utils::Compare(p_MassGainPerTimeStep, 0.0) == 0 )) { // These are the ''classical'' isolated pulsars. They experience spin-down. SpinDownIsolatedPulsar(p_Stepsize); @@ -513,75 +572,68 @@ void NS::UpdateMagneticFieldAndSpin(const bool p_CommonEnvelope, const bool p_Re // Pulsar evolution during stable mass transfer (RLOF), or if using the 'DISK' option // in the CE accretion option - double final_b=; - double final_p=; - double final_am=; - double final_pdot=; - x[0] = final_b; - x[1] = final_p; - x[2] = final_am; - x[3] = final_pdot; - + double newAM = -1.0; //updated angular momentum + double thisB = m_PulsarDetails.magneticField * TESLA_TO_GAUSS ; + double thisF = m_PulsarDetails.spinFrequency ; + double thisFdot = m_PulsarDetails.spinDownRate ; double divideTimestepBy = 100.0; - - controlled_stepper_type controlled_stepper; - state_type x(4); - - controlled_stepper.do_step(, x, t_start, dt); - - - // std::tuple accretionResults = PulsarAccretion(m_PulsarDetails.magneticField, m_PulsarDetails.spinFrequency, m_AngularMomentum_CGS, p_Stepsize, p_MassGainPerTimeStep / G_TO_KG, kappa, p_Epsilon); - - // double divideTimestepBy = 100.0; - // bool done = false; - // while (!done && utils::Compare(divideTimestepBy, 1000000) > 0.0) { + + while (utils::Compare(newAM, 0.0) < 0) { + double thisMassGain = p_MassGainPerTimeStep / G_TO_KG /divideTimestepBy; + double thisTimestepSize = p_Stepsize / divideTimestepBy; + //controlled_stepper_type controlled_stepper; + boost::numeric::odeint::runge_kutta4 stepper; + + state_type x(1); + x[0] = m_AngularMomentum_CGS; + std::cout<< "the original AM is " << x[0] << std::endl; + std::cout<< massNS << ", "<< radiusNS << ", "<< thisB<< ", " << thisF<< ", " << x[0]<< ", " << thisTimestepSize<< ", "<< kappa<< ", " << p_Epsilon << std::endl; - // divideTimestepBy *= 2.0; + // Solve for the angular momentum of the NS after accretion. + // Use boost adaptive ODE solver for speed and accuracy and ensure the result is always positive. + struct DynamicODE + { + double mass, radius, magField, spinFrequency, angularMomentum, stepsize, kkappa, epsilon; + DynamicODE( double mass0, double radius0, double magField0, double spinFreq0, double AM0, double stepsize0, double kkappa0, double epsilon0) : + mass(mass0), radius(radius0), magField(magField0), spinFrequency(spinFreq0), angularMomentum(AM0), stepsize(stepsize0), kkappa(kkappa0), epsilon(epsilon0) {} + + void operator()( state_type const& x , state_type& dxdt , double p_MassChange ) const { + dxdt[0] = NS::deltaAngularMomentumByPulsarAccretion(p_MassChange, mass, radius, magField, spinFrequency, angularMomentum, stepsize, kkappa, epsilon); + } + }; - // double thisTimestepSize = p_Stepsize / divideTimestepBy; - // double thisMassGain = p_MassGainPerTimeStep / G_TO_KG / divideTimestepBy; - // double B ; - // double f ; - // double fdot ; - // double am ; - - // std::tie(B, f, fdot, am) = PulsarAccretion(m_PulsarDetails.magneticField, m_PulsarDetails.spinFrequency, - // m_AngularMomentum_CGS, thisTimestepSize, thisMassGain, kappa, p_Epsilon); - - // int count = 0; - // while (!done) { - // std::tie(B, f, fdot, am) = PulsarAccretion(B, f, am, thisTimestepSize, thisMassGain, kappa, p_Epsilon); - - // if (utils::Compare(f, 0.0) < 0) break; - // if (++count >= divideTimestepBy) done = true; - // } - // } - - // double newTimeStepSize = p_Stepsize / divideTimestepBy; - // double newMassGain = p_MassGainPerTimeStep / G_TO_KG / divideTimestepBy ; - // accretionResults = PulsarAccretion(m_PulsarDetails.magneticField, m_PulsarDetails.spinFrequency, - // m_AngularMomentum_CGS, newTimeStepSize, newMassGain, kappa, p_Epsilon); - // for (int n = 1; n<= int(divideTimestepBy); n++){ + DynamicODE ode(massNS, radiusNS, thisB, thisF, x[0], thisTimestepSize, kappa, p_Epsilon); - // double last_B_2 = std::get<0>(accretionResults); - // double last_f_2 = std::get<1>(accretionResults); - // double last_am_2 = std::get<3>(accretionResults); + for (double dm = 0.0; dm < p_MassGainPerTimeStep / G_TO_KG; dm += thisMassGain){ + stepper.do_step( ode, x, dm, thisMassGain); + + thisB = (thisB - magFieldLowerLimit_G) * exp(-thisMassGain / kappa) + magFieldLowerLimit_G;; + thisB = thisB ; + thisF = x[0] / m_MomentOfInertia_CGS; + thisFdot = (x[0]-newAM)/ thisTimestepSize / m_MomentOfInertia_CGS; + newAM = x[0]; + std::cout << thisB << ", " << thisF << ", " << newAM <(accretionResults); - // m_PulsarDetails.spinFrequency = std::get<1>(accretionResults); - // m_AngularMomentum_CGS = std::get<3>(accretionResults); - // m_PulsarDetails.spinDownRate = std::get<2>(accretionResults); - // } + if (utils::Compare(newAM,0) < 0) { + divideTimestepBy *= 2.0 ; + thisB = m_PulsarDetails.magneticField * TESLA_TO_GAUSS ; + thisF = m_PulsarDetails.spinFrequency ; + thisFdot = m_PulsarDetails.spinDownRate ; + } + } + m_PulsarDetails.magneticField = thisB * GAUSS_TO_TESLA; + m_PulsarDetails.spinFrequency = thisF; + m_PulsarDetails.spinDownRate = thisFdot; + } else if (p_CommonEnvelope && OPTIONS->NeutronStarAccretionInCE() == NS_ACCRETION_IN_CE::SURFACE) { // Mass transfer through CE when accretion happens at the surface of the NS double initialMagField = m_PulsarDetails.magneticField; - double initialMagField_G = initialMagField * TESLA_TO_GAUSS; - double magFieldLowerLimit_G = PPOW(10.0, OPTIONS->PulsarLog10MinimumMagneticField()) ; + double initialMagField_G = initialMagField * TESLA_TO_GAUSS; double mass_g = m_Mass * MSOL_TO_G; // in g double r_cm = m_Radius * RSOL_TO_KM * KM_TO_CM; // in cm diff --git a/src/NS.h b/src/NS.h index 920efe6a8..bea496877 100755 --- a/src/NS.h +++ b/src/NS.h @@ -8,7 +8,7 @@ #include "Remnants.h" #include "BH.h" - +#include class BaseStar; class Remnants; @@ -46,8 +46,15 @@ class NS: virtual public BaseStar, public Remnants { static double CalculateRadiusOnPhase_Static(const double p_Mass) { return CalculateRadiusOnPhaseInKM_Static(p_Mass) * KM_TO_RSOL; } // Radius on phase in Rsol static double CalculateRemnantMass_Static(const double p_COCoreMass) { return 1.17 + (0.09 * p_COCoreMass); } // Hurley et al., eq 92 - - + static double deltaAngularMomentumByPulsarAccretion(const double p_MassGainPerTimeStep, + const double p_MagField, + const double p_Mass, + const double p_Radius, + const double p_SpinFrequency, + const double p_AngularMomentum, + const double p_Stepsize, + const double p_Kappa, + const double p_Epsilon) ; protected: void Initialise() { @@ -86,19 +93,22 @@ class NS: virtual public BaseStar, public Remnants { double CalculateSpinDownRate(const double p_Omega, const double p_MomentOfInteria, const double p_MagField, const double p_Radius) const; - double ChooseTimestep(const double p_Time) const; + double ChooseTimestep(const double p_Time) const; + + STELLAR_TYPE EvolveToNextPhase() { return STELLAR_TYPE::BLACK_HOLE; } bool ShouldEvolveOnPhase() const { return (m_Mass <= OPTIONS->MaximumNeutronStarMass()); } // Evolve as a neutron star unless mass > maximum neutron star mass (e.g. through accretion) void SpinDownIsolatedPulsar(const double p_Stepsize); - DBL_DBL_DBL_DBL PulsarAccretion(const double p_MagField, + DBL_DBL_DBL_DBL PulsarAccretion(const double p_MagField, const double p_SpinPeriod, const double p_AngularMomentum, const double p_Stepsize, const double p_MassGainPerTimeStep, const double kappa, const double p_Epsilon); + void UpdateMagneticFieldAndSpin(const bool p_CommonEnvelope, const bool p_RecycledNS, const double p_Stepsize, diff --git a/src/typedefs.h b/src/typedefs.h index e52b93a7f..50299dbe7 100755 --- a/src/typedefs.h +++ b/src/typedefs.h @@ -312,5 +312,6 @@ typedef struct StellarCEDetails { // Common Envelope detail typedef std::vector< double > state_type; typedef boost::numeric::odeint::runge_kutta_cash_karp54< state_type > error_stepper_type; typedef boost::numeric::odeint::controlled_runge_kutta< error_stepper_type > controlled_stepper_type; +typedef boost::numeric::odeint::runge_kutta4< state_type > stepper; #endif // __typedefs_h__ From cb33d166cf95b7567a553a203695621ab4a223dd Mon Sep 17 00:00:00 2001 From: Yuzhe Song Date: Wed, 4 Dec 2024 09:27:09 +1100 Subject: [PATCH 19/19] further changes made to use BOOST ODE integrator for MSP recycling --- src/NS.cpp | 144 +++++++++++++++++++++++++++++++++-------------------- src/NS.h | 6 ++- 2 files changed, 93 insertions(+), 57 deletions(-) diff --git a/src/NS.cpp b/src/NS.cpp index 348231a89..14ca44a64 100755 --- a/src/NS.cpp +++ b/src/NS.cpp @@ -487,22 +487,26 @@ DBL_DBL_DBL_DBL NS::PulsarAccretion(const double p_MagField, const double p_Spin * dJ/dm: change in angular momentum per mass transferred. * * DBL_DBL_DBL_DBL PulsarAccretion(const double p_MagField, const double p_SpinFrequency, const double p_AngularMomentum, const double p_Stepsize, const double p_MassGainPerTimeStep, const double kappa, const double p_Epsilon) - * @param [IN] p_MagField NS magnetic field strength at the beginning of accretion (in Tesla) + * @param [IN] p_MagField NS magnetic field strength at the beginning of accretion (in Gauss) * @param [IN] p_SpinFrequency Spin frequency for the NS at the beginning of accretion (in Hz) * @param [IN] p_AngularMomentum Angular momentum of the NS at the beginning of accretion (g cm-2 / s) * @param [IN] p_Stepsize Timestep size for integration (in seconds) * @param [IN] p_MassGainPerTimeStep Mass transferred from the secondary for each iteration (in g) * @param [IN] p_Kappa Magnetic field mass decay scale (in g) * @param [IN] p_Epsilon Efficiency factor allowing for uncertainties of coupling magnetic field and matter. + * @param [IN] p_MoI Moment of Inertia * @return Tuple containing the updated magnetic field strength, spin frequency, spin-down rate and angular momentum of neutron star */ -double NS::deltaAngularMomentumByPulsarAccretion(const double p_MassGainPerTimeStep, const double p_Mass, const double p_Radius, const double p_MagField, const double p_SpinFrequency, const double p_AngularMomentum, const double p_Stepsize, const double p_Kappa, const double p_Epsilon) { - double initialMagField_G = p_MagField * TESLA_TO_GAUSS; +DBL_DBL_DBL_DBL NS::deltaAngularMomentumByPulsarAccretion(const double p_MassGainPerTimeStep, const double p_Mass, const double p_Radius, const double p_MagField, const double p_SpinFrequency, const double p_AngularMomentum, const double p_Stepsize, const double p_Kappa, const double p_Epsilon, const double p_MoI) { + if (utils::Compare(p_MassGainPerTimeStep, 0.0) <= 0) { + return std::make_tuple(0.0, 0.0, 0.0, 0.0); + } + double initialMagField_G = p_MagField; double magFieldLowerLimit_G = PPOW(10.0, OPTIONS->PulsarLog10MinimumMagneticField()) ; // convert to Gauss double mass_g = p_Mass * MSOL_TO_G; // in g double r_cm = p_Radius * RSOL_TO_KM * KM_TO_CM; double angularMomentum = p_AngularMomentum; - + //std::cout << "in Func: " << p_Kappa <<" " << p_SpinFrequency << " " << mass_g << " " << r_cm << " " << angularMomentum << std::endl; //Magnetic field decay due to mass transfer. //Follows Eq. 12 in arxiv:1912.02415 double newPulsarMagneticField = (initialMagField_G - magFieldLowerLimit_G) * exp(-p_MassGainPerTimeStep / p_Kappa) + magFieldLowerLimit_G; @@ -516,6 +520,7 @@ double NS::deltaAngularMomentumByPulsarAccretion(const double p_MassGainPerTimeS double alfvenConst = PPOW(2 * PI_2 / G_CGS, 1.0/7.0) ; double alfvenRadius = alfvenConst * q * PPOW(initialMagField_G, 4.0/7.0); double magneticRadius = alfvenRadius / 2.0; + // std::cout<< "MagRad: " << mDot << " " << r_cm << " " << p << " " << mass_g << " " << q << " " << alfvenConst << std::endl; // calculate the difference in the keplerian angular velocity at the magnetic radius // and surface angular velocity of the neutron star // magnetic radius is half of alfven radius @@ -523,11 +528,15 @@ double NS::deltaAngularMomentumByPulsarAccretion(const double p_MassGainPerTimeS double keplerianVelocityAtMagneticRadius = std::sqrt((G_CGS) * mass_g / magneticRadius); double keplerianAngularVelocityAtMagneticRadius = keplerianVelocityAtMagneticRadius / magneticRadius; double omegaDifference = keplerianAngularVelocityAtMagneticRadius - p_SpinFrequency; - + // std::cout<< "Denomenators: " << p_Kappa << " " << mass_g << " " << p_Stepsize << " " << magneticRadius << std::endl; // calculate the change in angular momentum due to accretion // see Equation 12 in arXiv:0805.0059/ Equation 8 in arxiv:1912.02415 double deltaJ = p_Epsilon * omegaDifference * magneticRadius * magneticRadius ; - return(deltaJ); + double Jdot = p_Epsilon * omegaDifference * magneticRadius * magneticRadius * mDot; + return std::make_tuple(newPulsarMagneticField, + (angularMomentum + Jdot * p_Stepsize) / p_MoI, + deltaJ * mDot / p_MoI, + deltaJ); } @@ -572,61 +581,86 @@ void NS::UpdateMagneticFieldAndSpin(const bool p_CommonEnvelope, const bool p_Re // Pulsar evolution during stable mass transfer (RLOF), or if using the 'DISK' option // in the CE accretion option - double newAM = -1.0; //updated angular momentum + //double newAM = -1.0; //updated angular momentum double thisB = m_PulsarDetails.magneticField * TESLA_TO_GAUSS ; double thisF = m_PulsarDetails.spinFrequency ; double thisFdot = m_PulsarDetails.spinDownRate ; - double divideTimestepBy = 100.0; + double thisM = m_Mass ; + double divideTimestepBy = 10.0; + + //while (utils::Compare(newAM, 0.0) < 0) { + double thisMassGain = p_MassGainPerTimeStep / G_TO_KG / divideTimestepBy; + double thisTimestepSize = p_Stepsize / divideTimestepBy; + controlled_stepper_type controlled_stepper; + //boost::numeric::odeint::runge_kutta4 stepper; + + state_type x(5); + x[0] = m_AngularMomentum_CGS; + x[1] = thisB; + x[2] = thisF; + x[3] = thisFdot; + x[4] = thisM; + std::cout<< "the original AM is " << x[0] << std::endl; + std::cout<< massNS << ", "<< radiusNS << ", "<< thisB<< ", " << thisF<< ", " << x[0]<< ", " << thisTimestepSize<< ", "<< kappa<< ", " << p_Epsilon << std::endl; - while (utils::Compare(newAM, 0.0) < 0) { - double thisMassGain = p_MassGainPerTimeStep / G_TO_KG /divideTimestepBy; - double thisTimestepSize = p_Stepsize / divideTimestepBy; - //controlled_stepper_type controlled_stepper; - boost::numeric::odeint::runge_kutta4 stepper; - - state_type x(1); - x[0] = m_AngularMomentum_CGS; - std::cout<< "the original AM is " << x[0] << std::endl; - std::cout<< massNS << ", "<< radiusNS << ", "<< thisB<< ", " << thisF<< ", " << x[0]<< ", " << thisTimestepSize<< ", "<< kappa<< ", " << p_Epsilon << std::endl; - - // Solve for the angular momentum of the NS after accretion. - // Use boost adaptive ODE solver for speed and accuracy and ensure the result is always positive. - struct DynamicODE - { - double mass, radius, magField, spinFrequency, angularMomentum, stepsize, kkappa, epsilon; - DynamicODE( double mass0, double radius0, double magField0, double spinFreq0, double AM0, double stepsize0, double kkappa0, double epsilon0) : - mass(mass0), radius(radius0), magField(magField0), spinFrequency(spinFreq0), angularMomentum(AM0), stepsize(stepsize0), kkappa(kkappa0), epsilon(epsilon0) {} - - void operator()( state_type const& x , state_type& dxdt , double p_MassChange ) const { - dxdt[0] = NS::deltaAngularMomentumByPulsarAccretion(p_MassChange, mass, radius, magField, spinFrequency, angularMomentum, stepsize, kkappa, epsilon); - } - }; - - DynamicODE ode(massNS, radiusNS, thisB, thisF, x[0], thisTimestepSize, kappa, p_Epsilon); - - for (double dm = 0.0; dm < p_MassGainPerTimeStep / G_TO_KG; dm += thisMassGain){ - stepper.do_step( ode, x, dm, thisMassGain); - - thisB = (thisB - magFieldLowerLimit_G) * exp(-thisMassGain / kappa) + magFieldLowerLimit_G;; - thisB = thisB ; - thisF = x[0] / m_MomentOfInertia_CGS; - thisFdot = (x[0]-newAM)/ thisTimestepSize / m_MomentOfInertia_CGS; - newAM = x[0]; - std::cout << thisB << ", " << thisF << ", " << newAM <(results) ; + x[1] = std::get<0>(results); + x[2] = std::get<1>(results); + x[3] = std::get<2>(results); + x[4] = mass + p_MassChange/MSOL_TO_G; } - m_PulsarDetails.magneticField = thisB * GAUSS_TO_TESLA; - m_PulsarDetails.spinFrequency = thisF; - m_PulsarDetails.spinDownRate = thisFdot; + }; + std::cout << thisMassGain << " " << p_MassGainPerTimeStep << std::endl; + //DynamicODE ode(massNS, radiusNS, thisB, thisF, x[0], thisTimestepSize, kappa, p_Epsilon); + integrate_adaptive(controlled_stepper, DynamicODE{x[4], radiusNS, x[1], x[2], x[0], thisTimestepSize, + kappa, p_Epsilon, m_MomentOfInertia_CGS}, x, 0.0, p_MassGainPerTimeStep / G_TO_KG, thisMassGain); + // for (double dm = 0.0; dm < p_MassGainPerTimeStep / G_TO_KG; dm += thisMassGain){ + // stepper.do_step( ode, x, dm, thisMassGain); + // thisB = (thisB - magFieldLowerLimit_G) * exp(-thisMassGain / kappa) + magFieldLowerLimit_G;; + // thisB = thisB ; + // thisF = x[0] / m_MomentOfInertia_CGS; + // thisFdot = (x[0]-newAM)/ thisTimestepSize / m_MomentOfInertia_CGS; + // newAM = x[0]; + // std::cout << thisB << ", " << thisF << ", " << newAM <