#![allow(missing_docs)]
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct PyLbmConfig {
pub width: usize,
pub height: usize,
pub viscosity: f64,
pub body_force: [f64; 2],
}
impl PyLbmConfig {
pub fn new(width: usize, height: usize, viscosity: f64) -> Self {
Self {
width,
height,
viscosity: viscosity.max(1e-6),
body_force: [0.0; 2],
}
}
pub fn lid_driven_cavity() -> Self {
Self::new(64, 64, 0.01)
}
pub fn omega(&self) -> f64 {
1.0 / (3.0 * self.viscosity + 0.5)
}
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct PyLbmGrid {
width: usize,
height: usize,
omega: f64,
body_force: [f64; 2],
f: Vec<f64>,
f_tmp: Vec<f64>,
step_count: u64,
}
const D2Q9_W: [f64; 9] = [
4.0 / 9.0,
1.0 / 9.0,
1.0 / 9.0,
1.0 / 9.0,
1.0 / 9.0,
1.0 / 36.0,
1.0 / 36.0,
1.0 / 36.0,
1.0 / 36.0,
];
const D2Q9_EX: [f64; 9] = [0.0, 1.0, 0.0, -1.0, 0.0, 1.0, -1.0, -1.0, 1.0];
const D2Q9_EY: [f64; 9] = [0.0, 0.0, 1.0, 0.0, -1.0, 1.0, 1.0, -1.0, -1.0];
impl PyLbmGrid {
pub fn new(config: &PyLbmConfig) -> Self {
let n = config.width * config.height;
let mut f = vec![0.0f64; n * 9];
for i in 0..n {
for q in 0..9 {
f[i * 9 + q] = D2Q9_W[q];
}
}
Self {
width: config.width,
height: config.height,
omega: config.omega(),
body_force: config.body_force,
f: f.clone(),
f_tmp: f,
step_count: 0,
}
}
pub fn width(&self) -> usize {
self.width
}
pub fn height(&self) -> usize {
self.height
}
pub fn step_count(&self) -> u64 {
self.step_count
}
pub fn step(&mut self) {
let w = self.width;
let h = self.height;
let omega = self.omega;
let bfx = self.body_force[0];
let bfy = self.body_force[1];
for y in 0..h {
for x in 0..w {
let idx = (y * w + x) * 9;
let rho: f64 = self.f[idx..idx + 9].iter().sum();
let mut ux = 0.0f64;
let mut uy = 0.0f64;
for q in 0..9 {
ux += D2Q9_EX[q] * self.f[idx + q];
uy += D2Q9_EY[q] * self.f[idx + q];
}
let rho_inv = if rho > 1e-15 { 1.0 / rho } else { 0.0 };
ux = ux * rho_inv + bfx * 0.5 / omega;
uy = uy * rho_inv + bfy * 0.5 / omega;
let u2 = ux * ux + uy * uy;
for q in 0..9 {
let eu = D2Q9_EX[q] * ux + D2Q9_EY[q] * uy;
let feq = D2Q9_W[q] * rho * (1.0 + 3.0 * eu + 4.5 * eu * eu - 1.5 * u2);
self.f_tmp[idx + q] = self.f[idx + q] * (1.0 - omega) + feq * omega;
}
}
}
let f_src = self.f_tmp.clone();
for y in 0..h {
for x in 0..w {
let dst_idx = (y * w + x) * 9;
#[allow(clippy::manual_memcpy)]
for q in 0..9 {
let src_x =
((x as isize - D2Q9_EX[q] as isize).rem_euclid(w as isize)) as usize;
let src_y =
((y as isize - D2Q9_EY[q] as isize).rem_euclid(h as isize)) as usize;
let src_idx = (src_y * w + src_x) * 9;
self.f[dst_idx + q] = f_src[src_idx + q];
}
}
}
self.step_count += 1;
}
pub fn velocity_at(&self, x: usize, y: usize) -> [f64; 2] {
if x >= self.width || y >= self.height {
return [0.0; 2];
}
let idx = (y * self.width + x) * 9;
let rho: f64 = self.f[idx..idx + 9].iter().sum();
if rho < 1e-15 {
return [0.0; 2];
}
let mut ux = 0.0f64;
let mut uy = 0.0f64;
for q in 0..9 {
ux += D2Q9_EX[q] * self.f[idx + q];
uy += D2Q9_EY[q] * self.f[idx + q];
}
[ux / rho, uy / rho]
}
pub fn density_at(&self, x: usize, y: usize) -> f64 {
if x >= self.width || y >= self.height {
return 0.0;
}
let idx = (y * self.width + x) * 9;
self.f[idx..idx + 9].iter().sum()
}
pub fn velocity_field(&self) -> Vec<f64> {
let n = self.width * self.height;
let mut out = vec![0.0f64; n * 2];
for y in 0..self.height {
for x in 0..self.width {
let cell = y * self.width + x;
let v = self.velocity_at(x, y);
out[cell * 2] = v[0];
out[cell * 2 + 1] = v[1];
}
}
out
}
pub fn density_field(&self) -> Vec<f64> {
let n = self.width * self.height;
let mut out = vec![0.0f64; n];
for y in 0..self.height {
for x in 0..self.width {
out[y * self.width + x] = self.density_at(x, y);
}
}
out
}
}