Source code for pyopus.optimizer.coordinate

"""
.. inheritance-diagram:: pyopus.optimizer.coordinate
    :parts: 1

**Box constrained coordinate search optimizer (PyOPUS subsystem name: CSOPT)**

The algorithm (asymptotically) converges to a point where the projection of the 
cost function gradient is normal to the active constraint (i.e. stationary 
point in the sense of constrained optimization) if the following holds

* the cost function is continuously differentiable, 
* the step size is a rational multiple of :math:`n_s` 
  (see :class:`~pyopus.optimizer.base.BoxConstrainedOptimizer`), 
* the step scaling factors *stepup* and *stepdn* are rational. 

If the above conditions hold the algorithm can be classified as a pattern 
search algorithm. For a proof see

Lewis R. M., Torczon V.: Pattern search algorithms for bound constrained 
minimization. SIAM Journal on Optimization, vol. 9, pp. 1082-1099, 1999. 
"""

from ..misc.debug import DbgMsgOut, DbgMsg
from .base import BoxConstrainedOptimizer
from numpy import max, abs, array

__all__ = [ 'CoordinateSearch' ] 


[docs]class CoordinateSearch(BoxConstrainedOptimizer): """ Coordinate search optimizer class *stepup* is the factor by which the step size is increased after a successfull step. *stepdn* is the factor by which the step size is decreased after steps in all directions fail. *step0* is the initial vector of *ndim* step sizes corresponding to coordinate directions. It can also be a scalar in which case it is applied to all directions. *minstep* is a vector of step sizes used in the stopping condition. After all step sizes fall below the corresponding values in *minstep* the algorithm is stopped. *minstep* can also be a scalar in which case it applies to step sizes in all directions. If *step0* and *minstep* are not given they are set to :math:`n_s / 10` and :math:`n_s / 1000` when the :meth:`reset` method is called. Box constraints are handled by setting the components of a vector that violate a bound to the bound. Infinite bounds are allowed. This means that the algorithm behaves as an unconstrained algorithm if lower bounds are :math:`-\\infty` and upper bounds are :math:`+\\infty`. See the :class:`~pyopus.optimizer.base.BoxConstrainedOptimizer` class for more information. """ def __init__(self, function, xlo=None, xhi=None, debug=0, fstop=None, maxiter=None, stepup=1.0, stepdn=0.5, step0=None, minstep=None): BoxConstrainedOptimizer.__init__(self, function, xlo, xhi, debug, fstop, maxiter) # Initial step self.step0=step0 if self.step0 is not None: self.step0=array(self.step0) # Stopping condition (step size) self.minstep=minstep if self.minstep is not None: self.minstep=array(self.minstep) # Step size control self.stepup=array(stepup) self.stepdn=array(stepdn)
[docs] def check(self): """ Checks the optimization algorithm's settings and raises an exception if something is wrong. """ BoxConstrainedOptimizer.check(self) if self.step0 is not None and (self.step0<=0).any(): raise Exception(DbgMsg("CSOPT", "Initial step must be greater than 0.")) if (self.stepup<1): raise Exception(DbgMsg("CSOPT", "Step increase must be greater or equal 1.")) if (self.stepdn>=1) or (self.stepdn<=0): raise Exception(DbgMsg("CSOPT", "Step decrease must be between 0 and 1."))
[docs] def reset(self, x0): """ Puts the optimizer in its initial state and sets the initial point to be the 1-dimensional array *x0*. The length of the array becomes the dimension of the optimization problem (:attr:`ndim` member). The shape of *x* must match that of *xlo* and *xhi*. """ BoxConstrainedOptimizer.reset(self, x0) # Debug message if self.debug: DbgMsgOut("CSOPT", "Resetting coordinate search.") if self.step0 is None: self.step0=self.normScale/10.0 if self.minstep is None: self.minstep=self.normScale/1000.0 self.step=self.step0
[docs] def run(self): """ Runs the optimization algorithm. """ # Debug message if self.debug: DbgMsgOut("CSOPT", "Starting a coordinate search run at i="+str(self.niter)) # Reset stop flag self.stop=False # Check self.check() # Evaluate initial point (if needed) if self.f is None: self.f=self.fun(self.x) # Retrieve best-yet point x=self.x.copy() f=self.f.copy() # Retrieve step step=self.step # Main loop while not self.stop: if self.minstep is not None and (step<self.minstep).all(): if self.debug: DbgMsgOut("CSOPT", "Iteration i="+str(self.niter)+": step small enough, stopping") break; # Trial steps i=0 while i<self.ndim: if step.size==1: delta=step else: delta=step[i] xnew=x.copy() xnew[i]=x[i]+delta self.bound(xnew) fnew=self.fun(xnew) # Debug message if self.debug: DbgMsgOut("CSOPT", "Iteration i="+str(self.niter)+": di="+str(i+1)+" f="+str(f)+" step="+str(max(abs(step)))) if fnew<f: break xnew[i]=x[i]-delta self.bound(xnew) fnew=self.fun(xnew) # Debug message if self.debug: DbgMsgOut("CSOPT", "Iteration i="+str(self.niter)+": di="+str(-i-1)+" f="+str(f)+" step="+str(max(abs(step)))) if fnew<f: break i+=1 if fnew<f: x=xnew f=fnew if step.size==1: step=step*self.stepup else: step[i]=step[i]*self.stepup else: step=step*self.stepdn if self.debug: DbgMsgOut("CSOPT", "Coordinate search finished.") self.step=step