From c41b9f12e9b61296df2f039033238b1e9884380c Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Fri, 22 Nov 2013 11:58:31 -0800 Subject: [PATCH] Documentation updates to optimize. Updated the hook method. --- SimPEG/inverse/Optimize.py | 88 +++++++++++++++++++++++--------------- SimPEG/utils/__init__.py | 29 ++++++++----- docs/api_Optimize.rst | 3 +- 3 files changed, 73 insertions(+), 47 deletions(-) diff --git a/SimPEG/inverse/Optimize.py b/SimPEG/inverse/Optimize.py index 40ddb310..db708881 100644 --- a/SimPEG/inverse/Optimize.py +++ b/SimPEG/inverse/Optimize.py @@ -6,6 +6,9 @@ import scipy.sparse as sp from SimPEG import Solver +__all__ = ['Minimize', 'Remember', 'SteepestDescent', 'BFGS', 'GaussNewton', 'InexactGaussNewton', 'ProjectedGradient', 'NewtonRoot', 'StoppingCriteria', 'IterationPrinters'] + + class StoppingCriteria(object): """docstring for StoppingCriteria""" @@ -79,29 +82,26 @@ class IterationPrinters(object): class Minimize(object): """ - Minimize is a general class for derivative based optimization. - - """ - name = "General Optimization Algorithm" + name = "General Optimization Algorithm" #: The name of the optimization algorithm - maxIter = 20 - maxIterLS = 10 - maxStep = np.inf - LSreduction = 1e-4 - LSshorten = 0.5 - tolF = 1e-1 - tolX = 1e-1 - tolG = 1e-1 - eps = 1e-5 + maxIter = 20 #: Maximum number of iterations + maxIterLS = 10 #: Maximum number of iterations for the line-search + maxStep = np.inf #: Maximum step possible, used in scaling before the line-search. + LSreduction = 1e-4 #: Expected decrease in the line-search + LSshorten = 0.5 #: Line-search step is shortened by this amount each time. + tolF = 1e-1 #: Tolerance on function value decrease + tolX = 1e-1 #: Tolerance on norm(x) movement + tolG = 1e-1 #: Tolerance on gradient norm + eps = 1e-5 #: Small value - debug = False - debugLS = False + debug = False #: Print debugging information + debugLS = False #: Print debugging information for the line-search - comment = '' - counter = None + comment = '' #: Used by some functions to indicate what is going on in the algorithm + counter = None #: Set this to a SimPEG.utils.Counter() if you want to count things def __init__(self, **kwargs): self.stoppers = [StoppingCriteria.tolerance_f, StoppingCriteria.moving_x, StoppingCriteria.tolerance_g, StoppingCriteria.norm_g, StoppingCriteria.iteration] @@ -114,7 +114,8 @@ class Minimize(object): @timeIt def minimize(self, evalFunction, x0): - """ + """minimize(evalFunction, x0) + Minimizes the function (evalFunction) starting at the location x0. :param def evalFunction: function handle that evaluates: f, g, H = F(x) @@ -231,7 +232,6 @@ class Minimize(object): name = self.name if not inLS else self.nameLS printTitles(self, self.printers if not inLS else self.printersLS, name, pad) - @count def printIter(self, inLS=False): """ **printIter** is called directly after function evaluations. @@ -258,7 +258,6 @@ class Minimize(object): stoppers = self.stoppers if not inLS else self.stoppersLS printStoppers(self, stoppers, pad='', stop=stop, done=done) - @timeIt def stoppingCriteria(self, inLS=False): if self._iter == 0: self.f0 = self.f @@ -267,7 +266,8 @@ class Minimize(object): @timeIt def projection(self, p): - """ + """projection(p) + projects the search direction. by default, no projection is applied. @@ -281,7 +281,8 @@ class Minimize(object): @timeIt def findSearchDirection(self): - """ + """findSearchDirection() + **findSearchDirection** should return an approximation of: .. math:: @@ -311,7 +312,8 @@ class Minimize(object): @count def scaleSearchDirection(self, p): - """ + """scaleSearchDirection(p) + **scaleSearchDirection** should scale the search direction if appropriate. Set the parameter **maxStep** in the minimize object, to scale back the gradient to a maximum size. @@ -325,11 +327,12 @@ class Minimize(object): p = self.maxStep*p/np.abs(p.max()) return p - nameLS = "Armijo linesearch" + nameLS = "Armijo linesearch" #: The line-search name @timeIt def modifySearchDirection(self, p): - """ + """modifySearchDirection(p) + **modifySearchDirection** changes the search direction based on some sort of linesearch or trust-region criteria. By default, an Armijo backtracking linesearch is preformed with the following parameters: @@ -366,7 +369,8 @@ class Minimize(object): @count def modifySearchDirectionBreak(self, p): - """ + """modifySearchDirectionBreak(p) + Code is called if modifySearchDirection fails to find a descent direction. @@ -387,7 +391,8 @@ class Minimize(object): @count def doEndIteration(self, xt): - """ + """doEndIteration(xt) + **doEndIteration** is called at the end of each minimize iteration. By default, function values and x locations are shuffled to store 1 past iteration in memory. @@ -416,7 +421,6 @@ class Minimize(object): if self.debug: self.printDone() - class Remember(object): """ This mixin remembers all the things you tend to forget. @@ -467,7 +471,6 @@ class Remember(object): self._rememberList[param[0]].append( param[1](self) ) - class ProjectedGradient(Minimize, Remember): name = 'Projected Gradient' @@ -504,22 +507,35 @@ class ProjectedGradient(Minimize, Remember): @count def projection(self, x): - """Make sure we are feasible.""" + """projection(x) + + Make sure we are feasible. + + """ return np.median(np.c_[self.lower,x,self.upper],axis=1) @count def activeSet(self, x): - """If we are on a bound""" + """activeSet(x) + + If we are on a bound + + """ return np.logical_or(x == self.lower, x == self.upper) @count def inactiveSet(self, x): - """The free variables.""" + """inactiveSet(x) + + The free variables. + + """ return np.logical_not(self.activeSet(x)) @count def bindingSet(self, x): - """ + """bindingSet(x) + If we are on a bound and the negative gradient points away from the feasible set. Optimality condition. (Satisfies Kuhn-Tucker) MoreToraldo91 @@ -531,6 +547,10 @@ class ProjectedGradient(Minimize, Remember): @timeIt def findSearchDirection(self): + """findSearchDirection() + + Finds the search direction based on either CG or steepest descent. + """ self.aSet_prev = self.activeSet(self.xc) allBoundsAreActive = sum(self.aSet_prev) == self.xc.size @@ -572,6 +592,7 @@ class ProjectedGradient(Minimize, Remember): @timeIt def _doEndIteration_ProjectedGradient(self, xt): + """_doEndIteration_ProjectedGradient(xt)""" aSet = self.activeSet(xt) bSet = self.bindingSet(xt) @@ -600,7 +621,6 @@ class ProjectedGradient(Minimize, Remember): if self.debug: print 'doEndIteration.ProjGrad, stopDoingSD: ', self.stopDoingSD - class BFGS(Minimize, Remember): name = 'BFGS' nbfgs = 10 diff --git a/SimPEG/utils/__init__.py b/SimPEG/utils/__init__.py index 724f6f38..0c1e9ad1 100644 --- a/SimPEG/utils/__init__.py +++ b/SimPEG/utils/__init__.py @@ -15,6 +15,19 @@ import Geophysics import types import time import numpy as np +from functools import wraps + +def hook(obj, method, name=None, overwrite=False, silent=False): + """ + This dynamically binds a method to the instance of the class. + + If name is None, the name of the method is used. + """ + if name is None: name = method.__name__ + if not hasattr(obj,name) or overwrite: + setattr(obj, name, types.MethodType( method, obj )) + elif not silent: + print 'Method '+name+' was not overwritten.' def setKwargs(obj, **kwargs): """Sets key word arguments (kwargs) that are present in the object, throw an error if they don't exist.""" @@ -23,6 +36,9 @@ def setKwargs(obj, **kwargs): setattr(obj, attr, kwargs[attr]) else: raise Exception('%s attr is not recognized' % attr) + hook(obj,callHooks, silent=True) + hook(obj,hook, silent=True) + hook(obj,setKwargs, silent=True) def printTitles(obj, printers, name='Print Titles', pad=''): titles = '' @@ -70,17 +86,6 @@ def callHooks(obj, match, *args, **kwargs): if getattr(obj,'debug',False): print (match+' is calling self.'+method) getattr(obj,method)(*args, **kwargs) -def hook(obj, method, name=None, overwrite=False): - """ - This dynamically binds a method to the instance of the class. - - If name is None, the name of the method is used. - """ - if name is None: name = method.__name__ - if not hasattr(obj,name) or overwrite: - setattr(obj, name, types.MethodType( method, obj )) - else: - print 'Method '+name+' was not overwritten.' class Counter(object): @@ -156,6 +161,7 @@ class Counter(object): print " {0:<40}: {1:4.2e}, {2:4.2e}, {3:4d}x".format(prop,a.mean(),a.sum(),l) def count(f): + @wraps(f) def wrapper(self,*args,**kwargs): counter = getattr(self,'counter',None) if type(counter) is Counter: counter.count(self.__class__.__name__+'.'+f.__name__) @@ -164,6 +170,7 @@ def count(f): return wrapper def timeIt(f): + @wraps(f) def wrapper(self,*args,**kwargs): counter = getattr(self,'counter',None) if type(counter) is Counter: counter.countTic(self.__class__.__name__+'.'+f.__name__) diff --git a/docs/api_Optimize.rst b/docs/api_Optimize.rst index 283d8ce9..8ca74e27 100644 --- a/docs/api_Optimize.rst +++ b/docs/api_Optimize.rst @@ -5,9 +5,9 @@ Optimize .. automodule:: SimPEG.inverse.Optimize :show-inheritance: + :private-members: :members: :undoc-members: - :inherited-members: Inversion @@ -17,7 +17,6 @@ Inversion :show-inheritance: :members: :undoc-members: - :inherited-members: Beta Schedule *************