Training CFD
Training CFD
φo = φ(t = told )
φn = φ(t = tnew )
∂φ φn − φo
=
∂t ∆t
3 n
∂φ 2
φ − 2φo + 12 φoo
=
∂t ∆t
∂φ φn − φo
Z
dV = VP
V ∂t ∆t
3 n
∂φ φ − 2φo + 12 φoo
Z
2
dV = VP
V ∂t ∆t
Temporal Derivative
• Calculus: given φn , φo and ∆t create a field of the time derivative of φ
• Method: matrix representation. Since ∂φ
∂t
in cell P depends on φP , the matrix will
only have a diagonal contribution and a source
VP
◦ Diagonal coefficient: aP = ∆t
VP φo
◦ Source contribution: rP = ∆t
F = sf •uf
• Upwind differencing: taking into account the transportive property of the term:
information comes from upstream. No oscillations, but smears the solution
φf = pos(F ) φP + neg(F ) φN
• There exists a large number of schemes, trying to achieve good accuracy without
causing oscillations: e.g. TVD, and NVD families: φf = f (φP , φN , F, . . .)
F
φD
φf
+
φf
− φC
φU
− +
U f C f D
φN − φP
sf •(∇φ)f = |sf |
|df |
s
k
f
d ∆
P N
|sf |
λ (φN − φP ) > kf •∇(φ)f
|df |
[A][x] = [b]
where [A] contain matrix coefficients, [x] is the value of xP in all cells and [b] is the
right-hand-side
• [A] is potentially very big: N cells × N cells
• This is a square matrix: the number of equations equals the number of unknowns
• . . . but very few coefficients are non-zero. The matrix connectivity is always local,
potentially leading to storage savings if a good format can be found
∇•u = 0
∇•(uu) ≈ ∇•(uo un )
where uo is the currently available solution or an initial guess and un is the “new”
solution. The algorithm cycles until uo = un
• This structure will arise naturally when trying to solve a block system of equations
Ax + By = a
Cx + Dy = b
• The Schur complement arises when trying to eliminate x from the system using
partial Gaussian elimination by multiplying the first row with A−1 :
and
x = A−1 a − A−1 By
• Let us repeat the same set of operations on the block form of the pressure-velocity
system, attempting to assemble a pressure equation. Note that the operators in
the block system could be considered both as differential operators and in a
discretised form
" #" # " #
[Au ] [∇(.)] u 0
=
[∇•(.)] [0] p 0
[∇•(.)][A−1
u ][∇(.)][p] = 0
Here, A−1
u represent the inverse of the momentum matrix in the discretised form,
which acts as diffusivity in the Laplace equation for the pressure.
where [Du ] only contains diagonal entries. [Du ] is easy to invert and will preserve
the sparseness pattern in the triple product.
−1 −1
[∇•(.)][Du ][∇(.)][p] = [∇•(.)][Du ][LUu ][u]
−1
In both cases, matrix [Du ] is simple to assemble.
• It follows that the pressure equation is a Poisson equation with the diagonal part of
the discretised momentum acting as diffusivity and the divergence of the velocity
on the r.h.s.
• For simplicity, we shall introduce the H(u) operator, containing the off-diagonal
part of the momentum matrix and any associated r.h.s. contributions:
X
H(u) = r − au
N uN
N
• Note: H(u) carries ALL rhs luggage with it: this is a matrix-level operator and
cannot distinguish individual operator contributions
au
P uP = H(u) − ∇p
and
−1
uP = (au
P) (H(u) − ∇p)
• Conservative face flux should be created from the solution of the pressure
equation. Substituting expression for u into the flux equation, it follows:
−1 −1
F = −(au
P) sf •∇p + (au
P) sf •H(u)
• Note: this is the one and only set of conservative data for the velocity:
everything else is “pretending to be” divergence-free!
• Interpolating cell-centred u to the face carries discretisation error: which
differencing scheme is correct?
|sf |
(au
P)
−1
sf •∇p = (au
P)
−1
(pN − pP ) = apN (pN − pP )
|d|
|sf |
Here, apN = (au
P)
−1
|d|
is equal to the off-diagonal matrix coefficient in the
pressure Laplacian
• Note that in order for the face flux to be conservative, assembly of the flux must ba
completely consistent with the assembly of the pressure equation (e.g.
non-orthogonal correction)
fvVectorMatrix UEqn
(
fvm::ddt(U) // Uff, Oh!
+ fvm::div(phi, U)
- fvm::laplacian(nu, U)
);
solve(UEqn == -fvc::grad(p));
rDeltaT = 1.0/mesh().time().deltaT();
phiCorr = phiAbs.oldTime()
- (fvc::interpolate(U.oldTime()) & mesh().Sf());
return fvcDdtPhiCoeff*
fvc::interpolate(rDeltaT*rA)*phiCorr;
• . . . but this is horrible and inconsistent reverse engineering!
• Make sure that at the point of call phi.oldTime() and U.oldTime() are
consistent (How?!?)
• What about topo changes where old fluxes may not exist on current faces?
• Equivalent error exists with under-relaxation: not fixed!
• Let’s do this properly: eliminate the problem instead of patching up the
errors
// Convection-diffusion matrix
fvVectorMatrix HUEqn
(
fvm::div(phi, U)
- fvm::laplacian(nu, U)
);
U = HUEqn.H()/aU;
phi = (fvc::interpolate(U) & mesh.Sf());
adjustPhi(phi, U, p);
fvScalarMatrix pEqn
(
fvm::laplacian(1/aU, p) == fvc::div(phi)
);
pEqn.solve();
phi -= pEqn.flux();
U = 1.0/(aU + ddtUEqn.A())*
(
U*aU - fvc::grad(p) + ddtUEqn.H()
);
tmp<fvVectorMatrix> UEqn
(
fvm::div(phi, U)
+ turbulence->divDevReff(U)
);
solve(UEqn() == -fvc::grad(p));
volScalarField AU = UEqn().A();
U = UEqn().H()/AU;
phi = fvc::interpolate(U) & mesh.Sf();
adjustPhi(phi, U, p);
fvScalarMatrix pEqn
(
fvm::laplacian(1.0/AU, p) == fvc::div(phi)
);
pEqn.solve();
p.relax();
U -= fvc::grad(p)/AU;
U.correctBoundaryConditions();
tmp<fvVectorMatrix> HUEqn
(
fvm::div(phi, U)
+ turbulence->divDevReff()
);
solve
(
relax(HUEqn(), UUrf) // Keep orig. matrix intact!
==
-fvc::grad(p)
);