# Copyright (c) 2021-2025 Cubillos & Blecic
# Pyrat Bay is open-source software under the GPL-2.0 license (see LICENSE)
__all__ = [
'Namespace',
'parse',
'parse_bool',
'parse_str',
'parse_int',
'parse_float',
'parse_array',
'parse_var_vals',
]
import os
import argparse
from datetime import date
import configparser
import warnings
import numpy as np
import mc3.utils as mu
import matplotlib
from . import tools as pt
from .mpi_tools import (
check_mpi4py,
check_mpi_is_needed,
get_mpi_rank,
)
from .. import constants as pc
from ..version import __version__
[docs]
class Namespace(argparse.Namespace):
"""A container object to hold variables."""
def __init__(self, args=None, log=None):
if args is not None:
super().__init__(**args)
if log is None:
self._log = mu.Log(logname=None, verb=2, width=80)
else:
self._log = log
[docs]
def get_path(self, pname, desc='', exists=False, make_dir=False):
"""
Extract pname file path (or list of paths) from Namespace,
return the canonical path.
Parameters
----------
pname: String
The parameter name to extract.
desc: String
A description to display in case of raising an error
exists: Bool
If True, raise an error if the file path does not exists.
make_dir: Bool
If True, make directories for the file path if needed.
Examples
--------
>>> import pyratbay.tools as pt
>>> ns = pt.Namespace({'f1':'file1', 'f23':['file2', 'file3']})
>>> # Get path of a single file:
>>> ns.get_path('f1')
>>> # Get path of a list of files:
>>> ns.get_path('f23')
>>> # Attempt to get non-existing file:
>>> ns.get_path('f1', desc='Configuration', exists=True)
"""
value = getattr(self, pname)
if value is None:
return None
if isinstance(value, str):
values = [value]
is_list = False
else:
values = value
is_list = True
values = [
os.path.realpath(val.replace('{ROOT}', pc.ROOT))
for val in values
]
for val in values:
if exists and not os.path.isfile(val):
self._log.error(
f"{desc} file ({pname}) does not exist: '{val}'"
)
if make_dir:
pt.mkdir(val)
if not os.path.exists(os.path.dirname(val)):
self._log.error(
f"Folder for {desc} file ({pname}) does not exist: '{val}'"
)
if not is_list:
return values[0]
return values
def get_choice(self, pname, desc, choices, take_none=True):
value = getattr(self, pname)
if value is None and take_none:
return None
if isinstance(value, list):
values = value
is_list = True
else:
values = [value]
is_list = False
for value in values:
if value not in choices:
self._log.error(
f"Invalid {desc} ({pname}): '{value}'. Select from: {choices}"
)
if not is_list:
return values[0]
return values
[docs]
def get_default(
self, pname, desc, default=None, wflag=False,
gt=None, ge=None, lt=None, le=None,
):
"""
Extract pname variable from Namespace; if None, return
default. If any of gt, ge, lt, or le is not None, run
greater/lower/equal checks.
Parameters
----------
pname: String
Parameter name.
desc: String
Parameter description.
default: Any
Parameter default value.
gt: Float
If not None, check output is greater than gt.
ge: Float
If not None, check output is greater-equal than gt.
lt: Float
If not None, check output is lower than gt.
le: Float
If not None, check output is lower-equal than gt.
"""
value = getattr(self, pname)
if value is None and default is not None:
if wflag:
self._log.warning(f'{desc} ({pname}) defaulted to: {default}')
value = default
if value is None:
return None
if gt is not None and value <= gt:
self._log.error(f'{desc} ({pname}) must be > {gt}')
if ge is not None and value < ge:
self._log.error(f'{desc} ({pname}) must be >= {ge}')
if lt is not None and lt <= value:
self._log.error(f'{desc} ({pname}) must be < {lt}')
if le is not None and le < value:
self._log.error(f'{desc} ({pname}) must be <= {le}')
return value
[docs]
def get_units(self, pname):
"""
Extract units from a value input.
Return None if value does not have units or has an invalid format.
Parameters
----------
pname: String
Parameter name.
Returns
-------
units: String
"""
value = getattr(self, pname)
if not isinstance(value, str):
return None
par = value.split()
if len(par) != 2:
return None
units = par[1]
return units
def get_param(self, pname, units, desc, gt=None, ge=None, output_units=None):
try:
value = pt.get_param(getattr(self, pname), units)
except ValueError as error:
self._log.error(f'{error} for parameter {pname}')
if value is None:
return None
if output_units is not None:
value /= pt.u(output_units)
if gt is not None and value <= gt:
self._log.error(f'{desc} ({pname}) must be > {gt}')
if ge is not None and value < ge:
self._log.error('{desc} ({pname}) must be >= {ge}')
return value
[docs]
def parse_bool(args, param, default=False):
"""Parse a string parameter in args into a Bool."""
if param not in args:
args[param] = default
elif args[param].lower() in ['false', '0', 'no']:
args[param] = False
elif args[param].lower() in ['true', '1', 'yes']:
args[param] = True
else:
raise ValueError(
f"Invalid data type for parameter '{param}', could not "
f"convert string '{args[param]}'to bool"
)
[docs]
def parse_str(args, param):
"""Parse a string parameter in args into a string."""
if param not in args:
args[param] = None
else:
args[param] = str(args[param])
[docs]
def parse_int(args, param):
"""
Convert a dictionary's parameter from string to integer.
Set parameter to None if it was not in the dictionary.
Parameters
----------
args: dict
Dictionary where to operate.
param: String
Parameter to cast to int.
Examples
--------
>>> import pyratbay.tools as pt
>>> inputs = ['10', '-10', '+10', '10.0', '1e1',
>>> '10.5', 'None', 'True', 'inf', '10 20']
>>> args = {f'par{i}':val for i,val in enumerate(inputs)}
>>> for i,var in enumerate(inputs):
>>> try:
>>> par = f'par{i}'
>>> pt.parse_int(args, par)
>>> print(f"{par}: '{var}' -> {args[par]}")
>>> except ValueError as e:
>>> print(e)
par0: '10' -> 10
par1: '-10' -> -10
par2: '+10' -> 10
par3: '10.0' -> 10
par4: '1e1' -> 10
Invalid data type for par5, could not convert string to integer: '10.5'
Invalid data type for par6, could not convert string to integer: 'None'
Invalid data type for par7, could not convert string to integer: 'True'
Invalid data type for par8, could not convert string to integer: 'inf'
Invalid data type for par9, could not convert string to integer: '10 20'
"""
if param not in args:
args[param] = None
return
try:
val = np.double(args[param])
except:
raise ValueError(
f"Invalid data type for {param}, could not convert string "
f"to integer: '{args[param]}'"
)
if not np.isfinite(val) or int(val) != val:
raise ValueError(
f"Invalid data type for {param}, could not convert string "
f"to integer: '{args[param]}'"
)
args[param] = int(val)
[docs]
def parse_float(args, param):
"""
Convert a dictionary's parameter from string to float.
Set parameter to None if it was not in the dictinary.
Parameters
----------
args: dict
Dictionary where to operate.
param: String
Parameter to cast to float.
Examples
--------
>>> import pyratbay.tools as pt
>>> inputs = ['10', '-10', '+10', '10.5', '1e1', 'inf', 'nan',
>>> 'None', 'True', '10 20']
>>> args = {f'par{i}':val for i,val in enumerate(inputs)}
>>> for i,var in enumerate(inputs):
>>> try:
>>> par = f'par{i}'
>>> pt.parse_float(args, par)
>>> print(f"{par}: '{var}' -> {args[par]}")
>>> except ValueError as e:
>>> print(e)
par0: '10' -> 10.0
par1: '-10' -> -10.0
par2: '+10' -> 10.0
par3: '10.5' -> 10.5
par4: '1e5' -> 10.0
par5: 'inf' -> inf
par6: 'nan' -> nan
Invalid data type for par7, could not convert string to float: 'None'
Invalid data type for par8, could not convert string to float: 'True'
Invalid data type for par9, could not convert string to float: '10 20'
"""
if param not in args:
args[param] = None
return
try:
val = np.double(args[param])
except:
raise ValueError(
f"Invalid data type for {param}, could not convert string "
f"to float: '{args[param]}'"
)
args[param] = val
[docs]
def parse_array(args, param):
r"""
Convert a dictionary's parameter from string to iterable.
If possible cast into a float numpy array; otherwise,
set as a list of strings.
Assume any blank character delimits the elements in the string.
Set parameter to None if it was not in the dictinary.
Parameters
----------
args: dict
Dictionary where to operate.
param: String
Parameter to cast to array.
Examples
--------
>>> import pyratbay.tools as pt
>>> inputs = ['10 20', '10.0 20.0', 'a b', 'a\n b']
>>> args = {f'par{i}':val for i,val in enumerate(inputs)}
>>> for i,var in enumerate(inputs):
>>> par = f'par{i}'
>>> pt.parse_array(args, par)
>>> print(f"{par}: {repr(var)} -> {repr(args[par])}")
par0: '10 20' -> array([10., 20.])
par1: '10.0 20.0' -> array([10., 20.])
par2: 'a b' -> ['a', 'b']
par3: 'a\n b' -> ['a', 'b']
"""
if param not in args:
args[param] = None
return
val = args[param].split()
try:
val = np.asarray(val, np.double)
except:
pass
args[param] = val
[docs]
def parse_var_vals(var_input):
"""
Parse keys that contain variable and values
"""
if var_input is None:
return [], []
# parse models and parameters
inputs = [
par for par in var_input.splitlines()
if par != ''
]
# if any item is a number, then assume {vars,pars} pairs per line
has_pars = np.any([
pt.is_number(val)
for vars in inputs
for val in vars.split()
])
input_vars = []
input_pars = []
if has_pars:
for var in inputs:
var = var.split()
pars = None if len(var)==1 else np.array(var[1:], float)
input_vars.append(var[0])
input_pars.append(pars)
else:
input_vars = ' '.join(inputs).split()
input_pars = [None] * len(input_vars)
return input_vars, input_pars
[docs]
def parse(cfile, with_log=True, mute=False):
"""
Read the command line arguments.
Parameters
----------
cfile: String
A Pyrat Bay configuration file.
with_log: Bool
Flag to save screen outputs to file (True) or not (False)
(e.g., to prevent overwritting log of a previous run).
mute: Bool
If True, enforce verb to take a value of -1.
Returns
-------
args: Dictionary
A dictionary containing the input configuration variables.
log: mc3.Log
A logging log object.
"""
with pt.log_error():
if not os.path.isfile(cfile):
raise ValueError(f"Configuration file '{cfile}' does not exist.")
config = configparser.ConfigParser()
config.optionxform = str # Enable case-sensitive variable names
config.read([cfile])
if 'pyrat' not in config.sections():
raise ValueError(
f"\nInvalid configuration file: '{cfile}', no [pyrat] section."
)
args = dict(config.items("pyrat"))
# Parse data type:
with pt.log_error():
parse_str(args, 'logfile')
parse_str(args, 'runmode')
parse_int(args, 'ncpu')
parse_int(args, 'verb')
parse_array(args, 'dblist')
parse_array(args, 'pflist')
parse_array(args, 'dbtype')
parse_array(args, 'tlifile')
parse_str(args, 'molfile')
parse_array(args, 'sampled_cross_sec')
parse_array(args, 'continuum_cross_sec')
parse_array(args, 'extfile') # Deprecated
parse_array(args, 'csfile') # Deprecated
# Spectrum sampling options:
parse_str(args, 'wlunits')
parse_str(args, 'wl_low')
parse_str(args, 'wl_high')
parse_float(args, 'wnlow')
parse_float(args, 'wnhigh')
parse_float(args, 'wnstep')
parse_int(args, 'wnosamp')
parse_float(args, 'resolution')
parse_str(args, 'wlstep')
parse_int(args, 'wl_thinning')
parse_str(args, 'wllow') # Deprecated
parse_str(args, 'wlhigh') # Deprecated
# Atmospheric sampling options:
parse_str(args, 'atmfile')
parse_str(args, 'tmodel')
parse_array(args, 'tpars')
parse_str(args, 'radlow')
parse_str(args, 'radhigh')
parse_str(args, 'radstep')
parse_str(args, 'runits')
parse_str(args, 'punits')
parse_int(args, 'nlayers')
parse_str(args, 'ptop')
parse_str(args, 'pbottom')
parse_str(args, 'output_atmfile')
parse_str(args, 'radmodel')
# Variables for chemistry calculations
parse_str(args, 'chemistry')
parse_array(args, 'species')
parse_array(args, 'uniform') # Deprecated
parse_array(args, 'uniform_vmr')
parse_array(args, 'bulk')
parse_str(args, 'vmr_vars')
parse_str(args, 'ptfile')
parse_str(args, 'solar')
parse_float(args, 'xsolar') # Deprecated
parse_array(args, 'escale') # Deprecated
parse_array(args, 'elements') # Deprecated
parse_array(args, 'molvars') # Deprecated
parse_array(args, 'molfree') # Deprecated
parse_array(args, 'molmodel') # Deprecated
parse_array(args, 'molpars') # Deprecated
# Extinction options:
parse_float(args, 'tmin')
parse_float(args, 'tmax')
parse_float(args, 'tstep')
parse_float(args, 'ethresh')
parse_str(args, 'single_isotope')
parse_str(args, 'isotope_ratios')
# Voigt-profile options:
parse_float(args, 'vextent') # Deprecated
parse_float(args, 'vcutoff') # Deprecated
parse_float(args, 'voigt_extent')
parse_float(args, 'voigt_cutoff')
parse_float(args, 'dmin')
parse_float(args, 'dmax')
parse_int(args, 'ndop')
parse_float(args, 'lmin')
parse_float(args, 'lmax')
parse_int(args, 'nlor')
parse_float(args, 'dlratio')
# Hazes and clouds options:
parse_str(args, 'clouds')
parse_array(args, 'rayleigh')
parse_float(args, 'fpatchy')
parse_array(args, 'alkali')
parse_float(args, 'alkali_cutoff')
parse_array(args, 'h_ion')
# Optical depth options:
parse_str(args, 'rt_path')
parse_float(args, 'maxdepth')
parse_array(args, 'raygrid')
parse_int(args, 'quadrature')
parse_float(args, 'f_dilution')
# Data options:
parse_str(args, 'dunits')
parse_array(args, 'data')
parse_array(args, 'uncert')
parse_array(args, 'filters')
parse_str(args, 'obsfile')
parse_str(args, 'obsfile_hires')
parse_str(args, 'offset_inst')
parse_str(args, 'uncert_scaling')
parse_float(args, 'inst_resolution')
# Retrieval options:
parse_str(args, 'mcmcfile') # Deprecated
parse_str(args, 'sampler')
parse_bool(args, 'resume')
parse_bool(args, 'post_processing', default=True)
parse_array(args, 'retflag') # Deprecated
parse_float(args, 'qcap')
parse_str(args, 'retrieval_params')
parse_array(args, 'params')
parse_array(args, 'pstep')
parse_float(args, 'tlow')
parse_float(args, 'thigh')
parse_array(args, 'pmin')
parse_array(args, 'pmax')
parse_array(args, 'prior')
parse_array(args, 'priorlow')
parse_array(args, 'priorup')
parse_int(args, 'nsamples')
parse_int(args, 'nchains')
parse_int(args, 'burnin')
parse_int(args, 'thinning')
parse_float(args, 'grbreak')
parse_float(args, 'grnmin')
parse_str(args, 'theme')
parse_str(args, 'data_color')
parse_int(args, 'nlive')
parse_str(args, 'statistics')
parse_float(args, 'dt_retrieval_snapshot')
# Stellar models:
parse_str(args, 'starspec')
parse_str(args, 'kurucz')
parse_str(args, 'marcs')
parse_str(args, 'phoenix')
# System parameters:
parse_str(args, 'rstar')
parse_float(args, 'gstar') # Deprecated
parse_float(args, 'log_gstar')
parse_float(args, 'tstar')
parse_str(args, 'mstar')
parse_str(args, 'distance')
parse_str(args, 'rplanet')
parse_str(args, 'refpressure')
parse_str(args, 'mplanet')
parse_str(args, 'mpunits')
parse_float(args, 'gplanet')
parse_str(args, 'smaxis')
parse_float(args, 'tint')
parse_float(args, 'beta_irr')
# Outputs:
parse_str(args, 'specfile')
parse_array(args, 'logxticks')
parse_array(args, 'yran')
# Cast into a Namespace to make my life easier:
args = Namespace(args)
args.config_file = cfile
# Check that mpi4py is necessary and installed
check_mpi4py()
# Ensure that any process with rank !=0 is muted
rank = get_mpi_rank()
with_log &= rank==0
mute |= rank != 0
if mute:
args.verb = -1
else:
args.verb = args.get_default('verb', 'Verbosity', 2, lt=5)
# logfile is now a required parameter (though users can still run
# without creating a logfile by setting with_log=False)
args.logfile = args.get_path('logfile', 'Log', make_dir=True)
if args.logfile is None:
raise ValueError("Missing 'logfile' input in config file")
# Override logfile if requested:
if not with_log:
logfile = None
else:
logfile = pt.path(args.logfile)
#args.logfile = pt.path(args.logfile)
log = mu.Log(logname=logfile, verb=args.verb, width=80, append=args.resume)
# Temporary reference to log in args (will be deleted at the end):
args._log = log
# Welcome message:
log.head(
f"{log.sep}\n"
" Python Radiative Transfer in a Bayesian framework (Pyrat Bay).\n"
f" Version {__version__}.\n"
f" Copyright (c) 2021-{date.today().year} Cubillos & Blecic.\n"
" Pyrat Bay is open-source software under the GNU GPLv2 lincense "
"(see LICENSE).\n"
f"{log.sep}\n\n")
log.head(f"Read command-line arguments from configuration file: '{cfile}'")
if args.runmode == 'mcmc':
args.runmode = 'retrieval'
warning_msg = (
"The 'mcmc' option for the 'runmode' argument is deprecated "
"and will be removed in the future, use 'retrieval' instead "
)
warnings.warn(warning_msg, category=DeprecationWarning)
args.runmode = args.get_choice(
'runmode', 'running mode', pc.rmodes, take_none=False,
)
args.tlifile = args.get_path('tlifile', 'TLI')
args.atmfile = args.get_path('atmfile', 'Atmospheric')
args.output_atmfile = args.get_path('output_atmfile', 'Atmospheric')
args.specfile = args.get_path('specfile', 'Spectrum')
# Deprecated variable
if args.get_path('mcmcfile') is not None:
warning = (
"'mcmcfile' argument is deprecated, output file names "
"are now based on logfile"
)
warnings.warn(warning, category=DeprecationWarning)
args.sampled_cs = args.get_path(
'sampled_cross_sec',
'sampled cross-section',
)
# Deprecated variable
sampled_cs = args.extfile = args.get_path('extfile')
if sampled_cs is not None:
warning = "'extfile' argument is deprecated, use 'sampled_cross_sec' instead"
warnings.warn(warning, category=DeprecationWarning)
if args.sampled_cs is None and sampled_cs is not None:
args.sampled_cs = sampled_cs
# Default output filenames if needed base on logfile and runmode:
outfile, extension = os.path.splitext(args.logfile)
if args.runmode == 'tli' and args.tlifile is None:
args.tlifile = [outfile + '.tli']
if args.runmode == 'atmosphere' and args.output_atmfile is None:
args.output_atmfile = outfile + '.atm'
if args.runmode == 'opacity' and args.sampled_cs is None:
args.sampled_cs = [outfile + '.npz']
if args.runmode == 'spectrum' and args.specfile is None:
args.specfile = outfile + '.dat'
if args.runmode == 'radeq':
if args.specfile is None:
args.specfile = outfile + '.dat'
if args.output_atmfile is None:
args.output_atmfile = outfile + '.atm'
# Always use root from logfile as retrieval_file
args.retrieval_file = outfile
# Parse valid inputs and defaults:
args.dblist = args.get_path('dblist', 'Opacity database', exists=True)
args.molfile = args.get_path('molfile', 'Molecular data', exists=True)
args.ptfile = args.get_path('ptfile', 'Pressure-temperature')
args.continuum_cs = args.get_path(
'continuum_cross_sec',
'Continuum cross-section',
exists=True,
)
# Deprecated variable
continuum_cs = args.cia_files = args.get_path('csfile', exists=True)
if continuum_cs is not None:
warning = (
"'csfile' argument is deprecated, "
"use 'continuum_cross_sec' instead"
)
warnings.warn(warning, category=DeprecationWarning)
if args.continuum_cs is None and continuum_cs is not None:
args.continuum_cs = continuum_cs
# Validate opacity file path(s)
if args.runmode == 'opacity' and pt.isfile(args.sampled_cs) == -1:
log.error(
'Undefined output cross-section file (sampled_cross_sec) needed '
'to compute opacity table'
)
if args.runmode != 'opacity' and pt.isfile(args.sampled_cs) == 0:
missing = [efile for efile in args.sampled_cs if pt.isfile(efile) == 0]
log.error(
f"These input cross-section files are missing: {missing}"
)
# Deprecated wl keys
wl_low = args.get_default('wllow', '')
if wl_low is not None:
warning = "'wllow' is deprecated, use 'wl_low' instead"
warnings.warn(warning, category=DeprecationWarning)
setattr(args, 'wl_low', wl_low)
wl_high = args.get_default('wlhigh', '')
if wl_high is not None:
warning = "'wlhigh' is deprecated, use 'wl_high' instead"
warnings.warn(warning, category=DeprecationWarning)
setattr(args, 'wl_high', wl_high)
wlunits = args.get_default('wlunits', 'Wavelength units')
if wlunits is None:
wlunits = args.get_units('wl_low')
if wlunits is None:
wlunits = args.get_units('wl_high')
if wlunits is None:
wlunits = args.get_units('wlstep')
if wlunits is not None and not hasattr(pc, wlunits):
log.error(f'Invalid wavelength units (wlunits): {wlunits}')
args.wlunits = wlunits
args.wl_low = args.get_param(
'wl_low', wlunits, 'Wavelength lower boundary', gt=0.0)
args.wl_high = args.get_param(
'wl_high', wlunits, 'Wavelength higher boundary', gt=0.0)
args.wlstep = args.get_param(
'wlstep', wlunits, 'Wavelength sampling step', gt=0.0)
args.wnlow = args.get_default(
'wnlow', 'Wavenumber lower boundary', gt=0.0)
args.wnhigh = args.get_default(
'wnhigh', 'Wavenumber higher boundary', gt=0.0)
args.wnstep = args.get_default(
'wnstep', 'Wavenumber sampling step', gt=0.0)
args.wnosamp = args.get_default(
'wnosamp', 'Wavenumber oversampling factor', ge=1)
args.resolution = args.get_default(
'resolution', 'Spectral resolution', gt=0.0)
args.wl_thinning = args.get_default(
'wl_thinning',
'Wavelength-sampling thinning factor for Line_Sample opacities',
1, ge=1,
)
runits = args.get_default('runits', 'Planetary-radius units')
if runits is not None and not hasattr(pc, runits):
log.error(f'Invalid radius units (runits): {runits}')
args.rplanet = args.get_param(
'rplanet', runits, 'Planetary radius', gt=0.0)
if runits is None:
runits = args.get_units('rplanet')
args.runits = runits
args.rmodelname = args.get_choice(
'radmodel', 'Radius-profile model', pc.radmodels)
# Pressure inputs:
args.nlayers = args.get_default(
'nlayers', 'Number of atmospheric layers', gt=1,
)
punits = args.get_default('punits', 'Pressure units')
if punits is not None and not hasattr(pc, punits):
log.error(f'Invalid pressure units (punits): {punits}')
if punits is None and args.pbottom is not None:
punits = args.get_units('pbottom')
elif punits is None and args.ptop is not None:
punits = args.get_units('ptop')
elif punits is None and args.refpressure is not None:
punits = args.get_units('refpressure')
args.punits = punits
args.pbottom = args.get_param(
'pbottom', punits, 'Pressure at bottom of atmosphere',
gt=0.0, output_units='bar',
)
args.ptop = args.get_param(
'ptop', punits, 'Pressure at top of atmosphere',
gt=0.0, output_units='bar',
)
args.refpressure = args.get_param(
'refpressure', punits, 'Planetary reference pressure level',
gt=0.0, output_units='bar',
)
# Chemistry:
chemistry = getattr(args, 'chemistry')
chem_replace = {
'uniform': 'free',
'tea': 'equilibrium',
}
if chemistry in chem_replace:
setattr(args, 'chemistry', chem_replace[chemistry])
warning_msg = (
f"'{chemistry}' value for chemistry is deprecated, "
f"use {chem_replace[chemistry]} instead"
)
warnings.warn(warning_msg, category=DeprecationWarning)
args.chemistry = args.get_choice('chemistry', 'Chemical model', pc.chemmodels)
xsolar = args.get_default('xsolar', 'Atmospheric metallicity')
if xsolar is not None:
args.vmr_vars += f'\n[M/H] {np.log10(xsolar)}'
warning_msg = (
"The 'xsolar' argument is deprecated and will be removed in "
"the near future, use 'vmr_vars' instead"
)
warnings.warn(warning_msg, category=DeprecationWarning)
escale = args.get_default('escale', 'Elemental abundance scaling factors')
if escale is not None:
for atom,e_scale in zip(escale[::2], escale[1::2]):
e_scale = np.log10(float(e_scale))
args.vmr_vars += f'\n[{atom}/H] {e_scale}'
warning_msg = (
"The 'escale' argument is deprecated and will be removed in "
"the near future, use 'vmr_vars' instead"
)
warnings.warn(warning_msg, category=DeprecationWarning)
if args.elements is not None:
warning_msg = (
"The 'elements' argument is deprecated and will be removed in "
"the near future. Elemental compositions will be automatically "
"deduced from the atmospheric species."
)
warnings.warn(warning_msg, category=DeprecationWarning)
if args.uniform is not None:
warning_msg = (
"The 'uniform' argument is deprecated, and will be removed in "
"the near future. Use 'uniform_vmr' instead"
)
warnings.warn(warning_msg, category=DeprecationWarning)
if args.uniform_vmr is None:
args.uniform_vmr = args.uniform
# System physical parameters:
args.gplanet = args.get_default(
'gplanet', 'Planetary surface gravity (cm s-2)', gt=0.0,
)
mass_units = args.get_default('mpunits', 'Planetary-mass units')
if mass_units is not None and not hasattr(pc, mass_units):
log.error(f'Invalid planet mass units (mpunits): {mass_units}')
if mass_units is None:
mass_units = args.get_units('mplanet')
args.mass_units = mass_units
args.mplanet = args.get_param(
'mplanet', mass_units, 'Planetary mass', gt=0.0)
args.tint = args.get_default(
'tint', 'Planetary internal temperature', 100.0, ge=0.0)
args.beta_irr = args.get_default(
'beta_irr', 'Stellar irradiation beta factor', 0.25)
args.smaxis = args.get_param(
'smaxis', None, 'Orbital semi-major axis', gt=0.0)
args.rstar = args.get_param(
'rstar', None, 'Stellar radius', gt=0.0)
args.mstar = args.get_param(
'mstar', None, 'Stellar mass', gt=0.0)
args.tstar = args.get_default(
'tstar', 'Stellar effective temperature (K)', gt=0.0)
args.distance = args.get_param(
'distance', None, 'Distance from Earth to target', gt=0.0,
)
args.log_gstar = args.get_default(
'log_gstar', 'Stellar surface gravity (log10(cm s-2))')
gstar = args.get_default('gstar', 'Stellar surface gravity', gt=0.0)
if gstar is not None:
warning_msg = "'gstar' argument is deprecated, use 'log_gstar' instead"
warnings.warn(warning_msg, category=DeprecationWarning)
if args.log_gstar is None:
args.log_gstar = np.log10(gstar)
# New Voigt parameters
args.voigt_extent = args.get_default(
'voigt_extent', 'Voigt profile extent in HWHM', 300.0, ge=1.0)
args.voigt_cutoff = args.get_default(
'voigt_cutoff', 'Voigt profile cutoff in cm-1', 25.0, ge=0.0)
# Deprecated
voigt_extent = args.get_default('vextent', 'Voigt HWHM', ge=1.0)
voigt_cutoff = args.get_default('vcutoff', 'Voigt cutoff', ge=0.0)
if voigt_extent is not None:
warning = "'vextent' argument is deprecated, use 'voigt_extent' instead"
warnings.warn(warning, category=DeprecationWarning)
if args.voigt_extent is None:
args.voigt_extent = voigt_extent
if voigt_cutoff is not None:
warning = "'vcutoff' argument is deprecated, use 'voigt_cutoff' instead"
warnings.warn(warning, category=DeprecationWarning)
if args.voigt_cutoff is None:
args.voigt_cutoff = voigt_cutoff
args.voigt_ndop = args.get_default(
'ndop', 'Number of Doppler-width samples', 50, ge=1)
args.voigt_dmin = args.get_default(
'dmin', 'Minimum Doppler HWHM (cm-1)', gt=0.0)
args.voigt_dmax = args.get_default(
'dmax', 'Maximum Doppler HWHM (cm-1)', gt=0.0)
args.voigt_nlor = args.get_default(
'nlor', 'Number of Lorentz-width samples', 100, ge=1)
args.voigt_lmin = args.get_default(
'lmin', 'Minimum Lorentz HWHM (cm-1)', gt=0.0)
args.voigt_lmax = args.get_default(
'lmax', 'Maximum Lorentz HWHM (cm-1)', gt=0.0)
args.voigt_dlratio = args.get_default(
'dlratio', 'Doppler/Lorentz-width ratio threshold', 0.1, gt=0.0)
args.tmin = args.get_param(
'tmin', 'kelvin', 'Minimum temperature of opacity grid', gt=0.0)
args.tmax = args.get_param(
'tmax', 'kelvin', 'Maximum temperature of opacity grid', gt=0.0)
args.tstep = args.get_default(
'tstep', "Opacity grid's temperature sampling step in K", gt=0.0)
args.rayleigh = args.get_choice(
'rayleigh', 'Rayleigh model', pc.rmodels)
clouds, cpars = parse_var_vals(args.clouds)
for name in clouds:
if name in pc.cmodels:
continue
log.error(
f"Invalid cloud model (clouds): '{name}'. Select from: {pc.cmodels}"
)
args.fpatchy = args.get_default(
'fpatchy', 'Patchy-cloud fraction', ge=0.0, le=1.0,
)
args.alkali_models = args.get_choice(
'alkali', 'alkali model', pc.amodels)
args.alkali_cutoff = args.get_default(
'alkali_cutoff',
'Alkali profiles hard cutoff from line center (cm-1)', 4500.0, gt=0.0)
args.h_ion = args.get_choice(
'h_ion', 'H- opacity model', pc.h_ion_models,
)
args.rt_path = args.get_choice(
'rt_path', 'radiative-transfer observing geometry', pc.rt_paths,
)
args.ethresh = args.get_default(
'ethresh', 'Extinction-cofficient threshold', 1e-30, gt=0.0,
)
args.maxdepth = args.get_default(
'maxdepth', 'Maximum optical-depth', 10.0, ge=0.0)
args.starspec = args.get_path('starspec', 'Stellar spectrum', exists=True)
args.kurucz = args.get_path('kurucz', 'Kurucz model', exists=True)
args.marcs = args.get_path('marcs', 'MARCS model', exists=True)
args.phoenix = args.get_path('phoenix', 'PHOENIX model', exists=True)
args.raygrid = args.get_default(
'raygrid', 'Emission raygrid (deg)', np.array([0, 20, 40, 60, 80.]))
args.quadrature = args.get_default(
'quadrature', 'Number of Gaussian-quadrature points', ge=1)
args.f_dilution = args.get_default(
'f_dilution', 'Flux dilution factor', ge=0.0, le=1.0,
)
args.dunits = args.get_default(
'dunits', 'Data units', 'none', wflag=args.data is not None)
if not hasattr(pc, args.dunits):
log.error(f'Invalid data units (dunits): {args.dunits}')
args.data = args.get_param('data', args.dunits, 'Data')
args.uncert = args.get_param('uncert', args.dunits, 'Uncertainties')
args.filters = args.get_path('filters', 'Filter pass-bands', exists=True)
args.obsfile = args.get_path(
'obsfile', 'Observations data file', exists=True,
)
args.obsfile_hires = args.get_path(
'obsfile_hires', 'High-resolution observations data file', exists=True,
)
offsets = args.get_default('offset_inst', 'Instrumental offsets')
if offsets is None:
args.offset_inst = []
args.offset_pars = []
else:
pars = [
par for par in offsets.splitlines()
if par != ''
]
args.offset_inst = []
args.offset_pars = np.zeros(len(pars))
for i,par in enumerate(pars):
fields = par.split()
args.offset_inst.append(fields[0])
if len(fields) > 1:
args.offset_pars[i] = float(fields[1])
u_scaling = args.get_default('uncert_scaling', 'Uncertainty scaling')
if u_scaling is None:
args.uncert_scaling = []
args.uncert_pars = []
else:
pars = [
par for par in u_scaling.splitlines()
if par != ''
]
args.uncert_scaling = []
args.uncert_pars = np.tile(None, len(pars))
for i,uscale in enumerate(pars):
fields = uscale.split()
args.uncert_scaling.append(fields[0])
if len(fields) > 1:
args.uncert_pars[i] = float(fields[1])
args.inst_resolution = args.get_default(
'inst_resolution', 'Instrumental resolution', gt=0.0,
)
args.sampler = args.get_choice('sampler', 'posterior sampler', pc.samplers)
args.retflag = args.get_choice('retflag', 'retrieval flag', pc.retflags)
if args.retflag is not None:
warning_msg = (
"The 'retflag' argument is deprecated and will be removed in "
"the near future, use 'retrieval_params' instead"
)
warnings.warn(warning_msg, category=DeprecationWarning)
args.qcap = args.get_default(
'qcap', 'Metals volume-mixing-ratio cap', gt=0.0, le=1.0)
args.tlow = args.get_default(
'tlow', 'Retrieval low-temperature (K) bound', 0.0,
wflag=(args.runmode=='retrieval'))
args.thigh = args.get_default(
'thigh', 'Retrieval high-temperature (K) bound', np.inf,
wflag=(args.runmode=='retrieval'))
args.nsamples = args.get_default(
'nsamples', 'Number of MCMC samples', gt=0)
args.burnin = args.get_default(
'burnin', 'Number of burn-in samples per chain', gt=0)
args.thinning = args.get_default(
'thinning', 'MCMC posterior thinning', 1, ge=1)
args.nchains = args.get_default(
'nchains', 'Number of MCMC parallel chains', ge=1)
args.grbreak = args.get_default(
'grbreak', 'Gelman-Rubin convergence criteria', 0.0, ge=0)
args.grnmin = args.get_default(
'grnmin', 'Gelman-Rubin convergence fraction', 0.5, gt=0.0)
args.nlive = args.get_default(
'nlive', 'Number of Nested Sampling live points', 1000, gt=0)
args.dt_retrieval_snapshot = args.get_default(
'dt_retrieval_snapshot',
'Take a snapshot of the posterior during a retrieval d_time',
default=0.0,
ge=0.0,
)
args.statistics = args.get_choice(
'statistics',
'Prefered statistics for posterior plots',
pc.statistics,
)
if args.statistics is None:
args.statistics = 'med_central'
data_color = args.get_default('data_color', 'Color of data points', 'black')
if not matplotlib.colors.is_color_like(data_color):
data_color = 'black'
args.data_color = data_color
for arg in ['molvars', 'molmodel', 'molfree', 'molpars']:
if getattr(args, arg) is not None:
log.error(
f"The '{arg}' argument is deprecated, use 'vmr_vars' instead"
)
if args.tmodel == 'tcea':
args.tmodel = 'guillot'
warning_msg = (
"The 'tcea' temperature model is deprecated, it has been renamed "
"as 'guillot', please update your config files in the future"
)
warnings.warn(warning_msg, category=DeprecationWarning)
args.tmodelname = args.get_choice('tmodel', 'temperature model', pc.tmodels)
args.ncpu = args.get_default('ncpu', 'Number of processors', 1, ge=1)
# Now, check that we really needed MPI (exclusively for MultiNest)
check_mpi_is_needed(args)
del args._log
return args, log