Coverage for pySDC/implementations/transfer_classes/BaseTransfer_mass.py: 95%

75 statements  

« prev     ^ index     » next       coverage.py v7.17.0, created at 2026-11-02 12:30 +0000

1from pySDC.core.base_transfer import BaseTransfer 

2from pySDC.core.errors import UnlockError 

3 

4 

5class base_transfer_mass(BaseTransfer): 

6 """ 

7 Space-time transfer for problems with a mass matrix, which enters the FAS correction and the restricted u0. 

8 

9 Attributes: 

10 logger: custom logger for sweeper-related logging 

11 params(__Pars): parameter object containing the custom parameters passed by the user 

12 fine (pySDC.Level.level): reference to the fine level 

13 coarse (pySDC.Level.level): reference to the coarse level 

14 """ 

15 

16 def restrict(self): 

17 """ 

18 Space-time restriction routine 

19 

20 The routine applies the spatial restriction operator to teh fine values on the fine nodes, then reevaluates f 

21 on the coarse level. This is used for the first part of the FAS correction tau via integration. The second part 

22 is the integral over the fine values, restricted to the coarse level. Finally, possible tau corrections on the 

23 fine level are restricted as well. 

24 """ 

25 

26 # get data for easier access 

27 F = self.fine 

28 G = self.coarse 

29 

30 PG = G.prob 

31 PF = F.prob 

32 

33 SF = F.sweep 

34 SG = G.sweep 

35 

36 # only if the level is unlocked at least by prediction 

37 if not F.status.unlocked: 

38 raise UnlockError('fine level is still locked, cannot use data from there') 

39 

40 # restrict fine values in space 

41 tmp_u = [] 

42 for m in range(1, SF.coll.num_nodes + 1): 

43 tmp_u.append(self.space_transfer.project(F.u[m])) 

44 

45 # restrict collocation values 

46 G.u[0] = self.space_transfer.project(F.u[0]) 

47 for n in range(1, SG.coll.num_nodes + 1): 

48 G.u[n] = self.Rcoll[n - 1, 0] * tmp_u[0] 

49 for m in range(1, SF.coll.num_nodes): 

50 G.u[n] += self.Rcoll[n - 1, m] * tmp_u[m] 

51 

52 # re-evaluate f on coarse level 

53 G.f[0] = PG.eval_f(G.u[0], G.time) 

54 for m in range(1, SG.coll.num_nodes + 1): 

55 G.f[m] = PG.eval_f(G.u[m], G.time + G.dt * SG.coll.nodes[m - 1]) 

56 

57 # build coarse level tau correction part 

58 tauG = G.sweep.integrate() 

59 

60 for m in range(SG.coll.num_nodes): 

61 tauG[m] = PG.apply_mass_matrix(G.u[m + 1]) - tauG[m] 

62 

63 # build fine level tau correction part 

64 tauF = F.sweep.integrate() 

65 

66 for m in range(SF.coll.num_nodes): 

67 tauF[m] = PF.apply_mass_matrix(F.u[m + 1]) - tauF[m] 

68 

69 # restrict fine level tau correction part in space 

70 tmp_tau = [] 

71 for m in range(SF.coll.num_nodes): 

72 tmp_tau.append(self.space_transfer.restrict_dual(tauF[m])) 

73 

74 # restrict fine level tau correction part in collocation 

75 tauFG = [] 

76 for n in range(1, SG.coll.num_nodes + 1): 

77 tauFG.append(self.Rcoll[n - 1, 0] * tmp_tau[0]) 

78 for m in range(1, SF.coll.num_nodes): 

79 tauFG[-1] += self.Rcoll[n - 1, m] * tmp_tau[m] 

80 

81 # build tau correction 

82 for m in range(SG.coll.num_nodes): 

83 G.tau[m] = tauG[m] - tauFG[m] 

84 

85 if F.tau[0] is not None: 

86 # restrict possible tau correction from fine in space 

87 tmp_tau = [] 

88 for m in range(SF.coll.num_nodes): 

89 tmp_tau.append(self.space_transfer.restrict_dual(F.tau[m])) 

90 

91 # restrict possible tau correction from fine in collocation 

92 for n in range(SG.coll.num_nodes): 

93 for m in range(SF.coll.num_nodes): 

94 G.tau[n] += self.Rcoll[n, m] * tmp_tau[m] 

95 else: 

96 pass 

97 

98 # save u and rhs evaluations for interpolation 

99 for m in range(1, SG.coll.num_nodes + 1): 

100 G.uold[m] = PG.dtype_u(G.u[m]) 

101 G.fold[m] = PG.dtype_f(G.f[m]) 

102 PG.use_restricted_boundary_values(G.u[1:], [G.time + G.dt * tau for tau in SG.coll.nodes]) 

103 

104 # u0 lives in the DUAL space on every coarse level: the finest level supplies it as M u0, 

105 # and each further level restricts that dual vector with P^T. Without the else branch, 

106 # level 1 -> 2 kept the primal `project` result from above and L2-projected an already-dual 

107 # vector, which is why 2 levels worked and 3 did not. 

108 if F.level_index == 0: 

109 G.u[0] = self.space_transfer.restrict_dual(PF.apply_mass_matrix(F.u[0])) 

110 else: 

111 G.u[0] = self.space_transfer.restrict_dual(F.u[0]) 

112 

113 # works as a predictor 

114 G.status.unlocked = True 

115 

116 return None 

117 

118 def prolong(self): 

119 """ 

120 Space-time prolongation routine 

121 

122 This routine applies the spatial prolongation routine to the difference between the computed and the restricted 

123 values on the coarse level and then adds this difference to the fine values as coarse correction. 

124 """ 

125 

126 # get data for easier access 

127 F = self.fine 

128 G = self.coarse 

129 

130 PF = F.prob 

131 

132 SF = F.sweep 

133 SG = G.sweep 

134 

135 # only of the level is unlocked at least by prediction or restriction 

136 if not G.status.unlocked: 

137 raise UnlockError('coarse level is still locked, cannot use data from there') 

138 

139 # build coarse correction 

140 

141 # interpolate values in space first 

142 tmp_u = [] 

143 for m in range(1, SG.coll.num_nodes + 1): 

144 tmp_u.append(self.space_transfer.prolong(G.u[m] - G.uold[m])) 

145 

146 # interpolate values in collocation 

147 # F.u[0] += tmp_u[0] 

148 for n in range(1, SF.coll.num_nodes + 1): 

149 for m in range(SG.coll.num_nodes): 

150 F.u[n] += self.Pcoll[n - 1, m] * tmp_u[m] 

151 

152 # re-evaluate f on fine level 

153 # F.f[0] = PF.eval_f(F.u[0], F.time) 

154 for m in range(1, SF.coll.num_nodes + 1): 

155 F.f[m] = PF.eval_f(F.u[m], F.time + F.dt * SF.coll.nodes[m - 1]) 

156 

157 return None 

158 

159 def prolong_f(self): 

160 """ 

161 Space-time prolongation routine w.r.t. the rhs f 

162 

163 Not available for the mass formulation, so this falls back to prolong(). 

164 

165 Under the mass formulation eval_f returns a load vector (f.impl = K u, f.expl = M g), which 

166 cannot be interpolated -- doing so reads it as a nodal function and makes the coarse 

167 correction actively wrong. The dimensionally correct dual transfer M_f P M_c^-1 restores the 

168 right answer but needs a coarse mass solve per node and still costs about seven times the 

169 iterations of simply re-evaluating f (measured on step_7's heat problem: 14.40 against 2.00 

170 at restol 5e-10). Re-evaluating is cheaper and more accurate here, so finter is ignored. 

171 """ 

172 if not getattr(self, '_finter_warned', False): 

173 self.logger.warning('finter is not supported for the mass formulation, re-evaluating f instead') 

174 self._finter_warned = True 

175 

176 return self.prolong()