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
« 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
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.
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 """
16 def restrict(self):
17 """
18 Space-time restriction routine
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 """
26 # get data for easier access
27 F = self.fine
28 G = self.coarse
30 PG = G.prob
31 PF = F.prob
33 SF = F.sweep
34 SG = G.sweep
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')
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]))
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]
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])
57 # build coarse level tau correction part
58 tauG = G.sweep.integrate()
60 for m in range(SG.coll.num_nodes):
61 tauG[m] = PG.apply_mass_matrix(G.u[m + 1]) - tauG[m]
63 # build fine level tau correction part
64 tauF = F.sweep.integrate()
66 for m in range(SF.coll.num_nodes):
67 tauF[m] = PF.apply_mass_matrix(F.u[m + 1]) - tauF[m]
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]))
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]
81 # build tau correction
82 for m in range(SG.coll.num_nodes):
83 G.tau[m] = tauG[m] - tauFG[m]
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]))
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
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])
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])
113 # works as a predictor
114 G.status.unlocked = True
116 return None
118 def prolong(self):
119 """
120 Space-time prolongation routine
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 """
126 # get data for easier access
127 F = self.fine
128 G = self.coarse
130 PF = F.prob
132 SF = F.sweep
133 SG = G.sweep
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')
139 # build coarse correction
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]))
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]
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])
157 return None
159 def prolong_f(self):
160 """
161 Space-time prolongation routine w.r.t. the rhs f
163 Not available for the mass formulation, so this falls back to prolong().
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
176 return self.prolong()