Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
734 lines
24 KiB
Rust
734 lines
24 KiB
Rust
//! R7 phase 1: the composite Poisson problem with one nested ratio-2 patch
|
||
//! (`embedded3::composite`, a host prototype nothing else calls).
|
||
//!
|
||
//! - P1 (`composite_mms_ladder`, ignored): manufactured solution on the unit
|
||
//! cube, patch = the middle half, n = 16/32/64 coarse; errors split into
|
||
//! the interface cells (owners of a coarse–fine face), the rest of the
|
||
//! patch, and the rest of the coarse grid, for the three interface fluxes
|
||
//! and the uniform coarse / fine grids;
|
||
//! - P2 (`composite_cut_sphere`, ignored): a Neumann sphere inside the patch
|
||
//! cut by embedded3's `CutGeometry` on the fine level, against the
|
||
//! uniformly fine grid with the same cut;
|
||
//! - P3 (`composite_cost`, ignored): unknowns and wall time against the
|
||
//! uniformly fine grid solved by embedded3's production PCG.
|
||
//!
|
||
//! Output CSVs go to `$R7_OUT` when set. The default (non-ignored) test is a
|
||
//! seconds-scale smoke of the assembly and the solve.
|
||
|
||
use rtx_cfd::solvers::incompressible::MultigridParameters;
|
||
use rtx_cfd::solvers::incompressible::embedded3::Grid;
|
||
use rtx_cfd::solvers::incompressible::embedded3::composite::{
|
||
Composite, CompositeSpec, Interface, Sdf, solve_bicgstab, uniform_problem,
|
||
};
|
||
use rtx_cfd::solvers::incompressible::embedded3::poisson::solve_pcg;
|
||
use std::f64::consts::PI;
|
||
use std::io::Write;
|
||
use std::sync::Arc;
|
||
|
||
fn mms_u(x: f64, y: f64, z: f64) -> f64 {
|
||
(1.3 * PI * x + 0.2).cos() * (1.1 * PI * y + 0.4).cos() * (0.9 * PI * z + 0.6).cos()
|
||
}
|
||
|
||
fn mms_f(x: f64, y: f64, z: f64) -> f64 {
|
||
PI * PI * (1.69 + 1.21 + 0.81) * mms_u(x, y, z)
|
||
}
|
||
|
||
/// A localized feature inside the patch on top of the smooth field: the
|
||
/// case local refinement is for (a Gaussian of width 0.06 at the centre).
|
||
const BUMP_S: f64 = 0.06;
|
||
|
||
fn bump_u(x: f64, y: f64, z: f64) -> f64 {
|
||
let r2 = (x - 0.5).powi(2) + (y - 0.5).powi(2) + (z - 0.5).powi(2);
|
||
mms_u(x, y, z) + (-r2 / (BUMP_S * BUMP_S)).exp()
|
||
}
|
||
|
||
fn bump_f(x: f64, y: f64, z: f64) -> f64 {
|
||
let r2 = (x - 0.5).powi(2) + (y - 0.5).powi(2) + (z - 0.5).powi(2);
|
||
let s2 = BUMP_S * BUMP_S;
|
||
mms_f(x, y, z) - (4.0 * r2 / (s2 * s2) - 6.0 / s2) * (-r2 / s2).exp()
|
||
}
|
||
|
||
const SPH_C: [f64; 3] = [0.52, 0.49, 0.51];
|
||
const SPH_R: f64 = 0.12;
|
||
const SPH_A: f64 = 2.0 * PI;
|
||
|
||
fn sph_r(x: f64, y: f64, z: f64) -> f64 {
|
||
((x - SPH_C[0]).powi(2) + (y - SPH_C[1]).powi(2) + (z - SPH_C[2]).powi(2)).sqrt()
|
||
}
|
||
|
||
/// `cos(a (r − R))`: zero normal derivative on the sphere.
|
||
fn sph_u(x: f64, y: f64, z: f64) -> f64 {
|
||
(SPH_A * (sph_r(x, y, z) - SPH_R)).cos()
|
||
}
|
||
|
||
fn sph_f(x: f64, y: f64, z: f64) -> f64 {
|
||
let r = sph_r(x, y, z);
|
||
let q = SPH_A * (r - SPH_R);
|
||
SPH_A * SPH_A * q.cos() + 2.0 * SPH_A * q.sin() / r
|
||
}
|
||
|
||
fn sphere() -> Sdf {
|
||
Arc::new(|x, y, z| sph_r(x, y, z) - SPH_R)
|
||
}
|
||
|
||
/// Volume-weighted RMS and max of the error over a subset.
|
||
#[derive(Default, Clone, Copy)]
|
||
struct Err {
|
||
s2: f64,
|
||
vol: f64,
|
||
max: f64,
|
||
count: usize,
|
||
}
|
||
|
||
impl Err {
|
||
fn add(&mut self, e: f64, v: f64) {
|
||
self.s2 += e * e * v;
|
||
self.vol += v;
|
||
self.max = self.max.max(e.abs());
|
||
self.count += 1;
|
||
}
|
||
fn l2(&self) -> f64 {
|
||
if self.vol > 0.0 {
|
||
(self.s2 / self.vol).sqrt()
|
||
} else {
|
||
f64::NAN
|
||
}
|
||
}
|
||
}
|
||
|
||
fn out_file(name: &str) -> Option<std::fs::File> {
|
||
let dir = std::env::var("R7_OUT").ok()?;
|
||
std::fs::create_dir_all(&dir).ok()?;
|
||
std::fs::File::create(format!("{dir}/{name}")).ok()
|
||
}
|
||
|
||
struct CompositeRun {
|
||
unknowns: usize,
|
||
fine_unknowns: usize,
|
||
iterations: usize,
|
||
rel: f64,
|
||
setup_s: f64,
|
||
solve_s: f64,
|
||
asym: f64,
|
||
/// interface, patch interior (fine, not interface), coarse (not
|
||
/// interface), cut cells, all.
|
||
err: [Err; 5],
|
||
}
|
||
|
||
fn run_composite(
|
||
n: usize,
|
||
lo: usize,
|
||
hi: usize,
|
||
iface: Interface,
|
||
body: Option<Sdf>,
|
||
exact: fn(f64, f64, f64) -> f64,
|
||
source: fn(f64, f64, f64) -> f64,
|
||
measure_asym: bool,
|
||
) -> CompositeRun {
|
||
let g = Grid::cubic(n, n, n, 1.0 / n as f64);
|
||
let spec = CompositeSpec {
|
||
coarse: g,
|
||
lo: [lo; 3],
|
||
hi: [hi; 3],
|
||
interface: iface,
|
||
body,
|
||
source: &source,
|
||
dirichlet: &exact,
|
||
};
|
||
let t = std::time::Instant::now();
|
||
let c = Composite::build(&spec);
|
||
let build_s = t.elapsed().as_secs_f64();
|
||
let mut x = vec![0.0; c.unknowns()];
|
||
let st = solve_bicgstab(&c, &mut x, 1e-11, 400, 2);
|
||
assert!(st.converged, "composite solve did not converge: {st:?}");
|
||
let mut err = [Err::default(); 5];
|
||
for u in 0..c.unknowns() {
|
||
let p = c.centre[u];
|
||
let e = x[u] - exact(p[0], p[1], p[2]);
|
||
let v = c.volume[u];
|
||
let fine = u >= c.n_coarse;
|
||
let class = if c.at_interface[u] {
|
||
0
|
||
} else if fine {
|
||
1
|
||
} else {
|
||
2
|
||
};
|
||
err[class].add(e, v);
|
||
if c.cut[u] {
|
||
err[3].add(e, v);
|
||
}
|
||
err[4].add(e, v);
|
||
}
|
||
CompositeRun {
|
||
unknowns: c.unknowns(),
|
||
fine_unknowns: c.unknowns() - c.n_coarse,
|
||
iterations: st.iterations,
|
||
rel: st.rel_residual,
|
||
setup_s: build_s + st.setup_s,
|
||
solve_s: st.solve_s,
|
||
asym: if measure_asym {
|
||
c.a.asymmetry()
|
||
} else {
|
||
f64::NAN
|
||
},
|
||
err,
|
||
}
|
||
}
|
||
|
||
struct UniformRun {
|
||
cells: usize,
|
||
iterations: usize,
|
||
setup_s: f64,
|
||
solve_s: f64,
|
||
/// inside the patch region [lo, hi)·h_c, outside it, cut cells, all.
|
||
err: [Err; 4],
|
||
}
|
||
|
||
fn run_uniform(
|
||
n: usize,
|
||
region: (f64, f64),
|
||
body: Option<&Sdf>,
|
||
exact: fn(f64, f64, f64) -> f64,
|
||
source: fn(f64, f64, f64) -> f64,
|
||
) -> UniformRun {
|
||
let g = Grid::cubic(n, n, n, 1.0 / n as f64);
|
||
let t = std::time::Instant::now();
|
||
let (prob, frac) = uniform_problem(g, body, &source, &exact);
|
||
let build_s = t.elapsed().as_secs_f64();
|
||
let mut p = vec![0.0; g.cells()];
|
||
let l1: f64 = prob.rhs.iter().map(|v| v.abs()).sum();
|
||
let params = MultigridParameters {
|
||
max_iterations: 2000,
|
||
..MultigridParameters::default()
|
||
};
|
||
let sol = solve_pcg(&prob, &mut p, ¶ms, 1e-11 * l1, None);
|
||
assert!(sol.converged, "uniform solve did not converge");
|
||
let h = g.dx;
|
||
let mut err = [Err::default(); 4];
|
||
for k in 0..n {
|
||
for j in 0..n {
|
||
for i in 0..n {
|
||
let idx = g.cell(k, j, i);
|
||
if !prob.active[idx] {
|
||
continue;
|
||
}
|
||
let x = [
|
||
(i as f64 + 0.5) * h,
|
||
(j as f64 + 0.5) * h,
|
||
(k as f64 + 0.5) * h,
|
||
];
|
||
let e = p[idx] - exact(x[0], x[1], x[2]);
|
||
let v = frac[idx] * h * h * h;
|
||
let inside = x.iter().all(|&c| c > region.0 && c < region.1);
|
||
err[usize::from(!inside)].add(e, v);
|
||
if frac[idx] < 1.0 - 1e-9 {
|
||
err[2].add(e, v);
|
||
}
|
||
err[3].add(e, v);
|
||
}
|
||
}
|
||
}
|
||
UniformRun {
|
||
cells: prob.active.iter().filter(|&&a| a).count(),
|
||
iterations: sol.iterations,
|
||
setup_s: build_s + sol.setup_ns as f64 * 1e-9,
|
||
solve_s: sol.iterate_ns as f64 * 1e-9,
|
||
err,
|
||
}
|
||
}
|
||
|
||
fn order(a: f64, b: f64) -> f64 {
|
||
(a / b).log2()
|
||
}
|
||
|
||
#[test]
|
||
fn composite_smoke() {
|
||
for iface in [Interface::Direct, Interface::Octree, Interface::Quadratic] {
|
||
let r = run_composite(12, 3, 9, iface, None, mms_u, mms_f, true);
|
||
eprintln!(
|
||
"{iface:?}: {} unknowns, {} it, rel {:.2e}, asym {:.2e}, L2 iface {:.3e} all {:.3e}",
|
||
r.unknowns,
|
||
r.iterations,
|
||
r.rel,
|
||
r.asym,
|
||
r.err[0].l2(),
|
||
r.err[4].l2()
|
||
);
|
||
assert!(r.err[4].l2() < 5e-3);
|
||
if iface == Interface::Direct {
|
||
assert!(r.asym < 1e-14);
|
||
}
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
#[ignore = "P1 ladder (minutes)"]
|
||
fn composite_mms_ladder() {
|
||
ladder("p1_mms.csv", mms_u, mms_f);
|
||
}
|
||
|
||
#[test]
|
||
#[ignore = "P1b ladder with a localized bump (minutes)"]
|
||
fn composite_bump_ladder() {
|
||
ladder("p1b_bump.csv", bump_u, bump_f);
|
||
}
|
||
|
||
fn ladder(name: &str, exact: fn(f64, f64, f64) -> f64, source: fn(f64, f64, f64) -> f64) {
|
||
let mut csv = out_file(name);
|
||
if let Some(f) = csv.as_mut() {
|
||
writeln!(
|
||
f,
|
||
"scheme,n,unknowns,iters,rel,l2_iface,linf_iface,l2_patch,linf_patch,l2_coarse,linf_coarse,l2_all,linf_all,setup_s,solve_s"
|
||
)
|
||
.ok();
|
||
}
|
||
let ns = [16usize, 32, 64];
|
||
let mut table: Vec<(String, Vec<[f64; 8]>)> = Vec::new();
|
||
for iface in [Interface::Direct, Interface::Octree, Interface::Quadratic] {
|
||
let mut rows = Vec::new();
|
||
for &n in &ns {
|
||
let r = run_composite(n, n / 4, 3 * n / 4, iface, None, exact, source, false);
|
||
let e = r.err;
|
||
eprintln!(
|
||
"{iface:?} n {n}: {} unk, {} it, rel {:.1e}; iface L2 {:.3e} Linf {:.3e} | patch L2 {:.3e} Linf {:.3e} | coarse L2 {:.3e} Linf {:.3e} | all L2 {:.3e} | {:.2}+{:.2} s",
|
||
r.unknowns,
|
||
r.iterations,
|
||
r.rel,
|
||
e[0].l2(),
|
||
e[0].max,
|
||
e[1].l2(),
|
||
e[1].max,
|
||
e[2].l2(),
|
||
e[2].max,
|
||
e[4].l2(),
|
||
r.setup_s,
|
||
r.solve_s
|
||
);
|
||
if let Some(f) = csv.as_mut() {
|
||
writeln!(
|
||
f,
|
||
"{iface:?},{n},{},{},{:.3e},{:.6e},{:.6e},{:.6e},{:.6e},{:.6e},{:.6e},{:.6e},{:.6e},{:.3},{:.3}",
|
||
r.unknowns,
|
||
r.iterations,
|
||
r.rel,
|
||
e[0].l2(),
|
||
e[0].max,
|
||
e[1].l2(),
|
||
e[1].max,
|
||
e[2].l2(),
|
||
e[2].max,
|
||
e[4].l2(),
|
||
e[4].max,
|
||
r.setup_s,
|
||
r.solve_s
|
||
)
|
||
.ok();
|
||
}
|
||
rows.push([
|
||
e[0].l2(),
|
||
e[0].max,
|
||
e[1].l2(),
|
||
e[1].max,
|
||
e[2].l2(),
|
||
e[2].max,
|
||
e[4].l2(),
|
||
e[4].max,
|
||
]);
|
||
}
|
||
table.push((format!("{iface:?}"), rows));
|
||
}
|
||
for (label, n_of) in [("uniform-coarse", 1usize), ("uniform-fine", 2)] {
|
||
for &n in &ns {
|
||
let r = run_uniform(n_of * n, (0.25, 0.75), None, exact, source);
|
||
let e = r.err;
|
||
eprintln!(
|
||
"{label} n {}: {} cells, {} it; patch-region L2 {:.3e} Linf {:.3e} | outside L2 {:.3e} Linf {:.3e} | all L2 {:.3e} | {:.2}+{:.2} s",
|
||
n_of * n,
|
||
r.cells,
|
||
r.iterations,
|
||
e[0].l2(),
|
||
e[0].max,
|
||
e[1].l2(),
|
||
e[1].max,
|
||
e[3].l2(),
|
||
r.setup_s,
|
||
r.solve_s
|
||
);
|
||
if let Some(f) = csv.as_mut() {
|
||
writeln!(
|
||
f,
|
||
"{label},{},{},{},0,nan,nan,{:.6e},{:.6e},{:.6e},{:.6e},{:.6e},{:.6e},{:.3},{:.3}",
|
||
n_of * n,
|
||
r.cells,
|
||
r.iterations,
|
||
e[0].l2(),
|
||
e[0].max,
|
||
e[1].l2(),
|
||
e[1].max,
|
||
e[3].l2(),
|
||
e[3].max,
|
||
r.setup_s,
|
||
r.solve_s
|
||
)
|
||
.ok();
|
||
}
|
||
}
|
||
}
|
||
eprintln!("orders (16→32, 32→64): iface L2 / Linf | patch L2 | coarse L2 | all L2 / Linf");
|
||
for (label, rows) in &table {
|
||
let o = |c: usize| (order(rows[0][c], rows[1][c]), order(rows[1][c], rows[2][c]));
|
||
eprintln!(
|
||
"{label}: iface {:.2},{:.2} / {:.2},{:.2} | patch {:.2},{:.2} | coarse {:.2},{:.2} | all {:.2},{:.2} / {:.2},{:.2}",
|
||
o(0).0,
|
||
o(0).1,
|
||
o(1).0,
|
||
o(1).1,
|
||
o(2).0,
|
||
o(2).1,
|
||
o(4).0,
|
||
o(4).1,
|
||
o(6).0,
|
||
o(6).1,
|
||
o(7).0,
|
||
o(7).1
|
||
);
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
#[ignore = "P2 cut sphere in the patch (minutes)"]
|
||
fn composite_cut_sphere() {
|
||
let mut csv = out_file("p2_sphere.csv");
|
||
if let Some(f) = csv.as_mut() {
|
||
writeln!(
|
||
f,
|
||
"scheme,n,unknowns,iters,l2_iface,linf_iface,l2_patch,linf_patch,l2_coarse,l2_cut,linf_cut,l2_all,linf_all"
|
||
)
|
||
.ok();
|
||
}
|
||
let body = sphere();
|
||
for &n in &[16usize, 32, 64] {
|
||
for iface in [Interface::Direct, Interface::Octree, Interface::Quadratic] {
|
||
let r = run_composite(
|
||
n,
|
||
n / 4,
|
||
3 * n / 4,
|
||
iface,
|
||
Some(body.clone()),
|
||
sph_u,
|
||
sph_f,
|
||
false,
|
||
);
|
||
let e = r.err;
|
||
eprintln!(
|
||
"{iface:?} n {n}: {} unk ({} fine), {} it; iface L2 {:.3e} | patch L2 {:.3e} Linf {:.3e} | coarse L2 {:.3e} | cut L2 {:.3e} Linf {:.3e} | all L2 {:.3e} Linf {:.3e}",
|
||
r.unknowns,
|
||
r.fine_unknowns,
|
||
r.iterations,
|
||
e[0].l2(),
|
||
e[1].l2(),
|
||
e[1].max,
|
||
e[2].l2(),
|
||
e[3].l2(),
|
||
e[3].max,
|
||
e[4].l2(),
|
||
e[4].max
|
||
);
|
||
if let Some(f) = csv.as_mut() {
|
||
writeln!(
|
||
f,
|
||
"{iface:?},{n},{},{},{:.6e},{:.6e},{:.6e},{:.6e},{:.6e},{:.6e},{:.6e},{:.6e},{:.6e}",
|
||
r.unknowns,
|
||
r.iterations,
|
||
e[0].l2(),
|
||
e[0].max,
|
||
e[1].l2(),
|
||
e[1].max,
|
||
e[2].l2(),
|
||
e[3].l2(),
|
||
e[3].max,
|
||
e[4].l2(),
|
||
e[4].max
|
||
)
|
||
.ok();
|
||
}
|
||
}
|
||
for (label, m) in [("uniform-coarse", n), ("uniform-fine", 2 * n)] {
|
||
let r = run_uniform(m, (0.25, 0.75), Some(&body), sph_u, sph_f);
|
||
let e = r.err;
|
||
eprintln!(
|
||
"{label} n {m}: {} cells, {} it; patch-region L2 {:.3e} Linf {:.3e} | outside L2 {:.3e} | cut L2 {:.3e} Linf {:.3e} | all L2 {:.3e} Linf {:.3e}",
|
||
r.cells,
|
||
r.iterations,
|
||
e[0].l2(),
|
||
e[0].max,
|
||
e[1].l2(),
|
||
e[2].l2(),
|
||
e[2].max,
|
||
e[3].l2(),
|
||
e[3].max
|
||
);
|
||
if let Some(f) = csv.as_mut() {
|
||
writeln!(
|
||
f,
|
||
"{label},{m},{},{},nan,nan,{:.6e},{:.6e},{:.6e},{:.6e},{:.6e},{:.6e},{:.6e}",
|
||
r.cells,
|
||
r.iterations,
|
||
e[0].l2(),
|
||
e[0].max,
|
||
e[1].l2(),
|
||
e[2].l2(),
|
||
e[2].max,
|
||
e[3].l2(),
|
||
e[3].max
|
||
)
|
||
.ok();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
#[ignore = "P3 cost against the uniformly fine grid (minutes)"]
|
||
fn composite_cost() {
|
||
let mut csv = out_file("p3_cost.csv");
|
||
if let Some(f) = csv.as_mut() {
|
||
writeln!(
|
||
f,
|
||
"field,case,n,patch,unknowns,iters,setup_s,solve_s,l2_patch_region,linf_patch_region,l2_all"
|
||
)
|
||
.ok();
|
||
}
|
||
let n = 64usize;
|
||
let h = 1.0 / n as f64;
|
||
type Pair = (
|
||
&'static str,
|
||
fn(f64, f64, f64) -> f64,
|
||
fn(f64, f64, f64) -> f64,
|
||
);
|
||
let fields: [Pair; 2] = [("smooth", mms_u, mms_f), ("bump", bump_u, bump_f)];
|
||
for (field, exact, source) in fields {
|
||
for (label, lo, hi) in [("half", 16usize, 48usize), ("quarter", 24, 40)] {
|
||
let r = run_composite(n, lo, hi, Interface::Quadratic, None, exact, source, false);
|
||
// The patch region's error: interface fine cells + patch interior.
|
||
let mut e = Err::default();
|
||
let c = {
|
||
let g = Grid::cubic(n, n, n, h);
|
||
let spec = CompositeSpec {
|
||
coarse: g,
|
||
lo: [lo; 3],
|
||
hi: [hi; 3],
|
||
interface: Interface::Quadratic,
|
||
body: None,
|
||
source: &source,
|
||
dirichlet: &exact,
|
||
};
|
||
Composite::build(&spec)
|
||
};
|
||
let mut x = vec![0.0; c.unknowns()];
|
||
let _ = solve_bicgstab(&c, &mut x, 1e-11, 400, 2);
|
||
for u in c.n_coarse..c.unknowns() {
|
||
let p = c.centre[u];
|
||
e.add(x[u] - exact(p[0], p[1], p[2]), c.volume[u]);
|
||
}
|
||
eprintln!(
|
||
"{field} composite {label} (n {n}, patch {lo}..{hi}): {} unknowns, {} it, setup {:.2} s, solve {:.2} s, patch L2 {:.3e} Linf {:.3e}, all L2 {:.3e}",
|
||
r.unknowns,
|
||
r.iterations,
|
||
r.setup_s,
|
||
r.solve_s,
|
||
e.l2(),
|
||
e.max,
|
||
r.err[4].l2()
|
||
);
|
||
if let Some(f) = csv.as_mut() {
|
||
writeln!(
|
||
f,
|
||
"{field},composite-{label},{n},{lo}..{hi},{},{},{:.3},{:.3},{:.6e},{:.6e},{:.6e}",
|
||
r.unknowns,
|
||
r.iterations,
|
||
r.setup_s,
|
||
r.solve_s,
|
||
e.l2(),
|
||
e.max,
|
||
r.err[4].l2()
|
||
)
|
||
.ok();
|
||
}
|
||
for (ulabel, m) in [("uniform-fine", 2 * n), ("uniform-coarse", n)] {
|
||
let r = run_uniform(m, (lo as f64 * h, hi as f64 * h), None, exact, source);
|
||
eprintln!(
|
||
"{field} {ulabel} n {m} (region {lo}..{hi}): {} cells, {} it, setup {:.2} s, solve {:.2} s, region L2 {:.3e} Linf {:.3e}, all L2 {:.3e}",
|
||
r.cells,
|
||
r.iterations,
|
||
r.setup_s,
|
||
r.solve_s,
|
||
r.err[0].l2(),
|
||
r.err[0].max,
|
||
r.err[3].l2()
|
||
);
|
||
if let Some(f) = csv.as_mut() {
|
||
writeln!(
|
||
f,
|
||
"{field},{ulabel},{m},{lo}..{hi},{},{},{:.3},{:.3},{:.6e},{:.6e},{:.6e}",
|
||
r.cells,
|
||
r.iterations,
|
||
r.setup_s,
|
||
r.solve_s,
|
||
r.err[0].l2(),
|
||
r.err[0].max,
|
||
r.err[3].l2()
|
||
)
|
||
.ok();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
fn quad_u(x: f64, y: f64, z: f64) -> f64 {
|
||
x * x + 2.0 * y * y - 1.5 * z * z + 0.7 * x * y - 0.4 * y * z + 0.9 * x * z + 0.3 * x
|
||
}
|
||
|
||
fn quad_f(_x: f64, _y: f64, _z: f64) -> f64 {
|
||
-(2.0 + 4.0 - 3.0)
|
||
}
|
||
|
||
/// Local consistency: the exact quadratic's residual on the rows away from
|
||
/// the outer boundary, per interface flux (the Quadratic ghost is exact for
|
||
/// quadratics, so its interface rows must be at round-off).
|
||
#[test]
|
||
fn composite_quadratic_consistency() {
|
||
let n = 12;
|
||
let g = Grid::cubic(n, n, n, 1.0 / n as f64);
|
||
for iface in [Interface::Direct, Interface::Octree, Interface::Quadratic] {
|
||
let spec = CompositeSpec {
|
||
coarse: g,
|
||
lo: [3; 3],
|
||
hi: [9; 3],
|
||
interface: iface,
|
||
body: None,
|
||
source: &quad_f,
|
||
dirichlet: &quad_u,
|
||
};
|
||
let c = Composite::build(&spec);
|
||
let u: Vec<f64> = c.centre.iter().map(|p| quad_u(p[0], p[1], p[2])).collect();
|
||
let mut au = vec![0.0; u.len()];
|
||
c.a.apply(&u, &mut au);
|
||
let h = g.dx;
|
||
let (mut m_if, mut m_in) = (0.0f64, 0.0f64);
|
||
for r in 0..u.len() {
|
||
let p = c.centre[r];
|
||
if p.iter().any(|&x| x < h || x > 1.0 - h) {
|
||
continue;
|
||
}
|
||
// Relative to the row's volume source |f| V.
|
||
let res = (c.rhs[r] - au[r]).abs() / (3.0 * c.volume[r]);
|
||
if c.at_interface[r] {
|
||
m_if = m_if.max(res);
|
||
} else {
|
||
m_in = m_in.max(res);
|
||
}
|
||
}
|
||
eprintln!("{iface:?}: max |truncation| / |f V|: interface {m_if:.3e}, interior {m_in:.3e}");
|
||
assert!(m_in < 1e-10);
|
||
if iface == Interface::Quadratic {
|
||
assert!(m_if < 1e-10, "quadratic ghost not exact on a quadratic");
|
||
}
|
||
}
|
||
}
|
||
|
||
/// P2, direct: the composite solution on the patch's fine cells against
|
||
/// the uniformly fine solution on the SAME cells with the same cut (the
|
||
/// fine cut geometry of the patch vs the uniform grid's, compared too).
|
||
#[test]
|
||
#[ignore = "P2 composite vs uniform fine on the patch cells (minutes)"]
|
||
fn composite_sphere_vs_fine() {
|
||
let mut csv = out_file("p2_vs_fine.csv");
|
||
if let Some(f) = csv.as_mut() {
|
||
writeln!(
|
||
f,
|
||
"scheme,n,max_dfrac,l2_diff_patch,linf_diff_patch,l2_diff_cut,linf_diff_cut,l2_err_fine_patch,linf_err_fine_patch"
|
||
)
|
||
.ok();
|
||
}
|
||
let body = sphere();
|
||
for &n in &[16usize, 32, 64] {
|
||
let m = 2 * n;
|
||
let gf = Grid::cubic(m, m, m, 1.0 / m as f64);
|
||
let (prob, frac) = uniform_problem(gf, Some(&body), &sph_f, &sph_u);
|
||
let mut pf = vec![0.0; gf.cells()];
|
||
let l1: f64 = prob.rhs.iter().map(|v| v.abs()).sum();
|
||
let params = MultigridParameters {
|
||
max_iterations: 2000,
|
||
..MultigridParameters::default()
|
||
};
|
||
assert!(solve_pcg(&prob, &mut pf, ¶ms, 1e-12 * l1, None).converged);
|
||
for iface in [Interface::Octree, Interface::Quadratic] {
|
||
let g = Grid::cubic(n, n, n, 1.0 / n as f64);
|
||
let spec = CompositeSpec {
|
||
coarse: g,
|
||
lo: [n / 4; 3],
|
||
hi: [3 * n / 4; 3],
|
||
interface: iface,
|
||
body: Some(body.clone()),
|
||
source: &sph_f,
|
||
dirichlet: &sph_u,
|
||
};
|
||
let c = Composite::build(&spec);
|
||
let mut x = vec![0.0; c.unknowns()];
|
||
assert!(solve_bicgstab(&c, &mut x, 1e-12, 400, 2).converged);
|
||
let (mut d_all, mut d_cut, mut e_fine) =
|
||
(Err::default(), Err::default(), Err::default());
|
||
let mut max_dfrac: f64 = 0.0;
|
||
let off = n / 2; // the patch's first fine index on the uniform fine grid
|
||
for k in 0..c.fine.nz {
|
||
for j in 0..c.fine.ny {
|
||
for i in 0..c.fine.nx {
|
||
let u = c.fine_id[c.fine.cell(k, j, i)];
|
||
let idx = gf.cell(k + off, j + off, i + off);
|
||
if u == usize::MAX {
|
||
assert!(!prob.active[idx] || frac[idx] < 1e-12, "activity differs");
|
||
continue;
|
||
}
|
||
let hf = c.fine.dx;
|
||
let fr = c.volume[u] / (hf * hf * hf);
|
||
max_dfrac = max_dfrac.max((fr - frac[idx]).abs());
|
||
let d = x[u] - pf[idx];
|
||
d_all.add(d, c.volume[u]);
|
||
if c.cut[u] {
|
||
d_cut.add(d, c.volume[u]);
|
||
}
|
||
let p = c.centre[u];
|
||
e_fine.add(pf[idx] - sph_u(p[0], p[1], p[2]), c.volume[u]);
|
||
}
|
||
}
|
||
}
|
||
eprintln!(
|
||
"{iface:?} n {n} vs uniform {m}: max |Δfrac| {max_dfrac:.1e}; composite − fine on the patch L2 {:.3e} Linf {:.3e}; on cut cells ({}) L2 {:.3e} Linf {:.3e}; the fine grid's own error there L2 {:.3e} Linf {:.3e}",
|
||
d_all.l2(),
|
||
d_all.max,
|
||
d_cut.count,
|
||
d_cut.l2(),
|
||
d_cut.max,
|
||
e_fine.l2(),
|
||
e_fine.max
|
||
);
|
||
if let Some(f) = csv.as_mut() {
|
||
writeln!(
|
||
f,
|
||
"{iface:?},{n},{max_dfrac:.3e},{:.6e},{:.6e},{:.6e},{:.6e},{:.6e},{:.6e}",
|
||
d_all.l2(),
|
||
d_all.max,
|
||
d_cut.l2(),
|
||
d_cut.max,
|
||
e_fine.l2(),
|
||
e_fine.max
|
||
)
|
||
.ok();
|
||
}
|
||
}
|
||
}
|
||
}
|