Coverage for pySDC/helpers/firedrake_ensemble_communicator.py: 85%
53 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-17 13:41 +0000
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-17 13:41 +0000
1from mpi4py import MPI
2import firedrake as fd
3import numpy as np
6class _Requests:
7 """
8 The requests Firedrake returns for sending or receiving a function, one per subfunction, to be waited on together
9 like a single request.
10 """
12 def __init__(self, requests):
13 self.requests = requests
15 def Wait(self):
16 MPI.Request.Waitall(self.requests)
18 wait = Wait
21class FiredrakeEnsembleCommunicator:
22 """
23 Ensemble communicator for performing multiple similar distributed simulations with Firedrake, see https://www.firedrakeproject.org/firedrake/parallelism.html
24 This is intended to do space-time parallelism in pySDC.
25 This class wraps the time communicator. All requests that are not overloaded are passed to the time communicator. For instance, `ensemble.rank` will return the rank in the time communicator.
26 Some operations are overloaded to use the interface of the MPI communicator but handles communication with the ensemble communicator instead.
27 """
29 def __init__(self, comm, space_size):
30 """
31 Args:
32 comm (MPI.Intracomm): MPI communicator, which will be split into time and space communicators
33 space_size (int): Size of the spatial communicators
35 Attributes:
36 ensemble (firedrake.Ensemble): Ensemble communicator
37 """
38 self.ensemble = fd.Ensemble(comm, space_size)
39 self.comm_wold = comm
41 _owns_comm_wold = False # only communicators made by `Split` are freed by `Free`
43 def Split(self, *args, **kwargs):
44 """
45 Split the world communicator with mpi4py's `Split` and return a new `FiredrakeEnsembleCommunicator` on the
46 result, with the same spatial size.
47 """
48 split = FiredrakeEnsembleCommunicator(self.comm_wold.Split(*args, **kwargs), space_size=self.space_comm.size)
49 split._owns_comm_wold = True
50 return split
52 @property
53 def space_comm(self):
54 """The spatial communicator of the ensemble, `ensemble.comm`."""
55 return self.ensemble.comm
57 @property
58 def time_comm(self):
59 """The time communicator of the ensemble, `ensemble.ensemble_comm`."""
60 return self.ensemble.ensemble_comm
62 def __getattr__(self, name):
63 return getattr(self.time_comm, name)
65 def Reduce(self, sendbuf, recvbuf, op=MPI.SUM, root=0):
66 """
67 Wrap `Reduce` on the time communicator for numpy arrays and `Ensemble.reduce` for Firedrake functions, which
68 only takes `op=MPI.SUM` here.
69 """
70 if type(sendbuf) in [np.ndarray]:
71 self.ensemble.ensemble_comm.Reduce(sendbuf, recvbuf, op, root)
72 else:
73 assert op == MPI.SUM
74 self.ensemble.reduce(sendbuf, recvbuf, root=root)
76 def Allreduce(self, sendbuf, recvbuf, op=MPI.SUM):
77 """
78 Wrap `Allreduce` on the time communicator for numpy arrays and `Ensemble.allreduce` for Firedrake functions,
79 which only takes `op=MPI.SUM` here.
80 """
81 if type(sendbuf) in [np.ndarray]:
82 self.ensemble.ensemble_comm.Allreduce(sendbuf, recvbuf, op)
83 else:
84 assert op == MPI.SUM
85 self.ensemble.allreduce(sendbuf, recvbuf)
87 def Bcast(self, buf, root=0):
88 """Wrap `Bcast` on the time communicator for numpy arrays and `Ensemble.bcast` for Firedrake functions."""
89 if type(buf) in [np.ndarray]:
90 self.ensemble.ensemble_comm.Bcast(buf, root)
91 else:
92 self.ensemble.bcast(buf, root=root)
94 def Irecv(self, buf, source, tag=MPI.ANY_TAG):
95 """
96 Wrap `Irecv` on the time communicator for numpy arrays and lists, and `Ensemble.irecv` for Firedrake functions,
97 whose requests, one per subfunction, are returned to be waited on together.
98 """
99 if type(buf) in [np.ndarray, list]:
100 return self.ensemble.ensemble_comm.Irecv(buf=buf, source=source, tag=tag)
101 return _Requests(self.ensemble.irecv(buf, source, tag=tag))
103 def Isend(self, buf, dest, tag=0):
104 """
105 Wrap `Isend` on the time communicator for numpy arrays and lists, and `Ensemble.isend` for Firedrake functions,
106 whose requests, one per subfunction, are returned to be waited on together.
107 """
108 if type(buf) in [np.ndarray, list]:
109 return self.ensemble.ensemble_comm.Isend(buf=buf, dest=dest, tag=tag)
110 return _Requests(self.ensemble.isend(buf, dest, tag=tag))
112 def Free(self):
113 """
114 Free the world communicator if `Split` made it. The ensemble frees its own spatial and time communicators when
115 it is garbage collected.
116 """
117 if self._owns_comm_wold:
118 self.comm_wold.Free()
121def get_ensemble(comm, space_size):
122 """
123 Create a Firedrake ensemble.
125 Args:
126 comm (MPI.Intracomm): MPI communicator, which will be split into time and space communicators
127 space_size (int): Size of the spatial communicators
129 Returns:
130 firedrake.Ensemble: The ensemble
131 """
132 return fd.Ensemble(comm, space_size)