Source code for sfepy.base.conf
"""
Problem description file handling.
Notes
-----
Short syntax: key is suffixed with '__<number>' to prevent collisions with long
syntax keys -> both cases can be used in a single input.
"""
from __future__ import absolute_import
import re
import numpy as nm
from sfepy.base.base import (Struct, IndexedStruct, dict_to_struct,
output, copy, update_dict_recursively,
import_file, assert_, get_default, basestr)
from sfepy.base.parse_conf import create_bnf
import six
_required = ['filename_mesh|filename_domain', 'field_[0-9]+|fields',
# TODO originaly EBC were required to be specified but in some examples
# (especially 1D) only EPBCs specified is valid
'ebc_[0-9]+|ebcs|dgebc_[0-9]+|dgebcs|dgepbc_[0-9]+|dgepbcs',
'equations',
'region_[0-9]+|regions', 'variable_[0-9]+|variables',
'material_[0-9]+|materials',
'solver_[0-9]+|solvers']
_other = ['epbc_[0-9]+|epbcs',
'lcbc_[0-9]+|lcbcs', 'nbc_[0-9]+|nbcs',
'ic_[0-9]+|ics', 'function_[0-9]+|functions', 'options',
'integral_[0-9]+|integrals']
[docs]
def tuple_to_conf(name, vals, order):
"""
Convert a configuration tuple `vals` into a Struct named `name`, with
attribute names given in and ordered by `order`.
Items in `order` at indices outside the length of `vals` are ignored.
"""
conf = Struct(name=name)
for ii, key in enumerate(order[:len(vals)]):
setattr(conf, key, vals[ii])
return conf
[docs]
def transform_variables(adict):
d2 = {}
for ii, (key, conf) in enumerate(six.iteritems(adict)):
if isinstance(conf, tuple):
c2 = tuple_to_conf(key, conf, ['kind', 'field'])
if len(conf) >= 3:
kind = c2.kind.split()[0]
if kind == 'unknown':
c2.order = conf[2]
elif kind == 'test':
c2.dual = conf[2]
elif kind == 'parameter':
if isinstance(conf[2], basestr) or (conf[2] is None):
c2.like = conf[2]
else:
c2.like = None
c2.special = conf[2]
if len(conf) == 4:
c2.history = conf[3]
d2['variable_%s__%d' % (c2.name, ii)] = c2
else:
c2 = transform_to_struct_1(conf)
d2['variable_'+c2.name] = c2
return d2
[docs]
def transform_conditions(adict, prefix):
d2 = {}
for ii, (key, conf) in enumerate(six.iteritems(adict)):
if isinstance(conf, tuple):
if len(conf) == 2:
c2 = tuple_to_conf(key, conf, ['region', 'dofs'])
else:
c2 = tuple_to_conf(key, conf, ['region', 'times', 'dofs'])
d2['%s_%s__%d' % (prefix, c2.name, ii)] = c2
else:
c2 = transform_to_struct_1(conf)
d2['%s_%s' % (prefix, c2.name)] = c2
return d2
[docs]
def transform_lcbcs(adict):
d2 = {}
for ii, (key, conf) in enumerate(six.iteritems(adict)):
if isinstance(conf, tuple):
if len(conf) >= 4:
if isinstance(conf[1], dict):
c2 = tuple_to_conf(key, conf, ['region', 'dofs',
'dof_map_fun', 'kind'])
c2.arguments = conf[4:]
else:
c2 = tuple_to_conf(key, conf, ['region', 'times', 'dofs',
'dof_map_fun', 'kind'])
c2.arguments = conf[5:]
else:
msg = 'LCBC syntax has to be: region[, times], dofs,' \
' dof_map_fun, kind[, other arguments]'
raise SyntaxError(msg)
d2['lcbc_%s__%d' % (c2.name, ii)] = c2
else:
c2 = transform_to_struct_1(conf)
c2.set_default('dof_map_fun', None)
c2.set_default('arguments', ())
d2['lcbc_%s' % (c2.name)] = c2
return d2
[docs]
def transform_epbcs(adict, prefix="epbc"):
d2 = {}
for ii, (key, conf) in enumerate(six.iteritems(adict)):
if isinstance(conf, tuple):
if len(conf) == 3:
c2 = tuple_to_conf(key, conf, ['region', 'dofs', 'match'])
else:
c2 = tuple_to_conf(key, conf,
['region', 'times', 'dofs', 'match'])
d2['%s_%s__%d' % (prefix, c2.name, ii)] = c2
else:
c2 = transform_to_struct_1(conf)
d2['%s_%s' % (prefix, c2.name)] = c2
return d2
[docs]
def transform_regions(adict):
d2 = {}
for ii, (key, conf) in enumerate(six.iteritems(adict)):
if isinstance(conf, basestr):
c2 = Struct(name=key, select=conf)
d2['region_%s__%d' % (c2.name, ii)] = c2
elif isinstance(conf, tuple):
c2 = tuple_to_conf(key, conf, ['select', 'kind'])
if len(conf) == 3:
c2.parent = conf[2]
if len(conf) == 4:
c2.parent = conf[2]
c2.extra_options = conf[3]
d2['region_%s__%d' % (c2.name, ii)] = c2
else:
c2 = transform_to_struct_1(conf)
d2['region_'+c2.name] = c2
return d2
[docs]
def transform_integrals(adict):
d2 = {}
for ii, (key, conf) in enumerate(six.iteritems(adict)):
if isinstance(conf, int):
c2 = Struct(name=key, order=conf)
d2['integral_%s__%d' % (c2.name, ii)] = c2
elif isinstance(conf, tuple):
if len(conf) == 2: # Old tuple version with now-ignored 'kind'.
conf = conf[1]
c2 = Struct(name=key, order=conf)
elif len(conf) == 3:
c2 = tuple_to_conf(key, conf, ['order', 'vals', 'weights'])
d2['integral_%s__%d' % (c2.name, ii)] = c2
else:
c2 = transform_to_struct_1(conf)
d2['integral_'+c2.name] = c2
return d2
[docs]
def transform_fields(adict):
dtypes = {'real' : nm.float64, 'complex' : nm.complex128}
d2 = {}
for ii, (key, conf) in enumerate(six.iteritems(adict)):
if isinstance(conf, tuple):
c2 = tuple_to_conf(key, conf,
['dtype', 'shape', 'region', 'approx_order',
'space', 'poly_space_basis'])
if c2.dtype in dtypes:
c2.dtype = dtypes[c2.dtype]
d2['field_%s__%d' % (c2.name, ii)] = c2
else:
c2 = transform_to_struct_1(conf)
c2.set_default('dtype', nm.float64)
if c2.dtype in dtypes:
c2.dtype = dtypes[c2.dtype]
d2['field_'+c2.name] = c2
return d2
[docs]
def transform_materials(adict):
d2 = {}
for ii, (key, conf) in enumerate(six.iteritems(adict)):
if isinstance(conf, basestr):
c2 = Struct(name=key, function=conf)
d2['material_%s__%d' % (c2.name, ii)] = c2
elif isinstance(conf, tuple):
c2 = tuple_to_conf(key, conf,
['values', 'function', 'kind'])
if len(conf) == 4:
c2.flags = conf[3]
d2['material_%s__%d' % (c2.name, ii)] = c2
else:
c2 = transform_to_struct_1(conf)
d2['material_'+conf['name']] = c2
return d2
[docs]
def transform_solvers(adict):
d2 = {}
for ii, (key, conf) in enumerate(six.iteritems(adict)):
if isinstance(conf, tuple):
c2 = tuple_to_conf(key, conf, ['kind','params'])
for param, val in six.iteritems(c2.params):
setattr(c2, param, val)
delattr(c2, 'params')
d2['solvers_%s__%d' % (c2.name, ii)] = c2
else:
c2 = transform_to_struct_1(conf)
d2['solvers_'+c2.name] = c2
return d2
[docs]
def transform_functions(adict):
d2 = {}
for ii, (key, conf) in enumerate(six.iteritems(adict)):
if isinstance(conf, tuple):
c2 = tuple_to_conf(key, conf, ['function'])
d2['function_%s__%d' % (c2.name, ii)] = c2
else:
c2 = transform_to_struct_1(conf)
d2['function_'+c2.name] = c2
return d2
[docs]
def transform_to_i_struct_1(adict):
return dict_to_struct(adict, flag=(1,), constructor=IndexedStruct)
transforms = {
'options' : transform_to_i_struct_1,
'solvers' : transform_solvers,
'integrals' : transform_integrals,
'regions' : transform_regions,
'fields' : transform_fields,
'variables' : transform_variables,
'ebcs' : transform_ebcs,
'epbcs' : transform_epbcs,
'dgebcs' : transform_dgebcs,
'dgepbcs' : transform_dgepbcs,
'nbcs' : transform_to_struct_01,
'lcbcs' : transform_lcbcs,
'ics' : transform_ics,
'materials' : transform_materials,
'functions' : transform_functions,
}
[docs]
def dict_from_string(string, allow_tuple=False, free_word=False):
"""
Parse `string` and return a dictionary that can be used to
construct/override a ProblemConf instance.
"""
if string is None:
return {}
if isinstance(string, dict):
return string
parser = create_bnf(allow_tuple=allow_tuple, free_word=free_word)
out = {}
for r in parser.parseString(string, parseAll=True):
out.update(r)
return out
[docs]
def dict_from_options(options):
"""
Return a dictionary that can be used to construct/override a ProblemConf
instance based on `options`.
See ``--conf`` and ``--options`` options of the ``simple.py`` script.
"""
override = dict_from_string(options.conf)
if options.app_options:
if not 'options' in override:
override['options'] = {}
override_options = dict_from_string(options.app_options)
override['options'].update(override_options)
return override
##
# 27.10.2005, c
[docs]
class ProblemConf(Struct):
"""
Problem configuration, corresponding to an input (problem description
file). It validates the input using lists of required and other keywords
that have to/can appear in the input. Default keyword lists can be obtained
by sfepy.base.conf.get_standard_keywords().
ProblemConf instance is used to construct a Problem instance via
Problem.from_conf(conf).
"""
[docs]
@staticmethod
def from_file(filename, required=None, other=None, verbose=True,
define_args=None, override=None, setup=True):
"""
Loads the problem definition from a file.
The filename can either contain plain definitions, or it can contain
the define() function, in which case it will be called to return the
input definitions.
The job of the define() function is to return a dictionary of
parameters. How the dictionary is constructed is not our business, but
the usual way is to simply have a function define() along these lines
in the input file::
def define():
options = {
'save_eig_vectors' : None,
'eigen_solver' : 'eigen1',
}
region_2 = {
'name' : 'Surface',
'select' : 'nodes of surface',
}
return locals()
Optionally, the define() function can accept additional arguments
that should be defined using the `define_args` tuple or dictionary.
"""
funmod = import_file(filename, package_name=False)
if "define" in funmod.__dict__:
if define_args is None:
define_dict = funmod.__dict__["define"]()
else:
if isinstance(define_args, str):
define_args = dict_from_string(define_args)
if isinstance(define_args, dict):
define_dict = funmod.__dict__["define"](**define_args)
else:
define_dict = funmod.__dict__["define"](*define_args)
else:
define_dict = funmod.__dict__
obj = ProblemConf(define_dict, funmod=funmod, filename=filename,
required=required, other=other, verbose=verbose,
override=override, setup=setup)
return obj
[docs]
@staticmethod
def from_file_and_options(filename, options, required=None, other=None,
verbose=True, define_args=None, setup=True):
"""
Utility function, a wrapper around ProblemConf.from_file() with
possible override taken from `options`.
"""
override = dict_from_options(options)
obj = ProblemConf.from_file(filename, required=required, other=other,
verbose=verbose, define_args=define_args,
override=override, setup=setup)
return obj
[docs]
@staticmethod
def from_module(module, required=None, other=None, verbose=True,
override=None, setup=True):
obj = ProblemConf(module.__dict__, module, module.__name__,
required, other, verbose, override, setup=setup)
return obj
[docs]
@staticmethod
def from_dict(dict_, funmod, required=None, other=None, verbose=True,
override=None, setup=True):
obj = ProblemConf(dict_, funmod, None, required, other, verbose,
override, setup=setup)
return obj
def __init__(self, define_dict, funmod=None, filename=None,
required=None, other=None, verbose=True, override=None,
setup=True):
if override:
if isinstance(override, Struct):
override = override.__dict__
define_dict = update_dict_recursively(define_dict, override, True)
self.__dict__.update(define_dict)
self.verbose = verbose
if setup:
self.setup(funmod=funmod, filename=filename,
required=required, other=other)
[docs]
def setup(self, define_dict=None, funmod=None, filename=None,
required=None, other=None):
define_dict = get_default(define_dict, self.__dict__)
self._filename = filename
self.validate(required=required, other=other)
self.transform_input_trivial()
self._raw = {}
for key, val in six.iteritems(define_dict):
if isinstance(val, dict):
self._raw[key] = copy(val)
self.transform_input()
self.funmod = funmod
def _validate_helper(self, items, but_nots):
keys = list(self.__dict__.keys())
left_over = keys[:]
if but_nots is not None:
for item in but_nots:
match = re.compile('^' + item + '$').match
for key in keys:
if match(key):
left_over.remove(key)
missing = []
if items is not None:
for item in items:
found = False
match = re.compile('^' + item + '$').match
for key in keys:
if match(key):
found = True
left_over.remove(key)
if not found:
missing.append(item)
return left_over, missing
[docs]
def validate(self, required=None, other=None):
required_left_over, required_missing \
= self._validate_helper(required, other)
other_left_over, other_missing \
= self._validate_helper(other, required)
assert_(required_left_over == other_left_over)
if other_left_over and self.verbose:
output('left over:', other_left_over)
if required_missing:
raise ValueError('required missing: %s' % required_missing)
return other_missing
[docs]
def transform_input_trivial(self):
"""Trivial input transformations."""
##
# Unordered inputs.
tr_list = ['([a-zA-Z0-9]+)_[0-9]+']
# Keywords not in 'required', but needed even empty (e.g. for
# running tests).
for key in transforms.keys():
if key not in self.__dict__:
self.__dict__[key] = {}
keys = list(self.__dict__.keys())
for item in tr_list:
match = re.compile(item).match
for key in keys:
obj = match(key)
if obj:
new = obj.group(1) + 's'
result = {key : self.__dict__[key]}
try:
self.__dict__[new].update(result)
except:
self.__dict__[new] = result
del self.__dict__[key]
[docs]
def transform_input(self):
keys = list(self.__dict__.keys())
for key, transform in six.iteritems(transforms):
if not key in keys: continue
self.__dict__[key] = transform(self.__dict__[key])
[docs]
def get_item_by_name(self, key, item_name):
"""
Return item with name `item_name` in configuration group given
by `key`.
"""
val = getattr(self, key)
for item in six.itervalues(val):
if item.name == item_name:
return item
[docs]
def get_function(self, name):
"""
Get a function object given its name.
It can be either in `ProblemConf.funmod`, or a `ProblemConf`
attribute directly.
Parameters
----------
name : str or function or None
The function name or directly the function.
Returns
-------
fun : function or None
The required function, or None if `name` was `None`.
"""
if name is None:
fun = None
elif callable(name):
fun = name
else:
try:
fun = getattr(self.funmod, name)
except AttributeError:
try:
fun = getattr(self, name)
except AttributeError:
raise ValueError('function %s cannot be found!' % name)
return fun
[docs]
def update_conf(self, conf):
"""
Update configuration by values in another problem configuration.
Values that are dictionaries are updated in-place by ``dict.update()``.
Parameters
----------
conf : ProblemConf instance
The other configuration.
"""
for x in conf.__dict__:
his = conf.__dict__[x]
my = getattr(self, x, None)
if isinstance(my, dict) and isinstance(his, dict):
my.update(his)
else:
setattr(self, x, his)
[docs]
def add_missing(self, conf):
"""
Add missing values from another problem configuration.
Missing keys/values are added also to values that are dictionaries.
Parameters
----------
conf : ProblemConf instance
The other configuration.
"""
for x in conf.__dict__:
his = conf.__dict__[x]
my = getattr(self, x, None)
if isinstance(my, dict) and isinstance(his, dict):
for key in his:
if key not in my:
my[key]=his[key]
elif my is None:
setattr(self, x, his)