"""
.. inheritance-diagram:: pyopus.optimizer.hj
:parts: 1
**Box constrained Hooke-Jeeves optimizer (PyOPUS subsystem name: HJOPT)**
An extended version of coordinate search where so-called **speculative steps**
are taken from time to time. These steps are hoped to speed up the search.
The convergence theory applied to coordinate search can also be applied to
Hooke-Jeeves algorithm if the speculative step length factor is an integer.
The algorithm (unconstrained version) was first published in [hj]_.
.. [hj] Hooke R., Jeeves T. A., Direct Search Solution of Numerical and Statistical
Problems. Journal of the ACM (JACM), vol. 8, pp. 212-229, 1961.
"""
from ..misc.debug import DbgMsgOut, DbgMsg
from .coordinate import CoordinateSearch
from numpy import where, isinf, where, abs, max, array
__all__ = [ 'HookeJeeves' ]
[docs]class HookeJeeves(CoordinateSearch):
"""
Hooke-Jeeves optimizer class
*speculative* is the step length factor for the speculative step.
See the :class:`~pyopus.optimizer.base.coordinate.CoordinateSearch`
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, speculative=2.0):
# It has all members of a coordinate search
CoordinateSearch.__init__(self, function, xlo, xhi, debug, fstop, maxiter,
stepup=stepup, stepdn=stepdn, step0=step0, minstep=minstep)
# Speculative step length (factor)
self.speculative=speculative
# Speculative step flag
self.isSpeculative=None
# Trial step origin
self.xt=None
self.ft=None
# Fallback origin
self.xo=None
self.fo=None
[docs] def check(self):
"""
Checks the optimization algorithm's settings and raises an exception if
something is wrong.
"""
CoordinateSearch.check(self)
if (self.step0<=0).any():
raise Exception(DbgMsg("HJOPT", "Initial step must be greater than 0."))
if (self.stepup<1):
raise Exception(DbgMsg("HJOPT", "Step increase must be greater or equal 1."))
if (self.stepdn>=1):
raise Exception(DbgMsg("HJOPT", "Step decrease must be less than 1."))
[docs] def reset(self, x0):
"""
Puts the optimizer in its initial state and sets the initial point to
be the 1-dimensional array or list *x0*. The length of the array
becomes the dimension of the optimization problem (:attr:`ndim`
member). The shape of *x0* must match that of *xlo* and *xhi*.
"""
CoordinateSearch.reset(self, x0)
# Debug message
if self.debug:
DbgMsgOut("HJOPT", "Resetting Hooke-Jeeves.")
self.step=self.step0.copy()
self.isSpeculative=False
self.xt=array(x0)
self.ft=None
self.xo=array(x0)
self.fo=None
# Debug message
if self.debug:
DbgMsgOut("HJOPT", "Done.")
[docs] def run(self):
"""
Runs the optimization algorithm.
"""
# Debug message
if self.debug:
DbgMsgOut("HJOPT", "Starting a Hooke-Jeeves run at i="+str(self.niter))
# Reset stop flag
self.stop=False
# Check and evaluate initial point
self.check()
# Evaluate initial point (if needed)
if self.f is None:
self.f=self.fun(self.x)
if self.ft is None:
self.ft=self.f
if self.fo is None:
self.fo=self.f
# Prepare
x=self.x.copy()
f=self.f.copy()
xt=self.xt
ft=self.ft
xo=self.xo
fo=self.fo
step=self.step
isSpeculative=self.isSpeculative
# Main loop
while not self.stop:
# Speculative step
if isSpeculative:
xspec=(xt-xo)*self.speculative
self.bound(xspec)
xo=xt
fo=ft
xt=xspec
if self.debug:
DbgMsgOut("HJOPT", "Iteration i="+str(self.niter)+": speculative step")
# Trial steps
i=0
while i<self.ndim:
# Origin of trial steps in one dimension
xtmp=xt.copy()
ftmp=ft.copy()
if step.size==1:
delta=step
else:
delta=step[i]
# +step
xnew=xtmp.copy()
xnew[i]=xtmp[i]+delta
self.bound(xnew)
fnew=self.fun(xnew)
# Debug message
if self.debug:
DbgMsgOut("HJOPT", "Iteration i="+str(self.niter)+": f="+str(ft)+" step="+str(max(abs(step))))
if fnew<ft:
xt=xnew
ft=fnew
if self.stop:
break;
# -step
xnew=xtmp.copy()
xnew[i]=xtmp[i]-delta
self.bound(xnew)
fnew=self.fun(xnew)
# Debug message
if self.debug:
DbgMsgOut("HJOPT", "Iteration i="+str(self.niter)+": f="+str(ft)+" step="+str(max(abs(step))))
if fnew<ft:
xt=xnew
ft=fnew
if self.stop:
break;
# Speculative step failed
if isSpeculative and not (ft<fo):
break
# Next dimension
i+=1
if self.stop:
break;
# Check if we need to change the step
if isSpeculative:
# Speculative step
if ft>=fo:
# Failed
# Return to previous origin, next one is ordinary
isSpeculative=False
xt=xo
ft=fo
if self.debug:
DbgMsgOut("HJOPT", "Iteration i="+str(self.niter)+": speculative step FAILED")
else:
# OK, increase step
step*=self.stepup
None
else:
# Ordinary step
if ft>=fo:
# Failed, reduce step
step*=self.stepdn
else:
# OK, increase step, next one is speculative
isSpeculative=True
step*=self.stepup
# Check step size
if self.minstep is not None and (step<self.minstep).all():
if self.debug:
DbgMsgOut("HJOPT", "Iteration i="+str(self.niter)+": step small enough, stopping")
break;
if self.debug:
DbgMsgOut("HJOPT", "Hooke-Jeeves run finished.")
self.step=step