//! Density-scaling pin for the cut-cell predictor (host): the same flow at //! `ρ` and `1000 ρ` with `μ` scaled alike is the same velocity field and a //! pressure scaled by 1000 — every term of the momentum equation carries //! `ρ` (the convection term used to be a bare volume flux times velocity, //! which starved every ρ = 1000 cut-cell run of convection). use rtx_cfd::solvers::incompressible::ConvectionScheme; use rtx_cfd::solvers::incompressible::embedded3::{ Body, Boundaries, Field, Fluid, Grid, Parameters, Side, Solver, WallScheme, }; fn run(rho: f64, moving: bool) -> Field { let n = 16; let h = 1.0 / n as f64; let g = Grid::cubic(2 * n, n, n, h); let nu = 1e-2; let mut solver = Solver::new( Fluid { density: rho, viscosity: rho * nu, reference_velocity: 1.0, reference_length: 0.3, }, Parameters { corrector_steps: 2, tolerance: 1e-11, convection_scheme: ConvectionScheme::TvdVanAlbada, wall_scheme: WallScheme::CutCell, boundaries: Boundaries { x1: Side::PressureOutlet, ..Boundaries::default() }, max_surface_speed: if moving { Some(0.5) } else { None }, ..Parameters::default() }, ); solver.set_boundary_velocity(|x, _, _, _| { if x <= 0.0 { (1.0, 0.0, 0.0) } else { (0.0, 0.0, 0.0) } }); let xc = move |t: f64| 0.7 + if moving { 0.1 * (3.0 * t).sin() } else { 0.0 }; let body = Body::from_sdf(move |x, y, z, t| { ((x - xc(t)).powi(2) + (y - 0.5_f64).powi(2) + (z - 0.5_f64).powi(2)).sqrt() - 0.15 }) .with_surface_velocity(move |_, _, _, t| { (if moving { 0.3 * (3.0 * t).cos() } else { 0.0 }, 0.0, 0.0) }); if moving { solver.set_moving_body(body); } else { solver.set_body(body); } let mut field = Field::new(g); for k in 0..n { for j in 0..n { for i in 0..=2 * n { field.u[g.uface(k, j, i)] = 1.0; } } } solver.initialize(&mut field); let dt = 0.2 * h; for _ in 0..40 { solver.advance(&mut field, dt); } field } fn compare(moving: bool) { let a = run(1.0, moving); let b = run(1000.0, moving); let max = |x: &[f64], y: &[f64], s: f64| { x.iter() .zip(y) .map(|(p, q)| (p - q / s).abs()) .fold(0.0, f64::max) }; let du = max(&a.u, &b.u, 1.0) .max(max(&a.v, &b.v, 1.0)) .max(max(&a.w, &b.w, 1.0)); let dp = max(&a.p, &b.p, 1000.0); let pscale = a.p.iter().fold(0.0f64, |m, p| m.max(p.abs())); println!(" moving {moving}: max |Δu| {du:.3e}, max |Δp/1000| {dp:.3e} (p scale {pscale:.3e})"); assert!(du < 1e-9, "velocity is not density-invariant: {du:.3e}"); assert!( dp < 1e-9 * pscale.max(1.0), "pressure does not scale with density: {dp:.3e}" ); } #[test] fn cut_cell_flow_is_density_invariant_at_rest() { compare(false); } #[test] fn cut_cell_flow_is_density_invariant_moving() { compare(true); }