Skip to content

Commit c195939

Browse files
authored
Merge pull request #200 from mrava87/feat-prox
2 parents 2f788c5 + 098dbf7 commit c195939

26 files changed

Lines changed: 1991 additions & 56 deletions

‎.github/workflows/build.yml‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,12 @@ on:
1212
jobs:
1313
build:
1414
strategy:
15+
fail-fast: false
1516
matrix:
1617
os: [ubuntu-latest, macos-latest]
1718
python-version: ['3.11', '3.12', '3.13', '3.14']
1819
mpi: ['mpich', 'openmpi', 'intelmpi']
19-
rank: ['2', '4', '8']
20+
rank: ['2', '4']
2021
exclude:
2122
- os: macos-latest
2223
mpi: 'intelmpi'

‎Makefile‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -70,22 +70,22 @@ doc:
7070
doc_cupy:
7171
cp tutorials_cupy/* tutorials/
7272
cd docs && rm -rf source/api/generated && rm -rf source/gallery &&\
73-
rm -rf source/tutorials && rm -rf source/tutorials && rm -rf build &&\
74-
cd .. && sphinx-build -b html docs/source docs/build
73+
rm -rf source/tutorials && rm -rf build &&\
74+
cd .. && sphinx-build -b html docs/source docs/build &&\
7575
rm tutorials/*_cupy.py
7676

7777
doc_nccl:
7878
cp tutorials_cupy/* tutorials_nccl/* tutorials/
7979
cd docs && rm -rf source/api/generated && rm -rf source/gallery &&\
80-
rm -rf source/tutorials && rm -rf source/tutorials && rm -rf build &&\
81-
cd .. && sphinx-build -b html docs/source docs/build
80+
rm -rf source/tutorials && rm -rf build &&\
81+
cd .. && sphinx-build -b html docs/source docs/build &&\
8282
rm tutorials/*_cupy.py tutorials/*_nccl.py
8383

8484
docupdate:
8585
cd docs && NCCL_PYLOPS_MPI=0 make html && cd ..
8686

8787
servedoc:
88-
$(PYTHON) -m http.server --directory docs/build/
88+
$(PYTHON) -m http.server --directory docs/build/html/
8989

9090
# Run examples using mpi
9191
run_examples:

‎docs/source/api/index.rst‎

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ Basic Operators
4949
MPIStackedVStack
5050
MPIHStack
5151
MPIHalo
52-
5352

5453
Derivatives
5554
~~~~~~~~~~~
@@ -88,6 +87,30 @@ Wave-Equation processing
8887
MPIMDC
8988

9089

90+
Proximal operators
91+
------------------
92+
93+
Templates
94+
~~~~~~~~~
95+
96+
.. currentmodule:: pylops_mpi.proximal
97+
98+
.. autosummary::
99+
:toctree: generated/
100+
101+
MPIProxOperator
102+
103+
Basic Operators
104+
~~~~~~~~~~~~~~~
105+
106+
.. currentmodule:: pylops_mpi.proximal.proximal
107+
108+
.. autosummary::
109+
:toctree: generated/
110+
111+
MPIL2
112+
113+
91114
Solvers
92115
-------
93116

@@ -129,6 +152,19 @@ Sparsity
129152
ista
130153
fista
131154

155+
156+
Proximal
157+
~~~~~~~~
158+
159+
.. currentmodule:: pylops_mpi.proximal.optimization.primal
160+
161+
.. autosummary::
162+
:toctree: generated/
163+
164+
ProximalGradient
165+
ADMML2
166+
167+
132168
Utils
133169
-----
134170

‎docs/source/installation.rst‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ The minimal set of dependencies for the PyLops-MPI project is:
1414
* `Matplotlib <https://matplotlib.org/>`_
1515
* `MPI4py <https://mpi4py.readthedocs.io/en/stable/>`_
1616
* `PyLops <https://pylops.readthedocs.io/en/stable/>`_
17+
* `PyProximal <https://pyproximal.readthedocs.io/en/stable/>`_
1718

1819
Additionally, to use the CUDA-aware MPI engine, the following additional
1920
dependencies are required:

‎environment-dev.yml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ dependencies:
99
- scipy>=1.8.0
1010
- mpi4py
1111
- pylops>=2.0.0
12+
- pyproximal
1213
- matplotlib
1314
- ipython
1415
- pytest

‎environment.yml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,6 @@ dependencies:
77
- numpy>=1.15.0
88
- scipy>=1.8.0
99
- pylops>=2.0.0
10+
- pyproximal
1011
- matplotlib
1112
- mpi4py

‎examples/plot_prox.py‎

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
r"""
2+
Proximal operators
3+
==================
4+
5+
This example demonstrates the use of the :py:module:`pylops_mpi.proximal`
6+
module, and more specifically how to create and apply PyProximal operators
7+
to distributed array.
8+
9+
"""
10+
import numpy as np
11+
from mpi4py import MPI
12+
from matplotlib import pyplot as plt
13+
14+
import pylops
15+
import pyproximal
16+
17+
import pylops_mpi
18+
19+
np.random.seed(42)
20+
plt.close("all")
21+
comm = MPI.COMM_WORLD
22+
rank = comm.Get_rank()
23+
size = comm.Get_size()
24+
25+
###############################################################################
26+
# Let's start with so-called separable proximal operators. These are functionals
27+
# whose proximal operator can be computed in a element-wise fashion. As such,
28+
# no special implementation is required for the distributed counterpart of
29+
# those operators. Instead, we can simply wrap the PyProximal operator into
30+
# a :py:class:`pylops_mpi.proximal.MPIProxOperator`.
31+
#
32+
# We take the :py:class:`pyproximal.proximal.L1` norm as an example.
33+
34+
n = 10
35+
arr = pylops_mpi.DistributedArray(global_shape=n * size,
36+
partition=pylops_mpi.Partition.SCATTER)
37+
arr[:] = rank * np.arange(n)
38+
39+
l1 = pyproximal.proximal.L1(sigma=2.0)
40+
l1d = pylops_mpi.proximal.MPIProxOperator(l1)
41+
42+
# Call
43+
f = l1d(arr)
44+
45+
# Proximal
46+
prox = l1d.prox(arr, .1)
47+
proxdlocal = prox.asarray()
48+
49+
dprox = l1d.proxdual(arr, .1)
50+
dproxdlocal = dprox.asarray()
51+
52+
arrlocal = arr.asarray()
53+
if rank == 0:
54+
flocal = l1(arrlocal)
55+
proxlocal = l1.prox(arrlocal, .1)
56+
dproxlocal = l1.proxdual(arrlocal, .1)
57+
print("||x||_1: ", f, flocal)
58+
print("prox_||x||_1: ", all(proxdlocal == proxlocal))
59+
print("proxd_||x||_1: ", all(dproxdlocal == dproxlocal))
60+
61+
###############################################################################
62+
# We repeat now the same with the :py:class:`pyproximal.proximal.Box` operator.
63+
64+
arr = pylops_mpi.DistributedArray(global_shape=n * size,
65+
partition=pylops_mpi.Partition.SCATTER)
66+
67+
arr[:] = 3 * np.ones(n)
68+
if rank == 0:
69+
arr[n//2] = 20 # outside of the box
70+
71+
box = pyproximal.Box(lower=1., upper=5.)
72+
boxd = pylops_mpi.proximal.MPIProxOperator(box)
73+
74+
# Call
75+
f = boxd(arr)
76+
77+
# Proximal
78+
prox = boxd.prox(arr, .1)
79+
proxdlocal = prox.asarray()
80+
81+
# Dual-Proximal
82+
dprox = boxd.proxdual(arr, .1)
83+
dproxdlocal = dprox.asarray()
84+
85+
arrlocal = arr.asarray()
86+
if rank == 0:
87+
flocal = box(arrlocal)
88+
proxlocal = box.prox(arrlocal, .1)
89+
dproxlocal = box.proxdual(arrlocal, .1)
90+
print("Box(x): ", f, flocal)
91+
print("prox_Box ", all(proxdlocal == proxlocal))
92+
print("proxd_Box ", all(dproxdlocal == dproxlocal))
93+
94+
###############################################################################
95+
# We move on now to a operator that is not separable and must be fully
96+
# re-implemented in a distributed fashion, namely the
97+
# :py:class:`pylops_mpi.proximal.MPIL2` norm.
98+
#
99+
# More precisely, when ``Op`` and ``b`` are passed to this operator,
100+
# its proximal does call for the solution of a distributed inverse problem.
101+
#
102+
# However, let's start with the simplest case: :math:`||\mathbf{x}||_2^2`
103+
104+
arr = pylops_mpi.DistributedArray(global_shape=n * size,
105+
partition=pylops_mpi.Partition.SCATTER)
106+
107+
arr[:] = rank * np.arange(n)
108+
109+
l2 = pyproximal.L2(sigma=2.0)
110+
l2d = pylops_mpi.proximal.MPIL2(sigma=2.0)
111+
112+
# Call
113+
f = l2d(arr)
114+
115+
# Proximal
116+
prox = l2d.prox(arr, .1)
117+
proxdlocal = prox.asarray()
118+
119+
# Gradient
120+
grad = l2d.grad(arr)
121+
graddlocal = grad.asarray()
122+
123+
arrlocal = arr.asarray()
124+
if rank == 0:
125+
flocal = l2(arrlocal)
126+
proxlocal = l2.prox(arrlocal, .1)
127+
gradlocal = l2.grad(arrlocal)
128+
print("||x||_2^2: ", f, flocal)
129+
print("prox_||x||_2^2: ", all(proxdlocal == proxlocal))
130+
print("grad_||x||_2^2: ", all(graddlocal == gradlocal))
131+
132+
###############################################################################
133+
# Next we move onto the more general case
134+
# :math:`||\mathbf{Op} \mathbf{x} - \mathbf{b}||_2^2`
135+
136+
solver="cgls"
137+
Op = pylops.Diagonal(np.ones(n * size))
138+
Opd = pylops_mpi.MPIBlockDiag([pylops.Diagonal(np.ones(n)),])
139+
140+
b = pylops_mpi.DistributedArray(global_shape=n * size,
141+
partition=pylops_mpi.Partition.SCATTER)
142+
b[:] = rank * np.ones(n)
143+
blocal = b.asarray()
144+
145+
x0 = arr.zeros_like()
146+
x0local = x0.asarray()
147+
148+
l2 = pyproximal.L2(
149+
Op=Op, b=blocal, sigma=2.0,
150+
solver=solver, x0=x0local,
151+
kwargs_solver=dict(show=True))
152+
l2d = pylops_mpi.proximal.MPIL2(
153+
Op=Opd, b=b, sigma=2.0,
154+
solver=solver, x0=x0,
155+
kwargs_solver=dict(show=True if rank==0 else False))
156+
157+
# Call
158+
f = l2d(arr)
159+
160+
# Proximal
161+
prox = l2d.prox(arr, .1)
162+
proxdlocal = prox.asarray()
163+
164+
# Gradient
165+
grad = l2d.grad(arr)
166+
graddlocal = grad.asarray()
167+
168+
arrlocal = arr.asarray()
169+
if rank == 0:
170+
flocal = l2(arrlocal)
171+
proxlocal = l2.prox(arrlocal, .1)
172+
gradlocal = l2.grad(arrlocal)
173+
print("||Op . x - b||_2^2: ", f, flocal)
174+
print("prox_||Op . x - b||_2^2 - norm diff=",
175+
np.linalg.norm(proxdlocal - proxlocal))
176+
print("grad_||Op . x - b||_2^2: ", all(graddlocal == gradlocal))

0 commit comments

Comments
 (0)