Coverage for pySDC/implementations/problem_classes/VorticityVelocity_2D_FEniCS_periodic.py: 79%

111 statements  

« prev     ^ index     » next       coverage.py v7.16.2, created at 2026-10-17 13:41 +0000

1import logging 

2 

3import dolfin as df 

4import numpy as np 

5 

6from pySDC.core.problem import Problem 

7from pySDC.implementations.datatype_classes.fenics_mesh import fenics_mesh, rhs_fenics_mesh 

8 

9 

10# noinspection PyUnusedLocal 

11class fenics_vortex_2d(Problem): 

12 r""" 

13 2D periodic vorticity-velocity problem with FEniCS, IMEX (diffusion implicit), with the mass matrix inverted. 

14 

15 This class implements the vorticity-velocity problem in two dimensions with periodic boundary conditions 

16 in :math:`[0, 1]^2` 

17 

18 .. math:: 

19 \frac{\partial w}{\partial t} = \nu \Delta w 

20 

21 for some parameter :math:`\nu`. In this class the problem is implemented that the spatial part is solved 

22 using ``FEniCS`` [#]_. Hence, the problem is reformulated to the *weak formulation* 

23 

24 .. math:: 

25 \int_\Omega w_t v\,dx = - \nu \int_\Omega \nabla w \nabla v\,dx 

26 

27 This problem class treats the PDE in an IMEX way, with diffusion being the implicit part and everything else the explicit one. 

28 The mass matrix needs inversion for this type of problem class, see the derived one for the mass-matrix version without inversion. 

29 

30 Parameters 

31 ---------- 

32 c_nvars : tuple of int, optional 

33 Number of cells of the coarse mesh in :math:`x` and :math:`y` direction, e.g. ``c_nvars=(128, 128)``. 

34 family : str, optional 

35 Indicates the family of elements used to create the function space 

36 for the trail and test functions. The default is ``'CG'``, which are the class 

37 of Continuous Galerkin, a *synonym* for the Lagrange family of elements, see [#]_. 

38 order : int, optional 

39 Defines the order of the elements in the function space. 

40 refinements : int, optional 

41 Denotes the refinement of the mesh. ``refinements=2`` refines the mesh by factor :math:`2`. 

42 nu : float, optional 

43 Diffusion coefficient :math:`\nu`. 

44 rho : int, optional 

45 Steepness of the shear layers in the initial condition. 

46 delta : float, optional 

47 Amplitude of the perturbation in the initial condition. 

48 

49 Attributes 

50 ---------- 

51 V : FunctionSpace 

52 Defines the function space of the trial and test functions. 

53 M : scalar, vector, matrix or higher rank tensor 

54 Mass matrix for FENiCS. 

55 K : scalar, vector, matrix or higher rank tensor 

56 Stiffness matrix including diffusion coefficient (and correct sign). 

57 dtype_u : fenics_mesh 

58 FEniCS mesh data type. 

59 dtype_f : rhs_fenics_mesh 

60 FEniCS mesh data type with implicit and explicit parts. 

61 

62 References 

63 ---------- 

64 .. [#] The FEniCS Project Version 1.5. M. S. Alnaes, J. Blechta, J. Hake, A. Johansson, B. Kehlet, A. Logg, 

65 C. Richardson, J. Ring, M. E. Rognes, G. N. Wells. Archive of Numerical Software (2015). 

66 .. [#] Automated Solution of Differential Equations by the Finite Element Method. A. Logg, K.-A. Mardal, G. N. 

67 Wells and others. Springer (2012). 

68 """ 

69 

70 dtype_u = fenics_mesh 

71 dtype_f = rhs_fenics_mesh 

72 

73 def __init__(self, c_nvars=None, family='CG', order=4, refinements=None, nu=0.01, rho=50, delta=0.05): 

74 """Initialization routine""" 

75 

76 if c_nvars is None: 

77 c_nvars = (32, 32) 

78 

79 if refinements is None: 

80 refinements = 1 

81 

82 # Subdomain for Periodic boundary condition 

83 class PeriodicBoundary(df.SubDomain): 

84 # Left boundary is "target domain" G 

85 def inside(self, x, on_boundary): 

86 # return True if on left or bottom boundary AND NOT on one of the two corners (0, 1) and (1, 0) 

87 return bool( 

88 (df.near(x[0], 0) or df.near(x[1], 0)) 

89 and (not ((df.near(x[0], 0) and df.near(x[1], 1)) or (df.near(x[0], 1) and df.near(x[1], 0)))) 

90 and on_boundary 

91 ) 

92 

93 def map(self, x, y): 

94 if df.near(x[0], 1) and df.near(x[1], 1): 

95 y[0] = x[0] - 1.0 

96 y[1] = x[1] - 1.0 

97 elif df.near(x[0], 1): 

98 y[0] = x[0] - 1.0 

99 y[1] = x[1] 

100 else: # near(x[1], 1) 

101 y[0] = x[0] 

102 y[1] = x[1] - 1.0 

103 

104 # set logger level for FFC and dolfin 

105 logging.getLogger('FFC').setLevel(logging.WARNING) 

106 logging.getLogger('UFL').setLevel(logging.WARNING) 

107 

108 # set solver and form parameters 

109 df.parameters["form_compiler"]["optimize"] = True 

110 df.parameters["form_compiler"]["cpp_optimize"] = True 

111 

112 # set mesh and refinement (for multilevel) 

113 mesh = df.UnitSquareMesh(c_nvars[0], c_nvars[1]) 

114 for _ in range(refinements): 

115 mesh = df.refine(mesh) 

116 

117 self.mesh = df.Mesh(mesh) 

118 

119 # define function space for future reference 

120 self.V = df.FunctionSpace(mesh, family, order, constrained_domain=PeriodicBoundary()) 

121 tmp = df.Function(self.V) 

122 print('DoFs on this level:', len(tmp.vector()[:])) 

123 self.fix_bc_for_residual = False 

124 

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

126 super(fenics_vortex_2d, self).__init__(self.V) 

127 self._makeAttributeAndRegister( 

128 'c_nvars', 'family', 'order', 'refinements', 'nu', 'rho', 'delta', localVars=locals(), readOnly=True 

129 ) 

130 

131 w = df.TrialFunction(self.V) 

132 v = df.TestFunction(self.V) 

133 

134 # Stiffness term (diffusion) 

135 a_K = df.inner(df.nabla_grad(w), df.nabla_grad(v)) * df.dx 

136 

137 # Mass term 

138 a_M = w * v * df.dx 

139 

140 self.M = df.assemble(a_M) 

141 self.K = df.assemble(a_K) 

142 

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

144 r""" 

145 Dolfin's linear solver for :math:`(M - factor \cdot A)\vec{u} = \vec{rhs}`. 

146 

147 Parameters 

148 ---------- 

149 rhs : dtype_f 

150 Right-hand side for the nonlinear system. 

151 factor : float 

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

153 u0 : dtype_u 

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

155 t : float 

156 Current time. 

157 

158 Returns 

159 ------- 

160 u : dtype_u 

161 The solution as mesh. 

162 """ 

163 

164 A = self.M + self.nu * factor * self.K 

165 b = self.apply_mass_matrix(rhs) 

166 u = self.dtype_u(u0) 

167 df.solve(A, u.values.vector(), b.values.vector()) 

168 

169 return u 

170 

171 def __eval_fexpl(self, u, t): 

172 """ 

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

174 

175 Parameters 

176 ---------- 

177 u : dtype_u 

178 Current values of the numerical solution. 

179 t : float 

180 Current time at which the numerical solution is computed. 

181 

182 Returns 

183 ------- 

184 fexpl : dtype_u 

185 Explicit part of the right-hand side. 

186 """ 

187 

188 b = self.apply_mass_matrix(u) 

189 psi = self.dtype_u(self.V) 

190 df.solve(self.K, psi.values.vector(), b.values.vector()) 

191 

192 fexpl = self.dtype_u(self.V) 

193 fexpl.values = df.project( 

194 df.Dx(psi.values, 1) * df.Dx(u.values, 0) - df.Dx(psi.values, 0) * df.Dx(u.values, 1), self.V 

195 ) 

196 

197 return fexpl 

198 

199 def __eval_fimpl(self, u, t): 

200 """ 

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

202 

203 Parameters 

204 ---------- 

205 u : dtype_u 

206 Current values of the numerical solution. 

207 t : float 

208 Current time at which the numerical solution is computed. 

209 

210 Returns 

211 ------- 

212 fimpl : dtype_u 

213 Implicit part of the right-hand side. 

214 """ 

215 

216 A = -self.nu * self.K 

217 fimpl = self.dtype_u(self.V) 

218 A.mult(u.values.vector(), fimpl.values.vector()) 

219 fimpl = self.__invert_mass_matrix(fimpl) 

220 

221 return fimpl 

222 

223 def eval_f(self, u, t): 

224 """ 

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

226 

227 Parameters 

228 ---------- 

229 u : dtype_u 

230 Current values of the numerical solution. 

231 t : float 

232 Current time at which the numerical solution is computed. 

233 

234 Returns 

235 ------- 

236 f : dtype_f 

237 The right-hand side divided into two parts. 

238 """ 

239 

240 f = self.dtype_f(self.V) 

241 f.impl = self.__eval_fimpl(u, t) 

242 f.expl = self.__eval_fexpl(u, t) 

243 return f 

244 

245 def apply_mass_matrix(self, u): 

246 r""" 

247 Routine to apply mass matrix. 

248 

249 Parameters 

250 ---------- 

251 u : dtype_u 

252 Current values of the numerical solution. 

253 

254 Returns 

255 ------- 

256 me : dtype_u 

257 The product :math:`M\vec{u}`. 

258 """ 

259 

260 me = self.dtype_u(self.V) 

261 self.M.mult(u.values.vector(), me.values.vector()) 

262 

263 return me 

264 

265 def __invert_mass_matrix(self, u): 

266 """ 

267 Helper routine to invert mass matrix. 

268 

269 Parameters 

270 ---------- 

271 u : dtype_u 

272 Current values of the numerical solution. 

273 

274 Returns 

275 ------- 

276 me : dtype_u 

277 The product :math:`M^{-1} \vec{u}`. 

278 """ 

279 

280 me = self.dtype_u(self.V) 

281 df.solve(self.M, me.values.vector(), u.values.vector()) 

282 return me 

283 

284 def u_exact(self, t): 

285 r""" 

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

287 

288 Parameters 

289 ---------- 

290 t : float 

291 Time of the exact solution. 

292 

293 Returns 

294 ------- 

295 me : dtype_u 

296 The exact solution. 

297 """ 

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

299 

300 w = df.Expression( 

301 'r*(1-pow(tanh(r*((0.75-4) - x[1])),2)) + r*(1-pow(tanh(r*(x[1] - (0.25-4))),2)) - \ 

302 r*(1-pow(tanh(r*((0.75-3) - x[1])),2)) + r*(1-pow(tanh(r*(x[1] - (0.25-3))),2)) - \ 

303 r*(1-pow(tanh(r*((0.75-2) - x[1])),2)) + r*(1-pow(tanh(r*(x[1] - (0.25-2))),2)) - \ 

304 r*(1-pow(tanh(r*((0.75-1) - x[1])),2)) + r*(1-pow(tanh(r*(x[1] - (0.25-1))),2)) - \ 

305 r*(1-pow(tanh(r*((0.75-0) - x[1])),2)) + r*(1-pow(tanh(r*(x[1] - (0.25-0))),2)) - \ 

306 r*(1-pow(tanh(r*((0.75+1) - x[1])),2)) + r*(1-pow(tanh(r*(x[1] - (0.25+1))),2)) - \ 

307 r*(1-pow(tanh(r*((0.75+2) - x[1])),2)) + r*(1-pow(tanh(r*(x[1] - (0.25+2))),2)) - \ 

308 r*(1-pow(tanh(r*((0.75+3) - x[1])),2)) + r*(1-pow(tanh(r*(x[1] - (0.25+3))),2)) - \ 

309 r*(1-pow(tanh(r*((0.75+4) - x[1])),2)) + r*(1-pow(tanh(r*(x[1] - (0.25+4))),2)) - \ 

310 d*2*a*cos(2*a*(x[0]+0.25))', 

311 d=self.delta, 

312 r=self.rho, 

313 a=np.pi, 

314 degree=self.order, 

315 ) 

316 

317 me = self.dtype_u(self.V) 

318 me.values = df.interpolate(w, self.V) 

319 

320 # df.plot(me.values) 

321 # df.interactive() 

322 # exit() 

323 

324 return me 

325 

326 

327class fenics_vortex_2d_mass(fenics_vortex_2d): 

328 r""" 

329 2D periodic vorticity-velocity problem with FEniCS, IMEX (diffusion implicit), with the mass matrix applied. 

330 

331 This class implements the vorticity-velocity problem in two dimensions with periodic boundary conditions 

332 in :math:`[0, 1]^2` 

333 

334 .. math:: 

335 \frac{\partial w}{\partial t} = \nu \Delta w 

336 

337 for some parameter :math:`\nu`. In this class the problem is implemented that the spatial part is solved 

338 using ``FEniCS`` [#]_. Hence, the problem is reformulated to the *weak formulation* 

339 

340 .. math:: 

341 \int_\Omega w_t v\,dx = - \nu \int_\Omega \nabla w \nabla v\,dx 

342 

343 This problem class treats the PDE in an IMEX way, with diffusion being the implicit part and everything else the explicit one. 

344 No mass matrix inversion is needed here, i.e. using this problem class requires the imex_1st_order_mass sweeper. 

345 

346 Parameters 

347 ---------- 

348 c_nvars : tuple of int, optional 

349 Number of cells of the coarse mesh in :math:`x` and :math:`y` direction, e.g. ``c_nvars=(128, 128)``. 

350 family : str, optional 

351 Indicates the family of elements used to create the function space 

352 for the trail and test functions. The default is ``'CG'``, which are the class 

353 of Continuous Galerkin, a *synonym* for the Lagrange family of elements, see [#]_. 

354 order : int, optional 

355 Defines the order of the elements in the function space. 

356 refinements : int, optional 

357 Denotes the refinement of the mesh. ``refinements=2`` refines the mesh by factor :math:`2`. 

358 nu : float, optional 

359 Diffusion coefficient :math:`\nu`. 

360 rho : int, optional 

361 Steepness of the shear layers in the initial condition. 

362 delta : float, optional 

363 Amplitude of the perturbation in the initial condition. 

364 

365 Attributes 

366 ---------- 

367 V : FunctionSpace 

368 Defines the function space of the trial and test functions. 

369 M : scalar, vector, matrix or higher rank tensor 

370 Mass matrix for FENiCS. 

371 K : scalar, vector, matrix or higher rank tensor 

372 Stiffness matrix including diffusion coefficient (and correct sign). 

373 dtype_u : fenics_mesh 

374 FEniCS mesh data type. 

375 dtype_f : rhs_fenics_mesh 

376 FEniCS mesh data type with implicit and explicit parts. 

377 

378 References 

379 ---------- 

380 .. [#] The FEniCS Project Version 1.5. M. S. Alnaes, J. Blechta, J. Hake, A. Johansson, B. Kehlet, A. Logg, 

381 C. Richardson, J. Ring, M. E. Rognes, G. N. Wells. Archive of Numerical Software (2015). 

382 .. [#] Automated Solution of Differential Equations by the Finite Element Method. A. Logg, K.-A. Mardal, G. N. 

383 Wells and others. Springer (2012). 

384 """ 

385 

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

387 r""" 

388 Dolfin's linear solver for :math:`(M - factor \cdot A)\vec{u} = \vec{rhs}`. 

389 

390 Parameters 

391 ---------- 

392 rhs : dtype_f 

393 Right-hand side for the nonlinear system. 

394 factor : float 

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

396 u0 : dtype_u 

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

398 t : float 

399 Current time. 

400 

401 Returns 

402 ------- 

403 u : dtype_u 

404 The solution as mesh. 

405 """ 

406 

407 A = self.M + self.nu * factor * self.K 

408 b = self.dtype_u(rhs) 

409 u = self.dtype_u(u0) 

410 df.solve(A, u.values.vector(), b.values.vector()) 

411 

412 return u 

413 

414 def __eval_fexpl(self, u, t): 

415 """ 

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

417 

418 Parameters 

419 ---------- 

420 u : dtype_u 

421 Current values of the numerical solution. 

422 t : float 

423 Current time at which the numerical solution is computed. 

424 

425 Returns 

426 ------- 

427 fexpl : dtype_u 

428 Explicit part of the right-hand side. 

429 """ 

430 

431 b = self.apply_mass_matrix(u) 

432 psi = self.dtype_u(self.V) 

433 df.solve(self.K, psi.values.vector(), b.values.vector()) 

434 

435 # Assemble the load vector of the advection term directly. The previous version projected the 

436 # expression onto V and multiplied the result by M again, which is M M^-1 = identity: it paid 

437 # for a mass solve per right-hand side evaluation and produced the same vector. It is also the 

438 # form FFC miscompiles under PFASST, emitting a duplicate J_c1 declaration. 

439 # u may have been handed over from another step, and fenics_mesh copies keep the function 

440 # space they were created on. Under PFASST every step owns a separate (but identical) mesh, so 

441 # a form built straight from u.values would mix two meshes and UFL refuses to order them. 

442 # Copying the coefficients onto this problem's own space keeps the form single-domain. 

443 w = df.Function(self.V) 

444 w.vector()[:] = u.values.vector()[:] 

445 

446 v = df.TestFunction(self.V) 

447 expr = df.Dx(psi.values, 1) * df.Dx(w, 0) - df.Dx(psi.values, 0) * df.Dx(w, 1) 

448 

449 fexpl = self.dtype_u(self.V) 

450 fexpl.values = df.Function(self.V, df.assemble(expr * v * df.dx)) 

451 

452 return fexpl 

453 

454 def __eval_fimpl(self, u, t): 

455 """ 

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

457 

458 Parameters 

459 ---------- 

460 u : dtype_u 

461 Current values of the numerical solution. 

462 t : float 

463 Current time at which the numerical solution is computed. 

464 

465 Returns 

466 ------- 

467 fimpl : dtype_u 

468 Implicit part of the right-hand side. 

469 """ 

470 

471 A = -self.nu * self.K 

472 fimpl = self.dtype_u(self.V) 

473 A.mult(u.values.vector(), fimpl.values.vector()) 

474 

475 return fimpl 

476 

477 def eval_f(self, u, t): 

478 """ 

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

480 

481 Note: Need to add this here, because otherwise the parent class will call the "local" functions __eval_* 

482 and not the ones of the child class. 

483 

484 Parameters 

485 ---------- 

486 u : dtype_u 

487 Current values of the numerical solution. 

488 t : float 

489 Current time at which the numerical solution is computed. 

490 

491 Returns 

492 ------- 

493 f : dtype_f 

494 The right-hand side divided into two parts. 

495 """ 

496 

497 f = self.dtype_f(self.V) 

498 f.impl = self.__eval_fimpl(u, t) 

499 f.expl = self.__eval_fexpl(u, t) 

500 return f