Coverage for pySDC/tutorial/step_9/paradiag_setup.py: 100%
57 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"""
2The ParaDiag setup of tutorial step 9, parts D and E: the advection problem of part C, a few fixed alphas and the
3adaptive one, and a `run` that takes a communicator or not. Part D runs it with the virtually parallel controller,
4part E on MPI ranks, which is the whole point: the same function, serial or parallel.
5"""
7# we always do this many time-steps in total, no matter how many of them run in parallel
8num_steps_total = 4
11def get_description():
12 """
13 Set up the same advection problem as in Part C.
15 Returns:
16 dict: the description for the ParaDiag controller
17 """
18 from pySDC.implementations.problem_classes.AdvectionEquation_ND_FD import advectionNd
19 from pySDC.implementations.sweeper_classes.ParaDiagSweepers import QDiagonalization
21 level_params = {}
22 level_params['dt'] = 0.1
23 level_params['restol'] = 1e-6
25 sweeper_params = {}
26 sweeper_params['quad_type'] = 'RADAU-RIGHT'
27 sweeper_params['num_nodes'] = 3
28 sweeper_params['initial_guess'] = 'copy'
30 # Part C uses GMRES here to count linear solver work. We only care about the parallelism, and the
31 # complex shifted systems ParaDiag produces are hard for GMRES, so we solve them directly instead.
32 problem_params = {'nvars': 64, 'order': 8, 'c': 1, 'solver_type': 'direct'}
34 step_params = {}
35 step_params['maxiter'] = 99
37 description = {}
38 description['problem_class'] = advectionNd
39 description['problem_params'] = problem_params
40 description['sweeper_class'] = QDiagonalization
41 description['sweeper_params'] = sweeper_params
42 description['level_params'] = level_params
43 description['step_params'] = step_params
45 return description
48# the fixed values we compare against, plus the adaptive strategy
49alpha_settings = [1e-2, 1e-4, 1e-8, 'adaptive']
52def get_controller_params(alpha):
53 """
54 Controller parameters for one alpha setting.
56 Args:
57 alpha: a number, or the string 'adaptive'
59 Returns:
60 tuple: the controller parameters and the extra description entries
61 """
62 from pySDC.implementations.convergence_controller_classes.adaptive_alpha import AdaptiveAlpha
64 controller_params = {}
65 controller_params['logger_level'] = 30
66 controller_params['average_jacobian'] = False
68 extra_description = {}
69 if alpha == 'adaptive':
70 # the adaptive controller overwrites this from the first iteration onwards, but ParaDiag needs
71 # some alpha to build its first transform with
72 controller_params['alpha'] = 1e-4
73 extra_description['convergence_controllers'] = {AdaptiveAlpha: {}}
74 else:
75 controller_params['alpha'] = alpha
77 return controller_params, extra_description
80def format_result(mode, alpha, niter, error, final_alpha):
81 """
82 One line of output, in the same shape for both controllers so they can be compared.
84 Args:
85 mode (str): which controller produced it, e.g. 'virtual' or 'MPI on 4'
86 alpha: the alpha setting used
87 niter (int): number of iterations needed
88 error (float): error against the exact solution
89 final_alpha (float): the alpha in use when the run finished
91 Returns:
92 str: the formatted line
93 """
94 return (
95 f'{mode:>11s}: alpha {str(alpha):>9s} -> {niter:2d} iterations, '
96 f'error {error:.4e}, final alpha {final_alpha:.3e}'
97 )
100def run(alpha, block_size, comm=None):
101 """
102 Run the advection problem with one alpha setting.
104 Args:
105 alpha: a number, or the string 'adaptive'
106 block_size (int): number of time-steps in one block
107 comm: MPI communicator, or None for the virtually parallel controller
109 Returns:
110 tuple: the end value, the iteration count, the error and the final alpha
111 """
112 import numpy as np
113 from pySDC.helpers.stats_helper import get_sorted
115 controller_params, extra_description = get_controller_params(alpha)
116 description = {**get_description(), **extra_description}
118 if comm is None:
119 from pySDC.implementations.controller_classes.controller_ParaDiag_nonMPI import controller_ParaDiag_nonMPI
121 controller_params['mssdc_jac'] = False
122 controller = controller_ParaDiag_nonMPI(
123 controller_params=controller_params, description=description, num_procs=block_size
124 )
125 steps = controller.MS
126 else:
127 from pySDC.implementations.controller_classes.controller_ParaDiag_MPI import controller_ParaDiag_MPI
129 controller = controller_ParaDiag_MPI(controller_params=controller_params, description=description, comm=comm)
130 steps = [controller.S]
132 # ParaDiag diagonalizes in time, so the solution becomes complex
133 for S in steps:
134 S.levels[0].prob.init = tuple([*S.levels[0].prob.init[:2]] + [np.dtype('complex128')])
136 P = steps[0].levels[0].prob
137 dt = steps[0].levels[0].params.dt
138 Tend = num_steps_total * dt
140 uend, stats = controller.run(u0=P.u_exact(0.0), t0=0.0, Tend=Tend)
141 niter = max(int(me[1]) for me in get_sorted(stats, type='niter', sortby='time', comm=comm))
143 return uend, niter, abs(uend - P.u_exact(Tend)), controller.params.alpha