Files
rustytorch/crates/specialized/rtx-cfd/tests/curvilinear_operators.rs
T
Omar SobhandClaude Fable 5.1 52da75a3a9
CI / Distributed Training Tests (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
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 / CI Success (push) Canceled after 0s
Documentation / Build API Documentation (push) Canceled after 0s
Documentation / Build User Guide (push) Canceled after 0s
rtx-cfd: curvilinear collocated PISO on a structured patch (overset A-P0, WIP) — PatchMesh (right-handed s,n; periodic seam with shift; face metrics), patch generators (TFI, skewed annulus, sheared/varying-skew channels), CSR + Jacobi-BiCGSTAB, the Zang–Street–Koseff incremental step with the node-based 9-point L_f, LSQ gradients, explicit and line-implicit-n predictors, adjustPhi; tests: mesh metrics (5 green), operators exact on linear fields incl. the seam (green), sparse (2 green), MMS ladder (Cartesian 16/32: 1.37–1.39x the staggered error, order 0.83; n=64 stalls at a |du/dt| floor 2e-4 — open, tolerance-scaling hypothesis), annulus/Poiseuille not yet run
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01X2GmJXeQ2njUecEKiJZ1G2
2026-09-04 05:00:08 -07:00

89 lines
3.3 KiB
Rust

//! P0 step 2: the operators are exact on linear fields over a skewed,
//! stretched, periodic annulus — nodal reconstruction at interior nodes,
//! the least-squares cell gradient, and the face operator `L_f`
//! (seam faces included).
use rtx_cfd::mesh::patch_gen::annulus_skewed;
use rtx_cfd::solvers::incompressible::{Operators, PatchBoundaries};
#[test]
fn nodes_gradients_and_face_operator_are_exact_on_linear_fields() {
let mesh = annulus_skewed([0.2, -0.1], 0.5, 1.5, 32, 8, 0.3, 3.0).unwrap();
let ops = Operators::new(&mesh, &PatchBoundaries::default());
let (a, b, c) = (0.7, -1.3, 2.1);
let phi = |x: f64, y: f64| a + b * x + c * y;
let cell_vals: Vec<f64> = (0..mesh.cell_count())
.map(|i| {
let xy = mesh.centre(i);
phi(xy[0], xy[1])
})
.collect();
// Interior nodes (rows 1..nn-1, every column including the seam).
let nodes = ops.node_values(&mesh, &cell_vals, &|_, _| None);
let mut worst_node = 0.0_f64;
for k in 1..mesh.nn() {
for i in 0..=mesh.ns() {
let n = mesh.node(k, i);
let xy = mesh.node_xy(n);
worst_node = worst_node.max((nodes[n] - phi(xy[0], xy[1])).abs());
}
}
assert!(
worst_node < 1e-13,
"interior node reconstruction error {worst_node:.3e}"
);
// Cell gradients with the boundary faces at their exact values.
let mut worst_grad = 0.0_f64;
for cell in 0..mesh.cell_count() {
let g = ops.gradient(&mesh, cell, &cell_vals, &|f| {
let xy = mesh.faces()[f].centre;
Some(phi(xy[0], xy[1]))
});
worst_grad = worst_grad.max((g[0] - b).abs()).max((g[1] - c).abs());
}
assert!(worst_grad < 1e-12, "gradient error {worst_grad:.3e}");
// L_f on interior faces with exact node values: (∇φ)·S = b S_x + c S_y.
let exact_nodes: Vec<f64> = (0..ops.nodes().len())
.map(|n| {
let xy = mesh.node_xy(n);
phi(xy[0], xy[1])
})
.collect();
let mut worst_face = 0.0_f64;
let mut seam_checked = 0;
for (f, face) in mesh.faces().iter().enumerate() {
if face.owner.is_none() || face.neigh.is_none() {
continue;
}
let lf = ops.face_gradient_flux(&mesh, f, &cell_vals, &exact_nodes, None);
let exact = b * face.s[0] + c * face.s[1];
let scale = (face.s[0] * face.s[0] + face.s[1] * face.s[1]).sqrt() * (b.abs() + c.abs());
worst_face = worst_face.max((lf - exact).abs() / scale);
if mesh.is_sface(f) && f % mesh.sfaces_per_row() == 0 {
seam_checked += 1;
}
}
assert_eq!(seam_checked, mesh.nn());
assert!(worst_face < 1e-13, "face operator error {worst_face:.3e}");
// With the reconstructed (not exact) node values the interior faces are
// still exact, since the reconstruction is.
let mut worst_face2 = 0.0_f64;
for k in 1..mesh.nn() - 1 {
for i in 0..mesh.ns() {
let f = mesh.sface(k, i);
let face = &mesh.faces()[f];
let lf = ops.face_gradient_flux(&mesh, f, &cell_vals, &nodes, None);
let exact = b * face.s[0] + c * face.s[1];
worst_face2 = worst_face2.max((lf - exact).abs());
}
}
assert!(
worst_face2 < 1e-12,
"face operator with reconstructed nodes {worst_face2:.3e}"
);
}