Source code for sfepy.solvers.nls

import time

import numpy as nm
import numpy.linalg as nla

from sfepy.base.base import output, get_default, pause, debug, Struct
from sfepy.base.log import Log, get_logging_conf
from sfepy.solvers.solvers import make_get_conf, NonlinearSolver

[docs]def check_tangent_matrix( conf, vec_x0, fun, fun_grad ): """Verify the correctness of the tangent matrix as computed by fun_grad() by comparing it with its finite difference approximation evaluated by repeatedly calling fun() with vec_x items perturbed by a small delta.""" vec_x = vec_x0.copy() delta = conf.delta vec_r = fun( vec_x ) # Update state. mtx_a0 = fun_grad( vec_x ) mtx_a = mtx_a0.tocsc() mtx_d = mtx_a.copy() mtx_d.data[:] = 0.0 vec_dx = nm.zeros_like( vec_r ) for ic in range( vec_dx.shape[0] ): vec_dx[ic] = delta xx = vec_x.copy() - vec_dx vec_r1 = fun( xx ) vec_dx[ic] = -delta xx = vec_x.copy() - vec_dx vec_r2 = fun( xx ) vec_dx[ic] = 0.0; vec = 0.5 * (vec_r2 - vec_r1) / delta ## ir = mtx_a.indices[mtx_a.indptr[ic]:mtx_a.indptr[ic+1]] ## for ii in ir: ## mtx_d[ii,ic] = vec[ii] ir = mtx_a.indices[mtx_a.indptr[ic]:mtx_a.indptr[ic+1]] mtx_d.data[mtx_a.indptr[ic]:mtx_a.indptr[ic+1]] = vec[ir] vec_r = fun( vec_x ) # Restore. tt = time.clock() print mtx_a, '.. analytical' print mtx_d, '.. difference' import sfepy.base.plotutils as plu plu.plot_matrix_diff( mtx_d, mtx_a, delta, ['difference', 'analytical'], conf.check ) return time.clock() - tt ## # c: 02.12.2005, r: 02.04.2008
[docs]def conv_test( conf, it, err, err0 ): status = -1 if (abs( err0 ) < conf.macheps): err_r = 0.0 else: err_r = err / err0 output( 'nls: iter: %d, residual: %e (rel: %e)' % (it, err, err_r) ) if it > 0: if (err < conf.eps_a) and (err_r < conf.eps_r): status = 0 else: if err < conf.eps_a: status = 0 if (status == -1) and (it >= conf.i_max): status = 1 return status
[docs]class Newton(NonlinearSolver): r""" Solves a nonlinear system :math:`f(x) = 0` using the Newton method with backtracking line-search, starting with an initial guess :math:`x^0`. For common configuration parameters, see :class:`Solver <sfepy.solvers.solvers.Solver>`. Parameters ---------- i_max : int The maximum number of iterations. eps_a : float The absolute tolerance for the residual, i.e. :math:`||f(x^i)||`. eps_r : float The relative tolerance for the residual, i.e. :math:`||f(x^i)|| / ||f(x^0)||`. macheps : float The float considered to be machine "zero". lin_red : float The linear system solution error should be smaller than (`eps_a` * `lin_red`), otherwise a warning is printed. lin_precision : float or None If not None, the linear system solution tolerances are set in each nonlinear iteration relative to the current residual norm by the `lin_precision` factor. Ignored for direct linear solvers. ls_on : float Start the backtracking line-search by reducing the step, if :math:`||f(x^i)|| / ||f(x^{i-1})||` is larger than `ls_on`. ls_red : 0.0 < float < 1.0 The step reduction factor in case of correct residual assembling. ls_red_warp : 0.0 < float < 1.0 The step reduction factor in case of failed residual assembling (e.g. the "warp violation" error caused by a negative volume element resulting from too large deformations). ls_min : 0.0 < float < 1.0 The minimum step reduction factor. give_up_warp : bool If True, abort on the "warp violation" error. check : 0, 1 or 2 If >= 1, check the tangent matrix using finite differences. If 2, plot the resulting sparsity patterns. delta : float If `check >= 1`, the finite difference matrix is taken as :math:`A_{ij} = \frac{f_i(x_j + \delta) - f_i(x_j - \delta)}{2 \delta}`. is_plot : False If True, plot the solution and residual in each step. log : dict or None If not None, log the convergence according to the configuration in the following form:: {'text' : 'log.txt', 'plot' : 'log.pdf'} Each of the dict items can be None. problem : 'nonlinear' or 'linear' Specifies if the problem is linear or non-linear. """ name = 'nls.newton' @staticmethod
[docs] def process_conf(conf, kwargs): """ Missing items are set to default values for a linear problem. Example configuration, all items:: solver_1 = { 'name' : 'newton', 'kind' : 'nls.newton', 'i_max' : 2, 'eps_a' : 1e-8, 'eps_r' : 1e-2, 'macheps' : 1e-16, 'lin_red' : 1e-2, # Linear system error < (eps_a * lin_red). 'lin_precision' : None, 'ls_on' : 0.99999, 'ls_red' : 0.1, 'ls_red_warp' : 0.001, 'ls_min' : 1e-5, 'give_up_warp' : False, 'check' : 0, 'delta' : 1e-6, 'is_plot' : False, 'log' : None, # 'nonlinear' or 'linear' (ignore i_max) 'problem' : 'nonlinear', } """ get = make_get_conf(conf, kwargs) common = NonlinearSolver.process_conf(conf) log = get_logging_conf(conf) log = Struct(name='log_conf', **log) is_any_log = (log.text is not None) or (log.plot is not None) return Struct(i_max=get('i_max', 1), eps_a=get('eps_a', 1e-10), eps_r=get('eps_r', 1.0), macheps=get('macheps', nm.finfo(nm.float64).eps), lin_red=get('lin_red', 1.0), lin_precision=get('lin_precision', None), ls_red=get('ls_red', 0.1), ls_red_warp=get('ls_red_warp', 0.001), ls_on=get('ls_on', 0.99999), ls_min=get('ls_min', 1e-5), give_up_warp=get('give_up_warp', False), check=get('check', 0), delta=get('delta', 1e-6), is_plot=get('is_plot', False), problem=get('problem', 'nonlinear'), log=log, is_any_log=is_any_log) + common
[docs] def __init__(self, conf, **kwargs): NonlinearSolver.__init__( self, conf, **kwargs ) conf = self.conf if conf.is_any_log: self.log = Log([[r'$||r||$'], ['iteration']], xlabels=['', 'all iterations'], ylabels=[r'$||r||$', 'iteration'], yscales=['log', 'linear'], is_plot=conf.log.plot is not None, log_filename=conf.log.text, formats=[['%.8e'], ['%d']]) else: self.log = None
[docs] def __call__(self, vec_x0, conf=None, fun=None, fun_grad=None, lin_solver=None, iter_hook=None, status=None): """ Nonlinear system solver call. Solves a nonlinear system :math:`f(x) = 0` using the Newton method with backtracking line-search, starting with an initial guess :math:`x^0`. Parameters ---------- vec_x0 : array The initial guess vector :math:`x_0`. conf : Struct instance, optional The solver configuration parameters, fun : function, optional The function :math:`f(x)` whose zero is sought - the residual. fun_grad : function, optional The gradient of :math:`f(x)` - the tangent matrix. lin_solver : LinearSolver instance, optional The linear solver for each nonlinear iteration. iter_hook : function, optional User-supplied function to call before each iteration. status : dict-like, optional The user-supplied object to hold convergence statistics. Notes ----- * The optional parameters except `iter_hook` and `status` need to be given either here or upon `Newton` construction. * Setting `conf.problem == 'linear'` means a pre-assembled and possibly pre-solved matrix. This is mostly useful for linear time-dependent problems. """ import sfepy.base.plotutils as plu conf = get_default( conf, self.conf ) fun = get_default( fun, self.fun ) fun_grad = get_default( fun_grad, self.fun_grad ) lin_solver = get_default( lin_solver, self.lin_solver ) iter_hook = get_default(iter_hook, self.iter_hook) status = get_default( status, self.status ) ls_eps_a, ls_eps_r = lin_solver.get_tolerance() eps_a = get_default(ls_eps_a, 1.0) eps_r = get_default(ls_eps_r, 1.0) lin_red = conf.eps_a * conf.lin_red time_stats = {} vec_x = vec_x0.copy() vec_x_last = vec_x0.copy() vec_dx = None if self.log is not None: self.log.plot_vlines(color='r', linewidth=1.0) err = err0 = -1.0 err_last = -1.0 it = 0 while 1: if iter_hook is not None: iter_hook(self, vec_x, it, err, err0) ls = 1.0 vec_dx0 = vec_dx; while 1: tt = time.clock() try: vec_r = fun( vec_x ) except ValueError: if (it == 0) or (ls < conf.ls_min): output('giving up!') raise else: ok = False else: ok = True time_stats['rezidual'] = time.clock() - tt if ok: try: err = nla.norm( vec_r ) except: output( 'infs or nans in the residual:', vec_r ) output( nm.isfinite( vec_r ).all() ) debug() if self.log is not None: self.log(err, it) if it == 0: err0 = err; break if err < (err_last * conf.ls_on): break red = conf.ls_red; output( 'linesearch: iter %d, (%.5e < %.5e) (new ls: %e)'\ % (it, err, err_last * conf.ls_on, red * ls) ) else: # Failure. if conf.give_up_warp: output('giving up!') break red = conf.ls_red_warp; output( 'rezidual computation failed for iter %d' ' (new ls: %e)!' % (it, red * ls) ) if ls < conf.ls_min: output( 'linesearch failed, continuing anyway' ) break ls *= red; vec_dx = ls * vec_dx0; vec_x = vec_x_last.copy() - vec_dx # End residual loop. if self.log is not None: self.log.plot_vlines([1], color='g', linewidth=0.5) err_last = err; vec_x_last = vec_x.copy() condition = conv_test( conf, it, err, err0 ) if condition >= 0: break if (not ok) and conf.give_up_warp: condition = 2 break tt = time.clock() if conf.problem == 'nonlinear': mtx_a = fun_grad(vec_x) else: mtx_a = fun_grad( 'linear' ) time_stats['matrix'] = time.clock() - tt if conf.check: tt = time.clock() wt = check_tangent_matrix( conf, vec_x, fun, fun_grad ) time_stats['check'] = time.clock() - tt - wt if conf.lin_precision is not None: if ls_eps_a is not None: eps_a = max(err * conf.lin_precision, ls_eps_a) elif ls_eps_r is not None: eps_r = max(conf.lin_precision, ls_eps_r) lin_red = max(eps_a, err * eps_r) if conf.verbose: output('solving linear system...') tt = time.clock() vec_dx = lin_solver(vec_r, x0=vec_x, eps_a=eps_a, eps_r=eps_r, mtx=mtx_a) time_stats['solve'] = time.clock() - tt if conf.verbose: output('...done') for kv in time_stats.iteritems(): output( '%10s: %7.2f [s]' % kv ) vec_e = mtx_a * vec_dx - vec_r lerr = nla.norm( vec_e ) if lerr > lin_red: output('linear system not solved! (err = %e < %e)' % (lerr, lin_red)) vec_x -= vec_dx if conf.is_plot: plu.plt.ion() plu.plt.gcf().clear() plu.plt.subplot( 2, 2, 1 ) plu.plt.plot( vec_x_last ) plu.plt.ylabel( r'$x_{i-1}$' ) plu.plt.subplot( 2, 2, 2 ) plu.plt.plot( vec_r ) plu.plt.ylabel( r'$r$' ) plu.plt.subplot( 2, 2, 4 ) plu.plt.plot( vec_dx ) plu.plt.ylabel( r'$\_delta x$' ) plu.plt.subplot( 2, 2, 3 ) plu.plt.plot( vec_x ) plu.plt.ylabel( r'$x_i$' ) plu.plt.draw() plu.plt.ioff() pause() it += 1 if status is not None: status['time_stats'] = time_stats status['err0'] = err0 status['err'] = err status['n_iter'] = it status['condition'] = condition if conf.log.plot is not None: if self.log is not None: self.log(save_figure=conf.log.plot) return vec_x
[docs]class ScipyBroyden( NonlinearSolver ): """Interface to Broyden and Anderson solvers from scipy.optimize.""" name = 'nls.scipy_broyden_like' @staticmethod
[docs] def process_conf(conf, kwargs): """ Missing items are left to scipy defaults. Unused options are ignored. Example configuration, all items:: solver_1 = { 'name' : 'broyden', 'kind' : 'nls.scipy_broyden_like', 'method' : 'broyden3', 'i_max' : 10, 'alpha' : 0.9, 'M' : 5, 'w0' : 0.1, 'f_tol' : 6e-6, 'verbose' : True, } """ get = make_get_conf(conf, kwargs) common = NonlinearSolver.process_conf(conf) return Struct(method=get('method', 'broyden3'), i_max=get('i_max', 10), alpha=get('alpha', 0.9), M=get('M', 5), f_tol=get('f_tol', 6e-6), w0=get('w0', 0.1), verbose=get('verbose', False)) + common
[docs] def __init__( self, conf, **kwargs ): NonlinearSolver.__init__( self, conf, **kwargs ) self.set_method( self.conf )
[docs] def set_method( self, conf ): import scipy.optimize as so try: solver = getattr( so, conf.method ) except AttributeError: output( 'scipy solver %s does not exist!' % conf.method ) output( 'using broyden3 instead' ) solver = so.broyden3 self.solver = solver
[docs] def __call__(self, vec_x0, conf=None, fun=None, fun_grad=None, lin_solver=None, iter_hook=None, status=None): if conf is not None: self.set_method( conf ) else: conf = self.conf fun = get_default( fun, self.fun ) status = get_default( status, self.status ) tt = time.clock() kwargs = {'iter' : conf.i_max, 'alpha' : conf.alpha, 'verbose' : conf.verbose} if conf.method == 'broyden_generalized': kwargs.update( {'M' : conf.M} ) elif conf.method in ['anderson', 'anderson2']: kwargs.update( {'M' : conf.M, 'w0' : conf.w0} ) if conf.method in ['anderson', 'anderson2', 'broyden', 'broyden2' , 'newton_krylov']: kwargs.update( {'f_tol' : conf.f_tol } ) vec_x = self.solver( fun, vec_x0, **kwargs ) vec_x = nm.asarray(vec_x) if status is not None: status['time_stats'] = time.clock() - tt return vec_x