Coverage for pySDC/tutorial/step_2/A_step_data_structure.py: 100%

23 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 A: Step data structure 

12# 

13# Everything in pySDC is built from **steps**. A step represents one time step with whatever hierarchy we 

14# prescribe: it holds one or more **levels** (several of them later, for multilevel SDC), and each level holds the 

15# problem class, the sweeper, the values at the collocation nodes and its parameters. 

16# 

17# :::{note} 

18# This part is for demonstration only: users normally never touch these data structures themselves. The controller 

19# in [Part C](C_using_pySDCs_frontend) does it for them. 

20# ::: 

21# 

22# ## What SDC iterates 

23# 

24# [Step 1](../step_1) ended with the collocation problem of one time step, solved directly. For a general 

25# right-hand side $f$, it reads 

26# 

27# $$ 

28# \vec u = \vec u_0 + \Delta t\, Q\, \vec f(\vec u) , 

29# $$ 

30# 

31# with $\vec u$ the values at the $M$ nodes. SDC solves it iteratively: it replaces the full matrix $Q$, which 

32# couples all nodes, by a lower-triangular approximation $Q_\Delta$, and corrects for the difference with the last 

33# iterate, 

34# 

35# $$ 

36# \vec u^{k+1} - \Delta t\, Q_\Delta\, \vec f(\vec u^{k+1}) = \vec u_0 + \Delta t\, (Q - Q_\Delta)\, \vec f(\vec u^{k}) . 

37# $$ 

38# 

39# Because $Q_\Delta$ is lower triangular, this is solved node by node, from the first to the last: one **sweep**, in 

40# which each node needs one implicit solve the size of the spatial problem, like a step of implicit Euler. Once the 

41# iteration has converged, $\vec u^{k+1} = \vec u^k$, the $Q_\Delta$ terms cancel, and the result solves the 

42# collocation problem, whatever $Q_\Delta$ is. $Q_\Delta$ is the **preconditioner** of the iteration: it decides how 

43# fast SDC converges, not what it converges to. The sweeper parameter `QI` chooses it: `'IE'` is implicit Euler 

44# from node to node, `'LU'`, used below, takes it from the LU decomposition of $Q^T$ (the "LU trick"). How far an 

45# iterate is from the solution is measured by the **residual** 

46# $\|\vec u_0 + \Delta t\, Q\, \vec f(\vec u^{k}) - \vec u^{k}\|$. [Part B](B_my_first_sweeper) runs this iteration by hand. 

47# 

48# ## The description 

49# 

50# A step is created from a single dictionary, the **description**. It names the classes to use and gives each of 

51# them its parameters, again as dictionaries. 

52 

53# %% 

54from pySDC.core.step import Step 

55from pySDC.implementations.problem_classes.HeatEquation_ND_FD import heatNd_unforced 

56from pySDC.implementations.sweeper_classes.generic_implicit import generic_implicit 

57 

58# initialize level parameters 

59level_params = { 

60 'restol': 1e-10, # stop iterating when the residual is below this 

61 'dt': 0.1, # time-step size 

62} 

63 

64# initialize sweeper parameters 

65sweeper_params = { 

66 'quad_type': 'RADAU-RIGHT', # collocation node type 

67 'num_nodes': 3, # number of collocation nodes 

68 'QI': 'LU', # preconditioner of the implicit sweep 

69} 

70 

71# initialize problem parameters 

72problem_params = { 

73 'nu': 0.1, # diffusion coefficient 

74 'freq': 4, # frequency for the test value 

75 'nvars': 1023, # number of degrees of freedom 

76 'bc': 'dirichlet-zero', # boundary conditions 

77} 

78 

79# initialize step parameters 

80step_params = { 

81 'maxiter': 20, # maximum number of iterations 

82} 

83 

84# fill description dictionary for easy step instantiation 

85description = { 

86 'problem_class': heatNd_unforced, 

87 'problem_params': problem_params, 

88 'sweeper_class': generic_implicit, 

89 'sweeper_params': sweeper_params, 

90 'level_params': level_params, 

91 'step_params': step_params, 

92} 

93 

94# %% [markdown] 

95# Note that the description contains *classes*, not instances: `heatNd_unforced`, not `heatNd_unforced(...)`. pySDC 

96# instantiates them itself, with the parameters from the description. Passing classes around makes life much easier, 

97# even if it is not the textbook way to program. 

98# 

99# ## Creating a step 

100# 

101# That is all a step needs: 

102 

103# %% 

104# now the description contains more or less everything we need to create a step 

105S = Step(description=description) 

106 

107# we only have a single level, make a shortcut 

108L = S.levels[0] 

109 

110# one of the integral parts of each level is the problem class, make a shortcut 

111P = L.prob 

112 

113print(f'Step with {len(S.levels)} level(s), at most {S.params.maxiter} iterations') 

114print(f' └─ level {L.level_index}: dt = {L.params.dt}, restol = {L.params.restol}') 

115print(f' ├─ problem: {type(P).__name__} with {P.nvars[0]} unknowns') 

116print(f' ├─ sweeper: {type(L.sweep).__name__}, {L.sweep.coll.num_nodes} {L.sweep.params.quad_type} nodes') 

117print(f' └─ u, f: values at the initial time and the {len(L.u) - 1} nodes, still empty: {L.u}') 

118 

119# %% [markdown] 

120# The level holds a list `u` with one entry per collocation node, plus one at the front for the initial value, and 

121# the same for the right-hand side `f`. They are empty until someone starts the iteration, which we do in 

122# [Part B](B_my_first_sweeper). 

123# 

124# ## The problem inside the step 

125# 

126# The problem class inside the level is an ordinary instance, created from `problem_params`. We can run the same 

127# operator test on it as in [Step 1, Part A](../step_1/A_spatial_problem_setup): 

128 

129# %% 

130import numpy as np 

131 

132# fill u with a sine wave, and compare eval_f with the exact Laplacian 

133x = np.array([(i + 1) * P.dx for i in range(P.nvars[0])]) 

134u = P.dtype_u(init=P.init) 

135u[:] = np.sin(np.pi * P.freq[0] * x) 

136err = abs(P.eval_f(u, 0) + (np.pi * P.freq[0]) ** 2 * P.nu * u) 

137print(f'Error of the spatial accuracy test: {err:8.6e}') 

138 

139# %% [markdown] 

140# Same problem, same parameters, same error: the step built exactly what we built by hand before. 

141# 

142# ## Summary 

143# 

144# - The `description` dictionary is the central steering tool of pySDC. 

145# - A step holds levels; a level holds the problem, the sweeper, the values at the nodes, parameters and status. 

146# - Classes, not instances, go into the description. 

147# 

148# The check the tests run: 

149 

150# %% 

151assert err <= 2e-04, f"ERROR: the spatial accuracy is higher than expected, got {err}"