Skip to content

Commit 1351e7a

Browse files
authored
Merge pull request #114 from PyLops/2ddistr
2D-distribution
2 parents 778dc20 + 680c4fe commit 1351e7a

File tree

8 files changed

+321
-66
lines changed

8 files changed

+321
-66
lines changed

.github/workflows/build.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@ jobs:
1414
strategy:
1515
matrix:
1616
os: [ubuntu-latest, macos-latest]
17-
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12']
17+
python-version: ['3.9', '3.10', '3.11', '3.12']
1818
mpi: ['mpich', 'openmpi', 'intelmpi']
19+
rank: ['2', '3', '4']
1920
exclude:
2021
- os: macos-latest
2122
mpi: 'intelmpi'
@@ -52,4 +53,4 @@ jobs:
5253
- name: Install pylops-mpi
5354
run: pip install .
5455
- name: Testing using pytest-mpi
55-
run: mpiexec -n 2 pytest --with-mpi
56+
run: mpiexec -n ${{ matrix.rank }} pytest --with-mpi

examples/plot_distributed_array.py

Lines changed: 103 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,18 @@
1616
plt.close("all")
1717
np.random.seed(42)
1818

19+
# MPI parameters
20+
size = MPI.COMM_WORLD.Get_size() # number of nodes
21+
rank = MPI.COMM_WORLD.Get_rank() # rank of current node
22+
23+
1924
# Defining the global shape of the distributed array
2025
global_shape = (10, 5)
2126

2227
###############################################################################
23-
# Let's start by defining the
24-
# class with the input parameters ``global_shape``,
25-
# ``partition``, and ``axis``. Here's an example implementation of the class with ``axis=0``.
28+
# Let's start by defining the class with the input parameters ``global_shape``,
29+
# ``partition``, and ``axis``. Here's an example implementation of the class
30+
# with ``axis=0``.
2631
arr = pylops_mpi.DistributedArray(global_shape=global_shape,
2732
partition=pylops_mpi.Partition.SCATTER,
2833
axis=0)
@@ -72,6 +77,9 @@
7277
pylops_mpi.plot_local_arrays(arr2, "Distributed Array - 2", vmin=0, vmax=1)
7378

7479
###############################################################################
80+
# Let's move now to consider various operations that one can perform on
81+
# :py:class:`pylops_mpi.DistributedArray` objects.
82+
#
7583
# **Scaling** - Each process operates on its local portion of
7684
# the array and scales the corresponding elements by a given scalar.
7785
scale_arr = .5 * arr1
@@ -101,3 +109,95 @@
101109
# of the array and multiplies the corresponding elements together.
102110
mult_arr = arr1 * arr2
103111
pylops_mpi.plot_local_arrays(mult_arr, "Multiplication", vmin=0, vmax=1)
112+
113+
###############################################################################
114+
# Finally, let's look at the case where parallelism could be applied over
115+
# multiple axes - and more specifically one belonging to the model/data and one
116+
# to the operator. This kind of "2D"-parallelism requires repeating parts of
117+
# the model/data over groups of ranks. However, when global operations such as
118+
# ``dot`` or ``norm`` are applied on a ``pylops_mpi.DistributedArray`` of
119+
# this kind, we need to ensure that the repeated portions to do all contribute
120+
# to the computation. This can be achieved via the ``mask`` input parameter:
121+
# a list of size equal to the number of ranks, whose elements contain the index
122+
# of the subgroup/subcommunicator (with partial arrays in different groups
123+
# are identical to each other).
124+
125+
# Defining the local and global shape of the distributed array
126+
local_shape = 5
127+
global_shape = local_shape * size
128+
129+
# Create mask
130+
nsub = 2
131+
subsize = max(1, size // nsub)
132+
mask = np.repeat(np.arange(size // subsize), subsize)
133+
if rank == 0:
134+
print("1D masked arrays")
135+
print(f"Mask: {mask}")
136+
137+
# Create and fill the distributed array
138+
x = pylops_mpi.DistributedArray(global_shape=global_shape,
139+
partition=Partition.SCATTER,
140+
mask=mask)
141+
x[:] = (MPI.COMM_WORLD.Get_rank() % subsize + 1.) * np.ones(local_shape)
142+
xloc = x.asarray()
143+
144+
# Dot product
145+
dot = x.dot(x)
146+
dotloc = np.dot(xloc[local_shape * subsize * (rank // subsize):local_shape * subsize * (rank // subsize + 1)],
147+
xloc[local_shape * subsize * (rank // subsize):local_shape * subsize * (rank // subsize + 1)])
148+
print(f"Dot check (Rank {rank}): {np.allclose(dot, dotloc)}")
149+
150+
# Norm
151+
norm = x.norm(ord=2)
152+
normloc = np.linalg.norm(xloc[local_shape * subsize * (rank // subsize):local_shape * subsize * (rank // subsize + 1)],
153+
ord=2)
154+
print(f"Norm check (Rank {rank}): {np.allclose(norm, normloc)}")
155+
156+
###############################################################################
157+
# And with 2d-arrays distributed over axis=1
158+
extra_dim_shape = 2
159+
if rank == 0:
160+
print("2D masked arrays (over axis=1)")
161+
162+
# Create and fill the distributed array
163+
x = pylops_mpi.DistributedArray(global_shape=(extra_dim_shape, global_shape),
164+
partition=Partition.SCATTER,
165+
axis=1, mask=mask)
166+
x[:] = (MPI.COMM_WORLD.Get_rank() % subsize + 1.) * np.ones((extra_dim_shape, local_shape))
167+
xloc = x.asarray()
168+
169+
# Dot product
170+
dot = x.dot(x)
171+
dotloc = np.dot(xloc[:, local_shape * subsize * (rank // subsize):local_shape * subsize * (rank // subsize + 1)].ravel(),
172+
xloc[:, local_shape * subsize * (rank // subsize):local_shape * subsize * (rank // subsize + 1)].ravel())
173+
print(f"Dot check (Rank {rank}): {np.allclose(dot, dotloc)}")
174+
175+
# Norm
176+
norm = x.norm(ord=2, axis=1)
177+
normloc = np.linalg.norm(xloc[:, local_shape * subsize * (rank // subsize):local_shape * subsize * (rank // subsize + 1)],
178+
ord=2, axis=1)
179+
print(f"Norm check (Rank {rank}): {np.allclose(norm, normloc)}")
180+
181+
###############################################################################
182+
# And finally with 2d-arrays distributed over axis=0
183+
if rank == 0:
184+
print("2D masked arrays (over axis=0)")
185+
186+
# Create and fill the distributed array
187+
x = pylops_mpi.DistributedArray(global_shape=(global_shape, extra_dim_shape),
188+
partition=Partition.SCATTER,
189+
axis=0, mask=mask)
190+
x[:] = (MPI.COMM_WORLD.Get_rank() % subsize + 1.) * np.ones((local_shape, extra_dim_shape))
191+
xloc = x.asarray()
192+
193+
# Dot product
194+
dot = x.dot(x)
195+
dotloc = np.dot(xloc[local_shape * subsize * (rank // subsize):local_shape * subsize * (rank // subsize + 1)].ravel(),
196+
xloc[local_shape * subsize * (rank // subsize):local_shape * subsize * (rank // subsize + 1)].ravel())
197+
print(f"Dot check (Rank {rank}): {np.allclose(dot, dotloc)}")
198+
199+
# Norm
200+
norm = x.norm(ord=2, axis=0)
201+
normloc = np.linalg.norm(xloc[local_shape * subsize * (rank // subsize):local_shape * subsize * (rank // subsize + 1)],
202+
ord=2, axis=0)
203+
print(f"Norm check (Rank {rank}): {np.allclose(norm, normloc)}")

0 commit comments

Comments
 (0)