embedded3 S2-6: the oblique-wall instrument (tests/embedded3_wall_position_oblique.rs: z-flow / in-plane Poiseuille + Couette linear exactness; gate oblique_wall_position_is_second_order) and the closures it found, host + device, default OFF — the transverse centroid correction (RTX_E3_DIFFUSION_TRANSVERSE=1, wall_order bit 7), the fine distance floor (RTX_E3_DISTANCE_FLOOR=fine, bit 8); kernel geometry factored into cut_cv; DFG 2D-1 x-shift knob (RTX_E3_DFG_SHIFT_X); FLAG_X0 0.6 -> 0.25 in the three flag tests (the flag of every record was detached)
CI / Build (macos-latest) (push) Waiting to run
CI / Test (macos-latest) (push) Blocked by required conditions
CI / Test (ubuntu-latest) (push) Blocked by required conditions
CI / Python Bindings (maturin) (macos-latest) (push) Blocked by required conditions
CI / Python Bindings (maturin) (ubuntu-latest) (push) Blocked by required conditions
CI / WASM Build + Size Check (push) Blocked by required conditions
CI / Distributed Training Tests (push) Blocked by required conditions
CI / CI Success (push) Blocked by required conditions
CI / Format Check (push) Failing after 5s
CI / Clippy Check (push) Failing after 5s
CI / Build (ubuntu-latest) (push) Failing after 4s
Performance Benchmarks / Run Benchmarks (push) Failing after 5s
Documentation / Build User Guide (push) Successful in 6s
Documentation / Build API Documentation (push) Failing after 17s
CI / Build CPU-Only (Explicit) (push) Failing after 57s
CI / Build (macos-latest) (push) Waiting to run
CI / Test (macos-latest) (push) Blocked by required conditions
CI / Test (ubuntu-latest) (push) Blocked by required conditions
CI / Python Bindings (maturin) (macos-latest) (push) Blocked by required conditions
CI / Python Bindings (maturin) (ubuntu-latest) (push) Blocked by required conditions
CI / WASM Build + Size Check (push) Blocked by required conditions
CI / Distributed Training Tests (push) Blocked by required conditions
CI / CI Success (push) Blocked by required conditions
CI / Format Check (push) Failing after 5s
CI / Clippy Check (push) Failing after 5s
CI / Build (ubuntu-latest) (push) Failing after 4s
Performance Benchmarks / Run Benchmarks (push) Failing after 5s
Documentation / Build User Guide (push) Successful in 6s
Documentation / Build API Documentation (push) Failing after 17s
CI / Build CPU-Only (Explicit) (push) Failing after 57s
Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
9ae0e42dcd
commit
e9b2e7887b
@@ -47,6 +47,10 @@ fn dfg_2d_1_on_the_device() {
|
||||
let nz = NZ;
|
||||
let lz = nz as f64 * h;
|
||||
let t_end = env_f("RTX_E3_DFG_T", 10.0);
|
||||
// S2-6: the body (and the Δp probes) shifted in x by a fraction of h —
|
||||
// the lift's grid-registration scatter (a y-shift would change the
|
||||
// physical lift: the cylinder is 5 mm off the centreline).
|
||||
let cx = CX + env_f("RTX_E3_DFG_SHIFT_X", 0.0) * h;
|
||||
// Explicit stability: CFL 0.3 on U_m and half the viscous limit.
|
||||
let dt = (0.3 * h / U_M).min(0.5 * h * h / (6.0 * NU));
|
||||
let mut solver = Solver::new(
|
||||
@@ -81,7 +85,7 @@ fn dfg_2d_1_on_the_device() {
|
||||
// traction route (S2-1).
|
||||
solver.set_body(Body::extruded(
|
||||
rtx_cfd::solvers::incompressible::EmbeddedBody::circle(
|
||||
CX,
|
||||
cx,
|
||||
CY,
|
||||
0.5 * D + env_f("RTX_E3_DFG_DR", 0.0) * h,
|
||||
),
|
||||
@@ -101,8 +105,10 @@ fn dfg_2d_1_on_the_device() {
|
||||
solver.initialize(&mut field);
|
||||
let mask_cells = solver.mask().map_or(0, |m| m.fluid_cells());
|
||||
println!(
|
||||
" DFG 2D-1 ny {ny}: {nx}×{ny}×{nz} = {} cells ({mask_cells} fluid), h {h:.4e}, dt {dt:.3e}, t_end {t_end}",
|
||||
g.cells()
|
||||
" DFG 2D-1 ny {ny}: {nx}×{ny}×{nz} = {} cells ({mask_cells} fluid), h {h:.4e}, dt {dt:.3e}, t_end {t_end}, centre x/h {:.3} y/h {:.3}",
|
||||
g.cells(),
|
||||
cx / h,
|
||||
CY / h
|
||||
);
|
||||
let mut device = DeviceStep::new(solver, g);
|
||||
device.upload(&field);
|
||||
@@ -138,8 +144,8 @@ fn dfg_2d_1_on_the_device() {
|
||||
let ci = |x: f64| ((x / h).round() as usize).clamp(2, nx - 2);
|
||||
let cj = |y: f64| ((y / h).round() as usize).clamp(2, ny - 2);
|
||||
let bx = (
|
||||
ci(CX - margin),
|
||||
ci(CX + margin),
|
||||
ci(cx - margin),
|
||||
ci(cx + margin),
|
||||
cj(CY - 0.15),
|
||||
cj(CY + 0.15),
|
||||
0,
|
||||
@@ -159,10 +165,10 @@ fn dfg_2d_1_on_the_device() {
|
||||
let (cd_s, cl_s) = (coef * fs.f[0], coef * fs.f[1]);
|
||||
let zc = 0.5 * lz;
|
||||
let p_front = mask
|
||||
.pressure_at(&field.p, CX - 0.5 * D, CY, zc)
|
||||
.pressure_at(&field.p, cx - 0.5 * D, CY, zc)
|
||||
.unwrap_or(f64::NAN);
|
||||
let p_back = mask
|
||||
.pressure_at(&field.p, CX + 0.5 * D, CY, zc)
|
||||
.pressure_at(&field.p, cx + 0.5 * D, CY, zc)
|
||||
.unwrap_or(f64::NAN);
|
||||
let dp = p_front - p_back;
|
||||
let (cd, cl, cd_cv, cl_cv) = (coef * fw[0], coef * fw[1], coef * fcv[0], coef * fcv[1]);
|
||||
|
||||
@@ -4,7 +4,11 @@
|
||||
use rtx_cfd::solvers::incompressible::embedded3::{Body, Boundaries, Grid, Mask, Side};
|
||||
|
||||
const H: f64 = 0.41;
|
||||
const FLAG_X0: f64 = 0.6;
|
||||
/// The flag's ROOT: the cylinder's rear (the Turek–Hron flag runs from the
|
||||
/// cylinder to its tip A at x = 0.6). Until 2026-09-18 this was 0.6 — the
|
||||
/// flag sat DETACHED, its root where the benchmark's tip is; every flag
|
||||
/// record before that date is of that geometry.
|
||||
const FLAG_X0: f64 = 0.25;
|
||||
const FLAG_LEN: f64 = 0.35;
|
||||
const FLAG_HALF: f64 = 0.01;
|
||||
const FLAG_SPAN: f64 = 0.2;
|
||||
|
||||
@@ -19,7 +19,11 @@ const L: f64 = 2.5;
|
||||
const CX: f64 = 0.2;
|
||||
const CY: f64 = 0.2;
|
||||
const R_CYL: f64 = 0.05;
|
||||
const FLAG_X0: f64 = 0.6;
|
||||
/// The flag's ROOT: the cylinder's rear (the Turek–Hron flag runs from the
|
||||
/// cylinder to its tip A at x = 0.6). Until 2026-09-18 this was 0.6 — the
|
||||
/// flag sat DETACHED, its root where the benchmark's tip is; every flag
|
||||
/// record before that date is of that geometry.
|
||||
const FLAG_X0: f64 = 0.25;
|
||||
const FLAG_LEN: f64 = 0.35;
|
||||
const FLAG_HALF: f64 = 0.01;
|
||||
const AMP: f64 = 0.084;
|
||||
|
||||
@@ -31,7 +31,11 @@ const L: f64 = 2.5;
|
||||
const CX: f64 = 0.2;
|
||||
const CY: f64 = 0.2;
|
||||
const R_CYL: f64 = 0.05;
|
||||
const FLAG_X0: f64 = 0.6;
|
||||
/// The flag's ROOT: the cylinder's rear (the Turek–Hron flag runs from the
|
||||
/// cylinder to its tip A at x = 0.6). Until 2026-09-18 this was 0.6 — the
|
||||
/// flag sat DETACHED, its root where the benchmark's tip is; every flag
|
||||
/// record before that date is of that geometry.
|
||||
const FLAG_X0: f64 = 0.25;
|
||||
const FLAG_LEN: f64 = 0.35;
|
||||
const FLAG_HALF: f64 = 0.01;
|
||||
const FLAG_SPAN: f64 = 0.2;
|
||||
|
||||
@@ -0,0 +1,378 @@
|
||||
//! S2-6 instrument: the cut wall's effective position on an OBLIQUE wall
|
||||
//! with the flow IN the plane of the cut. Poiseuille flow (body force `F`
|
||||
//! along the tangent) in a channel between two embedded parallel planes
|
||||
//! `y = s x + c0` and `y = s x + c0 + w`; the sides carry the exact
|
||||
//! solution, z is periodic. The exact field is `U(r) t`, `U = F/(2μ)
|
||||
//! (G²/4 − r²)`, constant pressure, and the 5-point Laplacian is exact on
|
||||
//! it, so on full faces of a central window `u/t_x + F r²/(2μ)` is the
|
||||
//! constant `F G_eff²/(8μ)`: the effective gap from the u faces and from
|
||||
//! the v faces separately, each an effective wall offset per wall in
|
||||
//! units of h. Unlike the z-directed flat-wall test this one exercises the
|
||||
//! own-direction coupling of cut faces, the convective terms' cut-face
|
||||
//! values and the projection next to the wall (the spurious pressure is
|
||||
//! printed).
|
||||
use rtx_cfd::solvers::incompressible::ConvectionScheme;
|
||||
use rtx_cfd::solvers::incompressible::embedded3::{
|
||||
Body, Boundaries, Field, Fluid, Grid, Parameters, Side, Solver, WallScheme,
|
||||
};
|
||||
|
||||
const MU: f64 = 0.1;
|
||||
const LX: f64 = 2.0;
|
||||
const LY: f64 = 2.0;
|
||||
const W: f64 = 0.5;
|
||||
|
||||
/// The cut wall's parameters: the environment's (`None`), or the S2-6
|
||||
/// closures forced on / off for the gate.
|
||||
fn parameters(s26: Option<bool>) -> Parameters {
|
||||
let mut p = Parameters {
|
||||
corrector_steps: 2,
|
||||
tolerance: 1e-10,
|
||||
convection_scheme: ConvectionScheme::Upwind,
|
||||
wall_scheme: WallScheme::CutCell,
|
||||
boundaries: Boundaries {
|
||||
z0: Side::Periodic,
|
||||
z1: Side::Periodic,
|
||||
..Boundaries::default()
|
||||
},
|
||||
..Parameters::default()
|
||||
};
|
||||
if let Some(on) = s26 {
|
||||
p.diffusion_centroid = true;
|
||||
p.wall_distance_oblique = on;
|
||||
p.diffusion_transverse = on;
|
||||
p.distance_floor_fine = on;
|
||||
}
|
||||
p
|
||||
}
|
||||
|
||||
struct Reading {
|
||||
/// Effective wall offset per wall from the fitted profile of the u / v faces.
|
||||
off_u: f64,
|
||||
off_v: f64,
|
||||
/// The driving force the profile's curvature implies, over F.
|
||||
force_u: f64,
|
||||
/// 1 − (the fitted streamwise pressure slope)/F: must equal `force_u`.
|
||||
force_p: f64,
|
||||
/// RMS of the pressure about its linear fit, over F·G: full cells, cut cells.
|
||||
p_full: f64,
|
||||
p_cut: f64,
|
||||
}
|
||||
|
||||
/// Least squares of `y = a − b x`: returns (a, b).
|
||||
fn fit(points: &[(f64, f64)]) -> (f64, f64) {
|
||||
let n = points.len() as f64;
|
||||
let (sx, sy) = points
|
||||
.iter()
|
||||
.fold((0.0, 0.0), |s, p| (s.0 + p.0, s.1 + p.1));
|
||||
let (mx, my) = (sx / n, sy / n);
|
||||
let (sxx, sxy) = points.iter().fold((0.0, 0.0), |s, p| {
|
||||
(s.0 + (p.0 - mx) * (p.0 - mx), s.1 + (p.0 - mx) * (p.1 - my))
|
||||
});
|
||||
let slope = sxy / sxx;
|
||||
(my - slope * mx, -slope)
|
||||
}
|
||||
|
||||
fn reading(n: usize, slope: f64, c0: f64, along_z: bool, s26: Option<bool>) -> Reading {
|
||||
// The driving force (`RTX_E3_OBLIQUE_F`): the problem is linear in it
|
||||
// but for the convective terms, so a small value switches them off.
|
||||
#[allow(non_snake_case)]
|
||||
let F: f64 = std::env::var("RTX_E3_OBLIQUE_F")
|
||||
.ok()
|
||||
.and_then(|v| v.parse().ok())
|
||||
.unwrap_or(1.0);
|
||||
let h = 1.0 / n as f64;
|
||||
let (nx, ny, nz) = ((LX * n as f64) as usize, (LY * n as f64) as usize, 2);
|
||||
let norm = (1.0 + slope * slope).sqrt();
|
||||
let (tx, ty) = (1.0 / norm, slope / norm);
|
||||
let gap = W / norm;
|
||||
let r_of = move |x: f64, y: f64| ((y - slope * x - c0) - 0.5 * W) / norm;
|
||||
let speed = move |x: f64, y: f64| {
|
||||
let r = r_of(x, y);
|
||||
if r.abs() < 0.5 * gap {
|
||||
F / (2.0 * MU) * (0.25 * gap * gap - r * r)
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
};
|
||||
let mut solver = Solver::new(
|
||||
Fluid {
|
||||
density: 1.0,
|
||||
viscosity: MU,
|
||||
reference_velocity: 1.0,
|
||||
reference_length: 1.0,
|
||||
},
|
||||
parameters(s26),
|
||||
);
|
||||
// `along_z`: the same channel with the flow along the periodic z (the
|
||||
// cross-direction diffusion of w alone: no pressure, no convection).
|
||||
solver.set_boundary_velocity(move |x, y, _z, _t| {
|
||||
let s = speed(x, y);
|
||||
if along_z {
|
||||
(0.0, 0.0, s)
|
||||
} else {
|
||||
(s * tx, s * ty, 0.0)
|
||||
}
|
||||
});
|
||||
solver.set_momentum_source(move |_, _, _, _| {
|
||||
if along_z {
|
||||
(0.0, 0.0, F)
|
||||
} else {
|
||||
(F * tx, F * ty, 0.0)
|
||||
}
|
||||
});
|
||||
solver.set_body(Body::from_sdf(move |x, y, _z, _t| {
|
||||
0.5 * gap - r_of(x, y).abs()
|
||||
}));
|
||||
let g = Grid::cubic(nx, ny, nz, h);
|
||||
let mut field = Field::new(g);
|
||||
for k in 0..=nz {
|
||||
for j in 0..ny {
|
||||
for i in 0..nx {
|
||||
if along_z {
|
||||
field.w[g.wface(k, j, i)] = speed((i as f64 + 0.5) * h, (j as f64 + 0.5) * h);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for k in 0..nz {
|
||||
if along_z {
|
||||
break;
|
||||
}
|
||||
for j in 0..ny {
|
||||
for i in 0..=nx {
|
||||
field.u[g.uface(k, j, i)] = tx * speed(i as f64 * h, (j as f64 + 0.5) * h);
|
||||
}
|
||||
}
|
||||
for j in 0..=ny {
|
||||
for i in 0..nx {
|
||||
field.v[g.vface(k, j, i)] = ty * speed((i as f64 + 0.5) * h, j as f64 * h);
|
||||
}
|
||||
}
|
||||
}
|
||||
solver.initialize(&mut field);
|
||||
let dt = 0.5 * h * h / (6.0 * MU);
|
||||
let steps = (2.0 / dt).ceil() as usize;
|
||||
for _ in 0..steps {
|
||||
solver.advance(&mut field, dt);
|
||||
}
|
||||
let mask = solver.mask().expect("mask");
|
||||
// The sides pin the flow RATE (the exact profile), so a displaced wall
|
||||
// appears as a streamwise pressure slope: F_eff = F − dp/ds, and on the
|
||||
// full faces of a central window `u/t_x = F_eff/(2μ) (G_eff²/4 − r²)`
|
||||
// exactly (the 5-point Laplacian is exact on it). Fit both constants.
|
||||
let window = |x: f64| (x - 0.5 * LX).abs() < 0.3;
|
||||
let (mut pu, mut pv) = (Vec::new(), Vec::new());
|
||||
let (mut pf, mut pc) = (Vec::new(), Vec::new());
|
||||
for j in 0..ny {
|
||||
for i in 0..nx {
|
||||
let (xu, yu) = (i as f64 * h, (j as f64 + 0.5) * h);
|
||||
let fu = g.uface(0, j, i);
|
||||
let (xw, yw) = ((i as f64 + 0.5) * h, (j as f64 + 0.5) * h);
|
||||
let fw = g.wface(0, j, i);
|
||||
if along_z {
|
||||
if window(xw) && r_of(xw, yw).abs() < 0.4 * gap && mask.a_w(fw) >= 1.0 {
|
||||
pu.push((r_of(xw, yw).powi(2), field.w[fw]));
|
||||
}
|
||||
} else if window(xu) && r_of(xu, yu).abs() < 0.4 * gap && mask.a_u(fu) >= 1.0 {
|
||||
pu.push((r_of(xu, yu).powi(2), field.u[fu] / tx));
|
||||
}
|
||||
let (xv, yv) = ((i as f64 + 0.5) * h, j as f64 * h);
|
||||
let fv = g.vface(0, j, i);
|
||||
if !along_z
|
||||
&& slope > 0.0
|
||||
&& window(xv)
|
||||
&& r_of(xv, yv).abs() < 0.4 * gap
|
||||
&& mask.a_v(fv) >= 1.0
|
||||
{
|
||||
pv.push((r_of(xv, yv).powi(2), field.v[fv] / ty));
|
||||
}
|
||||
let c = g.cell(0, j, i);
|
||||
let (xc, yc) = ((i as f64 + 0.5) * h, (j as f64 + 0.5) * h);
|
||||
if window(xc) && mask.cell_active(c) && r_of(xc, yc).abs() < 0.5 * gap + h {
|
||||
let s_along = xc * tx + yc * ty;
|
||||
let full = r_of(xc, yc).abs() < 0.5 * gap - 1.5 * h;
|
||||
if full {
|
||||
pf.push((s_along, field.p[c]));
|
||||
} else {
|
||||
pc.push((s_along, field.p[c]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let profile = |points: &[(f64, f64)]| {
|
||||
if points.is_empty() {
|
||||
return (f64::NAN, f64::NAN);
|
||||
}
|
||||
let (a, b) = fit(points);
|
||||
let gap_eff = 2.0 * (a / b).sqrt();
|
||||
(0.5 * (gap - gap_eff) / h, 2.0 * MU * b / F)
|
||||
};
|
||||
let (off_u, force_u) = profile(&pu);
|
||||
let (off_v, _) = profile(&pv);
|
||||
let (p0, minus_slope) = fit(&pf);
|
||||
let rms = |points: &[(f64, f64)]| {
|
||||
(points
|
||||
.iter()
|
||||
.map(|(s, p)| (p - (p0 - minus_slope * s)).powi(2))
|
||||
.sum::<f64>()
|
||||
/ points.len().max(1) as f64)
|
||||
.sqrt()
|
||||
/ (F * gap)
|
||||
};
|
||||
Reading {
|
||||
off_u,
|
||||
off_v,
|
||||
force_u,
|
||||
force_p: 1.0 + minus_slope / F,
|
||||
p_full: rms(&pf),
|
||||
p_cut: rms(&pc),
|
||||
}
|
||||
}
|
||||
|
||||
/// The linear-exactness mode: in-plane Couette flow `u = K dist t` over ONE
|
||||
/// embedded oblique wall (no force, constant pressure, the sides carry the
|
||||
/// exact field). A scheme exact on linear fields returns the wall position
|
||||
/// to round-off; the fitted zero of the profile on full faces is the offset.
|
||||
fn couette(n: usize, slope: f64, c0: f64, s26: Option<bool>) -> (f64, f64) {
|
||||
const K: f64 = 1.0;
|
||||
let h = 1.0 / n as f64;
|
||||
let (nx, ny, nz) = ((LX * n as f64) as usize, (LY * n as f64) as usize, 2);
|
||||
let norm = (1.0 + slope * slope).sqrt();
|
||||
let (tx, ty) = (1.0 / norm, slope / norm);
|
||||
let dist = move |x: f64, y: f64| (y - slope * x - c0) / norm;
|
||||
let speed = move |x: f64, y: f64| K * dist(x, y).max(0.0);
|
||||
let mut solver = Solver::new(
|
||||
Fluid {
|
||||
density: 1.0,
|
||||
viscosity: MU,
|
||||
reference_velocity: 1.0,
|
||||
reference_length: 1.0,
|
||||
},
|
||||
parameters(s26),
|
||||
);
|
||||
solver.set_boundary_velocity(move |x, y, _z, _t| {
|
||||
let s = speed(x, y);
|
||||
(s * tx, s * ty, 0.0)
|
||||
});
|
||||
solver.set_body(Body::from_sdf(move |x, y, _z, _t| dist(x, y)));
|
||||
let g = Grid::cubic(nx, ny, nz, h);
|
||||
let mut field = Field::new(g);
|
||||
for k in 0..nz {
|
||||
for j in 0..ny {
|
||||
for i in 0..=nx {
|
||||
field.u[g.uface(k, j, i)] = tx * speed(i as f64 * h, (j as f64 + 0.5) * h);
|
||||
}
|
||||
}
|
||||
for j in 0..=ny {
|
||||
for i in 0..nx {
|
||||
field.v[g.vface(k, j, i)] = ty * speed((i as f64 + 0.5) * h, j as f64 * h);
|
||||
}
|
||||
}
|
||||
}
|
||||
solver.initialize(&mut field);
|
||||
let dt = 0.5 * h * h / (6.0 * MU);
|
||||
let steps = (2.0 / dt).ceil() as usize;
|
||||
for _ in 0..steps {
|
||||
solver.advance(&mut field, dt);
|
||||
}
|
||||
let mask = solver.mask().expect("mask");
|
||||
// u/t_x = K (dist − δ): fit on full faces of the central window, two to
|
||||
// six cells off the wall.
|
||||
let mut points = Vec::new();
|
||||
let mut worst: f64 = 0.0;
|
||||
for j in 0..ny {
|
||||
for i in 0..nx {
|
||||
let (x, y) = (i as f64 * h, (j as f64 + 0.5) * h);
|
||||
let f = g.uface(0, j, i);
|
||||
let dd = dist(x, y);
|
||||
if (x - 0.5 * LX).abs() < 0.3 && dd > 2.0 * h && dd < 6.0 * h && mask.a_u(f) >= 1.0 {
|
||||
points.push((dd, field.u[f] / tx));
|
||||
worst = worst.max((field.u[f] / tx - K * dd).abs() / (K * h));
|
||||
}
|
||||
}
|
||||
}
|
||||
let (a, minus_b) = fit(&points);
|
||||
// y = a − (−b) x with b the slope: the zero sits at dist = −a / b.
|
||||
let b = -minus_b;
|
||||
(-a / b / h, worst)
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "S2-6 instrument: linear exactness of the cut wall on an oblique wall (in-plane Couette; a minute on the host)"]
|
||||
fn oblique_wall_linear_exactness() {
|
||||
for slope in [0.0, 0.25, 0.5, 1.0] {
|
||||
for n in [16usize, 32] {
|
||||
let (off, worst) = couette(n, slope, 0.53, None);
|
||||
println!(
|
||||
" couette slope {slope:.2} n {n}: wall offset {off:+.4} h (negative = inside the body); worst full-face error {worst:.4} of K·h"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "S2-6 instrument: the oblique cut wall's effective position with in-plane flow (minutes on the host)"]
|
||||
fn oblique_wall_effective_position() {
|
||||
for (slope, c0) in [
|
||||
(0.0, 0.53),
|
||||
(0.0, 0.77),
|
||||
(0.25, 0.53),
|
||||
(0.5, 0.53),
|
||||
(1.0, 0.53),
|
||||
] {
|
||||
// `RTX_E3_OBLIQUE_N=64` adds a finer rung to the in-plane mode.
|
||||
let extra: Option<usize> = std::env::var("RTX_E3_OBLIQUE_N")
|
||||
.ok()
|
||||
.and_then(|v| v.parse().ok());
|
||||
let mut runs = vec![(16usize, false), (32, false), (16, true), (32, true)];
|
||||
if let Some(n) = extra {
|
||||
runs = vec![(n, false)];
|
||||
}
|
||||
for (n, along_z) in runs {
|
||||
let r = reading(n, slope, c0, along_z, None);
|
||||
println!(
|
||||
" slope {slope:.2} c0 {c0} n {n} {}: wall offset {:+.4} h (u faces) {:+.4} h (v faces), positive = inside the fluid; F_eff/F {:.5} (profile) {:.5} (pressure slope); pressure about its fit: {:.2e} full cells, {:.2e} near-wall cells (of F·G)",
|
||||
if along_z {
|
||||
"z-flow (w faces)"
|
||||
} else {
|
||||
"in-plane"
|
||||
},
|
||||
r.off_u,
|
||||
r.off_v,
|
||||
r.force_u,
|
||||
r.force_p,
|
||||
r.p_full,
|
||||
r.p_cut
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The gate (S2-6): with the oblique distance, the transverse centroid
|
||||
/// correction and the fine floor the cut wall is linear-exact to 0.02 h on
|
||||
/// an oblique wall (without them it sits 0.05–0.07 h inside the body at
|
||||
/// every h), and the z-directed Poiseuille offset halves per rung at
|
||||
/// slope ½ (without them: −0.087 → −0.083 h).
|
||||
#[test]
|
||||
fn oblique_wall_position_is_second_order() {
|
||||
for slope in [0.5, 1.0] {
|
||||
let (fixed, _) = couette(32, slope, 0.53, Some(true));
|
||||
let (before, _) = couette(32, slope, 0.53, Some(false));
|
||||
println!(
|
||||
" couette slope {slope}: offset {fixed:+.4} h with the S2-6 closures, {before:+.4} h without"
|
||||
);
|
||||
assert!(fixed.abs() < 0.02, "slope {slope}: {fixed}");
|
||||
assert!(
|
||||
before.abs() > 2.0 * fixed.abs(),
|
||||
"the instrument lost its contrast"
|
||||
);
|
||||
}
|
||||
let coarse = reading(16, 0.5, 0.53, true, Some(true)).off_u;
|
||||
let fine = reading(32, 0.5, 0.53, true, Some(true)).off_u;
|
||||
println!(" z-flow slope 0.5: offset {coarse:+.4} h at n 16, {fine:+.4} h at n 32");
|
||||
assert!(coarse.abs() < 0.04, "n 16 offset {coarse}");
|
||||
assert!(
|
||||
fine.abs() < 0.65 * coarse.abs(),
|
||||
"the offset does not halve: {coarse} → {fine}"
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user