Files
rustytorch/crates/specialized/rtx-cfd/tests/lbm_d3q19_tests.rs
T
2026-03-04 00:08:42 +00:00

205 lines
6.5 KiB
Rust

//! Tests for D3Q19 Lattice Boltzmann Method implementation
use approx::assert_relative_eq;
use nalgebra::Vector3;
use rtx_cfd::solvers::lbm::{D3Q19Parameters, D3Q19Solver};
#[test]
fn test_d3q19_lattice_velocities() {
let solver = D3Q19Solver::new(10, 10, 10, D3Q19Parameters::default());
let velocities = solver.lattice_velocities();
// D3Q19 should have 19 velocities
assert_eq!(velocities.len(), 19);
// Check specific velocity directions
assert_eq!(velocities[0], Vector3::new(0, 0, 0)); // Rest
assert_eq!(velocities[1], Vector3::new(1, 0, 0)); // +X
assert_eq!(velocities[2], Vector3::new(-1, 0, 0)); // -X
assert_eq!(velocities[3], Vector3::new(0, 1, 0)); // +Y
assert_eq!(velocities[4], Vector3::new(0, -1, 0)); // -Y
assert_eq!(velocities[5], Vector3::new(0, 0, 1)); // +Z
assert_eq!(velocities[6], Vector3::new(0, 0, -1)); // -Z
}
#[test]
fn test_d3q19_weights() {
let solver = D3Q19Solver::new(10, 10, 10, D3Q19Parameters::default());
let weights = solver.weights();
// D3Q19 should have 19 weights
assert_eq!(weights.len(), 19);
// Check weight values
assert_relative_eq!(weights[0], 1.0 / 3.0, epsilon = 1e-12); // Rest particle
assert_relative_eq!(weights[1], 1.0 / 18.0, epsilon = 1e-12); // Face neighbors
assert_relative_eq!(weights[7], 1.0 / 36.0, epsilon = 1e-12); // Edge neighbors
// Weights should sum to 1
let sum: f64 = weights.iter().sum();
assert_relative_eq!(sum, 1.0, epsilon = 1e-12);
}
#[test]
fn test_d3q19_equilibrium_distribution() {
let solver = D3Q19Solver::new(10, 10, 10, D3Q19Parameters::default());
let density = 1.0;
let velocity = Vector3::new(0.1, 0.05, 0.02);
let f_eq = solver.equilibrium_distribution(density, &velocity);
// Should have 19 components
assert_eq!(f_eq.len(), 19);
// 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_d3q19_equilibrium_at_rest() {
let solver = D3Q19Solver::new(10, 10, 10, D3Q19Parameters::default());
let density = 1.0;
let velocity = Vector3::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..19 {
assert_relative_eq!(f_eq[i], density * weights[i], epsilon = 1e-12);
}
}
#[test]
fn test_d3q19_macroscopic_variables() {
let mut solver = D3Q19Solver::new(10, 10, 10, D3Q19Parameters::default());
// Initialize with known state
let density = 1.2;
let velocity = Vector3::new(0.1, -0.05, 0.03);
// Set distribution functions to equilibrium
let f_eq = solver.equilibrium_distribution(density, &velocity);
solver.set_distribution_at(5, 5, 5, &f_eq);
// Extract macroscopic variables
let macro_vars = solver.macroscopic_variables_at(5, 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);
assert_relative_eq!(macro_vars.velocity.z, velocity.z, epsilon = 1e-12);
}
#[test]
fn test_d3q19_bgk_collision() {
let mut solver = D3Q19Solver::new(10, 10, 10, D3Q19Parameters::new(0.6));
// Initialize all cells with equilibrium
let density = 1.0;
let velocity = Vector3::new(0.1, 0.0, 0.0);
solver.initialize_uniform(density, velocity);
// Get original equilibrium distribution
let f_eq = solver.equilibrium_distribution(density, &velocity);
// Perturb from equilibrium by reducing a component
let mut f = f_eq.clone();
f[1] *= 0.8; // Reduce from equilibrium (80% of equilibrium value)
solver.set_distribution_at(5, 5, 5, &f);
let initial_value = f[1];
// Apply collision step
solver.collision_step();
// Check that distribution moves toward equilibrium
let f_after = solver.distribution_at(5, 5, 5);
// With BGK collision: f_new = f - omega * (f - f_eq)
// Since f[1] < f_eq[1], (f - f_eq) is negative, so f_new should increase
assert!(
f_after[1] > initial_value,
"BGK should move towards equilibrium: {} > {}",
f_after[1],
initial_value
);
// Check that it's moving in the right direction (don't check exact value due to neighboring cell effects)
let direction_to_equilibrium = f_eq[1] - initial_value;
let actual_change = f_after[1] - initial_value;
assert!(
direction_to_equilibrium > 0.0 && actual_change > 0.0,
"Should move towards equilibrium"
);
}
#[test]
fn test_d3q19_streaming_step() {
let mut solver = D3Q19Solver::new(5, 5, 5, D3Q19Parameters::default());
// Initialize central cell with specific distribution
let mut f = vec![0.0; 19];
f[1] = 1.0; // Only +X component
solver.set_distribution_at(2, 2, 2, &f);
// Apply streaming step
solver.streaming_step();
// Check that the distribution has moved in +X direction
let f_east = solver.distribution_at(3, 2, 2);
assert_relative_eq!(f_east[1], 1.0, epsilon = 1e-12);
// Original cell should have zero +X component
let f_original = solver.distribution_at(2, 2, 2);
assert_relative_eq!(f_original[1], 0.0, epsilon = 1e-12);
}
#[test]
fn test_d3q19_mass_conservation() {
let nx = 8;
let ny = 8;
let nz = 8;
let mut solver = D3Q19Solver::new(nx, ny, nz, D3Q19Parameters::default());
// Initialize with uniform density
solver.initialize_uniform(1.0, Vector3::new(0.05, 0.02, 0.01));
let initial_mass = solver.total_mass();
// Run simulation for several steps with periodic boundaries
for _ in 0..50 {
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_d3q19_parameters_validation() {
// Valid parameters
let valid_params = D3Q19Parameters::new(0.6);
assert!(valid_params.validate().is_ok());
// Invalid relaxation time (too small)
let invalid_params = D3Q19Parameters::new(0.4);
assert!(invalid_params.validate().is_err());
// Invalid relaxation time (too large)
let invalid_params = D3Q19Parameters::new(2.1);
assert!(invalid_params.validate().is_err());
}