Note
Go to the end to download the full example code.
Stacked Array#
This example shows how to use the pylops_mpi.StackedDistributedArray
.
This class provides a way to combine and act on multiple pylops_mpi.DistributedArray
within the same program. This is very useful in scenarios where an array can be logically
divided in subarrays and each of them lends naturally to distribution across multiple processes in
a parallel computing environment.
from matplotlib import pyplot as plt
import numpy as np
from mpi4py import MPI
import pylops
import pylops_mpi
plt.close("all")
np.random.seed(42)
rank = MPI.COMM_WORLD.Get_rank()
size = MPI.COMM_WORLD.Get_size()
Let’s start by defining two distributed array
We combine them into a single
pylops_mpi.StackedDistributedArray
object.
arr1 = pylops_mpi.StackedDistributedArray([subarr1, subarr2])
if rank == 0:
print('Stacked array:', arr1)
# Extract and print full array
full_arr1 = arr1.asarray()
if rank == 0:
print('Full array:', full_arr1)
# Modify the part of the first array in rank0
if rank == 0:
arr1[0][:] = 10
full_arr1 = arr1.asarray()
if rank == 0:
print('Modified full array:', full_arr1)
Stacked array: <StackedDistributedArray with 2 distributed arrays:
<DistributedArray with global shape=(np.int64(10),), local shape=(10,), dtype=<class 'numpy.float64'>, processes=[0])>
<DistributedArray with global shape=(np.int64(4),), local shape=(4,), dtype=<class 'numpy.float64'>, processes=[0])>
Full array: [1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 2. 2. 2. 2.]
Modified full array: [10. 10. 10. 10. 10. 10. 10. 10. 10. 10. 2. 2. 2. 2.]
Let’s now create a second pylops_mpi.StackedDistributedArray
object
and perform different mathematical operations on those two objects.
subarr1_ = pylops_mpi.DistributedArray(global_shape=size * 10,
partition=pylops_mpi.Partition.SCATTER,
axis=0)
subarr2_ = pylops_mpi.DistributedArray(global_shape=size * 4,
partition=pylops_mpi.Partition.SCATTER,
axis=0)
# Filling the local arrays
subarr1_[:], subarr2_[:] = 5, 6
arr2 = pylops_mpi.StackedDistributedArray([subarr1_, subarr2_])
if rank == 0:
print('Stacked array 2:', arr2)
full_arr2 = arr2.asarray()
if rank == 0:
print('Full array2:', full_arr2)
Stacked array 2: <StackedDistributedArray with 2 distributed arrays:
<DistributedArray with global shape=(np.int64(10),), local shape=(10,), dtype=<class 'numpy.float64'>, processes=[0])>
<DistributedArray with global shape=(np.int64(4),), local shape=(4,), dtype=<class 'numpy.float64'>, processes=[0])>
Full array2: [5. 5. 5. 5. 5. 5. 5. 5. 5. 5. 6. 6. 6. 6.]
Negation
neg_arr = -arr1
full_neg_arr = neg_arr.asarray()
if rank == 0:
print('Negated full array:', full_neg_arr)
Negated full array: [-10. -10. -10. -10. -10. -10. -10. -10. -10. -10. -2. -2. -2. -2.]
Element-wise Addition
sum_arr = arr1 + arr2
full_sum_arr = sum_arr.asarray()
if rank == 0:
print('Summed full array:', full_sum_arr)
Summed full array: [15. 15. 15. 15. 15. 15. 15. 15. 15. 15. 8. 8. 8. 8.]
Element-wise Subtraction
sub_arr = arr1 - arr2
full_sub_arr = sub_arr.asarray()
if rank == 0:
print('Subtracted full array:', full_sub_arr)
Subtracted full array: [ 5. 5. 5. 5. 5. 5. 5. 5. 5. 5. -4. -4. -4. -4.]
Multiplication
mult_arr = arr1 * arr2
full_mult_arr = mult_arr.asarray()
if rank == 0:
print('Multipled full array:', full_mult_arr)
Multipled full array: [50. 50. 50. 50. 50. 50. 50. 50. 50. 50. 12. 12. 12. 12.]
Dot-product
Dot-product: 548.0
Dot-product (np): 548.0
Norms
l0norm = arr1.norm(0)
l1norm = arr1.norm(1)
l2norm = arr1.norm(2)
linfnorm = arr1.norm(np.inf)
if rank == 0:
print('L0 norm', l0norm, np.linalg.norm(full_arr1, 0))
print('L1 norm', l1norm, np.linalg.norm(full_arr1, 1))
print('L2 norm', l2norm, np.linalg.norm(full_arr1, 2))
print('Linf norm', linfnorm, np.linalg.norm(full_arr1, np.inf))
L0 norm 14.0 14.0
L1 norm 108.0 108.0
L2 norm 31.874754901018456 31.874754901018456
Linf norm 10.0 10.0
Now that we have a way to stack multiple pylops_mpi.StackedDistributedArray
objects,
let’s see how we can apply operators to them. More specifically this can be
done using the pylops_mpi.MPIStackedVStack
operator that takes multiple
pylops_mpi.MPILinearOperator
objects, each acting on one specific
distributed array
x = pylops_mpi.DistributedArray(global_shape=size * 10,
partition=pylops_mpi.Partition.SCATTER,
axis=0)
# Filling the local arrays
x[:] = 1.
# Make stacked operator
mop1 = pylops_mpi.MPIBlockDiag([pylops.MatrixMult(np.ones((5, 10))), ])
mop2 = pylops_mpi.MPIBlockDiag([pylops.MatrixMult(2 * np.ones((8, 10))), ])
mop = pylops_mpi.MPIStackedVStack([mop1, mop2])
y = mop.matvec(x)
y_arr = y.asarray()
xadj = mop.rmatvec(y)
xadj_arr = xadj.asarray()
if rank == 0:
print('StackedVStack y', y, y_arr, y_arr.shape)
print('StackedVStack xadj', xadj, xadj_arr, xadj_arr.shape)
StackedVStack y <StackedDistributedArray with 2 distributed arrays:
<DistributedArray with global shape=(np.int64(5),), local shape=(np.int64(5),), dtype=float64, processes=[0])>
<DistributedArray with global shape=(np.int64(8),), local shape=(np.int64(8),), dtype=float64, processes=[0])> [10. 10. 10. 10. 10. 20. 20. 20. 20. 20. 20. 20. 20.] (13,)
StackedVStack xadj <DistributedArray with global shape=(np.int64(10),), local shape=(np.int64(10),), dtype=float64, processes=[0])> [370. 370. 370. 370. 370. 370. 370. 370. 370. 370.] (10,)
Finally, let’s solve now an inverse problem using stacked arrays instead of distributed arrays
x0 = x.copy()
x0[:] = 0.
xinv = pylops_mpi.cgls(mop, y, x0=x0, niter=15, tol=1e-10, show=False)[0]
xinv_array = xinv.asarray()
if rank == 0:
print('xinv_array', xinv_array)
xinv_array [1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
Total running time of the script: (0 minutes 0.016 seconds)