This document explains how manufactured solutions (MMS) are generated for cfd-vv-suite and how contributors can add new MMS cases.
The Method of Manufactured Solutions (MMS) is a code verification technique:
Manufactured solution:
u(x,y) = sin(pi*x) * cos(pi*y)
v(x,y) = -cos(pi*x) * sin(pi*y)
p(x,y) = sin(pi*x) * sin(pi*y)
nu = 0.1, domain [0,1]^2
Properties: Exactly divergence-free (div u = 0).
Source term derivation:
The Navier-Stokes x-momentum equation:
u*du/dx + v*du/dy + dp/dx - nu*(d^2u/dx^2 + d^2u/dy^2) = S_u
Substituting the manufactured solution:
du/dx = pi*cos(pi*x)*cos(pi*y)
du/dy = -pi*sin(pi*x)*sin(pi*y)
d^2u/dx^2 = d^2u/dy^2 = -pi^2*u
dp/dx = pi*cos(pi*x)*sin(pi*y)
Therefore:
S_u = u*du/dx + v*du/dy + dp/dx - nu*(d^2u/dx^2 + d^2u/dy^2)
S_v = u*dv/dx + v*dv/dy + dp/dy - nu*(d^2v/dx^2 + d^2v/dy^2)
S_p = 0 (continuity satisfied exactly)
Generation script: scripts/generate_solution.py
python scripts/generate_solution.py <nx> <ny> [solution.csv] [source_terms.csv]
Manufactured solution:
u(x,y,z) = sin(pi*x) * cos(pi*y) * cos(pi*z)
v(x,y,z) = cos(pi*x) * sin(pi*y) * cos(pi*z)
w(x,y,z) = -2*cos(pi*x) * cos(pi*y) * sin(pi*z)
p(x,y,z) = sin(pi*x) * sin(pi*y) * sin(pi*z)
nu = 0.1, domain [0,1]^3
Properties: div u = sincc + csinc + (-2ccsin) = … let’s verify: ∂u/∂x = picos(pix)cos(piy)cos(piz) ∂v/∂y = picos(pix)cos(piy)cos(piz) ∂w/∂z = -2picos(pix)cos(piy)cos(piz) Sum = piccc + piccc - 2piccc = 0 v
Source terms: Derived symbolically and verified. See reference/mms/source_terms.csv.
Not technically MMS — this is an exact steady solution:
u = sin(y) - cos(z)
v = sin(z) - cos(x)
w = sin(x) - cos(y)
p = -(sin(x)cos(y) + sin(y)cos(z) + sin(z)cos(x))
The Beltrami property curl(u) = u means (u·grad)u = -gradp exactly, so no source terms are needed.
Requirements:
Use symbolic differentiation (recommended: SymPy):
import sympy as sp
x, y, nu = sp.symbols('x y nu')
pi = sp.pi
# Define manufactured solution
u = sp.sin(pi*x) * sp.cos(pi*y)
v = -sp.cos(pi*x) * sp.sin(pi*y)
p = sp.sin(pi*x) * sp.sin(pi*y)
# Compute derivatives
dudx = sp.diff(u, x)
dudy = sp.diff(u, y)
d2udx2 = sp.diff(u, x, 2)
d2udy2 = sp.diff(u, y, 2)
dvdx = sp.diff(v, x)
dvdy = sp.diff(v, y)
d2vdx2 = sp.diff(v, x, 2)
d2vdy2 = sp.diff(v, y, 2)
dpdx = sp.diff(p, x)
dpdy = sp.diff(p, y)
# Source terms
Su = u*dudx + v*dudy + dpdx - nu*(d2udx2 + d2udy2)
Sv = u*dvdx + v*dvdy + dpdy - nu*(d2vdx2 + d2vdy2)
# Simplify
Su = sp.simplify(Su)
Sv = sp.simplify(Sv)
print(f"S_u = {Su}")
print(f"S_v = {Sv}")
# Export as Python functions
Su_func = sp.lambdify((x, y, nu), Su, 'numpy')
Sv_func = sp.lambdify((x, y, nu), Sv, 'numpy')
div = sp.simplify(sp.diff(u, x) + sp.diff(v, y))
print(f"div(u) = {div}") # Should be 0
Use the scripts/generate_solution.py template pattern:
import numpy as np, csv, sys
n = int(sys.argv[1]) # grid resolution
x = np.linspace(0, 1, n)
X, Y = np.meshgrid(x, x)
# Evaluate fields
u = ... # manufactured solution
v = ...
p = ...
# Write CSV
with open('solution.csv', 'w', newline='') as f:
w = csv.writer(f)
w.writerow(['x', 'y', 'u', 'v', 'p'])
for i in range(n):
for j in range(n):
w.writerow([X[i,j], Y[i,j], u[i,j], v[i,j], p[i,j]])
See templates/case-template/ for the full case structure:
my-mms-case/
+-- case.yaml
+-- README.md
+-- reference/
| +-- mms/
| +-- solution.csv
| +-- source_terms.csv
+-- scripts/
+-- generate_solution.py