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

1# --- 

2# jupyter: 

3# jupytext: 

4# formats: py:percent 

5# kernelspec: 

6# display_name: Python 3 

7# name: python3 

8# --- 

9 

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). 

22 

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 

28 

29# initialize level parameters 

30level_params = {'restol': 1e-10, 'dt': 0.1} 

31 

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} 

38 

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} 

46 

47# initialize step parameters 

48step_params = {'maxiter': 20} 

49 

50# initialize space transfer parameters 

51space_transfer_params = {'rorder': 2, 'iorder': 2} 

52 

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} 

64 

65# now the description contains more or less everything we need to create a step with multiple levels 

66S = Step(description=description) 

67 

68# %% [markdown] 

69# During the setup, dictionaries with list entries are turned into lists of dictionaries, one for each level: 

70 

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)) 

75 

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: 

89 

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}"