CI / Test (ubuntu-latest) (push) Canceled after 0s
CI / Build CPU-Only (Explicit) (push) Canceled after 0s
CI / Python Bindings (maturin) (macos-latest) (push) Canceled after 0s
CI / Python Bindings (maturin) (ubuntu-latest) (push) Canceled after 0s
CI / WASM Build + Size Check (push) Canceled after 0s
CI / Distributed Training Tests (push) Canceled after 0s
CI / CI Success (push) Canceled after 0s
Documentation / Build API Documentation (push) Canceled after 0s
Documentation / Build User Guide (push) Canceled after 0s
Performance Benchmarks / Run Benchmarks (push) Canceled after 0s
CI / Format Check (push) Canceled after 0s
CI / Clippy Check (push) Canceled after 0s
CI / Build (macos-latest) (push) Canceled after 0s
CI / Build (ubuntu-latest) (push) Canceled after 0s
CI / Test (macos-latest) (push) Canceled after 0s
First-order upwind's O(h) numerical viscosity was the measured limit on the
whole discretisation: MMS order ~0.9 at Re = 20 against 2.05 in the Stokes
limit. This adds a ConvectionScheme parameter to SimPLE — Upwind (default,
behaviour unchanged), TvdVanAlbada, TvdVanLeer — implemented by deferred
correction: the upwind operator stays implicit, so a_p = sum(a_nb) and
diagonal dominance survive unconditionally, and the limited
high-order-minus-upwind flux difference enters the source explicitly at the
current iterate. At a fixed point the two agree, so the converged answer is
the TVD discretisation. Faces whose far-upwind node lies outside the domain
fall back to pure upwind; wall faces pass no mass, so no correction enters.
Measured by the manufactured solution (van Albada, 16 -> 32 -> 64):
L2 velocity 1.325e-3 4.406e-4 1.232e-4 orders 1.59, 1.84
(upwind) 3.516e-2 1.954e-2 1.038e-2 orders 0.85, 0.91
The error is 27x to 84x below upwind's at equal resolution, the order climbs
toward 2 (the shortfall is limiter clipping plus the boundary fallback, both
of which shrink with h), the pressure error falls at the same rate, and
continuity still holds to solver tolerance in every cell.
On the Re = 100 lid-driven cavity at 65^2 the centreline minimum moves from
-0.1932 (upwind) to -0.2036 against Ghia's -0.2109 — 59% of the remaining
gap closed at equal resolution, converged in 790 iterations — and the vortex
position moves from 0.5000 to 0.4844 toward Ghia's 0.4531. Both new cavity
bounds exclude the upwind values, so falling back to first order fails them.
284 tests, 0 failing.
Co-Authored-By: Claude Fable 5 <[email protected]>
514 lines
20 KiB
Rust
514 lines
20 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, BoundaryLocation, BoundaryType, 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]
|
||
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);
|
||
|
||
// Steady solve: no pseudo-time step, under-relaxation folded into the
|
||
// momentum coefficients. The converged field is independent of both
|
||
// `time_step` (unused here) and the relaxation factor.
|
||
let params = SimpleParameters::default()
|
||
.with_pressure_relaxation(0.3)
|
||
.with_velocity_relaxation(0.7)
|
||
.with_max_iterations(8000)
|
||
// 2e-4, not 1e-6. The two lid corners, where the moving lid meets
|
||
// a stationary wall, carry a velocity discontinuity: the mass
|
||
// imbalance there does not reduce with iteration, so the
|
||
// normalised residual floors near 1.6e-4 on this grid. It falls
|
||
// roughly linearly with mesh size — 1.6e-3 at 17^2, 5.2e-4 at
|
||
// 33^2, 1.6e-4 at 65^2, 7.8e-5 at 97^2 — which is the signature of
|
||
// a singularity rather than of an unconverged solve. It is the
|
||
// same one Botella & Peyret (1998) subtract analytically to reach
|
||
// spectral accuracy. The *momentum* residual reaches machine zero.
|
||
// The physical assertions below establish correctness; this is
|
||
// only the stopping rule.
|
||
.with_tolerance(2e-4);
|
||
|
||
let mut solver = SimpleSolver::new(config, params)?;
|
||
|
||
// 65x65. First-order upwind carries a numerical viscosity of about
|
||
// |u| dx / 2, so the effective Reynolds number is well below the
|
||
// nominal 100 on a coarse grid; refining moves the solution steadily
|
||
// toward the reference (see the assertions on `u_min` below).
|
||
let nx = 65;
|
||
let ny = 65;
|
||
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.
|
||
//
|
||
// On a staggered MAC grid the only velocity components that live *on* a
|
||
// boundary are the normal ones: u faces `i = 0` and `i = nx` on the
|
||
// sides, v faces `j = 0` and `j = ny` on the floor and lid. Every u row
|
||
// sits at `y = (j + 0.5) dy` and every v column at `x = (i + 0.5) dx` —
|
||
// strictly interior, every one of them. The tangential no-slip and lid
|
||
// conditions are therefore not stored values at all; they enter the
|
||
// discretisation through the near-wall control volume's half-cell
|
||
// diffusion term, which is what `set_wall_velocity` supplies.
|
||
//
|
||
// `FreeSlipWall` is the condition that prescribes the normal component
|
||
// and leaves the tangential one free, so it is the right one on all four
|
||
// sides here — the no-slip part arrives via the wall velocity below.
|
||
//
|
||
// This previously prescribed whole u rows and v columns with
|
||
// `set_velocity_bc`, pinning lines that lie half a cell inside the
|
||
// domain. That was harmless only while the momentum sweeps froze those
|
||
// same lines. Now that every cell has a continuity equation, every cell
|
||
// needs at least one face the pressure correction may move, and pinning
|
||
// an interior face over-determines the cells beside it: the solve
|
||
// diverged, residual climbing steadily from 0.10 at iteration 20 to 3.8
|
||
// at iteration 8000, with max |div u| of 64.
|
||
let mut bcs = BoundaryConditions::new();
|
||
for location in [
|
||
BoundaryLocation::Left,
|
||
BoundaryLocation::Right,
|
||
BoundaryLocation::Bottom,
|
||
BoundaryLocation::Top,
|
||
] {
|
||
bcs.add_boundary_condition(location, BoundaryType::FreeSlipWall);
|
||
}
|
||
|
||
// The lid moves; the other three walls do not. Sampled at the wall face
|
||
// position, so the test compares `y` against the top of the domain,
|
||
// which is `ny * dy` — this grid spans slightly more than the unit
|
||
// square, since `FlowField` counts cells and `dx` is 1/(nx - 1).
|
||
let domain_top = ny as f64 * dy;
|
||
solver.set_wall_velocity(move |_x, y| {
|
||
if y > 0.5 * domain_top {
|
||
(1.0, 0.0)
|
||
} else {
|
||
(0.0, 0.0)
|
||
}
|
||
});
|
||
|
||
// 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 < 2e-4,
|
||
"Final residual should be below tolerance"
|
||
);
|
||
assert!(
|
||
result.solver_result.iterations < 8000,
|
||
"Should converge in reasonable iterations"
|
||
);
|
||
|
||
// The check that distinguishes a cavity from a sheared box.
|
||
//
|
||
// A lid-driven cavity recirculates: on the vertical centreline the
|
||
// horizontal velocity is *negative* through the lower half, as the
|
||
// return flow comes back beneath the primary vortex. Ghia, Ghia & Shin
|
||
// (1982) put the minimum at u = -0.2109, y = 0.4531 for Re = 100.
|
||
//
|
||
// Before the pressure-velocity coupling was repaired this solver
|
||
// produced a monotonic profile rising from 0 at the floor to 1 at the
|
||
// lid — Couette flow, with no recirculation anywhere — because the
|
||
// pressure correction was some four orders of magnitude too weak to
|
||
// enforce continuity, and the return flow in a cavity is driven
|
||
// entirely by the pressure gradient. Every assertion above passes for
|
||
// that wrong field; this one does not.
|
||
let mut u_min = 0.0_f64;
|
||
let mut y_at_min = 0.0_f64;
|
||
for j in 0..ny {
|
||
let (u, _) = flow_field.get_velocity_at(nx / 2, j)?;
|
||
if u < u_min {
|
||
u_min = u;
|
||
y_at_min = j as f64 / (ny - 1) as f64;
|
||
}
|
||
}
|
||
|
||
assert!(
|
||
u_min < -0.05,
|
||
"no recirculation on the centreline (minimum u = {u_min:.4}); \
|
||
the solution is a shear layer, not a cavity"
|
||
);
|
||
|
||
// Ghia puts the centreline minimum at y = 0.4531. This solver reads
|
||
// 0.5000 on a 65^2 grid, and the band below is set around what it
|
||
// actually delivers rather than around the reference — an honest record
|
||
// of a real disagreement, not a claim of agreement.
|
||
//
|
||
// The history is worth keeping straight, because two of the three
|
||
// numbers this has read were right for the wrong reasons. It used to
|
||
// read 0.484, which looked good: the diffusion conductances omitted the
|
||
// face area and were a factor 1/h too large, so the solver ran at a
|
||
// Reynolds number far below 100, and a strongly over-diffusive cavity
|
||
// approaches Stokes flow, whose vortex sits near mid-height. Correcting
|
||
// the viscosity exposed the discretisation's own error and it fell to
|
||
// 0.3906. Making the near-wall lines unknowns — rather than freezing
|
||
// them and imposing the wall half a cell inside the domain — moved it to
|
||
// 0.5000. The error against Ghia went 0.062 -> 0.047, so this is a real
|
||
// improvement, but it overshoots now where it undershot before, and a
|
||
// first-order scheme at 65^2 has no business claiming better.
|
||
//
|
||
// `tests/mms_navier_stokes.rs` measures the underlying error directly:
|
||
// the observed order of accuracy is now 0.85 to 0.91, up from 0.48,
|
||
// where first-order upwind should give 1.
|
||
assert!(
|
||
(0.44..0.56).contains(&y_at_min),
|
||
"the primary vortex is at y = {y_at_min:.4}, outside the band this \
|
||
solver currently warrants (Ghia: 0.4531)"
|
||
);
|
||
|
||
// The *strength* is limited by first-order upwind's numerical
|
||
// viscosity, which at this resolution is a substantial fraction of the
|
||
// physical viscosity, so Ghia's -0.2109 is not reachable here. The grid
|
||
// study in docs/solver_status.md showed the value climbing monotonically
|
||
// toward it: -0.123 at 17^2, -0.154 at 33^2, -0.1792 at 65^2, -0.182 at
|
||
// 97^2, Richardson-extrapolating to about -0.199.
|
||
//
|
||
// Solving the near-wall rows instead of freezing them moved the 65^2
|
||
// figure from -0.1792 to -0.1932 — 15% of the remaining gap to Ghia,
|
||
// closed on the same mesh, and in 733 outer iterations instead of 971.
|
||
// The band is shifted to match, not widened: it is the same 0.04 wide as
|
||
// before. Bounding it both ways catches a solver that has stopped
|
||
// recirculating *and* one that has become unphysically energetic.
|
||
assert!(
|
||
(-0.21..-0.17).contains(&u_min),
|
||
"centreline minimum {u_min:.4} is outside the band expected for \
|
||
first-order upwind at 65^2 approaching Ghia's -0.2109"
|
||
);
|
||
|
||
// Pressure must be O(rho U^2), not O(0). A near-zero pressure field is
|
||
// the signature of a correction that is not coupling to the momentum
|
||
// equation at all.
|
||
let mut p_max = 0.0_f64;
|
||
for j in 0..ny {
|
||
for i in 0..nx {
|
||
p_max = p_max.max(flow_field.p[(j, i)].abs());
|
||
}
|
||
}
|
||
assert!(
|
||
p_max > 0.1,
|
||
"peak pressure {p_max:.3e} is far below the rho U^2 scale of 1.0; \
|
||
the pressure field is not being driven"
|
||
);
|
||
|
||
// 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(())
|
||
}
|
||
|
||
/// The same Re = 100 cavity with the deferred-correction TVD scheme.
|
||
///
|
||
/// First-order upwind's numerical viscosity is what holds the 65^2
|
||
/// centreline minimum near -0.19 against Ghia's -0.2109; a second-order
|
||
/// convective flux removes most of that viscosity, so this measures how
|
||
/// far the cavity closes on the reference once the scheme, rather than
|
||
/// the resolution, stops being the limit.
|
||
#[tokio::test]
|
||
async fn test_lid_driven_cavity_re100_tvd() -> CfdResult<()> {
|
||
use rtx_cfd::solvers::incompressible::ConvectionScheme;
|
||
|
||
let config = CfdConfig::new()
|
||
.with_density(1.0)
|
||
.with_viscosity(1e-2)
|
||
.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(20000)
|
||
.with_convection_scheme(ConvectionScheme::TvdVanAlbada)
|
||
// Same corner-singularity floor as the upwind test above.
|
||
.with_tolerance(2e-4);
|
||
|
||
let mut solver = SimpleSolver::new(config, params)?;
|
||
|
||
let nx = 65;
|
||
let ny = 65;
|
||
let dx = 1.0 / (nx as f64 - 1.0);
|
||
let dy = 1.0 / (ny as f64 - 1.0);
|
||
let mut flow_field = FlowField::new(nx, ny, dx, dy)?;
|
||
|
||
let mut bcs = BoundaryConditions::new();
|
||
for location in [
|
||
BoundaryLocation::Left,
|
||
BoundaryLocation::Right,
|
||
BoundaryLocation::Bottom,
|
||
BoundaryLocation::Top,
|
||
] {
|
||
bcs.add_boundary_condition(location, BoundaryType::FreeSlipWall);
|
||
}
|
||
let domain_top = ny as f64 * dy;
|
||
solver.set_wall_velocity(move |_x, y| {
|
||
if y > 0.5 * domain_top {
|
||
(1.0, 0.0)
|
||
} else {
|
||
(0.0, 0.0)
|
||
}
|
||
});
|
||
flow_field.apply_boundary_conditions(&bcs)?;
|
||
|
||
let result = solver.solve(&mut flow_field, &bcs).await?;
|
||
assert!(
|
||
result.solver_result.converged,
|
||
"TVD cavity did not converge: residual {:.3e} after {} iterations",
|
||
result.solver_result.final_residual, result.solver_result.iterations
|
||
);
|
||
|
||
let mut u_min = 0.0_f64;
|
||
let mut y_at_min = 0.0_f64;
|
||
for j in 0..ny {
|
||
let (u, _) = flow_field.get_velocity_at(nx / 2, j)?;
|
||
if u < u_min {
|
||
u_min = u;
|
||
y_at_min = j as f64 / (ny - 1) as f64;
|
||
}
|
||
}
|
||
println!(
|
||
" TVD 65^2 cavity: u_min = {u_min:.4} at y = {y_at_min:.4} \
|
||
(Ghia: -0.2109 at 0.4531) iterations = {}",
|
||
result.solver_result.iterations
|
||
);
|
||
|
||
// Measured: u_min = -0.2036 at y = 0.4844, in 790 iterations. Upwind
|
||
// on the same mesh reads -0.1932 at 0.5000, so the TVD scheme closes
|
||
// 59% of the remaining gap to Ghia's -0.2109 at equal resolution. The
|
||
// band is set around what the scheme delivers and excludes the upwind
|
||
// value: falling back to first order is the regression this test is
|
||
// here to catch.
|
||
assert!(
|
||
(-0.215..-0.195).contains(&u_min),
|
||
"centreline minimum {u_min:.4} outside the band the TVD scheme \
|
||
warrants at 65^2 (measured -0.2036; upwind gives -0.1932; \
|
||
Ghia -0.2109)"
|
||
);
|
||
|
||
// Position: 0.4844 against Ghia's 0.4531, down from upwind's 0.5000.
|
||
// The gridline spacing is 1/64, so the reading is quantised; the upper
|
||
// bound excludes 0.5000 exactly because that is the upwind value.
|
||
assert!(
|
||
(0.44..0.50).contains(&y_at_min),
|
||
"primary vortex at y = {y_at_min:.4}, outside the TVD band \
|
||
(measured 0.4844; Ghia 0.4531; upwind reads 0.5000)"
|
||
);
|
||
|
||
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(())
|
||
}
|
||
}
|