""" OptionsDictionary class definition. """
import warnings
from six import iteritems
[docs]class OptionsDictionary(object):
""" A dictionary for storing options for components/drivers/solvers. It
is generally used like a standard Python dictionary, except that 1) you
can only set or get keys that have been registered with add_option, and
2) type is enforced
Args
----
read_only : bool
If this is True, these options should not be modified at run time,
and should not be printed in the docs."""
# When this is True, variables marked as 'lock_on_setup' cannot be
# changed. In all of OpenMDAO's default OptDicts, this will be set to
# True on setup.
locked = False
def __init__(self, read_only=True):
self._options = {}
self._deprecations = {}
# When this is True, no variables in the dictionary can be modified.
self.read_only = read_only
# Start out with all dictionaries unlocked. Nobody should be creating
# them after setup.
# TODO: This really is a hack, but we couldn't figure out a way
# around it.
OptionsDictionary.locked = False
[docs] def add_option(self, name, value, lower=None, upper=None, values=None,
desc='', lock_on_setup=False):
""" Adds an option to this options dictionary.
Args
----
name : str
Name of the option.
value : object
Default value for this option. The type of this value will be enforced.
lower : float, optional
Lower bounds for a float value.
upper : float, optional
Upper bounds for a float value.
values : list, optional
List of all possible values for an enumeration option.
desc : str, optional
String containing documentation of this option.
"""
if name in self._options:
print("raising an error")
raise ValueError("Option '{}' already exists".format(name))
opt = {
'val': value,
'lower' : lower,
'upper' : upper,
'values' : values,
'desc' : desc,
'lock_on_setup' : lock_on_setup,
'changed' : False
}
self._check(name, value, opt)
self._options[name] = opt
[docs] def remove_option(self, name):
""" Removes the named option. Does nothing if the option is not found.
Args
----
name : str
Name of the option to remove.
"""
try:
del self._options[name]
except KeyError:
pass
def _add_deprecation(self, oldname, newname):
""" For renamed options, maps the old name to the new name and prints
a DeprecationWarning on each get/set that uses the old name.
Args
----
oldname : str
The deprecated name.
newname : str
The correct name.
"""
if newname not in self._options:
raise NameError("The '%s' option was not found." % newname)
self._deprecations[oldname] = newname
def __getitem__(self, name):
try:
return self._options[name]['val']
except KeyError:
try:
newname = self._deprecations[name]
_print_deprecation(name, newname)
return self._options[newname]['val']
except KeyError:
raise KeyError("Option '{}' has not been added".format(name))
def __contains__(self, name):
return name in self._options or name in self._deprecations
def __setitem__(self, name, value):
""" Set an option using dictionary-like access."""
if name not in self._options:
if name in self._deprecations:
newname = self._deprecations[name]
_print_deprecation(name, newname)
name = newname
else:
raise KeyError("Option '{}' has not been added".format(name))
opt = self._options[name]
self._check(name, value, opt)
opt['val'] = value
opt['changed'] = True
def __setattr__(self, name, value):
""" To prevent user error, disallow direct setting."""
if name in ['_options', 'read_only', '_deprecations', 'locked']:
super(OptionsDictionary, self).__setattr__(name, value)
else:
raise ValueError("Use dict-like access for option '{}'".format(name))
[docs] def get(self, name, default=None):
""" Gets a value from this OptionsDictionary.
Returns
-------
object
The value of the named option. If not found, returns the
default value that was passed in.
"""
if name in self._options:
return self._options[name]['val']
elif name in self._deprecations:
newname = self._deprecations[name]
_print_deprecation(name, newname)
return self._options[name]['val']
return default
[docs] def iteritems(self):
return self.items()
[docs] def items(self):
"""
Returns
-------
iterator
Iterator returning the name and option for each option.
"""
return ((name, opt['val']) for name, opt in iteritems(self._options))
def _check(self, name, value, opt):
""" Type checking happens here. """
# Raise an error when trying to set a restricted variable after
# setup.
if self.locked and opt['lock_on_setup']:
msg = "The '%s' option cannot be changed after setup." % name
raise RuntimeError(msg)
values = opt['values']
if values is not None:
# Only need to check if we are in the list if we are an enum
self._check_values(name, value, values)
else:
lower = opt['lower']
upper = opt['upper']
_type = type(opt['val'])
self._check_type(name, value, _type)
if lower is not None:
self._check_low(name, value, lower)
if upper is not None:
self._check_high(name, value, upper)
def _check_type(self, name, value, _type):
""" Check for type. """
if type(value) != _type:
msg = "'{}' should be a '{}'"
raise ValueError(msg.format(name, _type))
def _check_low(self, name, value, lower):
""" Check for violation of lower bounds. """
if value < lower:
msg = "minimum allowed value for '{}' is '{}'"
raise ValueError(msg.format(name, lower))
def _check_high(self, name, value, upper):
""" Check for violation of upper bounds. """
if value > upper:
msg = "maximum allowed value for '{}' is '{}'"
raise ValueError(msg.format(name, upper))
def _check_values(self, name, value, values):
""" Check for value not in enumeration. """
if value not in values:
msg = "'{}' must be one of the following values: '{}'"
raise ValueError(msg.format(name, values))
def _generate_docstring(self, dictname):
""" Generates a numpy-style docstring for an OptionsDictionary.
Returns
-------
docstring : str
string that contains part of a basic numpy docstring.
"""
docstring = []
for (name, val) in sorted(self.items()):
docstring.extend([" ", dictname, "['", name, "']",
" : ", type(val).__name__, "("])
if isinstance(val, str):
docstring.append("'%s'"%val)
else:
docstring.append(str(val))
docstring.append(")\n")
desc = self._options[name]['desc']
if(desc):
docstring.extend([" ", desc, "\n"])
return ''.join(docstring)
def _print_deprecation(name, newname):
warnings.simplefilter('always', DeprecationWarning)
warnings.warn("Option '%s' is deprecated. Use '%s' instead." %
(name, newname),
DeprecationWarning,stacklevel=2)
warnings.simplefilter('ignore', DeprecationWarning)
[docs]class DeprecatedOptionsDictionary(object):
""" The fd_option dicts got renamed to deriv_options, and much of the
basic functions have changed. This object will help the user convert to
the new format.
"""
def __init__(self, opt):
self.opt = opt
def __getitem__(self, name):
warnings.simplefilter('always', DeprecationWarning)
warnings.warn("fd_options is deprecated. Use deriv_options instead.",
DeprecationWarning,stacklevel=2)
warnings.simplefilter('ignore', DeprecationWarning)
if name == 'force_fd':
return self.opt['type'] in ['fd', 'cs']
return self.opt[name]
def __setitem__(self, name, value):
warnings.simplefilter('always', DeprecationWarning)
warnings.warn("fd_options is deprecated. Use deriv_options instead.",
DeprecationWarning,stacklevel=2)
if name == 'force_fd':
msg = "Also, the 'force_fd' option has been removed. To force " + \
"finite difference now, set the 'type' option to 'fd'."
warnings.warn(msg)
deriv_type = 'fd' if value==True else 'user'
self.opt['type'] = deriv_type
elif name == 'step_type':
msg = "Also, the 'step_type' option has been renamed to 'step_calc."
warnings.warn(msg)
self.opt['step_calc'] = value
elif name == 'form' and value == 'complex_step':
msg = "Also, complex step is not longer activated using the 'form' " + \
"option. To turn on complex step, set the 'type' option to 'cs'"
warnings.warn(msg)
self.opt['type'] = 'cs'
elif name == 'extra_check_partials_form':
msg = "Also, options for check_partial_derivatives have been changed and expanded. " + \
"Please see the srcdocs for the Component class."
warnings.warn(msg)
self.opt['check_form'] = value
else:
self.opt[name] = value
warnings.simplefilter('ignore', DeprecationWarning)