Coverage for pySDC/projects/StroemungsRaum/problem_classes/ConvectionDiffusion_2D_FEniCS.py: 100%

59 statements  

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

1import dolfin as df 

2from pySDC.core.problem import Problem 

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

4 

5 

6class fenics_ConvDiff2D_mass(Problem): 

7 r""" 

8 Example implementing a forced two-dimensional convection-diffusion equation using Dirichlet 

9 boundary conditions. The problem considered is a benchmark test with a rotating Gaussian profile. 

10 

11 The equation we are solving is the two-dimensional convection-diffusion equation: 

12 

13 .. math:: 

14 \frac{\partial u}{\partial t} = - U \cdot \nabla u + \nu \Delta u + f 

15 

16 where: 

17 .. math:`u(x, y, t)` is the scalar field we are solving for (e.g., concentration or temperature). 

18 .. math:`U = (u, v)` is the velocity field of the flow. 

19 .. math:`\nu` is the kinematic viscosity, which quantifies the diffusion rate. 

20 .. math:`f(x, y, t)` is the source term, representing external forcing or generation of .. math:`u`. 

21 

22 The computational domain for this problem is: 

23 

24 .. math:: 

25 x \in \Omega := [-0.5, 0.5] \times [-0.5, 0.5] 

26 

27 Dirichlet boundary conditions are applied, meaning that the value of .. math:`u` is specified on the 

28 boundary of the domain. In this benchmark example, the forcing term .. math:`f` is: 

29 

30 .. math:: 

31 f(x,y,t) = 0 

32 

33 This implies there are no additional sources or sinks affecting the field .. math:`u`, simplifying the problem to just the 

34 effects of convection and diffusion. The analytical solution for the scalar field .. math:`u is given by: 

35 

36 .. math:: 

37 u(x,y,t) = \frac{\sigma^2}{\sigma^2 + 4 \nu t} \exp\left( - \frac{(\hat{x}-x_0)^2 + (\hat{y}-y_0)^2}{\sigma^2 + 4 \nu t} \right) 

38 

39 where: 

40 .. math:`\sigma` is the initial standard deviation of the Gaussian. 

41 .. math:`\omega` is the angular velocity of the rotation (in this example, :math:`\omega = 4`). 

42 .. math:`(\hat{x}, \hat{y})` are the rotated coordinates defined by: 

43 .. math:: 

44 \hat{x} = \cos(4 t) x + \sin(4 t) y \\ 

45 \hat{y} = -\sin(4 t) x + \cos(4 t) y 

46 .. math:`(x_0, y_0)` is the center of the Gaussian, given as .. math:`(-0.25, 0.0)`. 

47 

48 

49 The velocity field .. math:`U` for the rotating Gaussian is defined as: 

50 

51 .. math:: 

52 U = (u,v) = (-4*y, 4*x) 

53 

54 This represents a counter-clockwise rotation around the origin with angular velocity .. math:`\omega`. 

55 The flow causes the scalar field .. math:`u` to be advected in a circular pattern, simulating the rotation of the Gaussian. 

56 

57 

58 Parameters 

59 ---------- 

60 c_nvars : int, optional 

61 Spatial resolution, i.e., numbers of degrees of freedom in space. 

62 t0 : float, optional 

63 Starting time. 

64 family : str, optional 

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

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

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

68 order : int, optional 

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

70 sigma : float, optional 

71 Coefficient associated with the mass term or reaction term in the equation. 

72 nu : float, optional 

73 Diffusion coefficient :math:`\nu`. 

74 

75 Attributes 

76 ---------- 

77 V : FunctionSpace 

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

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

80 Denotes the expression :math:`\int_\Omega u_t v\,dx`. 

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

82 Denotes the expression :math:`- \nu \int_\Omega \nabla u \nabla v\,dx`. 

83 g : Expression 

84 The forcing term :math:`f` in the convection-diffusion equations. 

85 bc : DirichletBC 

86 Denotes the Dirichlet boundary conditions. 

87 

88 References 

89 ---------- 

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

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

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

93 Wells and others. Springer (2012). 

94 """ 

95 

96 dtype_u = fenics_mesh 

97 dtype_f = rhs_fenics_mesh 

98 

99 def __init__(self, c_nvars=64, t0=0.0, family='CG', order=2, nu=0.01, sigma=0.05): 

100 

101 # define the Dirichlet boundary 

102 def Boundary(x, on_boundary): 

103 return on_boundary 

104 

105 # set solver and form parameters 

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

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

108 df.parameters['allow_extrapolation'] = True 

109 

110 # set mesh and refinement (for multilevel) 

111 mesh = df.RectangleMesh(df.Point(-0.5, -0.5), df.Point(0.5, 0.5), c_nvars, c_nvars) 

112 

113 # define function space for future reference 

114 self.V = df.FunctionSpace(mesh, family, order) 

115 tmp = df.Function(self.V) 

116 self.logger.debug('DoFs on this level:', len(tmp.vector()[:])) 

117 

118 # define velocity 

119 self.VC = df.VectorFunctionSpace(mesh, family, order) 

120 self.U = df.interpolate(df.Expression(('-4*x[1]', '4*x[0]'), degree=order), self.VC) 

121 

122 super().__init__(self.V) 

123 self._makeAttributeAndRegister( 

124 'c_nvars', 't0', 'family', 'order', 'nu', 'sigma', localVars=locals(), readOnly=True 

125 ) 

126 

127 # define trial and test functions 

128 u = df.TrialFunction(self.V) 

129 v = df.TestFunction(self.V) 

130 

131 # mass, stiffness and convection terms 

132 a_K = -1.0 * df.inner(df.nabla_grad(u), self.nu * df.nabla_grad(v)) * df.dx 

133 a_M = u * v * df.dx 

134 a_C = -1.0 * df.inner(df.dot(self.U, df.nabla_grad(u)), v) * df.dx 

135 

136 # assemble the forms 

137 self.M = df.assemble(a_M) 

138 self.K = df.assemble(a_K) 

139 self.C = df.assemble(a_C) 

140 

141 # set exact solution for boundary conditions 

142 self.u_D = df.Expression( 

143 'pow(s,2)/(pow(s,2)+4*nu*t)*exp(-(pow(((cos(4*t)*x[0]+sin(4*t)*x[1])-x0),2)\ 

144 +pow(((-sin(4*t)*x[0]+cos(4*t)*x[1])-y0),2))/(pow(s,2)+4*nu*t))', 

145 s=self.sigma, 

146 nu=self.nu, 

147 x0=-0.25, 

148 y0=0.0, 

149 t=t0, 

150 degree=self.order, 

151 ) 

152 

153 # set boundary conditions 

154 self.bc = df.DirichletBC(self.V, self.u_D, Boundary) 

155 self.bc_hom = df.DirichletBC(self.V, df.Constant(0), Boundary) 

156 self.fix_bc_for_residual = True 

157 

158 # set forcing term as expression 

159 self.g = df.Expression('0.0', t=self.t0, degree=self.order) 

160 

161 def use_restricted_boundary_values(self, u, t): 

162 """ 

163 Refuse multilevel runs: the solves impose the time-dependent boundary data, which at the coarse nodes is not 

164 the restricted fine boundary values, so MLSDC and PFASST would not converge to the collocation solution. See 

165 :meth:`pySDC.core.problem.Problem.use_restricted_boundary_values`. 

166 """ 

167 raise NotImplementedError(f'{type(self).__name__} cannot impose restricted boundary values on coarse levels') 

168 

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

170 r""" 

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

172 

173 Parameters 

174 ---------- 

175 rhs : dtype_f 

176 Right-hand side for the nonlinear system. 

177 factor : float 

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

179 u0 : dtype_u 

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

181 t : float 

182 Current time. 

183 

184 Returns 

185 ------- 

186 u : dtype_u 

187 Solution. 

188 """ 

189 self.u_D.t = t 

190 

191 u = self.dtype_u(u0) 

192 T = self.M - factor * self.K 

193 

194 self.bc.apply(T, rhs.values.vector()) 

195 df.solve(T, u.values.vector(), rhs.values.vector()) 

196 

197 return u 

198 

199 def eval_f(self, u, t): 

200 """ 

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

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 f : dtype_f 

213 The right-hand side divided into two parts. 

214 """ 

215 self.g.t = t 

216 

217 f = self.dtype_f(self.V) 

218 self.K.mult(u.values.vector(), f.impl.values.vector()) 

219 self.C.mult(u.values.vector(), f.expl.values.vector()) 

220 

221 f.expl += self.apply_mass_matrix(self.dtype_u(df.interpolate(self.g, self.V))) 

222 

223 return f 

224 

225 def u_exact(self, t): 

226 r""" 

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

228 

229 Parameters 

230 ---------- 

231 t : float 

232 Time of the exact solution. 

233 

234 Returns 

235 ------- 

236 me : dtype_u 

237 Exact solution. 

238 """ 

239 

240 u0 = df.Expression( 

241 'pow(s,2)/(pow(s,2)+4*nu*t)*exp(-(pow(((cos(4*t)*x[0]+sin(4*t)*x[1])-x0),2)\ 

242 +pow(((-sin(4*t)*x[0]+cos(4*t)*x[1])-y0),2))/(pow(s,2)+4*nu*t))', 

243 s=self.sigma, 

244 nu=self.nu, 

245 x0=-0.25, 

246 y0=0.0, 

247 t=t, 

248 degree=self.order, 

249 ) 

250 

251 me = self.dtype_u(df.interpolate(u0, self.V)) 

252 

253 return me 

254 

255 def apply_mass_matrix(self, u): 

256 r""" 

257 Routine to apply mass matrix. 

258 

259 Parameters 

260 ---------- 

261 u : dtype_u 

262 Current values of the numerical solution. 

263 

264 Returns 

265 ------- 

266 me : dtype_u 

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

268 """ 

269 

270 me = self.dtype_u(self.V) 

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

272 

273 return me 

274 

275 def fix_residual(self, res): 

276 """ 

277 Applies homogeneous Dirichlet boundary conditions to the residual 

278 

279 Parameters 

280 ---------- 

281 res : dtype_u 

282 Residual 

283 """ 

284 self.bc_hom.apply(res.values.vector()) 

285 return None