229 lines
7.4 KiB
Rust
229 lines
7.4 KiB
Rust
//! Tests for D2Q9 Lattice Boltzmann Method implementation
|
|
|
|
use approx::assert_relative_eq;
|
|
use nalgebra::Vector2;
|
|
use rtx_cfd::solvers::lbm::common::MacroscopicVariables;
|
|
use rtx_cfd::solvers::lbm::{D2Q9Parameters, D2Q9Solver};
|
|
|
|
#[test]
|
|
fn test_d2q9_lattice_velocities() {
|
|
let solver = D2Q9Solver::new(10, 10, D2Q9Parameters::default());
|
|
let velocities = solver.lattice_velocities();
|
|
|
|
// D2Q9 should have 9 velocities
|
|
assert_eq!(velocities.len(), 9);
|
|
|
|
// Check specific velocity directions
|
|
assert_eq!(velocities[0], Vector2::new(0, 0)); // Rest
|
|
assert_eq!(velocities[1], Vector2::new(1, 0)); // East
|
|
assert_eq!(velocities[2], Vector2::new(0, 1)); // North
|
|
assert_eq!(velocities[3], Vector2::new(-1, 0)); // West
|
|
assert_eq!(velocities[4], Vector2::new(0, -1)); // South
|
|
assert_eq!(velocities[5], Vector2::new(1, 1)); // Northeast
|
|
assert_eq!(velocities[6], Vector2::new(-1, 1)); // Northwest
|
|
assert_eq!(velocities[7], Vector2::new(-1, -1)); // Southwest
|
|
assert_eq!(velocities[8], Vector2::new(1, -1)); // Southeast
|
|
}
|
|
|
|
#[test]
|
|
fn test_d2q9_weights() {
|
|
let solver = D2Q9Solver::new(10, 10, D2Q9Parameters::default());
|
|
let weights = solver.weights();
|
|
|
|
// D2Q9 should have 9 weights
|
|
assert_eq!(weights.len(), 9);
|
|
|
|
// Check weight values
|
|
assert_relative_eq!(weights[0], 4.0 / 9.0, epsilon = 1e-12); // Rest particle
|
|
assert_relative_eq!(weights[1], 1.0 / 9.0, epsilon = 1e-12); // Cardinal directions
|
|
assert_relative_eq!(weights[2], 1.0 / 9.0, epsilon = 1e-12);
|
|
assert_relative_eq!(weights[3], 1.0 / 9.0, epsilon = 1e-12);
|
|
assert_relative_eq!(weights[4], 1.0 / 9.0, epsilon = 1e-12);
|
|
assert_relative_eq!(weights[5], 1.0 / 36.0, epsilon = 1e-12); // Diagonal directions
|
|
assert_relative_eq!(weights[6], 1.0 / 36.0, epsilon = 1e-12);
|
|
assert_relative_eq!(weights[7], 1.0 / 36.0, epsilon = 1e-12);
|
|
assert_relative_eq!(weights[8], 1.0 / 36.0, epsilon = 1e-12);
|
|
|
|
// Weights should sum to 1
|
|
let sum: f64 = weights.iter().sum();
|
|
assert_relative_eq!(sum, 1.0, epsilon = 1e-12);
|
|
}
|
|
|
|
#[test]
|
|
fn test_d2q9_equilibrium_distribution() {
|
|
let solver = D2Q9Solver::new(10, 10, D2Q9Parameters::default());
|
|
let density = 1.0;
|
|
let velocity = Vector2::new(0.1, 0.05);
|
|
|
|
let f_eq = solver.equilibrium_distribution(density, &velocity);
|
|
|
|
// Should have 9 components
|
|
assert_eq!(f_eq.len(), 9);
|
|
|
|
// All components should be positive
|
|
for &val in &f_eq {
|
|
assert!(
|
|
val > 0.0,
|
|
"Equilibrium distribution component should be positive"
|
|
);
|
|
}
|
|
|
|
// Sum should equal density
|
|
let sum: f64 = f_eq.iter().sum();
|
|
assert_relative_eq!(sum, density, epsilon = 1e-12);
|
|
}
|
|
|
|
#[test]
|
|
fn test_d2q9_equilibrium_at_rest() {
|
|
let solver = D2Q9Solver::new(10, 10, D2Q9Parameters::default());
|
|
let density = 1.0;
|
|
let velocity = Vector2::zeros();
|
|
|
|
let f_eq = solver.equilibrium_distribution(density, &velocity);
|
|
let weights = solver.weights();
|
|
|
|
// For zero velocity, equilibrium should be density * weight
|
|
for i in 0..9 {
|
|
assert_relative_eq!(f_eq[i], density * weights[i], epsilon = 1e-12);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_d2q9_macroscopic_variables() {
|
|
let mut solver = D2Q9Solver::new(10, 10, D2Q9Parameters::default());
|
|
|
|
// Initialize with known state
|
|
let density = 1.2;
|
|
let velocity = Vector2::new(0.1, -0.05);
|
|
|
|
// Set distribution functions to equilibrium
|
|
let f_eq = solver.equilibrium_distribution(density, &velocity);
|
|
solver.set_distribution_at(5, 5, &f_eq);
|
|
|
|
// Extract macroscopic variables
|
|
let macro_vars = solver.macroscopic_variables_at(5, 5);
|
|
|
|
assert_relative_eq!(macro_vars.density, density, epsilon = 1e-12);
|
|
assert_relative_eq!(macro_vars.velocity.x, velocity.x, epsilon = 1e-12);
|
|
assert_relative_eq!(macro_vars.velocity.y, velocity.y, epsilon = 1e-12);
|
|
}
|
|
|
|
#[test]
|
|
fn test_d2q9_bgk_collision() {
|
|
let mut solver = D2Q9Solver::new(10, 10, D2Q9Parameters::new(0.6)); // tau = 0.6
|
|
|
|
// Initialize with non-equilibrium state
|
|
let density = 1.0;
|
|
let velocity = Vector2::new(0.1, 0.0);
|
|
let f_eq = solver.equilibrium_distribution(density, &velocity);
|
|
|
|
// Perturb from equilibrium
|
|
let mut f = f_eq.clone();
|
|
f[1] += 0.1; // Add perturbation
|
|
|
|
solver.set_distribution_at(5, 5, &f);
|
|
|
|
// Apply collision step
|
|
solver.collision_step();
|
|
|
|
// Check that distribution moves toward equilibrium
|
|
let f_after = solver.distribution_at(5, 5);
|
|
|
|
// The perturbed component should be closer to equilibrium
|
|
assert!(f_after[1] < f[1]);
|
|
assert!(f_after[1] > f_eq[1]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_d2q9_streaming_step() {
|
|
let mut solver = D2Q9Solver::new(5, 5, D2Q9Parameters::default());
|
|
|
|
// Initialize central cell with specific distribution
|
|
let mut f = vec![0.0; 9];
|
|
f[1] = 1.0; // Only eastward component
|
|
solver.set_distribution_at(2, 2, &f);
|
|
|
|
// Apply streaming step
|
|
solver.streaming_step();
|
|
|
|
// Check that the distribution has moved eastward
|
|
let f_east = solver.distribution_at(3, 2);
|
|
assert_relative_eq!(f_east[1], 1.0, epsilon = 1e-12);
|
|
|
|
// Original cell should have zero eastward component
|
|
let f_original = solver.distribution_at(2, 2);
|
|
assert_relative_eq!(f_original[1], 0.0, epsilon = 1e-12);
|
|
}
|
|
|
|
#[test]
|
|
fn test_d2q9_poiseuille_flow_analytical() {
|
|
// Test against analytical solution for Poiseuille flow
|
|
let nx = 32;
|
|
let ny = 32;
|
|
let mut solver = D2Q9Solver::new(nx, ny, D2Q9Parameters::new(0.8));
|
|
|
|
// Initialize Poiseuille flow
|
|
solver.initialize_poiseuille_flow(0.01); // Small driving force
|
|
|
|
// Run for sufficient time to reach steady state with equilibrium BCs
|
|
for _ in 0..1000 {
|
|
solver.step_with_boundaries(|s| s.apply_no_slip_boundaries());
|
|
}
|
|
|
|
// Check parabolic velocity profile at center
|
|
let x_center = nx / 2;
|
|
let mut max_velocity = 0.0f64;
|
|
|
|
for y in 1..(ny - 1) {
|
|
let vars = solver.macroscopic_variables_at(x_center, y);
|
|
max_velocity = max_velocity.max(vars.velocity.x);
|
|
}
|
|
|
|
// For Poiseuille flow, velocity should be maximum at center
|
|
let center_vars = solver.macroscopic_variables_at(x_center, ny / 2);
|
|
assert_relative_eq!(center_vars.velocity.x, max_velocity, epsilon = 1e-2);
|
|
|
|
// Velocity should be zero at walls
|
|
let wall_bottom = solver.macroscopic_variables_at(x_center, 0);
|
|
let wall_top = solver.macroscopic_variables_at(x_center, ny - 1);
|
|
assert!(wall_bottom.velocity.x.abs() < 1e-6);
|
|
assert!(wall_top.velocity.x.abs() < 1e-6);
|
|
}
|
|
|
|
#[test]
|
|
fn test_d2q9_mass_conservation() {
|
|
let nx = 16;
|
|
let ny = 16;
|
|
let mut solver = D2Q9Solver::new(nx, ny, D2Q9Parameters::default());
|
|
|
|
// Initialize with uniform density
|
|
solver.initialize_uniform(1.0, Vector2::new(0.05, 0.02));
|
|
|
|
let initial_mass = solver.total_mass();
|
|
|
|
// Run simulation for several steps with periodic boundaries
|
|
for _ in 0..100 {
|
|
solver.step_periodic();
|
|
}
|
|
|
|
let final_mass = solver.total_mass();
|
|
|
|
// Mass should be conserved
|
|
assert_relative_eq!(final_mass, initial_mass, epsilon = 1e-12);
|
|
}
|
|
|
|
#[test]
|
|
fn test_d2q9_parameters_validation() {
|
|
// Valid parameters
|
|
let valid_params = D2Q9Parameters::new(0.6);
|
|
assert!(valid_params.validate().is_ok());
|
|
|
|
// Invalid relaxation time (too small)
|
|
let invalid_params = D2Q9Parameters::new(0.4);
|
|
assert!(invalid_params.validate().is_err());
|
|
|
|
// Invalid relaxation time (too large)
|
|
let invalid_params = D2Q9Parameters::new(2.1);
|
|
assert!(invalid_params.validate().is_err());
|
|
}
|