Coverage for pySDC/implementations/sweeper_classes/ParaDiagSweepers.py: 89%
71 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
1"""
2These sweepers are made for use with ParaDiag. They can be used to some degree with SDC as well, but unless you know what you are doing, you probably want another sweeper.
3"""
5from pySDC.implementations.sweeper_classes.generic_implicit import generic_implicit
6from pySDC.implementations.sweeper_classes.imex_1st_order import imex_1st_order
7import numpy as np
8import scipy.sparse as sp
11class QDiagonalization(generic_implicit):
12 """
13 Sweeper solving the collocation problem directly via diagonalization of Q. Mainly made for ParaDiag.
14 Can be reconfigured for use with SDC.
16 Note that the initial conditions for the collocation problem are generally stored in node zero in pySDC. However,
17 this sweeper is intended for ParaDiag, where a node-local residual is needed as a right hand side for this sweeper
18 rather than a step local one. Therefore, this sweeper has an option `ignore_ic`. If true, the value in node zero
19 will only be used in computing the step-local residual, but not in the solves. If false, the values on the nodes
20 will be ignored in the solves and the node-zero value will be used as initial conditions. When using this as a time-
21 parallel algorithm outside ParaDiag, you should set this parameter to false, which is not the default!
23 Similarly, in ParaDiag, the solution is in Fourier space right after the solve. It therefore makes little sense to
24 evaluate the right hand side directly after. By default, this is not done! Set `update_f_evals=True` in the
25 parameters if you want to use this sweeper in SDC. With `ignore_ic`, the solve gives only an increment, which is
26 added to the solution later, so there is nothing to evaluate the right hand side at and `update_f_evals` is ignored.
27 """
29 def __init__(self, params, level):
30 """
31 Initialization routine for the custom sweeper
33 Args:
34 params: parameters for the sweeper
35 level (pySDC.Level.level): the level that uses this sweeper
36 """
37 if 'G_inv' not in params.keys():
38 params['G_inv'] = np.eye(params['num_nodes'])
39 params['update_f_evals'] = params.get('update_f_evals', False)
40 params['ignore_ic'] = params.get('ignore_ic', True)
42 super().__init__(params, level)
44 self.set_G_inv(self.params.G_inv)
46 def set_G_inv(self, G_inv):
47 """
48 In ParaDiag, QG^{-1} is diagonalized. This function stores the G_inv matrix and computes and stores the diagonalization.
49 """
50 self.params.G_inv = G_inv
51 self.w, self.S, self.S_inv = self.computeDiagonalization(A=self.coll.Qmat[1:, 1:] @ self.params.G_inv)
53 @staticmethod
54 def computeDiagonalization(A):
55 """
56 Compute diagonalization of dense matrix A = S diag(w) S^-1
58 Args:
59 A (numpy.ndarray): dense matrix to diagonalize
61 Returns:
62 numpy.array: Diagonal entries of the diagonalized matrix w
63 numpy.ndarray: Matrix of eigenvectors S
64 numpy.ndarray: Inverse of S
65 """
66 w, S = np.linalg.eig(A)
67 S_inv = np.linalg.inv(S)
68 assert np.allclose(S @ np.diag(w) @ S_inv, A)
69 return w, S, S_inv
71 def mat_vec(self, mat, vec):
72 """
73 Compute matrix-vector multiplication. Vector can be list.
75 Args:
76 mat: Matrix
77 vec: Vector
79 Returns:
80 list: mat @ vec
81 """
82 assert mat.shape[1] == len(vec)
83 result = []
84 for m in range(mat.shape[0]):
85 result.append(self.level.prob.u_init)
86 for j in range(mat.shape[1]):
87 result[-1] += mat[m, j] * vec[j]
88 return result
90 def update_nodes(self):
91 """
92 Update the u- and f-values at the collocation nodes -> corresponds to a single sweep over all nodes
94 Returns:
95 None
96 """
98 L = self.level
99 P = L.prob
100 M = self.coll.num_nodes
102 if L.tau[0] is not None:
103 raise NotImplementedError('This sweeper does not work with multi-level SDC')
105 # perform local solves on the collocation nodes, can be parallelized!
106 if self.params.ignore_ic:
107 x1 = self.mat_vec(self.S_inv, [self.level.residual[m] for m in range(M)])
108 else:
109 x1 = self.mat_vec(self.S_inv, [self.level.u[0] for _ in range(M)])
111 # get averaged state over all nodes for constructing the Jacobian
112 u_avg = P.u_init
113 if not any(me is None for me in L.u_avg):
114 for m in range(M):
115 u_avg += L.u_avg[m] / M
117 x2 = []
118 for m in range(M):
119 x2.append(P.solve_jacobian(x1[m], self.w[m] * L.dt, u=u_avg, t=L.time + L.dt * self.coll.nodes[m]))
120 z = self.mat_vec(self.S, x2)
121 y = self.mat_vec(self.params.G_inv, z)
123 # update solution and evaluate right hand side
124 for m in range(M):
125 if self.params.ignore_ic:
126 L.increment[m] = y[m]
127 else:
128 L.u[m + 1] = y[m]
129 if self.params.update_f_evals:
130 L.f[m + 1] = P.eval_f(L.u[m + 1], L.time + L.dt * self.coll.nodes[m])
132 L.status.updated = True
133 return None
135 def eval_f_at_all_nodes(self):
136 """
137 Evaluate the right hand side at all collocation nodes, since the solve does not do this by default.
139 Returns:
140 None
141 """
142 L = self.level
143 P = self.level.prob
144 for m in range(self.coll.num_nodes):
145 L.f[m + 1] = P.eval_f(L.u[m + 1], L.time + L.dt * self.coll.nodes[m])
147 def get_residual(self):
148 """
149 This function computes and returns the "spatially extended" residual, not the norm of the residual!
151 Returns:
152 pySDC.datatype: Spatially extended residual
154 """
155 self.eval_f_at_all_nodes()
157 # start with integral dt*Q*F
158 residual = self.integrate()
160 # subtract u and add u0 to arrive at r = dt*Q*F - u + u0
161 for m in range(self.coll.num_nodes):
162 residual[m] -= self.level.u[m + 1]
163 residual[m] += self.level.u[0]
165 return residual
167 def compute_residual(self, *args, **kwargs):
168 """
169 Evaluate the right hand side at all nodes and then compute the residual as in the base class.
171 Args:
172 *args: passed on to the base class method, typically the stage (str)
173 **kwargs: passed on to the base class method
175 Returns:
176 None
177 """
178 self.eval_f_at_all_nodes()
179 return super().compute_residual(*args, **kwargs)
182class QDiagonalizationIMEX(QDiagonalization):
183 """
184 Variant of QDiagonalization for ParaDiag on problems whose right hand side has implicit and explicit parts.
185 Note that it will not work with SDC.
186 """
188 integrate = imex_1st_order.integrate