Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ENH: add nonlinear version of ADMM, closes #1201 #1202

Open
wants to merge 6 commits into
base: master
Choose a base branch
from

Conversation

kohr-h
Copy link
Member

@kohr-h kohr-h commented Oct 21, 2017

TODO:

  • Implement algorithm
  • Unit test
  • Example

@kohr-h
Copy link
Member Author

kohr-h commented Oct 21, 2017

Ready for review. I also found the bug that was causing headaches for aliased input and output, see #1181. It was in OperatorSum! Incredible that this hasn't turned up earlier.

Copy link
Member

@adler-j adler-j left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some things could use improvement or more thoughts, with that said getting ADMM properly in is very nice!

# Define number of energy bins on both sides as well as the source spectrum
num_bins_reco = 4
num_bins_data = 2
assert num_bins_reco >= num_bins_data
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not at all sure we want asserts in examples

# tau^k <- opnorm_factor / (delta * ||dL(x^k)||^2)
A = L.derivative(x)
xstart = A.domain.one() if i == 0 else x
A_norm = power_method_opnorm(A, xstart, maxiter=opnorm_maxiter)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

power method in each iterate is going to be expensive not to say un-usable. Is there no way around this?

@@ -0,0 +1,169 @@
"""Total variation spectral tomography using preconditioned nonlinear ADMM.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Examples like this truly need to go into a "complicated" folder so to say, this is getting out of hand.

Nothing against the example per se, just that we want beginners to find them "in the right order".

group_size = num_bins_reco // num_bins_data
# Note: the spectrum (i.e. intensity per channel) is additive on the data
# side, i.e., adding more entries adds to the total signal in the data bins
source_spectrum = [8e4, 1e5, 8e4, 6e4]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why soo high values?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll have to work a bit on this example anyway. Currently it's a bit silly since for n x n problems, you can actually linearize.

@@ -159,3 +170,212 @@ def admm_linearized_simple(x, f, g, L, tau, sigma, niter, **kwargs):
u = L(x) + u - z
if callback is not None:
callback(x)


def admm_precon_nonlinear(x, f, g, L, delta, niter, sigma=None, **kwargs):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure that "precon" should have its own function, IMO it should just be optional and this can be called "admm_nonlinear"

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, are we sure that this is the admm_nonlinear method, or can we get a naming collision here in the future?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I understand it, the preconditioning is part of the method, and the way it is used makes the method explicit, i.e., everything can be expressed in terms of proximals. The ADMM method has implicit steps in the form of some argmin which is not a proximal.

if not 0 < sigma < 1 / delta:
raise ValueError(
'`sigma` must lie strictly between 0 and 1/delta`, got '
'sigma={:.4} and (1/delta)={:.4}'.format(sigma_in, 1 / delta))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4 digits is bad practice for errors, just print all of them.

y = L.range.zero()
# mu = [mu_old +] delta * (L(x) [- y])
mu = delta * L(x)
# mubar = 2 * mu [- mu_old]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

leftover?

out.lincomb(1.0 / (1 + 2 * sig * lam), x)
else:
out.lincomb(1.0 / (1 + 2 * sig * lam), x,
2 * sig * lam / (1 + 2 * sig * lam), g)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mixing 1.0 and 1 here, lets go for one of them

@@ -72,5 +74,65 @@ def test_admm_lin_l1():
assert all_almost_equal(x, data_1, places=2)


def test_admm_lin_vs_simple():
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess these are old, or do we intend for them to stay? If so, I'd move the "simple" guy into this file

Copy link
Member

@adler-j adler-j left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Submitting a review I started long ago.

Overall my main issue with this PR is that, as we discussed with @mehrhardt before (I think he even made an issue), is that the example is very complicated and I'm not at all sure if this fits in the standard examples folder. Do we have any plan for this?

where ``||.||_{nuc, 1, 2}`` is the nuclear L1-L2 norm and ``A`` a 2 x 4
operator matrix

(c_1 * exp(-R) c_2 * exp(-R) )
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add zeros to this for clarity

A = ( c_3 * exp(-R) c_4 * exp(-R))

with constants ``c_i`` and the parallel beam ray transform ``R``. In other
words, ``A`` acts on an input ``x`` with 4 components as follows:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

x = (x_1, x_2, x_3, x_4)


# Create the forward operator
ray_trafo = odl.tomo.RayTransform(single_energy_space, geometry,
impl='astra_cpu')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't hardcode backend. With that said we must fix the astra issue: astra-toolbox/astra-toolbox#109, perhaps we need to do so locally.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This issue is actually fixed now

The linear operator that is composed with ``g`` in the problem
definition. It must fulfill ``L.domain == f.domain`` and
``L.range == g.domain``.
Linear operator composed with ``g`` in the problem definition.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that is composed with

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants