278 lines
9.0 KiB
Rust
278 lines
9.0 KiB
Rust
//! Tests for SIMPLE algorithm implementation
|
||
//!
|
||
//! The SIMPLE (Semi-Implicit Method for Pressure Linked Equations) algorithm
|
||
//! is a widely used method for solving incompressible Navier-Stokes equations.
|
||
//! These tests verify the real mathematical implementation.
|
||
|
||
use approx::assert_relative_eq;
|
||
use rtx_cfd::{CfdConfig, CfdResult};
|
||
|
||
#[cfg(test)]
|
||
mod simple_tests {
|
||
use super::*;
|
||
use rtx_cfd::solvers::incompressible::{
|
||
BoundaryConditions, FlowField, IncompressibleSolver, SimpleParameters, SimpleSolver,
|
||
};
|
||
|
||
#[tokio::test]
|
||
async fn test_simple_solver_creation() -> CfdResult<()> {
|
||
let config = CfdConfig::new().with_density(1.0).with_viscosity(1e-3);
|
||
|
||
let params = SimpleParameters::default()
|
||
.with_pressure_relaxation(0.3)
|
||
.with_velocity_relaxation(0.7)
|
||
.with_max_iterations(1000)
|
||
.with_tolerance(1e-6);
|
||
|
||
let solver = SimpleSolver::new(config, params)?;
|
||
|
||
assert_eq!(solver.parameters().pressure_relaxation, 0.3);
|
||
assert_eq!(solver.parameters().velocity_relaxation, 0.7);
|
||
|
||
Ok(())
|
||
}
|
||
|
||
#[tokio::test]
|
||
#[ignore = "Pre-existing SIMPLE solver convergence issue"]
|
||
async fn test_lid_driven_cavity_re100() -> CfdResult<()> {
|
||
// Classic benchmark: lid-driven cavity at Re=100
|
||
let config = CfdConfig::new()
|
||
.with_density(1.0)
|
||
.with_viscosity(1e-2) // Re = UL/ν = 1*1/0.01 = 100
|
||
.with_reference_velocity(1.0)
|
||
.with_reference_length(1.0);
|
||
|
||
let params = SimpleParameters::default()
|
||
.with_pressure_relaxation(0.3)
|
||
.with_velocity_relaxation(0.7)
|
||
.with_max_iterations(1000)
|
||
.with_tolerance(1e-6);
|
||
|
||
let mut solver = SimpleSolver::new(config, params)?;
|
||
|
||
// Setup 32x32 grid for testing
|
||
let nx = 32;
|
||
let ny = 32;
|
||
let dx = 1.0 / (nx as f64 - 1.0);
|
||
let dy = 1.0 / (ny as f64 - 1.0);
|
||
|
||
// Initialize flow field
|
||
let mut flow_field = FlowField::new(nx, ny, dx, dy)?;
|
||
|
||
// Setup boundary conditions for lid-driven cavity
|
||
let mut bcs = BoundaryConditions::new();
|
||
|
||
// Top wall (lid): u=1, v=0
|
||
for i in 0..nx {
|
||
bcs.set_velocity_bc(i, ny - 1, 1.0, 0.0)?;
|
||
}
|
||
|
||
// Other walls: u=0, v=0 (no-slip)
|
||
for i in 0..nx {
|
||
bcs.set_velocity_bc(i, 0, 0.0, 0.0)?; // Bottom
|
||
}
|
||
for j in 0..ny {
|
||
bcs.set_velocity_bc(0, j, 0.0, 0.0)?; // Left
|
||
bcs.set_velocity_bc(nx - 1, j, 0.0, 0.0)?; // Right
|
||
}
|
||
|
||
// Apply boundary conditions
|
||
flow_field.apply_boundary_conditions(&bcs)?;
|
||
|
||
// Run SIMPLE iterations
|
||
let result = solver.solve(&mut flow_field, &bcs).await?;
|
||
|
||
// Verify convergence
|
||
assert!(
|
||
result.solver_result.converged,
|
||
"SIMPLE solver should converge for lid-driven cavity"
|
||
);
|
||
assert!(
|
||
result.solver_result.final_residual < 1e-6,
|
||
"Final residual should be below tolerance"
|
||
);
|
||
assert!(
|
||
result.solver_result.iterations < 1000,
|
||
"Should converge in reasonable iterations"
|
||
);
|
||
|
||
// Verify physical correctness
|
||
// 1. Check mass conservation (div(u) ≈ 0)
|
||
// Note: compute_max_divergence would need to be implemented
|
||
// For now, we'll just check that velocity field is reasonable
|
||
|
||
// 2. Check that maximum velocity is at the lid
|
||
// Find max u-velocity manually
|
||
let mut u_max = 0.0_f64;
|
||
let mut u_max_loc = (0, 0);
|
||
for i in 0..nx {
|
||
for j in 0..ny {
|
||
if let Ok((u, _)) = flow_field.get_velocity_at(i, j) {
|
||
if u > u_max {
|
||
u_max = u;
|
||
u_max_loc = (i, j);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
assert!(
|
||
u_max_loc.1 >= ny - 5,
|
||
"Maximum u-velocity should be near the lid"
|
||
);
|
||
assert!(u_max <= 1.1, "Max velocity should be reasonable");
|
||
|
||
// 3. Check center vortex characteristics for Re=100
|
||
let (u_center, v_center) = flow_field.get_velocity_at(nx / 2, ny / 2)?;
|
||
assert!(
|
||
u_center.abs() < 0.5,
|
||
"Center u-velocity should be reasonable"
|
||
);
|
||
assert!(
|
||
v_center.abs() < 0.5,
|
||
"Center v-velocity should be reasonable"
|
||
);
|
||
|
||
Ok(())
|
||
}
|
||
|
||
#[tokio::test]
|
||
async fn test_simple_pressure_correction() -> CfdResult<()> {
|
||
// Test that pressure correction step actually corrects mass balance
|
||
let config = CfdConfig::new().with_density(1.0).with_viscosity(1e-3);
|
||
|
||
let params = SimpleParameters::new()
|
||
.with_pressure_relaxation(0.3)
|
||
.with_velocity_relaxation(0.7);
|
||
|
||
let mut solver = SimpleSolver::new(config, params)?;
|
||
|
||
let nx = 16;
|
||
let ny = 16;
|
||
let dx = 0.1;
|
||
let dy = 0.1;
|
||
|
||
let mut flow_field = FlowField::new(nx, ny, dx, dy)?;
|
||
|
||
// Create artificial mass imbalance
|
||
for i in 1..nx - 1 {
|
||
for j in 1..ny - 1 {
|
||
flow_field.set_velocity(i, j, 0.1 * (i as f64), 0.1 * (j as f64))?;
|
||
}
|
||
}
|
||
|
||
// Note: Direct pressure correction step testing would require internal solver API
|
||
// For now, we test that the solver can perform a time step
|
||
let result = solver
|
||
.solve_time_step(&mut flow_field, &BoundaryConditions::new(), 0.01)
|
||
.await?;
|
||
assert!(
|
||
result.solver_result.final_residual >= 0.0,
|
||
"Residual should be non-negative"
|
||
);
|
||
|
||
Ok(())
|
||
}
|
||
|
||
#[tokio::test]
|
||
async fn test_simple_momentum_prediction() -> CfdResult<()> {
|
||
// Test momentum equation solution (prediction step)
|
||
let config = CfdConfig::new().with_density(1.0).with_viscosity(1e-2);
|
||
|
||
let params = SimpleParameters::default();
|
||
let mut solver = SimpleSolver::new(config, params)?;
|
||
|
||
let nx = 16;
|
||
let ny = 16;
|
||
let dx = 0.1;
|
||
let dy = 0.1;
|
||
|
||
let mut flow_field = FlowField::new(nx, ny, dx, dy)?;
|
||
|
||
// Setup simple shear flow
|
||
for i in 0..nx {
|
||
for j in 0..ny {
|
||
let y = j as f64 * dy;
|
||
flow_field.set_velocity(i, j, y, 0.0)?; // Linear shear
|
||
}
|
||
}
|
||
|
||
let dt = 0.001;
|
||
|
||
// Store initial kinetic energy - calculate manually
|
||
let mut initial_ke = 0.0;
|
||
for i in 0..nx {
|
||
for j in 0..ny {
|
||
if let Ok((u, v)) = flow_field.get_velocity_at(i, j) {
|
||
initial_ke += 0.5 * (u * u + v * v);
|
||
}
|
||
}
|
||
}
|
||
|
||
// Apply a time step
|
||
let _result = solver
|
||
.solve_time_step(&mut flow_field, &BoundaryConditions::new(), dt)
|
||
.await?;
|
||
|
||
// Verify that momentum equations are being solved
|
||
// (viscous diffusion should change the velocity field)
|
||
let mut final_ke = 0.0;
|
||
for i in 0..nx {
|
||
for j in 0..ny {
|
||
if let Ok((u, v)) = flow_field.get_velocity_at(i, j) {
|
||
final_ke += 0.5 * (u * u + v * v);
|
||
}
|
||
}
|
||
}
|
||
|
||
// With viscosity, kinetic energy should not increase excessively
|
||
assert!(
|
||
final_ke <= initial_ke * 1.5,
|
||
"Kinetic energy should not increase excessively"
|
||
);
|
||
|
||
Ok(())
|
||
}
|
||
|
||
#[tokio::test]
|
||
async fn test_simple_under_relaxation() -> CfdResult<()> {
|
||
// Test that under-relaxation factors work correctly
|
||
let config = CfdConfig::default();
|
||
|
||
// Test with strong under-relaxation
|
||
let params_conservative = SimpleParameters::default()
|
||
.with_pressure_relaxation(0.1)
|
||
.with_velocity_relaxation(0.1);
|
||
|
||
// Test with weak under-relaxation
|
||
let params_aggressive = SimpleParameters::default()
|
||
.with_pressure_relaxation(0.8)
|
||
.with_velocity_relaxation(0.8);
|
||
|
||
let solver_conservative = SimpleSolver::new(config.clone(), params_conservative)?;
|
||
let solver_aggressive = SimpleSolver::new(config, params_aggressive)?;
|
||
|
||
// Both should have different relaxation parameters
|
||
assert_ne!(
|
||
solver_conservative.parameters().pressure_relaxation,
|
||
solver_aggressive.parameters().pressure_relaxation
|
||
);
|
||
|
||
Ok(())
|
||
}
|
||
|
||
#[tokio::test]
|
||
async fn test_simple_parameters_validation() -> CfdResult<()> {
|
||
// Test parameter validation - with_pressure_relaxation clamps to non-negative
|
||
let params = SimpleParameters::default().with_pressure_relaxation(-0.1); // Invalid: negative relaxation
|
||
|
||
// Should clamp to 0.0 (see with_pressure_relaxation implementation)
|
||
assert!(params.pressure_relaxation >= 0.0);
|
||
|
||
let params2 = SimpleParameters::default().with_pressure_relaxation(1.5); // Potentially unstable but valid
|
||
|
||
assert_eq!(params2.pressure_relaxation, 1.5);
|
||
|
||
Ok(())
|
||
}
|
||
}
|