Source code for pyratbay.tools.parser

# Copyright (c) 2021-2022 Patricio Cubillos
# Pyrat Bay is open-source software under the GPL-2.0 license (see LICENSE)

__all__ = [
    'Namespace',
    'parse',
    'parse_str',
    'parse_int',
    'parse_float',
    'parse_array',
]

import os
import argparse
from datetime import date
import configparser
import warnings

import numpy as np
import mc3.utils as mu

from . import tools as pt
from .. import constants as pc
from .. import io as io
from .. import spectrum as ps
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(Namespace, self).__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): """ Extract pname file path (or list of paths) from Namespace, return the canonical path. 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}'", tracklev=-3) if not os.path.exists(os.path.dirname(val)): self._log.error( f"Folder for {desc} file ({pname}) does not exist: '{val}'", tracklev=-3) 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}. " f"Select from: {choices}.", tracklev=-3) 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}', tracklev=-3) if ge is not None and value < ge: self._log.error(f'{desc} ({pname}) must be >= {ge}', tracklev=-3) if lt is not None and lt <= value: self._log.error(f'{desc} ({pname}) must be < {lt}', tracklev=-3) if le is not None and le < value: self._log.error(f'{desc} ({pname}) must be <= {le}', tracklev=-3) 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): try: value = pt.get_param(getattr(self, pname), units) except ValueError as error: self._log.error(f'{error} for parameter {pname}.', tracklev=-3) if value is None: return None if gt is not None and value <= gt: self._log.error(f'{desc} ({pname}) must be > {gt}', tracklev=-3) if ge is not None and value < ge: self._log.error('{desc} ({pname}) must be >= {ge}', tracklev=-3) return value
[docs]def parse_str(args, param): """Parse a string parameter into args.""" 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. Raise ValueError if the operation is not possible. Set parameter to None if it was not in the dictinary. 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. Raise ValueError if the operation is not possible. 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(pyrat, cfile, no_logfile=False, mute=False): """ Read the command line arguments. Parameters ---------- cfile: String A Pyrat Bay configuration file. no_logfile: Bool If True, enforce not to write outputs to a log file (e.g., to prevent overwritting log of a previous run). mute: Bool If True, enforce verb to take a value of -1. """ 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, '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_array(args, 'csfile') parse_str(args, 'molfile') parse_array(args, 'extfile') # Spectrum sampling options: parse_str(args, 'wlunits') parse_str(args, 'wllow') parse_str(args, 'wlhigh') parse_float(args, 'wnlow') parse_float(args, 'wnhigh') parse_float(args, 'wnstep') parse_int(args, 'wnosamp') parse_float(args, 'resolution') parse_float(args, 'wlstep') # Atmospheric sampling options: 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, 'atmfile') parse_str(args, 'radmodel') # Variables for chemistry calculations parse_str(args, 'chemistry') parse_array(args, 'species') parse_array(args, 'uniform') parse_str(args, 'ptfile') parse_str(args, 'solar') parse_float(args, 'xsolar') parse_float(args, 'metallicity') parse_array(args, 'escale') parse_array(args, 'e_scale') parse_array(args, 'e_ratio') parse_array(args, 'e_abundances') parse_array(args, 'elements') # Extinction options: parse_float(args, 'tmin') parse_float(args, 'tmax') parse_float(args, 'tstep') parse_float(args, 'ethresh') # Voigt-profile options: parse_float(args, 'vextent') parse_float(args, 'vcutoff') 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_array(args, 'clouds') parse_array(args, 'cpars') parse_array(args, 'rayleigh') parse_array(args, 'rpars') parse_float(args, 'fpatchy') parse_array(args, 'alkali') parse_float(args, 'alkali_cutoff') # Optical depth options: parse_str(args, 'rt_path') parse_float(args, 'maxdepth') parse_array(args, 'raygrid') parse_int(args, 'quadrature') # Data options: parse_str(args, 'dunits') parse_array(args, 'data') parse_array(args, 'uncert') parse_array(args, 'filters') parse_str(args, 'obsfile') # Abundances: parse_array(args, 'molmodel') parse_array(args, 'molfree') parse_array(args, 'molpars') parse_array(args, 'bulk') # Retrieval options: parse_str(args, 'mcmcfile') parse_array(args, 'retflag') parse_float(args, 'qcap') 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_str(args, 'sampler') 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_int(args, 'resume') # False, action='store_true') # 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') parse_float(args, 'tstar') parse_str(args, 'mstar') 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_str(args, 'logfile') parse_array(args, 'logxticks') parse_array(args, 'yran') # Cast into a Namespace to make my life easier: args = Namespace(args) args.configfile = cfile if mute: args.verb = -1 pyrat.verb = args.get_default('verb', 'Verbosity', 2, lt=5) runmode = pyrat.runmode = args.get_choice( 'runmode', 'running mode', pc.rmodes, take_none=False) # Define logfile name and initialize log object: pyrat.lt.tlifile = args.get_path('tlifile', 'TLI') pyrat.atm.atmfile = args.get_path('atmfile', 'Atmospheric') pyrat.spec.specfile = args.get_path('specfile', 'Spectrum') pyrat.ex.extfile = args.get_path('extfile', 'Extinction-coefficient') pyrat.ret.mcmcfile = args.get_path('mcmcfile', 'MCMC') outfile_dict = { 'tli': args.tlifile, 'atmosphere': args.atmfile, 'spectrum': args.specfile, 'radeq': args.specfile, 'opacity': args.extfile, 'mcmc': args.mcmcfile, } outfile = outfile_dict[args.runmode] if args.logfile is None and outfile is not None: if args.runmode in ['tli', 'opacity']: outfile = outfile[0] args.logfile = os.path.splitext(outfile)[0] + '.log' args.logfile = args.get_path('logfile', 'Log') # Override logfile if requested: if no_logfile: args.logfile = None args.logfile = pt.path(args.logfile) log = pyrat.log = mu.Log( logname=args.logfile, verb=pyrat.verb, width=80, append=args.resume) 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} Patricio Cubillos.\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}'") # :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: # Parse valid inputs and defaults: pyrat.inputs = args phy = pyrat.phy spec = pyrat.spec atm = pyrat.atm pyrat.lt.dblist = args.get_path('dblist', 'Opacity database', exists=True) pyrat.mol.molfile = args.get_path('molfile', 'Molecular data', exists=True) pyrat.cs.files = args.get_path('csfile', 'Cross-section', exists=True) pyrat.atm.ptfile = args.get_path('ptfile', 'Pressure-temperature') spec.wlunits = args.get_default('wlunits', 'Wavelength units') if spec.wlunits is not None and not hasattr(pc, spec.wlunits): log.error(f'Invalid wavelength units (wlunits): {spec.wlunits}') spec.wllow = args.get_param( 'wllow', spec.wlunits, 'Wavelength lower boundary', gt=0.0) spec.wlhigh = args.get_param( 'wlhigh', spec.wlunits, 'Wavelength higher boundary', gt=0.0) if spec.wlunits is None: spec.wlunits = args.get_units('wllow') spec.wnlow = args.get_default( 'wnlow', 'Wavenumber lower boundary', gt=0.0) spec.wnhigh = args.get_default( 'wnhigh', 'Wavenumber higher boundary', gt=0.0) spec.wnstep = args.get_default( 'wnstep', 'Wavenumber sampling step', gt=0.0) spec.wnosamp = args.get_default( 'wnosamp', 'Wavenumber oversampling factor', ge=1) spec.resolution = args.get_default( 'resolution', 'Spectral resolution', gt=0.0) spec.wlstep = args.get_param( 'wlstep', spec.wlunits, 'Wavelength sampling step', gt=0.0) atm.runits = args.get_default('runits', 'Planetary-radius units') if atm.runits is not None and not hasattr(pc, atm.runits): log.error(f'Invalid radius units (runits): {atm.runits}') phy.rplanet = args.get_param( 'rplanet', atm.runits, 'Planetary radius', gt=0.0) if atm.runits is None: atm.runits = args.get_units('rplanet') atm.nlayers = args.get_default( 'nlayers', 'Number of atmospheric layers', gt=1) atm.rmodelname = args.get_choice( 'radmodel', 'Radius-profile model', pc.radmodels) # Pressure inputs: atm.punits = args.get_default('punits', 'Pressure units') if atm.punits is not None and not hasattr(pc, atm.punits): log.error(f'Invalid pressure units (punits): {atm.punits}') atm.pbottom = args.get_param( 'pbottom', atm.punits, 'Pressure at bottom of atmosphere', gt=0.0) atm.ptop = args.get_param( 'ptop', atm.punits, 'Pressure at top of atmosphere', gt=0.0) atm.refpressure = args.get_param( 'refpressure', atm.punits, 'Planetary reference pressure level', gt=0.0) if atm.punits is None and atm.pbottom is not None: atm.punits = args.get_units('pbottom') elif atm.punits is None and atm.ptop is not None: atm.punits = args.get_units('ptop') elif atm.punits is None and atm.refpressure is not None: atm.punits = args.get_units('refpressure') # else, set atm.punits from atmospheric file in read_atm(). # Radius boundaries: atm.radlow = args.get_param( 'radlow', atm.runits, 'Radius at bottom of atmosphere', ge=0.0) atm.radhigh = args.get_param( 'radhigh', atm.runits, 'Radius at top of atmosphere', gt=0.0) atm.radstep = args.get_param( 'radstep', atm.runits, 'Radius sampling step', gt=0.0) # Chemistry: atm.chemistry = args.get_choice( 'chemistry', 'Chemical model', pc.chemmodels) xsolar = args.get_default( 'xsolar', 'Atmospheric metallicity',) if xsolar is not None: args.metallicity = np.log10(xsolar) warning_msg = ( "The 'xsolar' argument is deprecated and will be removed in " "the near future, use 'metallicity' instead" ) warnings.warn(warning_msg, category=DeprecationWarning) atm.metallicity = args.get_default( 'metallicity', 'Atmospheric metallicity (dex, relative to solar)', default=0.0) escale = args.get_default('escale', 'Elemental abundance scaling factors') if escale is not None: warning_msg = ( "The 'escale' argument is deprecated and will be removed in " "the near future, use 'e_scale' instead" ) warnings.warn(warning_msg, category=DeprecationWarning) atm.e_scale = { atom: np.log10(float(fscale)) for atom,fscale in zip(escale[::2], escale[1::2]) } e_scale = args.get_default( 'e_scale', 'Elemental abundance scaling factors (dex)', [], ) atm.e_scale = { atom: float(fscale) for atom,fscale in zip(e_scale[::2], e_scale[1::2]) } e_ratio = args.get_default( 'e_ratio', 'Elemental abundance ratios', [], ) atm.e_ratio = { pair: float(ratio) for pair,ratio in zip(e_ratio[::2], e_ratio[1::2]) } e_abundances = args.get_default( 'e_abundances', 'Elemental abundances (dex relative to H=12)', [], ) atm.e_abundances = { atom: float(abundance) for atom,abundance in zip(e_abundances[::2], e_abundances[1::2]) } # System physical parameters: phy.mpunits = args.get_default('mpunits', 'Planetary-mass units') if phy.mpunits is not None and not hasattr(pc, phy.mpunits): log.error(f'Invalid planet mass units (mpunits): {phy.mpunits}') phy.mplanet = args.get_param( 'mplanet', phy.mpunits, 'Planetary mass', gt=0.0) if phy.mpunits is None: phy.mpunits = args.get_units('mplanet') phy.gplanet = args.get_default( 'gplanet', 'Planetary surface gravity (cm s-2)', gt=0.0) phy.tint = args.get_default( 'tint', 'Planetary internal temperature', 100.0, ge=0.0) phy.beta_irr = args.get_default( 'beta_irr', 'Stellar irradiation beta factor', 0.25) phy.smaxis = args.get_param( 'smaxis', None, 'Orbital semi-major axis', gt=0.0) phy.rstar = args.get_param( 'rstar', None, 'Stellar radius', gt=0.0) phy.mstar = args.get_param( 'mstar', None, 'Stellar mass', gt=0.0) phy.gstar = args.get_default( 'gstar', 'Stellar surface gravity', gt=0.0) phy.tstar = args.get_default( 'tstar', 'Stellar effective temperature (K)', gt=0.0) pyrat.voigt.extent = args.get_default( 'vextent', 'Voigt profile extent in HWHM', 100.0, ge=1.0) pyrat.voigt.cutoff = args.get_default( 'vcutoff', 'Voigt profile cutoff in cm-1', 25.0, ge=0.0) pyrat.voigt.ndop = args.get_default( 'ndop', 'Number of Doppler-width samples', 40, ge=1) pyrat.voigt.dmin = args.get_default( 'dmin', 'Minimum Doppler HWHM (cm-1)', gt=0.0) pyrat.voigt.dmax = args.get_default( 'dmax', 'Maximum Doppler HWHM (cm-1)', gt=0.0) pyrat.voigt.nlor = args.get_default( 'nlor', 'Number of Lorentz-width samples', 40, ge=1) pyrat.voigt.lmin = args.get_default( 'lmin', 'Minimum Lorentz HWHM (cm-1)', gt=0.0) pyrat.voigt.lmax = args.get_default( 'lmax', 'Maximum Lorentz HWHM (cm-1)', gt=0.0) pyrat.voigt.dlratio = args.get_default( 'dlratio', 'Doppler/Lorentz-width ratio threshold', 0.1, gt=0.0) pyrat.ex.tmin = args.get_param( 'tmin', 'kelvin', 'Minimum temperature of opacity grid', gt=0.0) pyrat.ex.tmax = args.get_param( 'tmax', 'kelvin', 'Maximum temperature of opacity grid', gt=0.0) pyrat.ex.tstep = args.get_default( 'tstep', "Opacity grid's temperature sampling step in K", gt=0.0) pyrat.rayleigh.pars = args.rpars pyrat.cloud.pars = args.cpars pyrat.rayleigh.model_names = args.get_choice( 'rayleigh', 'Rayleigh model', pc.rmodels) pyrat.cloud.model_names = args.get_choice( 'clouds', 'cloud model', pc.cmodels) pyrat.alkali.model_names = args.get_choice( 'alkali', 'alkali model', pc.amodels) pyrat.alkali.cutoff = args.get_default( 'alkali_cutoff', 'Alkali profiles hard cutoff from line center (cm-1)', 4500.0, gt=0.0) pyrat.cloud.fpatchy = args.get_default( 'fpatchy', 'Patchy-cloud fraction', ge=0.0, le=1.0) pyrat.od.rt_path = args.get_choice( 'rt_path', 'radiative-transfer observing geometry', pc.rt_paths) pyrat.spec._rt_path = pyrat.od.rt_path pyrat.ex.ethresh = args.get_default( 'ethresh', 'Extinction-cofficient threshold', 1e-15, gt=0.0) pyrat.od.maxdepth = args.get_default( 'maxdepth', 'Maximum optical-depth', 10.0, ge=0.0) phy.starspec = args.get_path('starspec', 'Stellar spectrum', exists=True) phy.kurucz = args.get_path('kurucz', 'Kurucz model', exists=True) phy.marcs = args.get_path('marcs', 'MARCS model', exists=True) phy.phoenix = args.get_path('phoenix', 'PHOENIX model', exists=True) spec.raygrid = args.get_default( 'raygrid', 'Emission raygrid (deg)', np.array([0, 20, 40, 60, 80.])) spec.quadrature = args.get_default( 'quadrature', 'Number of Gaussian-quadrature points', ge=1) pyrat.obs.units = args.get_default( 'dunits', 'Data units', 'none', wflag=args.data is not None) if not hasattr(pc, pyrat.obs.units): log.error(f'Invalid data units (dunits): {pyrat.obs.units}') pyrat.obs.data = args.get_param('data', pyrat.obs.units, 'Data') pyrat.obs.uncert = args.get_param( 'uncert', pyrat.obs.units, 'Uncertainties') filters = args.get_path('filters', 'Filter pass-bands', exists=True) if filters is not None: pyrat.obs.filters = [ ps.PassBand(filter_file) for filter_file in filters ] pyrat.obs.obsfile = args.get_path( 'obsfile', 'Observations data file', exists=True, ) if pyrat.obs.obsfile is not None: # TBD: Throw error if data, uncert, or filters already exist obs_data = io.read_observations(pyrat.obs.obsfile) if len(obs_data) == 3: pyrat.obs.filters, pyrat.obs.data, pyrat.obs.uncert = obs_data elif len(obs_data) == 1: pyrat.obs.filters = obs_data pyrat.ret.retflag = args.get_choice( 'retflag', 'retrieval flag', pc.retflags) if pyrat.ret.retflag is None: pyrat.ret.retflag = [] pyrat.ret.params = args.params pyrat.ret.pstep = args.pstep pyrat.ret.pmin = args.pmin pyrat.ret.pmax = args.pmax pyrat.ret.prior = args.prior pyrat.ret.priorlow = args.priorlow pyrat.ret.priorup = args.priorup pyrat.ret.qcap = args.get_default( 'qcap', 'Metals volume-mixing-ratio cap', 1.0, gt=0.0, le=1.0) pyrat.ret.params = args.params pyrat.ret.pstep = args.pstep pyrat.ret.tlow = args.get_default( 'tlow', 'Retrieval low-temperature (K) bound', 0, wflag=(runmode=='mcmc')) pyrat.ret.thigh = args.get_default( 'thigh', 'Retrieval high-temperature (K) bound', np.inf, wflag=(runmode=='mcmc')) pyrat.ret.sampler = args.sampler pyrat.ret.nsamples = args.get_default( 'nsamples', 'Number of MCMC samples', gt=0) pyrat.ret.burnin = args.get_default( 'burnin', 'Number of burn-in samples per chain', gt=0) pyrat.ret.thinning = args.get_default( 'thinning', 'MCMC posterior thinning', 1, ge=1) pyrat.ret.nchains = args.get_default( 'nchains', 'Number of MCMC parallel chains', ge=1) pyrat.ret.grbreak = args.get_default( 'grbreak', 'Gelman-Rubin convergence criteria', 0.0, ge=0) pyrat.ret.grnmin = args.get_default( 'grnmin', 'Gelman-Rubin convergence fraction', 0.5, gt=0.0) atm.molmodel = args.get_choice( 'molmodel', 'molecular-abundance model', pc.molmodels) if atm.molmodel is None: atm.molmodel = [] atm.molfree = args.molfree atm.molpars = args.molpars atm.bulk = args.bulk 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) atm.tmodelname = args.get_choice('tmodel', 'temperature model', pc.tmodels) atm.tpars = args.tpars pyrat.ncpu = args.get_default('ncpu', 'Number of processors', 1, ge=1) return