Coverage for pySDC/implementations/sweeper_classes/multi_implicit.py: 86%
59 statements
« prev ^ index » next coverage.py v7.10.6, created at 2025-09-04 15:08 +0000
« prev ^ index » next coverage.py v7.10.6, created at 2025-09-04 15:08 +0000
1from pySDC.core.sweeper import Sweeper
4class multi_implicit(Sweeper):
5 """
6 Custom sweeper class, implements Sweeper.py
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
49 me = []
51 # integrate RHS over all collocation nodes
52 for m in range(1, self.coll.num_nodes + 1):
53 # new instance of dtype_u, initialize values with 0
54 me.append(P.dtype_u(P.init, val=0.0))
55 for j in range(1, self.coll.num_nodes + 1):
56 me[-1] += L.dt * self.coll.Qmat[m, j] * (L.f[j].comp1 + L.f[j].comp2)
58 return me
60 def update_nodes(self):
61 """
62 Update the u- and f-values at the collocation nodes -> corresponds to a single sweep over all nodes
64 Returns:
65 None
66 """
68 # get current level and problem description
69 L = self.level
70 P = L.prob
72 # only if the level has been touched before
73 assert L.status.unlocked
75 # get number of collocation nodes for easier access
76 M = self.coll.num_nodes
78 # gather all terms which are known already (e.g. from the previous iteration)
80 # get QF(u^k)
81 integral = self.integrate()
82 for m in range(M):
83 # subtract Q1F1(u^k)_m
84 for j in range(1, M + 1):
85 integral[m] -= L.dt * self.Q1[m + 1, j] * L.f[j].comp1
86 # add initial value
87 integral[m] += L.u[0]
88 # add tau if associated
89 if L.tau[m] is not None:
90 integral[m] += L.tau[m]
92 # store Q2F2(u^k) for later usage
93 Q2int = []
94 for m in range(M):
95 Q2int.append(P.dtype_u(P.init, val=0.0))
96 for j in range(1, M + 1):
97 Q2int[-1] += L.dt * self.Q2[m + 1, j] * L.f[j].comp2
99 # do the sweep
100 for m in range(0, M):
101 # build rhs, consisting of the known values from above and new values from previous nodes (at k+1)
102 rhs = P.dtype_u(integral[m])
103 for j in range(1, m + 1):
104 rhs += L.dt * self.Q1[m + 1, j] * L.f[j].comp1
106 # implicit solve with prefactor stemming from Q1
107 L.u[m + 1] = P.solve_system_1(
108 rhs, L.dt * self.Q1[m + 1, m + 1], L.u[m + 1], L.time + L.dt * self.coll.nodes[m]
109 )
111 # substract Q2F2(u^k) and add Q2F(u^k+1)
112 rhs = L.u[m + 1] - Q2int[m]
113 for j in range(1, m + 1):
114 rhs += L.dt * self.Q2[m + 1, j] * L.f[j].comp2
116 L.u[m + 1] = P.solve_system_2(
117 rhs,
118 L.dt * self.Q2[m + 1, m + 1],
119 L.u[m + 1], # TODO: is this a good guess?
120 L.time + L.dt * self.coll.nodes[m],
121 )
123 # update function values
124 L.f[m + 1] = P.eval_f(L.u[m + 1], L.time + L.dt * self.coll.nodes[m])
126 # indicate presence of new values at this level
127 L.status.updated = True
129 return None
131 def compute_end_point(self):
132 """
133 Compute u at the right point of the interval
135 The value uend computed here is a full evaluation of the Picard formulation unless do_full_update==False
137 Returns:
138 None
139 """
141 # get current level and problem description
142 L = self.level
143 P = L.prob
145 # check if Mth node is equal to right point and do_coll_update is false, perform a simple copy
146 if self.coll.right_is_node and not self.params.do_coll_update:
147 # a copy is sufficient
148 L.uend = P.dtype_u(L.u[-1])
149 else:
150 # start with u0 and add integral over the full interval (using coll.weights)
151 L.uend = P.dtype_u(L.u[0])
152 for m in range(self.coll.num_nodes):
153 L.uend += L.dt * self.coll.weights[m] * (L.f[m + 1].comp1 + L.f[m + 1].comp2)
154 # add up tau correction of the full interval (last entry)
155 if L.tau[-1] is not None:
156 L.uend += L.tau[-1]
158 return None