#![allow(dead_code)]
use crate::math::{Fix128, Vec3Fix};
#[cfg(not(feature = "std"))]
use alloc::vec;
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
#[derive(Clone, Debug)]
pub struct MacGrid {
pub nx: usize,
pub ny: usize,
pub nz: usize,
pub dx: Fix128,
pub u: Vec<Fix128>,
pub v: Vec<Fix128>,
pub w: Vec<Fix128>,
pub pressure: Vec<Fix128>,
}
impl MacGrid {
#[must_use]
pub fn new(nx: usize, ny: usize, nz: usize, dx: Fix128) -> Self {
Self {
nx,
ny,
nz,
dx,
u: vec![Fix128::ZERO; (nx + 1) * ny * nz],
v: vec![Fix128::ZERO; nx * (ny + 1) * nz],
w: vec![Fix128::ZERO; nx * ny * (nz + 1)],
pressure: vec![Fix128::ZERO; nx * ny * nz],
}
}
#[inline]
#[must_use]
pub(crate) fn idx_u(&self, i: usize, j: usize, k: usize) -> usize {
i + (self.nx + 1) * (j + self.ny * k)
}
#[inline]
#[must_use]
pub(crate) fn idx_v(&self, i: usize, j: usize, k: usize) -> usize {
i + self.nx * (j + (self.ny + 1) * k)
}
#[inline]
#[must_use]
pub(crate) fn idx_w(&self, i: usize, j: usize, k: usize) -> usize {
i + self.nx * (j + self.ny * k)
}
#[inline]
#[must_use]
fn idx_c(&self, i: usize, j: usize, k: usize) -> usize {
i + self.nx * (j + self.ny * k)
}
#[must_use]
pub fn u(&self, i: usize, j: usize, k: usize) -> Fix128 {
if i > self.nx || j >= self.ny || k >= self.nz {
return Fix128::ZERO;
}
self.u[self.idx_u(i, j, k)]
}
#[must_use]
pub fn v(&self, i: usize, j: usize, k: usize) -> Fix128 {
if i >= self.nx || j > self.ny || k >= self.nz {
return Fix128::ZERO;
}
self.v[self.idx_v(i, j, k)]
}
#[must_use]
pub fn w(&self, i: usize, j: usize, k: usize) -> Fix128 {
if i >= self.nx || j >= self.ny || k > self.nz {
return Fix128::ZERO;
}
self.w[self.idx_w(i, j, k)]
}
#[must_use]
pub fn pressure(&self, i: usize, j: usize, k: usize) -> Fix128 {
if i >= self.nx || j >= self.ny || k >= self.nz {
return Fix128::ZERO;
}
self.pressure[self.idx_c(i, j, k)]
}
#[must_use]
pub fn cell_velocity(&self, i: usize, j: usize, k: usize) -> (Fix128, Fix128, Fix128) {
let u_c = (self.u(i, j, k) + self.u(i + 1, j, k)).half();
let v_c = (self.v(i, j, k) + self.v(i, j + 1, k)).half();
let w_c = (self.w(i, j, k) + self.w(i, j, k + 1)).half();
(u_c, v_c, w_c)
}
#[must_use]
pub fn divergence(&self, i: usize, j: usize, k: usize) -> Fix128 {
if self.dx.is_zero() {
return Fix128::ZERO;
}
let du = self.u(i + 1, j, k) - self.u(i, j, k);
let dv = self.v(i, j + 1, k) - self.v(i, j, k);
let dw = self.w(i, j, k + 1) - self.w(i, j, k);
(du + dv + dw) / self.dx
}
}
pub fn project_pressure(grid: &mut MacGrid, dt_s: Fix128, density_kg_m3: Fix128, iterations: u32) {
project_pressure_red_black_gs(grid, dt_s, density_kg_m3, iterations);
}
pub(crate) fn project_pressure_red_black_gs(
grid: &mut MacGrid,
dt_s: Fix128,
density_kg_m3: Fix128,
iterations: u32,
) {
if grid.dx.is_zero() || density_kg_m3.is_zero() || dt_s.is_zero() {
return;
}
let scale = density_kg_m3 * grid.dx * grid.dx / dt_s;
let n = grid.nx * grid.ny * grid.nz;
let mut rhs = vec![Fix128::ZERO; n];
for k in 0..grid.nz {
for j in 0..grid.ny {
for i in 0..grid.nx {
let idx = i + grid.nx * (j + grid.ny * k);
rhs[idx] = grid.divergence(i, j, k) * scale;
}
}
}
let sixth = Fix128::from_ratio(1, 6);
for _ in 0..iterations {
for colour in 0..2u32 {
for k in 0..grid.nz {
for j in 0..grid.ny {
for i in 0..grid.nx {
if ((i + j + k) as u32 % 2) != colour {
continue;
}
let px = if i > 0 {
grid.pressure(i - 1, j, k)
} else {
Fix128::ZERO
};
let pxx = if i + 1 < grid.nx {
grid.pressure(i + 1, j, k)
} else {
Fix128::ZERO
};
let py = if j > 0 {
grid.pressure(i, j - 1, k)
} else {
Fix128::ZERO
};
let pyy = if j + 1 < grid.ny {
grid.pressure(i, j + 1, k)
} else {
Fix128::ZERO
};
let pz = if k > 0 {
grid.pressure(i, j, k - 1)
} else {
Fix128::ZERO
};
let pzz = if k + 1 < grid.nz {
grid.pressure(i, j, k + 1)
} else {
Fix128::ZERO
};
let idx = i + grid.nx * (j + grid.ny * k);
grid.pressure[idx] = (px + pxx + py + pyy + pz + pzz - rhs[idx]) * sixth;
}
}
}
}
}
let inv_dx = Fix128::ONE / grid.dx;
let coeff = dt_s / density_kg_m3 * inv_dx;
for k in 0..grid.nz {
for j in 0..grid.ny {
for i in 1..grid.nx {
let dp = grid.pressure(i, j, k) - grid.pressure(i - 1, j, k);
let ix = grid.idx_u(i, j, k);
grid.u[ix] = grid.u[ix] - coeff * dp;
}
}
}
for k in 0..grid.nz {
for j in 1..grid.ny {
for i in 0..grid.nx {
let dp = grid.pressure(i, j, k) - grid.pressure(i, j - 1, k);
let ix = grid.idx_v(i, j, k);
grid.v[ix] = grid.v[ix] - coeff * dp;
}
}
}
for k in 1..grid.nz {
for j in 0..grid.ny {
for i in 0..grid.nx {
let dp = grid.pressure(i, j, k) - grid.pressure(i, j, k - 1);
let ix = grid.idx_w(i, j, k);
grid.w[ix] = grid.w[ix] - coeff * dp;
}
}
}
}
pub(crate) fn project_pressure_jacobi(
grid: &mut MacGrid,
dt_s: Fix128,
density_kg_m3: Fix128,
iterations: u32,
) {
if grid.dx.is_zero() || density_kg_m3.is_zero() || dt_s.is_zero() {
return;
}
let scale = density_kg_m3 * grid.dx * grid.dx / dt_s;
let n = grid.nx * grid.ny * grid.nz;
let mut rhs = vec![Fix128::ZERO; n];
for k in 0..grid.nz {
for j in 0..grid.ny {
for i in 0..grid.nx {
let idx = i + grid.nx * (j + grid.ny * k);
rhs[idx] = grid.divergence(i, j, k) * scale;
}
}
}
let sixth = Fix128::from_ratio(1, 6);
let mut p_new = grid.pressure.clone();
for _ in 0..iterations {
for k in 0..grid.nz {
for j in 0..grid.ny {
for i in 0..grid.nx {
let px = if i > 0 {
grid.pressure(i - 1, j, k)
} else {
Fix128::ZERO
};
let pxx = if i + 1 < grid.nx {
grid.pressure(i + 1, j, k)
} else {
Fix128::ZERO
};
let py = if j > 0 {
grid.pressure(i, j - 1, k)
} else {
Fix128::ZERO
};
let pyy = if j + 1 < grid.ny {
grid.pressure(i, j + 1, k)
} else {
Fix128::ZERO
};
let pz = if k > 0 {
grid.pressure(i, j, k - 1)
} else {
Fix128::ZERO
};
let pzz = if k + 1 < grid.nz {
grid.pressure(i, j, k + 1)
} else {
Fix128::ZERO
};
let idx = i + grid.nx * (j + grid.ny * k);
p_new[idx] = (px + pxx + py + pyy + pz + pzz - rhs[idx]) * sixth;
}
}
}
grid.pressure.clone_from(&p_new);
}
let inv_dx = Fix128::ONE / grid.dx;
let coeff = dt_s / density_kg_m3 * inv_dx;
for k in 0..grid.nz {
for j in 0..grid.ny {
for i in 1..grid.nx {
let dp = grid.pressure(i, j, k) - grid.pressure(i - 1, j, k);
let ix = grid.idx_u(i, j, k);
grid.u[ix] = grid.u[ix] - coeff * dp;
}
}
}
for k in 0..grid.nz {
for j in 1..grid.ny {
for i in 0..grid.nx {
let dp = grid.pressure(i, j, k) - grid.pressure(i, j - 1, k);
let ix = grid.idx_v(i, j, k);
grid.v[ix] = grid.v[ix] - coeff * dp;
}
}
}
for k in 1..grid.nz {
for j in 0..grid.ny {
for i in 0..grid.nx {
let dp = grid.pressure(i, j, k) - grid.pressure(i, j, k - 1);
let ix = grid.idx_w(i, j, k);
grid.w[ix] = grid.w[ix] - coeff * dp;
}
}
}
}
pub(crate) fn project_pressure_bicgstab(
grid: &mut MacGrid,
dt_s: Fix128,
density_kg_m3: Fix128,
max_iterations: u32,
tolerance: Fix128,
) -> BicgstabStats {
let default_stats = BicgstabStats {
iterations: 0,
final_residual: Fix128::ZERO,
converged: true,
};
if grid.dx.is_zero() || density_kg_m3.is_zero() || dt_s.is_zero() {
return default_stats;
}
let scale = density_kg_m3 * grid.dx * grid.dx / dt_s;
let nx = grid.nx;
let ny = grid.ny;
let nz = grid.nz;
let n = nx * ny * nz;
let mut rhs = vec![Fix128::ZERO; n];
let mut diag = vec![Fix128::ZERO; n];
for k in 0..nz {
for j in 0..ny {
for i in 0..nx {
let idx = i + nx * (j + ny * k);
rhs[idx] = grid.divergence(i, j, k) * scale;
let mut deg: i64 = 6;
if i == 0 {
deg -= 1;
}
if i + 1 == nx {
deg -= 1;
}
if j == 0 {
deg -= 1;
}
if j + 1 == ny {
deg -= 1;
}
if k == 0 {
deg -= 1;
}
if k + 1 == nz {
deg -= 1;
}
diag[idx] = Fix128::from_int(-deg); }
}
}
let mut x = grid.pressure.clone();
let mut r = vec![Fix128::ZERO; n];
apply_poisson_a(&x, &mut r, nx, ny, nz);
for i in 0..n {
r[i] = rhs[i] - r[i];
}
let r_hat = r.clone();
let mut p_vec = r.clone();
let mut rho_prev = dot(&r_hat, &r);
let mut alpha = Fix128::ONE;
let mut omega = Fix128::ONE;
let mut v_vec = vec![Fix128::ZERO; n];
let mut y_vec = vec![Fix128::ZERO; n];
let mut z_vec = vec![Fix128::ZERO; n];
let mut s_vec = vec![Fix128::ZERO; n];
let mut t_vec = vec![Fix128::ZERO; n];
let mut iterations = 0_u32;
let mut residual = linf_norm(&r);
let mut converged = residual < tolerance;
while iterations < max_iterations && !converged {
iterations += 1;
let rho = dot(&r_hat, &r);
if rho.is_zero() {
break;
}
if iterations > 1 {
let beta = (rho / rho_prev) * (alpha / omega);
for i in 0..n {
p_vec[i] = r[i] + beta * (p_vec[i] - omega * v_vec[i]);
}
}
for i in 0..n {
y_vec[i] = if diag[i].is_zero() {
p_vec[i]
} else {
p_vec[i] / diag[i]
};
}
apply_poisson_a(&y_vec, &mut v_vec, nx, ny, nz);
let denom = dot(&r_hat, &v_vec);
if denom.is_zero() {
break;
}
alpha = rho / denom;
for i in 0..n {
s_vec[i] = r[i] - alpha * v_vec[i];
}
let s_norm = linf_norm(&s_vec);
if s_norm < tolerance {
for i in 0..n {
x[i] = x[i] + alpha * y_vec[i];
}
residual = s_norm;
converged = true;
break;
}
for i in 0..n {
z_vec[i] = if diag[i].is_zero() {
s_vec[i]
} else {
s_vec[i] / diag[i]
};
}
apply_poisson_a(&z_vec, &mut t_vec, nx, ny, nz);
let tt = dot(&t_vec, &t_vec);
if tt.is_zero() {
break;
}
omega = dot(&t_vec, &s_vec) / tt;
for i in 0..n {
x[i] = x[i] + alpha * y_vec[i] + omega * z_vec[i];
}
for i in 0..n {
r[i] = s_vec[i] - omega * t_vec[i];
}
residual = linf_norm(&r);
converged = residual < tolerance;
rho_prev = rho;
}
grid.pressure = x;
let inv_dx = Fix128::ONE / grid.dx;
let coeff = dt_s / density_kg_m3 * inv_dx;
for k in 0..grid.nz {
for j in 0..grid.ny {
for i in 1..grid.nx {
let dp = grid.pressure(i, j, k) - grid.pressure(i - 1, j, k);
let ix = grid.idx_u(i, j, k);
grid.u[ix] = grid.u[ix] - coeff * dp;
}
}
}
for k in 0..grid.nz {
for j in 1..grid.ny {
for i in 0..grid.nx {
let dp = grid.pressure(i, j, k) - grid.pressure(i, j - 1, k);
let ix = grid.idx_v(i, j, k);
grid.v[ix] = grid.v[ix] - coeff * dp;
}
}
}
for k in 1..grid.nz {
for j in 0..grid.ny {
for i in 0..grid.nx {
let dp = grid.pressure(i, j, k) - grid.pressure(i, j, k - 1);
let ix = grid.idx_w(i, j, k);
grid.w[ix] = grid.w[ix] - coeff * dp;
}
}
}
BicgstabStats {
iterations,
final_residual: residual,
converged,
}
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct BicgstabStats {
pub(crate) iterations: u32,
pub(crate) final_residual: Fix128,
pub(crate) converged: bool,
}
fn apply_poisson_a(p: &[Fix128], out: &mut [Fix128], nx: usize, ny: usize, nz: usize) {
let idx = |i: usize, j: usize, k: usize| i + nx * (j + ny * k);
let neg_six = Fix128::from_int(-6);
for k in 0..nz {
for j in 0..ny {
for i in 0..nx {
let c = p[idx(i, j, k)];
let mut acc = c * neg_six;
if i > 0 {
acc = acc + p[idx(i - 1, j, k)];
}
if i + 1 < nx {
acc = acc + p[idx(i + 1, j, k)];
}
if j > 0 {
acc = acc + p[idx(i, j - 1, k)];
}
if j + 1 < ny {
acc = acc + p[idx(i, j + 1, k)];
}
if k > 0 {
acc = acc + p[idx(i, j, k - 1)];
}
if k + 1 < nz {
acc = acc + p[idx(i, j, k + 1)];
}
out[idx(i, j, k)] = acc;
}
}
}
}
fn dot(a: &[Fix128], b: &[Fix128]) -> Fix128 {
let mut acc = Fix128::ZERO;
for i in 0..a.len() {
acc = acc + a[i] * b[i];
}
acc
}
fn linf_norm(v: &[Fix128]) -> Fix128 {
let mut best = Fix128::ZERO;
for &x in v {
let ax = x.abs();
if ax > best {
best = ax;
}
}
best
}
fn split(p_over_dx: Fix128, axis_offset: Fix128) -> (usize, Fix128) {
let shifted = p_over_dx - axis_offset;
if shifted.hi < 0 {
(0, Fix128::ZERO)
} else {
(
shifted.hi as usize,
Fix128 {
hi: 0,
lo: shifted.lo,
},
)
}
}
pub(crate) fn sample_u_trilinear(grid: &MacGrid, pos_m: Vec3Fix) -> Fix128 {
if grid.dx.is_zero() {
return Fix128::ZERO;
}
let inv_dx = Fix128::ONE / grid.dx;
let fx = pos_m.x * inv_dx;
let fy = pos_m.y * inv_dx;
let fz = pos_m.z * inv_dx;
let (i, u) = split(fx, Fix128::ZERO);
let (j, v) = split(fy, Fix128::from_ratio(1, 2));
let (k, w) = split(fz, Fix128::from_ratio(1, 2));
let i0 = i.min(grid.nx);
let i1 = (i + 1).min(grid.nx);
let j0 = j.min(grid.ny - 1);
let j1 = (j + 1).min(grid.ny - 1);
let k0 = k.min(grid.nz - 1);
let k1 = (k + 1).min(grid.nz - 1);
let om_u = Fix128::ONE - u;
let om_v = Fix128::ONE - v;
let om_w = Fix128::ONE - w;
let c00 = grid.u(i0, j0, k0) * om_u + grid.u(i1, j0, k0) * u;
let c10 = grid.u(i0, j1, k0) * om_u + grid.u(i1, j1, k0) * u;
let c01 = grid.u(i0, j0, k1) * om_u + grid.u(i1, j0, k1) * u;
let c11 = grid.u(i0, j1, k1) * om_u + grid.u(i1, j1, k1) * u;
let c0 = c00 * om_v + c10 * v;
let c1 = c01 * om_v + c11 * v;
c0 * om_w + c1 * w
}
pub(crate) fn sample_v_trilinear(grid: &MacGrid, pos_m: Vec3Fix) -> Fix128 {
if grid.dx.is_zero() {
return Fix128::ZERO;
}
let inv_dx = Fix128::ONE / grid.dx;
let fx = pos_m.x * inv_dx;
let fy = pos_m.y * inv_dx;
let fz = pos_m.z * inv_dx;
let (i, u) = split(fx, Fix128::from_ratio(1, 2));
let (j, v) = split(fy, Fix128::ZERO);
let (k, w) = split(fz, Fix128::from_ratio(1, 2));
let i0 = i.min(grid.nx - 1);
let i1 = (i + 1).min(grid.nx - 1);
let j0 = j.min(grid.ny);
let j1 = (j + 1).min(grid.ny);
let k0 = k.min(grid.nz - 1);
let k1 = (k + 1).min(grid.nz - 1);
let om_u = Fix128::ONE - u;
let om_v = Fix128::ONE - v;
let om_w = Fix128::ONE - w;
let c00 = grid.v(i0, j0, k0) * om_v + grid.v(i0, j1, k0) * v;
let c10 = grid.v(i1, j0, k0) * om_v + grid.v(i1, j1, k0) * v;
let c01 = grid.v(i0, j0, k1) * om_v + grid.v(i0, j1, k1) * v;
let c11 = grid.v(i1, j0, k1) * om_v + grid.v(i1, j1, k1) * v;
let c0 = c00 * om_u + c10 * u;
let c1 = c01 * om_u + c11 * u;
c0 * om_w + c1 * w
}
pub(crate) fn sample_w_trilinear(grid: &MacGrid, pos_m: Vec3Fix) -> Fix128 {
if grid.dx.is_zero() {
return Fix128::ZERO;
}
let inv_dx = Fix128::ONE / grid.dx;
let fx = pos_m.x * inv_dx;
let fy = pos_m.y * inv_dx;
let fz = pos_m.z * inv_dx;
let (i, u) = split(fx, Fix128::from_ratio(1, 2));
let (j, v) = split(fy, Fix128::from_ratio(1, 2));
let (k, w) = split(fz, Fix128::ZERO);
let i0 = i.min(grid.nx - 1);
let i1 = (i + 1).min(grid.nx - 1);
let j0 = j.min(grid.ny - 1);
let j1 = (j + 1).min(grid.ny - 1);
let k0 = k.min(grid.nz);
let k1 = (k + 1).min(grid.nz);
let om_u = Fix128::ONE - u;
let om_v = Fix128::ONE - v;
let om_w = Fix128::ONE - w;
let c00 = grid.w(i0, j0, k0) * om_w + grid.w(i0, j0, k1) * w;
let c10 = grid.w(i1, j0, k0) * om_w + grid.w(i1, j0, k1) * w;
let c01 = grid.w(i0, j1, k0) * om_w + grid.w(i0, j1, k1) * w;
let c11 = grid.w(i1, j1, k0) * om_w + grid.w(i1, j1, k1) * w;
let c0 = c00 * om_u + c10 * u;
let c1 = c01 * om_u + c11 * u;
c0 * om_v + c1 * v
}
pub(crate) fn sample_u_range(grid: &MacGrid, pos_m: Vec3Fix) -> (Fix128, Fix128) {
if grid.dx.is_zero() {
return (Fix128::ZERO, Fix128::ZERO);
}
let inv_dx = Fix128::ONE / grid.dx;
let (i, _) = split(pos_m.x * inv_dx, Fix128::ZERO);
let (j, _) = split(pos_m.y * inv_dx, Fix128::from_ratio(1, 2));
let (k, _) = split(pos_m.z * inv_dx, Fix128::from_ratio(1, 2));
let i0 = i.min(grid.nx);
let i1 = (i + 1).min(grid.nx);
let j0 = j.min(grid.ny - 1);
let j1 = (j + 1).min(grid.ny - 1);
let k0 = k.min(grid.nz - 1);
let k1 = (k + 1).min(grid.nz - 1);
corner_range(&[
grid.u(i0, j0, k0),
grid.u(i1, j0, k0),
grid.u(i0, j1, k0),
grid.u(i1, j1, k0),
grid.u(i0, j0, k1),
grid.u(i1, j0, k1),
grid.u(i0, j1, k1),
grid.u(i1, j1, k1),
])
}
pub(crate) fn sample_v_range(grid: &MacGrid, pos_m: Vec3Fix) -> (Fix128, Fix128) {
if grid.dx.is_zero() {
return (Fix128::ZERO, Fix128::ZERO);
}
let inv_dx = Fix128::ONE / grid.dx;
let (i, _) = split(pos_m.x * inv_dx, Fix128::from_ratio(1, 2));
let (j, _) = split(pos_m.y * inv_dx, Fix128::ZERO);
let (k, _) = split(pos_m.z * inv_dx, Fix128::from_ratio(1, 2));
let i0 = i.min(grid.nx - 1);
let i1 = (i + 1).min(grid.nx - 1);
let j0 = j.min(grid.ny);
let j1 = (j + 1).min(grid.ny);
let k0 = k.min(grid.nz - 1);
let k1 = (k + 1).min(grid.nz - 1);
corner_range(&[
grid.v(i0, j0, k0),
grid.v(i1, j0, k0),
grid.v(i0, j1, k0),
grid.v(i1, j1, k0),
grid.v(i0, j0, k1),
grid.v(i1, j0, k1),
grid.v(i0, j1, k1),
grid.v(i1, j1, k1),
])
}
pub(crate) fn sample_w_range(grid: &MacGrid, pos_m: Vec3Fix) -> (Fix128, Fix128) {
if grid.dx.is_zero() {
return (Fix128::ZERO, Fix128::ZERO);
}
let inv_dx = Fix128::ONE / grid.dx;
let (i, _) = split(pos_m.x * inv_dx, Fix128::from_ratio(1, 2));
let (j, _) = split(pos_m.y * inv_dx, Fix128::from_ratio(1, 2));
let (k, _) = split(pos_m.z * inv_dx, Fix128::ZERO);
let i0 = i.min(grid.nx - 1);
let i1 = (i + 1).min(grid.nx - 1);
let j0 = j.min(grid.ny - 1);
let j1 = (j + 1).min(grid.ny - 1);
let k0 = k.min(grid.nz);
let k1 = (k + 1).min(grid.nz);
corner_range(&[
grid.w(i0, j0, k0),
grid.w(i1, j0, k0),
grid.w(i0, j1, k0),
grid.w(i1, j1, k0),
grid.w(i0, j0, k1),
grid.w(i1, j0, k1),
grid.w(i0, j1, k1),
grid.w(i1, j1, k1),
])
}
fn corner_range(corners: &[Fix128; 8]) -> (Fix128, Fix128) {
let mut lo = corners[0];
let mut hi = corners[0];
for &c in &corners[1..] {
if c < lo {
lo = c;
}
if c > hi {
hi = c;
}
}
(lo, hi)
}
#[must_use]
pub fn g2p_velocity(grid: &MacGrid, pos_m: Vec3Fix) -> Vec3Fix {
if grid.dx.is_zero() {
return Vec3Fix::default();
}
Vec3Fix::new(
sample_u_trilinear(grid, pos_m),
sample_v_trilinear(grid, pos_m),
sample_w_trilinear(grid, pos_m),
)
}
fn deposit_u_trilinear(grid: &mut MacGrid, pos_m: Vec3Fix, vx: Fix128) {
if grid.dx.is_zero() {
return;
}
let inv_dx = Fix128::ONE / grid.dx;
let (i, u) = split(pos_m.x * inv_dx, Fix128::ZERO);
let (j, v) = split(pos_m.y * inv_dx, Fix128::from_ratio(1, 2));
let (k, w) = split(pos_m.z * inv_dx, Fix128::from_ratio(1, 2));
let om_u = Fix128::ONE - u;
let om_v = Fix128::ONE - v;
let om_w = Fix128::ONE - w;
let corners = [
(i, j, k, om_u * om_v * om_w),
(i + 1, j, k, u * om_v * om_w),
(i, j + 1, k, om_u * v * om_w),
(i + 1, j + 1, k, u * v * om_w),
(i, j, k + 1, om_u * om_v * w),
(i + 1, j, k + 1, u * om_v * w),
(i, j + 1, k + 1, om_u * v * w),
(i + 1, j + 1, k + 1, u * v * w),
];
for (ci, cj, ck, weight) in corners {
if ci <= grid.nx && cj < grid.ny && ck < grid.nz {
let ix = grid.idx_u(ci, cj, ck);
grid.u[ix] = grid.u[ix] + weight * vx;
}
}
}
fn deposit_v_trilinear(grid: &mut MacGrid, pos_m: Vec3Fix, vy: Fix128) {
if grid.dx.is_zero() {
return;
}
let inv_dx = Fix128::ONE / grid.dx;
let (i, u) = split(pos_m.x * inv_dx, Fix128::from_ratio(1, 2));
let (j, v) = split(pos_m.y * inv_dx, Fix128::ZERO);
let (k, w) = split(pos_m.z * inv_dx, Fix128::from_ratio(1, 2));
let om_u = Fix128::ONE - u;
let om_v = Fix128::ONE - v;
let om_w = Fix128::ONE - w;
let corners = [
(i, j, k, om_u * om_v * om_w),
(i + 1, j, k, u * om_v * om_w),
(i, j + 1, k, om_u * v * om_w),
(i + 1, j + 1, k, u * v * om_w),
(i, j, k + 1, om_u * om_v * w),
(i + 1, j, k + 1, u * om_v * w),
(i, j + 1, k + 1, om_u * v * w),
(i + 1, j + 1, k + 1, u * v * w),
];
for (ci, cj, ck, weight) in corners {
if ci < grid.nx && cj <= grid.ny && ck < grid.nz {
let ix = grid.idx_v(ci, cj, ck);
grid.v[ix] = grid.v[ix] + weight * vy;
}
}
}
fn deposit_w_trilinear(grid: &mut MacGrid, pos_m: Vec3Fix, vz: Fix128) {
if grid.dx.is_zero() {
return;
}
let inv_dx = Fix128::ONE / grid.dx;
let (i, u) = split(pos_m.x * inv_dx, Fix128::from_ratio(1, 2));
let (j, v) = split(pos_m.y * inv_dx, Fix128::from_ratio(1, 2));
let (k, w) = split(pos_m.z * inv_dx, Fix128::ZERO);
let om_u = Fix128::ONE - u;
let om_v = Fix128::ONE - v;
let om_w = Fix128::ONE - w;
let corners = [
(i, j, k, om_u * om_v * om_w),
(i + 1, j, k, u * om_v * om_w),
(i, j + 1, k, om_u * v * om_w),
(i + 1, j + 1, k, u * v * om_w),
(i, j, k + 1, om_u * om_v * w),
(i + 1, j, k + 1, u * om_v * w),
(i, j + 1, k + 1, om_u * v * w),
(i + 1, j + 1, k + 1, u * v * w),
];
for (ci, cj, ck, weight) in corners {
if ci < grid.nx && cj < grid.ny && ck <= grid.nz {
let ix = grid.idx_w(ci, cj, ck);
grid.w[ix] = grid.w[ix] + weight * vz;
}
}
}
pub(crate) fn p2g_trilinear(grid: &mut MacGrid, pos_m: Vec3Fix, vel_m_per_s: Vec3Fix) {
deposit_u_trilinear(grid, pos_m, vel_m_per_s.x);
deposit_v_trilinear(grid, pos_m, vel_m_per_s.y);
deposit_w_trilinear(grid, pos_m, vel_m_per_s.z);
}
pub(crate) fn p2g_nearest(grid: &mut MacGrid, pos_m: Vec3Fix, vel_m_per_s: Vec3Fix) {
if grid.dx.is_zero() {
return;
}
let inv_dx = Fix128::ONE / grid.dx;
let ix = (pos_m.x * inv_dx).hi.max(0) as usize;
let iy = (pos_m.y * inv_dx).hi.max(0) as usize;
let iz = (pos_m.z * inv_dx).hi.max(0) as usize;
if ix >= grid.nx || iy >= grid.ny || iz >= grid.nz {
return;
}
let u_lo = grid.idx_u(ix, iy, iz);
let u_hi = grid.idx_u(ix + 1, iy, iz);
let half = Fix128::from_ratio(1, 2);
grid.u[u_lo] = grid.u[u_lo] + vel_m_per_s.x * half;
grid.u[u_hi] = grid.u[u_hi] + vel_m_per_s.x * half;
let v_lo = grid.idx_v(ix, iy, iz);
let v_hi = grid.idx_v(ix, iy + 1, iz);
grid.v[v_lo] = grid.v[v_lo] + vel_m_per_s.y * half;
grid.v[v_hi] = grid.v[v_hi] + vel_m_per_s.y * half;
let w_lo = grid.idx_w(ix, iy, iz);
let w_hi = grid.idx_w(ix, iy, iz + 1);
grid.w[w_lo] = grid.w[w_lo] + vel_m_per_s.z * half;
grid.w[w_hi] = grid.w[w_hi] + vel_m_per_s.z * half;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn mac_grid_dimensions() {
let g = MacGrid::new(4, 3, 2, Fix128::ONE);
assert_eq!(g.u.len(), 5 * 3 * 2);
assert_eq!(g.v.len(), 4 * 4 * 2);
assert_eq!(g.w.len(), 4 * 3 * 3);
assert_eq!(g.pressure.len(), 24);
}
#[test]
fn mac_grid_zero_initialised() {
let g = MacGrid::new(3, 3, 3, Fix128::ONE);
assert_eq!(g.u(0, 0, 0), Fix128::ZERO);
assert_eq!(g.v(1, 1, 1), Fix128::ZERO);
assert_eq!(g.pressure(0, 0, 0), Fix128::ZERO);
}
#[test]
fn mac_grid_out_of_range_returns_zero() {
let g = MacGrid::new(3, 3, 3, Fix128::ONE);
assert_eq!(g.u(10, 0, 0), Fix128::ZERO);
assert_eq!(g.pressure(5, 0, 0), Fix128::ZERO);
}
#[test]
fn cell_velocity_averages_faces() {
let mut g = MacGrid::new(2, 1, 1, Fix128::ONE);
let i0 = g.idx_u(0, 0, 0);
let i1 = g.idx_u(1, 0, 0);
g.u[i0] = Fix128::from_int(2);
g.u[i1] = Fix128::from_int(4);
let (uc, _, _) = g.cell_velocity(0, 0, 0);
assert_eq!(uc, Fix128::from_int(3));
}
#[test]
fn divergence_zero_for_still_grid() {
let g = MacGrid::new(3, 3, 3, Fix128::ONE);
assert_eq!(g.divergence(1, 1, 1), Fix128::ZERO);
}
#[test]
fn divergence_positive_for_expanding_flow() {
let mut g = MacGrid::new(3, 3, 3, Fix128::ONE);
let ix = g.idx_u(2, 1, 1);
g.u[ix] = Fix128::ONE;
let d = g.divergence(1, 1, 1);
assert!(d > Fix128::ZERO);
}
#[test]
fn project_zero_flow_stays_zero() {
let mut g = MacGrid::new(3, 3, 3, Fix128::ONE);
project_pressure(
&mut g,
Fix128::from_ratio(1, 60),
Fix128::from_int(1000),
10,
);
assert_eq!(g.divergence(1, 1, 1), Fix128::ZERO);
}
#[test]
fn project_reduces_divergence() {
let mut g = MacGrid::new(4, 4, 4, Fix128::ONE);
for k in 0..4 {
for j in 0..4 {
for i in 0..=4 {
let ix = g.idx_u(i, j, k);
g.u[ix] = Fix128::from_int(i as i64);
}
}
}
let div_before = g.divergence(2, 2, 2);
project_pressure(
&mut g,
Fix128::from_ratio(1, 60),
Fix128::from_int(1000),
50,
);
let div_after = g.divergence(2, 2, 2);
assert!(div_after.abs() < div_before.abs());
}
#[test]
fn p2g_scatters_x_velocity() {
let mut g = MacGrid::new(4, 4, 4, Fix128::ONE);
let pos = Vec3Fix::new(
Fix128::from_ratio(15, 10),
Fix128::from_ratio(15, 10),
Fix128::from_ratio(15, 10),
);
let vel = Vec3Fix::new(Fix128::from_int(4), Fix128::ZERO, Fix128::ZERO);
p2g_nearest(&mut g, pos, vel);
assert_eq!(g.u(1, 1, 1), Fix128::from_int(2));
assert_eq!(g.u(2, 1, 1), Fix128::from_int(2));
}
#[test]
fn g2p_reads_cell_average_velocity() {
let mut g = MacGrid::new(4, 4, 4, Fix128::ONE);
let i0 = g.idx_u(1, 1, 1);
let i1 = g.idx_u(2, 1, 1);
g.u[i0] = Fix128::from_int(2);
g.u[i1] = Fix128::from_int(4);
let vel = g2p_velocity(
&g,
Vec3Fix::new(
Fix128::from_ratio(15, 10),
Fix128::from_ratio(15, 10),
Fix128::from_ratio(15, 10),
),
);
assert_eq!(vel.x, Fix128::from_int(3));
}
#[test]
fn p2g_out_of_range_ignored() {
let mut g = MacGrid::new(2, 2, 2, Fix128::ONE);
let pos = Vec3Fix::new(
Fix128::from_int(100),
Fix128::from_int(100),
Fix128::from_int(100),
);
let vel = Vec3Fix::new(Fix128::ONE, Fix128::ONE, Fix128::ONE);
p2g_nearest(&mut g, pos, vel);
assert_eq!(g.u(0, 0, 0), Fix128::ZERO);
}
#[test]
fn p2g_trilinear_deposits_to_8_corners() {
let mut g = MacGrid::new(4, 4, 4, Fix128::ONE);
let pos = Vec3Fix::new(
Fix128::from_ratio(15, 10),
Fix128::from_ratio(15, 10),
Fix128::from_ratio(15, 10),
);
let vel = Vec3Fix::new(Fix128::from_int(4), Fix128::ZERO, Fix128::ZERO);
p2g_trilinear(&mut g, pos, vel);
assert_eq!(g.u(1, 1, 1), Fix128::from_int(2));
assert_eq!(g.u(2, 1, 1), Fix128::from_int(2));
}
#[test]
fn g2p_trilinear_between_faces_interpolates() {
let mut g = MacGrid::new(4, 4, 4, Fix128::ONE);
let i0 = g.idx_u(1, 1, 1);
let i1 = g.idx_u(2, 1, 1);
g.u[i0] = Fix128::from_int(10);
g.u[i1] = Fix128::from_int(20);
let pos = Vec3Fix::new(
Fix128::from_ratio(15, 10),
Fix128::from_ratio(15, 10),
Fix128::from_ratio(15, 10),
);
let v = g2p_velocity(&g, pos);
assert_eq!(v.x, Fix128::from_int(15));
}
#[test]
fn split_negative_position_clamps_to_zero() {
let (base, _) = split(Fix128::from_int(-3), Fix128::ZERO);
assert_eq!(base, 0);
}
fn seed_divergent_flow(nx: usize) -> MacGrid {
let mut g = MacGrid::new(nx, nx, nx, Fix128::ONE);
for i in 0..=nx {
for j in 0..nx {
for k in 0..nx {
let ix = g.idx_u(i, j, k);
if ix < g.u.len() {
g.u[ix] = Fix128::from_int(i as i64);
}
}
}
}
g
}
#[test]
fn bicgstab_reduces_divergence_below_jacobi_iterations() {
let mut g = seed_divergent_flow(4);
let dt = Fix128::from_ratio(1, 100);
let rho = Fix128::from_int(1000);
let div_before = g.divergence(2, 2, 2).abs();
let stats = project_pressure_bicgstab(&mut g, dt, rho, 15, Fix128::from_ratio(1, 10_000));
let div_after = g.divergence(2, 2, 2).abs();
assert!(div_after < div_before);
assert!(stats.iterations <= 15);
}
#[test]
fn bicgstab_matches_jacobi_within_tolerance() {
let mut g_bicg = seed_divergent_flow(4);
let mut g_jac = g_bicg.clone();
let dt = Fix128::from_ratio(1, 100);
let rho = Fix128::from_int(1000);
let _ = project_pressure_bicgstab(&mut g_bicg, dt, rho, 30, Fix128::from_ratio(1, 100_000));
project_pressure_jacobi(&mut g_jac, dt, rho, 200);
let div_bicg = g_bicg.divergence(2, 2, 2).abs();
let div_jac = g_jac.divergence(2, 2, 2).abs();
assert!(div_bicg < Fix128::from_ratio(1, 10));
assert!(div_jac < Fix128::from_ratio(1, 10));
}
#[test]
fn bicgstab_stats_reports_convergence_status() {
let mut g = seed_divergent_flow(4);
let dt = Fix128::from_ratio(1, 100);
let rho = Fix128::from_int(1000);
let stats =
project_pressure_bicgstab(&mut g, dt, rho, 50, Fix128::from_ratio(1, 1_000_000));
assert!(stats.iterations >= 1);
assert!(stats.iterations <= 50);
}
#[test]
fn bicgstab_no_op_on_zero_dt() {
let mut g = seed_divergent_flow(3);
let stats = project_pressure_bicgstab(
&mut g,
Fix128::ZERO,
Fix128::from_int(1000),
10,
Fix128::from_ratio(1, 10_000),
);
assert_eq!(stats.iterations, 0);
assert!(stats.converged);
}
#[test]
fn apply_poisson_a_is_symmetric_stencil() {
let nx = 3;
let ny = 3;
let nz = 3;
let n = nx * ny * nz;
let mut p = vec![Fix128::ZERO; n];
let ctr = 1 + nx * (1 + ny);
p[ctr] = Fix128::ONE;
let mut out = vec![Fix128::ZERO; n];
apply_poisson_a(&p, &mut out, nx, ny, nz);
assert_eq!(out[ctr], Fix128::from_int(-6));
let neighbour = nx * (1 + ny);
assert_eq!(out[neighbour], Fix128::ONE);
}
}