Coverage for pySDC/tutorial/step_4/B_multilevel_hierarchy.py: 100%
18 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# ---
2# jupyter:
3# jupytext:
4# formats: py:percent
5# kernelspec:
6# display_name: Python 3
7# name: python3
8# ---
10# %% [markdown]
11# # Part B: Multilevel hierarchy
12#
13# How does a step get several levels? From the description: wherever a problem, sweeper or level parameter is a
14# **list** instead of a single value, the step creates one level per entry, the first entry being the finest level.
15# There are two generic ways to coarsen:
16#
17# - in space: make the problem parameter `nvars` a list,
18# - in the collocation order: make the sweeper parameter `num_nodes` a list.
19#
20# A third way is to make an entry of the description itself a list, e.g. a list of problem classes; we use that in
21# [Part D](D_MLSDC_with_particles).
23# %%
24from pySDC.core.step import Step
25from pySDC.implementations.problem_classes.HeatEquation_ND_FD import heatNd_unforced
26from pySDC.implementations.sweeper_classes.generic_implicit import generic_implicit
27from pySDC.implementations.transfer_classes.TransferMesh import mesh_to_mesh
29# initialize level parameters
30level_params = {'restol': 1e-10, 'dt': 0.1}
32# initialize sweeper parameters
33sweeper_params = {
34 'quad_type': 'RADAU-RIGHT',
35 'num_nodes': [5, 3], # number of collocation nodes for each level
36 'QI': 'LU',
37}
39# initialize problem parameters
40problem_params = {
41 'nu': 0.1, # diffusion coefficient
42 'freq': 4, # frequency for the test value
43 'nvars': [31, 15, 7], # number of degrees of freedom for each level
44 'bc': 'dirichlet-zero', # boundary conditions
45}
47# initialize step parameters
48step_params = {'maxiter': 20}
50# initialize space transfer parameters
51space_transfer_params = {'rorder': 2, 'iorder': 2}
53# fill description dictionary for easy step instantiation
54description = {
55 'problem_class': heatNd_unforced,
56 'problem_params': problem_params,
57 'sweeper_class': generic_implicit,
58 'sweeper_params': sweeper_params,
59 'level_params': level_params,
60 'step_params': step_params,
61 'space_transfer_class': mesh_to_mesh, # the transfer between levels is part of the description, too
62 'space_transfer_params': space_transfer_params,
63}
65# now the description contains more or less everything we need to create a step with multiple levels
66S = Step(description=description)
68# %% [markdown]
69# During the setup, dictionaries with list entries are turned into lists of dictionaries, one for each level:
71# %%
72for l in range(len(S.levels)):
73 L = S.levels[l]
74 print('Level %2i: nvars = %4i -- nnodes = %2i' % (l, L.prob.nvars[0], L.sweep.coll.num_nodes))
76# %% [markdown]
77# Three levels, although `num_nodes` has only two entries: the longest list decides how many levels there are, and
78# levels beyond the end of a shorter list get its last entry. That is why levels 1 and 2 both have 3 nodes.
79#
80# :::{admonition} Important things to note
81# - Not all lists need the same length: the longest defines the number of levels, shorter ones are extended with
82# their last entry.
83# - Like most other parameters, `space_transfer_class` and `space_transfer_params` are part of the description.
84# - For advanced users: `base_transfer_params` passes parameters to the class that ties two levels together
85# (`BaseTransfer`, which uses the space transfer class), and `base_transfer_class` replaces that class.
86# :::
87#
88# The checks the tests run:
90# %%
91for l in range(len(S.levels)):
92 L = S.levels[l]
93 assert (
94 L.prob.nvars[0] == problem_params['nvars'][min(l, len(problem_params['nvars']) - 1)]
95 ), f"ERROR: number of DOFs is not correct on this level, got {L.prob.nvars}"
96 assert (
97 L.sweep.coll.num_nodes == sweeper_params['num_nodes'][min(l, len(sweeper_params['num_nodes']) - 1)]
98 ), f"ERROR: number of nodes is not correct on this level, got {L.sweep.coll.num_nodes}"