Translated from the author's Korean original. Read the original (한국어)
Filling the Third Moment Honestly Made the Error 2.8x Worse — What a Forcing Term Actually Owes
The third moment of a forcing term reaches the momentum equation only at Burnett order, and a tensor correction the lattice grants by halves is worse than none.
How many moments does a forcing term owe?#
When we put gravity or surface tension into an LBM code, we match three lines. Zeroth moment zero, first moment , second moment . What about the third? The force term of the continuous Boltzmann equation has a definite third moment too. Matching that value on the lattice looks like a free accuracy gain.
The numbers went the other way. Filling the third moment with what kinetic theory demands pushed the error from 0.053% to 0.150%. The sliders and are the velocity. Raising them splits the moment rows that had matched. The checkbox closes only the yellow rows.
Drag ux, uy toward zero and every row lines up. Push them out and the two yellow rows open a gap that the checkbox can close, while the red row cannot be touched at all: on D2Q9 cx^3 is the same vector as cx. Largest remaining gap, relative to the biggest moment on screen: 0.00%.
Pull the velocity back toward zero and the rows line up again. The red row stays open no matter how far the sliders go.
What the continuous Boltzmann equation demands#
For a body force , the force term of the Boltzmann equation is a derivative in velocity space.
Here is molecular velocity and is density. One integration by parts closes the third moment.
with . So the third moment being asked for reads as follows.
A piece and a piece sit side by side. The first is velocity-independent, the second is quadratic in velocity.
What Guo forcing actually supplies#
The forcing term on the lattice is usually written in Guo's form. Strip the half-step factor and call the bracket .
are the lattice weights and the discrete velocities. The actual carries a factor on top. Why that factor is there is covered separately in the half step the discrete LBE leaves behind.
Compute the third moment of this using the fourth-order isotropy of D2Q9 and this is what survives.
The piece is exactly right. Not one of the terms is there. The second bracket builds a fifth moment, which vanishes on D2Q9.
That is the split the velocity sliders already opened: two yellow rows move, and the red row stays locked.
: the two components the lattice locked#
The red row is the lattice itself. D2Q9 velocity components are only . That makes in every direction. The two vectors are the same vector.
is not a value you get to choose. Fixing the first moment to fixes it at the same time. The demand is , so is a permanent deficit.
and , by contrast, are independent. Of nine degrees of freedom the 0th to 2nd moments take six, leaving three. So of the four components, two are free and two are locked. You can fix half of a tensor.
A term that carries no mass and no momentum still moves momentum#
You can build a correction vector that changes only , by an amount . Its 0th, 1st and 2nd moments all vanish, so on the spot it touches neither density, nor momentum, nor stress. On D2Q9 that vector looks like this.
One streaming step changes the story. Each component flies to a different neighbour. Collect only the -momentum and this is what is left.
A second central difference. It is exactly zero when is uniform, and grows with the square of the wavenumber when oscillates. A uniform waveform kills the cell-to-cell change. Shorten a sine and that change grows with the square of the wavenumber. The sum of the cells stays zero.
A channel with a pinned force pattern and a cross-flow#
Verification needs a problem with an exact solution. Pin a sinusoidal -body-force in a periodic box, then blow a uniform cross-flow through it in . Porous walls injecting on one side and sucking on the other.
is kinematic viscosity, the wavenumber, the force amplitude. A single mode gives a closed form.
The cross-flow does two things. It shaves the amplitude and it lags the phase by . At the same time stays alive, so oscillates in space. That is the switch for the leak above.
Four combinations on the same lattice#
A 16-node periodic lattice, , , four runs. Plain Guo, the force third moment filled, the equilibrium third moment filled, and both.
import numpy as np
CX = np.array([0, 1, 0,-1, 0, 1,-1,-1, 1], float)
CY = np.array([0, 0, 1, 0,-1, 1, 1,-1,-1], float)
WT = np.array([4/9, 1/9, 1/9, 1/9, 1/9, 1/36, 1/36, 1/36, 1/36])
CS2 = 1.0/3.0
def hermite3_vectors():
"""Zero 0th-2nd moments, unit third moment for xxy / xyy only."""
rows = np.vstack([np.ones(9), CX, CY, CX*CX, CX*CY, CY*CY, CX*CX*CY, CX*CY*CY])
pinv = np.linalg.pinv(rows)
return pinv @ np.eye(8)[6], pinv @ np.eye(8)[7]
VXXY, VXYY = hermite3_vectors()
def feq_second_order(rho, ux, uy):
cu = CX[:, None, None]*ux + CY[:, None, None]*uy
return WT[:, None, None]*rho*(1 + cu/CS2 + cu*cu/(2*CS2**2)
- (ux*ux + uy*uy)/(2*CS2))
def force_populations(ux, uy, fx, fy, tau):
"""Guo forcing: first moment F, second moment uF+Fu."""
cF = CX[:, None, None]*fx + CY[:, None, None]*fy
gxx, gyy, gxy = ux*fx, uy*fy, 0.5*(ux*fy + uy*fx)
quad = (gxx*(CX[:, None, None]**2 - CS2) + gyy*(CY[:, None, None]**2 - CS2)
+ 2*gxy*(CX*CY)[:, None, None])
return WT[:, None, None]*(1 - 1/(2*tau))*(cF/CS2 + quad/CS2**2)
def analytic_wave(ny, nu, V, a0):
"""Periodic steady solution of nu u'' - V u' + a0 sin(ky) = 0."""
k = 2*np.pi/ny
y = np.arange(ny, dtype=float)
c1 = a0/(nu*k*k + V*V/nu)
return c1*np.sin(k*y) - (V*c1/(nu*k))*np.cos(k*y)
def run_cross_flow(ny, tau, V, a0, cubic_eq, cubic_force, nstep=6000, tol=1e-12):
nx = 3
k = 2*np.pi/ny
fx = np.tile(a0*np.sin(k*np.arange(ny)), (nx, 1))
fy = np.zeros((nx, ny))
rho = np.ones((nx, ny)); ux = np.zeros((nx, ny)); uy = np.full((nx, ny), V)
f = feq_second_order(rho, ux, uy)
half, prev = 1 - 1/(2*tau), ux.copy()
for s in range(nstep):
rho = f.sum(0)
ux = ((CX[:, None, None]*f).sum(0) + 0.5*fx)/rho
uy = ((CY[:, None, None]*f).sum(0) + 0.5*fy)/rho
feq = feq_second_order(rho, ux, uy)
if cubic_eq: # fill the cubic defect rho*u*u*u of the equilibrium
feq = feq + VXXY[:, None, None]*(rho*ux*ux*uy) \
+ VXYY[:, None, None]*(rho*ux*uy*uy)
src = force_populations(ux, uy, fx, fy, tau)
if cubic_force: # fill the cubic defect F*u*u of the force term
src = src + half*(VXXY[:, None, None]*(2*fx*ux*uy + fy*ux*ux)
+ VXYY[:, None, None]*(fx*uy*uy + 2*fy*ux*uy))
f = f - (f - feq)/tau + src
for i in range(9):
f[i] = np.roll(f[i], (int(CX[i]), int(CY[i])), axis=(0, 1))
if s % 1000 == 999:
if np.max(np.abs(ux - prev)) < tol:
break
prev = ux.copy()
return ux[0]
print("D2Q9 lock check: max|cx^3 - cx| =", float(np.abs(CX**3 - CX).max()))
for tag, mom in (("xxy", CX*CX*CY), ("xyy", CX*CY*CY), ("xxx", CX**3), ("xy", CX*CY)):
print(f" correction vector v_xxy : sum v_i c_{tag:3s} = {float(mom @ VXXY): .6f}")
NY, TAU = 16, 1.0
NU, A0 = CS2*(TAU - 0.5), 0.004
print(f"\nny={NY} tau={TAU} nu={NU:.5f} k={2*np.pi/NY:.4f} a0={A0}")
print("\n V |u|max Guo +force +feq3 +both")
for V in (0.05, 0.10, 0.20):
ref = analytic_wave(NY, NU, V, A0)
errs = []
for ce, cf in ((0, 0), (0, 1), (1, 0), (1, 1)):
u = run_cross_flow(NY, TAU, V, A0, bool(ce), bool(cf))
errs.append(100*np.linalg.norm(u - ref)/np.linalg.norm(ref))
print(f"{V:5.2f} {np.abs(ref).max():.5f} " + " ".join(f"{e:7.4f}%" for e in errs))D2Q9 lock check: max|cx^3 - cx| = 0.0
correction vector v_xxy : sum v_i c_xxy = 1.000000
correction vector v_xxy : sum v_i c_xyy = -0.000000
correction vector v_xxy : sum v_i c_xxx = 0.000000
correction vector v_xxy : sum v_i c_xy = -0.000000
ny=16 tau=1.0 nu=0.16667 k=0.3927 a0=0.004
V |u|max Guo +force +feq3 +both
0.05 0.12258 0.5993% 0.5915% 0.0181% 0.0143%
0.10 0.08374 1.6422% 1.6207% 0.0295% 0.0375%
0.20 0.04826 3.7307% 3.6840% 0.0531% 0.1503%The gap between +feq3 and +both in that table grows as the wavelength shrinks. The waveform toggle and the wavelength slider are the bars for that leak.
Switch d(y) to uniform: the dots still fly, the green bars all collapse to zero. Switch back to sine and shorten the wavelength: the bars grow like the second difference. Peak delivered momentum 0.00e+0, total over the column 0 (exactly), predicted peak 0.5*|d''| = 0.00e+0.
On uniform the green bars sit at zero. Shorten the wavelength on sine and the bars grow with the square of the wavenumber, while the sum of the cells stays zero.
The correction that turned 0.053% into 0.150%#
Read the table one row at a time and the ordering shows up. Numbers are at .
| Combination | error | versus plain |
|---|---|---|
| Plain Guo | 3.7307% | — |
| Force third moment | 3.6840% | 1.3% better |
| Equilibrium third moment | 0.0531% | 70x better |
| Both | 0.1503% | 2.8x worse than equilibrium alone |
Filling the third moment of the force buys 1.3% at best. Filling the third moment of the equilibrium cuts the error 70-fold. Put both in and it gets worse again. The three numbers tell one story.
The reason lives in the order of the Chapman-Enskog expansion. The second moment of the force enters the stress equation directly. The third moment of the force enters the third-moment equation, descends from there into the stress, and from the stress into momentum. It arrives in the momentum equation at , that is, at Burnett order. For an isothermal LBM aiming at Navier-Stokes, that term is not something to match.
The third moment of the equilibrium, by contrast, enters the stress directly. That is why the cubic defect of the equilibrium dominates.
The path by which the correction manufactured error#
So why does filling the force third moment actively hurt? The leak formula above answers it. Since , streaming manufactures a spurious body force of this size.
Put in , , and it is of . The largest gap measured between the two solutions in the code was of . The two numbers agree. The lattice faithfully delivered a term the momentum equation never asked for.
The dependence shows up cleanly too. Raising through , the gap between the two solutions went . A factor of four each time.
Where the third moment genuinely starts to matter#
None of this makes the derivation of pointless. There are places that need it.
In thermal or compressible LBM that solves an energy equation, the third moment is the heat flux. There the third moment of the force enters the energy equation directly. No trip to Burnett order required. Multiphase LBM that builds interfaces from a non-ideal equation of state is similar. The force is large and spatially steep. Once approaches one, is no longer small.
At , plain Guo is 3.7307%. Filling only the force third moment gives 3.6840%. The difference is 0.0467 percentage points, about 1.3% of 3.7307. Equilibrium alone is 0.0531%. Divide 3.7307 by 0.0531 and the factor is about 70. Both together give 0.1503%. Divide 0.1503 by 0.0531 and the factor is about 2.83, the 2.8× in the table. At , both together is 0.0143%, smaller than equilibrium alone at 0.0181%. At the order flips, 0.0375% against 0.0295%. At the gap is 0.0972 percentage points.
Plug , , into the leak. Then is . Times the leading that is . , and is about . One quarter times 0.04 times 0.1542 is 0.001542, which is 0.154% of . The code measured 0.156% of . The two sit within 0.002 percentage points. Doubling from 0.05 to 0.10 to 0.20 multiplies by four, and the gaps 0.010%, 0.039%, 0.156% do the same. falls through 0.12258, 0.08374, 0.04826. The denominator of is with , so a larger shaves the amplitude.
Where the missing half of the force goes is what the side-by-side of Guo, Shan–Chen, and EDM splits on the same factor . Count the target equation, and the order at which the term arrives, before adding the line.
Why you cannot half-fill a term the lattice does not carry#
The real lesson of this experiment sits ahead of the order counting. is a tensor. D2Q9 grants and and locks and . Fill half of a tensor and you have inserted a correction with broken isotropy. Errors that were cancelling can surface, and here they did.
When you find a term the lattice cannot carry, there are two options. Enlarge the velocity set until you own that moment (D2Q17, D2Q37), or leave it alone entirely. There is no middle. Before adding one line to a forcing term, count whether the rest of that line exists on your lattice.
Related
Share if you found it helpful.