-
Notifications
You must be signed in to change notification settings - Fork 38
Add a monolithic incompressible Navier-Stokes example and a fully implicit mass sweeper #642
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,327 @@ | ||||||||||||||
| import logging | ||||||||||||||
| import os | ||||||||||||||
|
|
||||||||||||||
| import dolfin as df | ||||||||||||||
| import numpy as np | ||||||||||||||
|
|
||||||||||||||
| from pySDC.core.problem import Problem | ||||||||||||||
| from pySDC.implementations.datatype_classes.fenics_mesh import fenics_mesh | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| class fenics_NSE_2D_Monolithic(Problem): | ||||||||||||||
| r""" | ||||||||||||||
| Example implementing the forced two-dimensional incompressible Navier-Stokes equations with | ||||||||||||||
| time-dependent Dirichlet boundary conditions | ||||||||||||||
|
|
||||||||||||||
| .. math:: | ||||||||||||||
| \frac{\partial u}{\partial t} = - u \cdot \nabla u + \nu \nabla u - \nabla p + f | ||||||||||||||
| 0 = \nabla \cdot u | ||||||||||||||
|
|
||||||||||||||
| for :math:`x \in \Omega`, where the forcing term :math:`f` is defined by | ||||||||||||||
|
|
||||||||||||||
| .. math:: | ||||||||||||||
| f(x, t) = (0, 0). | ||||||||||||||
|
|
||||||||||||||
| This implementation follows a monolithic approach, where velocity and pressure are | ||||||||||||||
| solved simultaneously in a coupled system using a mixed finite element formulation. | ||||||||||||||
|
|
||||||||||||||
| Boundary conditions are applied on subsets of the boundary: | ||||||||||||||
| - no-slip on channel walls and cylinder surface, | ||||||||||||||
| - a time-dependent inflow profile at the inlet, | ||||||||||||||
| - pressure condition at the outflow. | ||||||||||||||
|
|
||||||||||||||
| In this class the problem is implemented in the way that the spatial part is solved using ``FEniCS`` [1]_. Hence, the problem | ||||||||||||||
| is reformulated to the *weak formulation* | ||||||||||||||
|
|
||||||||||||||
| .. math: | ||||||||||||||
| \int_\Omega u_t v\,dx = - \int_\Omega u \cdot \nabla u v\,dx - \nu \int_\Omega \nabla u \nabla v\,dx + \int_\Omega p \nabla \cdot v\,dx + \int_\Omega f v\,dx | ||||||||||||||
| \int_\Omega \nabla \cdot u q\,dx = 0 | ||||||||||||||
|
|
||||||||||||||
| Parameters | ||||||||||||||
| ---------- | ||||||||||||||
| t0 : float, optional | ||||||||||||||
| Starting time. | ||||||||||||||
| order : int, optional | ||||||||||||||
| Defines the order of the elements in the function space. | ||||||||||||||
| nu : float, optional | ||||||||||||||
| Diffusion coefficient :math:`\nu`. | ||||||||||||||
|
|
||||||||||||||
| Attributes | ||||||||||||||
| ---------- | ||||||||||||||
| V : FunctionSpace | ||||||||||||||
| Defines the function space of the trial and test functions for velocity. | ||||||||||||||
| Q : FunctionSpace | ||||||||||||||
| Defines the function space of the trial and test functions for pressure. | ||||||||||||||
| W : FunctionSpace | ||||||||||||||
| Defines the mixed function space for the coupled velocity-pressure system. | ||||||||||||||
| M : scalar, vector, matrix or higher rank tensor | ||||||||||||||
| Denotes the expression :math:`\int_\Omega u_t v\,dx`. | ||||||||||||||
| Mf : scalar, vector, matrix or higher rank tensor | ||||||||||||||
| Denotes the expression :math:`\int_\Omega u v\,dx + \int_\Omega p q\,dx`. | ||||||||||||||
| g : Expression | ||||||||||||||
| The forcing term :math:`f` in the heat equation. | ||||||||||||||
| bc : DirichletBC | ||||||||||||||
| Denotes the time-dependent Dirichlet boundary conditions. | ||||||||||||||
| bc_hom : DirichletBC | ||||||||||||||
| Denotes the homogeneous Dirichlet boundary conditions, potentially required for fixing the residual | ||||||||||||||
| fix_bc_for_residual: boolean | ||||||||||||||
| flag to indicate that the residual requires special treatment due to boundary conditions | ||||||||||||||
|
|
||||||||||||||
| References | ||||||||||||||
| ---------- | ||||||||||||||
| .. [1] The FEniCS Project Version 1.5. M. S. Alnaes, J. Blechta, J. Hake, A. Johansson, B. Kehlet, A. Logg, | ||||||||||||||
| C. Richardson, J. Ring, M. E. Rognes, G. N. Wells. Archive of Numerical Software (2015). | ||||||||||||||
| .. [2] Automated Solution of Differential Equations by the Finite Element Method. A. Logg, K.-A. Mardal, G. N. | ||||||||||||||
| Wells and others. Springer (2012). | ||||||||||||||
| """ | ||||||||||||||
|
|
||||||||||||||
| dtype_u = fenics_mesh | ||||||||||||||
| dtype_f = fenics_mesh | ||||||||||||||
|
|
||||||||||||||
| df.set_log_active(False) | ||||||||||||||
|
|
||||||||||||||
| def __init__(self, t0=0.0, order=2, nu=0.001): | ||||||||||||||
|
|
||||||||||||||
| # set logger level for FFC and dolfin | ||||||||||||||
| logging.getLogger('FFC').setLevel(logging.WARNING) | ||||||||||||||
| logging.getLogger('UFL').setLevel(logging.WARNING) | ||||||||||||||
|
|
||||||||||||||
| # set solver and form parameters | ||||||||||||||
| df.parameters["form_compiler"]["optimize"] = True | ||||||||||||||
| df.parameters["form_compiler"]["cpp_optimize"] = True | ||||||||||||||
| df.parameters['allow_extrapolation'] = True | ||||||||||||||
|
|
||||||||||||||
| # load mesh | ||||||||||||||
| path = f"{os.path.dirname(__file__)}/../meshs/" | ||||||||||||||
| mesh = df.Mesh(path + '/cylinder.xml') | ||||||||||||||
|
|
||||||||||||||
| # define function spaces for future reference (Taylor-Hood) | ||||||||||||||
| P2 = df.VectorElement("P", mesh.ufl_cell(), order) | ||||||||||||||
| P1 = df.FiniteElement("P", mesh.ufl_cell(), order - 1) | ||||||||||||||
| TH = df.MixedElement([P2, P1]) | ||||||||||||||
| self.W = df.FunctionSpace(mesh, TH) | ||||||||||||||
| self.V = df.FunctionSpace(mesh, P2) | ||||||||||||||
| self.Q = df.FunctionSpace(mesh, P1) | ||||||||||||||
|
|
||||||||||||||
| # print the number of DoFs for debugging purposes | ||||||||||||||
| tmp = df.Function(self.W) | ||||||||||||||
| self.logger.debug('DoFs on this level:', len(tmp.vector()[:])) | ||||||||||||||
|
||||||||||||||
| self.logger.debug('DoFs on this level:', len(tmp.vector()[:])) | |
| self.logger.debug('DoFs on this level: %d', len(tmp.vector()[:])) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you really need to invert the mass matrix? Can't you rewrite all of this multiplying the whole thing with the mass matrix?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| # update time in Boundary conditions | |
| # update time in boundary conditions |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The absolute tolerance should maybe be a parameter of the problem and be larger by default. This is too close to machine precision for my liking.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| g = self.dtype_f(df.interpolate(self.g, self.V), val=self.V) | |
| g = self.dtype_f(df.interpolate(self.g, self.V)) |
right...?
Copilot
AI
Apr 8, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
eval_f assembles into a temporary vector and then assigns into f.values.vector()[:]. This forces an allocation every call and can be a significant cost in SDC sweeps.
Prefer assembling directly into the existing vector (e.g. via df.assemble(F, tensor=f.values.vector()) or an equivalent in-place assemble) to avoid repeated allocations and improve performance.
| f.values.vector()[:] = df.assemble(F) | |
| df.assemble(F, tensor=f.values.vector()) |
Copilot
AI
Apr 8, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
__invert_mass_matrix calls df.solve(self.Mf, ...) each time solve_system runs. Since Mf is constant, repeatedly refactorizing/setting up the linear solve can dominate runtime (this is called once per node per sweep).
Consider creating and caching a df.LUSolver/df.KrylovSolver for Mf in __init__ (or caching the factorization) and reusing it here to avoid repeated setup costs.
| df.solve(self.Mf, me.values.vector(), w.values.vector()) | |
| if not hasattr(self, '_Mf_solver'): | |
| self._Mf_solver = df.LUSolver(self.Mf) | |
| self._Mf_solver.solve(me.values.vector(), w.values.vector()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The docstring has a few mathematical / Sphinx issues that make it misleading or render incorrectly:
\nu \nabla ubut should be\nu \Delta u(and typically-\nu\Delta udepending on sign convention)... math:instead of.. math::.gis the forcing term “in the heat equation”, which is unrelated here.Please fix these so the documentation matches the implemented monolithic Navier–Stokes formulation and builds cleanly in Sphinx.