Source code for implementations.transfer_classes.BaseTransferDeltaMPI
r"""
Node-parallel counterpart of :mod:`pySDC.implementations.transfer_classes.BaseTransferDelta`.
Same two identities -- the coarse residual is the restricted fine residual, and the coarse-grid
correction is the sum of the sweep's own increments -- with every loop over collocation nodes
written as the reduction the node-parallel layout needs, since one rank holds one node.
One difference from the serial transfer: this one still builds the FAS :math:`\tau`, which the
delta-form hierarchy then never reads. Skipping it is the saving
:meth:`BaseTransferDelta.delta_transfer.restrict_state` takes, and it has not been written for the
node-parallel layout yet.
"""
from mpi4py import MPI
from pySDC.implementations.transfer_classes.BaseTransferMPI import base_transfer_MPI
[docs]
class delta_transfer_MPI(base_transfer_MPI):
"""
Node-parallel counterpart of
:class:`~pySDC.implementations.transfer_classes.BaseTransferDelta.delta_transfer`.
Same two identities -- the coarse residual is the restricted fine residual, and the coarse-grid
correction is the sum of the sweep's own increments -- with the collocation transfer written as
a reduction, since each rank holds one node.
"""
[docs]
def restrict(self):
"""
Restrict as usual, then hand the coarse level the restricted fine residual.
Returns
-------
None
"""
SF, SG = self.fine.sweep, self.coarse.sweep
SF._delta_setup()
SG._delta_setup()
eps_F = SF._residual_nodes()[0]
super().restrict()
CF, CG, PG = self.comm_fine, self.comm_coarse, self.coarse.prob
tmp = self.space_transfer.restrict(eps_F)
received = PG.u_init
for n in range(SG.coll.num_nodes):
CF.Reduce(self.Rcoll[n, CF.rank] * tmp, received if n == CG.rank else None, root=n, op=MPI.SUM)
SG.eps_in = [SG._to_work(PG, received)]
SG.delta_acc = None
self.coarse.u0_reference = PG.dtype_u(self.coarse.u[0])
return None
[docs]
def prolong(self):
"""
Add the coarse level's accumulated correction to the fine level.
Returns
-------
None
"""
if self.coarse.sweep.delta_acc is None:
return super().prolong()
F, PF = self.fine, self.fine.prob
SF, CF, CG = self.fine.sweep, self.comm_fine, self.comm_coarse
SF._delta_setup()
tmp = self.space_transfer.prolong(self.coarse.sweep.delta_acc[0])
correction = PF.u_init
for n in range(SF.coll.num_nodes):
CG.Reduce(self.Pcoll[n, CG.rank] * tmp, correction if n == CF.rank else None, root=n, op=MPI.SUM)
# quantised, then back to backend units before it is applied -- see the serial transfer
applied = SF._to_backend(PF, SF._to_work(PF, correction))
correction = SF._to_work(PF, applied)
rank = CF.rank
t_node = F.time + F.dt * SF.coll.nodes[rank]
u_old, f_old = PF.dtype_u(F.u[rank + 1]), PF.dtype_f(F.f[rank + 1])
F.u[rank + 1] += applied
F.f[rank + 1] = PF.eval_f(F.u[rank + 1], t_node)
if SF.eps_in is not None:
# records into SF._dfs, which is what advance_residual reads
SF._f_increment(PF, F.f[rank + 1], f_old, u_old, applied, t_node)
SF.eps_in = SF.advance_residual(SF.eps_in, [correction], SF._dfs)
SF.accumulate([correction])
return None