Coverage for pySDC/implementations/sweeper_classes/imex_1st_order.py: 100%
77 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
1import numpy as np
3from pySDC.core.sweeper import Sweeper
6class imex_1st_order(Sweeper):
7 """
8 SDC sweeper for right hand sides split into an implicitly and an explicitly treated part (IMEX-SDC).
10 First-order IMEX sweeper using implicit/explicit Euler as base integrator
12 Attributes:
13 QI: implicit Euler integration matrix
14 QE: explicit Euler integration matrix
15 """
17 def __init__(self, params, level):
18 """
19 Initialization routine for the custom sweeper
21 Args:
22 params: parameters for the sweeper
23 level (pySDC.Level.level): the level that uses this sweeper
24 """
26 if 'QI' not in params:
27 params['QI'] = 'IE'
28 if 'QE' not in params:
29 params['QE'] = 'EE'
31 super().__init__(params, level)
33 # IMEX integration matrices
34 self.QI = self.get_Qdelta_implicit(qd_type=self.params.QI)
35 self.QE = self.get_Qdelta_explicit(qd_type=self.params.QE)
37 def integrate(self):
38 """
39 Integrates the right-hand side (here impl + expl)
41 Returns:
42 list of dtype_u: containing the integral as values
43 """
45 L = self.level
46 P = L.prob
48 # sum the two parts once per node, not once per entry of Q
49 F = [L.f[j].impl + L.f[j].expl for j in range(1, self.coll.num_nodes + 1)]
51 me = []
52 # integrate RHS over all collocation nodes
53 for m in range(1, self.coll.num_nodes + 1):
54 me.append(P.dtype_u(P.init, val=0.0))
55 for j in range(1, self.coll.num_nodes + 1):
56 me[m - 1] += L.dt * self.coll.Qmat[m, j] * F[j - 1]
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 # the terms which are known already (from the previous iteration) are u0 + QF(u^k) - QIFI(u^k) - QEFE(u^k) + tau,
79 # built with Q - QI and Q - QE in one pass over the parts of F(u^k), kept here because the sweep replaces L.f[m]
80 FI = [L.f[j].impl for j in range(1, M + 1)]
81 FE = [L.f[j].expl for j in range(1, M + 1)]
82 diff_weights_impl = self.coll.Qmat - self.QI
83 diff_weights_expl = self.coll.Qmat - self.QE
85 # do the sweep
86 for m in range(0, M):
87 # build rhs, consisting of the known values and new values from previous nodes (at k+1)
88 rhs = P.dtype_u(L.u[0])
89 for j in range(1, M + 1):
90 rhs += L.dt * diff_weights_impl[m + 1, j] * FI[j - 1]
91 rhs += L.dt * diff_weights_expl[m + 1, j] * FE[j - 1]
92 if L.tau[m] is not None:
93 rhs += L.tau[m]
94 for j in range(1, m + 1):
95 rhs += L.dt * self.QI[m + 1, j] * L.f[j].impl
96 rhs += L.dt * self.QE[m + 1, j] * L.f[j].expl
98 # implicit solve with prefactor stemming from QI
99 L.u[m + 1] = P.solve_system(
100 rhs, L.dt * self.QI[m + 1, m + 1], L.u[m + 1], L.time + L.dt * self.coll.nodes[m]
101 )
103 # update function values
104 L.f[m + 1] = P.eval_f(L.u[m + 1], L.time + L.dt * self.coll.nodes[m])
106 # indicate presence of new values at this level
107 L.status.updated = True
109 return None
111 def compute_end_point(self):
112 """
113 Compute u at the right point of the interval
115 The value uend computed here is a full evaluation of the Picard formulation unless do_full_update==False
117 Returns:
118 None
119 """
121 # get current level and problem description
122 L = self.level
123 P = L.prob
125 # check if Mth node is equal to right point and do_coll_update is false, perform a simple copy
126 if self.coll.right_is_node and not self.params.do_coll_update:
127 # a copy is sufficient
128 L.uend = P.dtype_u(L.u[-1])
129 else:
130 # start with u0 and add integral over the full interval (using coll.weights)
131 L.uend = P.dtype_u(L.u[0])
132 for m in range(self.coll.num_nodes):
133 L.uend += L.dt * self.coll.weights[m] * (L.f[m + 1].impl + L.f[m + 1].expl)
134 # add up tau correction of the full interval (last entry)
135 if L.tau[-1] is not None:
136 L.uend += L.tau[-1]
138 return None
140 def get_sweeper_mats(self):
141 """
142 Returns the three matrices Q, QI, QE which define the sweeper.
144 The first row and column, corresponding to the left starting value, are removed to correspond to the notation
145 introduced in Ruprecht & Speck, Spectral deferred corrections with fast-wave slow-wave splitting, 2016
146 """
147 QE = self.QE[1:, 1:]
148 QI = self.QI[1:, 1:]
149 Q = self.coll.Qmat[1:, 1:]
150 return QE, QI, Q
152 def get_scalar_problems_sweeper_mats(self, lambdas=None):
153 """
154 This function returns the corresponding matrices of an IMEX-SDC sweep in matrix formulation.
156 See Ruprecht & Speck, Spectral deferred corrections with fast-wave slow-wave splitting, 2016 for the derivation.
158 Args:
159 lambdas (numpy.ndarray): the first entry in lambdas is lambda_fast, the second is lambda_slow.
160 """
161 QE, QI, Q = self.get_sweeper_mats()
162 if lambdas is None:
163 pass
164 # should use lambdas from attached problem and make sure it is a scalar IMEX
165 raise NotImplementedError("At the moment, the values for lambda have to be provided")
166 else:
167 lambda_fast = lambdas[0]
168 lambda_slow = lambdas[1]
169 nnodes = self.coll.num_nodes
170 dt = self.level.dt
171 LHS = np.eye(nnodes) - dt * (lambda_fast * QI + lambda_slow * QE)
172 RHS = dt * ((lambda_fast + lambda_slow) * Q - (lambda_fast * QI + lambda_slow * QE))
173 return LHS, RHS
175 def get_scalar_problems_manysweep_mat(self, nsweeps, lambdas=None):
176 """
177 For a scalar problem, K sweeps of IMEX-SDC can be written in matrix form.
179 Args:
180 nsweeps (int): number of sweeps
181 lambdas (numpy.ndarray): the first entry in lambdas is lambda_fast, the second is lambda_slow.
182 """
183 LHS, RHS = self.get_scalar_problems_sweeper_mats(lambdas=lambdas)
184 Pinv = np.linalg.inv(LHS)
185 Mat_sweep = np.linalg.matrix_power(Pinv.dot(RHS), nsweeps)
186 for k in range(0, nsweeps):
187 Mat_sweep += np.linalg.matrix_power(Pinv.dot(RHS), k).dot(Pinv)
188 return Mat_sweep