A crude and naive way to do is to split the world into a 3D grid of cells, labeled whether it is an obstacle, fluid, or empty space. The fluid velocity is defined on the cell wall.
source code
The fluid velocity has 3 components
u=(u1,u2,u3)
like 3 scalar fields, with u1 defined on the faces normal to the x−direction (and similarly with the other two). Velocity on a wall next to a solid cell must be 0.
source code
Let F denote the set of faces incident to both fluid cells.
Let C denote the set of fluid cells.
Velocity field is a data u∈RF.
Scalar quantities like pressure and density are stored at cell center.
The divergence of the velocity field is defined on fluid cells
q=div u∈RC
where div:RF→RC (a ∣C∣×∣F∣ sparse matrix) is defined by aggregating the total flux from each cell wall.
Pressure Projection
Given non divergence free u~∈RF, (some velocity field generated without respect to pressure), we need to try and project it to ker(div)⊂RF. The idea here is that ker(div) consists of all velocity fields with exactly zero divergence. The goal of the algorithm is to find the closest valid vector in this subspace to our invalid input vector u~.
Idea
This is mathematically the least-squares projection problem. Again, treat div as a linear operator (matrix).
We want to solve for pressure p∈RC where
u=u~−div⊤p
and div u=0 (to satisfy incompressibility). Mathematically this means we need to find a p where
Note that div⊤:RC→RF is a discrete (negative) gradient operator. So, solving this system is the same as finding Ax=b where
Adivdiv⊤p=bdiv u~
and x:=p. We can also see that the matrix divdiv⊤∈R∣C∣×∣C∣ is a discrete Laplacian. Fortunately, this means we can solve this by any linear algebra routine such as conjugate gradient.
Applying FFT (Spectral Method)
If the domain has a simple periodic boundary condition, then we can use the Fast Fourier Transform (FFT) to turn this system into a diagonalized equation with direct solution.
For a fluid to have a simple periodic boundary condition, it means a fluid flows out one side of some volume (like a cube) and flows in seamlessly on the left1.
Let (L1,L2,L3) be the tuple that describes the “size” of the periodic domain. The goal is to solve the Poisson problem Δu=f2 such that
∂x2∂2u+∂y2∂2u+∂z2∂2u=f
2Δ is actually the Laplacian operator, the continuous equivalent of the discrete divdiv⊤ matrix!
We will discretize both u and f on a regular grid with resolution (N1,N2,N3). Then apply FFT:
where j1,j2,j3 represent the individual grid cells and k1,k2,k3 represent the individual frequencies. Recall that u,f represent variables in the spatial domain and u^,f^ represent variables in the frequency domain.
This is fast because we can represent the pressure field as a linear combination of Fourier basis functions. More simply, image u is the sum of one wave, u=eikx where x is the position in space and k is the frequency of the wave. The derivative of u is ∂x∂u=ikeikx, making this operation on a computer much easier.
Since the simulation exists on a discrete grid of fixed pixels, we need to prevent aliasing2 via the following definition of p1:
There are two distinct subspaces that our final velocity field u can exist in
The subspace of all possible velocity fields where the fluid is perfectly incompressible everywhere (i.e. div u=0).
The subspace of all possible velocity fields where the fluid inside and immediately on the boundary of the solid obstacle is completely stationary (i.e. u=0).
The solver finds the intersection of both. The alternating projection (pressure project → set velocity to zero by enforcing obstacles) will converge to their intersection.
Obstacles
If we want to add obstacles, we can just do Pressure Projection and set velocity in the obstacle to zero.
solve the advection term (for half timestep):
∂t∂u+∇uu=0
pressure “reflect”
u←u−∇Δ−1∇⋅u
Because we advected the fluid while ignoring pressure, the fluid followed the wrong trajectory for that entire Δt. Projecting it straight back down does not return it to where it would have been if pressure had been applied continuously.
Reflection Method (Strang)
We can solve this by: for each time step, we will
solve the advection term (for half timestep):
∂t∂u+∇uu=0
pressure “reflect”
u∗u=u−∇Δ−1∇⋅u←2u∗−u
This reflection across the div-free subspace allows you to push the fluid further along the correct trajectory (via the advection step) before projecting it back down to the div-free subspace.
solve the advection equation (for half timestep):
∂t∂u+∇uu=0
pressure project
source code
Advection
Let v be a time-independent vector field over the world coordinate W. A time dependent scalar function f is advected by v if
∂t∂f+v⋅∇f=0 or ∂t∂f+df[[v]]=0 or ∂t∂f+Lvf=0
where Lv is the Lie Derivative. Equivalently, if φt:W→W is the flow map generated by v (φ˙=v∘φ and φ0=idW) and let ψt:=φt−1 (inverse flow map), then an advected function satisfies
ft(φt(X))=f0(X) or ft(x)=f0(ψt(x))
In other words, tracing the flow with ψt allows us to see where the fluid originally came from. We can convert this to the language of pullbacks.
connecting continuous geometry (the pullback) to discrete numerical methods (the t).
We have two numerical methods for advection.
Eulerian method: approximate the exp(derivative) or the pullback operator
Lagrangian method: set quantites on particles (or other geometry) and let the particle move by v.
1D Advection of a Function
Suppose we have an advection equation with flow velocitya=a(x).
∂t∂u(t,x)+a∂x∂u(t,x)=0 or u˙+au′=0
Let uin denote the approximate solution at the i−th gridpoint and n−th timestep.
Aside: Von Neumann Stability Analysis
We can analyze the stability of a numerical method by looking at how it amplifies or dampens perfect continuous sine waves rnexp(ikx). The scaling factor is r∈C. If ∣r∣>1, the wave amplifies and the method is unstable. If ∣r∣<1, the wave dampens and the method is stable. If ∣r∣=1, the wave is preserved and the method is neutrally stable.
Central Difference Method
A straightforward approach is to look at the neighboring cells to find the slope. We then step forward in time by Δt using the formula
Because its magnitude is greater than 1, this method is unconditionally unstable, meaning no matter how small we make Δt, the method will always explode.
But this leaves us with some Taylor expansion errors since we will try to approximate a continuos function on discrete grid points. We must analyze the modified euquation to see what errors we are making. For example, the central difference method generates
u˙=−au′−numericaldispersion12Δx2au′′′
The numerical dispersion term means waves of different frequencies will travel at different speeds, and cause rippling near sharp corners (like in a square wave).
Lax-Friedrichs Method
One quick fix to the central difference method is to stop using the current cell value, and instead take the average of its left and right neighbors. This gives us the following update rule:
uin+1=21(ui−1n+ui+1n)−2ΔxaΔt(ui+1n−ui−1n)
Using Von Neumann Stability Analysis, we can find that
r=cos(k)−iΔxaΔtsin(k)
It is stable iff
ΔxaΔt≤1
and so it is conditionally stable. We get the modified equation:
u˙=−au′+numericaldiffusion2Δx2u′′
The average injects an artificial diffusion term, which causes the solution to blur over time.
Upwind Method
Recall the u˙+au′=0 equation. The Upwind Method examines how a, the transport coefficient, changes sign.
aΔt is the actual physical distance the fluid travels in one time step.
Δx is the physical width of one grid cell.
The CFL condition is a strict speed limit: the fluid cannot travel more than one grid cell per time step.
Semi-Lagrangian Scheme
Consider again the advection equation u˙+au′=0. Sometimes we want a larger time step. We can do this by tracing the flow backwards in time.
uin+1=u(xi−aΔt)
How it works:
Stand at the current grid cell center xi.
Trace a trajectory backwards in time by Δt seconds.
Arrive at the physical location ψΔt(xi)=xi−aΔt.
The starting location will almost certainly not land perfectly on a grid cell center, so interpolate the values of the surrounding grid cells to estimate the fluid data at that exact continuous coordinate.
Copy that value into the current grid cell.
This method is unconditionally stable. The modified equation is the same as the Upwind Method.
nD Advection of a Function
The advection equation is now
f˙+Lvf=0
We want to approximate
fn+1=ψΔt∗fn
The Semi-Lagrangian scheme is somewhat the same. For scalar functions, we use
fin+1=Interpolate(fn,xi−Δtvi)
In 1D, interpolation is simple between two points. In 2D, we need interpolate the surronding 4 grid cells (pixels). In 3D, we need to interpolate the surrounding 8 grid cells (voxels).
Let us call one semi-Lagrangian step as fn+1−Av,ΔtsL(fn). By combining multiple semi-Lagrangian steps, we can bootstrap a higher order method.
Back-and-Forth Error Correction and Compensation (BFECC)
How can we remove the blur caused by the u′′ term if the grid fundamentally requires interpolation? The idea is to trick the grid into calculating its own error and then compensate (subtract) for it.
Here, we are calculating the forward step via f∗, and then finding the backward step by applying the opposite velocity −v to f∗ giving us f∗∗. The remaining term is the error, which we take the semi-Lagrangian step of and subtract from f∗. Indeed,
ffinal=f∗−Av,ΔtsLerror(2f∗∗−f)
The tradeoff is that we remove numerical dissipation (blurring) but we get some numerical dispersion (spatial oscillation). We can detect and cutoff overshoots using a limiter.
Limiter
If the value of the field after BFECC is lying outside some convex hull of the neighborhood values of stable semi-Lagrangian, blend with or use the stbale semi-Lagrangian result.
This is a term from computer graphics. It refers to the phenomenon where high-frequency signals are misinterpreted as low-frequency signals when sampled at a discrete rate. ↩↩2↩3