This code calculates the vertical and radial structure of accretion
- Installation
- Calculate structure
- Irradiated discs
- Structure Choice
- Vertical and radial profile calculation, S-curves
- Physical background
- Calculation tips and tricks
- BibTeX
If you only need analytical approximation for opacity and equation of state, choose this installation way.
- Create and activate virtual environment
$ python3 -m venv ~/.venv/vs
$ source ~/.venv/vs/bin/activate
- Update pip and setuptools
$ pip3 install -U pip setuptools
- Install 'alpha_disc' package
$ pip3 install git+https://github.com/AndreyTavleev/AlphaDiSC.git
- Run Python script to calculate a simple structure:
$ python3 -m alpha_disc.vs
Finding Pi parameters of structure and making a structure plot.
Structure with opacity laws from (Bell & Lin, 1994) and ideal gas EOS.
M = 1.98841e+34 grams = 10 M_sun
r = 1.1813e+09 cm = 400 rg
alpha = 0.01
Mdot = 1e+18 g/s
The vertical structure has been calculated successfully.
Pi parameters = [5.53427755 0.56016427 1.19857844 0.41824962]
z0/r = 0.05768581641820652
Prad/Pgas_c = 0.9450305089292704
Plot of structure is successfully saved to fig/vs.pdf.
'mesa2py' is used to bind tabular opacities and EoS routines
from MESA code to Python3.
If you want to use tabular values of opacity and EoS to calculate the structure, you should use a Docker image we provide.
The image includes MESA SDK, pre-compiled static C-library binding eos
and kappa
MESA modules, and Python binding module.
Download and create an alias of the Docker image:
$ docker pull ghcr.io/andreytavleev/alphadisc:latest
$ docker tag ghcr.io/andreytavleev/alphadisc alphadisc
Or build the Docker image by yourself:
$ git clone https://github.com/AndreyTavleev/AlphaDiSC.git
$ cd AlphaDiSC
$ docker build -t alphadisc .
Then run 'alphadisc' image as a container and try mesa_vs.main()
$ docker run -v$(pwd)/fig:/app/fig --rm -ti alphadisc python3 -m alpha_disc.mesa_vs
Calculating structure and making a structure plot.
Structure with tabular MESA opacity and EoS.
Chemical composition is solar.
M = 1.98841e+34 grams = 10 M_sun
r = 1.1813e+09 cm = 400 rg
alpha = 0.01
Mdot = 1e+18 g/s
The vertical structure has been calculated successfully.
z0/r = 0.04482123680748539
Prad/Pgas_c = 0.13498412232642973
Plot of structure is successfully saved to fig/vs_mesa.pdf.
As the result, the plot vs_mesa.pdf
is saved to the /app/fig/
(/app/
is the working directory)
in the container and to the ./fig/
in the host machine.
Module vs
contains several classes that represent the vertical
structure of accretion discs in X-ray binaries for different analytical approximations
of the opacity law. The classes calculate vertical structure of the disc for given parameters,
and allow to study its viscous stability.
Module mesa_vs
contains some additional classes, that represent
the vertical structure for tabular opacities, EoS, and convective energy transport.
Both vs
and mesa_vs
modules have Python docstrings with description of available structure classes,
use Python's build-in help
function to read them:
from alpha_disc import vs, mesa_vs
help(vs)
help(mesa_vs)
from alpha_disc import vs
# Input parameters:
M = 2e33 # Mass of central object in grams
alpha = 0.01 # alpha parameter
r = 2e9 # radius in cm
F = 2e34 # viscous torque
mu = 0.6 # molecular weight
# Structure with (Bell & Lin, 1994) power-law opacities and ideal gas EoS
# with molecular weight mu and radiative temperature gradient.
vertstr = vs.IdealBellLin1994VerticalStructure(Mx=M, alpha=alpha, r=r, F=F, mu=mu) # create the structure object
# the estimation of z0r free parameter is calculated automatically (default)
z0r, result = vertstr.fit(z0r_estimation=None) # calculate structure
varkappa_C, rho_C, T_C, P_C, Sigma0 = vertstr.parameters_C()
print(z0r) # semi-thickness z0/r of disc
print(varkappa_C, rho_C, T_C, P_C) # Opacity, bulk density, temperature and gas pressure in the symmetry plane of disc
print(Sigma0) # Surface density of disc
print(vertstr.tau()) # optical thickness of disc
$ docker run --rm -ti alphadisc python3
from alpha_disc import mesa_vs
# Input parameters:
M = 2e33 # Mass of central object in grams
alpha = 0.01 # alpha parameter
r = 2e9 # radius in cm
F = 2e34 # viscous torque
# Structure with tabular MESA opacities and EoS
# and both radiative and convective energy transport,
# default chemical composition is solar.
vertstr = mesa_vs.MesaVerticalStructureRadConv(Mx=M, alpha=alpha, r=r, F=F) # create the structure object
z0r, result = vertstr.fit() # calculate structure
varkappa_C, rho_C, T_C, P_C, Sigma0 = vertstr.parameters_C()
print(z0r) # semi-thickness z0/r of disc
print(varkappa_C, rho_C, T_C, P_C) # Opacity, bulk density, temperature and gas pressure in the symmetry plane of disc
print(Sigma0) # Surface density of disc
print(vertstr.tau()) # optical thickness of disc
You can run your own files inside Docker
$ docker run -v/path/to/fig:/app/fig \
-v/path_to/your_file/file.py:/app/file.py \
--rm -ti alphadisc python3 file.py
In this example, the files produce with the code should be saved
in the folder fig
(/app/fig/
inside the Docker working
directory), which is also mounted using the -v
Docker flag, see
details in the Docker documentation.
Module mesa_vs
also contains classes that represent
the vertical structure of self-irradiated discs. Description of structure classes
with irradiation is available in mesa_vs
help.
from alpha_disc import mesa_vs
help(mesa_vs)
Irradiation can be taken into account in two ways:
- Via either irradiation temperature
T_irr
$(T_{\rm irr})$ or irradiation parameterC_irr
$(C_{\rm irr})$ . This case corresponds to a simple model for the irradiation: the external flux doesn't penetrate into the disc and only heats the disc surface.$T_{\rm irr}$ and$C_{\rm irr}$ relate as following:
where
2. In the second approximation the external flux is penetrated into the disc and affect the energy flux
and disc temperature. In this case there are more additional parameters are required, which describe
the incident spectral flux:
Such parameters are: frequency range nu_irr
, units of frequency range
spectrum_irr_par
, spectrum spectrum_irr
, luminosity of irradiation source L_X_irr
and the cosine of incident angle cos_theta_irr
.
- Frequency range
nu_irr
is an array-like and can be either in Hz (spectrum_irr_par='nu'
) or in kEV (spectrum_irr_par='E_in_keV'
). - Spectrum
spectrum_irr
can be either an array-like or a Python function.- If
spectrum_irr
is an array-like, it must be in 1/Hz or in 1/keV depending on 'spectrum_irr_par', must be normalised to unity, and its size must be equal tonu_irr.size
. - If
spectrum_irr
is a Python function, the spectrum is calculated for the frequency rangenu_irr
and automatically normalised to unity overnu_irr
. Note, that units ofnu_irr
and units ofspectrum_irr
arguments must be consistent. There are two optional parametersargs_spectrum_irr
andkwargs_spectrum_irr
for arguments (keyword arguments) of spectrum function.
- If
- Cosine of incident angle
cos_theta_irr
can be either exact value orNone
. In the latter case cosine is calculated self-consistently ascos_theta_irr_exp * z0 / r
, wherecos_theta_irr_exp
is additional parameter, namely the$({\rm d}\ln z_0/{\rm d}\ln r - 1)$ derivative.
from alpha_disc import mesa_vs
# Input parameters:
M = 2e33 # Mass of central object in grams
alpha = 0.01 # alpha parameter
r = 2e9 # radius in cm
F = 2e34 # viscous torque
Tirr = 1.2e4 # irradiation temperature
# Structure with tabular MESA opacities and EoS,
# both radiative and convective energy transport,
# default chemical composition is solar,
# external irradiation is taken into account
# through the simple scheme.
vertstr = mesa_vs.MesaVerticalStructureRadConvExternalIrradiationZeroAssumption(
Mx=M, alpha=alpha, r=r, F=F, T_irr=Tirr) # create the structure object
z0r, result = vertstr.fit() # calculate structure
varkappa_C, rho_C, T_C, P_C, Sigma0 = vertstr.parameters_C()
print(z0r) # semi-thickness z0/r of disc
print(varkappa_C, rho_C, T_C, P_C) # Opacity, bulk density, temperature and gas pressure in the symmetry plane of disc
print(Sigma0) # Surface density of disc
print(vertstr.tau()) # optical thickness of disc
print(vertstr.C_irr) # corresponding irradiation parameter
Define the incident X-ray spectrum as a function of energy in keV:
def power_law_exp_spectrum(E, n, scale):
return (E / scale) ** n * np.exp(-E / scale)
Then calculate structure with this spectrum:
from alpha_disc import mesa_vs
import numpy as np
# Input parameters:
M = 2e33 # Mass of central object in grams
alpha = 0.01 # alpha parameter
r = 4e9 # radius in cm
F = 2e34 # viscous torque
nu_irr = np.geomspace(1, 10, 30) # energy range from 1 to 10 keV
spectrum_par = 'E_in_keV' # units of nu_irr if energy in keV
kwargs={'n': -0.4, 'scale': 8} # spectrum function parameters
cos_theta_irr = None # incident angle is calculated self-consistently
cos_theta_irr_exp = 1 / 12 # as cos_theta_irr_exp * z0 / r
L_X_irr = 1.0 * 1.25e38 * M / 2e33 # luminosity of X-ray source = 1.0 * L_eddington
# Structure with tabular MESA opacities and EoS,
# both radiative and convective energy transport,
# default chemical composition is solar,
# external irradiation is taken into account
# through the advanced scheme.
vertstr = mesa_vs.MesaVerticalStructureRadConvExternalIrradiation(
Mx=M, alpha=alpha, r=r, F=F, nu_irr=nu_irr,
spectrum_irr=power_law_exp_spectrum, L_X_irr=L_X_irr,
spectrum_irr_par=spectrum_par,
kwargs_spectrum_irr=kwargs,
cos_theta_irr=cos_theta_irr,
cos_theta_irr_exp=cos_theta_irr_exp) # create the structure object
# let us set the free parameters estimations
result = vertstr.fit(z0r_estimation=0.07, Sigma0_estimation=1020) # calculate structure
# if structure is fitted successfully, cost function must be less than 1e-16
print(result.cost) # cost function
z0r, Sigma0 = result.x # Sigma0 is additional free parameter to find
varkappa_C, rho_C, T_C, P_C, Sigma0 = vertstr.parameters_C()
print(z0r) # semi-thickness z0/r of disc
print(varkappa_C, rho_C, T_C, P_C) # Opacity, bulk density, temperature and gas pressure in the symmetry plane of disc
print(Sigma0) # Surface density of disc
print(vertstr.tau()) # optical thickness of disc
print(vertstr.C_irr, vertstr.T_irr) # irradiation parameter and temperature
Module profiles
, containing StructureChoice()
function, serves as interface for creating
the right structure object in a simpler way. The input parameter of all structure classes is F
- viscous torque.
One can use other input parameters instead viscous torque F
(such as effective temperature input
parameter, and choose the structure type using structure
parameter. The relation between
where F_in
parameter) for correct calculation of the relation above.
Usage:
from alpha_disc.profiles import StructureChoice
# Input parameters:
M = 2e33 # Mass of central object in grams
alpha = 0.01 # alpha parameter
r = 4e9 # radius in cm
input = 'Teff' # the input parameter will be effective temperature
Par = 1e4 # instead of viscous torque F
structure = 'BellLin' # type of structure
mu = 0.6 # mean molecular weight for this type
# Returns the instance of chosen structure type.
# Also returns viscous torque F, effective temperature Teff and accretion rate Mdot.
vertstr, F, Teff, Mdot = StructureChoice(M=M, alpha=alpha, r=r, Par=Par,
input=input, structure=structure, mu=mu)
z0r, result = vertstr.fit() # calculate the structure
The StructureChoice
function has the detailed documentation
from alpha_disc import profiles
help(profiles.StructureChoice)
where, in particular, you can find the available structure types (structure
parameter).
Module profiles
contains functions, that calculate vertical and radial disc profiles and S-curves, and return tables
with disc parameters. With profiles.main()
the vertical structure, S-curve and radial structure
can be calculated for default parameters, stored as tables and plots:
$ python3 -m alpha_disc.profiles
profiles
contains three functions: Vertical_Profile
, S_curve
and Radial_Profile
.
Vertical_Profile
returns table with parameters of disc as functions of vertical coordinate at specific radius.S_curve
calculates S-curve and return table with disc parameters on the curve.Radial_Profile
returns table with parameters of disc as functions of radius for a given radius range.
from alpha_disc import profiles
# Input parameters:
M = 5 * 2e33 # 5 * M_sun
alpha = 0.1
r = 1e10
Teff = 1e4
profiles.Vertical_Profile(M, alpha, r, Teff, input='Teff', structure='BellLin', mu=0.62,
n=100, add_Pi_values=True, path_dots='vs.dat')
profiles.S_curve(4e3, 1e4, M, alpha, r, input='Teff', structure='BellLin', mu=0.62,
n=200, tau_break=True, path_dots='S-curve.dat', add_Pi_values=True)
r_start, r_end = 1e9, 1e12
profiles.Radial_Profile(M, alpha, r_start, r_end, Par=1, input='Mdot_Mdot_edd', structure='BellLin', mu=0.62,
n=200, tau_break=True, path_dots='radial_struct.dat', add_Pi_values=True)
Both profiles
module and functions in it have help
from alpha_disc import profiles
help(profiles)
help(profiles.Vertical_Profile)
help(profiles.S_curve)
help(profiles.Radial_Profile)
The vertical structure of accretion disc is described by system of four ordinary differential equations with four boundary conditions at the disc surface and one additional boundary condition at the central plane for flux:
Here
The temperature gradient
where radiation gradient
Temperature gradient in the presence of convection is calculated according to the mixing length theory (see Kippenhahn et al. 2012):
where
Here
where eos
module of the MESA code.
After the normalizing t
variable), one has:
The initial boundary condition for gas pressure
Characteristic values of pressure, temperature and mass coordinate are as follows:
(i). Via either irradiation temperature
(ii). In the second approximation the external flux is penetrated into the disc and affect the energy flux and disc temperature. In this case following equations and boundary conditions change their form:
where
However, the atmosphere model is unspecified, so
Equation of state (EoS) and opacity law:
can be set both analytically or by tabular values. For analytical description, the ideal gas equation is adopted:
where mu
of Structure class.
An analytic opacity coefficient is approximated by a power-law function:
There are following analytic opacity options:
- Kramers law for solar composition:
$(\zeta = 1, \gamma = -7/2, \varkappa_0 = 5\cdot10^{24})$ and Thomson electron scattering$(\varkappa_{\rm R} = 0.34)$ . - Two analytic approximations by Bell & Lin (1994) to opacity:
opacity from bound-free and free-free transitions
$(\varkappa_0 = 1.5\cdot10^{20}, \zeta = 1, \gamma = -5/2)$ , opacity from scattering off hydrogen atoms$(\varkappa_0 = 1\cdot10^{-36}, \zeta = 1/3, \gamma = 10)$ and Thomson electron scattering$(\varkappa_{\rm R} = 0.34)$ .
Tabular values of opacity and EoS are obtained by interpolation using eos
and kappa
modules from
the MESA code. In this case the additional input parameter is abundance
- the chemical composition of the disc
matter. It should be a dictionary with format {'isotope_name': abundance}, e.g. {'h1': 0.7, 'he4': 0.3}
, look for full
list of available isotopes in the MESA source code. Also, you can use 'solar'
string to set the solar composition.
System has a single free parameter
In the presence of external irradiation in scheme (i), the only change is the boundary condition
for temperature. If irradiation is calculated through the advanced scheme (ii), the second free parameter is
Code was tested for disc
If during the fitting process PgasPradNotConvergeError
exception is raised failing the calculation. In this case we recommend to set manually the estimation of 'z0r' free parameter (usually smaller one). Also, you can get additional information about the value of the free parameter during the fitting process through verbose
parameter:
verstr = ... # definition of the structure class
# let us set the free parameter estimation
# and print value of z0r parameter at each fitting iteration
z0r, result = vertstr.fit(z0r_estimation=0.05, verbose=True)
Note, that the higher
If during the fitting process PgasPradNotConvergeError
exception is raised. In this case we also recommend setting 'z0r' free parameter initial guess manually.
Another reason of calculation failure concerns the calculation of PphNotConvergeError
exception is raised. We recommend to set P_ph_0
parameter initial guess manually (usually higher):
verstr = ... # definition of the structure class
# let us set the free parameter estimation
# and set the estimation of pressure boundary condition
# and print value of z0r parameter at each fitting iteration
result = vertstr.fit(z0r_estimation=0.05, verbose=True, P_ph_0=1e5)
This case is similar to one for scheme (i), and the ways solving the convergence problem are almost the same. The main difference is the presence of the second free parameter of system Sigma0_par
, the surface density IrrNotConvergeError
exception is raised. We recommend to set initial guesses for the free parameters manually (usually smaller z0r
and higher Sigma0_par
) as well as the P_ph_0
parameter.
One specific case is unstable disc region, where the code may converge in both hot and cold disc state. Here, even for right z0r
and Sigma0_par
values, the wrong P_ph_0
estimation (much higher or smaller).
verstr = ... # definition of the structure class
# let us set the free parameters estimations
# and set the estimation of pressure boundary condition
# and print value of free parameters at each fitting iteration
result = vertstr.fit(z0r_estimation=0.07, Sigma0_estimation=5000, verbose=True, P_ph_0=1e5)
Radial profile calculates from r_start
to r_end
, and r_start
should be less than r_end
. Similarly, S-curve calculates from Par_max
to Par_min
(obviously, Par_max
> Par_min
). The estimations of all necessary parameters at the next point are taken from the previous points. Additionally, estimations of the parameters at the first point can be set manually. Usually this works well, but regions of small radii (big effective temperature, or accretion rate etc. for S-curve) may not converge due to high
In this case we recommend calculating such 'bad' regions again, but vise versa from higher radius (smaller effective temperature etc.), where code has converged, to smaller radius (higher effective temperature etc.). Then the initial guesses of the free parameters would become more suitable for convergence. Such vise versa calculation is easy to do, just swap r_start
and r_end
(Par_max
and Par_min
) so that r_start
> r_end
(Par_max
< Par_min
).
When calculating the profile and S-curve of a disc with irradiation scheme (ii), a discontinuity (gap) in the instability region may occur, resulting in a lack of smooth transition between the 'hot' and 'cold' stable disc regions. This can be identified by code convergence failure after the 'hot' stable disc region, leading to all corresponding points being labeled as 'Non-converged_fits'. To address this, simply calculate the 'cold' region using different 'z0r' and 'Sigma0_par' (and potentially 'P_ph_0') initial guesses.
@ARTICLE{2023MNRAS.524.3647T,
author = {{Tavleev}, A.~S. and {Lipunova}, G.~V. and {Malanchev}, K.~L.},
title = "{Analysis of accretion disc structure and stability using open code for vertical structure}",
journal = {\mnras},
keywords = {accretion, accretion discs, instabilities, X-rays: binaries, Astrophysics - High Energy Astrophysical Phenomena},
year = 2023,
month = sep,
volume = {524},
number = {3},
pages = {3647-3661},
doi = {10.1093/mnras/stad1881},
archivePrefix = {arXiv},
eprint = {2303.02184},
primaryClass = {astro-ph.HE},
adsurl = {https://ui.adsabs.harvard.edu/abs/2023MNRAS.524.3647T},
adsnote = {Provided by the SAO/NASA Astrophysics Data System}
}