"""
pyathena.fields.fields
Registry of derived fields and their plotting metadata.
Each `set_derived_fields_*` function builds and returns six dictionaries:
- func[name] : callable(d, u) -> array-like
- field_dep[name] : set/list of primitive fields required for func[name]
- label[name] : label string (typically LaTeX) for colorbars/axes
- cmap[name] : colormap (string or matplotlib/cmasher colormap)
- vminmax[name] : (vmin, vmax) defaults for visualization
- take_log[name] : bool; if True use LogNorm, else Normalize
"""
import matplotlib as mpl
import numpy as np
import xarray as xr
import astropy.constants as ac
import astropy.units as au
import cmasher as cmr
from matplotlib.colors import Normalize, LogNorm
from ..plt_tools.cmap import cmap_apply_alpha, cmap_shift
from ..microphysics.cool import get_xe_mol
# Optional dependency: xray emissivity helper requires `yt`.
# Keep import soft so users without yt can still use non-Xray functionality.
try:
from .xray_emissivity import get_xray_emissivity
except ModuleNotFoundError:
get_xray_emissivity = None
def static_vars(**kwargs):
"""
Decorator that attaches static attributes to a function.
Used in this file to stash a reference point `x0` on functions that need it.
"""
def decorate(func):
for k in kwargs:
setattr(func, k, kwargs[k])
return func
return decorate
[docs]def set_derived_fields_def(par, x0):
"""
Function to define derived fields info, for example,
functions to calculate derived fields, dependency, label, colormap, etc.
May not work correctly for problems using different unit system.
Assume that density = nH, length unit = pc, etc.
This includes:
- density (rho, nH)
- pressure (pok, pok_trbz)
- distance (r)
- velocity (vmag, vr, vx/vy/vz)
- sound speeds (cs, csound)
- momentum density, ram pressure (Mr, Mr_abs, rhovr2ok)
Parameters
----------
par: dict
Dictionary containing simulation parameter information
x0: sequence of floats
Coordinate of the center with respect to which distance is measured
Returns
-------
Tuple of dictionaries containing derived fields info
"""
func = dict()
field_dep = dict()
label = dict()
cmap = dict()
vminmax = dict()
take_log = dict()
# rho [g cm^-3] - gas density
f = 'rho'
field_dep[f] = ['density']
def _rho(d, u):
return d['density']*(u.muH*u.mH).cgs.value
func[f] = _rho
label[f] = r'$\rho\;[{\rm g\,cm^{-3}}]$'
cmap[f] = 'Spectral_r'
vminmax[f] = (1e-27,1e-20)
take_log[f] = True
# nH [cm^-3] (assume that density = nH)
f = 'nH'
field_dep[f] = ['density']
def _nH(d, u):
return d['density']
func[f] = _nH
label[f] = r'$n_{\rm H}\;[{\rm cm^{-3}}]$'
cmap[f] = 'Spectral_r'
vminmax[f] = (1e-3,1e4)
take_log[f] = True
# P/kB [K cm^-3] - thermal pressure
f = 'pok'
field_dep[f] = set(['pressure'])
def _pok(d, u):
return d['pressure']*(u.energy_density/ac.k_B).cgs.value
func[f] = _pok
label[f] = r'$P\;[k_B\,{\rm cm^{-3}\,K}]$'
cmap[f] = 'inferno'
vminmax[f] = (1e2,1e7)
take_log[f] = True
# rho vz^2/kB [K cm^-3]
f = 'pok_trbz'
field_dep[f] = set(['density','velocity'])
def _pok_trbz(d, u):
return d['density']*d['velocity3']**2*(u.energy_density/ac.k_B).cgs.value
func[f] = _pok_trbz
label[f] = r'$P_{\rm turb,z}\;[k_B\,{\rm cm^{-3}\,K}]$'
cmap[f] = 'inferno'
vminmax[f] = (1e2,1e7)
take_log[f] = True
# Distance from x0 [pc]
f = 'r'
field_dep[f] = set(['density'])
@static_vars(x0=x0)
def _r(d, u):
# NOTE: this assumes d provides coordinates 'x','y','z'
z, y, x = np.meshgrid(d['z'], d['y'], d['x'], indexing='ij')
return xr.DataArray(
np.sqrt((x - _r.x0[0])**2 + (y - _r.x0[1])**2 + (z - _r.x0[2])**2),
dims=('z', 'y', 'x'),
name='r',
)
func[f] = _r
label[f] = r'$r\;[{\rm pc}]$'
cmap[f] = 'viridis'
# Set vmax to the largest box dimension
if "domain1" in par:
blockname = "domain1"
elif "mesh" in par:
blockname = "mesh"
else:
raise KeyError('Expected "domain1" or "mesh" in parameter dictionary.')
Lx = par[blockname]['x1max'] - par[blockname]['x1min']
Ly = par[blockname]['x2max'] - par[blockname]['x2min']
Lz = par[blockname]['x3max'] - par[blockname]['x3min']
Lmax = max(max(Lx, Ly), Lz)
vminmax[f] = (0, Lmax)
take_log[f] = False
# Velocity magnitude [km/s]
f = 'vmag'
field_dep[f] = set(['velocity'])
def _vmag(d, u):
return (d['velocity1']**2 + d['velocity2']**2 + d['velocity3']**2)**0.5*u.kms
func[f] = _vmag
label[f] = r'$|\mathbf{v}|\;[{\rm km\,s^{-1}}]$'
vminmax[f] = (0.1, 1000.0)
cmap[f] = cmr.guppy_r
take_log[f] = True
# Radial velocity w.r.t. x0 [km/s]
f = 'vr'
field_dep[f] = set(['velocity'])
@static_vars(x0=x0)
def _vr(d, u):
z, y, x = np.meshgrid(d['z'], d['y'], d['x'], indexing='ij')
r = xr.DataArray(np.sqrt((x - _vr.x0[0])**2 + (y - _vr.x0[1])**2 + (z - _vr.x0[2])**2),
dims=('z','y','x'), name='r')
return (x*d['velocity1'] + y*d['velocity2'] + z*d['velocity3'])/r*u.kms
func[f] = _vr
label[f] = r'$v_r\;[{\rm km\,s^{-1}}]$'
vminmax[f] = (-20, 100)
# Set cmap midpoint accordingly (midpoint=abs(vmin)/(abs(vmin)+abs(vmax))
cmap[f] = cmap_shift(mpl.cm.BrBG,
midpoint=abs(vminmax[f][0]) / \
(abs(vminmax[f][0]) + abs(vminmax[f][1])),
name='cmap_vr')
take_log[f] = False
# Velocity components [km/s]
def _make_vcomp(idx):
def _vcomp(d, u):
return d[f'velocity{idx}'] * u.kms
return _vcomp
for idx, axis in enumerate(('x', 'y', 'z'), start=1):
f = f'v{axis}'
field_dep[f] = set(['velocity'])
func[f] = _make_vcomp(idx)
label[f] = rf'$v_{axis}\;[{{\rm km\,s^{{-1}}}}]$'
cmap[f] = 'bwr'
vminmax[f] = (-100.0, 100.0)
take_log[f] = False
# -------------------------------------------------------------------------
# Sound speeds [km/s]
# -------------------------------------------------------------------------
# cs [km/s]
# JGK: rename to csound_iso?
f = 'cs'
field_dep[f] = set(['pressure','density'])
def _cs(d, u):
return np.sqrt(d['pressure']/d['density'])
func[f] = _cs
label[f] = r'$c_s\;[{\rm km}\,{\rm s}^{-1}]$'
cmap[f] = 'magma'
vminmax[f] = (0.1,1e3)
take_log[f] = True
# cs [km/s] - sound speed
f = 'csound'
field_dep[f] = set(['pressure','density'])
def _csound(d, u):
return np.sqrt(par['problem']['gamma']*d['pressure']/d['density'])
func[f] = _csound
label[f] = r'$c_s\;[{\rm km}\,{\rm s}^{-1}]$'
cmap[f] = 'magma'
vminmax[f] = (0.1,1e3)
take_log[f] = True
# Radial momentum w.r.t. x0 [km/s cm^-3]
f = 'Mr'
field_dep[f] = set(['density','velocity'])
@static_vars(x0=x0)
def _Mr(d, u):
z, y, x = np.meshgrid(d['z'], d['y'], d['x'], indexing='ij')
r = xr.DataArray(np.sqrt((x - _r.x0[0])**2 + (y - _r.x0[1])**2 + (z - _r.x0[2])**2),
dims=('z','y','x'), name='r')
return d['density']*(x*d['velocity1'] + y*d['velocity2'] + z*d['velocity3'])/r*u.kms
func[f] = _Mr
label[f] = r'$p_r\;[{\rm cm^{-3}\,km\,s^{-1}}]$'
vminmax[f] = (-1e5, 1e5)
# Set cmap midpoint accordingly (midpoint=abs(vmin)/(abs(vmin)+abs(vmax))
cmap[f] = cmap_shift(mpl.cm.BrBG,
midpoint=abs(vminmax[f][0]) / \
(abs(vminmax[f][0]) + abs(vminmax[f][1])),
name='cmap_pyathena_Mr')
take_log[f] = False
# Absolute value of radial momentum w.r.t. x0 [km/s cm^-3]
f = 'Mr_abs'
field_dep[f] = set(['density','velocity'])
@static_vars(x0=x0)
def _Mr_abs(d, u):
z, y, x = np.meshgrid(d['z'], d['y'], d['x'], indexing='ij')
r = xr.DataArray(np.sqrt((x - _r.x0[0])**2 + (y - _r.x0[1])**2 + (z - _r.x0[2])**2),
dims=('z','y','x'), name='r')
return np.abs(d['density']*(x*d['velocity1'] + y*d['velocity2'] + z*d['velocity3'])/r)*u.kms
func[f] = _Mr_abs
label[f] = r'$|p_r|\;[{\rm cm^{-3}\,km\,s^{-1}}]$'
vminmax[f] = (1e-2, 1e4)
# Set cmap midpoint accordingly (midpoint=abs(vmin)/(abs(vmin)+abs(vmax))
cmap[f] = cmap_shift(mpl.cm.BrBG,
midpoint=abs(vminmax[f][0]) / \
(abs(vminmax[f][0]) + abs(vminmax[f][1])),
name='cmap_pyathena_Mr_abs')
take_log[f] = True
# rho vr^2 / kB [cm^-3 K]
f = 'rhovr2ok'
field_dep[f] = set(['density','velocity'])
@static_vars(x0=x0)
def _rhovr2ok(d, u):
z, y, x = np.meshgrid(d['z'], d['y'], d['x'], indexing='ij')
r = xr.DataArray(np.sqrt((x - _r.x0[0])**2 + (y - _r.x0[1])**2 + (z - _r.x0[2])**2),
dims=('z','y','x'), name='r')
return (d['density']*u.density.cgs.value)*\
((x*d['velocity1'] + y*d['velocity2'] + z*d['velocity3'])/r*u.velocity.cgs.value)**2/ac.k_B.cgs.value
func[f] = _rhovr2ok
label[f] = r'$\rho v_r^2\;[{\rm cm}^{-3}\,{\rm K}]$'
vminmax[f] = (1e2, 1e7)
# Set cmap midpoint accordingly (midpoint=abs(vmin)/(abs(vmin)+abs(vmax))
cmap[f] = 'inferno'
take_log[f] = True
return func, field_dep, label, cmap, vminmax, take_log
[docs]def set_derived_fields_cooling(par, newcool):
"""
Function to define derived fields info, for example,
functions to calculate derived fields, dependency, label, colormap, etc.
May not work correctly for problems using different unit system.
Assume that density = nH, length unit = pc, etc.
Parameters
----------
par: dict
Dictionary containing simulation parameter information
newcool: bool
Is new cooling turned on?
Returns
-------
Tuple of dictionaries containing derived fields info
"""
func = dict()
field_dep = dict()
label = dict()
cmap = dict()
vminmax = dict()
take_log = dict()
# T [K] - gas temperature
f = 'T'
if newcool:
field_dep[f] = set(['density','pressure','xe','xH2'])
def _T(d, u):
return d['pressure']/(d['density']*(1.1 + d['xe'] - d['xH2']))/\
(ac.k_B/u.energy_density).cgs.value
else:
field_dep[f] = set(['temperature'])
def _T(d, u):
return d['temperature']
func[f] = _T
label[f] = r'$T\;[{\rm K}]$'
cmap[f] = cmap_shift(mpl.cm.RdYlBu_r, midpoint=3./7., name='cmap_pyathena_T')
vminmax[f] = (1e1,1e7)
take_log[f] = True
# Td [K] - dust temperature
if newcool:
f = 'Td'
field_dep[f] = set(['temperature_dust'])
def _Td(d, u):
return d['temperature_dust']
func[f] = _Td
label[f] = r'$T_{\rm d}\;[{\rm K}]$'
cmap[f] = cmap_shift(mpl.cm.RdYlBu_r, midpoint=3./7., name='cmap_pyathena_Td')
vminmax[f] = (1e0,1e2)
take_log[f] = True
# Cooling rate per volume [erg/s/cm^3] - nH^2*Lambda
f = 'cool_rate_cgs'
field_dep[f] = set(['cool_rate'])
def _cool_rate(d, u):
return d['cool_rate']*(u.energy_density/u.time).cgs.value
func[f] = _cool_rate
label[f] = r'$\mathcal{L}\;[{\rm erg}\,{\rm cm^{-3}}\,{\rm s}^{-1}]$'
cmap[f] = 'cubehelix_r'
vminmax[f] = (1e-30,1e-20)
take_log[f] = True
# Heating rate per volume [erg/s/cm^3] - nH*Gamma
f = 'heat_rate_cgs'
field_dep[f] = set(['heat_rate'])
def _heat_rate(d, u):
return d['heat_rate']*(u.energy_density/u.time).cgs.value
func[f] = _heat_rate
label[f] = r'$\mathcal{G}\;[{\rm erg}\,{\rm cm^{-3}}\,{\rm s}^{-1}]$'
cmap[f] = 'cubehelix_r'
vminmax[f] = (1e-30,1e-20)
take_log[f] = True
# Net cooling rate per volume [erg/s/cm^3] - nH^2*Lambda - nH*Gamma
# (averaged over dt_mhd)
f = 'net_cool_rate'
field_dep[f] = set(['net_cool_rate'])
def _net_cool_rate(d, u):
return d['net_cool_rate']
func[f] = _net_cool_rate
label[f] = r'$\mathcal{L}\;[{\rm erg}\,{\rm cm^{-3}}\,{\rm s}^{-1}]$'
cmap[f] = 'bwr_r'
vminmax[f] = (-1e-20,1e-20)
take_log[f] = False
# Cooling efficiency [erg*cm^3/s]
f = 'Lambda_cool'
field_dep[f] = set(['density','cool_rate'])
def _Lambda_cool(d, u):
return d['cool_rate']*(u.energy_density/u.time).cgs.value/d['density']**2
func[f] = _Lambda_cool
label[f] = r'$\Lambda\;[{\rm erg}\,{\rm cm^{3}}\,{\rm s}^{-1}]$'
cmap[f] = 'cubehelix_r'
vminmax[f] = (1e-30,1e-20)
take_log[f] = True
# Specific cooling rate [erg/s/H]
f = 'nHLambda_cool'
field_dep[f] = set(['density','cool_rate'])
def _nHLambda_cool(d, u):
return d['cool_rate']*(u.energy_density/u.time).cgs.value/d['density']
func[f] = _nHLambda_cool
label[f] = r'$n_{\rm H}\Lambda\;[{\rm erg}\,{\rm cm^{3}}\,{\rm s}^{-1}]$'
cmap[f] = 'cubehelix_r'
vminmax[f] = (1e-30,1e-20)
take_log[f] = True
# Specific net cooling rate [erg/s/H]
f = 'nHLambda_cool_net'
field_dep[f] = set(['density','cool_rate','heat_rate'])
def _nHLambda_cool_net(d, u):
return (d['cool_rate'] - d['heat_rate'])*(u.energy_density/u.time).cgs.value/d['density']
func[f] = _nHLambda_cool_net
label[f] = r'$n_{\rm H}\Lambda_{\rm net}\;[{\rm erg}\,{\rm cm^{3}}\,{\rm s}^{-1}]$'
cmap[f] = 'cubehelix_r'
vminmax[f] = (1e-30,1e-20)
take_log[f] = True
# Heating efficiency [erg/s/H]
f = 'Gamma_heat'
field_dep[f] = set(['density','heat_rate'])
def _Gamma_heat(d, u):
return d['heat_rate']*(u.energy_density/u.time).cgs.value/d['density']
func[f] = _Gamma_heat
label[f] = r'$\Gamma_{\rm heat}\;[{\rm erg}\,{\rm s}^{-1}]$'
cmap[f] = 'cubehelix_r'
vminmax[f] = (1e-30,1e-20)
take_log[f] = True
# Cooling time [Myr]
f = 't_cool'
field_dep[f] = set(['pressure', 'cool_rate'])
def _t_cool(d, u):
return d['pressure']/d['cool_rate']*u.Myr
func[f] = _t_cool
label[f] = r'$t_{\rm cool}\;[{\rm yr}]$'
cmap[f] = 'cubehelix_r'
vminmax[f] = (1e-4,1e2)
take_log[f] = True
return func, field_dep, label, cmap, vminmax, take_log
[docs]def set_derived_fields_mag(par, x0):
"""
Magnetic-field / MHD derived fields.
"""
func = dict()
field_dep = dict()
label = dict()
cmap = dict()
vminmax = dict()
take_log = dict()
# Magnitude of Alfven velocity [km/s]
f = 'vAmag'
field_dep[f] = set(['density', 'cell_centered_B'])
def _vAmag(d, u):
return (d['cell_centered_B1']**2 +
d['cell_centered_B2']**2 +
d['cell_centered_B3']**2)**0.5/np.sqrt(d['density'])*u.kms
func[f] = _vAmag
label[f] = r'$v_A\;[{\rm km}\,{\rm s}^{-1}]$'
vminmax[f] = (0.1, 1000.0)
cmap[f] = 'cividis'
take_log[f] = True
def _make_vA_component(idx):
"""Return Alfven velocity component vA{idx} in km/s."""
def _vA(d, u):
return d[f'cell_centered_B{idx}']*np.sqrt(d['density'])*u.kms
return _vA
def _make_B_uG_component(idx):
"""Return magnetic field component B{idx} in micro-Gauss."""
def _B(d, u):
return d[f'cell_centered_B{idx}']*np.sqrt(u.energy_density.cgs.value)*np.sqrt(4.0*np.pi)*1e6
return _B
for idx, axis in enumerate(('x', 'y', 'z'), start=1):
# vA components
f = f'vA{axis}'
field_dep[f] = set(['density', 'cell_centered_B'])
func[f] = _make_vA_component(idx)
label[f] = rf'$v_{{A,{axis}}}\;[{{\rm km/s}}]$'
cmap[f] = 'RdBu'
vminmax[f] = (-1e2, 1e2)
take_log[f] = False
# B components
f = f'B{axis}'
field_dep[f] = set(['cell_centered_B'])
func[f] = _make_B_uG_component(idx)
label[f] = rf'$B_{{{axis}}}\;[\mu{{\rm G}}]$'
cmap[f] = 'RdBu'
vminmax[f] = (-1e2, 1e2)
take_log[f] = False
# Magnetic fields magnitude [micro-Gauss]
f = 'Bmag'
field_dep[f] = set(['cell_centered_B'])
def _Bmag(d, u):
return (d['cell_centered_B1']**2 +
d['cell_centered_B2']**2 +
d['cell_centered_B3']**2)**0.5*np.sqrt(u.energy_density.cgs.value)\
*np.sqrt(4.0*np.pi)*1e6
func[f] = _Bmag
label[f] = r'$|\mathbf{B}|\;[\mu{\rm G}]$'
vminmax[f] = (1e-1, 1e2)
cmap[f] = 'cividis'
take_log[f] = True
# Magnetic pressure magnitude [Pmag/kB]
f = 'pok_mag'
field_dep[f] = set(['cell_centered_B'])
def _pok_mag(d, u):
return (d['cell_centered_B1']**2 +
d['cell_centered_B2']**2 +
d['cell_centered_B3']**2)**0.5*(u.energy_density/ac.k_B).cgs.value
func[f] = _pok_mag
label[f] = r'$P_{\rm mag}\;[k_B\,{\rm cm^{-3}\,K}]$'
cmap[f] = 'inferno'
vminmax[f] = (1e2,1e7)
take_log[f] = True
return func, field_dep, label, cmap, vminmax, take_log
[docs]def set_derived_fields_rad(par, x0):
"""Register radiation field derived fields.
Includes normalized FUV (PE and LW band), C ionizing, and H2
dissociating radiation field strengths in Draine ISRF units
(chi_PE, chi_LW, chi_FUV, chi_CI, chi_H2).
Parameters
----------
par : dict
Dictionary containing simulation parameter information.
x0 : sequence of floats
Reference coordinate (unused here, kept for interface consistency).
Returns
-------
tuple
Six dictionaries: (func, field_dep, label, cmap, vminmax, take_log).
"""
func = dict()
field_dep = dict()
label = dict()
cmap = dict()
vminmax = dict()
take_log = dict()
# Dust PE opacity for Z'=1
kappa_dust_PE_def = 418.7
try:
Erad_PE0 = par['cooling']['Erad_PE0']
Erad_LW0 = par['cooling']['Erad_LW0']
except KeyError:
Erad_PE0 = 7.613e-14
Erad_LW0 = 1.335e-14
# Normalized PE radiation field strength (Draine field unit)
f = 'chi_PE'
field_dep[f] = set(['rad_energy_density_PE'])
def _chi_PE(d, u):
return d['rad_energy_density_PE']*(u.energy_density.cgs.value/Erad_PE0)
func[f] = _chi_PE
label[f] = r'$\chi_{\rm PE}$'
cmap[f] = 'viridis'
vminmax[f] = (1e-4,1e4)
take_log[f] = True
# Normalized LW radiation field strength (Draine field unit)
f = 'chi_LW'
field_dep[f] = set(['rad_energy_density_LW'])
def _chi_LW(d, u):
return d['rad_energy_density_LW']*(u.energy_density.cgs.value/Erad_LW0)
func[f] = _chi_LW
label[f] = r'$\chi_{\rm LW}$'
cmap[f] = 'viridis'
vminmax[f] = (1e-4,1e4)
take_log[f] = True
# Normalized FUV radiation field strength (Draine field unit)
f = 'chi_FUV'
field_dep[f] = set(['rad_energy_density_PE','rad_energy_density_LW'])
def _chi_FUV(d, u):
return (d['rad_energy_density_PE'] + d['rad_energy_density_LW'])*\
(u.energy_density.cgs.value/(Erad_PE0 + Erad_LW0))
func[f] = _chi_FUV
label[f] = r'$\chi_{\rm FUV}$'
cmap[f] = 'viridis'
vminmax[f] = (1e-4,1e4)
take_log[f] = True
# Normalized LW radiation field
f = 'chi_CI'
field_dep[f] = set(['rad_energy_density_CI'])
def _chi_CI(d, u):
return d['rad_energy_density_CI']*(u.energy_density.cgs.value/Erad_LW0)
func[f] = _chi_CI
label[f] = r'$\chi_{\rm C^0}$'
cmap[f] = 'viridis'
vminmax[f] = (1e-8,1e4)
take_log[f] = True
# Normalized LW radiation field strength (Draine ISRF)
f = 'chi_H2'
field_dep[f] = set(['rad_energy_density_LW_diss'])
def _chi_H2(d, u):
return d['rad_energy_density_LW_diss']*(u.energy_density.cgs.value/Erad_LW0)
func[f] = _chi_H2
label[f] = r'$\chi_{\rm H_2}$'
cmap[f] = 'viridis'
vminmax[f] = (1e-8,1e4)
take_log[f] = True
return func, field_dep, label, cmap, vminmax, take_log
[docs]def set_derived_fields_newcool(par, x0):
"""Register new-cooling (NCR) chemistry derived fields.
Includes temperature, molecular/atomic hydrogen fractions and densities
(T, xH2, nH2, xHI, nHI, xe), carbon/oxygen species (xCII, nCII, xCI,
nCI, xCO, nCO, xOI), cosmic-ray ionization rate, and cooling/heating rates.
Parameters
----------
par : dict
Dictionary containing simulation parameter information.
x0 : sequence of floats
Reference coordinate (unused here, kept for interface consistency).
Returns
-------
tuple
Six dictionaries: (func, field_dep, label, cmap, vminmax, take_log).
"""
func = dict()
field_dep = dict()
label = dict()
cmap = dict()
vminmax = dict()
take_log = dict()
# set metallicity and standard abundances
try:
Zgas = par['problem']['Z_gas']
Zdust = par['problem']['Z_dust']
except KeyError:
Zgas = 1.0
Zdust = 1.0
try:
xCstd = par['cooling']['xCstd']
except KeyError:
xCstd = 1.6e-4
print('xCstd not found. Use {:.2g}.'.format(xCstd))
xCtot = Zgas*xCstd
try:
xOstd = Zgas*par['cooling']['xOstd']
except KeyError:
xOstd = 3.2e-4
print('xOstd not found. Use {:.2g}.'.format(xOstd))
xOtot = Zgas*xOstd
# set xi_cr0
try:
if "photchem_ncr" in par:
xi_CR0= par["photchem_ncr"]["xi_cr0"]
else:
xi_CR0 = par['problem']['xi_CR0']
except KeyError:
xi_CR0 = 2.e-16
print('xi_CR0 not found. Use {:.2g}.'.format(xi_CR0))
# temperature
f = "T"
field_dep[f] = set(['density','pressure','xe','xH2'])
def _T(d, u):
return d['pressure']/(d['density']*(1.1 + d['xe'] - d['xH2']))/\
(ac.k_B/u.energy_density).cgs.value
func[f] = _T
label[f] = r'$T\;[{\rm K}]$'
cmap[f] = cmap_shift(mpl.cm.RdYlBu_r, midpoint=3./7., name='cmap_pyathena_T')
vminmax[f] = (1e1,1e7)
take_log[f] = True
# nH2 [cm^-3] (assume d=nH)
f = 'nH2'
field_dep[f] = set(['density', 'xH2'])
def _nH2(d, u):
return d['density']*d['xH2']
func[f] = _nH2
label[f] = r'$n_{\rm H_2}\;[{\rm cm^{-3}}]$'
cmap[f] = cmap_apply_alpha('Greens')
vminmax[f] = (1e0,1e4)
take_log[f] = True
# 2nH2 [cm^-3] (assume d=nH)
f = '2nH2'
field_dep[f] = set(['density', 'xH2'])
def _2nH2(d, u):
return 2.0*d['density']*d['xH2']
func[f] = _2nH2
label[f] = r'$2n_{\rm H_2}\;[{\rm cm^{-3}}]$'
cmap[f] = cmap_apply_alpha('Greens')
vminmax[f] = (1e0,1e4)
take_log[f] = True
# xH2 [cm^-3] (assume d=nH)
f = 'xH2'
field_dep[f] = set(['xH2'])
def _xH2(d, u):
return d['xH2']
func[f] = _xH2
label[f] = r'$x_{\rm H_2}$'
cmap[f] = 'viridis'
vminmax[f] = (0,0.5)
take_log[f] = False
# 2xH2 [cm^-3] (assume d=nH)
f = '2xH2'
field_dep[f] = set(['xH2'])
def _2xH2(d, u):
return 2*d['xH2']
func[f] = _2xH2
label[f] = r'$2x_{\rm H_2}$'
cmap[f] = 'viridis'
vminmax[f] = (0,1.0)
take_log[f] = False
# nHI [cm^-3]
f = 'nHI'
field_dep[f] = set(['density', 'xHI'])
def _nHI(d, u):
return d['density']*d['xHI']
func[f] = _nHI
label[f] = r'$n_{\rm H^0}\;[{\rm cm^{-3}}]$'
cmap[f] = cmap_apply_alpha('Blues')
vminmax[f] = (1e-3,1e4)
take_log[f] = True
# xHI
f = 'xHI'
field_dep[f] = set(['xHI'])
def _xHI(d, u):
return d['xHI']
func[f] = _xHI
label[f] = r'$x_{\rm H^0}$'
cmap[f] = 'viridis'
vminmax[f] = (0.0, 1.0)
take_log[f] = False
# nHII [cm^-3]
f = 'nHII'
field_dep[f] = set(['density', 'xHI', 'xH2'])
def _nHII(d, u):
return d['density']*(1.0 - d['xHI'] - 2.0*d['xH2'])
func[f] = _nHII
label[f] = r'$n_{\rm H^+}\;[{\rm cm^{-3}}]$'
cmap[f] = cmap_apply_alpha('Oranges')
vminmax[f] = (1e-3,1e4)
take_log[f] = True
# xHII
f = 'xHII'
field_dep[f] = set(['xH2','xHI'])
def _xHII(d, u):
return 1.0 - d['xHI'] - 2.0*d['xH2']
func[f] = _xHII
label[f] = r'$x_{\rm H^+}$'
cmap[f] = 'viridis'
vminmax[f] = (0.0, 1.0)
take_log[f] = False
# nHn [cm^-3]
f = 'nHn'
field_dep[f] = set(['density', 'xHI', 'xH2'])
def _nHn(d, u):
return d['density']*(d['xHI'] + 2.0*d['xH2'])
func[f] = _nHn
label[f] = r'$n_{\rm H^0} + 2n_{\rm H_2}\;[{\rm cm^{-3}}]$'
cmap[f] = cmap_apply_alpha('Blues')
vminmax[f] = (1e-3,1e4)
take_log[f] = True
# xn [cm^-3]
f = 'xn'
field_dep[f] = set(['xHI', 'xH2'])
def _xn(d, u):
return d['xHI'] + 2.0*d['xH2']
func[f] = _xn
label[f] = r'$x_{\rm n}$'
cmap[f] = cmap_apply_alpha('YlGn')
vminmax[f] = (0,1)
take_log[f] = False
# ne
f = 'ne'
field_dep[f] = set(['density','xe'])
def _ne(d, u):
return d['density']*d['xe']
func[f] = _ne
label[f] = r'$n_{\rm e}$'
cmap[f] = 'viridis'
vminmax[f] = (1e-4, 1e3)
take_log[f] = True
# nesq
f = 'nesq'
field_dep[f] = set(['density','xe'])
def _nesq(d, u):
return (d['density']*d['xe'])**2
func[f] = _nesq
label[f] = r'$n_{\rm e}^2$'
cmap[f] = 'plasma'
vminmax[f] = (1e-8, 1e6)
take_log[f] = True
# xe
f = 'xe'
field_dep[f] = set(['xe'])
def _xe(d, u):
return d['xe']
func[f] = _xe
label[f] = r'$x_{\rm e}$'
cmap[f] = 'viridis'
vminmax[f] = (1e-6, 1.208)
take_log[f] = True
# xCI - atomic neutral carbon
f = 'xCI'
field_dep[f] = set(['xCI_over_xCtot'])
def _xCI(d, u):
# Apply floor and ceiling
return np.maximum(0.0,np.minimum(xCtot,d['xCI_over_xCtot']*xCtot))
func[f] = _xCI
label[f] = r'$x_{\rm CI}$'
cmap[f] = 'viridis'
vminmax[f] = (0,xCtot)
take_log[f] = False
# nCI
f = 'nCI'
field_dep[f] = set(['density','xCI_over_xCtot'])
def _nCI(d, u):
return d['density']*xCtot*np.maximum(0.0,np.minimum(1.0,d['xCI_over_xCtot']))
func[f] = _nCI
label[f] = r'$x_{\rm CI}$'
cmap[f] = 'viridis'
vminmax[f] = (1e2*xCtot,1e4*xCtot)
take_log[f] = True
# xOII - single ionized oxygen
f = 'xOII'
field_dep[f] = set(['xH2','xHI'])
def _xOII(d, u):
return xOtot*(1.0 - d['xHI'] - 2.0*d['xH2'])
func[f] = _xOII
label[f] = r'$x_{\rm OII}$'
cmap[f] = 'viridis'
vminmax[f] = (0,xOtot)
take_log[f] = False
# xCII - singly ionized carbon
# Use with caution.
# (Do not apply to hot gas and depend on cooling implementation)
f = 'xCII'
field_dep[f] = set(['xe','xH2','xHI','pressure','density','CR_ionization_rate'])
def _xCII(d, u):
d['T'] = d['pressure']/(d['density']*(1.1 + d['xe'] - d['xH2']))/\
(ac.k_B/u.energy_density).cgs.value
xe_mol = get_xe_mol(d['density'],d['xH2'],d['xe'],d['T'],d['CR_ionization_rate'],
Zgas, Zdust)
# Apply floor and ceiling
return np.maximum(0.0,np.minimum(xCtot,
d['xe'] - (1.0 - d['xHI'] - 2.0*d['xH2'])*(1.0 + xOtot) - xe_mol))
func[f] = _xCII
label[f] = r'$x_{\rm CII}$'
cmap[f] = 'viridis'
vminmax[f] = (0,xCtot)
take_log[f] = False
# xCII - singly ionized carbon (use when CR ionization is uniform everywhere)
# Use with caution.
# (Do not apply to hot gas and depend on cooling implementation)
f = 'xCII_alt'
field_dep[f] = set(['xe','xH2','xHI','pressure','density'])
def _xCII_alt(d, u):
d['T'] = d['pressure']/(d['density']*(1.1 + d['xe'] - d['xH2']))/\
(ac.k_B/u.energy_density).cgs.value
xe_mol = get_xe_mol(d['density'],d['xH2'],d['xe'],d['T'],xi_CR0,
Zgas, Zdust)
# Apply floor and ceiling
return np.maximum(0.0,np.minimum(xCtot,
d['xe'] - (1.0 - d['xHI'] - 2.0*d['xH2'])*(1.0 + xOtot) - xe_mol))
func[f] = _xCII_alt
label[f] = r'$x_{\rm CII}$'
cmap[f] = 'viridis'
vminmax[f] = (0,xCtot)
take_log[f] = False
# xi_CR
f = 'xi_CR'
field_dep[f] = set(['CR_ionization_rate'])
def _xi_CR(d, u):
return d['CR_ionization_rate']
func[f] = _xi_CR
label[f] = r'$\xi_{\rm CR}\;[{\rm s}^{-1}]$'
cmap[f] = 'viridis'
vminmax[f] = (1e-17,1e-15)
take_log[f] = True
# T_alt [K]
f = 'T_alt'
field_dep[f] = set(['pressure','density','xe','xH2'])
def _T_alt(d, u):
return d['pressure']*(u.energy_density/ac.k_B).cgs.value/(d['density']*(1.1 + d['xe'] - d['xH2']))
func[f] = _T_alt
label[f] = r'$T_{\rm alt}\;[{\rm K}]$'
cmap[f] = cmap_shift(mpl.cm.RdYlBu_r, midpoint=3./7., name='cmap_pyathena_T')
vminmax[f] = (1e1,1e7)
take_log[f] = True
return func, field_dep, label, cmap, vminmax, take_log
[docs]def set_derived_fields_sixray(par, x0):
"""Register six-ray external radiation field derived fields.
Includes external (attenuated) PE and LW radiation field strengths in
Draine ISRF units (chi_PE_ext, chi_LW_ext, chi_H2_ext).
Parameters
----------
par : dict
Dictionary containing simulation parameter information.
x0 : sequence of floats
Reference coordinate (unused here, kept for interface consistency).
Returns
-------
tuple
Six dictionaries: (func, field_dep, label, cmap, vminmax, take_log).
"""
func = dict()
field_dep = dict()
label = dict()
cmap = dict()
vminmax = dict()
take_log = dict()
try:
Erad_PE0 = par['cooling']['Erad_PE0']
Erad_LW0 = par['cooling']['Erad_LW0']
except KeyError:
Erad_PE0 = 7.613e-14
Erad_LW0 = 1.335e-14
# Normalized FUV radiation field strength (Draine ISRF)
f = 'chi_PE_ext'
field_dep[f] = set(['rad_energy_density_PE_ext'])
def _chi_PE_ext(d, u):
return d['rad_energy_density_PE_ext']*(u.energy_density.cgs.value/Erad_PE0)
func[f] = _chi_PE_ext
label[f] = r'$\chi_{\rm PE,ext}$'
cmap[f] = 'viridis'
vminmax[f] = (1e-4,1e4)
take_log[f] = True
# Normalized LW radiation field
f = 'chi_LW_ext'
field_dep[f] = set(['rad_energy_density_LW_ext'])
def _chi_LW_ext(d, u):
return d['rad_energy_density_LW_ext']*(u.energy_density.cgs.value/Erad_LW0)
func[f] = _chi_LW_ext
label[f] = r'$\chi_{\rm LW,ext}$'
cmap[f] = 'viridis'
vminmax[f] = (1e-4,1e4)
take_log[f] = True
# Normalized FUV radiation field strength (Draine field unit)
f = 'chi_FUV_ext'
field_dep[f] = set(['rad_energy_density_PE_ext','rad_energy_density_LW_ext'])
def _chi_FUV_ext(d, u):
return (d['rad_energy_density_PE_ext'] + d['rad_energy_density_LW_ext'])*\
(u.energy_density.cgs.value/(Erad_PE0 + Erad_LW0))
func[f] = _chi_FUV_ext
label[f] = r'$\chi_{\rm FUV,ext}$'
cmap[f] = 'viridis'
vminmax[f] = (1e-4,1e4)
take_log[f] = True
# Normalized LW radiation field
f = 'chi_CI_ext'
field_dep[f] = set(['rad_energy_density_CI_ext'])
def _chi_CI_ext(d, u):
return d['rad_energy_density_CI_ext']*(u.energy_density.cgs.value/Erad_LW0)
func[f] = _chi_CI_ext
label[f] = r'$\chi_{\rm CI,ext}$'
cmap[f] = 'viridis'
vminmax[f] = (1e-4,1e4)
take_log[f] = True
# Normalized LW radiation field strength (Draine ISRF)
f = 'chi_H2_ext'
field_dep[f] = set(['rad_energy_density_LW_diss_ext'])
def _chi_H2_ext(d, u):
return d['rad_energy_density_LW_diss_ext']*(u.energy_density.cgs.value/Erad_LW0)
func[f] = _chi_H2_ext
label[f] = r'$\chi_{\rm H2,ext}$'
cmap[f] = 'viridis'
vminmax[f] = (1e-8,1e2)
take_log[f] = True
return func, field_dep, label, cmap, vminmax, take_log
[docs]def set_derived_fields_xray(par, x0, newcool):
"""Register X-ray emissivity derived fields.
Registers the X-ray emissivity field (j_X) in the 0.5–7 keV band,
computed using APEC emissivity tables via the ``yt`` library.
.. note::
Requires the optional dependency ``yt``. Raises
:exc:`ModuleNotFoundError` if ``yt`` is not installed.
Parameters
----------
par : dict
Dictionary containing simulation parameter information.
x0 : sequence of floats
Reference coordinate (unused here, kept for interface consistency).
newcool : bool
If ``True``, use new-cooling chemistry fields for the emissivity
calculation; otherwise use classic fields.
Returns
-------
tuple
Six dictionaries: (func, field_dep, label, cmap, vminmax, take_log).
"""
# Fail fast with a clear message if yt-dependent helper is missing.
if get_xray_emissivity is None:
raise ModuleNotFoundError(
"X-ray derived fields require the optional dependency `yt`.\n"
"Install it with:\n"
" python -m pip install yt\n"
"or:\n"
" conda install -c conda-forge yt"
)
func = dict()
field_dep = dict()
label = dict()
cmap = dict()
vminmax = dict()
take_log = dict()
# TODO-JKIM: Need Z_gas parameter in the problem block
# Or metallicity field
# set metallicity and standard abundances
try:
Z_gas = par['problem']['Z_gas']
except KeyError:
Z_gas = 1.0
emin = 0.5 # keV
emax = 7.0 # keV
energy = True # If set to False, returns photon emissivity [#/s/cm^3]
# Normalized FUV radiation field strength (Draine field unit)
f = 'j_X'
if newcool:
field_dep[f] = set(['density','pressure','xe','xH2'])
else:
field_dep[f] = set(['density','temperature'])
# Frequency integrated volume emissivity
def _j_Xray(d, u):
if newcool:
d['temperature'] = d['pressure']/(d['density']*(1.1 + d['xe'] - d['xH2']))/\
(ac.k_B/u.energy_density).cgs.value
em = get_xray_emissivity(d['temperature'].data, Z_gas,
emin, emax, energy=energy)
return d['density']**2*em
func[f] = _j_Xray
label[f] = r'$j_{\rm X}$ (0.5-7.0 keV) $[{\rm erg}\,{\rm s}^{-1}\,{\rm cm}^{-3}]$'
cmap[f] = 'plasma'
vminmax[f] = (1e-34,1e-25)
take_log[f] = True
return func, field_dep, label, cmap, vminmax, take_log
[docs]def set_derived_fields_cosmic_ray(par):
"""Register cosmic-ray derived fields.
Includes CR parallel diffusion coefficient (sigma_para), CR pressure
(pok_cr), CR energy density (Ec), and CR-related velocity fields.
Parameters
----------
par : dict
Dictionary containing simulation parameter information. Must include
a ``cr`` block with at least ``vmax`` (maximum CR streaming speed
in cm/s).
Returns
-------
tuple
Six dictionaries: (func, field_dep, label, cmap, vminmax, take_log).
"""
func = dict()
field_dep = dict()
label = dict()
cmap = dict()
vminmax = dict()
take_log = dict()
gamma_cr = 4./3.
vmax = par["cr"]["vmax"] # in cm/s
# cr diffusion coefficient
f = 'sigma_para'
field_dep[f] = set(['0-Sigma_diff1'])
def _sigma_para(d, u):
units = (u.length**2/u.time).cgs.value
vmax_ = vmax/(u.cm/u.s)
return d['0-Sigma_diff1']/vmax_/units
func[f] = _sigma_para
label[f] = r'$\sigma_\parallel\;[{\rm cm^{-2}\,s}]$'
cmap[f] = "viridis"
vminmax[f] = [6e-30,1.5e-27]
take_log[f] = True
# cr pressure
f = 'pok_cr'
field_dep[f] = set(['0-Ec'])
def _pok_cr(d, u):
return d['0-Ec']*(gamma_cr-1)*(u.energy_density/ac.k_B).cgs.value
func[f] = _pok_cr
label[f] = r'$P_{\rm cr}\;[k_B\,{\rm cm^{-3}\,K}]$'
cmap[f] = 'YlOrRd_r'
vminmax[f] = (1,5e4)
take_log[f] = True
# cr fluxes
f = 'Fcr1'
field_dep[f] = set(['0-Fc1'])
def _Fcr1(d, u):
vmax_ = vmax/(u.cm/u.s)
return d['0-Fc1']*vmax_*u.erg/u.kpc**2/(1.e6*u.Myr)
func[f] = _Fcr1
label[f] = r'$F_{\rm cr,x}\;[{\rm erg\,kpc^{-2}\,yr^{-1}}]$'
cmap[f] = 'pink'
vminmax[f] = (5e43,2.5e46)
take_log[f] = True
f = 'Fcr2'
field_dep[f] = set(['0-Fc2'])
def _Fcr2(d, u):
vmax_ = vmax/(u.cm/u.s)
return d['0-Fc2']*vmax_*u.erg/u.kpc**2/(1.e6*u.Myr)
func[f] = _Fcr2
label[f] = r'$F_{\rm cr,y}\;[{\rm erg\,kpc^{-2}\,yr^{-1}}]$'
cmap[f] = 'pink'
vminmax[f] = (5e43,2.5e46)
take_log[f] = True
f = 'Fcr3'
field_dep[f] = set(['0-Fc3'])
def _Fcr3(d, u):
vmax_ = vmax/(u.cm/u.s)
return d['0-Fc3']*vmax_*u.erg/u.kpc**2/(1.e6*u.Myr)
func[f] = _Fcr3
label[f] = r'$F_{\rm cr,z}\;[{\rm erg\,kpc^{-2}\,yr^{-1}}]$'
cmap[f] = 'pink'
vminmax[f] = (5e43,2.5e46)
take_log[f] = True
f = 'Fcr_mag'
field_dep[f] = set(['0-Fc1','0-Fc2','0-Fc3'])
def _Fcr_mag(d, u):
vmax_ = vmax/(u.cm/u.s)
Fmag = np.sqrt(d['0-Fc1']**2+d['0-Fc2']**2+d['0-Fc3']**2)
return Fmag*vmax_*u.erg/u.kpc**2/(1.e6*u.Myr)
func[f] = _Fcr_mag
label[f] = r'$F_{\rm cr}\;[{\rm erg\,kpc^{-2}\,yr^{-1}}]$'
cmap[f] = 'pink'
vminmax[f] = (5e43,2.5e46)
take_log[f] = True
# Ion Alfven speed
f = 'VAi_mag'
field_dep[f] = set(['0-Vs1','0-Vs2','0-Vs3'])
def _VAi_mag(d, u):
vmag = np.sqrt(d['0-Vs1']**2+d['0-Vs2']**2+d['0-Vs3']**2)
return vmag*u.kms
func[f] = _VAi_mag
label[f] = r'$|v_{\rm A,i}|\;[{\rm km\,s^{-1}}]$'
cmap[f] = 'managua_r'
vminmax[f] = (2,250)
take_log[f] = True
f = 'VAi1'
field_dep[f] = set(['0-Vs1','0-Vs2','0-Vs3'])
def _VAi1(d, u):
return d["0-Vs1"]*u.kms
func[f] = _VAi1
label[f] = r'$v_{\rm A,i,x}\;[{\rm km\,s^{-1}}]$'
cmap[f] = 'bwr'
vminmax[f] = (-100.0,100.0)
take_log[f] = False
f = 'VAi2'
field_dep[f] = set(['0-Vs1','0-Vs2','0-Vs3'])
def _VAi2(d, u):
return d["0-Vs2"]*u.kms
func[f] = _VAi2
label[f] = r'$v_{\rm A,i,y}\;[{\rm km\,s^{-1}}]$'
cmap[f] = 'bwr'
vminmax[f] = (-100.0,100.0)
take_log[f] = False
f = 'VAi3'
field_dep[f] = set(['0-Vs1','0-Vs2','0-Vs3'])
def _VAi3(d, u):
return d["0-Vs3"]*u.kms
func[f] = _VAi3
label[f] = r'$v_{\rm A,i,z}\;[{\rm km\,s^{-1}}]$'
cmap[f] = 'bwr'
vminmax[f] = (-100.0,100.0)
take_log[f] = False
# Vcr
f = 'Vcr_mag'
field_dep[f] = set(['0-Fc1','0-Fc2','0-Fc3','0-Ec'])
def _Vcr(d, u):
vmax_ = vmax/(u.cm/u.s)
Fmag = np.sqrt(d['0-Fc1']**2+d['0-Fc2']**2+d['0-Fc3']**2)
return Fmag*vmax_/(4/3.*d['0-Ec'])*u.kms
func[f] = _Vcr
label[f] = r'$|v_{\rm eff}|\;[{\rm km\,s^{-1}}]$'
cmap[f] = 'cool'
vminmax[f] = (2,250)
take_log[f] = True
# CR scalar
f = "rCR"
field_dep[f] = set(["rCR"])
def _rCR(d, u):
return d["rCR"]
func[f] = _rCR
label[f] = r'$r_{\rm CR}$'
cmap[f] = cmr.ghostlight
vminmax[f] = (0, 1)
take_log[f] = False
# CR injection
f = "pok_cr_inj"
field_dep[f] = set(["eCRinj"])
def _pok_CR_inj(d, u):
return d["eCRinj"]*(gamma_cr-1)*u.pok
func[f] = _pok_CR_inj
label[f] = r'$P_{\rm CR,inj}\;[k_B\,{\rm cm^{-3}\,K}]$'
cmap[f] = 'YlOrRd_r'
vminmax[f] = (1,5e4)
take_log[f] = True
return func, field_dep, label, cmap, vminmax, take_log
[docs]def set_derived_fields_feedback_scalars(par):
"""Register feedback scalar derived fields.
Includes gas metallicity (Zgas) and SN ejecta mass fraction (rSN)
derived from passive scalar fields tracked during stellar feedback.
Parameters
----------
par : dict
Dictionary containing simulation parameter information.
Returns
-------
tuple
Six dictionaries: (func, field_dep, label, cmap, vminmax, take_log).
"""
func = dict()
field_dep = dict()
label = dict()
cmap = dict()
vminmax = dict()
take_log = dict()
Zsolar=0.02 # need to get from parameter
# metallicity
f = 'Zgas'
field_dep[f] = set(['rmetal'])
def _Zgas(d, u):
return d['rmetal']/Zsolar
func[f] = _Zgas
label[f] = r'$Z/Z_\odot$'
cmap[f] = cmr.pepper_r
vminmax[f] = [1,10]
take_log[f] = True
# SN ejecta mass fraction
f = 'rSN'
field_dep[f] = set(['rSN'])
def _rSN(d, u):
return d['rSN']
func[f] = _rSN
label[f] = r'$f_{\rm SN}$'
cmap[f] = cmr.bubblegum
vminmax[f] = [0,1]
take_log[f] = False
# mass return fraction
f = 'rret'
field_dep[f] = set(['rret'])
def _rret(d, u):
return d['rret']
func[f] = _rret
label[f] = r'$f_{\rm ret}$'
cmap[f] = cmr.bubblegum
vminmax[f] = [0,1]
take_log[f] = False
return func, field_dep, label, cmap, vminmax, take_log
[docs]class DerivedFields(object):
"""Registry of derived fields for an Athena/Athena++ simulation.
Reads simulation parameters and builds dictionaries of derived field
metadata (compute function, primitive field dependencies, plot label,
colormap, value range, and log-scale flag). The active set of fields
is determined automatically from the ``par`` configuration (e.g. MHD,
cooling, radiation, cosmic rays).
Parameters
----------
par : dict
Simulation parameter dictionary as returned by
:func:`pyathena.read_athinput`.
x0 : array-like of float, optional
Reference position ``[x, y, z]`` used for distance-based fields
such as radial velocity. Default is ``[0.0, 0.0, 0.0]``.
Attributes
----------
func : dict
Maps field name → callable ``f(d, u)`` that computes the field.
field_dep : dict
Maps field name → set of required primitive field names.
label : dict
Maps field name → LaTeX label string for colorbars/axes.
cmap : dict
Maps field name → colormap.
vminmax : dict
Maps field name → ``(vmin, vmax)`` defaults for visualization.
take_log : dict
Maps field name → bool; if ``True`` use log normalization.
norm : dict
Maps field name → :class:`matplotlib.colors.Normalize` instance.
imshow_args : dict
Maps field name → dict of kwargs suitable for imshow/colorbar calls.
dfi : dict
Combined per-field metadata dict (union of all the above).
Examples
--------
>>> from pyathena import read_athinput
>>> from pyathena.fields.fields import DerivedFields
>>> par = read_athinput("/path/to/athinput")
>>> df = DerivedFields(par)
>>> print(list(df.func.keys())[:5])
"""
def __init__(self, par, x0=np.array([0.0,0.0,0.0])):
# Create a dictionary containing all information about derived fields
self.dfi = dict()
athenapp = "mesh" in par
# default configuration
cooling = False
newcool = False
mhd = False
radps = False
sixray = False
wind = False
xray = False
cosmic_ray = False
feedback_scalars = False
if athenapp:
# Athena++ configuration
try:
mhd = par["configure"]["Magnetic_fields"] == "ON"
except KeyError:
pass
try:
newcool = par["photchem"]["mode"] == "ncr"
except KeyError:
pass
try:
cooling = par["cooling"]["cooling"] != "none"
except KeyError:
pass
try:
cosmic_ray = par["configure"]["Cosmic_Ray_Transport"] == "Multigroups"
except KeyError:
pass
try:
feedback_scalars = par["configure"]["Number_of_feedback_scalar"] > 0
except KeyError:
pass
else:
# Athena configuration
try:
newcool = par['configure']['new_cooling'] == 'ON'
except KeyError:
pass
try:
mhd = par['configure']['gas'] == 'mhd'
except KeyError:
pass
try:
cooling = par['configure']['cooling'] == 'ON'
except KeyError:
pass
try:
radps = par['configure']['radps'] == 'ON'
except KeyError:
pass
try:
sixray = par['configure']['sixray'] == 'ON'
except KeyError:
pass
try:
wind = par['feedback']['iWind'] != 0
except KeyError:
pass
try:
xray = ((par['feedback']['iSN'] > 0) or
(par['feedback']['iWind'] > 0) or
(par['feedback']['iEarly'] > 0))
except KeyError:
pass
self.func, self.field_dep, \
self.label, self.cmap, \
self.vminmax, self.take_log = set_derived_fields_def(par, x0)
dicts = (self.func, self.field_dep, self.label, self.cmap, \
self.vminmax, self.take_log)
if mhd:
dicts_ = set_derived_fields_mag(par, x0)
for d, d_ in zip(dicts, dicts_):
d.update(d_)
if cooling or newcool:
dicts_ = set_derived_fields_cooling(par, newcool)
for d, d_ in zip(dicts, dicts_):
d.update(d_)
if newcool:
dicts_ = set_derived_fields_newcool(par, x0)
for d, d_ in zip(dicts, dicts_):
d.update(d_)
if radps:
dicts_ = set_derived_fields_rad(par, x0)
for d, d_ in zip(dicts, dicts_):
d.update(d_)
if sixray:
dicts_ = set_derived_fields_sixray(par, x0)
for d, d_ in zip(dicts, dicts_):
d.update(d_)
# if wind:
# dicts_ = set_derived_fields_wind(par, x0)
# for d, d_ in zip(dicts, dicts_):
# d.update(d_)
if xray:
dicts_ = set_derived_fields_xray(par, x0, newcool)
for d, d_ in zip(dicts, dicts_):
d.update(d_)
# TIGRESS++ specific
if cosmic_ray:
dicts_ = set_derived_fields_cosmic_ray(par)
for d, d_ in zip(dicts, dicts_):
d.update(d_)
if feedback_scalars:
dicts_ = set_derived_fields_feedback_scalars(par)
for d, d_ in zip(dicts, dicts_):
d.update(d_)
self.derived_field_list = self.func
# Set colormap normalization and scale
self.norm = dict()
self.scale = dict()
self.imshow_args = dict()
for f in self.derived_field_list:
if self.take_log[f]:
self.norm[f] = LogNorm(*self.vminmax[f])
self.scale[f] = 'log'
else:
self.norm[f] = Normalize(*self.vminmax[f])
self.scale[f] = 'linear'
self.imshow_args[f] = dict(norm=self.norm[f], cmap=self.cmap[f],
cbar_kwargs=dict(label=self.label[f]))
for f in self.derived_field_list:
self.dfi[f] = dict(field_dep=self.field_dep[f],
func=self.func[f],
label=self.label[f],
norm=self.norm[f],
vminmax=self.vminmax[f],
cmap=self.cmap[f],
scale=self.scale[f],
take_log=self.take_log[f],
imshow_args=self.imshow_args[f])