Coverage for pySDC/implementations/sweeper_classes/multi_implicit.py: 87%
60 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 pySDC.core.sweeper import Sweeper
4class multi_implicit(Sweeper):
5 """
6 SDC sweeper for right hand sides with two parts, each treated implicitly with its own solver and preconditioner.
8 First-order multi-implicit sweeper for two components
10 Attributes:
11 Q1: implicit integration matrix for the first component
12 Q2: implicit integration matrix for the second component
13 """
15 def __init__(self, params, level):
16 """
17 Initialization routine for the custom sweeper
19 Args:
20 params: parameters for the sweeper
21 level (pySDC.Level.level): the level that uses this sweeper
22 """
24 # Default choice: implicit Euler
25 if 'Q1' not in params:
26 params['Q1'] = 'IE'
27 if 'Q2' not in params:
28 params['Q2'] = 'IE'
30 # call parent's initialization routine
31 super(multi_implicit, self).__init__(params, level)
33 # Integration matrices
34 self.Q1 = self.get_Qdelta_implicit(qd_type=self.params.Q1)
35 self.Q2 = self.get_Qdelta_implicit(qd_type=self.params.Q2)
37 def integrate(self):
38 """
39 Integrates the right-hand side (two components)
41 Returns:
42 list of dtype_u: containing the integral as values
43 """
45 # get current level and problem description
46 L = self.level
47 P = L.prob
48 M = self.coll.num_nodes
50 # sum the components once per node, not once per entry of Q
51 F = [L.f[j].comp1 + L.f[j].comp2 for j in range(1, M + 1)]
53 me = []
55 # integrate RHS over all collocation nodes
56 for m in range(1, M + 1):
57 # new instance of dtype_u, initialize values with 0
58 me.append(P.dtype_u(P.init, val=0.0))
59 for j in range(1, M + 1):
60 me[-1] += L.dt * self.coll.Qmat[m, j] * F[j - 1]
62 return me
64 def update_nodes(self):
65 """
66 Update the u- and f-values at the collocation nodes -> corresponds to a single sweep over all nodes
68 Returns:
69 None
70 """
72 # get current level and problem description
73 L = self.level
74 P = L.prob
76 # only if the level has been touched before
77 assert L.status.unlocked
79 # get number of collocation nodes for easier access
80 M = self.coll.num_nodes
81 Q, Q1, Q2 = self.coll.Qmat, self.Q1, self.Q2
83 # the components of F(u^k), taken once: the sweep replaces L.f[m], so these stay the old values
84 F1 = [L.f[j].comp1 for j in range(1, M + 1)]
85 F2 = [L.f[j].comp2 for j in range(1, M + 1)]
87 # do the sweep
88 for m in range(1, M + 1):
89 t = L.time + L.dt * self.coll.nodes[m - 1]
91 # u0 + QF(u^k) - Q1F1(u^k) + tau, plus Q1F1(u^k+1) from the nodes already updated
92 rhs = P.dtype_u(L.u[0])
93 for j in range(1, M + 1):
94 rhs += L.dt * (Q[m, j] - Q1[m, j]) * F1[j - 1]
95 rhs += L.dt * Q[m, j] * F2[j - 1]
96 if L.tau[m - 1] is not None:
97 rhs += L.tau[m - 1]
98 for j in range(1, m):
99 rhs += L.dt * Q1[m, j] * L.f[j].comp1
101 # implicit solve with prefactor stemming from Q1
102 L.u[m] = P.solve_system_1(rhs, L.dt * Q1[m, m], L.u[m], t)
104 # substract Q2F2(u^k) and add Q2F2(u^k+1)
105 rhs = P.dtype_u(L.u[m])
106 for j in range(1, M + 1):
107 rhs -= L.dt * Q2[m, j] * F2[j - 1]
108 for j in range(1, m):
109 rhs += L.dt * Q2[m, j] * L.f[j].comp2
111 L.u[m] = P.solve_system_2(rhs, L.dt * Q2[m, m], L.u[m], t) # TODO: is L.u[m] a good guess?
113 # update function values
114 L.f[m] = P.eval_f(L.u[m], t)
116 # indicate presence of new values at this level
117 L.status.updated = True
119 return None
121 def compute_end_point(self):
122 """
123 Compute u at the right point of the interval
125 The value uend computed here is a full evaluation of the Picard formulation unless do_full_update==False
127 Returns:
128 None
129 """
131 # get current level and problem description
132 L = self.level
133 P = L.prob
135 # check if Mth node is equal to right point and do_coll_update is false, perform a simple copy
136 if self.coll.right_is_node and not self.params.do_coll_update:
137 # a copy is sufficient
138 L.uend = P.dtype_u(L.u[-1])
139 else:
140 # start with u0 and add integral over the full interval (using coll.weights)
141 L.uend = P.dtype_u(L.u[0])
142 for m in range(self.coll.num_nodes):
143 L.uend += L.dt * self.coll.weights[m] * (L.f[m + 1].comp1 + L.f[m + 1].comp2)
144 # add up tau correction of the full interval (last entry)
145 if L.tau[-1] is not None:
146 L.uend += L.tau[-1]
148 return None