40) CFD examples#
Today#
Computational Fluid Dynamics Examples
Conservation
Hamiltonians
Symplectic integrators
using Plots
default(linewidth=3)
using LinearAlgebra
using LaTeXStrings
using SparseArrays
function plot_stability(Rz, title; xlims=(-2, 2), ylims=(-2, 2))
x = LinRange(xlims[1], xlims[2], 100)
y = LinRange(ylims[1], ylims[2], 100)
heatmap(x, y, (x, y) -> abs(Rz(x + 1im*y)), c=:bwr, clims=(0, 2), aspect_ratio=:equal, title=title)
end
struct RKTable
A::Matrix
b::Vector
c::Vector
function RKTable(A, b)
s = length(b)
A = reshape(A, s, s)
c = vec(sum(A, dims=2))
new(A, b, c)
end
end
function rk_stability(z, rk)
s = length(rk.b)
1 + z * rk.b' * ((I - z*rk.A) \ ones(s))
end
rk4 = RKTable([0 0 0 0; .5 0 0 0; 0 .5 0 0; 0 0 1 0], [1, 2, 2, 1] / 6)
function ode_rk_explicit(f, u0; tfinal=1., h=0.1, table=rk4)
u = copy(u0)
t = 0.
n, s = length(u), length(table.c)
fY = zeros(n, s)
thist = [t]
uhist = [u0]
while t < tfinal
tnext = min(t+h, tfinal)
h = tnext - t
for i in 1:s
ti = t + h * table.c[i]
Yi = u + h * sum(fY[:,1:i-1] * table.A[i,1:i-1], dims=2)
fY[:,i] = f(ti, Yi)
end
u += h * fY * table.b
t = tnext
push!(thist, t)
push!(uhist, u)
end
thist, hcat(uhist...)
end
ode_rk_explicit (generic function with 1 method)
1. Computational Fluid Dynamics (CFD) examples#
Gas equations of state#
There are many ways to describe a gas
Name |
variable |
units |
---|---|---|
pressure |
\(p\) |
force/area |
density |
\(\rho\) |
mass/volume |
temperature |
\(T\) |
Kelvin |
(specific) internal energy |
\(e\) |
[energy]/mass |
entropy |
\(s\) |
[energy]/Kelvin |
Equation of state#
Ideal gas#
pressure(rho, T) = rho*T
contour(LinRange(0, 2, 30), LinRange(0, 2, 30), pressure, xlabel="\$\\rho\$", ylabel="\$T\$")
temperature(rho, pressure) = pressure / rho
contour(LinRange(0, 2, 30), LinRange(0, 2, 30), temperature, xlabel="\$\\rho\$", ylabel="\$p\$")
Conservation equations#
Conservation of mass#
Let \(\mathbf u\) be the fluid velocity. The mass flux (mass/time) moving through an area \(A\) is
If mass is conserved in a volume \(V\) with surface \(A\), then the total mass inside the volume must evolve as
where we have applied Gauss’ divergence theorem.
Dropping the integrals over arbitrary volumes, we have the evolution equation for conservation of mass:
also called the continuity equation.
Conservation of linear momentum#
The momentum \(\rho \mathbf u\) has a flux that includes
convection \(\rho \mathbf u \otimes \mathbf u\)
this is saying that each component of momentum is carried along in the vector velocity field
pressure \(p I\)
stress tensor \(-\boldsymbol\tau\) due to viscosity (internal friction forces)
A similar integral principle leads to the momentum equation
Simplifications#
In the case of inviscid fluids (e.g, water, air) we ignore the viscous stress tensor \(\boldsymbol \tau\)
Ignore energy equation (not yet written) and assume constant temperature
\(p = a^2 \rho\) where \(a\) is the acoustic wave speed
Linearization#
Split each state variable into a mean state (denoted by bar) and a small fluctuation (denoted by tilde)
\(\rho = \bar\rho + \tilde\rho\)
\(u = \bar u + \tilde u\)
Let \(\widetilde{\rho u} = (\bar\rho + \tilde\rho) (\bar u + \tilde u) - \bar\rho\bar u \approx \tilde \rho \bar u + \bar\rho \tilde u\), where we have dropped the second order term \(\tilde \rho\tilde u\) because both are assumed small.
We consider background state \(\bar u = 0\) and constant \(\bar\rho(x,y,t) = \bar\rho\). Then
Two forms of acoustic wave equation#
Divide the momentum equation through by background density and dropping the tildes yields the standard form.
Let’s examine the second equation term:
and thus
Let’s differentiate the first equation in time,
and substitute in the second equation
Note: we had to assume these derivatives exist!
Any \(n\)-th order linear ODE can be rewritten as \(n\) first-order ODEs. Hence, we can reduce this to a system of two first-order equations as
Question#
How is the problem size different?
What might we be concerned about in choosing the second formulation?
Example: Laplacian in a periodic domain#
function laplacian_matrix(n)
h = 2 / n
rows = Vector{Int64}()
cols = Vector{Int64}()
vals = Vector{Float64}()
wrap(i) = (i + n - 1) % n + 1
idx(i, j) = (wrap(i)-1)*n + wrap(j)
stencil_diffuse = [-1, -1, 4, -1, -1] / h^2 # centered diff on uniform grid
for i in 1:n
for j in 1:n
append!(rows, repeat([idx(i,j)], 5))
append!(cols, [idx(i-1,j), idx(i,j-1), idx(i,j), idx(i+1,j), idx(i,j+1)])
append!(vals, stencil_diffuse)
end
end
sparse(rows, cols, vals)
end
cond(Matrix(laplacian_matrix(5)))
2.9959163385932148e16
L = laplacian_matrix(10)
ev = eigvals(Matrix(L))
scatter(real(ev), imag(ev), label = "eigenvalues", xlims = (-50, 200), ylims = (-50, 200))
The wave operator#
Let’s focus on the second equation in the system, a wave equation:
function acoustic_wave_matrix(n; a=1)
Z = spzeros(n^2, n^2)
L = laplacian_matrix(n)
[Z I; -a^2*L Z]
end
acoustic_wave_matrix(2)
8×8 SparseMatrixCSC{Float64, Int64} with 16 stored entries:
⋅ ⋅ ⋅ ⋅ 1.0 ⋅ ⋅ ⋅
⋅ ⋅ ⋅ ⋅ ⋅ 1.0 ⋅ ⋅
⋅ ⋅ ⋅ ⋅ ⋅ ⋅ 1.0 ⋅
⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ 1.0
-4.0 2.0 2.0 ⋅ ⋅ ⋅ ⋅ ⋅
2.0 -4.0 ⋅ 2.0 ⋅ ⋅ ⋅ ⋅
2.0 ⋅ -4.0 2.0 ⋅ ⋅ ⋅ ⋅
⋅ 2.0 2.0 -4.0 ⋅ ⋅ ⋅ ⋅
A = acoustic_wave_matrix(8; a=2) * .1
ev = eigvals(Matrix(A))
plot_stability(z -> rk_stability(z, rk4), "RK4", xlims=(-4, 4), ylims=(-4, 4))
scatter!(real(ev), imag(ev), color=:black, label = "eigenvalues", xlims=(-4, 4), ylims=(-4, 4), xlabel = L"Re(z)", ylabel = L"Im(z)")
Question: would forward Euler work?#
Example: 2D wave solver with RK4#
n = 20
A = acoustic_wave_matrix(n)
x = LinRange(-1, 1, n+1)[1:end-1]
y = x
rho0 = vec(exp.(-9*((x .+ 1e-4).^2 .+ y'.^2)))
sol0 = vcat(rho0, zero(rho0))
thist, solhist = ode_rk_explicit((t, sol) -> A * sol, sol0, h=.02)
size(solhist)
(800, 51)
@gif for tstep in 1:length(thist)
rho = solhist[1:n^2, tstep]
contour(x, y, reshape(rho, n, n), title="\$ t = $(thist[tstep])\$")
end
[ Info: Saved animation to /home/valeria/Dropbox/SDSU/Teaching/Comp526/fall24/slides/tmp.gif
2. Conservation#
Accuracy and conservation of mass with RK4#
thist, solhist = ode_rk_explicit((t, sol) -> A * sol, sol0, h=.05,
tfinal=1)
tfinal = thist[end]
M = exp(Matrix(A*tfinal))
sol_exact = M * sol0
sol_final = solhist[:, end]
norm(sol_final - sol_exact)
0.02015111748435498
mass = vec(sum(solhist[1:n^2, :], dims=1))
plot(thist[2:end], mass[2:end] .- mass[1], label = "mass conservation", xlabel = "t", ylabel = L"mass_{t_f} - mass_{t_0}")
Let’s analyze the conservation of energy with RK4.
3. Hamiltonians#
We can express the total energy for our system as a sum of kinetic and potential energy:
where we identify \(\rho\) as a generalized position and \(\dot\rho\) as generalized momentum. Hamilton’s equations state that the equations of motion are
where we have used the weak form to associate \(\int \nabla v \cdot \nabla u = v^T L u\).
function energy(sol, n)
L = laplacian_matrix(n)
rho = sol[1:end÷2]
rhodot = sol[end÷2+1:end]
kinetic = .5 * norm(rhodot)^2
potential = .5 * rho' * L * rho
kinetic + potential
end
ehist = [energy(solhist[:,i], n) for i in 1:length(thist)]
plot(thist, ehist, xlabel = "t", ylabel = "e", label = "e")
Velocity Verlet integrator#
function wave_verlet(n, u0; tfinal=1., h=0.1)
L = laplacian_matrix(n)
u = copy(u0)
t = 0.
thist = [t]
uhist = [u0]
irho = 1:n^2
irhodot = n^2+1:2*n^2
accel = -L * u[irho]
while t < tfinal
tnext = min(t+h, tfinal)
h = tnext - t
u[irho] += h * u[irhodot] + h^2/2 * accel
accel_next = -L * u[irho]
u[irhodot] += h/2 * (accel + accel_next)
accel = accel_next
t = tnext
push!(thist, t)
push!(uhist, copy(u))
end
thist, hcat(uhist...)
end
wave_verlet (generic function with 1 method)
thist, solhist = wave_verlet(n, sol0, h=.04)
@gif for tstep in 1:length(thist)
rho = solhist[1:n^2, tstep]
contour(x, y, reshape(rho, n, n), title="\$ t = $(thist[tstep])\$")
end
[ Info: Saved animation to /home/valeria/Dropbox/SDSU/Teaching/Comp526/fall24/slides/tmp.gif
Accuracy and conservation for Verlet#
thist, solhist = wave_verlet(n, sol0, h=.05, tfinal=50)
tfinal = thist[end]
M = exp(Matrix(A*tfinal))
sol_exact = M * sol0
sol_final = solhist[:, end]
@show norm(sol_final - sol_exact)
mass = vec(sum(solhist[1:n^2, :], dims=1))
plot(thist[2:end], mass[2:end] .- mass[1], label = "mass conservation", xlabel = "t", ylabel = L"mass_{t_f} - mass_{t_0}")
norm(sol_final - sol_exact) = 6.862500992527711
ehist = [energy(solhist[:,i], n) for i in 1:length(thist)]
plot(thist, ehist, xlabel = "t", ylabel = "e", label = "e")
Notes on time integrators#
We need stability on the imaginary axis for our discretization (and the physical system)
If the model is dissipative (e.g., we didn’t make the zero-viscosity assumption), then we need stability in the left half plane.
The split form \(\rho, \rho\mathbf u\) form is usually used with (nonlinear) upwinding, and thus will have dissipation.
Runge-Kutta methods#
Easy to use, stability region designed for spatial discretization
Energy drift generally present
Verlet/leapfrog/Newmark and symplectic integrators#
These preserve the “geometry of the Hamiltonian”
energy is not exactly conserved, but it doesn’t drift over time
such methods are called “symplectic integrators”
May not have stability away from the imaginary axis (for dissipation)
Most require a generalized position/momentum split, “canonical variables”