#!/usr/bin/env python # coding: utf-8 # # # # # [Polytropic TOV](https://en.wikipedia.org/wiki/Tolman%E2%80%93Oppenheimer%E2%80%93Volkoff_equation) Initial Data # # ## Authors: Phil Chang, Zach Etienne, & Leo Werneck # ### Formatting improvements courtesy Brandon Clark # # ## This module sets up initial data for a [TOV](https://en.wikipedia.org/wiki/Tolman%E2%80%93Oppenheimer%E2%80%93Volkoff_equation) star in *spherical, isotropic coordinates* # # **Notebook Status:** Validated # # **Validation Notes:** This module has been validated to exhibit convergence to zero of the Hamiltonian constraint violation at the expected order to the exact solution (see [start-to-finish TOV module](Tutorial-Start_to_Finish-BSSNCurvilinear-Setting_up_TOV_initial_data.ipynb) for full test). Note that convergence at the surface of the star is lower order due to the sharp drop to zero in $T^{\mu\nu}$. # # ### NRPy+ Source Code for this module: [TOV/TOV_Solver.py](../edit/TOV/TOV_Solver.py) # # [comment]: <> (Introduction: TODO) # # # # Table of Contents # $$\label{toc}$$ # # This notebook is organized as follows: # # 1. [Step 1](#initializenrpy): Initialize core Python/NRPy+ modules # 1. [Step 2](#tov): The TOV Equations # 1. [Step 3](#code_validation): Code Validation against `TOV.TOV_Solver` NRPy+ module # 1. [Step 4](#latex_pdf_output): Output this notebook to $\LaTeX$-formatted PDF file # # # # Step 1: Initialize core Python/NRPy+ modules \[Back to [top](#toc)\] # $$\label{initializenrpy}$$ # In[1]: # Step 1: Import needed Python/NRPy+ modules import numpy as np # NumPy: A numerical methods module for Python import scipy.integrate as si # SciPy: Python module for mathematics, science, and engineering applications import math, sys # Standard Python modules for math; multiplatform OS-level functions import TOV.Polytropic_EOSs as ppeos # NRPy+: Piecewise polytrope equation of state support # # # # Step 2: The TOV equations \[Back to [top](#toc)\] # $$\label{tov}$$ # # The [TOV line element](https://en.wikipedia.org/wiki/Tolman%E2%80%93Oppenheimer%E2%80%93Volkoff_equation) in terms of the *Schwarzschild coordinate* $r$ is written (in the $-+++$ form): # $$ # ds^2 = - c^2 e^\nu dt^2 + \left(1 - \frac{2Gm}{rc^2}\right)^{-1} dr^2 + r^2 d\Omega^2, # $$ # where $m(r)$ is the mass-energy enclosed at a given $r$, and is equal to the total star's mass outside the stellar radius $r=R$. # # In terms of the *isotropic coordinate* $\bar{r}$ with $G=c=1$ (i.e., the coordinate system and units we'd prefer to use), the ($-+++$ form) line element is written: # $$ # ds^2 = - e^{\nu} dt^2 + e^{4\phi} \left(d\bar{r}^2 + \bar{r}^2 d\Omega^2\right), # $$ # where $\phi$ here is the *conformal factor*. # # Setting components of the above line element equal to one another, we get (in $G=c=1$ units): # # \begin{align} # r^2 &= e^{4\phi} \bar{r}^2 \implies e^{4\phi} = \frac{r^2}{\bar{r}^2} \\ # \left(1 - \frac{2m}{r}\right)^{-1} dr^2 &= e^{4\phi} d\bar{r}^2 \\ # \implies \frac{d\bar{r}(r)}{dr} &= \left(1 - \frac{2m}{r} \right)^{-1/2} \frac{\bar{r}(r)}{r}. # \end{align} # # The TOV equations provide radial ODEs for the pressure and $\nu$ (from [the Wikipedia article on the TOV solution](https://en.wikipedia.org/wiki/Tolman%E2%80%93Oppenheimer%E2%80%93Volkoff_equation)): # # \begin{align} # \frac{dP}{dr} &= - \frac{1}{r} \left( \frac{\rho + P}{2} \right) \left(\frac{2 m}{r} + 8 \pi r^2 P\right) \left(1 - \frac{2 m}{r}\right)^{-1} \\ # \frac{d \nu}{d r} &= \frac{1}{r}\left(1 - \frac{2 m}{r}\right)^{-1} \left(\frac{2 m}{r} + 8 \pi r^2 P\right) \\ # \end{align} # # Assuming a polytropic equation of state, which relates the pressure $P$ to the baryonic rest-mass density $\rho_B$, # # $$ # P(\rho_B) = K \rho_B^\Gamma, # $$ # the specific internal energy will be given by # $$ # \epsilon = \frac{P}{\rho_B (\Gamma - 1)}, # $$ # # so the total mass-energy density $\rho$ is given by # $$ # \rho = \rho_B (1 + \epsilon). # $$ # # Given this, the mass-energy $m(r)$ density is the solution to the ODE: # $$ # \frac{dm(r)}{dr} = 4\pi r^2 \rho(r) # $$ # # Thus the full set of ODEs that need to be solved is given by # # $$ # \boxed{ # \begin{matrix} # \frac{dP}{dr} &=& - \frac{1}{r} \left( \frac{\rho + P}{2} \right) \left(\frac{2 m}{r} + 8 \pi r^2 P\right) \left(1 - \frac{2 m}{r}\right)^{-1} \\ # \frac{d \nu}{d r} &=& \frac{1}{r}\left(1 - \frac{2 m}{r}\right)^{-1} \left(\frac{2 m}{r} + 8 \pi r^2 P\right) \\ # \frac{m(r)}{dr} &=& 4\pi r^2 \rho(r) \\ # \frac{d\bar{r}(r)}{dr} &=& \left(1 - \frac{2m}{r} \right)^{-1/2} \frac{\bar{r}(r)}{r} # \end{matrix} # }\ . # $$ # # The following code solves these equations, and was largely written by Phil Chang. # In[2]: # Step 2: The TOV equations ## TOV SOLVER FOR SINGLE AND PIECEWISE POLYTROPES ## Authors: Phil Chang, Zachariah B. Etienne, Leo Werneck # Full documentation for this module may be found in the NRPy+ tutorial Jupyter notebook: # Tutorial-Start_to_Finish-BSSNCurvilinear-Setting_up_TOV_initial_data.ipynb # Inputs: # * Output data file name # * rho_baryon_central, the central density of the TOV star. # * n, the polytropic equation of state index. n=1 models cold, degenerate neutron star matter. # * K_Polytrope, the polytropic constant. # * Verbose output toggle (default = True) # Output: An initial data file (default file name = "outputTOVpolytrope.txt") that well # samples the (spherically symmetric) solution both inside and outside the star. # It is up to the initial data module to perform the 1D interpolation to generate # the solution at arbitrary radius. The file has the following columns: # Column 1: Schwarzschild radius # Column 2: rho(r), *total* mass-energy density (as opposed to baryonic rest-mass density) # Column 3: P(r), Pressure # Column 4: m(r), mass enclosed # Column 5: e^{nu(r)}, g_{tt}(r) # Column 6: e^{4 phi(r)}, conformal factor g_{rr}(r) # Column 7: rbar(r), Isotropic radius # rbar refers to the isotropic radius, and # R_Schw refers to the Schwarzschild radius def TOV_Solver(eos, outfile = "outputTOVpolytrope.txt", rho_baryon_central = 0.129285, verbose = True, return_M_and_RSchw = False, accuracy = "medium", integrator_type = "default", no_output_File = False, pressure_renormalization=1): # reset the pressure to stellar oscillations studies def TOV_rhs(r_Schw, y) : # In \tilde units # P = y[0] m = y[1] # nu = y[2] # nu is not needed as input into TOV_rhs rbar = y[3] # Compute rho_b and eps_cold, to be used below # to compute rho_(total) rho_baryon, eps_cold = ppeos.Polytrope_EOS__compute_rhob_and_eps_cold_from_P_cold(eos,P) # with open("rhob_P_cold_and_eps_cold.dat","a+") as file: # file.write(str(r_Schw).format("%.15e")+" "+str(rho_baryon).format("%.15e")+" "+str(P).format("%.15e")+" "+str(eps_cold).format("%.15e")+"\n") # Compute rho, the *total* mass-energy density: # .------------------------------. # | rho = (1 + eps)*rho_(baryon) | # .------------------------------. # with eps = eps_cold, for the initial data. rho = (1.0 + eps_cold)*rho_baryon # m = 4*math.pi/3. * rho*r_Schw**3 if( r_Schw < 1e-4 or m <= 0.): # From https://github.com/natj/tov/blob/master/tov.py#L33: # dPdr = -cgs.G*(eden + P/cgs.c**2)*(m + 4.0*pi*r**3*P/cgs.c**2) # dPdr = dPdr/(r*(r - 2.0*cgs.G*m/cgs.c**2)) dPdrSchw = -(rho + P)*(4.*math.pi/3.*r_Schw*rho + 4.*math.pi*r_Schw*P)/(1.-8.*math.pi*rho*r_Schw*r_Schw) drbardrSchw = 1./(1. - 8.*math.pi*rho*r_Schw*r_Schw)**0.5 else: dPdrSchw = -(rho + P)*(m + 4.*math.pi*r_Schw**3*P)/(r_Schw*r_Schw*(1.-2.*m/r_Schw)) drbardrSchw = 1./(1. - 2.*m/r_Schw)**0.5*rbar/r_Schw dmdrSchw = 4.*math.pi*r_Schw*r_Schw*rho dnudrSchw = -2./(P + rho)*dPdrSchw return [dPdrSchw, dmdrSchw, dnudrSchw, drbardrSchw] def integrateStar( eos, P, dumpData = False ): if accuracy == "medium": min_step_size = 1e-5 max_step_size = 1e-2 integrator = 'dop853' elif accuracy == "low": min_step_size = 1e-3 max_step_size = 1e-1 integrator = 'dopri5' elif accuracy == "verylow": min_step_size = 1e-1 max_step_size = 5e-1 integrator = 'dopri5' elif accuracy == "high": min_step_size = 1e-5 max_step_size = 1e-5 integrator = 'dop853' elif accuracy == "veryhigh": min_step_size = 1e-7 max_step_size = 1e-6 integrator = 'dop853' else: print("Unknown accuracy option: "+str(accuracy)) if integrator_type == "default": pass else: integrator = integrator_type integrator = si.ode(TOV_rhs).set_integrator(integrator)#,rtol=1e-4,atol=1e-4) # integrator = si.ode(TOV_rhs).set_integrator('dopri5',rtol=1e-4) y0 = [P, 0., 0., 0.] r_Schw = 0. integrator.set_initial_value(y0,r_Schw) dr_Schw = min_step_size P = y0[0] PArr = [] r_SchwArr = [] mArr = [] nuArr = [] rbarArr = [] while integrator.successful() and P > 1e-19*y0[0] : P, m, nu, rbar = integrator.integrate(r_Schw + dr_Schw) # Update the value of r_Schw to the latest integrated value r_Schw += dr_Schw dPdrSchw, dmdrSchw, dnudrSchw, drbardrSchw = TOV_rhs( r_Schw, [P,m,nu,rbar]) dr_Schw = 0.1*min(abs(P/dPdrSchw), abs(m/dmdrSchw)) dr_Schw = min(dr_Schw, max_step_size) PArr.append(P) r_SchwArr.append(r_Schw) mArr.append(m) nuArr.append(nu) rbarArr.append(rbar) M = mArr[-1] R_Schw = r_SchwArr[-1] if no_output_File == True: return R_Schw, M # Apply integration constant to ensure rbar is continuous across TOV surface for ii in range(len(rbarArr)): rbarArr[ii] *= 0.5*(np.sqrt(R_Schw*(R_Schw - 2.0*M)) + R_Schw - M) / rbarArr[-1] nuArr_np = np.array(nuArr) # Rescale solution to nu so that it satisfies BC: exp(nu(R))=exp(nutilde-nu(r=R)) * (1 - 2m(R)/R) # Thus, nu(R) = (nutilde - nu(r=R)) + log(1 - 2*m(R)/R) nuArr_np = nuArr_np - nuArr_np[-1] + math.log(1.-2.*mArr[-1]/r_SchwArr[-1]) r_SchwArrExtend_np = 10.**(np.arange(0.01,5.0,0.01))*r_SchwArr[-1] r_SchwArr.extend(r_SchwArrExtend_np) mArr.extend(r_SchwArrExtend_np*0. + M) PArr.extend(r_SchwArrExtend_np*0.) exp2phiArr_np = np.append( np.exp(nuArr_np), 1. - 2.*M/r_SchwArrExtend_np) nuArr.extend(np.log(1. - 2.*M/r_SchwArrExtend_np)) rbarArr.extend( 0.5*(np.sqrt(r_SchwArrExtend_np**2 - 2.*M*r_SchwArrExtend_np) + r_SchwArrExtend_np - M) ) #phiArr_np = np.append( np.exp(nuArr_np), 1. - 2.*M/r_SchwArrExtend_np) # Appending to a Python array does what one would reasonably expect. # Appending to a numpy array allocates space for a new array with size+1, # then copies the data over... over and over... super inefficient. r_SchwArr_np = np.array(r_SchwArr) PArr_np = np.array(PArr) rho_baryonArr_np = np.array(PArr) # This is just to initialize the array for j in range(len(PArr_np)): # Compute rho_b from P rho_baryonArr_np[j] = ppeos.Polytrope_EOS__compute_rhob_from_P_cold(eos,PArr_np[j]) mArr_np = np.array(mArr) rbarArr_np = np.array(rbarArr) confFactor_exp4phi_np = (r_SchwArr_np/rbarArr_np)**2 # Compute the *total* mass-energy density (as opposed to the *baryonic* mass density) rhoArr_np = [] for i in range(len(PArr)): rho_baryon, eps_cold = ppeos.Polytrope_EOS__compute_rhob_and_eps_cold_from_P_cold(eos,PArr[i]) rho = (1.0 + eps_cold ) * rho_baryon rhoArr_np.append(rho) if verbose: print(len(r_SchwArr_np),len(rhoArr_np),len(rho_baryonArr_np),len(PArr_np),len(mArr_np),len(exp2phiArr_np)) PArr_np *= pressure_renormalization # set for pressure renormalization studies # Special thanks to Leonardo Werneck for pointing out this issue with zip() if sys.version_info[0] < 3: np.savetxt(outfile, zip(r_SchwArr_np,rhoArr_np,rho_baryonArr_np,PArr_np,mArr_np,exp2phiArr_np,confFactor_exp4phi_np,rbarArr_np), fmt="%.15e") else: np.savetxt(outfile, list(zip(r_SchwArr_np,rhoArr_np,rho_baryonArr_np,PArr_np,mArr_np,exp2phiArr_np,confFactor_exp4phi_np,rbarArr_np)), fmt="%.15e") return R_Schw, M # Set initial condition from rho_baryon_central P_initial_condition = ppeos.Polytrope_EOS__compute_P_cold_from_rhob(eos, rho_baryon_central) # Integrate the initial condition R_Schw_TOV, M_TOV = integrateStar(eos, P_initial_condition, True) if verbose: print("Just generated a TOV star with R_Schw = %.15e , M = %.15e , M/R_Schw = %.15e ." %(R_Schw_TOV,M_TOV,(M_TOV / R_Schw_TOV))) if return_M_and_RSchw: return M_TOV, R_Schw_TOV ############################ # Single polytrope example # ############################ # Set neos = 1 (single polytrope) neos = 1 # Set rho_poly_tab (not needed for a single polytrope) rho_poly_tab = [] # Set Gamma_poly_tab Gamma_poly_tab = [2.0] # Set K_poly_tab0 K_poly_tab0 = 1. # ZACH NOTES: CHANGED FROM 100. # Set the eos quantities eos = ppeos.set_up_EOS_parameters__complete_set_of_input_variables(neos,rho_poly_tab,Gamma_poly_tab,K_poly_tab0) # Set initial condition (Pressure computed from central density) rho_baryon_central = 0.129285 M_TOV, R_Schw_TOV = TOV_Solver(eos,outfile="outputTOVpolytrope.txt",rho_baryon_central=0.129285,verbose = True, return_M_and_RSchw=True,accuracy="medium",integrator_type="default", pressure_renormalization=1.0) # # # # Step 3: Code Validation \[Back to [top](#toc)\] # $$\label{code_validation}$$ # # Here, as a code validation check, we verify agreement in the SymPy expressions for these TOV initial data between # # 1. this tutorial and # 2. the NRPy+ [TOV.TOV_Solver](../edit/TOV/TOV_Solver.py) module. # In[3]: # Step 3: Code Validation against TOV.TOV_Solver module import filecmp import TOV.TOV_Solver as TOV TOV.TOV_Solver(eos, outfile="outputTOVpolytrope-validation.txt", rho_baryon_central=0.129285, verbose = True, accuracy="medium", integrator_type="default", no_output_File = False) if filecmp.cmp('outputTOVpolytrope.txt', 'outputTOVpolytrope-validation.txt') == False: print("ERROR: TOV initial data test FAILED!") sys.exit(1) else: print("TOV initial data test PASSED.") # # # # Step 4: Output this notebook to $\LaTeX$-formatted PDF file \[Back to [top](#toc)\] # $$\label{latex_pdf_output}$$ # # The following code cell converts this Jupyter notebook into a proper, clickable $\LaTeX$-formatted PDF file. After the cell is successfully run, the generated PDF may be found in the root NRPy+ tutorial directory, with filename # [Tutorial-ADM_Initial_Data-TOV](Tutorial-ADM_Initial_Data-TOV.pdf) (Note that clicking on this link may not work; you may need to open the PDF file through another means.) # In[4]: import cmdline_helper as cmd # NRPy+: Multi-platform Python command-line interface cmd.output_Jupyter_notebook_to_LaTeXed_PDF("Tutorial-ADM_Initial_Data-TOV")