-
Notifications
You must be signed in to change notification settings - Fork 119
Description
Copied from the discussion in #1252:
I am not sure that this change in API makes a big difference. It means that if one wants to have more specialised callbacks, then one can recycle some code by using the update function. However, in some applications one also might want to choose which values to compute and which ones to store. Thus, for these one needs to change the update function. Thus, I am not convinced one gains that much with the proposed changes.
If you want to write callbacks that do more interesting stuff, then yes, agreed. But the whole point of writing a class that exposes elements of the optimization method is to not have to use callbacks to do interesting stuff.
See, the purpose of callbacks is basically to get information out of a black box. You don't know if or when things will happen, but you say to the black box "Whenever you do X, please call this function." But if you have a "grey box" rather than a black box, you have all kinds of possibilities, depending on how much the box exposes.
So in our example, think of something like this:
Basic version
class Landweber(object):
def __init__(self, op, x, rhs, omega=1):
self.op = op
self.x = x
self.rhs = rhs
self.omega = omega
def step(self):
self.x.assign(self.x - self.omega * self.op.adjoint(self.op(self.x) - self.rhs))
def current(self):
return self.xMore advanced version
class Landweber(object):
def __init__(self, op, x, rhs, omega=1):
self.op = op
self.x = x
self.rhs = rhs
self.omega = omega
def step(self):
self.x.assign(self.current() - self.omega * self.current_l2sq_grad())
def current_l2sq_grad(self):
return self.op.adjoint(self.current_residual())
def current_residual(self):
return self.op(self.x) - self.rhs
def current(self):
return self.xWith intermediate storage
class Landweber(object):
def __init__(self, op, x, rhs, omega=1, use_tmp_dom=True, use_tmp_ran=True):
self.op = op
self.x = x
self.rhs = rhs
self.omega = omega
self.tmp_dom = self.op.domain.element() if use_tmp_dom else None
self.tmp_ran = self.op.range.element() if use_tmp_ran else None
def step(self):
self.x.lincomb(1, self.x, -self.omega, self.current_l2sq_grad())
def current_l2sq_grad(self):
return self.op.adjoint(self.current_residual(), out=self.tmp_dom)
def current_residual(self):
tmp = self.op(self.x, out=self.tmp_ran)
tmp -= self.rhs
return tmp
def current(self):
return self.xThis now gives you all kinds of possibilities to alter the iteration, inspect intermediate results etc. without having to resort to callbacks.
Also note that these changes can be added gradually without changing the interface (step).