Skip to content

Commit 004ed07

Browse files
author
Salman Naqvi
committed
resolved merge conflicts
2 parents 181d7e0 + 9adcbe8 commit 004ed07

File tree

21 files changed

+615
-87
lines changed

21 files changed

+615
-87
lines changed

.github/workflows/build.yaml

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,30 @@ on: [push, pull_request]
44

55
jobs:
66
build:
7-
8-
runs-on: ubuntu-latest
97
strategy:
108
matrix:
11-
python-version: [3.8]
9+
platform: [ ubuntu-latest, macos-latest ]
10+
python-version: ["3.8", "3.9", "3.10"]
1211

12+
runs-on: ${{ matrix.platform }}
1313
steps:
14-
- uses: actions/checkout@v2
14+
- uses: actions/checkout@v3
15+
- name: Get history and tags for SCM versioning to work
16+
run: |
17+
git fetch --prune --unshallow
18+
git fetch --depth=1 origin +refs/tags/*:refs/tags/*
1519
- name: Set up Python ${{ matrix.python-version }}
16-
uses: actions/setup-python@v2
20+
uses: actions/setup-python@v4
1721
with:
1822
python-version: ${{ matrix.python-version }}
1923
- name: Install dependencies
2024
run: |
21-
python -m pip install --upgrade pip
25+
python -m pip install --upgrade pip setuptools
2226
pip install flake8 pytest
2327
if [ -f requirements.txt ]; then pip install -r requirements-dev.txt; fi
2428
- name: Install pyproximal
2529
run: |
30+
python -m setuptools_scm
2631
pip install .
2732
- name: Test with pytest
2833
run: |

.readthedocs.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,5 @@ sphinx:
2121
python:
2222
install:
2323
- requirements: requirements-dev.txt
24-
- method: setuptools
24+
- method: pip
2525
path: .

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ dev-install_conda:
3131

3232
tests:
3333
make pythoncheck
34-
$(PYTHON) setup.py test
34+
pytest
3535

3636
doc:
3737
cd docs && rm -rf source/api/generated && rm -rf source/gallery &&\

azure-pipelines.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ jobs:
6464
displayName: 'Install prerequisites and library'
6565
6666
- script: |
67-
python setup.py test
67+
pytest
6868
condition: succeededOrFailed()
6969
displayName: 'Run tests'
7070
@@ -93,7 +93,7 @@ jobs:
9393
displayName: 'Install prerequisites and library'
9494
9595
- script: |
96-
python setup.py test
96+
pytest
9797
condition: succeededOrFailed()
9898
displayName: 'Run tests'
9999

docs/source/api/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ Non-Convex
9292
Log1
9393
QuadraticEnvelopeCard
9494
QuadraticEnvelopeCardIndicator
95+
RelaxedMumfordShah
9596
SCAD
9697

9798

docs/source/conf.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
import sys
33
import os
44
import datetime
5-
import sphinx_rtd_theme
65
import sphinx_gallery
76
from sphinx_gallery.sorting import ExampleTitleSortKey
87
from pyproximal import __version__

environment-dev.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ dependencies:
2020
- bm3d
2121
- pytest-runner
2222
- setuptools_scm
23-
- sphinx-rtd-theme
23+
- pydata-sphinx-theme
2424
- sphinx-gallery
2525
- nbsphinx
2626
- image

pyproject.toml

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
[build-system]
2+
requires = [
3+
"setuptools >= 65",
4+
"setuptools_scm[toml]",
5+
"wheel",
6+
]
7+
build-backend = "setuptools.build_meta"
8+
9+
[project]
10+
name = "pyproximal"
11+
description = "Python library implementing proximal operators to solve non-smooth, constrained convex problems with proximal algorithms"
12+
readme = "README.md"
13+
authors = [
14+
{name = "Matteo Ravasi", email = "[email protected]"},
15+
]
16+
license = {file = "LICENSE.md"}
17+
keywords = ["algebra", "inverse problems", "proximal", "convex optimization", "large-scale optimization"]
18+
classifiers = [
19+
"Development Status :: 5 - Production/Stable",
20+
"Intended Audience :: Developers",
21+
"Intended Audience :: Science/Research",
22+
"Intended Audience :: Education",
23+
"License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)",
24+
"Natural Language :: English",
25+
"Operating System :: OS Independent",
26+
"Programming Language :: Python :: 3 :: Only",
27+
"Programming Language :: Python :: 3.8",
28+
"Programming Language :: Python :: 3.9",
29+
"Programming Language :: Python :: 3.10",
30+
"Topic :: Scientific/Engineering : Mathematics",
31+
]
32+
dependencies = [
33+
"numpy >= 1.15.0",
34+
"scipy >= 1.8.0",
35+
"pylops >= 2.0.0",
36+
]
37+
dynamic = ["version"]
38+
39+
[project.optional-dependencies]
40+
advanced = [
41+
"llvmlite",
42+
"numba",
43+
]
44+
45+
[tool.setuptools.packages.find]
46+
exclude = ["pytests"]
47+
48+
[tool.setuptools_scm]
49+
version_file = "pyproximal/version.py"

pyproximal/optimization/primal.py

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,7 @@ def GeneralizedProximalGradient(proxfs, proxgs, x0, tau=None,
309309
.. math::
310310
311311
\mathbf{x} = \argmin_\mathbf{x} \sum_{i=1}^n f_i(\mathbf{x})
312-
+ \sum_{j=1}^m \tau_j g_j(\mathbf{x}),~~n,m \in \mathbb{N}^+
312+
+ \sum_{j=1}^m \epsilon_j g_j(\mathbf{x}),~~n,m \in \mathbb{N}^+
313313
314314
where the :math:`f_i(\mathbf{x})` are smooth convex functions with a uniquely
315315
defined gradient and the :math:`g_j(\mathbf{x})` are any convex function that
@@ -330,7 +330,7 @@ def GeneralizedProximalGradient(proxfs, proxgs, x0, tau=None,
330330
backtracking is used to adaptively estimate the best tau at each
331331
iteration.
332332
epsg : :obj:`float` or :obj:`np.ndarray`, optional
333-
Scaling factor of g function
333+
Scaling factor(s) of ``g`` function(s)
334334
niter : :obj:`int`, optional
335335
Number of iterations of iterative scheme
336336
acceleration: :obj:`str`, optional
@@ -353,11 +353,12 @@ def GeneralizedProximalGradient(proxfs, proxgs, x0, tau=None,
353353
354354
.. math::
355355
\text{for } j=1,\cdots,n, \\
356-
~~~~\mathbf z_j^{k+1} = \mathbf z_j^{k} + \eta_k (prox_{\frac{\tau^k}{\omega_j} g_j}(2 \mathbf{x}^{k} - z_j^{k})
357-
- \tau^k \sum_{i=1}^n \nabla f_i(\mathbf{x}^{k})) - \mathbf{x}^{k} \\
356+
~~~~\mathbf z_j^{k+1} = \mathbf z_j^{k} + \epsilon_j
357+
\left[prox_{\frac{\tau^k}{\omega_j} g_j}\left(2 \mathbf{x}^{k} - \mathbf{z}_j^{k}
358+
- \tau^k \sum_{i=1}^n \nabla f_i(\mathbf{x}^{k})\right) - \mathbf{x}^{k} \right] \\
358359
\mathbf{x}^{k+1} = \sum_{j=1}^n \omega_j f_j \\
359360
360-
where :math:`\sum_{j=1}^n \omega_j=1`.
361+
where :math:`\sum_{j=1}^n \omega_j=1`. In the current implementation :math:`\omega_j=1/n`.
361362
"""
362363
# check if epgs is a vector
363364
if np.asarray(epsg).size == 1.:
@@ -394,23 +395,23 @@ def GeneralizedProximalGradient(proxfs, proxgs, x0, tau=None,
394395
for iiter in range(niter):
395396
xold = x.copy()
396397

397-
# proximal step
398+
# gradient
398399
grad = np.zeros_like(x)
399400
for i, proxf in enumerate(proxfs):
400401
grad += proxf.grad(x)
401402

402-
sol = np.zeros_like(x)
403+
# proximal step
404+
x = np.zeros_like(x)
403405
for i, proxg in enumerate(proxgs):
404-
tmp = 2 * y - zs[i] - tau * grad
405-
tmp[:] = proxg.prox(tmp, epsg *tau *len(proxgs) )
406-
zs[i] += (tmp - y)
407-
sol += zs[i] / len(proxgs)
408-
x[:] = sol.copy()
406+
ztmp = 2 * y - zs[i] - tau * grad
407+
ztmp = proxg.prox(ztmp, epsg * tau * len(proxgs))
408+
zs[i] += (ztmp - y)
409+
x += zs[i] / len(proxgs)
409410

410411
# update y
411412
if acceleration == 'vandenberghe':
412413
omega = iiter / (iiter + 3)
413-
elif acceleration== 'fista':
414+
elif acceleration == 'fista':
414415
told = t
415416
t = (1. + np.sqrt(1. + 4. * t ** 2)) / 2.
416417
omega = ((told - 1.) / t)
@@ -782,7 +783,7 @@ def ADMML2(proxg, Op, b, A, x0, tau, niter=10, callback=None, show=False, **kwar
782783

783784
if show:
784785
if iiter < 10 or niter - iiter < 10 or iiter % (niter // 10) == 0:
785-
pf, pg = np.linalg.norm(Op @ x - b), proxg(Ax)
786+
pf, pg = 0.5 * np.linalg.norm(Op @ x - b) ** 2, proxg(Ax)
786787
msg = '%6g %12.5e %10.3e %10.3e %10.3e' % \
787788
(iiter + 1, x[0], pf, pg, pf + pg)
788789
print(msg)

pyproximal/proximal/RelaxedMS.py

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import numpy as np
2+
3+
from pyproximal.ProxOperator import _check_tau
4+
from pyproximal import ProxOperator
5+
from pyproximal.proximal.L1 import _current_sigma
6+
7+
8+
def _l2(x, alpha):
9+
r"""Scaling operation.
10+
11+
Applies the proximal of ``alpha||y - x||_2^2`` which is essentially a scaling operation.
12+
13+
Parameters
14+
----------
15+
x : :obj:`numpy.ndarray`
16+
Vector
17+
alpha : :obj:`float`
18+
Scaling parameter
19+
20+
Returns
21+
-------
22+
y : :obj:`numpy.ndarray`
23+
Scaled vector
24+
25+
"""
26+
y = 1 / (1 + 2 * alpha) * x
27+
return y
28+
29+
30+
def _current_kappa(kappa, count):
31+
if not callable(kappa):
32+
return kappa
33+
else:
34+
return kappa(count)
35+
36+
37+
class RelaxedMumfordShah(ProxOperator):
38+
r"""Relaxed Mumford-Shah norm proximal operator.
39+
40+
Proximal operator of the relaxed Mumford-Shah norm:
41+
:math:`\text{rMS}(x) = \min (\alpha\Vert x\Vert_2^2, \kappa)`.
42+
43+
Parameters
44+
----------
45+
sigma : :obj:`float` or :obj:`list` or :obj:`np.ndarray` or :obj:`func`, optional
46+
Multiplicative coefficient of L2 norm that controls the smoothness of the solutuon.
47+
This can be a constant number, a list of values (for multidimensional inputs, acting
48+
on the second dimension) or a function that is called passing a counter which keeps
49+
track of how many times the ``prox`` method has been invoked before and returns a
50+
scalar (or a list of) ``sigma`` to be used.
51+
kappa : :obj:`float` or :obj:`list` or :obj:`np.ndarray` or :obj:`func`, optional
52+
Constant value in the rMS norm which essentially controls when the norm allows a jump. This can be a
53+
constant number, a list of values (for multidimensional inputs, acting on the second dimension) or
54+
a function that is called passing a counter which keeps track of how many
55+
times the ``prox`` method has been invoked before and returns a scalar (or a list of)
56+
``kappa`` to be used.
57+
58+
Notes
59+
-----
60+
The :math:`rMS` proximal operator is defined as [1]_:
61+
62+
.. math::
63+
\text{prox}_{\tau \text{rMS}}(x) =
64+
\begin{cases}
65+
\frac{1}{1+2\tau\alpha}x & \text{ if } & \vert x\vert \leq \sqrt{\frac{\kappa}{\alpha}(1 + 2\tau\alpha)} \\
66+
\kappa & \text{ else }
67+
\end{cases}.
68+
69+
.. [1] Strekalovskiy, E., and D. Cremers, 2014, Real-time minimization of the piecewise smooth
70+
Mumford-Shah functional: European Conference on Computer Vision, 127–141.
71+
72+
"""
73+
def __init__(self, sigma=1., kappa=1.):
74+
super().__init__(None, False)
75+
self.sigma = sigma
76+
self.kappa = kappa
77+
self.count = 0
78+
79+
def __call__(self, x):
80+
sigma = _current_sigma(self.sigma, self.count)
81+
kappa = _current_sigma(self.kappa, self.count)
82+
return np.minimum(sigma * np.linalg.norm(x) ** 2, kappa)
83+
84+
def _increment_count(func):
85+
"""Increment counter
86+
"""
87+
def wrapped(self, *args, **kwargs):
88+
self.count += 1
89+
return func(self, *args, **kwargs)
90+
return wrapped
91+
92+
@_increment_count
93+
@_check_tau
94+
def prox(self, x, tau):
95+
sigma = _current_sigma(self.sigma, self.count)
96+
kappa = _current_sigma(self.kappa, self.count)
97+
98+
x = np.where(np.abs(x) <= np.sqrt(kappa / sigma * (1 + 2 * tau * sigma)), _l2(x, tau * sigma), x)
99+
return x

0 commit comments

Comments
 (0)