Hello everyone,
I am working on a 2D periodic Taylor-Green Vortex problem using NGSolve and trying to validate my finite element Navier–Stokes solver. My setup is:
- Domain: [0,1]×[0,1][0,1]\times[0,1][0,1]×[0,1], with fully periodic boundary conditions.
- Viscosity: ν=1/Re\nu = 1/\text{Re}ν=1/Re with Re=100\text{Re} = 100Re=100.
- Finite Element Spaces:
- Velocity:
V = Periodic(VectorH1(mesh, order=k))
- Pressure:
Q = Periodic(H1(mesh, order=k-1))
- Combined:
X = FESpace([V, Q])
- Velocity:
I am attempting a time-stepping approach where I assemble a bilinear form a
that includes velocity and pressure terms:
python
复制代码
a = BilinearForm(
(InnerProduct(u, v)
+ dt*(nu*InnerProduct(grad(u), grad(v))
- div(v)*p
- div(u)*q))
* dx
)
Then I create its inverse (inv = a.mat.Inverse(X.FreeDofs())
) and perform partial updates for the velocity and pressure in each time step. However, I notice that my pressure solution effectively remains constant or has large errors compared to the analytical Taylor-Green pressure.
What I’ve Tried
- Removing the mean pressure:
Since I know that in a fully periodic domain, the pressure is only determined up to an additive constant, I attempted to remove the global mean of the pressure after each time step:
python
复制代码
pgrid = gfu.components[1]
mean_val = Integrate(pgrid, mesh) / Integrate(CoefficientFunction(1), mesh)
pgrid.Set(pgrid - mean_val)
This does shift the pressure field so that its integral is zero, but the overall pressure error remains large (and sometimes increases for smaller time steps).
2. SetMeanValue(0.0)
approach:
I tried to call Q.SetMeanValue(0.0)
on the periodic pressure space but got:
plaintext
复制代码
AttributeError: 'ngsolve.comp.Periodic' object has no attribute 'SetMeanValue'
So it seems that this function is not exposed for a Periodic(...)
-wrapped space in my NGSolve version.
3. Verification with L2 errors:
- The velocity converges reasonably well with decreasing
dt
(showing about first-order in time, as expected for my approach). - The pressure, however, exhibits high or inconsistent errors. For example:
csharp
复制代码
dt = 0.10000, L2 Error (pressure) = 3.82e-01
dt = 0.05000, L2 Error (pressure) = 2.22e+00
dt = 0.02500, L2 Error (pressure) = 2.22e+00
dt = 0.01250, L2 Error (pressure) = 2.50e-01
The pattern is somewhat erratic, which suggests my time-stepping algorithm is not properly enforcing a pressure correction in each step.
My Questions
- How should I properly impose a zero-mean (or some form of constraint) on the pressure in a fully periodic problem in NGSolve?
- Is there a recommended way to define a “mean-free” space for the pressure or add a constraint through a Lagrange multiplier in the saddle-point problem?
- Is my partial-step approach for velocity and pressure appropriate for an incompressible Navier–Stokes problem with periodic boundaries?
- Right now, I update
gfu1
,gfu2
,gfu3
and then combine them into a final solution using certain coefficients (alpha
andbeta
), but it seems the pressure is never explicitly “coupled” at each step.
- Any suggestions on a stable time discretization (e.g., projection methods or fully coupled solves) that are known to handle periodic incompressible flow and yield accurate pressure fields?
Codes
from ngsolve import *
from netgen.geom2d import *
import numpy as np
import matplotlib.pyplot as plt
定义参数
k = 2
Re = 50
nu = 1 / Re # 动力黏性系数
dt_list = [0.1, 0.05, 0.025, 0.0125, 0.00625] # 选择不同的时间步长
tend = 1 # 计算时间
创建网格和有限元空间
geo = SplineGeometry()
points = [(0,0), (1,0), (1,1), (0,1)]
pnums = [geo.AppendPoint(*p) for p in points]
bottom = geo.Append([“line”, pnums[0], pnums[1]], leftdomain=1, rightdomain=0, bc=“periodic”)
right = geo.Append([“line”, pnums[1], pnums[2]], leftdomain=1, rightdomain=0, bc=“periodic”)
top = geo.Append([“line”, pnums[3], pnums[2]], leftdomain=0, rightdomain=1, copy=bottom, bc=“periodic”)
left = geo.Append([“line”, pnums[0], pnums[3]], leftdomain=0, rightdomain=1, copy=right, bc=“periodic”)
mesh = Mesh(geo.GenerateMesh(maxh=1/64))
V = Periodic(VectorH1(mesh, order=k))
Q = Periodic(H1(mesh, order=k-1))
X = FESpace([V, Q]) # 混合空间
定义试探函数和检验函数
u, p = X.TrialFunction()
v, q = X.TestFunction()
解析解
def taylor_green_velocity(t):
decay = exp(-8 * pi**2 * nu * t)
return CoefficientFunction((sin(2pix)cos(2piy)decay, -cos(2pix)sin(2pi*y)*decay))
存储误差
errors =
遍历不同的时间步长
for dt in dt_list:
print(f"Computing for dt = {dt}")
# 初始速度
gfu = GridFunction(X)
velocity, pressure = gfu.components
velocity.Set(taylor_green_velocity(0))
# 定义 bilinear 和 linear forms
a = BilinearForm((nu * InnerProduct(grad(u),grad(v)) - div(v) * p - div(u) * q) * dx).Assemble()
mstar = BilinearForm(X)
mstar += InnerProduct(u, v) * dx # 质量项
mstar += dt * nu * InnerProduct(grad(u), grad(v)) * dx # 黏性扩散项
mstar += dt * (-div(v) * p * dx - div(u) * q * dx) # 连续性约束(压力-速度耦合)
mstar.Assemble()
r = BilinearForm((InnerProduct(u,v) - dt * (Grad(u) * u) * v) * dx).Assemble()
inv = mstar.mat.Inverse(X.FreeDofs())
f = LinearForm(X)
t = 0
while t < tend - 0.5*dt:
f.vec.data = r.mat * gfu.vec
gfu.vec.data = inv * f.vec
t += dt
# 计算 L2 误差
u_exact = taylor_green_velocity(tend)
l2_error = sqrt(Integrate(InnerProduct(velocity - u_exact, velocity - u_exact), mesh))
errors.append(l2_error)
计算误差阶数(时间收敛阶)
orders =
for i in range(len(dt_list) - 1):
order = np.log(errors[i] / errors[i + 1]) / np.log(dt_list[i] / dt_list[i + 1])
orders.append(order)
打印误差和收敛阶数
for i in range(len(dt_list)):
print(f"dt = {dt_list[i]:.5f}, Error = {errors[i]:.6e}")
for i in range(len(orders)):
print(f"Order between dt={dt_list[i]} and dt={dt_list[i+1]}: {orders[i]:.2f}")
绘制误差对 dt 的对数图
plt.figure()
plt.loglog(dt_list, errors, marker=“o”, linestyle=“–”, label=“L2 Error”)
plt.xlabel(“Time Step dt”)
plt.ylabel(“L2 Error”)
plt.legend()
plt.grid(True)
plt.show()