Skip to content

Commit b02a92a

Browse files
committed
feature: Added HQS solver
1 parent 4959e76 commit b02a92a

File tree

4 files changed

+116
-7
lines changed

4 files changed

+116
-7
lines changed

docs/source/api/index.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ Convex
6464
EuclideanBall
6565
Huber
6666
Intersection
67+
L0
6768
L0Ball
6869
L1
6970
L1Ball
@@ -136,6 +137,7 @@ Primal
136137
AcceleratedProximalGradient
137138
ADMM
138139
ADMML2
140+
HQS
139141
LinearizedADMM
140142
ProximalGradient
141143
ProximalPoint

pyproximal/optimization/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@
1111
ProximalPoint Proximal point algorithm (or proximal min.)
1212
ProximalGradient Proximal gradient algorithm
1313
AcceleratedProximalGradient Accelerated Proximal gradient algorithm
14+
HQS Half Quadrating Splitting
1415
ADMM Alternating Direction Method of Multipliers
15-
ADMML2 ADMM with L2 misfit term
16+
ADMML2 ADMM with L2 misfit term
1617
LinearizedADMM Linearized ADMM
1718
TwIST Two-step Iterative Shrinkage/Threshold
1819
PlugAndPlay Plug-and-Play Prior with ADMM

pyproximal/optimization/primal.py

Lines changed: 110 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -372,7 +372,106 @@ def AcceleratedProximalGradient(proxf, proxg, x0, tau=None, beta=0.5,
372372
return x
373373

374374

375-
def ADMM(proxf, proxg, x0, tau, niter=10, callback=None, show=False):
375+
def HQS(proxf, proxg, x0, tau, niter=10, gfirst=True, callback=None, show=False):
376+
r"""Half Quadratic splitting
377+
378+
Solves the following minimization problem using Half Quadratic splitting
379+
algorithm:
380+
381+
.. math::
382+
383+
\mathbf{x},\mathbf{z} = \argmin_{\mathbf{x},\mathbf{z}}
384+
f(\mathbf{x}) + g(\mathbf{z}) \\
385+
s.t. \; \mathbf{x}=\mathbf{z}
386+
387+
where :math:`f(\mathbf{x})` and :math:`g(\mathbf{z})` are any convex
388+
function that has a known proximal operator.
389+
390+
Parameters
391+
----------
392+
proxf : :obj:`pyproximal.ProxOperator`
393+
Proximal operator of f function
394+
proxg : :obj:`pyproximal.ProxOperator`
395+
Proximal operator of g function
396+
x0 : :obj:`numpy.ndarray`
397+
Initial vector
398+
tau : :obj:`float`, optional
399+
CHECK!!!
400+
niter : :obj:`int`, optional
401+
Number of iterations of iterative scheme
402+
gfirst : :obj:`bool`, optional
403+
Apply Proximal of operator ``g`` first (``True``) or Proximal of
404+
operator ``f`` first (``False``)
405+
callback : :obj:`callable`, optional
406+
Function with signature (``callback(x)``) to call after each iteration
407+
where ``x`` is the current model vector
408+
show : :obj:`bool`, optional
409+
Display iterations log
410+
411+
Returns
412+
-------
413+
x : :obj:`numpy.ndarray`
414+
Inverted model
415+
z : :obj:`numpy.ndarray`
416+
Inverted second model
417+
418+
Notes
419+
-----
420+
The HQS algorithm can be expressed by the following recursion [1]_:
421+
422+
.. math::
423+
424+
\mathbf{z}^{k+1} = \prox_{\tau g}(\mathbf{x}^{k})
425+
\mathbf{x}^{k+1} = \prox_{\tau f}(\mathbf{z}^{k+1})\\
426+
427+
Note that ``x`` and ``z`` converge to each other, however if iterations are
428+
stopped too early ``x`` is guaranteed to belong to the domain of ``f``
429+
while ``z`` is guaranteed to belong to the domain of ``g``. Depending on
430+
the problem either of the two may be the best solution.
431+
432+
.. [1] D., Geman, and C., Yang, "Nonlinear image recovery with halfquadratic
433+
regularization", IEEE Transactions on Image Processing,
434+
4, 7, pp. 932-946, 1995.
435+
436+
"""
437+
if show:
438+
tstart = time.time()
439+
print('HQS\n'
440+
'---------------------------------------------------------\n'
441+
'Proximal operator (f): %s\n'
442+
'Proximal operator (g): %s\n'
443+
'tau = %10e\tniter = %d\n' % (type(proxf), type(proxg),
444+
tau, niter))
445+
head = ' Itn x[0] f g J = f + g'
446+
print(head)
447+
448+
x = x0.copy()
449+
z = np.zeros_like(x)
450+
for iiter in range(niter):
451+
if gfirst:
452+
z = proxg.prox(x, tau)
453+
x = proxf.prox(z, tau)
454+
else:
455+
x = proxf.prox(z, tau)
456+
z = proxg.prox(x, tau)
457+
458+
# run callback
459+
if callback is not None:
460+
callback(x)
461+
462+
if show:
463+
if iiter < 10 or niter - iiter < 10 or iiter % (niter // 10) == 0:
464+
pf, pg = proxf(x), proxg(x)
465+
msg = '%6g %12.5e %10.3e %10.3e %10.3e' % \
466+
(iiter + 1, x[0], pf, pg, pf + pg)
467+
print(msg)
468+
if show:
469+
print('\nTotal time (s) = %.2f' % (time.time() - tstart))
470+
print('---------------------------------------------------------\n')
471+
return x, z
472+
473+
474+
def ADMM(proxf, proxg, x0, tau, niter=10, gfirst=False, callback=None, show=False):
376475
r"""Alternating Direction Method of Multipliers
377476
378477
Solves the following minimization problem using Alternating Direction
@@ -417,6 +516,9 @@ def ADMM(proxf, proxg, x0, tau, niter=10, callback=None, show=False):
417516
the Lipschitz constant of :math:`\nabla f`.
418517
niter : :obj:`int`, optional
419518
Number of iterations of iterative scheme
519+
gfirst : :obj:`bool`, optional
520+
Apply Proximal of operator ``g`` first (``True``) or Proximal of
521+
operator ``f`` first (``False``)
420522
callback : :obj:`callable`, optional
421523
Function with signature (``callback(x)``) to call after each iteration
422524
where ``x`` is the current model vector
@@ -445,7 +547,7 @@ def ADMM(proxf, proxg, x0, tau, niter=10, callback=None, show=False):
445547
\mathbf{z}^{k+1} = \prox_{\tau g}(\mathbf{x}^{k+1} + \mathbf{u}^{k})\\
446548
\mathbf{u}^{k+1} = \mathbf{u}^{k} + \mathbf{x}^{k+1} - \mathbf{z}^{k+1}
447549
448-
Note that ``x`` and ``z`` converge to each other, but if iterations are
550+
Note that ``x`` and ``z`` converge to each other, however if iterations are
449551
stopped too early ``x`` is guaranteed to belong to the domain of ``f``
450552
while ``z`` is guaranteed to belong to the domain of ``g``. Depending on
451553
the problem either of the two may be the best solution.
@@ -465,8 +567,12 @@ def ADMM(proxf, proxg, x0, tau, niter=10, callback=None, show=False):
465567
x = x0.copy()
466568
u = z = np.zeros_like(x)
467569
for iiter in range(niter):
468-
x = proxf.prox(z - u, tau)
469-
z = proxg.prox(x + u, tau)
570+
if gfirst:
571+
z = proxg.prox(x + u, tau)
572+
x = proxf.prox(z - u, tau)
573+
else:
574+
x = proxf.prox(z - u, tau)
575+
z = proxg.prox(x + u, tau)
470576
u = u + x - z
471577

472578
# run callback

pyproximal/optimization/primaldual.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,9 @@ def PrimalDual(proxf, proxg, A, x0, tau, mu, z=None, theta=1., niter=10,
9393
\mathbf{y}^{k+1} = \prox_{\mu g^*}(\mathbf{y}^{k} +
9494
\mu \mathbf{A}\bar{\mathbf{x}}^{k+1})
9595
96-
.. [1] A., Chambolle, and T., Pock, "A first-order primal-dual algorithm for
96+
.. [1] A., Chambolle, and T., Pock, "A first-order primal-dual algorithm for
9797
convex problems with applications to imaging", Journal of Mathematical
98-
Imaging and Vision, 40, 8pp. 120145. 2011.
98+
Imaging and Vision, 40, 8pp. 120-145. 2011.
9999
100100
"""
101101
if show:

0 commit comments

Comments
 (0)