-
Notifications
You must be signed in to change notification settings - Fork 78
WIP( Work in progress): add monolithic CR–P0 Navier–Stokes solver #291
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
Open
akarve1507
wants to merge
12
commits into
jorgensd:main
Choose a base branch
from
akarve1507:monolithic-ns-crp0
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+369
−0
Open
Changes from 2 commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
fb40d69
WIP: add monolithic CR–P0 Navier–Stokes solver
akarve1507 2dddd17
Add CR–P0 Navier–Stokes tutorial markdown file
akarve1507 526a555
Update tutorial .md with corrected image path and equations
akarve1507 9adcad8
Tidy CR–P0 Navier–Stokes tutorial text
akarve1507 aa123d3
Update chapter2/monolithic_navierstokes_crp0.py
akarve1507 72ccb23
Update chapter2/monolithic_navierstokes_crp0.py
akarve1507 71ff533
Update chapter2/monolithic_navierstokes_crp0.py
akarve1507 7a18b68
Update chapter2/monolithic_navierstokes_crp0.py
akarve1507 5341048
Update chapter2/monolithic_navierstokes_crp0.py
akarve1507 97c45f6
Update chapter2/monolithic_navierstokes_crp0.py
akarve1507 44d23f3
Update chapter2/monolithic_navierstokes_crp0.py
akarve1507 8e965a3
Apply reviewer feedback and update Navier–Stokes CR–P0 solver
akarve1507 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,103 @@ | ||
| # Monolithic Incompressible Navier–Stokes with CR–P0 (Steady Picard) | ||
|
|
||
| --- | ||
|
|
||
| We solve the steady incompressible Navier–Stokes equations in a 2D channel using a **monolithic** formulation with **Crouzeix–Raviart (CR)** velocity and **DG-0** pressure. Dirichlet velocities on the left, top, and bottom are imposed **weakly via Nitsche’s method**, suitable for the nonconforming CR element. | ||
|
|
||
| --- | ||
akarve1507 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| ## Key Parameters | ||
| - **Velocity space:** $ V_h = [\mathrm{CR}_1]^2 $ (nonconforming, facet-based) | ||
| - **Pressure space:** $ Q_h = \mathrm{DG}_0 $ | ||
| - **Linearization:** Picard (frozen convection); start from Stokes baseline | ||
| - **Stabilization:** small pressure mass $ \varepsilon_p \int_\Omega p\,q\,dx $ removes nullspace | ||
| - **Solver:** PETSc GMRES + Jacobi | ||
| - **Output:** XDMF for ParaView | ||
akarve1507 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| --- | ||
|
|
||
| ## Governing Equations | ||
|
|
||
| $$ | ||
| -\nu \Delta \mathbf{u} + (\mathbf{u}\cdot\nabla)\mathbf{u} + \nabla p = \mathbf{f}, | ||
| \quad \text{in } \Omega, | ||
| $$ | ||
|
|
||
| $$ | ||
| \nabla\cdot\mathbf{u} = 0, \quad \text{in } \Omega. | ||
akarve1507 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| $$ | ||
|
|
||
| ### Boundary Conditions | ||
| - Inlet (left): $ \mathbf{u} = \mathbf{u}_{\text{in}} $ | ||
| - Walls (top/bottom): $ \mathbf{u} = \mathbf{0} $ | ||
| - Outlet (right): natural (traction-free) | ||
|
|
||
| --- | ||
|
|
||
| ## Weak Formulation | ||
|
|
||
| For test functions $ \mathbf{v}, q $, find $ (\mathbf{u},p) $ such that: | ||
|
|
||
| $$ | ||
| a_{\text{core}}(\mathbf{u},p;\mathbf{v},q) | ||
| = \nu (\nabla\mathbf{u}, \nabla\mathbf{v})_\Omega | ||
| - (p, \nabla\cdot\mathbf{v})_\Omega | ||
| + (q, \nabla\cdot\mathbf{u})_\Omega, | ||
| $$ | ||
|
|
||
| $$ | ||
| a_{\text{conv}}(\mathbf{u};\mathbf{v}\mid\mathbf{u}_k) | ||
| = ((\mathbf{u}_k\cdot\nabla)\mathbf{u},\mathbf{v})_\Omega, | ||
| $$ | ||
|
|
||
| $$ | ||
| a_{\text{pm}}(p;q) | ||
| = \varepsilon_p (p,q)_\Omega. | ||
akarve1507 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| $$ | ||
|
|
||
| Right-hand side: | ||
| $$ | ||
| \ell(\mathbf{v}) = (\mathbf{f},\mathbf{v})_\Omega. | ||
| $$ | ||
|
|
||
| --- | ||
|
|
||
| ### Nitsche Boundary Terms | ||
|
|
||
| $$ | ||
| \begin{aligned} | ||
| & -\nu\int_{\Gamma_D} (\nabla\mathbf{u}\,\mathbf{n})\cdot\mathbf{v}\,ds | ||
| -\nu\int_{\Gamma_D} (\nabla\mathbf{v}\,\mathbf{n})\cdot(\mathbf{u}-\mathbf{u}_D)\,ds | ||
| + \alpha\frac{\nu}{h}\int_{\Gamma_D} (\mathbf{u}-\mathbf{u}_D)\cdot\mathbf{v}\,ds \\ | ||
| &\quad - \int_{\Gamma_D} p\,\mathbf{n}\cdot\mathbf{v}\,ds | ||
| + \int_{\Gamma_D} q\,\mathbf{n}\cdot(\mathbf{u}-\mathbf{u}_D)\,ds. | ||
akarve1507 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| \end{aligned} | ||
| $$ | ||
|
|
||
| ### Under-relaxed Picard Update | ||
|
|
||
| $$ | ||
| \mathbf{u}_{k+1}^{(\text{lin})} | ||
| = \theta\,\mathbf{u}_{k}^{(\text{new})} | ||
| + (1-\theta)\,\mathbf{u}_{k}^{(\text{lin})}, \quad 0<\theta\le1. | ||
| $$ | ||
|
|
||
| --- | ||
|
|
||
| ## Example Output (ParaView) | ||
|
|
||
|  | ||
|
|
||
| **Figure:** Velocity magnitude field with CR–P0 (Stokes baseline). | ||
| Color shows $ |\mathbf{u}| $ and arrows indicate flow direction and speed. | ||
| Inlet on the left, natural outlet on the right. | ||
|
|
||
| --- | ||
|
|
||
| ## Run Instructions (Docker) | ||
|
|
||
| ```bash | ||
| docker run --rm -it -v "$PWD":"$PWD" -w "$PWD" \ | ||
| ghcr.io/fenics/dolfinx/dolfinx:stable \ | ||
| python3 chapter2/monolithic_navierstokes_crp0.py \ | ||
| --uin 0.5 --nu 1e-2 --theta 0.5 --alpha 300 | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,219 @@ | ||
| from __future__ import annotations | ||
| import argparse | ||
| import numpy as np | ||
| from mpi4py import MPI | ||
| from dolfinx import fem, io, mesh | ||
| from dolfinx.fem import petsc | ||
| from petsc4py import PETSc | ||
| import ufl | ||
| from basix.ufl import element, mixed_element | ||
|
|
||
|
|
||
| def parse_args(): | ||
| p = argparse.ArgumentParser(description="CR–P0 steady Navier–Stokes with Nitsche BCs (robust)") | ||
| p.add_argument("--nx", type=int, default=64) | ||
| p.add_argument("--ny", type=int, default=32) | ||
| p.add_argument("--Lx", type=float, default=2.0) | ||
| p.add_argument("--Ly", type=float, default=1.0) | ||
| p.add_argument("--nu", type=float, default=1e-2) | ||
| p.add_argument("--uin", type=float, default=1.0) | ||
| p.add_argument("--picard-it", type=int, default=20) | ||
| p.add_argument("--picard-tol", type=float, default=1e-8) | ||
| p.add_argument("--theta", type=float, default=0.5) | ||
| p.add_argument("--eps-p", type=float, default=1e-12) # tiny pressure mass to remove nullspace | ||
| p.add_argument("--stokes-only", action="store_true") | ||
| p.add_argument("--no-output", action="store_true") | ||
| p.add_argument("--alpha", type=float, default=400.0, help="Nitsche penalty (100–1000 recommended)") | ||
| p.add_argument("--outfile", type=str, default="chapter2/open_cavity_crp0.xdmf") | ||
| return p.parse_args() | ||
akarve1507 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
|
|
||
| args = parse_args() | ||
| comm = MPI.COMM_WORLD | ||
| rank = comm.rank | ||
|
|
||
| # --- mesh --- | ||
| domain = mesh.create_rectangle( | ||
| comm, | ||
| [np.array([0.0, 0.0]), np.array([args.Lx, args.Ly])], | ||
| [args.nx, args.ny], | ||
| cell_type=mesh.CellType.triangle, | ||
| ) | ||
| tdim = domain.topology.dim | ||
| fdim = tdim - 1 | ||
| gdim = domain.geometry.dim | ||
| cell = domain.basix_cell() | ||
|
|
||
| # Ensure needed connectivities | ||
| domain.topology.create_connectivity(fdim, tdim) | ||
| domain.topology.create_connectivity(tdim, tdim) | ||
|
|
||
| # --- boundary facet tags (needed for ds with Nitsche) --- | ||
| left_facets = mesh.locate_entities_boundary(domain, fdim, lambda x: np.isclose(x[0], 0.0)) | ||
| right_facets = mesh.locate_entities_boundary(domain, fdim, lambda x: np.isclose(x[0], args.Lx)) | ||
| bottom_facets = mesh.locate_entities_boundary(domain, fdim, lambda x: np.isclose(x[1], 0.0)) | ||
| top_facets = mesh.locate_entities_boundary(domain, fdim, lambda x: np.isclose(x[1], args.Ly)) | ||
|
|
||
| # Tag: 1=left, 2=right, 3=bottom, 4=top | ||
| facet_indices = np.concatenate([left_facets, right_facets, bottom_facets, top_facets]).astype(np.int32) | ||
| facet_tags = np.concatenate([ | ||
| np.full_like(left_facets, 1, dtype=np.int32), | ||
| np.full_like(right_facets, 2, dtype=np.int32), | ||
| np.full_like(bottom_facets, 3, dtype=np.int32), | ||
| np.full_like(top_facets, 4, dtype=np.int32), | ||
| ]) | ||
|
|
||
| # Create facet meshtags | ||
| if facet_indices.size == 0: | ||
| ft = mesh.meshtags(domain, fdim, np.array([], dtype=np.int32), np.array([], dtype=np.int32)) | ||
| else: | ||
| order = np.argsort(facet_indices) | ||
| ft = mesh.meshtags(domain, fdim, facet_indices[order], facet_tags[order]) | ||
akarve1507 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| dx = ufl.Measure("dx", domain=domain) | ||
| ds = ufl.Measure("ds", domain=domain, subdomain_data=ft) | ||
|
|
||
| # --- spaces: CR–P0 (mixed) --- | ||
| V_el = element("CR", cell, 1, shape=(gdim,)) | ||
| Q_el = element("DG", cell, 0) | ||
| W = fem.functionspace(domain, mixed_element([V_el, Q_el])) | ||
| (u, p) = ufl.TrialFunctions(W) | ||
| (v, q) = ufl.TestFunctions(W) | ||
|
|
||
| # --- parameters / RHS --- | ||
| nu = fem.Constant(domain, PETSc.ScalarType(args.nu)) | ||
akarve1507 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| eps_p = fem.Constant(domain, PETSc.ScalarType(args.eps_p)) | ||
| zero = fem.Constant(domain, PETSc.ScalarType(0.0)) | ||
| f_vec = ufl.as_vector((zero, zero)) # zero body force | ||
akarve1507 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| # --- Picard state (for convection) --- | ||
| w = fem.Function(W) # holds (u_k, p_k) | ||
| u_k, p_k = w.split() | ||
|
|
||
| def build_forms(): | ||
| n = ufl.FacetNormal(domain) | ||
| h = ufl.CellDiameter(domain) | ||
| alpha = PETSc.ScalarType(args.alpha) | ||
akarve1507 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| u_in = ufl.as_vector((PETSc.ScalarType(args.uin), PETSc.ScalarType(0.0))) | ||
| zero_vec = ufl.as_vector((PETSc.ScalarType(0.0), PETSc.ScalarType(0.0))) | ||
|
|
||
| # Core Stokes + tiny pressure mass | ||
| F = ( | ||
| nu * ufl.inner(ufl.grad(u), ufl.grad(v)) * dx | ||
| - ufl.div(v) * p * dx | ||
| + q * ufl.div(u) * dx | ||
| + eps_p * p * q * dx | ||
| - ufl.inner(f_vec, v) * dx | ||
| ) | ||
|
|
||
| # Add convection if not stokes-only | ||
| if not args.stokes_only: | ||
| F += ufl.inner(ufl.dot(u_k, ufl.nabla_grad(u)), v) * dx | ||
|
|
||
| # -------- Nitsche + boundary consistency terms on Dirichlet parts -------- | ||
| # Left inlet (tag=1): u = u_in | ||
| F += ( | ||
| - nu * ufl.inner(ufl.grad(u) * n, v) # consistency | ||
| - nu * ufl.inner(ufl.grad(v) * n, (u - u_in)) # symmetry | ||
| + alpha * nu / h * ufl.inner(u - u_in, v) # penalty | ||
| - ufl.inner(p * n, v) # momentum BC consistency | ||
| + q * ufl.inner(u - u_in, n) # continuity BC consistency: q * ((u-u_in)·n) | ||
| ) * ds(1) | ||
akarve1507 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| # Bottom (3) and Top (4): no-slip u = 0 | ||
| for tag in (3, 4): | ||
| F += ( | ||
| - nu * ufl.inner(ufl.grad(u) * n, v) | ||
| - nu * ufl.inner(ufl.grad(v) * n, (u - zero_vec)) | ||
| + alpha * nu / h * ufl.inner(u - zero_vec, v) | ||
| - ufl.inner(p * n, v) | ||
| + q * ufl.inner(u - zero_vec, n) | ||
| ) * ds(tag) | ||
akarve1507 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| # Right (2) left as natural outlet (do-nothing) | ||
| return ufl.lhs(F), ufl.rhs(F) | ||
akarve1507 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
|
|
||
| def solve_once(): | ||
akarve1507 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| a, L = build_forms() | ||
| # No strong BCs with CR → Nitsche handles boundaries | ||
| A = petsc.assemble_matrix(fem.form(a), bcs=[]) | ||
| A.assemble() | ||
| b = A.createVecRight(); b.set(0.0) | ||
| petsc.assemble_vector(b, fem.form(L)) | ||
| # (No lifting/BC since bcs=[]) | ||
| b.ghostUpdate(addv=PETSc.InsertMode.ADD, mode=PETSc.ScatterMode.REVERSE) | ||
|
|
||
| if rank == 0: | ||
| PETSc.Sys.Print(f" ||b||_2 = {b.norm():.3e}") | ||
|
|
||
| # Robust Krylov (no factorization) | ||
| ksp = PETSc.KSP().create(comm) | ||
| ksp.setOperators(A) | ||
| ksp.setType("gmres") | ||
| ksp.setTolerances(rtol=1e-8, max_it=1000) | ||
| ksp.getPC().setType("jacobi") | ||
akarve1507 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| # wrap dolfinx vector, solve, copy back | ||
| x = PETSc.Vec().createWithArray(w.x.array, comm=comm) | ||
| ksp.solve(b, x) | ||
| w.x.array[:] = x.getArray() | ||
| w.x.scatter_forward() | ||
| if rank == 0: | ||
| PETSc.Sys.Print(f" ||x||_2 = {x.norm():.3e}") | ||
|
|
||
|
|
||
| # --- Picard loop --- | ||
| theta = float(args.theta) | ||
| tol = float(args.picard_tol) | ||
| max_it = int(args.picard_it) | ||
|
|
||
| # for residual check (velocity only) | ||
| V_sub = W.sub(0) | ||
| V0, _ = V_sub.collapse() | ||
| u_prev_fun = fem.Function(V0) | ||
| u_prev_arr = np.zeros_like(u_prev_fun.x.array) | ||
|
|
||
| for it in range(1, max_it + 1): | ||
| solve_once() | ||
|
|
||
| # velocity from mixed solution | ||
| u_view = w.sub(0).collapse() | ||
| u_curr = u_view[0] if isinstance(u_view, tuple) else u_view | ||
| u_curr_arr = u_curr.x.array.copy() | ||
|
|
||
| diff = u_curr_arr - u_prev_arr | ||
| err = float(np.linalg.norm(diff)) if np.all(np.isfinite(diff)) else float("inf") | ||
|
|
||
| if rank == 0: | ||
| PETSc.Sys.Print(f"Picard {it:02d}: ||u - u_prev|| = {err:.3e}") | ||
| PETSc.Sys.Print(f" |u| range: [{np.abs(u_curr_arr).min():.3e}, {np.abs(u_curr_arr).max():.3e}]") | ||
|
|
||
| if not np.isfinite(err) or err < tol or args.stokes_only: | ||
| break | ||
|
|
||
| # under-relax linearization state u_k := θ u_curr + (1-θ) u_prev | ||
| relaxed = theta * u_curr_arr + (1.0 - theta) * u_prev_arr | ||
| u_k.x.array[:len(relaxed)] = relaxed | ||
| u_prev_arr = u_curr_arr.copy() | ||
|
|
||
| # --- output --- | ||
| if not args.no_output: | ||
| u_view = w.sub(0).collapse() | ||
| p_view = w.sub(1).collapse() | ||
| u_fun = u_view[0] if isinstance(u_view, tuple) else u_view | ||
| p_fun = p_view[0] if isinstance(p_view, tuple) else p_view | ||
|
|
||
| # interpolate to CG1 for cleaner XDMF viewing | ||
| Vout = fem.functionspace(domain, element("Lagrange", cell, 1, shape=(gdim,))) | ||
| Qout = fem.functionspace(domain, element("Lagrange", cell, 1)) | ||
| u_out = fem.Function(Vout, name="u"); u_out.interpolate(u_fun) | ||
| p_out = fem.Function(Qout, name="p"); p_out.interpolate(p_fun) | ||
|
|
||
| with io.XDMFFile(domain.comm, args.outfile, "w") as xdmf: | ||
| xdmf.write_mesh(domain) | ||
| xdmf.write_function(u_out) | ||
| xdmf.write_function(p_out) | ||
akarve1507 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| if rank == 0: | ||
| PETSc.Sys.Print(f"Wrote {args.outfile}") | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.