Coverage for pySDC/implementations/controller_classes/controller_nonMPI.py: 99%

296 statements  

« prev     ^ index     » next       coverage.py v7.17.0, created at 2026-11-02 12:30 +0000

1import itertools 

2import copy as cp 

3import numpy as np 

4import dill 

5 

6from pySDC.core.controller import Controller 

7from pySDC.core.step import Step 

8from pySDC.core.errors import ControllerError, CommunicationError 

9from pySDC.implementations.convergence_controller_classes.basic_restarting import BasicRestarting 

10 

11 

12class controller_nonMPI(Controller): 

13 """ 

14 

15 Controller running SDC, MLSDC and PFASST with the time steps of a block emulated serially in one process. 

16 

17 PFASST controller, running serialized version of PFASST in blocks (MG-style) 

18 

19 """ 

20 

21 def __init__(self, num_procs, controller_params, description): 

22 """ 

23 Initialization routine for PFASST controller 

24 

25 Args: 

26 num_procs: number of parallel time steps (still serial, though), can be 1 

27 controller_params: parameter set for the controller and the steps 

28 description: all the parameters to set up the rest (levels, problems, transfer, ...) 

29 """ 

30 

31 if 'predict' in controller_params: 

32 raise ControllerError('predict flag is ignored, use predict_type instead') 

33 

34 # call parent's initialization routine 

35 super().__init__(controller_params, description, useMPI=False) 

36 

37 self.MS: list[Step] = [Step(description)] 

38 

39 # try to initialize via dill.copy (much faster for many time-steps) 

40 try: 

41 for _ in range(num_procs - 1): 

42 self.MS.append(dill.copy(self.MS[0])) 

43 # if this fails (e.g. due to un-picklable data in the steps), initialize separately 

44 except (dill.PicklingError, TypeError, ValueError) as error: 

45 self.logger.warning(f'Need to initialize steps separately due to pickling error: {error}') 

46 for _ in range(num_procs - 1): 

47 self.MS.append(Step(description)) 

48 

49 self.base_convergence_controllers += [BasicRestarting.get_implementation(useMPI=False)] 

50 for convergence_controller in self.base_convergence_controllers: 

51 self.add_convergence_controller(convergence_controller, description) 

52 

53 if self.params.dump_setup: 

54 self.dump_setup(step=self.MS[0], controller_params=controller_params, description=description) 

55 

56 if num_procs > 1 and len(self.MS[0].levels) > 1: 

57 for S in self.MS: 

58 for L in S.levels: 

59 if not L.sweep.coll.right_is_node: 

60 raise ControllerError("For PFASST to work, we assume uend^k = u_M^k") 

61 

62 if all(len(S.levels) == len(self.MS[0].levels) for S in self.MS): 

63 self.nlevels = len(self.MS[0].levels) 

64 else: 

65 raise ControllerError('all steps need to have the same number of levels') 

66 

67 if self.nlevels == 0: 

68 raise ControllerError('need at least one level') 

69 

70 # The stages read `nsweeps` off a step they own, the same way `controller_MPI` reads it off 

71 # the one step a rank has, so this only has to establish that any step will do. 

72 for nl in range(self.nlevels): 

73 if not all(S.levels[nl].params.nsweeps == self.MS[0].levels[nl].params.nsweeps for S in self.MS): 

74 raise ControllerError('all steps need to agree on the number of sweeps per level') 

75 

76 # `it_coarse` sweeps the coarsest level exactly once. Single-level Gauss-like MSSDC routes 

77 # through it too, so reject multiple sweeps there as well instead of silently ignoring them. 

78 # `mssdc_jac` only decides the routing when there is more than one step: a single step is 

79 # plain SDC and always goes through `it_fine`, which honours nsweeps. 

80 if self.MS[0].levels[-1].params.nsweeps > 1 and ( 

81 self.nlevels > 1 or (num_procs > 1 and not self.params.mssdc_jac) 

82 ): 

83 raise ControllerError('this controller cannot do multiple sweeps on coarsest level') 

84 

85 self.check_variable_coefficients(num_procs) 

86 

87 if self.nlevels == 1 and self.params.predict_type is not None: 

88 self.logger.warning( 

89 'you have specified a predictor type but only a single level.. predictor will be ignored' 

90 ) 

91 

92 for C in [self.convergence_controllers[i] for i in self.convergence_controller_order]: 

93 C.reset_buffers_nonMPI(self) 

94 C.setup_status_variables(self, MS=self.MS) 

95 

96 self.stages = self.get_stages() 

97 

98 def run(self, u0, t0, Tend): 

99 """ 

100 Main driver for running the serial version of SDC, MSSDC, MLSDC and PFASST (virtual parallelism) 

101 

102 Args: 

103 u0: initial values 

104 t0: starting time 

105 Tend: ending time 

106 

107 Returns: 

108 end values on the finest level 

109 stats object containing statistics for each step, each level and each iteration 

110 """ 

111 

112 # some initializations and reset of statistics 

113 uend = None 

114 num_procs = len(self.MS) 

115 for hook in self.hooks: 

116 hook.reset_stats() 

117 

118 # start from the step size in the description, not the one adaptivity left from a previous run 

119 for S in self.MS: 

120 for L in S.levels: 

121 L.params.dt = L.params.dt_initial 

122 

123 # initial ordering of the steps: 0,1,...,Np-1 

124 slots = list(range(num_procs)) 

125 

126 # initialize time variables of each step 

127 time = [t0 + sum(self.MS[j].dt for j in range(p)) for p in slots] 

128 

129 # determine which steps are still active (time < Tend) 

130 active = [self.step_is_active(time[p], block_start=time[0], t0=t0, Tend=Tend, dt=self.MS[p].dt) for p in slots] 

131 

132 if not any(active): 

133 raise ControllerError('Nothing to do, check t0, dt and Tend.') 

134 

135 # compress slots according to active steps, i.e. remove all steps which have times above Tend 

136 active_slots = list(itertools.compress(slots, active)) 

137 

138 # initialize block of steps with u0 

139 self.restart_block(active_slots, time, u0) 

140 

141 for hook in self.hooks: 

142 hook.post_setup(step=None, level_number=None) 

143 

144 # call pre-run hook 

145 for S in self.MS: 

146 for hook in self.hooks: 

147 hook.pre_run(step=S, level_number=0) 

148 

149 # main loop: as long as at least one step is still active (time < Tend), do something 

150 while any(active): 

151 MS_active = [self.MS[p] for p in active_slots] 

152 done = False 

153 while not done: 

154 done = self.pfasst(MS_active) 

155 

156 restarts = [S.status.restart for S in MS_active] 

157 restart_at = np.where(restarts)[0][0] if True in restarts else len(MS_active) 

158 if True in restarts: # restart part of the block 

159 # initial condition to next block is initial condition of step that needs restarting 

160 uend = self.MS[restart_at].levels[0].u[0] 

161 time[active_slots[0]] = time[restart_at] 

162 self.logger.info(f'Starting next block with initial conditions from step {restart_at}') 

163 

164 else: # move on to next block 

165 # initial condition for next block is last solution of current block 

166 uend = self.MS[active_slots[-1]].levels[0].uend 

167 time[active_slots[0]] = time[active_slots[-1]] + self.MS[active_slots[-1]].dt 

168 

169 for S in MS_active[:restart_at]: 

170 for C in [self.convergence_controllers[i] for i in self.convergence_controller_order]: 

171 C.post_step_processing(self, S, MS=MS_active) 

172 

173 for C in [self.convergence_controllers[i] for i in self.convergence_controller_order]: 

174 [C.prepare_next_block(self, S, len(active_slots), time, Tend, MS=MS_active) for S in self.MS] 

175 

176 # setup the times of the steps for the next block 

177 for i in range(1, len(active_slots)): 

178 time[active_slots[i]] = time[active_slots[i] - 1] + self.MS[active_slots[i] - 1].dt 

179 

180 # determine new set of active steps and compress slots accordingly 

181 active = [ 

182 self.step_is_active(time[p], block_start=time[0], t0=t0, Tend=Tend, dt=self.MS[p].dt) for p in slots 

183 ] 

184 active_slots = list(itertools.compress(slots, active)) 

185 

186 # restart active steps (reset all values and pass uend to u0) 

187 self.restart_block(active_slots, time, uend) 

188 

189 # call post-run hook 

190 for S in self.MS: 

191 for hook in self.hooks: 

192 hook.post_run(step=S, level_number=0) 

193 

194 for S in self.MS: 

195 for C in [self.convergence_controllers[i] for i in self.convergence_controller_order]: 

196 C.post_run_processing(self, S, MS=MS_active) 

197 

198 return uend, self.return_stats() 

199 

200 def restart_block(self, active_slots, time, u0): 

201 """ 

202 Helper routine to reset/restart block of (active) steps 

203 

204 Args: 

205 active_slots: list of active steps 

206 time: list of new times 

207 u0: initial value to distribute across the steps 

208 

209 """ 

210 

211 # loop over active slots (not directly, since we need the previous entry as well) 

212 for j in range(len(active_slots)): 

213 # get slot number 

214 p = active_slots[j] 

215 

216 # store current slot number for diagnostics 

217 self.MS[p].status.slot = p 

218 # store link to previous step 

219 self.MS[p].prev = self.MS[active_slots[j - 1]] 

220 # resets step 

221 self.MS[p].reset_step() 

222 # determine whether I am the first and/or last in line 

223 self.MS[p].status.first = active_slots.index(p) == 0 

224 self.MS[p].status.last = active_slots.index(p) == len(active_slots) - 1 

225 # initialize step with u0 

226 self.MS[p].init_step(u0) 

227 # reset some values 

228 self.MS[p].status.done = False 

229 self.MS[p].status.prev_done = False 

230 self.MS[p].status.iter = 0 

231 self.MS[p].status.stage = 'SPREAD' 

232 self.MS[p].status.force_done = False 

233 self.MS[p].status.time_size = len(active_slots) 

234 

235 for l in self.MS[p].levels: 

236 l.tag = None 

237 l.status.sweep = 1 

238 

239 for p in active_slots: 

240 for lvl in self.MS[p].levels: 

241 lvl.status.time = time[p] 

242 

243 for C in [self.convergence_controllers[i] for i in self.convergence_controller_order]: 

244 C.reset_status_variables(self, active_slots=active_slots) 

245 

246 def send_full(self, S, level=None, add_to_stats=False): 

247 """ 

248 Function to perform the send, including bookkeeping and logging 

249 

250 Args: 

251 S: the current step 

252 level: the level number 

253 add_to_stats: a flag to end recording data in the hooks (defaults to False) 

254 

255 Note: 

256 Computing the end point is this function's job, not the caller's, exactly as in 

257 `controller_MPI.send_full`. It happens whether or not anyone is listening, because the 

258 last step has no successor but its `uend` is still the block's result. 

259 """ 

260 for hook in self.hooks: 

261 hook.pre_comm(step=S, level_number=level) 

262 

263 # sending here means computing uend ("one-sided communication") 

264 S.levels[level].sweep.compute_end_point() 

265 

266 if not S.status.last: 

267 self.logger.debug( 

268 'Process %2i provides data on level %2i with tag %s' % (S.status.slot, level, S.status.iter) 

269 ) 

270 S.levels[level].tag = cp.deepcopy((level, S.status.iter, S.status.slot)) 

271 

272 for hook in self.hooks: 

273 hook.post_comm(step=S, level_number=level, add_to_stats=add_to_stats) 

274 

275 def recv_full(self, S, level=None, add_to_stats=False): 

276 """ 

277 Function to perform the recv, including bookkeeping and logging 

278 

279 Args: 

280 S: the current step 

281 level: the level number 

282 add_to_stats: a flag to end recording data in the hooks (defaults to False) 

283 """ 

284 

285 def recv(target, source, tag=None): 

286 """ 

287 Receive function 

288 

289 Args: 

290 target: level which will receive the values 

291 source: level which initiated the send 

292 tag: identifier to check if this message is really for me 

293 """ 

294 

295 if tag is not None and source.tag != tag: 

296 raise CommunicationError('source and target tag are not the same, got %s and %s' % (source.tag, tag)) 

297 # simply do a deepcopy of the values uend to become the new u0 at the target 

298 target.u[0] = target.prob.dtype_u(source.uend) 

299 # re-evaluate f on left interval boundary 

300 target.f[0] = target.prob.eval_f(target.u[0], target.time) 

301 

302 for hook in self.hooks: 

303 hook.pre_comm(step=S, level_number=level) 

304 if not S.status.prev_done and not S.status.first: 

305 self.logger.debug( 

306 'Process %2i receives from %2i on level %2i with tag %s' 

307 % (S.status.slot, S.prev.status.slot, level, S.status.iter) 

308 ) 

309 recv(S.levels[level], S.prev.levels[level], tag=(level, S.status.iter, S.prev.status.slot)) 

310 for hook in self.hooks: 

311 hook.post_comm(step=S, level_number=level, add_to_stats=add_to_stats) 

312 

313 def pfasst(self, local_MS_active): 

314 """ 

315 Main function including the stages of SDC, MLSDC and PFASST (the "controller") 

316 

317 For the workflow of this controller, check out one of our PFASST talks or the pySDC paper 

318 

319 This method changes self.MS directly by accessing active steps through local_MS_active. Nothing is returned. 

320 

321 Args: 

322 local_MS_active (list): all active steps 

323 """ 

324 

325 # if all stages are the same (or DONE), continue, otherwise abort 

326 stages = [S.status.stage for S in local_MS_active if S.status.stage != 'DONE'] 

327 if stages[1:] == stages[:-1]: 

328 stage = stages[0] 

329 else: 

330 raise ControllerError('not all stages are equal') 

331 

332 self.logger.debug(stage) 

333 

334 MS_running = [S for S in local_MS_active if S.status.stage != 'DONE'] 

335 

336 self.stages.get(stage, self.default)(MS_running) 

337 

338 return all(S.status.done for S in local_MS_active) 

339 

340 def get_stages(self): 

341 """ 

342 The stages this controller can be in, and what to run in each. 

343 

344 A subclass that iterates differently replaces the iteration stages here and says which one 

345 to enter in `next_iteration_stage`; everything around the iteration is the same for any 

346 algorithm this controller runs. 

347 

348 Returns: 

349 dict: stage name -> the method that runs it 

350 """ 

351 return { 

352 'SPREAD': self.spread, 

353 'PREDICT': self.predict, 

354 'IT_CHECK': self.it_check, 

355 'IT_FINE': self.it_fine, 

356 'IT_DOWN': self.it_down, 

357 'IT_COARSE': self.it_coarse, 

358 'IT_UP': self.it_up, 

359 } 

360 

361 def next_iteration_stage(self, S): 

362 """ 

363 The stage that starts one iteration of the algorithm. 

364 

365 Args: 

366 S (pySDC.Step.step): The current step 

367 

368 Returns: 

369 str: name of the stage to enter 

370 """ 

371 if len(S.levels) > 1: # MLSDC or PFASST 

372 return 'IT_DOWN' 

373 elif S.status.time_size == 1 or self.params.mssdc_jac: # SDC or parallel MSSDC (Jacobi-like) 

374 return 'IT_FINE' 

375 else: 

376 return 'IT_COARSE' # serial MSSDC (Gauss-like) 

377 

378 def spread(self, local_MS_running): 

379 """ 

380 Spreading phase 

381 

382 Args: 

383 local_MS_running (list): list of currently running steps 

384 """ 

385 

386 for S in local_MS_running: 

387 # first stage: spread values 

388 for hook in self.hooks: 

389 hook.pre_step(step=S, level_number=0) 

390 

391 # call predictor from sweeper 

392 S.levels[0].sweep.predict() 

393 

394 self.compute_residual_after_spread(S) 

395 

396 # update stage 

397 if len(S.levels) > 1: # MLSDC or PFASST with predict 

398 S.status.stage = 'PREDICT' 

399 else: 

400 S.status.stage = 'IT_CHECK' 

401 

402 for C in [self.convergence_controllers[i] for i in self.convergence_controller_order]: 

403 C.post_spread_processing(self, S, MS=local_MS_running) 

404 

405 def predict(self, local_MS_running): 

406 """ 

407 Predictor phase 

408 

409 Args: 

410 local_MS_running (list): list of currently running steps 

411 """ 

412 

413 for S in local_MS_running: 

414 for hook in self.hooks: 

415 hook.pre_predict(step=S, level_number=0) 

416 

417 if self.params.predict_type is None: 

418 pass 

419 

420 elif self.params.predict_type == 'fine_only': 

421 # do a fine sweep only 

422 for S in local_MS_running: 

423 S.levels[0].sweep.update_nodes() 

424 

425 elif self.params.predict_type == 'pfasst_burnin': 

426 # loop over all steps 

427 for S in local_MS_running: 

428 # restrict to coarsest level 

429 for l in range(1, len(S.levels)): 

430 S.transfer(source=S.levels[l - 1], target=S.levels[l]) 

431 

432 # loop over all steps 

433 for q in range(len(local_MS_running)): 

434 # loop over last steps: [1,2,3,4], [2,3,4], [3,4], [4] 

435 for p in range(q, len(local_MS_running)): 

436 S = local_MS_running[p] 

437 

438 # do the sweep with new values 

439 S.levels[-1].sweep.update_nodes() 

440 

441 # send updated values on coarsest level 

442 self.send_full(S, level=len(S.levels) - 1) 

443 

444 # loop over last steps: [2,3,4], [3,4], [4] 

445 for p in range(q + 1, len(local_MS_running)): 

446 S = local_MS_running[p] 

447 # receive values sent during previous sweep 

448 self.recv_full(S, level=len(S.levels) - 1, add_to_stats=(p == len(local_MS_running) - 1)) 

449 

450 # loop over all steps 

451 for S in local_MS_running: 

452 # interpolate back to finest level 

453 for l in range(len(S.levels) - 1, 0, -1): 

454 S.transfer(source=S.levels[l], target=S.levels[l - 1]) 

455 

456 # send updated values forward 

457 self.send_full(S, level=0) 

458 # receive values 

459 self.recv_full(S, level=0) 

460 

461 # end this with a fine sweep 

462 for S in local_MS_running: 

463 S.levels[0].sweep.update_nodes() 

464 

465 elif self.params.predict_type == 'fmg': 

466 # TODO: implement FMG predictor 

467 raise NotImplementedError('FMG predictor is not yet implemented') 

468 

469 else: 

470 raise ControllerError('Wrong predictor type, got %s' % self.params.predict_type) 

471 

472 for S in local_MS_running: 

473 for hook in self.hooks: 

474 hook.post_predict(step=S, level_number=0) 

475 

476 for S in local_MS_running: 

477 # update stage 

478 S.status.stage = 'IT_CHECK' 

479 

480 def it_check(self, local_MS_running): 

481 """ 

482 Key routine to check for convergence/termination 

483 

484 Args: 

485 local_MS_running (list): list of currently running steps 

486 """ 

487 

488 self.prepare_convergence_check(local_MS_running) 

489 

490 for S in local_MS_running: 

491 if S.status.iter > 0: 

492 for hook in self.hooks: 

493 hook.post_iteration(step=S, level_number=0) 

494 

495 # decide if the step is done, needs to be restarted and other things convergence related 

496 for C in [self.convergence_controllers[i] for i in self.convergence_controller_order]: 

497 C.post_iteration_processing(self, S, MS=local_MS_running) 

498 C.convergence_control(self, S, MS=local_MS_running) 

499 

500 for C in [self.convergence_controllers[i] for i in self.convergence_controller_order]: 

501 C.post_iteration_processing_block(self, MS=local_MS_running) 

502 

503 for S in local_MS_running: 

504 for C in [self.convergence_controllers[i] for i in self.convergence_controller_order]: 

505 C.communicate_convergence(self, S, MS=local_MS_running) 

506 

507 if not S.status.done: 

508 # increment iteration count here (and only here) 

509 S.status.iter += 1 

510 for hook in self.hooks: 

511 hook.pre_iteration(step=S, level_number=0) 

512 for C in [self.convergence_controllers[i] for i in self.convergence_controller_order]: 

513 C.pre_iteration_processing(self, S, MS=local_MS_running) 

514 

515 S.status.stage = self.next_iteration_stage(S) 

516 else: 

517 for hook in self.hooks: 

518 hook.post_step(step=S, level_number=0) 

519 S.status.stage = 'DONE' 

520 

521 for C in [self.convergence_controllers[i] for i in self.convergence_controller_order]: 

522 C.reset_buffers_nonMPI(self) 

523 

524 def compute_residual_after_spread(self, S): 

525 """ 

526 Make the residual current right after the initial guess, for algorithms that need it there. 

527 

528 This controller computes the residual at the top of `it_check` instead, so there is nothing 

529 to do; an algorithm whose residual is not available at that point overrides this. Called 

530 before `post_spread_processing`, because convergence controllers there may still change the 

531 initial iterate. 

532 

533 Args: 

534 S (pySDC.Step.step): The current step 

535 """ 

536 pass 

537 

538 def prepare_convergence_check(self, local_MS_running): 

539 """ 

540 Make current what `it_check` is about to read: the end point and the residual. 

541 

542 For a sweep-based algorithm both come out of the exchange with the neighbours, so this is 

543 the send and receive plus the residual it enables. An algorithm that gets its residual from 

544 the iteration instead overrides this, and still owes the end point. 

545 

546 Args: 

547 local_MS_running (list): list of currently running steps 

548 """ 

549 for S in local_MS_running: 

550 # send updated values forward 

551 self.send_full(S, level=0) 

552 # receive values 

553 self.recv_full(S, level=0) 

554 # compute current residual 

555 S.levels[0].sweep.compute_residual(stage='IT_CHECK') 

556 

557 def it_fine(self, local_MS_running: list[Step]): 

558 """ 

559 Fine sweeps 

560 

561 Args: 

562 local_MS_running (list): list of currently running steps 

563 """ 

564 

565 for S in local_MS_running: 

566 S.levels[0].status.sweep = 0 

567 

568 nsweeps = local_MS_running[0].levels[0].params.nsweeps 

569 

570 for k in range(nsweeps): 

571 for S in local_MS_running: 

572 S.levels[0].status.sweep += 1 

573 

574 for S in local_MS_running: 

575 # send updated values forward 

576 self.send_full(S, level=0) 

577 # receive values 

578 self.recv_full(S, level=0, add_to_stats=(k == nsweeps - 1)) 

579 

580 for S in local_MS_running: 

581 # standard sweep workflow: update nodes, log progress (the residual follows once, in IT_CHECK) 

582 for hook in self.hooks: 

583 hook.pre_sweep(step=S, level_number=0) 

584 

585 # update QDelta coefficients if variable preconditioner, indexed by the sweep count across iterations 

586 S.levels[0].sweep.updateVariableCoeffs((S.status.iter - 1) * nsweeps + k + 1) 

587 S.levels[0].sweep.update_nodes() 

588 

589 for hook in self.hooks: 

590 hook.post_sweep(step=S, level_number=0) 

591 

592 for S in local_MS_running: 

593 # update stage 

594 S.status.stage = 'IT_CHECK' 

595 

596 def it_down(self, local_MS_running): 

597 """ 

598 Go down the hierarchy from finest to coarsest level 

599 

600 Args: 

601 local_MS_running (list): list of currently running steps 

602 """ 

603 

604 for S in local_MS_running: 

605 S.transfer(source=S.levels[0], target=S.levels[1]) 

606 

607 for l in range(1, self.nlevels - 1): 

608 # sweep on middle levels (not on finest, not on coarsest, though) 

609 

610 nsweeps = local_MS_running[0].levels[l].params.nsweeps 

611 

612 for _ in range(nsweeps): 

613 for S in local_MS_running: 

614 # send updated values forward 

615 self.send_full(S, level=l) 

616 # receive values 

617 self.recv_full(S, level=l) 

618 

619 for S in local_MS_running: 

620 for hook in self.hooks: 

621 hook.pre_sweep(step=S, level_number=l) 

622 S.levels[l].sweep.update_nodes() 

623 for hook in self.hooks: 

624 hook.post_sweep(step=S, level_number=l) 

625 

626 for S in local_MS_running: 

627 # transfer further down the hierarchy 

628 S.transfer(source=S.levels[l], target=S.levels[l + 1]) 

629 

630 for S in local_MS_running: 

631 # update stage 

632 S.status.stage = 'IT_COARSE' 

633 

634 def it_coarse(self, local_MS_running): 

635 """ 

636 Coarse sweep 

637 

638 Args: 

639 local_MS_running (list): list of currently running steps 

640 """ 

641 

642 for S in local_MS_running: 

643 # receive from previous step (if not first) 

644 self.recv_full(S, level=len(S.levels) - 1) 

645 

646 # do the sweep 

647 for hook in self.hooks: 

648 hook.pre_sweep(step=S, level_number=len(S.levels) - 1) 

649 S.levels[-1].sweep.update_nodes() 

650 for hook in self.hooks: 

651 hook.post_sweep(step=S, level_number=len(S.levels) - 1) 

652 

653 # send to succ step 

654 self.send_full(S, level=len(S.levels) - 1, add_to_stats=True) 

655 

656 # update stage 

657 if len(S.levels) > 1: # MLSDC or PFASST 

658 S.status.stage = 'IT_UP' 

659 else: # MSSDC 

660 S.status.stage = 'IT_CHECK' 

661 

662 def it_up(self, local_MS_running): 

663 """ 

664 Prolong corrections up to finest level (parallel) 

665 

666 Args: 

667 local_MS_running (list): list of currently running steps 

668 """ 

669 

670 for l in range(self.nlevels - 1, 0, -1): 

671 for S in local_MS_running: 

672 # prolong values 

673 S.transfer(source=S.levels[l], target=S.levels[l - 1]) 

674 

675 # on middle levels: do communication and sweep as usual 

676 if l - 1 > 0: 

677 nsweeps = local_MS_running[0].levels[l - 1].params.nsweeps 

678 

679 for k in range(nsweeps): 

680 for S in local_MS_running: 

681 # send updated values forward 

682 self.send_full(S, level=l - 1) 

683 # receive values 

684 self.recv_full(S, level=l - 1, add_to_stats=(k == nsweeps - 1)) 

685 

686 for S in local_MS_running: 

687 for hook in self.hooks: 

688 hook.pre_sweep(step=S, level_number=l - 1) 

689 S.levels[l - 1].sweep.update_nodes() 

690 for hook in self.hooks: 

691 hook.post_sweep(step=S, level_number=l - 1) 

692 

693 for S in local_MS_running: 

694 # update stage 

695 S.status.stage = 'IT_FINE' 

696 

697 def default(self, local_MS_running): 

698 """ 

699 Default routine to catch wrong status 

700 

701 Args: 

702 local_MS_running (list): list of currently running steps 

703 """ 

704 raise ControllerError('Unknown stage, got %s' % local_MS_running[0].status.stage) # TODO