Coverage for pySDC / projects / StroemungsRaum / problem_classes / NavierStokes_2D_FEniCS.py: 100%
93 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-27 07:06 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-27 07:06 +0000
1import logging
2import dolfin as df
3import numpy as np
4import os
6from pySDC.core.problem import Problem
7from pySDC.implementations.datatype_classes.fenics_mesh import fenics_mesh, rhs_fenics_mesh
10class fenics_NSE_2D_mass(Problem):
11 r"""
12 Example implementing a forced two-dimensional incompressible Navier–Stokes problem for the DFG
13 benchmark flow around cylinder using FEniCS (dolfin).
15 The unknowns are the velocity field :math:`\mathbf{u}(x,y,t)` and the pressure field
16 :math:`p(x,y,t)` on a 2D domain :math:`\Omega` (here loaded from ``cylinder.xml``).
17 The model is
19 .. math::
20 \frac{\partial \mathbf{u}}{\partial t}
21 + (\mathbf{u}\cdot\nabla)\mathbf{u}
22 - \nu \Delta \mathbf{u}
23 + \nabla p
24 = \mathbf{f},
25 \qquad
26 \nabla\cdot \mathbf{u} = 0,
28 where :math:`\nu` is the kinematic viscosity and :math:`\mathbf{f}` is a forcing term.
30 This implementation uses a fractional-step /projection method:
31 a viscous velocity solve, a pressure Poisson solve enforcing incompressibility, and a velocity
32 correction using the pressure gradient.
34 Boundary conditions are applied on subsets of the boundary:
35 - no-slip on channel walls and cylinder surface,
36 - a time-dependent inflow profile at the inlet,
37 - pressure condition at the outflow.
39 Parameters
40 ----------
41 t0 : float, optional
42 Starting time.
43 family : str, optional
44 Finite element family for velocity and pressure spaces. Default is ``'CG'``.
45 order : int, optional
46 Polynomial degree for the velocity space. The pressure degree is chosen as ``order - 1``.
47 nu : float, optional
48 Kinematic viscosity :math:`\nu`.
50 Attributes
51 ----------
52 mesh : Mesh
53 Computational mesh loaded from ``cylinder.xml``.
54 V : VectorFunctionSpace
55 Function space for the velocity field.
56 Q : FunctionSpace
57 Function space for the pressure field.
58 M : Matrix
59 Assembled velocity mass matrix, corresponding to :math:`\int_\Omega \mathbf{u}\cdot\mathbf{v}\,dx`.
60 K : Matrix
61 Assembled viscous operator matrix, corresponding to
62 :math:`- \int_\Omega \nabla \mathbf{u} : (\nu \nabla \mathbf{v})\,dx`.
63 Mp : Matrix
64 Assembled pressure mass matrix, corresponding to :math:`\int_\Omega p\,q\,dx`.
65 g : Expression
66 Forcing term :math:`\mathbf{f}` (here set to zero).
67 bcu : list[DirichletBC]
68 Velocity Dirichlet boundary conditions (walls, inflow, cylinder).
69 bcp : list[DirichletBC]
70 Pressure boundary conditions (outflow).
71 bc_hom : DirichletBC
72 Homogeneous velocity boundary condition used to fix the residual when requested.
74 Notes
75 -----
76 The right-hand side evaluation splits into an implicit viscous part and an explicit part
77 containing forcing and nonlinear convection:
78 - implicit: :math:`K\,\mathbf{u}`
79 - explicit: :math:`M\,\mathbf{f} - M\,(\mathbf{u}\cdot\nabla)\mathbf{u}`
80 """
82 dtype_u = fenics_mesh
83 dtype_f = rhs_fenics_mesh
85 def __init__(self, t0=0.0, family='CG', order=2, nu=0.001):
87 # set logger level for FFC and dolfin
88 logging.getLogger('FFC').setLevel(logging.WARNING)
89 logging.getLogger('UFL').setLevel(logging.WARNING)
91 # set solver and form parameters
92 df.parameters["form_compiler"]["optimize"] = True
93 df.parameters["form_compiler"]["cpp_optimize"] = True
94 df.parameters['allow_extrapolation'] = True
96 # load mesh
97 path = f"{os.path.dirname(__file__)}/../meshs/"
98 mesh = df.Mesh(path + '/cylinder.xml')
100 # define function space for future reference
101 self.V = df.VectorFunctionSpace(mesh, family, order)
102 self.Q = df.FunctionSpace(mesh, family, order - 1)
104 tmp_u = df.Function(self.V)
105 tmp_p = df.Function(self.Q)
106 self.logger.debug('Velocity DoFs on this level:', len(tmp_u.vector()[:]))
107 self.logger.debug('Pressure DoFs on this level:', len(tmp_p.vector()[:]))
109 super().__init__(self.V)
110 self._makeAttributeAndRegister('t0', 'family', 'order', 'nu', localVars=locals(), readOnly=True)
112 # define trial and test functions
113 self.u = df.TrialFunction(self.V)
114 self.v = df.TestFunction(self.V)
115 self.p = df.TrialFunction(self.Q)
116 self.q = df.TestFunction(self.Q)
118 # initialize solution functions for pressure
119 self.pn = df.Function(self.Q)
121 # mass and stiffness terms
122 a_K = -1.0 * df.inner(df.nabla_grad(self.u), self.nu * df.nabla_grad(self.v)) * df.dx
123 a_M = df.inner(self.u, self.v) * df.dx
124 a_S = df.inner(df.nabla_grad(self.p), df.nabla_grad(self.q)) * df.dx
125 a_Mp = df.inner(self.p, self.q) * df.dx
127 # assemble the forms
128 self.M = df.assemble(a_M)
129 self.K = df.assemble(a_K)
130 self.S = df.assemble(a_S)
131 self.Mp = df.assemble(a_Mp)
133 # set inflow profile at the domain inlet
134 self.inflow_profile = df.Expression(
135 ('4.0*1.5*sin(pi*t/8)*x[1]*(0.41 - x[1]) / pow(0.41, 2)', '0'), pi=np.pi, t=t0, degree=self.order
136 )
138 # define boundaries
139 inflow = 'near(x[0], 0)'
140 outflow = 'near(x[0], 2.2)'
141 walls = 'near(x[1], 0) || near(x[1], 0.41)'
142 cylinder = 'on_boundary && x[0]>0.1 && x[0]<0.3 && x[1]>0.1 && x[1]<0.3'
144 # define boundary conditions
145 bcu_noslip = df.DirichletBC(self.V, df.Constant((0, 0)), walls)
146 bcu_inflow = df.DirichletBC(self.V, self.inflow_profile, inflow)
147 bcu_cylinder = df.DirichletBC(self.V, df.Constant((0, 0)), cylinder)
148 bcp_outflow = df.DirichletBC(self.Q, df.Constant(0), outflow)
150 # set boundary values
151 self.bcu = [bcu_noslip, bcu_inflow, bcu_cylinder]
152 self.bcp = [bcp_outflow]
153 self.bc_hom = df.DirichletBC(self.V, df.Constant((0.0, 0.0)), 'on_boundary')
154 self.fix_bc_for_residual = True
156 # Define measure for drag and lift computation
157 Cylinder = df.CompiledSubDomain(cylinder)
158 CylinderBoundary = df.MeshFunction("size_t", mesh, mesh.topology().dim() - 1, 0)
159 Cylinder.mark(CylinderBoundary, 1)
160 self.dsc = df.Measure("ds", domain=mesh, subdomain_data=CylinderBoundary, subdomain_id=1)
162 # set forcing term as expression
163 self.g = df.Expression(('0.0', '0.0'), t=self.t0, degree=self.order)
165 # initialize XDMF files for velocity and pressure if needed
166 self.xdmffile_p = None
167 self.xdmffile_u = None
169 def solve_system(self, rhs, factor, u0, t, dtau):
170 r"""
171 Dolfin's linear solver for :math:`(M - factor A) \vec{u} = \vec{rhs}`.
173 Parameters
174 ----------
175 rhs : dtype_f
176 Right-hand side for the nonlinear system.
177 factor : float
178 Factor for the linear system.
179 u0 : dtype_u
180 Initial guess for the iterative solver (not used here so far).
181 t : float
182 Current time.
183 dtau : float
184 Zero-to-node step size in the SDC sweep, used in the pressure correction step.
186 Returns
187 -------
188 u : dtype_u
189 Solution: velocity.
190 p : space function
191 Solution: pressure.
193 """
195 u = self.dtype_u(u0)
196 p = df.Function(self.Q)
197 T = self.M - factor * self.K
199 # solve for the intermediate velocity
200 self.inflow_profile.t = t
201 [bc.apply(T, rhs.values.vector()) for bc in self.bcu]
202 df.solve(T, u.values.vector(), rhs.values.vector())
204 # solve for the pressure correction
205 L2 = -(1 / dtau) * df.div(u.values) * self.q * df.dx
206 Rhs2 = df.assemble(L2)
207 [bc.apply(self.S, Rhs2) for bc in self.bcp]
208 df.solve(self.S, p.vector(), Rhs2)
210 # velocity correction
211 L3 = df.dot(u.values, self.v) * df.dx - dtau * df.dot(df.nabla_grad(p), self.v) * df.dx
212 Rhs3 = df.assemble(L3)
213 [bc.apply(self.M, Rhs3) for bc in self.bcu]
214 df.solve(self.M, u.values.vector(), Rhs3)
216 return u, p
218 def eval_f(self, u, t):
219 """
220 Routine to evaluate both parts of the right-hand side.
222 Parameters
223 ----------
224 u : dtype_u
225 Current values of the numerical solution.
226 t : float
227 Current time at which the numerical solution is computed.
229 Returns
230 -------
231 f : dtype_f
232 The right-hand side divided into two parts.
233 """
234 self.g.t = t
235 conv = -1.0 * df.dot(u.values, df.nabla_grad(u.values))
237 f = self.dtype_f(self.V)
239 self.K.mult(u.values.vector(), f.impl.values.vector())
240 f.expl = self.apply_mass_matrix(self.dtype_u(df.project(conv, self.V)))
241 f.expl += self.apply_mass_matrix(self.dtype_u(df.interpolate(self.g, self.V)))
243 return f
245 def apply_mass_matrix(self, u):
246 r"""
247 Routine to apply mass matrix.
249 Parameters
250 ----------
251 u : dtype_u
252 Current values of the numerical solution.
254 Returns
255 -------
256 me : dtype_u
257 The product :math:`M \vec{u}`.
258 """
260 me = self.dtype_u(self.V)
261 self.M.mult(u.values.vector(), me.values.vector())
263 return me
265 def u_exact(self, t):
266 r"""
267 Routine to compute the exact solution at time :math:`t`.
269 Parameters
270 ----------
271 t : float
272 Time of the exact solution.
274 Returns
275 -------
276 me : dtype_u
277 Exact solution.
278 """
280 u0 = df.Expression(('0.0', '0.0'), degree=self.order)
281 me = self.dtype_u(df.interpolate(u0, self.V), val=self.V)
283 return me
285 def fix_residual(self, res):
286 """
287 Applies homogeneous Dirichlet boundary conditions to the residual
289 Parameters
290 ----------
291 res : dtype_u
292 Residual
293 """
294 self.bc_hom.apply(res.values.vector())
295 return None