Coverage for pySDC/implementations/problem_classes/Boussinesq_2D_FD_imex.py: 99%

67 statements  

« prev     ^ index     » next       coverage.py v7.17.0, created at 2026-11-02 12:30 +0000

1import numpy as np 

2from scipy.sparse.linalg import gmres 

3 

4from pySDC.core.problem import Problem 

5from pySDC.implementations.datatype_classes.mesh import mesh, imex_mesh 

6from pySDC.implementations.problem_classes.boussinesq_helpers.build2DFDMatrix import get2DMesh 

7from pySDC.implementations.problem_classes.boussinesq_helpers.buildBoussinesq2DMatrix import getBoussinesq2DMatrix 

8from pySDC.implementations.problem_classes.boussinesq_helpers.buildBoussinesq2DMatrix import getBoussinesq2DUpwindMatrix 

9from pySDC.implementations.problem_classes.boussinesq_helpers.helper_classes import Callback, logging 

10from pySDC.implementations.problem_classes.boussinesq_helpers.unflatten import unflatten 

11 

12 

13# noinspection PyUnusedLocal 

14class boussinesq_2d_imex(Problem): 

15 r""" 

16 Linearized 2D Boussinesq equations with finite differences, IMEX with waves implicit (GMRES), advection explicit. 

17 

18 The equations, for different boundary conditions, are 

19 

20 .. math:: 

21 \frac{\partial u}{\partial t} + U \frac{\partial u}{\partial x} + \frac{\partial p}{\partial x} = 0, 

22 

23 .. math:: 

24 \frac{\partial w}{\partial t} + U \frac{\partial w}{\partial x} + \frac{\partial p}{\partial z} = 0, 

25 

26 .. math:: 

27 \frac{\partial b}{\partial t} + U \frac{\partial b}{\partial x} + N^2 w = 0, 

28 

29 .. math:: 

30 \frac{\partial p}{\partial t} + U \frac{\partial p}{\partial x} + c^2 (\frac{\partial u}{\partial x} + \frac{\partial w}{\partial z}) = 0. 

31 

32 They can be derived from the linearized Euler equations by a transformation of variables [1]_. 

33 

34 Parameters 

35 ---------- 

36 nvars : list of tuple, optional 

37 List of number of unknowns nvars, e.g. ``nvars=[(4, 300, 3)]``. 

38 c_s : float, optional 

39 Acoustic velocity :math:`c_s`. 

40 u_adv : float, optional 

41 Advection speed :math:`U`. 

42 Nfreq : float, optional 

43 Stability frequency. 

44 x_bounds : list, optional 

45 Domain in x-direction. 

46 z_bounds : list, optional 

47 Domain in z-direction. 

48 order_upw : int, optional 

49 Order of upwind scheme for discretization. 

50 order : int, optional 

51 Order for discretization. 

52 gmres_maxiter : int, optional 

53 Maximum number of iterations for GMRES solver. 

54 gmres_restart : int, optional 

55 Number of iterations between restarts in GMRES solver. 

56 gmres_tol_limit : float, optional 

57 Tolerance for GMRES solver to terminate. 

58 

59 Attributes 

60 ---------- 

61 N : list 

62 List of number of unknowns. 

63 bc_hor : list 

64 Contains type of boundary conditions for both boundaries for both dimensions. 

65 bc_ver : 

66 Contains type of boundary conditions for both boundaries for both dimemsions, e.g. ``'neumann'`` or ``'dirichlet'``. 

67 xx : np.ndarray 

68 List of np.ndarrays for mesh in x-direction. 

69 zz : np.ndarray 

70 List of np.ndarrays for mesh in z-direction. 

71 h : float 

72 Mesh size. 

73 Id : sp.sparse.eye 

74 Identity matrix for the equation of appropriate size. 

75 M : np.ndarray 

76 Boussinesq 2D Matrix. 

77 D_upwind : sp.csc_matrix 

78 Boussinesq 2D Upwind matrix for discretization. 

79 gmres_logger : object 

80 Logger for GMRES solver. 

81 

82 References 

83 ---------- 

84 .. [1] D. R. Durran. Numerical Methods for Fluid Dynamics. Texts Appl. Math. 32. Springer-Verlag, New York (2010) 

85 http://dx.doi.org/10.1007/978-1-4419-6412-0. 

86 """ 

87 

88 dtype_u = mesh 

89 dtype_f = imex_mesh 

90 

91 def __init__( 

92 self, 

93 nvars=None, 

94 c_s=0.3, 

95 u_adv=0.02, 

96 Nfreq=0.01, 

97 x_bounds=None, 

98 z_bounds=None, 

99 order_upw=5, 

100 order=4, 

101 gmres_maxiter=500, 

102 gmres_restart=10, 

103 gmres_tol_limit=1e-5, 

104 ): 

105 """Initialization routine""" 

106 

107 if nvars is None: 

108 nvars = (4, 300, 30) 

109 

110 if x_bounds is None: 

111 x_bounds = (-150.0, 150.0) 

112 

113 if z_bounds is None: 

114 z_bounds = (0.0, 10.0) 

115 

116 # invoke super init, passing number of dofs, dtype_u and dtype_f 

117 super().__init__(init=(nvars, None, np.dtype('float64'))) 

118 self._makeAttributeAndRegister( 

119 'nvars', 

120 'c_s', 

121 'u_adv', 

122 'Nfreq', 

123 'x_bounds', 

124 'z_bounds', 

125 'order_upw', 

126 'order', 

127 'gmres_maxiter', 

128 'gmres_restart', 

129 localVars=locals(), 

130 readOnly=True, 

131 ) 

132 # not read-only: rungmrescounter_boussinesq tightens it for its reference solution 

133 self._makeAttributeAndRegister('gmres_tol_limit', localVars=locals()) 

134 

135 self.N = [self.nvars[1], self.nvars[2]] 

136 

137 self.bc_hor = [ 

138 ['periodic', 'periodic'], 

139 ['periodic', 'periodic'], 

140 ['periodic', 'periodic'], 

141 ['periodic', 'periodic'], 

142 ] 

143 self.bc_ver = [ 

144 ['neumann', 'neumann'], 

145 ['dirichlet', 'dirichlet'], 

146 ['dirichlet', 'dirichlet'], 

147 ['neumann', 'neumann'], 

148 ] 

149 

150 self.xx, self.zz, self.h = get2DMesh(self.N, self.x_bounds, self.z_bounds, self.bc_hor[0], self.bc_ver[0]) 

151 

152 self.Id, self.M = getBoussinesq2DMatrix( 

153 self.N, self.h, self.bc_hor, self.bc_ver, self.c_s, self.Nfreq, self.order 

154 ) 

155 self.D_upwind = getBoussinesq2DUpwindMatrix(self.N, self.h[0], self.u_adv, self.order_upw) 

156 

157 self.gmres_logger = logging() 

158 

159 def solve_system(self, rhs, factor, u0, t): 

160 r""" 

161 Simple linear solver for :math:`(I - factor \cdot A) \vec{u} = \vec{rhs}` using GMRES. 

162 

163 Parameters 

164 ---------- 

165 rhs : dtype_f 

166 Right-hand side for the nonlinear system. 

167 factor : float 

168 Abbrev. for the node-to-node stepsize (or any other factor required). 

169 u0 : dtype_u 

170 Initial guess for the iterative solver (not used here so far). 

171 t : float 

172 Current time (e.g. for time-dependent BCs). 

173 

174 Returns 

175 ------- 

176 me : dtype_u 

177 The solution as mesh. 

178 """ 

179 

180 b = rhs.flatten() 

181 cb = Callback() 

182 

183 sol, info = gmres( 

184 self.Id - factor * self.M, 

185 b, 

186 x0=u0.flatten(), 

187 rtol=self.gmres_tol_limit, 

188 restart=self.gmres_restart, 

189 maxiter=self.gmres_maxiter, 

190 atol=0, 

191 callback=cb, 

192 ) 

193 # If this is a dummy call with factor==0.0, do not log because it should not be counted as a solver call 

194 if factor != 0.0: 

195 self.gmres_logger.add(cb.getcounter()) 

196 me = self.dtype_u(self.init) 

197 me[:] = unflatten(sol, 4, self.N[0], self.N[1]) 

198 

199 return me 

200 

201 def __eval_fexpl(self, u, t): 

202 """ 

203 Helper routine to evaluate the explicit part of the right-hand side. 

204 

205 Parameters 

206 ---------- 

207 u : dtype_u 

208 Current values of the numerical solution. 

209 t : float 

210 Current time at which the numerical solution is computed (not used here). 

211 

212 Returns 

213 ------- 

214 fexpl : dtype_u 

215 Explicit part of right-hand side. 

216 """ 

217 

218 # Evaluate right hand side 

219 fexpl = self.dtype_u(self.init) 

220 temp = u.flatten() 

221 temp = self.D_upwind.dot(temp) 

222 fexpl[:] = unflatten(temp, 4, self.N[0], self.N[1]) 

223 

224 return fexpl 

225 

226 def __eval_fimpl(self, u, t): 

227 """ 

228 Helper routine to evaluate the implicit part of the right-hand side. 

229 

230 Parameters 

231 ---------- 

232 u : dtype_u 

233 Current values of the numerical solution. 

234 t : float 

235 Current time at which the numerical solution is computed (not used here). 

236 

237 Returns 

238 ------- 

239 fexpl : dtype_u 

240 Implicit part of right-hand side. 

241 """ 

242 

243 temp = u.flatten() 

244 temp = self.M.dot(temp) 

245 fimpl = self.dtype_u(self.init) 

246 fimpl[:] = unflatten(temp, 4, self.N[0], self.N[1]) 

247 

248 return fimpl 

249 

250 def eval_f(self, u, t): 

251 """ 

252 Routine to evaluate both parts of the right-hand side. 

253 

254 Parameters 

255 ---------- 

256 u : dtype_u 

257 Current values of the numerical solution. 

258 t : float 

259 Current time the numerical solution is computed. 

260 

261 Returns 

262 ------- 

263 f : dtype_f 

264 Right-hand side divided into two parts. 

265 """ 

266 

267 f = self.dtype_f(self.init) 

268 f.impl[:] = self.__eval_fimpl(u, t) 

269 f.expl[:] = self.__eval_fexpl(u, t) 

270 return f 

271 

272 def u_exact(self, t): 

273 r""" 

274 Routine to compute the exact solution at time :math:`t`. 

275 

276 Parameters 

277 ---------- 

278 t : float 

279 Time of the exact solution. 

280 

281 Returns 

282 ------- 

283 me : dtype_u 

284 The exact solution. 

285 """ 

286 assert t == 0, 'ERROR: u_exact only valid for t=0' 

287 

288 dtheta = 0.01 

289 H = 10.0 

290 a = 5.0 

291 x_c = -50.0 

292 

293 me = self.dtype_u(self.init) 

294 me[0, :, :] = 0.0 * self.xx 

295 me[1, :, :] = 0.0 * self.xx 

296 # me[2,:,:] = 0.0*self.xx 

297 # me[3,:,:] = np.exp(-0.5*(self.xx-0.0)**2.0/0.15**2.0)*np.exp(-0.5*(self.zz-0.5)**2/0.15**2) 

298 # me[2,:,:] = np.exp(-0.5*(self.xx-0.0)**2.0/0.05**2.0)*np.exp(-0.5*(self.zz-0.5)**2/0.2**2) 

299 me[2, :, :] = dtheta * np.sin(np.pi * self.zz / H) / (1.0 + np.square(self.xx - x_c) / (a * a)) 

300 me[3, :, :] = 0.0 * self.xx 

301 return me