Part C: Using pySDC’s frontend#

Finally, the user-friendliest interface pySDC has to offer: a controller does the whole iteration logic of Part B for us. We use controller_nonMPI, the default controller. It needs no mpi4py, and depending on the description it can do SDC, multilevel SDC, multistep SDC and PFASST (more on this in the next steps).

The description is the same as in Part B. New are the controller parameters, here used to also write the log to a file.

from pathlib import Path

import matplotlib.pyplot as plt
import numpy as np

from pySDC.implementations.controller_classes.controller_nonMPI import controller_nonMPI
from pySDC.implementations.problem_classes.HeatEquation_ND_FD import heatNd_forced
from pySDC.implementations.sweeper_classes.imex_1st_order import imex_1st_order

# initialize level parameters
level_params = {'restol': 1e-10, 'dt': 0.1}

# initialize sweeper parameters
sweeper_params = {'quad_type': 'RADAU-RIGHT', 'num_nodes': 3}

# initialize problem parameters
problem_params = {
    'nu': 0.1,  # diffusion coefficient
    'freq': 4,  # frequency for the test value
    'nvars': 1023,  # number of degrees of freedom
    'bc': 'dirichlet-zero',  # boundary conditions
}

# initialize step parameters
step_params = {'maxiter': 20}

# initialize controller parameters
controller_params = {
    'log_to_file': True,
    'fname': 'data/step_2_C_out.txt',
}

# Fill description dictionary for easy hierarchy creation
description = {
    'problem_class': heatNd_forced,
    'problem_params': problem_params,
    'sweeper_class': imex_1st_order,
    'sweeper_params': sweeper_params,
    'level_params': level_params,
    'step_params': step_params,
}

Path("data").mkdir(parents=True, exist_ok=True)

Instantiating the controller#

When the controller is created, it prints the full setup. Everything marked with --> was set by us, -> marks what follows from that, and the rest are defaults. This is the place to check what a run actually used. Set the controller parameter dump_setup to False to switch it off.

# instantiate the controller
controller = controller_nonMPI(num_procs=1, controller_params=controller_params, description=description)
controller - INFO: Welcome to the one and only, really very astonishing and 87.3% bug free
                                 _____ _____   _____ 
                                / ____|  __ \ / ____|
                    _ __  _   _| (___ | |  | | |     
                   | '_ \| | | |\___ \| |  | | |     
                   | |_) | |_| |____) | |__| | |____ 
                   | .__/ \__, |_____/|_____/ \_____|
                   | |     __/ |                     
                   |_|    |___/                      
                                                     
controller - INFO: Setup overview (--> user-defined, -> dependency) -- BEGIN
controller - INFO: ----------------------------------------------------------------------------------------------------

Controller: <class 'pySDC.implementations.controller_classes.controller_nonMPI.controller_nonMPI'>
    all_to_done = False
    dump_setup = True
--> fname = data/step_2_C_out.txt
    hook_class = [<class 'pySDC.core.default_hook.DefaultHooks'>, <class 'pySDC.core.timings.CPUTimings'>]
--> log_to_file = True
    logger_level = 20
    mssdc_jac = True
    predict_type = None
    use_iteration_estimator = False

Step: <class 'pySDC.core.step.Step'>
--> maxiter = 20
    Number of steps: None
    Level: <class 'pySDC.core.level.Level'>
        Level  0
-->         dt = 0.1
            dt_initial = 0.1
            nsweeps = 1
            residual_type = full_abs
-->         restol = 1e-10
-->         Problem: <class 'pySDC.implementations.problem_classes.HeatEquation_ND_FD.heatNd_forced'>
-->             bc = dirichlet-zero
-->             freq = (4,)
                liniter = 10000
                lintol = 1e-12
-->             nu = 0.1
-->             nvars = (1023,)
                order = 2
                sigma = 0.06
                solver_type = direct
                stencil_type = center
 ->             Data type u: <class 'pySDC.implementations.datatype_classes.mesh.mesh'>
 ->             Data type f: <class 'pySDC.implementations.datatype_classes.mesh.imex_mesh'>
-->             Sweeper: <class 'pySDC.implementations.sweeper_classes.imex_1st_order.imex_1st_order'>
                    QE = EE
                    QI = IE
                    do_coll_update = False
                    initial_guess = spread
-->                 num_nodes = 3
-->                 quad_type = RADAU-RIGHT
                    skip_residual_computation = ()
 ->                 Collocation: <class 'pySDC.core.collocation.CollBase'>

Active convergence controllers:
    |  # | order | convergence controller
----+----+-------+---------------------------------------------------------------------------------------
    |  0 |    95 | BasicRestartingNonMPI
 -> |  1 |   100 | SpreadStepSizesBlockwiseNonMPI
    |  2 |   200 | CheckConvergence
controller - INFO: ----------------------------------------------------------------------------------------------------
controller - INFO: Setup overview (--> user-defined, -> dependency) -- END

Running#

We ask for the time interval \([0.1, 0.3]\), which with dt = 0.1 means two time steps. The initial value comes from the problem class, which the controller has created inside its step, just like in Part A.

# set time parameters
t0 = 0.1
Tend = 0.3  # note that we are requesting 2 time steps here (dt is 0.1)

# get initial values on finest level
P = controller.MS[0].levels[0].prob
uinit = P.u_exact(t0)

# call main function to get things done...
uend, stats = controller.run(u0=uinit, t0=t0, Tend=Tend)

# compute exact solution and compare
uex = P.u_exact(Tend)
err = abs(uex - uend)
print(f'Error after SDC iterations: {err:8.6e}')
hooks - INFO: Process  0 on time 0.100000 at stage         IT_FINE: Level: 0 -- Iteration:  1 -- Sweep:  1 -- residual: 4.11190756e-03
hooks - INFO: Process  0 on time 0.100000 at stage         IT_FINE: Level: 0 -- Iteration:  2 -- Sweep:  1 -- residual: 6.68442665e-04
hooks - INFO: Process  0 on time 0.100000 at stage         IT_FINE: Level: 0 -- Iteration:  3 -- Sweep:  1 -- residual: 8.80377582e-05
hooks - INFO: Process  0 on time 0.100000 at stage         IT_FINE: Level: 0 -- Iteration:  4 -- Sweep:  1 -- residual: 1.21707909e-05
hooks - INFO: Process  0 on time 0.100000 at stage         IT_FINE: Level: 0 -- Iteration:  5 -- Sweep:  1 -- residual: 1.38271970e-06
hooks - INFO: Process  0 on time 0.100000 at stage         IT_FINE: Level: 0 -- Iteration:  6 -- Sweep:  1 -- residual: 6.36446134e-07
hooks - INFO: Process  0 on time 0.100000 at stage         IT_FINE: Level: 0 -- Iteration:  7 -- Sweep:  1 -- residual: 1.68953336e-07
hooks - INFO: Process  0 on time 0.100000 at stage         IT_FINE: Level: 0 -- Iteration:  8 -- Sweep:  1 -- residual: 3.52588866e-08
hooks - INFO: Process  0 on time 0.100000 at stage         IT_FINE: Level: 0 -- Iteration:  9 -- Sweep:  1 -- residual: 6.07224706e-09
hooks - INFO: Process  0 on time 0.100000 at stage         IT_FINE: Level: 0 -- Iteration: 10 -- Sweep:  1 -- residual: 8.27159898e-10
hooks - INFO: Process  0 on time 0.100000 at stage         IT_FINE: Level: 0 -- Iteration: 11 -- Sweep:  1 -- residual: 1.19139256e-10
hooks - INFO: Process  0 on time 0.100000 at stage         IT_FINE: Level: 0 -- Iteration: 12 -- Sweep:  1 -- residual: 1.44547065e-11
hooks - INFO: Process  0 on time 0.200000 at stage         IT_FINE: Level: 0 -- Iteration:  1 -- Sweep:  1 -- residual: 6.69984764e-03
hooks - INFO: Process  0 on time 0.200000 at stage         IT_FINE: Level: 0 -- Iteration:  2 -- Sweep:  1 -- residual: 1.05518433e-03
hooks - INFO: Process  0 on time 0.200000 at stage         IT_FINE: Level: 0 -- Iteration:  3 -- Sweep:  1 -- residual: 1.40642622e-04
hooks - INFO: Process  0 on time 0.200000 at stage         IT_FINE: Level: 0 -- Iteration:  4 -- Sweep:  1 -- residual: 1.85982064e-05
hooks - INFO: Process  0 on time 0.200000 at stage         IT_FINE: Level: 0 -- Iteration:  5 -- Sweep:  1 -- residual: 2.79216822e-06
hooks - INFO: Process  0 on time 0.200000 at stage         IT_FINE: Level: 0 -- Iteration:  6 -- Sweep:  1 -- residual: 1.12278626e-06
hooks - INFO: Process  0 on time 0.200000 at stage         IT_FINE: Level: 0 -- Iteration:  7 -- Sweep:  1 -- residual: 2.85495413e-07
hooks - INFO: Process  0 on time 0.200000 at stage         IT_FINE: Level: 0 -- Iteration:  8 -- Sweep:  1 -- residual: 5.78934357e-08
hooks - INFO: Process  0 on time 0.200000 at stage         IT_FINE: Level: 0 -- Iteration:  9 -- Sweep:  1 -- residual: 9.68346997e-09
hooks - INFO: Process  0 on time 0.200000 at stage         IT_FINE: Level: 0 -- Iteration: 10 -- Sweep:  1 -- residual: 1.26328381e-09
hooks - INFO: Process  0 on time 0.200000 at stage         IT_FINE: Level: 0 -- Iteration: 11 -- Sweep:  1 -- residual: 1.83275934e-10
hooks - INFO: Process  0 on time 0.200000 at stage         IT_FINE: Level: 0 -- Iteration: 12 -- Sweep:  1 -- residual: 1.91464310e-11
hooks - INFO: Finished run after 8.66e-02s
Error after SDC iterations: 1.166689e-05

That is the whole program a user writes: a description, some pre- and some post-processing. The controller also wrote its log to the file we named; here are its last lines:

print(''.join(Path(controller_params['fname']).read_text().splitlines(True)[-3:]))
2026-09-29 12:50:58,420 - hooks - default_hook - post_sweep - 22 - INFO: Process  0 on time 0.200000 at stage         IT_FINE: Level: 0 -- Iteration: 11 -- Sweep:  1 -- residual: 1.83275934e-10
2026-09-29 12:50:58,423 - hooks - default_hook - post_sweep - 22 - INFO: Process  0 on time 0.200000 at stage         IT_FINE: Level: 0 -- Iteration: 12 -- Sweep:  1 -- residual: 1.91464310e-11
2026-09-29 12:50:58,425 - hooks - timings - post_run - 287 - INFO: Finished run after 8.66e-02s

Hide code cell source

x = np.array([(i + 1) * P.dx for i in range(P.nvars[0])])
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 3))
ax1.plot(x, uinit, label=f'$t = {t0}$')
ax1.plot(x, uend, label=f'$t = {Tend}$, SDC')
ax1.plot(x[::40], uex[::40], 'k.', label=f'$t = {Tend}$, exact')
ax1.set_xlabel('$x$')
ax1.legend(frameon=False, fontsize=8)
ax2.plot(x, np.abs(uend - uex))
ax2.set_xlabel('$x$')
ax2.set_title('error at $t = 0.3$', fontsize=10)
fig.tight_layout()
../../_images/5e1a159c89f1db30e82f82f79a5869413e113fe051eed8cd094d32a76e68cf7a.png

Important things to note

  • The description is all a user needs to steer pySDC; most of the logic and the data structures stay hidden. This part is the prototype for working with pySDC.

  • Unlike in Part B, we have no direct access to the residuals or the iteration counts yet: they are in stats, which Step 3 is about.

The check the tests run:

assert err <= 2e-5, f"ERROR: controller doing IMEX SDC iteration did not reduce the error enough, got {err}"