Source code for sfepy.solvers.optimize

import time

import numpy as nm
import numpy.linalg as nla

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

import scipy.optimize as sopt
import scipy.optimize.linesearch as linesearch

##
# 19.04.2006, c
# 26.04.2006
# 28.04.2006
[docs]def conv_test( conf, it, of, of0, ofg_norm = None ): """ Returns ------- flag : int * -1 ... continue * 0 ... small OF -> stop * 1 ... i_max reached -> stop * 2 ... small OFG -> stop * 3 ... small relative decrase of OF """ status = -1 output( 'opt: iter: %d, of: %e (||ofg||: %e)' % (it, of, ofg_norm) ) # print (of0 - of), (conf.eps_rd * of0) if (abs( of ) < conf.eps_of): status = 0 elif ofg_norm and (ofg_norm < conf.eps_ofg): status = 2 elif (it > 0) and (abs(of0 - of) < (conf.eps_rd * abs( of0 ))): status = 3 if (status == -1) and (it >= conf.i_max): status = 1 return status ## # 19.04.2006, from scipy.optimize # 21.04.2006 # 27.03.2007
[docs]def wrap_function( function, args ): ncalls = [0] times = [] def function_wrapper( x ): ncalls[0] += 1 tt = time.time() out = function( x, *args ) tt2 = time.time() if tt2 < tt: raise RuntimeError, '%f >= %f' % (tt, tt2) times.append( tt2 - tt ) return out return ncalls, times, function_wrapper ## # 20.04.2006, c
[docs]def check_gradient( xit, aofg, fn_of, delta, check ): dofg = nm.zeros_like( aofg ) xd = xit.copy() for ii in xrange( xit.shape[0] ): xd[ii] = xit[ii] + delta ofp = fn_of( xd ) xd[ii] = xit[ii] - delta ofm = fn_of( xd ) xd[ii] = xit[ii] dofg[ii] = 0.5 * (ofp - ofm) / delta output( '**********', ii, aofg[ii], dofg[ii] ) diff = abs( aofg - dofg ) aux = nm.concatenate( (aofg[:,nm.newaxis], dofg[:,nm.newaxis], diff[:,nm.newaxis]), 1 ) output( aux ) output( nla.norm( diff, nm.Inf ) ) aofg.tofile( 'aofg.txt', ' ' ) dofg.tofile( 'dofg.txt', ' ' ) diff.tofile( 'diff.txt', ' ' ) if check == 2: import pylab pylab.plot( aofg ) pylab.plot( dofg ) pylab.legend( ('analytical', 'finite difference') ) pylab.show() pause( 'gradient checking done' ) ## # 17.10.2007, c
[docs]class FMinSteepestDescent( OptimizationSolver ): name = 'opt.fmin_sd' @staticmethod
[docs] def process_conf(conf, kwargs): """ Missing items are set to default values. Example configuration, all items:: solver_0 = { 'name' : 'fmin_sd', 'kind' : 'opt.fmin_sd', 'i_max' : 10, 'eps_rd' : 1e-5, # Relative delta of objective function 'eps_of' : 1e-4, 'eps_ofg' : 1e-8, 'norm' : nm.Inf, 'ls' : True, # Linesearch. 'ls_method' : 'backtracking', # 'backtracking' or 'full' 'ls0' : 0.25, 'ls_red' : 0.5, 'ls_red_warp' : 0.1, 'ls_on' : 0.99999, 'ls_min' : 1e-5, 'check' : 0, 'delta' : 1e-6, 'output' : None, # 'itc' 'log' : {'text' : 'output/log.txt', 'plot' : 'output/log.png'}, 'yscales' : ['linear', 'log', 'log', 'linear'], } """ get = make_get_conf(conf, kwargs) common = OptimizationSolver.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', 10), eps_rd=get('eps_rd', 1e-5), eps_of=get('eps_of', 1e-4), eps_ofg=get('eps_ofg', 1e-8), norm=get('norm', nm.Inf), ls=get('ls', True), ls_method=get('ls_method', 'backtracking'), ls0=get('ls0', 0.25), ls_red=get('ls_red', 0.5), ls_red_warp=get('ls_red_warp', 0.1), ls_on=get('ls_on', 0.99999), ls_min=get('ls_min', 1e-5), check=get('check', 0), delta=get('delta', 1e-6), output=get('output', None), yscales=get('yscales', ['linear', 'log', 'log', 'linear']), log=log, is_any_log=is_any_log) + common ## # 17.10.2007, c
def __init__( self, conf, **kwargs ): OptimizationSolver.__init__( self, conf, **kwargs ) conf = self.conf if conf.is_any_log: self.log = Log([[r'$||\Psi||$'], [r'$||\nabla \Psi||$'], [r'$\alpha$'], ['iteration']], xlabels=['', '', 'all iterations', 'all iterations'], yscales=conf.yscales, is_plot=conf.log.plot is not None, log_filename=conf.log.text, formats=[['%.8e'], ['%.3e'], ['%.3e'], ['%d']]) else: self.log = None ## # 19.04.2006, c # 20.04.2006 # 21.04.2006 # 26.04.2006 # 06.06.2006 # 07.06.2006 # 04.09.2006 # 21.03.2007 # 17.10.2007, from fmin_sd() def __call__( self, x0, conf = None, obj_fun = None, obj_fun_grad = None, status = None, obj_args = None ): # def fmin_sd( conf, x0, fn_of, fn_ofg, args = () ): conf = get_default( conf, self.conf ) obj_fun = get_default( obj_fun, self.obj_fun ) obj_fun_grad = get_default( obj_fun_grad, self.obj_fun_grad ) status = get_default( status, self.status ) obj_args = get_default( obj_args, self.obj_args ) if conf.output: globals()['output'] = conf.output output( 'entering optimization loop...' ) nc_of, tt_of, fn_of = wrap_function( obj_fun, obj_args ) nc_ofg, tt_ofg, fn_ofg = wrap_function( obj_fun_grad, obj_args ) time_stats = {'of' : tt_of, 'ofg': tt_ofg, 'check' : []} ofg = None it = 0 xit = x0.copy() while 1: of = fn_of( xit ) if it == 0: of0 = ofit0 = of_prev = of of_prev_prev = of + 5000.0 if ofg is None: ofg = fn_ofg( xit ) if conf.check: tt = time.clock() check_gradient( xit, ofg, fn_of, conf.delta, conf.check ) time_stats['check'].append( time.clock() - tt ) ofg_norm = nla.norm( ofg, conf.norm ) ret = conv_test( conf, it, of, ofit0, ofg_norm ) if ret >= 0: break ofit0 = of ## # Backtrack (on errors). alpha = conf.ls0 can_ls = True while 1: xit2 = xit - alpha * ofg aux = fn_of( xit2 ) if self.log is not None: self.log(of, ofg_norm, alpha, it) if aux is None: alpha *= conf.ls_red_warp can_ls = False output( 'warp: reducing step (%f)' % alpha ) elif conf.ls and conf.ls_method == 'backtracking': if aux < of * conf.ls_on: break alpha *= conf.ls_red output( 'backtracking: reducing step (%f)' % alpha ) else: of_prev_prev = of_prev of_prev = aux break if alpha < conf.ls_min: if aux is None: raise RuntimeError, 'giving up...' output( 'linesearch failed, continuing anyway' ) break # These values are modified by the line search, even if it fails of_prev_bak = of_prev of_prev_prev_bak = of_prev_prev if conf.ls and can_ls and conf.ls_method == 'full': output( 'full linesearch...' ) alpha, fc, gc, of_prev, of_prev_prev, ofg1 = \ linesearch.line_search(fn_of,fn_ofg,xit, -ofg,ofg,of_prev,of_prev_prev, c2=0.4) if alpha is None: # line search failed -- use different one. alpha, fc, gc, of_prev, of_prev_prev, ofg1 = \ sopt.line_search(fn_of,fn_ofg,xit, -ofg,ofg,of_prev_bak, of_prev_prev_bak) if alpha is None or alpha == 0: # This line search also failed to find a better solution. ret = 3 break output( ' -> alpha: %.8e' % alpha ) else: if conf.ls_method == 'full': output( 'full linesearch off (%s and %s)' % (conf.ls, can_ls) ) ofg1 = None if self.log is not None: self.log.plot_vlines(color='g', linewidth=0.5) xit = xit - alpha * ofg if ofg1 is None: ofg = None else: ofg = ofg1.copy() for key, val in time_stats.iteritems(): if len( val ): output( '%10s: %7.2f [s]' % (key, val[-1]) ) it = it + 1 output( 'status: %d' % ret ) output( 'initial value: %.8e' % of0 ) output( 'current value: %.8e' % of ) output( 'iterations: %d' % it ) output( 'function evaluations: %d in %.2f [s]' \ % (nc_of[0], nm.sum( time_stats['of'] ) ) ) output( 'gradient evaluations: %d in %.2f [s]' \ % (nc_ofg[0], nm.sum( time_stats['ofg'] ) ) ) if self.log is not None: self.log(of, ofg_norm, alpha, it) if conf.log.plot is not None: self.log(save_figure=conf.log.plot, finished=True) else: self.log(finished=True) if status is not None: status['log'] = self.log status['status'] = status status['of0'] = of0 status['of'] = of status['it'] = it status['nc_of'] = nc_of[0] status['nc_ofg'] = nc_ofg[0] status['time_stats'] = time_stats return xit
[docs]class ScipyFMinSolver(OptimizationSolver): """ Interface to SciPy optimization solvers scipy.optimize.fmin_*. """ name = 'nls.scipy_fmin_like' _i_max_name = { 'fmin' : 'maxiter', 'fmin_bfgs' : 'maxiter', 'fmin_cg' : 'maxiter', 'fmin_cobyla' : 'maxfun', 'fmin_l_bfgs_b' : 'maxfun', 'fmin_ncg' : 'maxiter', 'fmin_powell' : 'maxiter', 'fmin_slsqp' : 'iter', 'fmin_tnc' : 'maxfun', } _omit = ('name', 'kind', 'method', 'i_max', 'verbose') _has_grad = ('fmin_bfgs', 'fmin_cg', 'fmin_l_bfgs_b', 'fmin_ncg', 'fmin_slsqp', 'fmin_tnc') @staticmethod
[docs] def process_conf(conf, kwargs): """ Missing items are left to SciPy defaults. Unused options are ignored. Besides 'i_max', use option names according to scipy.optimize function arguments. The 'i_max' translates either to 'maxiter' or 'maxfun' as available. Example configuration:: solver_1 = { 'name' : 'fmin', 'kind' : 'nls.scipy_fmin_like', 'method' : 'bfgs', 'i_max' : 10, 'verbose' : True, 'gtol' : 1e-7 } """ get = make_get_conf(conf, kwargs) common = OptimizationSolver.process_conf(conf) opts = Struct(method=get('method', 'fmin'), i_max=get('i_max', 10), verbose=get('verbose', False)) + common other = {} keys = opts.to_dict().keys() for key, val in conf.to_dict().iteritems(): if key not in keys: other[key] = val return opts + Struct(**other)
def __init__(self, conf, **kwargs): OptimizationSolver.__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: raise ValueError('scipy solver %s does not exist!' % conf.method) self.solver = solver
def __call__(self, x0, conf=None, obj_fun=None, obj_fun_grad=None, status=None, obj_args=None): import inspect if conf is not None: self.set_method(conf) else: conf = self.conf obj_fun = get_default(obj_fun, self.obj_fun) obj_fun_grad = get_default(obj_fun_grad, self.obj_fun_grad) status = get_default(status, self.status) obj_args = get_default(obj_args, self.obj_args) tt = time.clock() kwargs = {self._i_max_name[conf.method] : conf.i_max, 'args' : obj_args} if conf.method in self._has_grad: kwargs['fprime'] = obj_fun_grad if 'disp' in inspect.getargspec(self.solver)[0]: kwargs['disp'] = conf.verbose for key, val in conf.to_dict().iteritems(): if key not in self._omit: kwargs[key] = val out = self.solver(obj_fun, x0, **kwargs) if status is not None: status['time_stats'] = time.clock() - tt return out