Coverage for pySDC/projects/matrixPFASST/controller_matrix_nonMPI.py: 98%
183 statements
« prev ^ index » next coverage.py v7.6.9, created at 2024-12-20 14:51 +0000
« prev ^ index » next coverage.py v7.6.9, created at 2024-12-20 14:51 +0000
1import numpy as np
4from pySDC.implementations.controller_classes.controller_nonMPI import controller_nonMPI
5from pySDC.implementations.sweeper_classes.generic_implicit import generic_implicit
8class controller_matrix_nonMPI(controller_nonMPI):
9 """
11 PFASST controller, running serial matrix-based versions
13 """
15 def __init__(self, num_procs, controller_params, description):
16 """
17 Initialization routine for PFASST controller
19 Args:
20 num_procs: number of parallel time steps (still serial, though), can be 1
21 controller_params: parameter set for the controller and the steps
22 description: all the parameters to set up the rest (levels, problems, transfer, ...)
23 """
25 assert description['sweeper_class'] is generic_implicit, (
26 'ERROR: matrix version will only work with generic_implicit sweeper, got %s' % description['sweeper_class']
27 )
29 # call parent's initialization routine
30 super(controller_matrix_nonMPI, self).__init__(
31 num_procs=num_procs, controller_params=controller_params, description=description
32 )
34 self.nsteps = len(self.MS)
35 self.nlevels = len(self.MS[0].levels)
36 self.nnodes = self.MS[0].levels[0].sweep.coll.num_nodes
37 self.nspace = self.MS[0].levels[0].prob.init[0]
39 self.dt = self.MS[0].levels[0].dt
40 self.tol = self.MS[0].levels[0].params.restol
41 self.maxiter = self.MS[0].params.maxiter
43 prob = self.MS[0].levels[0].prob
45 assert isinstance(self.nspace, int), 'ERROR: can only handle 1D data, got %s' % self.nspace
46 assert [level.sweep.coll.right_is_node for step in self.MS for level in step.levels].count(
47 True
48 ) == self.nlevels * self.nsteps, 'ERROR: all collocation nodes have to be of Gauss-Radau type'
49 assert self.nlevels <= 2, 'ERROR: cannot use matrix-PFASST with more than 2 levels' # TODO: fixme
50 assert [level.dt for step in self.MS for level in step.levels].count(
51 self.dt
52 ) == self.nlevels * self.nsteps, 'ERROR: dt must be equal for all steps and all levels'
53 # assert [level.sweep.coll.num_nodes for step in self.MS for level in step.levels].count(self.nnodes) == \
54 # self.nlevels * self.nsteps, 'ERROR: nnodes must be equal for all steps and all levels'
55 assert [type(level.prob) for step in self.MS for level in step.levels].count(
56 type(prob)
57 ) == self.nlevels * self.nsteps, 'ERROR: all probem classes have to be the same'
59 assert self.params.predict_type is None, 'ERROR: no predictor for matrix controller yet' # TODO: fixme
61 assert hasattr(prob, 'A'), 'ERROR: need system matrix A for this (and linear problems!)'
63 A = prob.A.todense()
64 Q = self.MS[0].levels[0].sweep.coll.Qmat[1:, 1:]
65 Qd = self.MS[0].levels[0].sweep.QI[1:, 1:]
67 E = np.zeros((self.nsteps, self.nsteps))
68 np.fill_diagonal(E[1:, :], 1)
70 N = np.zeros((self.nnodes, self.nnodes))
71 N[:, -1] = 1
73 self.C = (
74 np.eye(self.nsteps * self.nnodes * self.nspace)
75 - self.dt * np.kron(np.eye(self.nsteps), np.kron(Q, A))
76 - np.kron(E, np.kron(N, np.eye(self.nspace)))
77 )
78 self.C = np.array(self.C)
79 self.P = np.eye(self.nsteps * self.nnodes * self.nspace) - self.dt * np.kron(
80 np.eye(self.nsteps), np.kron(Qd, A)
81 )
82 self.P = np.array(self.P)
84 if self.nlevels > 1:
85 prob_c = self.MS[0].levels[1].prob
86 self.nspace_c = prob_c.init[0]
88 Ac = prob_c.A.todense()
89 Qdc = self.MS[0].levels[1].sweep.QI[1:, 1:]
90 nnodesc = self.MS[0].levels[1].sweep.coll.num_nodes
91 Nc = np.zeros((nnodesc, nnodesc))
92 Nc[:, -1] = 1
94 if hasattr(self.MS[0].base_transfer.space_transfer, 'Pspace'):
95 TcfA = self.MS[0].base_transfer.space_transfer.Pspace.todense()
96 else:
97 TcfA = np.eye(self.nspace_c)
98 if hasattr(self.MS[0].base_transfer.space_transfer, 'Rspace'):
99 TfcA = self.MS[0].base_transfer.space_transfer.Rspace.todense()
100 else:
101 TfcA = np.eye(self.nspace)
102 TcfQ = self.MS[0].base_transfer.Pcoll
103 TfcQ = self.MS[0].base_transfer.Rcoll
105 self.Tcf = np.array(np.kron(np.eye(self.nsteps), np.kron(TcfQ, TcfA)))
106 self.Tfc = np.array(np.kron(np.eye(self.nsteps), np.kron(TfcQ, TfcA)))
108 self.Pc = (
109 np.eye(self.nsteps * nnodesc * self.nspace_c)
110 - self.dt * np.kron(np.eye(self.nsteps), np.kron(Qdc, Ac))
111 - np.kron(E, np.kron(Nc, np.eye(self.nspace_c)))
112 )
113 self.Pc = np.array(self.Pc)
115 self.u = np.zeros(self.nsteps * self.nnodes * self.nspace)
116 self.res = np.zeros(self.nsteps * self.nnodes * self.nspace)
117 self.u0 = np.zeros(self.nsteps * self.nnodes * self.nspace)
119 def run(self, u0, t0, Tend):
120 """
121 Main driver for running the serial matrix version of SDC, MSSDC, MLSDC and PFASST
123 Args:
124 u0: initial values
125 t0: starting time
126 Tend: ending time
128 Returns:
129 end values on the finest level
130 stats object containing statistics for each step, each level and each iteration
131 """
133 # some initializations and reset of statistics
134 uend = None
135 num_procs = len(self.MS)
136 for hook in self.hooks:
137 hook.reset_stats()
139 assert (
140 (Tend - t0) / self.dt
141 ).is_integer(), 'ERROR: dt, t0, Tend were not chosen correctly, do not divide interval to be computed equally'
143 assert int((Tend - t0) / self.dt) % num_procs == 0, 'ERROR: num_procs not chosen correctly'
145 # initial ordering of the steps: 0,1,...,Np-1
146 slots = list(range(num_procs))
148 # initialize time variables of each step
149 time = [t0 + sum(self.dt for _ in range(p)) for p in slots]
151 # initialize block of steps with u0
152 self.restart_block(slots, time, u0)
154 # call pre-run hook
155 for S in self.MS:
156 for hook in self.hooks:
157 hook.pre_run(step=S, level_number=0)
159 nblocks = int((Tend - t0) / self.dt / num_procs)
161 for _ in range(nblocks):
162 self.MS = self.pfasst(self.MS)
164 for p in slots:
165 time[p] += num_procs * self.dt
167 # uend is uend of the last active step in the list
168 uend = self.MS[-1].levels[0].uend
169 self.restart_block(slots, time, uend)
171 # call post-run hook
172 for S in self.MS:
173 for hook in self.hooks:
174 hook.post_run(step=S, level_number=0)
176 return uend, self.return_stats()
178 def build_propagation_matrix(self, niter):
179 """
180 Helper routine to create propagation matrix if requested
182 Args:
183 niter: number of iterations
185 Returns:
186 mat: propagation matrix
187 """
189 # build smoother iteration matrix and preconditioner using nsweeps
190 Pinv = np.linalg.inv(self.P)
191 precond_smoother = Pinv.copy()
192 iter_mat_smoother = np.eye(self.nsteps * self.nnodes * self.nspace) - precond_smoother.dot(self.C)
193 for k in range(1, self.MS[0].levels[0].params.nsweeps):
194 precond_smoother += np.linalg.matrix_power(iter_mat_smoother, k).dot(Pinv)
195 iter_mat_smoother = np.linalg.matrix_power(iter_mat_smoother, self.MS[0].levels[0].params.nsweeps)
197 # add coarse-grid correction (single sweep, though!)
198 if self.nlevels > 1:
199 precond_cgc = self.Tcf.dot(np.linalg.inv(self.Pc)).dot(self.Tfc)
200 iter_mat_cgc = np.eye(self.nsteps * self.nnodes * self.nspace) - precond_cgc.dot(self.C)
201 iter_mat = iter_mat_smoother.dot(iter_mat_cgc)
202 precond = precond_smoother + precond_cgc - precond_smoother.dot(self.C).dot(precond_cgc)
203 else:
204 iter_mat = iter_mat_smoother
205 precond = precond_smoother
207 # form span and reduce matrices and add to operator
208 Tspread = np.kron(np.concatenate([[1] * (self.nsteps * self.nnodes)]), np.eye(self.nspace)).T
209 Tnospread = np.kron(
210 np.concatenate([[1], [0] * (self.nsteps - 1)]), np.kron(np.ones(self.nnodes), np.eye(self.nspace))
211 ).T
212 Treduce = np.kron(np.concatenate([[0] * (self.nsteps * self.nnodes - 1), [1]]), np.eye(self.nspace))
214 if self.MS[0].levels[0].sweep.params.initial_guess == 'spread':
215 mat = np.linalg.matrix_power(iter_mat, niter).dot(Tspread)
216 # mat = iter_mat_smoother.dot(Tspread) + precond_smoother.dot(Tnospread)
217 else:
218 mat = np.linalg.matrix_power(iter_mat, niter).dot(Tnospread)
219 # mat = iter_mat_smoother.dot(Tnospread) + precond_smoother.dot(Tnospread) # No, the latter is not a typo!
221 # build propagation matrix
222 # mat = np.linalg.matrix_power(iter_mat, niter - 1).dot(mat)
223 for k in range(niter):
224 mat += np.linalg.matrix_power(iter_mat, k).dot(precond).dot(Tnospread)
225 mat = Treduce.dot(mat)
227 return mat
229 def restart_block(self, slots, time, u0):
230 """
231 Helper routine to reset/restart block of steps
233 Args:
234 slots: list of steps
235 time: list of new times
236 u0: initial value to distribute across the steps
238 """
240 # loop over steps
241 for p in slots:
242 # store current slot number for diagnostics
243 self.MS[p].status.slot = p
245 for p in slots:
246 for lvl in self.MS[p].levels:
247 lvl.status.time = time[p]
248 P = lvl.prob
249 for m in range(1, lvl.sweep.coll.num_nodes + 1):
250 lvl.u[m] = P.dtype_u(init=P.init, val=0.0)
251 lvl.f[m] = P.dtype_f(init=P.init, val=0.0)
253 self.u0 = np.kron(np.concatenate([[1], [0] * (self.nsteps - 1)]), np.kron(np.ones(self.nnodes), u0))
255 if self.MS[0].levels[0].sweep.params.initial_guess == 'spread':
256 self.u = np.kron(np.ones(self.nsteps * self.nnodes), u0)
257 else:
258 self.u = self.u0.copy()
260 self.res = np.zeros(self.nsteps * self.nnodes * self.nspace)
262 @staticmethod
263 def update_data(MS, u, res, niter, level, stage):
264 for S in MS:
265 S.status.stage = stage
266 S.status.iter = niter
268 L = S.levels[level]
269 P = L.prob
270 nnodes = L.sweep.coll.num_nodes
271 nspace = P.init[0]
273 first = S.status.slot * nnodes * nspace
274 last = (S.status.slot + 1) * nnodes * nspace
276 L.status.residual = np.linalg.norm(res[first:last], np.inf)
278 for m in range(1, nnodes + 1):
279 mstart = first + (m - 1) * nspace
280 mend = first + m * nspace
281 L.u[m][:] = u[mstart:mend]
282 L.f[m] = P.eval_f(L.u[m], L.time + L.dt * L.sweep.coll.nodes[m - 1])
284 S.levels[level].sweep.compute_end_point()
286 return MS
288 def pfasst(self, MS):
289 """
290 Main function including the stages of SDC, MLSDC and PFASST (the "controller")
292 Args:
293 MS: all active steps
295 Returns:
296 all active steps
297 """
299 niter = 0
301 self.res = self.u0 - self.C.dot(self.u)
303 MS = self.update_data(MS=MS, u=self.u, res=self.res, niter=niter, level=0, stage='PRE_STEP')
304 for S in MS:
305 for hook in self.hooks:
306 hook.pre_step(step=S, level_number=0)
308 while np.linalg.norm(self.res, np.inf) > self.tol and niter < self.maxiter:
309 niter += 1
311 MS = self.update_data(MS=MS, u=self.u, res=self.res, niter=niter, level=0, stage='PRE_ITERATION')
312 for S in MS:
313 for hook in self.hooks:
314 hook.pre_iteration(step=S, level_number=0)
316 if self.nlevels > 1:
317 for _ in range(MS[0].levels[1].params.nsweeps):
318 MS = self.update_data(MS=MS, u=self.u, res=self.res, niter=niter, level=1, stage='PRE_COARSE_SWEEP')
319 for S in MS:
320 for hook in self.hooks:
321 hook.pre_sweep(step=S, level_number=1)
323 self.u += self.Tcf.dot(np.linalg.solve(self.Pc, self.Tfc.dot(self.res)))
324 self.res = self.u0 - self.C.dot(self.u)
326 MS = self.update_data(
327 MS=MS, u=self.u, res=self.res, niter=niter, level=1, stage='POST_COARSE_SWEEP'
328 )
329 for S in MS:
330 for hook in self.hooks:
331 hook.post_sweep(step=S, level_number=1)
333 for _ in range(MS[0].levels[0].params.nsweeps):
334 MS = self.update_data(MS=MS, u=self.u, res=self.res, niter=niter, level=0, stage='PRE_FINE_SWEEP')
335 for S in MS:
336 for hook in self.hooks:
337 hook.pre_sweep(step=S, level_number=0)
339 self.u += np.linalg.solve(self.P, self.res)
340 self.res = self.u0 - self.C.dot(self.u)
342 MS = self.update_data(MS=MS, u=self.u, res=self.res, niter=niter, level=0, stage='POST_FINE_SWEEP')
343 for S in MS:
344 for hook in self.hooks:
345 hook.post_sweep(step=S, level_number=0)
347 MS = self.update_data(MS=MS, u=self.u, res=self.res, niter=niter, level=0, stage='POST_ITERATION')
348 for S in MS:
349 for hook in self.hooks:
350 hook.post_iteration(step=S, level_number=0)
352 MS = self.update_data(MS=MS, u=self.u, res=self.res, niter=niter, level=0, stage='POST_STEP')
353 for S in MS:
354 for hook in self.hooks:
355 hook.post_step(step=S, level_number=0)
357 return MS