From cce626f954691f96a413290fe923385c03a69e6a Mon Sep 17 00:00:00 2001 From: Denis Laxalde Date: Mon, 4 Jun 2012 10:10:27 -0400 Subject: [PATCH 1/3] ENH: use memoization in MINPACK routines --- scipy/optimize/minpack.py | 6 +++++- scipy/optimize/optimize.py | 26 ++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/scipy/optimize/minpack.py b/scipy/optimize/minpack.py index a30789294581..7e987c31bd0f 100644 --- a/scipy/optimize/minpack.py +++ b/scipy/optimize/minpack.py @@ -4,7 +4,7 @@ from numpy import atleast_1d, dot, take, triu, shape, eye, \ transpose, zeros, product, greater, array, \ all, where, isscalar, asarray, inf, abs -from optimize import Result +from optimize import Result, MemoizeFun error = _minpack.error @@ -183,6 +183,7 @@ def _root_hybr(func, x0, args=(), jac=None, options=None): diag = options.get('diag', None) full_output = True + func = MemoizeFun(func, first_only=True) x0 = array(x0, ndmin=1) n = len(x0) if type(args) != type(()): @@ -199,6 +200,7 @@ def _root_hybr(func, x0, args=(), jac=None, options=None): retval = _minpack._hybrd(func, x0, args, full_output, xtol, maxfev, ml, mu, epsfcn, factor, diag) else: + Dfun = MemoizeFun(Dfun, first_only=True) _check_func('fsolve', 'fprime', Dfun, x0, args, n, (n,n)) if (maxfev == 0): maxfev = 100*(n + 1) @@ -348,6 +350,7 @@ def leastsq(func, x0, args=(), Dfun=None, full_output=0, params """ + func = MemoizeFun(func, first_only=True) x0 = array(x0, ndmin=1) n = len(x0) if type(args) != type(()): @@ -361,6 +364,7 @@ def leastsq(func, x0, args=(), Dfun=None, full_output=0, retval = _minpack._lmdif(func, x0, args, full_output, ftol, xtol, gtol, maxfev, epsfcn, factor, diag) else: + Dfun = MemoizeFun(Dfun, first_only=True) if col_deriv: _check_func('leastsq', 'Dfun', Dfun, x0, args, n, (n,m)) else: diff --git a/scipy/optimize/optimize.py b/scipy/optimize/optimize.py index 59e0800d54ec..e6fb44ffa2ec 100644 --- a/scipy/optimize/optimize.py +++ b/scipy/optimize/optimize.py @@ -39,6 +39,32 @@ 'pr_loss': 'Desired error not necessarily achieved due ' 'to precision loss.'} + +class MemoizeFun(object): + """ Decorator that caches the value of the objective function each time + it is called or only the first time if `first_only==True`.""" + def __init__(self, fun, first_only=False): + self.fun = fun + self.calls = 0 + self.first_only = first_only + + def __call__(self, x, *args): + if self.calls == 0: + self.x = numpy.asarray(x).copy() + self.f = self.fun(x, *args) + self.calls += 1 + return self.f + elif self.first_only and self.calls > 1: + self.calls += 1 + return self.fun(x, *args) + elif numpy.any(x != self.x): + self.x = numpy.asarray(x).copy() + self.f = self.fun(x, *args) + self.calls += 1 + return self.f + else: + return self.f + class MemoizeJac(object): """ Decorator that caches the value gradient of function each time it is called. """ From cd8ba576d91dafc2917b8c088ae41648a3978c46 Mon Sep 17 00:00:00 2001 From: Denis Laxalde Date: Mon, 4 Jun 2012 13:16:21 -0400 Subject: [PATCH 2/3] FIX: copy __name__ attribute upon memoization in optimize This is needed since, for some kinds of failures, MINPACK routines output an error message which refers to the __name__ attribute of the objective function and jacobian. --- scipy/optimize/optimize.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scipy/optimize/optimize.py b/scipy/optimize/optimize.py index e6fb44ffa2ec..461ba7fee4d9 100644 --- a/scipy/optimize/optimize.py +++ b/scipy/optimize/optimize.py @@ -45,6 +45,8 @@ class MemoizeFun(object): it is called or only the first time if `first_only==True`.""" def __init__(self, fun, first_only=False): self.fun = fun + if hasattr(fun, '__name__'): + self.__name__ = fun.__name__ self.calls = 0 self.first_only = first_only @@ -70,6 +72,8 @@ class MemoizeJac(object): is called. """ def __init__(self, fun): self.fun = fun + if hasattr(fun, '__name__'): + self.__name__ = fun.__name__ self.jac = None self.x = None From 3d24d3bd7f511566ee91c1fc38dd259dab04771f Mon Sep 17 00:00:00 2001 From: Denis Laxalde Date: Thu, 7 Jun 2012 16:32:46 -0400 Subject: [PATCH 3/3] ENH: allow MemoizeFun to skip first points in MINPACK --- scipy/optimize/minpack.py | 8 ++++---- scipy/optimize/optimize.py | 19 ++++++++++++------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/scipy/optimize/minpack.py b/scipy/optimize/minpack.py index 7e987c31bd0f..69d3347e7e0f 100644 --- a/scipy/optimize/minpack.py +++ b/scipy/optimize/minpack.py @@ -183,7 +183,7 @@ def _root_hybr(func, x0, args=(), jac=None, options=None): diag = options.get('diag', None) full_output = True - func = MemoizeFun(func, first_only=True) + func = MemoizeFun(func, first_only=True, nskip=1) x0 = array(x0, ndmin=1) n = len(x0) if type(args) != type(()): @@ -200,7 +200,7 @@ def _root_hybr(func, x0, args=(), jac=None, options=None): retval = _minpack._hybrd(func, x0, args, full_output, xtol, maxfev, ml, mu, epsfcn, factor, diag) else: - Dfun = MemoizeFun(Dfun, first_only=True) + Dfun = MemoizeFun(Dfun, first_only=True, nskip=1) _check_func('fsolve', 'fprime', Dfun, x0, args, n, (n,n)) if (maxfev == 0): maxfev = 100*(n + 1) @@ -350,7 +350,7 @@ def leastsq(func, x0, args=(), Dfun=None, full_output=0, params """ - func = MemoizeFun(func, first_only=True) + func = MemoizeFun(func, first_only=True, nskip=1) x0 = array(x0, ndmin=1) n = len(x0) if type(args) != type(()): @@ -364,7 +364,7 @@ def leastsq(func, x0, args=(), Dfun=None, full_output=0, retval = _minpack._lmdif(func, x0, args, full_output, ftol, xtol, gtol, maxfev, epsfcn, factor, diag) else: - Dfun = MemoizeFun(Dfun, first_only=True) + Dfun = MemoizeFun(Dfun, first_only=True, nskip=1) if col_deriv: _check_func('leastsq', 'Dfun', Dfun, x0, args, n, (n,m)) else: diff --git a/scipy/optimize/optimize.py b/scipy/optimize/optimize.py index 461ba7fee4d9..ae03a43654fb 100644 --- a/scipy/optimize/optimize.py +++ b/scipy/optimize/optimize.py @@ -41,28 +41,33 @@ class MemoizeFun(object): - """ Decorator that caches the value of the objective function each time - it is called or only the first time if `first_only==True`.""" - def __init__(self, fun, first_only=False): + """ Decorator that caches the value of the objective function. + If `first_only==True`, only the first point is memoized. + If `nskip > 0`, it is assumed that the function is evaluated `nskip` + times at the first point, so that the memoized values is used. """ + def __init__(self, fun, first_only=False, nskip=0): self.fun = fun if hasattr(fun, '__name__'): self.__name__ = fun.__name__ - self.calls = 0 self.first_only = first_only + self.nskip = nskip def __call__(self, x, *args): + if hasattr(self, 'calls'): + self.calls += 1 + else: + self.calls = 0 if self.calls == 0: self.x = numpy.asarray(x).copy() self.f = self.fun(x, *args) - self.calls += 1 + return self.f + elif self.calls <= self.nskip: return self.f elif self.first_only and self.calls > 1: - self.calls += 1 return self.fun(x, *args) elif numpy.any(x != self.x): self.x = numpy.asarray(x).copy() self.f = self.fun(x, *args) - self.calls += 1 return self.f else: return self.f