Coverage for pySDC/tutorial/step_4/A_spatial_transfer_operators.py: 100%

51 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: Spatial transfer operators 

12# 

13# Multilevel SDC works on a hierarchy of levels, for example several spatial resolutions of the same problem, and 

14# moves values between them: **restriction** from fine to coarse, **interpolation** (or prolongation) from coarse to 

15# fine. How this is done depends on the data type, so the user provides it as a `space_transfer_class`. For meshes 

16# of finite differences, pySDC has `mesh_to_mesh`, which interpolates with Lagrange polynomials. 

17# 

18# :::{note} 

19# As for the sweeper and everything else, users do not instantiate the transfer class themselves when they use a 

20# controller. We do it here to test it. 

21# ::: 

22 

23# %% 

24from collections import namedtuple 

25 

26import matplotlib.pyplot as plt 

27import numpy as np 

28 

29from pySDC.implementations.problem_classes.HeatEquation_ND_FD import heatNd_unforced 

30from pySDC.implementations.transfer_classes.TransferMesh import mesh_to_mesh 

31 

32# initialize problem parameters 

33problem_params = { 

34 'nu': 0.1, # diffusion coefficient 

35 'freq': 3, # frequency for the test value 

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

37} 

38 

39# initialize transfer parameters 

40space_transfer_params = { 

41 'rorder': 2, # order of the restriction 

42 'iorder': 4, # order of the interpolation 

43} 

44 

45# %% [markdown] 

46# ## One interpolation 

47# 

48# A fine problem and a coarse one with half the unknowns (`nvars` of the form $2^p - 1$ halve nicely, as the 

49# boundary points are not unknowns). `prolong` takes a coarse value to the fine mesh: 

50 

51# %% 

52problem_params['nvars'] = 15 

53Pfine = heatNd_unforced(**problem_params) 

54problem_params['nvars'] = 7 

55Pcoarse = heatNd_unforced(**problem_params) 

56T = mesh_to_mesh(fine_prob=Pfine, coarse_prob=Pcoarse, params=space_transfer_params) 

57 

58xc = np.array([(i + 1) * Pcoarse.dx for i in range(Pcoarse.nvars[0])]) 

59xf = np.array([(i + 1) * Pfine.dx for i in range(Pfine.nvars[0])]) 

60uc = Pcoarse.dtype_u(Pcoarse.init) 

61uc[:] = np.sin(np.pi * Pcoarse.freq[0] * xc) 

62uf = T.prolong(uc) 

63 

64fig, ax = plt.subplots(figsize=(6, 3)) 

65x = np.linspace(0, 1, 200) 

66ax.plot(x, np.sin(np.pi * problem_params['freq'] * x), color='0.7', label='exact') 

67ax.plot(xc, uc, 'o', ms=8, label='coarse, 7 unknowns') 

68ax.plot(xf, uf, 'x', ms=8, label='interpolated, 15 unknowns') 

69ax.set_xlabel('$x$') 

70ax.legend(frameon=False, fontsize=8) 

71fig.tight_layout() 

72 

73# %% [markdown] 

74# ## The order of interpolation 

75# 

76# As before, we check the order: interpolate the exact coarse values for a sequence of meshes and compare with the 

77# exact fine values. We expect 4, the `iorder` we asked for. 

78 

79# %% 

80nvars_fine_list = [2**p - 1 for p in range(5, 10)] 

81 

82# setup id for gathering the results (will sort by nvars) 

83ID = namedtuple('ID', 'nvars_fine') 

84 

85# set up dictionary to store results (plus lists) 

86results = {'nvars_list': nvars_fine_list} 

87 

88for nvars_fine in nvars_fine_list: 

89 print('Working on nvars_fine = %4i...' % nvars_fine) 

90 

91 # instantiate fine problem 

92 problem_params['nvars'] = nvars_fine # number of degrees of freedom 

93 Pfine = heatNd_unforced(**problem_params) 

94 

95 # instantiate coarse problem using half of the DOFs 

96 problem_params['nvars'] = int((nvars_fine + 1) / 2.0 - 1) 

97 Pcoarse = heatNd_unforced(**problem_params) 

98 

99 # instantiate spatial interpolation 

100 T = mesh_to_mesh(fine_prob=Pfine, coarse_prob=Pcoarse, params=space_transfer_params) 

101 

102 # set exact fine solution to compare with 

103 xvalues_fine = np.array([(i + 1) * Pfine.dx for i in range(Pfine.nvars[0])]) 

104 uexact_fine = Pfine.dtype_u(Pfine.init) 

105 uexact_fine[:] = np.sin(np.pi * Pfine.freq[0] * xvalues_fine) 

106 

107 # set exact coarse solution as source 

108 xvalues_coarse = np.array([(i + 1) * Pcoarse.dx for i in range(Pcoarse.nvars[0])]) 

109 uexact_coarse = Pfine.dtype_u(Pcoarse.init) 

110 uexact_coarse[:] = np.sin(np.pi * Pcoarse.freq[0] * xvalues_coarse) 

111 

112 # do the interpolation/prolongation 

113 uinter = T.prolong(uexact_coarse) 

114 

115 # compute error and store 

116 id = ID(nvars_fine=nvars_fine) 

117 results[id] = abs(uinter - uexact_fine) 

118 

119# %% 

120# observed order between consecutive meshes, as in step 1, part B 

121nvars = sorted(results['nvars_list']) 

122errors = [results[ID(nvars_fine=n)] for n in nvars] 

123orders = [np.log(errors[i - 1] / errors[i]) / np.log(nvars[i] / nvars[i - 1]) for i in range(1, len(nvars))] 

124 

125for p in range(len(orders)): 

126 print( 

127 'Expected order %2i, got order %5.2f, deviation of %5.2f%%' 

128 % ( 

129 space_transfer_params['iorder'], 

130 orders[p], 

131 100 * abs(space_transfer_params['iorder'] - orders[p]) / space_transfer_params['iorder'], 

132 ) 

133 ) 

134 

135# %% [markdown] 

136# :::{admonition} Important things to note 

137# - MLSDC and PFASST rely on high-order interpolation in space. With Lagrange-based interpolation, orders of 4 to 6 

138# and above are recommended. 

139# - Restriction, however, can be of order 2, and is thus not tested here. 

140# ::: 

141# 

142# :::{admonition} Try it yourself 

143# :class: tip 

144# Set `space_transfer_params['iorder'] = 6` and run the order check again. 

145# ::: 

146# 

147# :::{dropdown} Answer 

148# Sixth order, but approached from above: 6.45, 6.83, 6.39 and finally 5.98 on the finest meshes. The coarse ones 

149# are not in the asymptotic regime yet, so the check below, which allows 5% everywhere, fails. 

150# ::: 

151# 

152# The check the tests run: 

153 

154# %% 

155for order in orders: 

156 assert ( 

157 abs(space_transfer_params['iorder'] - order) / space_transfer_params['iorder'] < 0.05 

158 ), f'ERROR: did not get expected orders for interpolation, got {order}'