On May 2, 6:08 am, Carsten Haese <cars...@uniqsy s.comwrote:
On Tue, 2007-05-01 at 22:21 -0700, Michael wrote:
Is there a reason for using the closure here? Using function defaults
seems to give better performance:[...]
It does? Not as far as I can measure it to any significant degree on my
computer.
I agree the performance gains are minimal. Using function defaults
rather than closures, however, seemed much cleaner an more explicit to
me. For example, I have been bitten by the following before:
>>def f(x):
.... def g():
.... x = x + 1
.... return x
.... return g
>>g = f(3)
g()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in g
UnboundLocalErr or: local variable 'x' referenced before assignment
If you use default arguments, this works as expected:
>>def f(x):
.... def g(x=x):
.... x = x + 1
.... return x
.... return g
>>g = f(3)
g()
4
The fact that there also seems to be a performance gain (granted, it
is extremely slight here) led me to ask if there was any advantage to
using closures. It seems not.
An overriding theme in this thread is that you are greatly concerned
with the speed of your solution rather than the structure and
readability of your code.
Yes, it probably does seem that way, because I am burying this code
deeply and do not want to revisit it when profiling later, but my
overriding concern is reliability and ease of use. Using function
attributes seemed the best way to achieve both goals until I found out
that the pythonic way of copying functions failed. Here was how I
wanted my code to work:
@define_options (first_option=' abs_tol')
def step(f,x,J,abs_ tol=1e-12,rel_tol=1e-8,**kwargs):
"""Take a step to minimize f(x) using the jacobian J.
Return (new_x,converge d) where converged is true if the tolerance
has been met.
"""
<compute dx and check convergence>
return (x + dx, converged)
@define_options (first_option=' min_h')
def jacobian(f,x,mi n_h=1e-6,max_h=0.1):
"""Compute jacobian using a step min_h < h < max_h."""
<compute J>
return J
class Minimizer(objec t):
"""Object to minimize a function."""
def __init__(self,s tep,jacobian,** kwargs):
self.options = step.options + jacobian.option s
self.step = step
self.jacobian = jacobian
def minimize(self,f ,x0,**kwargs):
"""Minimize the function f(x) starting at x0."""
step = self.step
jacobian = self.jacobian
step.set_option s(**kwargs)
jacobian.set_op tions(**kwargs)
converged = False
while not converged:
J = jacobian(f,x)
(x,converged) = step(f,x,J)
return x
@property
def options(self):
"""List of supported options."""
return self.options
The idea is that one can define different functions for computing the
jacobian, step etc. that take various parameters, and then make a
custom minimizer class that can provide the user with information
about the supported options etc.
The question is how to define the decorator define_options?
1) I thought the cleanest solution was to add a method f.set_options()
which would set f.func_defaults , and a list f.options for
documentation purposes. The docstring remains unmodified without any
special "wrapping", step and jacobian are still "functions" and
performance is optimal.
2) One could return an instance f of a class with f.__call__,
f.options and f.set_options defined. This would probably be the most
appropriate OO solution, but it makes the decorator much more messy,
or requires the user to define classes rather than simply define the
functions as above. In addition, this is at least a factor of 2.5
timese slower on my machine than option 1) because of the class
instance overhead. (This is my only real performance concern because
this is quite a large factor. Otherwise I would just use this
method.)
3) I could pass generators to Minimize and construct the functions
dynamically. This would have the same performance, but would require
the user to define generators, or require the decorator to return a
generator when the user appears to be defining a function. This just
seems much less elegant.
....
@define_options _generator(firs t_option='min_h ')
def jacobian_gen(f, x,min_h=1e-6,max_h=0.1):
"""Compute jacobian using a step min_h < h < max_h."""
<compute J>
return J
class Minimizer(objec t):
"""Object to minimize a function."""
def __init__(self,s tep_gen,jacobia n_gen,**kwargs) :
self.options = step_gen.option s + jacobian_gen.op tions
self.step_gen = step_gen
self.jacobian_g en = jacobian_gen
def minimize(self,f ,x0,**kwargs):
"""Minimize the function f(x) starting at x0."""
step = self.step_gen(* *kwargs)
jacobian = self.jacobian_g en(**kwargs)
converged = False
while not converged:
J = jacobian(f,x)
(x,converged) = step(f,x,J)
return x
...
4) Maybe there is a better, cleaner way to do this, but I thought that
my option 1) was the most clear, readable and fast. I would
appreciate any suggestions. The only problem is that it does use
mutable functions, and so the user might be tempted to try:
new_step = copy(step)
which would fail (because modifying new_step would also modify step).
I guess that this is a pretty big problem (I could provide a custom
copy function so that
new_step = step.copy()
would work) and I wondered if there was a better solution (or if maybe
copy.py should be fixed. Checking for a defined __copy__ method
*before* checking for pre-defined mutable types does not seem to break
anything.)
Thanks again everyone for your suggestions, it is really helping me
learn about python idioms.
Michael.