Coverage for pySDC/projects/DAE/sweepers/fullyImplicitDAEMPI.py: 97%

58 statements  

« prev     ^ index     » next       coverage.py v7.6.7, created at 2024-11-16 14:51 +0000

1from mpi4py import MPI 

2 

3from pySDC.core.errors import ParameterError 

4from pySDC.implementations.sweeper_classes.generic_implicit_MPI import SweeperMPI, generic_implicit_MPI 

5from pySDC.projects.DAE.sweepers.fullyImplicitDAE import FullyImplicitDAE 

6 

7 

8class SweeperDAEMPI(SweeperMPI): 

9 r""" 

10 MPI base class for DAE sweepers where each rank administers one collocation node. 

11 

12 This class provides all the stuff to be done in parallel during the simulation. When implementing a new MPI-DAE sweeper multiple inheritance is used here 

13 

14 >>> class FullyImplicitDAEMPI(SweeperDAEMPI, generic_implicit_MPI, FullyImplicitDAE): 

15 

16 Be careful with ordering of classes where the child class should inherit from! Due to multiple inheritance several methods are overwritten here. In the example above (which is the class below) the class first inherits the methods 

17 

18 - compute_residual() 

19 - predict() 

20 

21 from ``SweeperDAEMPI``. ``compute_end_point()`` is inherited from ``SweeperMPI``. From second inherited class ``generic_implicit_MPI`` the methods 

22 

23 - integrate() 

24 - update_nodes() 

25 

26 are inherited, which then overwritten by the child class. 

27 

28 Parameters 

29 ---------- 

30 params : dict 

31 Parameters passed to the sweeper. 

32 """ 

33 

34 def compute_residual(self, stage=None): 

35 r""" 

36 Uses the absolute value of the DAE system 

37 

38 .. math:: 

39 ||F(t, u, u')|| 

40 

41 for computing the residual in a chosen norm. If norm computes residual by using all values on collocation nodes, result is broadcasted to all processes; when norm only uses the value on last node, the result is collected on last process! 

42 

43 Parameters 

44 ---------- 

45 stage : str, optional 

46 The current stage of the step the level belongs to. 

47 """ 

48 

49 L = self.level 

50 P = L.prob 

51 

52 # Check if we want to skip the residual computation to gain performance 

53 # Keep in mind that skipping any residual computation is likely to give incorrect outputs of the residual! 

54 if stage in self.params.skip_residual_computation: 

55 L.status.residual = 0.0 if L.status.residual is None else L.status.residual 

56 return None 

57 

58 res = P.eval_f(L.u[self.rank + 1], L.f[self.rank + 1], L.time + L.dt * self.coll.nodes[self.rank]) 

59 res_norm = abs(res) # use abs function from data type here 

60 

61 # find maximal residual over the nodes 

62 if L.params.residual_type == 'full_abs': 

63 L.status.residual = self.comm.allreduce(res_norm, op=MPI.MAX) 

64 elif L.params.residual_type == 'last_abs': 

65 L.status.residual = self.comm.bcast(res_norm, root=self.comm.size - 1) 

66 elif L.params.residual_type == 'full_rel': 

67 L.status.residual = self.comm.allreduce(res_norm / abs(L.u[0]), op=MPI.MAX) 

68 elif L.params.residual_type == 'last_rel': 

69 L.status.residual = self.comm.bcast(res_norm / abs(L.u[0]), root=self.comm.size - 1) 

70 else: 

71 raise NotImplementedError(f'residual type \"{L.params.residual_type}\" not implemented!') 

72 

73 # indicate that the residual has seen the new values 

74 L.status.updated = False 

75 

76 return None 

77 

78 def predict(self): 

79 r""" 

80 Predictor to fill values at nodes before first sweep. It can decide whether the 

81 

82 - initial condition is spread to each node ('initial_guess' = 'spread'), 

83 - or zero values are spread to each node ('initial_guess' = 'zero'). 

84 

85 Default prediction for sweepers, only copies values to all collocation nodes. This function 

86 overrides the base implementation by always initialising ``level.f`` to zero. This is necessary since 

87 ``level.f`` stores the solution derivative in the fully implicit case, which is not initially known. 

88 """ 

89 

90 L = self.level 

91 P = L.prob 

92 

93 # set initial guess for gradient to zero 

94 L.f[0] = P.dtype_f(init=P.init, val=0.0) 

95 

96 if self.params.initial_guess == 'spread': 

97 L.u[self.rank + 1] = P.dtype_u(L.u[0]) 

98 L.f[self.rank + 1] = P.dtype_f(init=P.init, val=0.0) 

99 elif self.params.initial_guess == 'zero': 

100 L.u[self.rank + 1] = P.dtype_u(init=P.init, val=0.0) 

101 L.f[self.rank + 1] = P.dtype_f(init=P.init, val=0.0) 

102 else: 

103 raise ParameterError(f'initial_guess option {self.params.initial_guess} not implemented') 

104 

105 # indicate that this level is now ready for sweeps 

106 L.status.unlocked = True 

107 L.status.updated = True 

108 

109 

110class FullyImplicitDAEMPI(SweeperDAEMPI, generic_implicit_MPI, FullyImplicitDAE): 

111 r""" 

112 Custom sweeper class to implement the fully-implicit SDC parallelized across the nodes for solving fully-implicit DAE problems of the form 

113 

114 .. math:: 

115 F(t, u, u') = 0. 

116 

117 More detailed description can be found in ``fullyImplicitDAE.py``. 

118 

119 This sweeper uses diagonal matrices :math:`\mathbf{Q}_\Delta` to 

120 solve the implicit system on each node in parallel. First diagonal matrices were first developed and applied in [1]_. Years later intensive theory in [2]_ is developed to that topic. Ideas of parallelizing SDC across the method are basically applied for the DAE case here. 

121 

122 Note 

123 ---- 

124 Please supply a communicator as `comm` to the parameters! The number of processes used to parallelize needs to be equal to the number of collocation nodes used. For example, three collocation nodes are used and passed to the sweeper via 

125 

126 >>> sweeper_params = {'num_nodes': 3} 

127 

128 running a script ``example_mpi.py`` using ``mpi4py`` that is used to enable parallelism have to be done by using the command 

129 

130 >>> mpiexec -n 3 python3 example.py 

131 

132 Reference 

133 --------- 

134 .. [1] R. Speck. Parallelizing spectral deferred corrections across the method. Comput. Vis. Sci. 19, No. 3-4, 75-83 (2018). 

135 .. [2] G. Čaklović, T. Lunet, S. Götschel, D. Ruprecht. Improving Efficiency of Parallel Across the Method Spectral Deferred Corrections. Preprint, arXiv:2403.18641 [math.NA] (2024). 

136 """ 

137 

138 def integrate(self, last_only=False): 

139 r""" 

140 Integrates the gradient. ``me`` serves as buffer, and root process ``m`` stores 

141 the result of integral at node :math:`\tau_m` there. 

142 

143 Parameters 

144 ---------- 

145 last_only : bool, optional 

146 Integrate only the last node for the residual or all of them. 

147 

148 Returns 

149 ------- 

150 me : list of dtype_u 

151 Containing the integral as values. 

152 """ 

153 

154 L = self.level 

155 P = L.prob 

156 

157 me = P.dtype_u(P.init, val=0.0) 

158 for m in [self.coll.num_nodes - 1] if last_only else range(self.coll.num_nodes): 

159 integral = L.dt * self.coll.Qmat[m + 1, self.rank + 1] * L.f[self.rank + 1][:] 

160 recvBuf = me[:] if m == self.rank else None 

161 self.comm.Reduce(integral, recvBuf, root=m, op=MPI.SUM) 

162 

163 return me 

164 

165 def update_nodes(self): 

166 r""" 

167 Updates values of ``u`` and ``f`` at collocation nodes. This correspond to a single iteration of the 

168 preconditioned Richardson iteration in **"ordinary"** SDC. 

169 """ 

170 

171 L = self.level 

172 P = L.prob 

173 

174 # only if the level has been touched before 

175 assert L.status.unlocked 

176 

177 integral = self.integrate() 

178 integral -= L.dt * self.QI[self.rank + 1, self.rank + 1] * L.f[self.rank + 1] 

179 integral += L.u[0] 

180 

181 u_approx = P.dtype_u(integral) 

182 

183 L.f[self.rank + 1][:] = P.solve_system( 

184 FullyImplicitDAE.F, 

185 u_approx, 

186 L.dt * self.QI[self.rank + 1, self.rank + 1], 

187 L.f[self.rank + 1], 

188 L.time + L.dt * self.coll.nodes[self.rank], 

189 ) 

190 

191 integral = self.integrate() 

192 L.u[self.rank + 1] = L.u[0] + integral 

193 # indicate presence of new values at this level 

194 L.status.updated = True 

195 

196 return None