def __init__(self, mass, S_want, magprofile=False, omega=0., L_want=0., temp_c=False, mintemp=1e5, composition="CO", togglecoulomb=True, S_old=False, mlt_coeff="phil", P_end_ratio=1e-8, ps_eostol=1e-8, fakeouterpoint=False, stop_invertererr=True, stop_mrat=2., stop_positivepgrad=True, stop_mindenserr=1e-10, densest=False, omegaest=False, mass_tol=1e-6, L_tol=1e-6, omega_crit_tol=1e-3, nreps=30, stopcount_max=5, dontintegrate=False, verbose=True): # Stop doing whatever if user inserts rotation and B field if magprofile and omega != 0.: print "You cannot currently insert a magnetic field simultaneously with non-zero rotation! Quitting." return maghydrostar_core.__init__(self, mass, temp_c, magprofile=magprofile, omega=omega, L_want=L_want, mintemp=mintemp, composition=composition, togglecoulomb=togglecoulomb, fakeouterpoint=fakeouterpoint, stop_invertererr=stop_invertererr, stop_mrat=stop_mrat, stop_positivepgrad=stop_positivepgrad, stop_mindenserr=stop_mindenserr, mass_tol=mass_tol, L_tol=L_tol, omega_crit_tol=omega_crit_tol, nreps=nreps, stopcount_max=stopcount_max, verbose=verbose) self.nablarat_crit = False # This should only be used for debugging! # Initialize nuclear specific energy generation rate td = rtc.timescale_data(max_axes=[1e12,1e12]) self.eps_nuc_interp = td.getinterp2d("eps_nuc") if S_old: self.populateS_old(S_old) if verbose: print "S_old defined! Will use old entropy profile if new one dips below it." else: self.S_old = False self.S_old_reltol = 1e-6 # Relative tolerance to shoot for in connect_S_old self.derivatives = self.derivatives_steve self.first_deriv = self.first_derivatives_steve self.mlt_coeff = mlt_coeff # Derivative and getconvection will need this for scaled Stevenson self.set_mlt_coeff(self.mlt_coeff) if self.verbose: # self.verbose is implicitly defined in maghydrostar print "Stevenson 79 derivative selected! MLT coefficient = {0:s}".format(mlt_coeff) if dontintegrate: if self.verbose: print "WARNING: integration disabled within mhs_steve!" else: if self.omega < 0.: self.omega = 0. self.getmaxomega(P_end_ratio=P_end_ratio, densest=densest, S_want=S_want, ps_eostol=ps_eostol) else: if L_want: self.getrotatingstarmodel_2d(densest=densest, omegaest=omegaest, S_want=S_want, P_end_ratio=P_end_ratio, ps_eostol=ps_eostol, damp_nrstep=0.25) else: self.getstarmodel(densest=densest, S_want=S_want, P_end_ratio=P_end_ratio, ps_eostol=ps_eostol) # Checks omega, just to make sure user didn't initialze a "dontintegrate" but set omega < 0 assert self.omega >= 0.
def make_runaway_steve(starmass=1.2*1.9891e33, mymag=False, omega=0., omega_run_rat=0.8, S_arr=10**np.arange(7.5,8.2,0.25), mlt_coeff="phil", uvs_k=3, uvs_s=None, mintemp=1e5, S_old=False, mass_tol=1e-6, P_end_ratio=1e-8, densest=False, stop_mindenserr=1e-10, L_tol=1e-6, keepstars=False, omega_crit_tol=1e-3, omega_warn=10., de_err_tol=[1e6, 1e7], verbose=True): """Obtains runaway of a star of some given mass, magnetic field, and rotation. Outputs an object (usually several hundred megs large) that includes run inputs as "run_inputs", as well as all stellar output curves (hence its size) under "stars". Arguments: starmass : wanted mass mymag : magnetic profile object. Defaults to false, meaning no magnetic field. omega : rigid rotation angular velocity. Defaults to 0 (non-rotating). If < 0, attempts to estimate break-up omega with self.getomegamax(), if >= 0, uses user defined value. S_arr : list of central entropy values in the runaway track mintemp : temperature floor, effectively switches from adiabatic to isothermal profile if reached S_old : if True, uses the S_old function of StarModSteve to prevent entropy from decreasing below previously calculated entropy curve (which should not occur in a runaway and leads to unphysical "right hooks" in runaway rho-T diagrams). mlt_coeff : ["phil", "wwk", "kippw", "steve"] Sets mixing length theory coefficients for calculating velocity and superadiabatic temperature gradients. "phil" is the standard faire coefficients suggested by Phil Chang; "wwk" is back-derived from Woosley, Wunch and Kuhlen 2004; "kippw" is from Kippenhahn & Wieigert (identical to Cox & Giuli); "steve" is from Stevenson 1979. Since Stevenson's rotational and magnetic corrections to convection are expressed as ratios of velocity and temperature gradient, they can be used with any of these mlt_coeffs. uvs_k : degree of the smoothing spline in scipy.interpolate.UnivariateSpline. Must be <= 5; default is k=3. uvs_s : UnivariateSpline positive smoothing factor used to choose the number of knots. Default is None. mass_tol : fractional tolerance between mass wanted and mass produced by self.getstarmodel() P_end_ratio : ratio of P/P_c at which to terminate stellar integration densest : central density initial estimate for self.getstarmodel() stop_mindenserr : density floor, below which integration is halted. Default is set to 1e-10 to prevent it from ever being reached. Helmholtz sometimes has trouble below this 1e-8; try adjusting this value to eliminate inverter errors. L_tol : conservation of angular momentum error tolerance omega_crit_tol : when using mystar.getomegamax(), absolute error tolerance for maximum omega omega_warn : stop integration within mystar.getrotatingstarmodel() if self.omega approaches omega_warn*omega_crit estimate. Defaults to 10 to prevent premature stoppage. de_err_tol : densities at which relaxed error tolerances kick in. verbose : report happenings within code """ # Save a few details about the magnetic field (though we'll need better records for the actual paper) try: mymagsave = np.array([float(mymag.fBfld_r(0)), float(mymag.fBfld_r(2e8))]) except: if verbose: print "Magnetic field not found! Hopefully this is what you wanted." mymagsave = False r_in = {"mass": starmass, "magprofile": mymagsave, "omega": omega, "omega_run_rat": omega_run_rat, "S_arr": S_arr, "mintemp": mintemp, "S_old": S_old, "mlt_coeff": mlt_coeff, "composition": "CO", "tog_coul": True, "P_end_ratio": P_end_ratio, "ps_eostol": 1e-8, "fakeouterpoint": False, "stop_invertererr": True, "stop_mrat": 2., "stop_positivepgrad": True, "stop_mindenserr": stop_mindenserr, "densest": densest, "mass_tol": mass_tol, "L_tol": L_tol, "omega_crit_tol": omega_crit_tol, "omega_warn": omega_warn, "lowerr_tol": de_err_tol[0], "mederr_tol": de_err_tol[1], "uvs_k": uvs_k, "uvs_s": uvs_s} if (omega != 0) or mymag: print "*************You want to make an MHD/rotating star; let's first try making a stationary pure hydro star!************" mymagzero = magprof.magprofile(None, None, None, None, blankfunc=True) hstar = Star.mhs_steve(r_in["mass"], False, magprofile=mymagzero, omega=0., temp_c=5e6, mintemp=r_in["mintemp"], composition=r_in["composition"], togglecoulomb=r_in["tog_coul"], mlt_coeff=r_in["mlt_coeff"], P_end_ratio=r_in["P_end_ratio"], ps_eostol=r_in["ps_eostol"], fakeouterpoint=r_in["fakeouterpoint"], stop_invertererr=r_in["stop_invertererr"], stop_mrat=r_in["stop_mrat"], stop_positivepgrad=r_in["stop_positivepgrad"], stop_mindenserr=r_in["stop_mindenserr"], densest=r_in["densest"], mass_tol=r_in["mass_tol"], L_tol=r_in["L_tol"], omega_crit_tol=r_in["omega_crit_tol"], nreps=100, verbose=verbose) densest=0.9*hstar.data["rho"][0] print "*************Okay, let's make a low-temperature (MHD/rotating) star************" #Rest after this is identical to function call above mystar = Star.mhs_steve(r_in["mass"], False, magprofile=mymag, omega=r_in["omega"], temp_c=5e6, mintemp=r_in["mintemp"], composition=r_in["composition"], togglecoulomb=r_in["tog_coul"], mlt_coeff=r_in["mlt_coeff"], P_end_ratio=r_in["P_end_ratio"], ps_eostol=r_in["ps_eostol"], fakeouterpoint=r_in["fakeouterpoint"], stop_invertererr=r_in["stop_invertererr"], stop_mrat=r_in["stop_mrat"], stop_positivepgrad=r_in["stop_positivepgrad"], stop_mindenserr=r_in["stop_mindenserr"], densest=r_in["densest"], mass_tol=r_in["mass_tol"], L_tol=r_in["L_tol"], omega_crit_tol=r_in["omega_crit_tol"], nreps=100, verbose=verbose) if r_in["omega"] < 0: print "FOUND critical Omega = {0:.3e}! We'll use {1:.3e} of this value for the runaway.".format(mystar.omega, r_in["omega_run_rat"]) r_in["omega_crit_foundinfirststep"] = mystar.omega mystar.omega *= r_in["omega_run_rat"] mystar.getstarmodel(densest=0.9*mystar.data["rho"][0], P_end_ratio=r_in["P_end_ratio"], ps_eostol=r_in["ps_eostol"]) if r_in["omega"]: if mystar.omega > mystar.getcritrot(max(mystar.data["M"]), mystar.data["R"][-1]): print "WARNING: exceeding estimated critical rotation! Consider restarting this run." r_in["L_original"] = mystar.getmomentofinertia(mystar.data["R"], mystar.data["rho"])[-1]*mystar.omega mystar.L_want = r_in["L_original"] # Store initial angular momentum for future use. out_dict = {"temp_c": np.zeros(len(S_arr)+1), "dens_c": np.zeros(len(S_arr)+1), "omega": np.zeros(len(S_arr)+1), "B_c": np.zeros(len(S_arr)+1), "S_c": np.zeros(len(S_arr)+1), "R": np.zeros(len(S_arr)+1), "stars": []} if "R_nuc" not in mystar.data.keys(): # Obtain timescale info if it's not already printed. mystar.gettimescales() out_dict["run_inputs"] = r_in if r_in["omega"] < 0: out_dict["omega_crit"] = r_in["omega_crit_foundinfirststep"] out_dict["S_c"][0] = mystar.data["Sgas"][0] out_dict["temp_c"][0] = mystar.data["T"][0] out_dict["dens_c"][0] = mystar.data["rho"][0] out_dict["omega"][0] = mystar.omega out_dict["B_c"][0] = np.mean(mystar.data["B"][:10]) out_dict["R"][0] = mystar.data["R"][-1] if keepstars: out_dict["stars"].append(copy.deepcopy(mystar)) else: out_dict["stars"].append(copy.deepcopy(mystar.data)) # Obtain tau_cc = tau_neutrino equality line td = rtc.timescale_data(max_axes=[1e12,1e12]) if r_in["S_old"]: ignition_line_f = td.get_tauneunuc_line() for i in range(len(r_in["S_arr"])): print "*************Star #{0:d}, entropy = {1:.3f}************".format(i, r_in["S_arr"][i]) # If we turn on S_old and, during the previous step, we passed the ignition line, start recording old entropy profiles # and passing them on to the next star. if r_in["S_old"] and mystar.data["T"][0] > ignition_line_f(mystar.data["rho"][0]): # print "Using previous entropy profile" # if r_in["rezero_Sold"]: # Bomb-proofing to keep extremely low entropy values from messing up integration # mystar.data["Sgas"][mystar.data["Sgas"] < 0.] = 0. myS_old = Star.sprof.entropy_profile(mystar.data["M"], mystar.data["Sgas"], mystar.data["v_conv_st"], spline_k=r_in["uvs_k"], spline_s=r_in["uvs_s"]) mystar.S_old = myS_old.S_old mystar.dS_old = myS_old.dS_old mystar.vconv_Sold = myS_old.vconv_Sold outerr_code = obtain_model(mystar, i, r_in, omega_warn=r_in["omega_warn"], verbose=verbose) if outerr_code: print "===== RUNAWAY.PY REPORTS OUTERR: ", outerr_code, "for star", i, "so will stop model making! =====" break if "R_nuc" not in mystar.data.keys(): # Obtain timescale info if it's not already printed. mystar.getconvection(td=td) mystar.gettimescales() out_dict["S_c"][i+1] = mystar.data["Sgas"][0] out_dict["temp_c"][i+1] = mystar.data["T"][0] out_dict["dens_c"][i+1] = mystar.data["rho"][0] out_dict["omega"][i+1] = mystar.omega out_dict["B_c"][i+1] = np.mean(mystar.data["B"][:10]) out_dict["R"][i+1] = mystar.data["R"][-1] if keepstars: out_dict["stars"].append(copy.deepcopy(mystar)) else: out_dict["stars"].append(copy.deepcopy(mystar.data)) return out_dict
def getsimmermodel(self, S_want, S_old, Mconv, Lconvrat=False, densest=False, out_search=False): """Obtains star at end of convective simmering, where (super-)adiabatic temperature gradient is used before user defined mass shell M_conv, and user-defined entropy profile is used after. Arguments: S_want: central entropy S_old: previous entropy profile Mconv: enclosed mass of convection zone densest: central density estimate (false for code to guess) out_search: return trial densities and corresponding masses """ if S_old: self.S_old = S_old #Store old entropy structure else: self.S_old = lambda x: S_want if Mconv: self.Mconv = Mconv else: self.Mconv = 1e100 if Lconvrat: self.Lconvrat = Lconvrat td = rtc.timescale_data(max_axes=[1e12,1e12]) self.eps_nuc_interp = td.getinterp2d("eps_nuc") else: self.Lconvrat = False if not densest: densest = 3.73330253e-60*self.mass_want*self.mass_want*3. #The 3. is recently added to reduce the time for integrating massive WDs from scratch stepmod = 0.1*densest i = 1 [Mtot, outerr_code] = self.integrate_simmer(densest, S_want, outputerr=True) beforedirec = int((self.mass_want - Mtot)/abs(self.mass_want - Mtot)) #1 means next shot should have larger central density, -1 means smaller) stepmod = float(beforedirec)*stepmod #Set initial direction (same notation as above) if self.verbose: print "First shot: M = {0:.5e} (vs. wanted M = {1:.5e})".format(Mtot, self.mass_want) print "Direction is {0:d}".format(beforedirec) #Minor additional test M_arr = np.array([Mtot]) dens_arr = np.array([densest]) checkglobalextreme = False #If we incur hugemass_err the first time if outerr_code == "hugemass_err": if self.verbose: print "hugemass_err is huge from the first estimate. Let's try a much lower density." densest = 0.1*densest [Mtot, outerr_code] = self.integrate_simmer(densest, S_want, outputerr=True) #If we incur any error (or hugemass_err again) if outerr_code: print "OUTERR_CODE {0:s}! EXITING FUNCTION!".format(outerr_code) return outerr_code while abs(Mtot - self.mass_want) >= self.mass_tol*self.mass_want and i < self.nreps and beforedirec != 0 and not outerr_code: [stepmodchange, beforedirec] = self.checkdirec(beforedirec, self.mass_want - Mtot) #For the first time, this will give stepmodchange = 1, beforedirec = 1 stepmod *= stepmodchange densest += stepmod if densest <= 1e1: #Central density estimate really shouldn't be lower than about 1e4 g/cc densest = abs(stepmod)*0.1 stepmod = 0.1*stepmod if self.verbose: print "Old density estimate rho = {0:.5e}; new rho = {1:.5e}".format(densest - stepmod, densest) [Mtot, outerr_code] = self.integrate_simmer(densest, S_want, outputerr=True) if self.verbose: print "Current shot: M = {0:.5e} (vs. wanted M = {1:.5e})".format(Mtot, self.mass_want) M_arr = np.append(M_arr, Mtot) dens_arr = np.append(dens_arr, densest) #Check for extreme circumstances where a solution might be impossible if int((dens_arr[i] - dens_arr[i-1])/abs(dens_arr[i] - dens_arr[i-1])) != int((M_arr[i] - M_arr[i-1])/abs(M_arr[i] - M_arr[i-1])) and not checkglobalextreme: print "Density and mass don't have a positive-definite relationship in the regime you selected!" if i == 1: #Check that increasing (decreasing) central density increases (decreases) mass print "We're at the beginning of mass finding (i = 1), so reversing direction!" stepmod *= -1.0 checkglobalextreme = True stopcount = 0 #If we've already activated checkglobalextreme, we should only integrate up to stopcount = stopcount_max if checkglobalextreme: stopcount += 1 if stopcount > self.stopcount_max: #If we've integrated one times too many after checkglobalextreme = True outerr_code = self.chk_global_extrema(M_arr, Mtot - self.mass_want, self.mass_want) i += 1 if beforedirec == 0: print "ERROR! checkdirec is printing out 0!" if i == self.nreps: print "WARNING, maximum number of shooting attempts {0:d} reached!".format(i) #If we incur any error (or hugemass_err again) if outerr_code: print "OUTERR_CODE {0:s}! EXITING FUNCTION!".format(outerr_code) return outerr_code if self.verbose: print "Final shot!" Mtot = self.integrate_simmer(densest, S_want, recordstar=True) if abs((Mtot - self.mass_want)/self.mass_want) > self.mass_tol: print "ERROR!!!! (M_total - mass_want)/mass_want = {0:.5e}".format((Mtot - self.mass_want)/self.mass_want) print "THIS IS BIGGER THAN YOUR TOLERANCE! CHECK YOUR ICS!" else: print "(M_total - mass_want)/mass_want = {0:.5e}".format((Mtot - self.mass_want)/self.mass_want) if out_search: return [M_arr, dens_arr]