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
Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01X2GmJXeQ2njUecEKiJZ1G2
89 lines
3.3 KiB
Rust
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}"
|
|
);
|
|
}
|