Part B: Multilevel hierarchy#
How does a step get several levels? From the description: wherever a problem, sweeper or level parameter is a list instead of a single value, the step creates one level per entry, the first entry being the finest level. There are two generic ways to coarsen:
in space: make the problem parameter
nvarsa list,in the collocation order: make the sweeper parameter
num_nodesa list.
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 Part D.
from pySDC.core.step import Step
from pySDC.implementations.problem_classes.HeatEquation_ND_FD import heatNd_unforced
from pySDC.implementations.sweeper_classes.generic_implicit import generic_implicit
from pySDC.implementations.transfer_classes.TransferMesh import mesh_to_mesh
# initialize level parameters
level_params = {'restol': 1e-10, 'dt': 0.1}
# initialize sweeper parameters
sweeper_params = {
'quad_type': 'RADAU-RIGHT',
'num_nodes': [5, 3], # number of collocation nodes for each level
'QI': 'LU',
}
# initialize problem parameters
problem_params = {
'nu': 0.1, # diffusion coefficient
'freq': 4, # frequency for the test value
'nvars': [31, 15, 7], # number of degrees of freedom for each level
'bc': 'dirichlet-zero', # boundary conditions
}
# initialize step parameters
step_params = {'maxiter': 20}
# initialize space transfer parameters
space_transfer_params = {'rorder': 2, 'iorder': 2}
# fill description dictionary for easy step instantiation
description = {
'problem_class': heatNd_unforced,
'problem_params': problem_params,
'sweeper_class': generic_implicit,
'sweeper_params': sweeper_params,
'level_params': level_params,
'step_params': step_params,
'space_transfer_class': mesh_to_mesh, # the transfer between levels is part of the description, too
'space_transfer_params': space_transfer_params,
}
# now the description contains more or less everything we need to create a step with multiple levels
S = Step(description=description)
During the setup, dictionaries with list entries are turned into lists of dictionaries, one for each level:
for l in range(len(S.levels)):
L = S.levels[l]
print('Level %2i: nvars = %4i -- nnodes = %2i' % (l, L.prob.nvars[0], L.sweep.coll.num_nodes))
Level 0: nvars = 31 -- nnodes = 5
Level 1: nvars = 15 -- nnodes = 3
Level 2: nvars = 7 -- nnodes = 3
Three levels, although num_nodes has only two entries: the longest list decides how many levels there are, and
levels beyond the end of a shorter list get its last entry. That is why levels 1 and 2 both have 3 nodes.
Important things to note
Not all lists need the same length: the longest defines the number of levels, shorter ones are extended with their last entry.
Like most other parameters,
space_transfer_classandspace_transfer_paramsare part of the description.For advanced users:
base_transfer_paramspasses parameters to the class that ties two levels together (BaseTransfer, which uses the space transfer class), andbase_transfer_classreplaces that class.
The checks the tests run:
for l in range(len(S.levels)):
L = S.levels[l]
assert (
L.prob.nvars[0] == problem_params['nvars'][min(l, len(problem_params['nvars']) - 1)]
), f"ERROR: number of DOFs is not correct on this level, got {L.prob.nvars}"
assert (
L.sweep.coll.num_nodes == sweeper_params['num_nodes'][min(l, len(sweeper_params['num_nodes']) - 1)]
), f"ERROR: number of nodes is not correct on this level, got {L.sweep.coll.num_nodes}"