# -*- coding: utf-8 -*-
"""
Utility functions for parameter sets.
----
.. include license and copyright
.. include:: ../include/copy.rst
----
.. include common links, assuming primary doc root is up one directory
.. include:: ../include/links.rst
"""
import numpy
[docs]
def _eval_ignore():
"""Provides a list of strings that should not be evaluated."""
return [ 'open', 'file', 'dict', 'list', 'tuple' ]
[docs]
def recursive_dict_evaluate(d):
"""
Recursively run `eval`_ on each element of the provided
dictionary.
A raw read of a configuration file with `configobj.ConfigObj`_
results in a dictionary that contains strings or lists of strings.
However, when assigning the values for the various
:class:`mangadap.par.parset.ParSet` objects, the
:func:`mangadap.par.parset.ParSet.from_dict` methods expect the
dictionary values to have the appropriate type. E.g., the
`configobj.ConfigObj`_ will have something like ``d['foo'] = '1'``,
when the :func:`mangadap.par.parset.ParSet.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 by the :func:`_eval_ignore` function. Any value in
this list or where::
eval(d[k]) for k in d.keys()
raises an exception and is returned as the original string.
Args:
d (:obj:`dict`):
Dictionary of values to evaluate
Returns:
:obj:`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):
d[k] = recursive_dict_evaluate(d[k])
elif isinstance(d[k], list):
replacement = []
for v in d[k]:
if v in _eval_ignore():
replacement += [ v ]
else:
try:
replacement += [ eval(v) ]
except:
replacement += [ v ]
d[k] = replacement
else:
try:
d[k] = eval(d[k]) if d[k] not in _eval_ignore() else d[k]
except:
pass
return d
[docs]
def recursive_dict_str_to_None(d):
"""
Recursively run any string values of 'None' in a dictionary to be explicitly
None.
Args:
d (:obj:`dict`):
Dictionary of values to evaluate
Returns:
:obj:`dict`: Identical to input dictionary, but with all string values
of 'None' set to None explicitly. **The input dictionary is altered
directly.**
"""
for k in d.keys():
if isinstance(d[k], dict):
d[k] = recursive_dict_evaluate(d[k])
elif isinstance(d[k], list):
replacement = []
for v in d[k]:
replacement += [v if v != 'None' else None]
d[k] = replacement
elif d[k] == 'None':
d[k] = None
return d
[docs]
def get_parset_list(cfg, pk, parsetclass):
"""
Create a list of :class:`mangadap.par.parset.ParSet` objects based
on a root keyword for a set of defined groups in the configuration
file.
For example, the a parameter group could allow for a list of parset
objects instead of a single one. This function parses the provided
configuration object (``cfg``) to find any sections with a string
keyword (`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.
Args:
cfg (`configobj.ConfigObj`_, :obj:`dict`):
The top-level configuration that defines a list of
sub-ParSets.
pk (:obj:`str`):
The root of the keywords used to set a list of sub-ParSets.
parsetclass (:class:`mangadap.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:`mangadap.par.parset.ParSet` based on the provided
subsection/subdict from ``cfg``.
Returns:
:obj:`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 = numpy.argsort(order)
if numpy.any(numpy.array(order)[srt]-1 != numpy.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