Source code for pypeit.par.util

# -*- coding: utf-8 -*-
"""
Utility functions for PypeIt parameter sets

.. include:: ../include/links.rst
"""
import os
import time
import glob
from IPython import embed

import numpy as np

from astropy.table import Table

from configobj import ConfigObj

from pypeit import msgs, __version__


#-----------------------------------------------------------------------
# Parameter utility functions
#-----------------------------------------------------------------------
[docs]def _eval_ignore(): """Provides a list of strings that should not be evaluated.""" return [ 'open', 'file', 'dict', 'list', 'tuple' ]
[docs]def eval_tuple(inp): """ Evaluate the input to one or more tuples. This allows conversion of one or more tuples provided to a configuration parameters. .. warning:: - Currently can only handle simple components that can also be evaluated (e.g., integers and floats). Args: inp (:obj:`list`): A list of strings that are converted into a list of tuples. The parentheses must be within the list of elements. Return: :obj:`list`: The list of tuples. """ joined = ','.join(inp) try: basic = eval(joined) except: msgs.error(f'Cannot evaluate {joined} into a valid tuple.') # If any element of the basic evaulation is also a tuple, assume the result # of the evaluation is a tuple of tuples. This is converted to a list. return list(basic) if any([isinstance(e, tuple) for e in basic]) else [basic]
[docs]def recursive_dict_evaluate(d): """ Recursively run :func:`eval` on each element of the provided dictionary. A raw read of a configuration file with `configobj`_ results in a dictionary that contains strings or lists of strings. However, when assigning the values for the various ParSets, the `from_dict` methods expect the dictionary values to have the appropriate type. E.g., the `configobj`_ will have something like d['foo'] = '1', when the `from_dict` method expects the value to be an integer (d['foo'] = 1). This function tries to evaluate *all* dictionary values, except for those listed above in the :func:`_eval_ignore` function. Any value in this list or where:: eval(d[k]) for k in d.keys() raises an exception is returned as the original string. This is currently only used in :func:`~pypeit.par.pypeitpar.PypitPar.from_cfg_file`; see further comments there. Args: d (dict): Dictionary of values to evaluate Returns: dict: Identical to input dictionary, but with all string values replaced with the result of `eval(d[k])` for all `k` in `d.keys()`. """ ignore = _eval_ignore() for k in d.keys(): if isinstance(d[k], dict): # Recursive call to deal with nested dictionaries d[k] = recursive_dict_evaluate(d[k]) continue if isinstance(d[k], list) and any(['(' in e for e in d[k]]): # NOTE: This enables syntax for constructing one or more tuples. d[k] = eval_tuple(d[k]) continue if isinstance(d[k], list): replacement = [] for v in d[k]: if v in ignore: replacement += [ v ] else: try: replacement += [ eval(v) ] except: replacement += [ v ] d[k] = replacement continue try: d[k] = eval(d[k]) if d[k] not in ignore else d[k] except: pass return d
# TODO: I don't think this is used. We should deprecate it.
[docs]def get_parset_list(cfg, pk, parsetclass): """ Create a list of ParSets based on a root keyword for a set of defined groups in the configuration file. For example, the :class:`InstrumentPar` group allows for a list of detectors (:class:`DetectorPar`) with keywords like `detector1`, `detector2`, etc. This function parses the provided configuration object (`cfg`) to find any sections with `detector` (`pk`) as its root. The remainder of the section name must be able to be converted to an integer and the section itself must be able to setup an instance of `parsetclass`. The sections must be number sequentially from 1..N. E.g., the :class:`InstrumentPar` configuration file cannot have `dectector1` and `detector3`, but no `detector2`. The call to setup the detectors in the :class:`InstrumentPar` is:: kwargs['detector'] = get_parset_list(cfg, 'detector', DetectorPar) Args: cfg (`configobj`_, :obj:`dict`): The top-level configuration that defines a list of sub-ParSets. pk (str): The root of the keywords used to set a list of sub-ParSets. parsetclass (:class:`pypeit.par.parset.ParSet`): The class used to construct each element in the list of parameter subsets. The class **must** have a `from_dict` method that instantiates the :class:`pypeit.par.parset.ParSet` based on the provide subsection/subdict from cfg. Returns: list: A list of instances of `parsetclass` parsed from the provided configuration data. Raises: ValueError: Raised if the indices of the subsections are not sequential and 1-indexed. """ # Get the full list of keys k = cfg.keys() # Iterate through the list of keys to find the appropriate sub # parameter sets and their order. par = [] order = [] for _k in k: if _k == pk and cfg[_k] is None: continue if pk in _k: try: # Get the order for this subgroup (e.g., 2 for # 'detector2' order += [ int(_k.replace(pk,'')) ] # And instantiate the parameter set par += [ parsetclass.from_dict(cfg[_k]) ] except: continue if len(par) > 0: # Make sure the instances are correctly sorted and sequential srt = np.argsort(order) if np.any(np.array(order)[srt]-1 != np.arange(order[srt[-1]])): raise ValueError('Parameter set series must be sequential and 1-indexed.') # Return the sorted instances return [par[i] for i in srt] # No such subsets were defined, so return a null result return None
[docs]def parset_to_dict(par): """ Convert the provided parset into a dictionary. Args: par (ParSet): Returns: dict: Converted ParSet """ try: d = dict(ConfigObj(par.to_config(section_name='tmp'))['tmp']) except: d = dict(ConfigObj(par.to_config())) return recursive_dict_evaluate(d)