const DISCHARGE_COEFFICIENT: f64 = 0.6;
const DRY_TOLERANCE: f64 = 1e-10;
const CFL_SAFETY: f64 = 0.9;
pub struct ColumnFluid {
pub heights: Vec<f64>,
pub width: usize,
pub dx: f64,
pub density: f64,
pub g: f64,
}
impl ColumnFluid {
#[must_use]
pub fn new(width: usize, dx: f64, density: f64, g: f64) -> Self {
assert!(dx > 0.0, "column spacing dx must be positive");
assert!(density > 0.0, "fluid density must be positive");
assert!(g > 0.0, "gravitational acceleration must be positive");
Self {
heights: vec![0.0; width],
width,
dx,
density,
g,
}
}
pub fn set_height(&mut self, col: usize, h: f64) {
assert!(col < self.width, "column index out of bounds");
assert!(h >= 0.0, "height must be non-negative");
self.heights[col] = h;
}
pub fn step(&mut self, dt: f64) {
if self.width < 2 {
return;
}
let n_interfaces = self.width - 1;
let mut flow = vec![0.0; n_interfaces];
for k in 0..n_interfaces {
let dh = self.heights[k] - self.heights[k + 1];
let abs_dh = dh.abs();
let q = DISCHARGE_COEFFICIENT * self.dx * dh.signum()
* (2.0 * self.g * abs_dh).sqrt();
flow[k] = q;
}
let mut new_heights = self.heights.clone();
for k in 0..n_interfaces {
let dh_transfer = flow[k] * dt / self.dx;
new_heights[k] -= dh_transfer;
new_heights[k + 1] += dh_transfer;
}
for h in &mut new_heights {
if *h < 0.0 {
*h = 0.0;
}
}
self.heights = new_heights;
}
#[must_use]
pub fn total_volume(&self) -> f64 {
self.heights.iter().sum::<f64>() * self.dx
}
}
pub struct ShallowWater1D {
pub h: Vec<f64>,
pub hu: Vec<f64>,
pub nx: usize,
pub dx: f64,
pub g: f64,
}
impl ShallowWater1D {
#[must_use]
pub fn new(nx: usize, dx: f64, g: f64) -> Self {
assert!(dx > 0.0, "grid spacing dx must be positive");
assert!(g > 0.0, "gravitational acceleration must be positive");
Self {
h: vec![0.0; nx],
hu: vec![0.0; nx],
nx,
dx,
g,
}
}
#[must_use]
pub fn velocity(&self, i: usize) -> f64 {
if self.h[i] < DRY_TOLERANCE {
0.0
} else {
self.hu[i] / self.h[i]
}
}
fn momentum_flux(&self, i: usize) -> f64 {
if self.h[i] < DRY_TOLERANCE {
0.0
} else {
self.hu[i] * self.hu[i] / self.h[i] + 0.5 * self.g * self.h[i] * self.h[i]
}
}
pub fn step_hll(&mut self, dt: f64) {
let b = vec![0.0; self.nx];
crate::cfd::shallow_water::swe_1d_step_hll(
&mut self.h,
&mut self.hu,
&b,
self.dx,
self.g,
dt,
DRY_TOLERANCE,
true,
);
}
pub fn step_lax_friedrichs(&mut self, dt: f64) {
if self.nx < 3 {
return;
}
let ratio = dt / (2.0 * self.dx);
let mut h_new = vec![0.0; self.nx];
let mut hu_new = vec![0.0; self.nx];
for i in 1..self.nx - 1 {
h_new[i] = 0.5 * (self.h[i - 1] + self.h[i + 1])
- ratio * (self.hu[i + 1] - self.hu[i - 1]);
let f_plus = self.momentum_flux(i + 1);
let f_minus = self.momentum_flux(i - 1);
hu_new[i] = 0.5 * (self.hu[i - 1] + self.hu[i + 1])
- ratio * (f_plus - f_minus);
if h_new[i] < 0.0 {
h_new[i] = 0.0;
hu_new[i] = 0.0;
}
}
h_new[0] = h_new[1];
hu_new[0] = -hu_new[1];
h_new[self.nx - 1] = h_new[self.nx - 2];
hu_new[self.nx - 1] = -hu_new[self.nx - 2];
self.h = h_new;
self.hu = hu_new;
}
#[must_use]
pub fn max_wave_speed(&self) -> f64 {
let mut max_speed: f64 = 0.0;
for i in 0..self.nx {
let u = self.velocity(i);
let c = (self.g * self.h[i].max(0.0)).sqrt();
max_speed = max_speed.max(u.abs() + c);
}
max_speed
}
#[must_use]
pub fn stable_dt(&self) -> f64 {
let s = self.max_wave_speed();
if s < DRY_TOLERANCE {
return self.dx; }
CFL_SAFETY * self.dx / s
}
#[must_use]
pub fn total_volume(&self) -> f64 {
self.h.iter().sum::<f64>() * self.dx
}
#[must_use]
pub fn total_energy(&self) -> f64 {
let mut e = 0.0;
for i in 0..self.nx {
let u = self.velocity(i);
let h = self.h[i];
e += (0.5 * h * u * u + 0.5 * self.g * h * h) * self.dx;
}
e
}
}
pub struct EulerFluid2D {
pub vx: Vec<f64>,
pub vy: Vec<f64>,
pub pressure: Vec<f64>,
pub nx: usize,
pub ny: usize,
pub dx: f64,
pub dy: f64,
pub density: f64,
}
#[inline]
fn idx(i: usize, j: usize, ny: usize) -> usize {
i * ny + j
}
impl EulerFluid2D {
#[must_use]
pub fn new(nx: usize, ny: usize, dx: f64, dy: f64, density: f64) -> Self {
assert!(dx > 0.0, "grid spacing dx must be positive");
assert!(dy > 0.0, "grid spacing dy must be positive");
assert!(density > 0.0, "fluid density must be positive");
let n = nx * ny;
Self {
vx: vec![0.0; n],
vy: vec![0.0; n],
pressure: vec![0.0; n],
nx,
ny,
dx,
dy,
density,
}
}
pub fn set_velocity(&mut self, i: usize, j: usize, vx: f64, vy: f64) {
let k = idx(i, j, self.ny);
self.vx[k] = vx;
self.vy[k] = vy;
}
pub fn step(&mut self, dt: f64, gravity_x: f64, gravity_y: f64) {
let n = self.nx * self.ny;
let mut vx_star = vec![0.0; n];
let mut vy_star = vec![0.0; n];
for i in 0..self.nx {
for j in 0..self.ny {
let k = idx(i, j, self.ny);
let u = self.vx[k];
let v = self.vy[k];
let advect_vx = self.upwind_advect(i, j, u, v, &self.vx);
let advect_vy = self.upwind_advect(i, j, u, v, &self.vy);
vx_star[k] = u - dt * advect_vx + dt * gravity_x;
vy_star[k] = v - dt * advect_vy + dt * gravity_y;
}
}
self.apply_boundary_velocity(&mut vx_star, &mut vy_star);
let mut rhs = vec![0.0; n];
for i in 1..self.nx - 1 {
for j in 1..self.ny - 1 {
let dvx_dx = (vx_star[idx(i + 1, j, self.ny)]
- vx_star[idx(i - 1, j, self.ny)])
/ (2.0 * self.dx);
let dvy_dy = (vy_star[idx(i, j + 1, self.ny)]
- vy_star[idx(i, j - 1, self.ny)])
/ (2.0 * self.dy);
rhs[idx(i, j, self.ny)] = (self.density / dt) * (dvx_dx + dvy_dy);
}
}
self.pressure = self.solve_pressure_poisson(&rhs);
let dt_over_rho = dt / self.density;
for i in 1..self.nx - 1 {
for j in 1..self.ny - 1 {
let k = idx(i, j, self.ny);
let dp_dx = (self.pressure[idx(i + 1, j, self.ny)]
- self.pressure[idx(i - 1, j, self.ny)])
/ (2.0 * self.dx);
let dp_dy = (self.pressure[idx(i, j + 1, self.ny)]
- self.pressure[idx(i, j - 1, self.ny)])
/ (2.0 * self.dy);
self.vx[k] = vx_star[k] - dt_over_rho * dp_dx;
self.vy[k] = vy_star[k] - dt_over_rho * dp_dy;
}
}
let mut vx_tmp = std::mem::take(&mut self.vx);
let mut vy_tmp = std::mem::take(&mut self.vy);
self.apply_boundary_velocity(&mut vx_tmp, &mut vy_tmp);
self.vx = vx_tmp;
self.vy = vy_tmp;
}
fn upwind_advect(&self, i: usize, j: usize, u: f64, v: f64, field: &[f64]) -> f64 {
let phi = field[idx(i, j, self.ny)];
let dphi_dx = if u >= 0.0 {
if i > 0 {
(phi - field[idx(i - 1, j, self.ny)]) / self.dx
} else {
0.0
}
} else if i < self.nx - 1 {
(field[idx(i + 1, j, self.ny)] - phi) / self.dx
} else {
0.0
};
let dphi_dy = if v >= 0.0 {
if j > 0 {
(phi - field[idx(i, j - 1, self.ny)]) / self.dy
} else {
0.0
}
} else if j < self.ny - 1 {
(field[idx(i, j + 1, self.ny)] - phi) / self.dy
} else {
0.0
};
u * dphi_dx + v * dphi_dy
}
fn apply_boundary_velocity(&self, vx: &mut [f64], vy: &mut [f64]) {
for j in 0..self.ny {
vx[idx(0, j, self.ny)] = 0.0;
vx[idx(self.nx - 1, j, self.ny)] = 0.0;
}
for i in 0..self.nx {
vy[idx(i, 0, self.ny)] = 0.0;
vy[idx(i, self.ny - 1, self.ny)] = 0.0;
}
}
fn solve_pressure_poisson(&self, rhs: &[f64]) -> Vec<f64> {
crate::cfd::stable_fluids::poisson_neumann_cg_rect(
rhs, self.nx, self.ny, self.dx, self.dy, 1e-10, 2000,
)
}
#[must_use]
pub fn divergence(&self) -> f64 {
let mut max_div: f64 = 0.0;
for i in 1..self.nx - 1 {
for j in 1..self.ny - 1 {
let dvx_dx = (self.vx[idx(i + 1, j, self.ny)]
- self.vx[idx(i - 1, j, self.ny)])
/ (2.0 * self.dx);
let dvy_dy = (self.vy[idx(i, j + 1, self.ny)]
- self.vy[idx(i, j - 1, self.ny)])
/ (2.0 * self.dy);
max_div = max_div.max((dvx_dx + dvy_dy).abs());
}
}
max_div
}
#[must_use]
pub fn kinetic_energy(&self) -> f64 {
let cell_area = self.dx * self.dy;
let mut ke = 0.0;
for k in 0..self.nx * self.ny {
ke += self.vx[k] * self.vx[k] + self.vy[k] * self.vy[k];
}
0.5 * self.density * ke * cell_area
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::math::constants;
fn approx_eq(a: f64, b: f64, tol: f64) -> bool {
(a - b).abs() < tol
}
fn approx_rel_eq(a: f64, b: f64, tol: f64) -> bool {
if b.abs() < 1e-15 {
a.abs() < tol
} else {
((a - b) / b).abs() < tol
}
}
#[test]
fn column_fluid_equilibrates_to_equal_height() {
let mut fluid = ColumnFluid::new(2, 1.0, 1000.0, constants::G_ACCEL);
fluid.set_height(0, 2.0);
fluid.set_height(1, 0.0);
let expected_height = 1.0;
for _ in 0..10_000 {
fluid.step(0.001);
}
let h0 = fluid.heights[0];
assert!(
approx_eq(h0, expected_height, 0.05),
"column 0: {h0} != {expected_height}",
);
let h1 = fluid.heights[1];
assert!(
approx_eq(h1, expected_height, 0.05),
"column 1: {h1} != {expected_height}",
);
}
#[test]
fn column_fluid_conserves_volume() {
let mut fluid = ColumnFluid::new(10, 0.5, 1000.0, constants::G_ACCEL);
fluid.set_height(0, 3.0);
fluid.set_height(3, 1.5);
fluid.set_height(7, 2.0);
let initial_volume = fluid.total_volume();
for _ in 0..5000 {
fluid.step(0.0005);
}
let final_volume = fluid.total_volume();
assert!(
approx_rel_eq(initial_volume, final_volume, 1e-6),
"volume not conserved: {initial_volume} -> {final_volume}",
);
}
#[test]
fn shallow_water_dam_break_wave_speed() {
let nx = 500;
let dx = 0.1;
let g = constants::G_ACCEL;
let h0 = 1.0;
let mut sw = ShallowWater1D::new(nx, dx, g);
let dam_pos = nx / 2;
for i in 0..dam_pos {
sw.h[i] = h0;
}
let expected_wave_speed = (g * h0).sqrt(); let total_time = 1.0;
let mut t = 0.0;
while t < total_time {
let dt = sw.stable_dt().min(total_time - t);
sw.step_lax_friedrichs(dt);
t += dt;
}
let threshold = 0.01 * h0;
let mut front_idx = dam_pos;
for i in (dam_pos..nx).rev() {
if sw.h[i] > threshold {
front_idx = i;
break;
}
}
let front_distance = (front_idx - dam_pos) as f64 * dx;
let measured_speed = front_distance / total_time;
assert!(
measured_speed > 0.5 * expected_wave_speed,
"wave too slow: measured {measured_speed} m/s vs expected {expected_wave_speed} m/s",
);
assert!(
measured_speed < 2.0 * expected_wave_speed,
"wave too fast: measured {measured_speed} m/s vs expected {expected_wave_speed} m/s",
);
}
#[test]
fn shallow_water_step_hll_matches_stoker_dam_break() {
use crate::cfd::shallow_water::swe_1d_exact_dam_break;
let nx = 800;
let domain = 40.0;
let dx = domain / nx as f64;
let g = 9.81;
let (h_l, h_r) = (2.0_f64, 1.0_f64);
let t_end = 1.0_f64;
let fill = |sw: &mut ShallowWater1D| {
for i in 0..nx {
sw.h[i] = if (i as f64 + 0.5) * dx < 0.5 * domain { h_l } else { h_r };
sw.hu[i] = 0.0;
}
};
let mut hll = ShallowWater1D::new(nx, dx, g);
fill(&mut hll);
let v0 = hll.total_volume();
let e0 = hll.total_energy();
let mut t = 0.0;
while t < t_end {
let dt = hll.stable_dt().min(t_end - t);
hll.step_hll(dt);
t += dt;
assert!(hll.h.iter().all(|&h| h >= 0.0), "negative depth at t = {t}");
}
assert!(
approx_rel_eq(hll.total_volume(), v0, 1e-12),
"volume drift {} -> {}",
v0,
hll.total_volume()
);
assert!(
hll.total_energy() <= e0 * (1.0 + 1e-12),
"energy grew: {e0} -> {}",
hll.total_energy()
);
let l1 = |sw: &ShallowWater1D| -> f64 {
let mut acc = 0.0;
for i in 0..nx {
let x = (i as f64 + 0.5) * dx - 0.5 * domain;
let (he, _) = swe_1d_exact_dam_break(x, t_end, h_l, h_r, g);
acc += (sw.h[i] - he).abs();
}
acc / nx as f64
};
let l1_hll = l1(&hll);
assert!(l1_hll < 0.01, "HLL L1 depth error {l1_hll}");
let (h_star, u_star) = swe_1d_exact_dam_break(4.0, t_end, h_l, h_r, g);
let i_star = ((0.5 * domain + 4.0) / dx) as usize;
assert!(
approx_rel_eq(hll.h[i_star], h_star, 0.02),
"plateau depth {} vs exact {h_star}",
hll.h[i_star]
);
assert!(
approx_rel_eq(hll.velocity(i_star), u_star, 0.03),
"plateau speed {} vs exact {u_star}",
hll.velocity(i_star)
);
let x_front = 0.5 * domain
+ (0..nx)
.rev()
.find(|&i| hll.h[i] > h_r + 0.05)
.map_or(0.0, |i| (i as f64 + 0.5) * dx - 0.5 * domain);
let s_shock = (x_front - 0.5 * domain) / t_end;
let s_exact = h_star * u_star / (h_star - h_r);
assert!(
(s_shock / s_exact - 1.0).abs() < 0.05,
"bore speed {s_shock} vs exact {s_exact}"
);
assert!(
approx_rel_eq(hll.h[nx - 5], h_r, 1e-9),
"far field disturbed: {}",
hll.h[nx - 5]
);
let mut lf = ShallowWater1D::new(nx, dx, g);
fill(&mut lf);
let mut t = 0.0;
while t < t_end {
let dt = lf.stable_dt().min(t_end - t);
lf.step_lax_friedrichs(dt);
t += dt;
}
assert!(
l1_hll < l1(&lf),
"HLL ({l1_hll}) not sharper than Lax-Friedrichs ({})",
l1(&lf)
);
}
#[test]
fn shallow_water_step_hll_keeps_a_lake_at_rest() {
let nx = 64;
let dx = 0.1;
let g = constants::G_ACCEL;
let mut sw = ShallowWater1D::new(nx, dx, g);
sw.h.iter_mut().for_each(|h| *h = 1.5);
let v0 = sw.total_volume();
for _ in 0..200 {
let dt = sw.stable_dt();
sw.step_hll(dt);
}
let max_dev = sw.h.iter().map(|&h| (h - 1.5).abs()).fold(0.0_f64, f64::max);
let max_mom = sw.hu.iter().map(|m| m.abs()).fold(0.0_f64, f64::max);
assert!(max_dev < 1e-12, "lake at rest disturbed: {max_dev}");
assert!(max_mom < 1e-12, "spurious momentum {max_mom}");
assert!(approx_rel_eq(sw.total_volume(), v0, 1e-14), "volume drift");
}
#[test]
fn shallow_water_flat_surface_stays_flat() {
let nx = 100;
let dx = 0.1;
let g = constants::G_ACCEL;
let h0 = 1.0;
let mut sw = ShallowWater1D::new(nx, dx, g);
for i in 0..nx {
sw.h[i] = h0;
}
for _ in 0..1000 {
let dt = sw.stable_dt();
sw.step_lax_friedrichs(dt);
}
let max_deviation = sw
.h
.iter()
.skip(1) .take(nx - 2)
.map(|&h| (h - h0).abs())
.fold(0.0_f64, f64::max);
assert!(
max_deviation < 1e-6,
"flat surface developed perturbation: max deviation = {max_deviation}",
);
}
#[test]
fn euler_2d_divergence_near_zero_after_step() {
let nx = 32;
let ny = 32;
let dx = 0.1;
let dy = 0.1;
let density = 1.0;
let mut fluid = EulerFluid2D::new(nx, ny, dx, dy, density);
let cx = (nx as f64 * dx) / 2.0;
let cy = (ny as f64 * dy) / 2.0;
for i in 1..nx - 1 {
for j in 1..ny - 1 {
let x = i as f64 * dx - cx;
let y = j as f64 * dy - cy;
let r = (x * x + y * y).sqrt();
let envelope = (1.0 - r / (cx.min(cy))).max(0.0);
fluid.set_velocity(i, j, -y * envelope * 0.5, x * envelope * 0.5);
}
}
fluid.step(0.001, 0.0, 0.0);
let div = fluid.divergence();
assert!(
div < 0.1,
"divergence too large after projection: {div}",
);
}
#[test]
fn shallow_water_max_wave_speed_still_water() {
let nx = 50;
let dx = 0.1;
let g = 9.81;
let h0 = 2.0;
let mut sw = ShallowWater1D::new(nx, dx, g);
for i in 0..nx {
sw.h[i] = h0;
}
let s = sw.max_wave_speed();
let expected = 4.429_446_918_070_02;
assert!(
approx_rel_eq(s, expected, 1e-10),
"max_wave_speed={s}, expected {expected}"
);
}
#[test]
fn shallow_water_max_wave_speed_dry() {
let sw = ShallowWater1D::new(10, 0.1, 9.81);
assert!(approx_eq(sw.max_wave_speed(), 0.0, 1e-15));
}
#[test]
fn shallow_water_total_energy_still_water() {
let nx = 50;
let dx = 0.1;
let g = 9.81;
let h0 = 1.0;
let mut sw = ShallowWater1D::new(nx, dx, g);
for i in 0..nx {
sw.h[i] = h0;
}
let e = sw.total_energy();
let expected = 24.525;
assert!(
approx_rel_eq(e, expected, 1e-10),
"total_energy={e}, expected {expected}"
);
}
#[test]
fn shallow_water_velocity_dry_cell() {
let sw = ShallowWater1D::new(5, 0.1, 9.81);
assert!(approx_eq(sw.velocity(0), 0.0, 1e-15));
}
#[test]
fn shallow_water_velocity_wet_cell() {
let mut sw = ShallowWater1D::new(5, 0.1, 9.81);
sw.h[2] = 2.0;
sw.hu[2] = 6.0;
assert!(approx_rel_eq(sw.velocity(2), 3.0, 1e-10));
}
#[test]
fn euler_2d_approximately_conserves_kinetic_energy() {
let nx = 32;
let ny = 32;
let dx = 0.1;
let dy = 0.1;
let density = 1.0;
let mut fluid = EulerFluid2D::new(nx, ny, dx, dy, density);
let u0 = 1.0;
for i in 1..nx - 1 {
for j in 1..ny - 1 {
fluid.set_velocity(i, j, u0, 0.0);
}
}
let ke_initial = fluid.kinetic_energy();
assert!(ke_initial > 0.0, "initial KE should be positive");
let dt = 0.001;
let n_steps = 50;
for _ in 0..n_steps {
fluid.step(dt, 0.0, 0.0);
}
let ke_final = fluid.kinetic_energy();
assert!(
ke_final <= ke_initial * 1.01,
"KE grew (instability): {ke_initial} -> {ke_final}",
);
assert!(
ke_final > ke_initial * 0.001,
"KE lost too much: {ke_initial} -> {ke_final}",
);
}
#[test]
fn column_fluid_single_column_step() {
let mut fluid = ColumnFluid::new(1, 1.0, 1000.0, constants::G_ACCEL);
fluid.set_height(0, 5.0);
fluid.step(0.001);
assert!(approx_eq(fluid.heights[0], 5.0, 1e-12));
}
#[test]
fn column_fluid_negative_height_clamped() {
let mut fluid = ColumnFluid::new(3, 0.1, 1000.0, constants::G_ACCEL);
fluid.set_height(0, 0.001);
fluid.set_height(1, 10.0);
fluid.set_height(2, 0.001);
for _ in 0..50 {
fluid.step(0.01);
}
for h in &fluid.heights {
assert!(*h >= 0.0, "Height should never be negative, got {h}");
}
}
#[test]
fn shallow_water_small_nx_noop() {
let mut sw = ShallowWater1D::new(2, 1.0, constants::G_ACCEL);
sw.h[0] = 1.0;
sw.h[1] = 1.0;
sw.step_lax_friedrichs(0.01);
assert!(sw.h[0].is_finite());
}
#[test]
fn shallow_water_negative_depth_clamped() {
let mut sw = ShallowWater1D::new(5, 0.1, constants::G_ACCEL);
sw.h[2] = 100.0;
sw.hu[2] = 500.0;
let dt = sw.stable_dt() * 0.5;
for _ in 0..3 {
sw.step_lax_friedrichs(dt);
}
for &h in &sw.h {
assert!(h >= 0.0, "Depth should never be negative, got {h}");
}
}
#[test]
fn shallow_water_dry_stable_dt() {
let sw = ShallowWater1D::new(10, 0.5, constants::G_ACCEL);
let dt = sw.stable_dt();
assert!(approx_eq(dt, sw.dx, 1e-12));
}
#[test]
fn shallow_water_total_volume() {
let mut sw = ShallowWater1D::new(5, 0.5, constants::G_ACCEL);
sw.h = vec![1.0, 2.0, 3.0, 2.0, 1.0];
let v = sw.total_volume();
assert!(approx_eq(v, 9.0 * 0.5, 1e-12));
}
#[test]
fn column_fluid_large_dt_forces_negative_clamp() {
let mut fluid = ColumnFluid::new(2, 1.0, 1000.0, constants::G_ACCEL);
fluid.set_height(0, 10.0);
fluid.set_height(1, 0.0);
fluid.step(100.0);
assert!(fluid.heights[0] >= 0.0);
assert!(fluid.heights[1] >= 0.0);
}
#[test]
fn shallow_water_large_dt_forces_negative_clamp() {
let mut sw = ShallowWater1D::new(5, 0.5, constants::G_ACCEL);
sw.h = vec![0.0, 0.0, 10.0, 0.0, 0.0];
sw.hu = vec![0.0, 0.0, 50.0, 0.0, 0.0];
sw.step_lax_friedrichs(10.0);
for &h in &sw.h {
assert!(h >= 0.0);
}
}
#[test]
fn euler2d_upwind_boundary_edges() {
let nx = 5;
let ny = 5;
let dx = 0.1;
let dy = 0.1;
let mut sim = EulerFluid2D::new(nx, ny, dx, dy, 1.0);
for i in 0..nx {
for j in 0..ny {
let k = i * ny + j;
sim.vx[k] = -1.0;
sim.vy[k] = -1.0;
}
}
for k in 0..nx * ny {
sim.pressure[k] = 1.0;
}
sim.step(0.001, 0.0, 0.0);
assert!(sim.divergence().is_finite());
}
#[test]
fn euler2d_poisson_converges() {
let nx = 10;
let ny = 10;
let dx = 0.1;
let dy = 0.1;
let mut sim = EulerFluid2D::new(nx, ny, dx, dy, 1.0);
for i in 0..nx {
for j in 0..ny {
let k = i * ny + j;
sim.vx[k] = ((i as f64) * 0.1).sin();
sim.vy[k] = ((j as f64) * 0.1).cos();
}
}
sim.step(0.0001, 0.0, 0.0);
let div = sim.divergence();
assert!(div.is_finite());
}
#[test]
fn test_approx_rel_eq_near_zero_b() {
assert!(approx_rel_eq(0.0, 0.0, 1e-6));
assert!(!approx_rel_eq(1.0, 0.0, 0.5));
}
}