Source code for sfepy.solvers.solvers

"""
Base (abstract) solver classes.
"""
from __future__ import absolute_import
import numpy as nm
from sfepy.base.base import Struct, output, structify
import six

[docs]def make_get_conf(conf, kwargs): def _get_conf_item(name, default=None, msg_if_none=None): return kwargs.get(name, conf.get(name, default=default, msg_if_none=msg_if_none)) return _get_conf_item
[docs]def format_next(text, new_text, pos, can_newline, width, ispaces): new_len = len(new_text) if (pos + new_len > width) and can_newline: text += '\n' + ispaces + new_text pos = new_len can_newline = False else: if pos > 0: text += ' ' + new_text pos += new_len + 1 else: text += ispaces + new_text pos += len(ispaces) + new_len can_newline = True return text, pos, can_newline
[docs]def typeset_to_indent(txt, indent, width): if not len(txt): return txt txt_lines = txt.strip().split('\n') ispaces = ' ' * indent can_newline = False pos = 0 text = '' for line in txt_lines: for word in line.split(): text, pos, can_newline = format_next(text, word, pos, can_newline, width, ispaces) return text
[docs]def make_option_docstring(name, kind, default, required, doc): if default is None: entry = ' %s : %s\n' % (name, kind) else: entry = ' %s : %s (default: %s)\n' % (name, kind, repr(default)) entry += typeset_to_indent(doc, 8, 75) return entry
[docs]def use_first_available(solver_list, context=None): """ Use the first available solver from `solver_list`. Parameters ---------- solver_list : list of str or Struct The list of solver names or configuration objects. context : object, optional An optional solver context to pass to the solver. Returns ------- out : Solver The first available solver. """ prev_name = None for solver, conf in solver_list: name = solver if isinstance(solver, str) else solver.name if prev_name is not None: output("'%s' not available, trying '%s'" % (prev_name, name)) try: _conf = conf.copy() _conf.__dict__.pop('fallback', None) if isinstance(solver, str): out = Solver.any_from_conf(_conf, context=context) else: out = solver(_conf, contex=context) output("using '%s' solver" % name) break except (KeyboardInterrupt, SystemExit): raise except Exception as exc: output('solver construction failed with: "{0!r}"'.format(exc)) prev_name = name else: raise ValueError('no solver available from %s!' % [ii[0] for ii in solver_list]) return out
par_template = \ """ For common configuration parameters, see :class:`Solver <sfepy.solvers.solvers.Solver>`. Specific configuration parameters: Parameters ---------- """
[docs]class SolverMeta(type): """ Metaclass for solver classes that automatically adds configuration parameters to the solver class docstring from the ``_parameters`` class attribute. """ def __new__(cls, name, bases, ndict): if '__doc__' in ndict: ndict['__doc__'] = ndict['__doc__'].rstrip() solver_kind = ndict.get('name') if solver_kind is not None: ndict['__doc__'] += '\n\n Kind: %s\n' % repr(solver_kind) ndict['__doc__'] += par_template options = ndict.get('_parameters') if options is not None: docs = [make_option_docstring(*option) for option in options] ndict['__doc__'] = (ndict['__doc__'].rstrip() + '\n' + '\n'.join(docs)) return super(SolverMeta, cls).__new__(cls, name, bases, ndict)
[docs]class Solver(Struct): """ Base class for all solver kinds. Takes care of processing of common configuration options. The factory method any_from_conf() can be used to create an instance of any subclass. The subclasses have to reimplement __init__() and __call__(). All solvers use the following configuration parameters: Parameters ---------- """ __metaclass__ = SolverMeta _parameters = [ ('name', 'str', None, True, 'The name referred to in problem description options.'), ('kind', 'str', None, True, """The solver kind, as given by the `name` class attribute of the Solver subclasses."""), ('verbose', 'bool', False, False, """If True, the solver can print more information about the solution."""), ]
[docs] @classmethod def process_conf(cls, conf, kwargs): """ Process configuration parameters. """ get = make_get_conf(conf, kwargs) if len(cls._parameters) and cls._parameters[0][0] != 'name': options = Solver._parameters + cls._parameters else: options = Solver._parameters opts = Struct() allow_extra = False for name, _, default, required, _ in options: if name == '*': allow_extra = True continue msg = ('missing "%s" in options!' % name) if required else None setattr(opts, name, get(name, default, msg)) if allow_extra: all_keys = set(conf.to_dict().keys()) other = all_keys.difference(list(opts.to_dict().keys())) for name in other: setattr(opts, name, get(name, None, None)) return opts
def __init__(self, conf=None, context=None, **kwargs): if conf is None: conf = Struct() elif isinstance(conf, dict): conf = Struct(**conf) if conf.get('name', None) is None: conf.name = 'auto_' + self.__class__.__name__ if conf.get('kind', None) is None: if hasattr(self.__class__, 'name'): conf.kind = self.__class__.name else: raise ValueError('solver kind cannot be determined!') new_conf = self.process_conf(conf, kwargs) Struct.__init__(self, conf=new_conf, orig_conf=conf, context=context, **kwargs)
[docs] def build_solver_kwargs(self, conf): """ Build the `kwargs` dict for the underlying solver function using the extra options (marked by '*' in ``_parameters``) in `conf`. The declared parameters are omitted. """ if len(self._parameters) and self._parameters[0][0] != 'name': options = Solver._parameters + self._parameters else: options = self._parameters std = set([ii[0] for ii in options if ii[0] != '*']) kwargs = {} for key, val in six.iteritems(conf.to_dict()): if key not in std: kwargs[key] = val return kwargs
def __call__(self, **kwargs): raise ValueError('called an abstract Solver instance!')
[docs] def set_field_split(self, field_ranges, **kwargs): pass
[docs]class LinearSolver(Solver): """ Abstract linear solver class. """ def __init__(self, conf, mtx=None, status=None, context=None, **kwargs): Solver.__init__(self, conf=conf, mtx=mtx, status=status, context=context, **kwargs) self.mtx_digest = (0, '') def __call__(self, rhs, x0=None, conf=None, eps_a=None, eps_r=None, i_max=None, mtx=None, status=None, context=None, **kwargs): raise ValueError('called an abstract LinearSolver instance!')
[docs] def get_tolerance(self): """ Return tuple `(eps_a, eps_r)` of absolute and relative tolerance settings. Either value can be `None`, meaning that the solver does not use that setting. """ return self.conf.get('eps_a', None), self.conf.get('eps_r', None)
[docs] def presolve(self, mtx): pass
[docs]class NonlinearSolver(Solver): """ Abstract nonlinear solver class. """ def __init__(self, conf, fun=None, fun_grad=None, lin_solver=None, iter_hook=None, status=None, context=None, **kwargs): Solver.__init__(self, conf=conf, fun=fun, fun_grad=fun_grad, lin_solver=lin_solver, iter_hook=iter_hook, status=status, context=context, **kwargs) def __call__(self, state0, conf=None, fun=None, fun_grad=None, lin_solver=None, iter_hook=None, status=None): raise ValueError('called an abstract NonlinearSolver instance!')
[docs]class TimeSteppingSolver(Solver): """ Abstract time stepping solver class. """ def __init__(self, conf, nls=None, status=None, context=None, **kwargs): Solver.__init__(self, conf=conf, nls=nls, status=status, context=context, **kwargs) def __call__(self, vec0=None, nls=None, init_fun=None, prestep_fun=None, poststep_fun=None, status=None, **kwargs): raise ValueError('called an abstract TimeSteppingSolver instance!')
[docs]class OptimizationSolver(Solver): """ Abstract optimization solver class. """ def __init__(self, conf, obj_fun=None, obj_fun_grad=None, status=None, obj_args=None, context=None, **kwargs): Solver.__init__(self, conf=conf, obj_fun=obj_fun, obj_fun_grad=obj_fun_grad, status=status, obj_args=obj_args, context=context, **kwargs) def __call__(self, state0, conf=None, obj_fun=None, obj_fun_grad=None, status=None, obj_args=None): raise ValueError('called an abstract OptimizationSolver instance!')
[docs]class EigenvalueSolver(Solver): """ Abstract eigenvalue solver class. """ def __init__(self, conf, mtx_a=None, mtx_b=None, n_eigs=None, eigenvectors=None, status=None, context=None, **kwargs): Solver.__init__(self, conf=conf, mtx_a=mtx_a, mtx_b=mtx_b, n_eigs=n_eigs, eigenvectors=eigenvectors, status=status, context=context) def __call__(self, mtx_a, mtx_b=None, n_eigs=None, eigenvectors=None, status=None, conf=None): raise ValueError('called an abstract EigenvalueSolver instance!') def _to_array(self, mtx_a, mtx_b=None): if hasattr(mtx_a, 'toarray'): mtx_a = mtx_a.toarray() if mtx_b is not None: if hasattr(mtx_b, 'toarray'): mtx_b = mtx_b.toarray() return mtx_a, mtx_b def _ret_zero(self, mtx, eigenvectors=None): out = nm.zeros(0, dtype=nm.float64) if eigenvectors: out = (out, nm.zeros((mtx.shape[0], 0), dtype=nm.float64)) return out
[docs]class QuadraticEVPSolver(Solver): """ Abstract quadratic eigenvalue problem solver class. """ def __init__(self, conf, mtx_m=None, mtx_d=None, mtx_k=None, n_eigs=None, eigenvectors=None, status=None, context=None, **kwargs): Solver.__init__(self, conf=conf, mtx_m=mtx_m, mtx_d=mtx_d, mtx_k=mtx_k, n_eigs=n_eigs, eigenvectors=eigenvectors, status=status, context=context) solver_conf = structify(conf.solver) self.solver = Solver.any_from_conf(solver_conf) def __call__(self, mtx_m, mtx_d, mtx_k, n_eigs=None, eigenvectors=None, status=None, conf=None): raise ValueError('called an abstract QuadraticEVPSolver instance!')