rtx-cfd: cylinder_flag_patch_deformed_from — the warm-started regeneration (P5's cost lever): the previous patch's interior and its outer ring projected onto the new offset polygon as the start, and an IDEMPOTENT build (one Winslow sweep alternating with the ray re-spacing; the P4-0 one-shot is not a fixed point — measured as the interior moving 0.05 h per build at rest), so a base converged as that map's fixed point stays put (9e-11 h at rest) and each build tracks the outline (interior/wall motion 0.99–1.14, worst angle 74.7°, wall row 0.37 h) 4.5× faster than the cold build; the rigid generator untouched (pins pass); fsi2 overset harness: RTX_FSI2O_WARM_SWEEPS with the converged base as the initial patch and the committed step's mesh as each step's warm start
Documentation / Build User Guide (push) Canceled after 0s
CI / Test (ubuntu-latest) (push) Canceled after 0s
CI / Build CPU-Only (Explicit) (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
Documentation / Build API Documentation (push) Canceled after 0s
CI / CI Success (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 / Distributed Training Tests (push) Canceled after 0s
Performance Benchmarks / Run Benchmarks (push) Canceled after 0s
Documentation / Build User Guide (push) Canceled after 0s
CI / Test (ubuntu-latest) (push) Canceled after 0s
CI / Build CPU-Only (Explicit) (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
Documentation / Build API Documentation (push) Canceled after 0s
CI / CI Success (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 / Distributed Training Tests (push) Canceled after 0s
Performance Benchmarks / Run Benchmarks (push) Canceled after 0s
Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_0116sg1Qz1gMv9hdcKP1XUam
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
62f1d4bf9b
commit
3a68048934
@@ -589,7 +589,16 @@ pub fn cylinder_flag_patch(
|
|||||||
// 1.74 → 1.31 with fillet = h/2 on the ny = 41/62/82 ladder).
|
// 1.74 → 1.31 with fillet = h/2 on the ny = 41/62/82 ladder).
|
||||||
let inner = cylinder_flag_outline(centre, r, t, x_tip, fillet, 3, 16, h, 1.15);
|
let inner = cylinder_flag_outline(centre, r, t, x_tip, fillet, 3, 16, h, 1.15);
|
||||||
let hull_src = hull_source(centre, r, [x_tip - t, centre[1]], t, [1.0, 0.0]);
|
let hull_src = hull_source(centre, r, [x_tip - t, centre[1]], t, [1.0, 0.0]);
|
||||||
o_grid_from_outline(inner, hull_src, h, offset, nn, stretch, winslow_sweeps)
|
o_grid_from_outline(
|
||||||
|
inner,
|
||||||
|
hull_src,
|
||||||
|
h,
|
||||||
|
offset,
|
||||||
|
nn,
|
||||||
|
stretch,
|
||||||
|
winslow_sweeps,
|
||||||
|
None,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The FEA flag's wetted edges, deformed: `bottom` from the root to the
|
/// The FEA flag's wetted edges, deformed: `bottom` from the root to the
|
||||||
@@ -622,11 +631,62 @@ pub fn cylinder_flag_patch_deformed(
|
|||||||
nn: usize,
|
nn: usize,
|
||||||
stretch: f64,
|
stretch: f64,
|
||||||
winslow_sweeps: usize,
|
winslow_sweeps: usize,
|
||||||
|
) -> CfdResult<(PatchMesh, (usize, f64))> {
|
||||||
|
cylinder_flag_patch_deformed_from(
|
||||||
|
None,
|
||||||
|
centre,
|
||||||
|
r,
|
||||||
|
t,
|
||||||
|
edges,
|
||||||
|
x_tip_ref,
|
||||||
|
h,
|
||||||
|
fillet,
|
||||||
|
offset,
|
||||||
|
nn,
|
||||||
|
stretch,
|
||||||
|
winslow_sweeps,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// [`cylinder_flag_patch_deformed`] warm-started from `prev` (the same
|
||||||
|
/// topology: its interior rows are the Winslow start instead of the
|
||||||
|
/// transfinite one, so a few sweeps suffice when the outline moved a
|
||||||
|
/// little — P5's per-pass regeneration). `None` is the cold build.
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
pub fn cylinder_flag_patch_deformed_from(
|
||||||
|
prev: Option<&PatchMesh>,
|
||||||
|
centre: [f64; 2],
|
||||||
|
r: f64,
|
||||||
|
t: f64,
|
||||||
|
edges: &FlagEdges,
|
||||||
|
x_tip_ref: f64,
|
||||||
|
h: f64,
|
||||||
|
fillet: f64,
|
||||||
|
offset: f64,
|
||||||
|
nn: usize,
|
||||||
|
stretch: f64,
|
||||||
|
winslow_sweeps: usize,
|
||||||
) -> CfdResult<(PatchMesh, (usize, f64))> {
|
) -> CfdResult<(PatchMesh, (usize, f64))> {
|
||||||
let (inner, tip) =
|
let (inner, tip) =
|
||||||
cylinder_flag_outline_deformed(centre, r, t, edges, x_tip_ref, fillet, 3, 16, h, 1.15);
|
cylinder_flag_outline_deformed(centre, r, t, edges, x_tip_ref, fillet, 3, 16, h, 1.15);
|
||||||
let hull_src = hull_source(centre, r, tip.centre, tip.radius, tip.axis);
|
let hull_src = hull_source(centre, r, tip.centre, tip.radius, tip.axis);
|
||||||
o_grid_from_outline(inner, hull_src, h, offset, nn, stretch, winslow_sweeps)
|
let start = prev.and_then(|m| {
|
||||||
|
(m.ns() == inner.len() && m.nn() == nn).then(|| {
|
||||||
|
(0..=nn)
|
||||||
|
.map(|k| (0..=m.ns()).map(|i| m.node_xy(m.node(k, i))).collect())
|
||||||
|
.collect::<Vec<Vec<[f64; 2]>>>()
|
||||||
|
})
|
||||||
|
});
|
||||||
|
o_grid_from_outline(
|
||||||
|
inner,
|
||||||
|
hull_src,
|
||||||
|
h,
|
||||||
|
offset,
|
||||||
|
nn,
|
||||||
|
stretch,
|
||||||
|
winslow_sweeps,
|
||||||
|
start,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The deformed flag's rounded tip: the semicircle's centre, radius and
|
/// The deformed flag's rounded tip: the semicircle's centre, radius and
|
||||||
@@ -857,6 +917,7 @@ fn hull_source(
|
|||||||
/// The O-grid body shared by the rigid and the deformed generators: the
|
/// The O-grid body shared by the rigid and the deformed generators: the
|
||||||
/// inner ring (counter-clockwise in, reversed to clockwise), the outer
|
/// inner ring (counter-clockwise in, reversed to clockwise), the outer
|
||||||
/// ring on the hull offset, transfinite start, Winslow, re-spacing.
|
/// ring on the hull offset, transfinite start, Winslow, re-spacing.
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn o_grid_from_outline(
|
fn o_grid_from_outline(
|
||||||
mut inner: Vec<[f64; 2]>,
|
mut inner: Vec<[f64; 2]>,
|
||||||
hull_src: Vec<[f64; 2]>,
|
hull_src: Vec<[f64; 2]>,
|
||||||
@@ -865,6 +926,7 @@ fn o_grid_from_outline(
|
|||||||
nn: usize,
|
nn: usize,
|
||||||
stretch: f64,
|
stretch: f64,
|
||||||
winslow_sweeps: usize,
|
winslow_sweeps: usize,
|
||||||
|
start: Option<Vec<Vec<[f64; 2]>>>,
|
||||||
) -> CfdResult<(PatchMesh, (usize, f64))> {
|
) -> CfdResult<(PatchMesh, (usize, f64))> {
|
||||||
inner.reverse(); // clockwise
|
inner.reverse(); // clockwise
|
||||||
let ns = inner.len();
|
let ns = inner.len();
|
||||||
@@ -892,9 +954,25 @@ fn o_grid_from_outline(
|
|||||||
.collect();
|
.collect();
|
||||||
let mut inner_closed = inner.clone();
|
let mut inner_closed = inner.clone();
|
||||||
inner_closed.push(inner[0]);
|
inner_closed.push(inner[0]);
|
||||||
// Transfinite start.
|
// Transfinite start, or the previous mesh's interior with the new
|
||||||
|
// inner and outer rings (the warm start).
|
||||||
let eta = stretched_fractions(nn, stretch);
|
let eta = stretched_fractions(nn, stretch);
|
||||||
let mut x: Vec<Vec<[f64; 2]>> = eta
|
let warm = start.is_some();
|
||||||
|
let mut x: Vec<Vec<[f64; 2]>> = match start {
|
||||||
|
Some(mut rows) => {
|
||||||
|
rows[0] = inner_closed.clone();
|
||||||
|
// The outer ring warm-starts too: the previous ring's nodes
|
||||||
|
// projected onto the new offset polygon keep the sliding
|
||||||
|
// rows' converged distribution (the crude normal push would
|
||||||
|
// undo it every build — measured as the interior moving
|
||||||
|
// 0.05 h with the wall at rest).
|
||||||
|
rows[nn] = rows[nn]
|
||||||
|
.iter()
|
||||||
|
.map(|&p| nearest_on_polyline(&outer_poly, p))
|
||||||
|
.collect();
|
||||||
|
rows
|
||||||
|
}
|
||||||
|
None => eta
|
||||||
.iter()
|
.iter()
|
||||||
.map(|&e| {
|
.map(|&e| {
|
||||||
(0..=ns)
|
(0..=ns)
|
||||||
@@ -906,9 +984,38 @@ fn o_grid_from_outline(
|
|||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
})
|
})
|
||||||
.collect();
|
.collect(),
|
||||||
|
};
|
||||||
|
// The cold build: Winslow, then the ray re-spacing (the P4-0 one-shot).
|
||||||
|
// The warm build must be IDEMPOTENT at rest, and the one-shot is not
|
||||||
|
// (the re-spaced mesh is not the smoother's fixed point: measured as
|
||||||
|
// the interior moving 0.05 h per build with the wall at rest), so it
|
||||||
|
// alternates one sweep with a re-spacing — the base converged as a
|
||||||
|
// fixed point of that map stays put, and a moving outline is tracked.
|
||||||
|
let report = if warm {
|
||||||
|
let mut last = f64::INFINITY;
|
||||||
|
let mut done = 0;
|
||||||
|
for k in 0..winslow_sweeps {
|
||||||
|
let before = x.clone();
|
||||||
|
winslow_smooth(&mut x, ns, 0.0, 1, Some(&outer_poly));
|
||||||
|
respace_rays(&mut x, ns, &eta);
|
||||||
|
last = x
|
||||||
|
.iter()
|
||||||
|
.zip(&before)
|
||||||
|
.flat_map(|(r, b)| r.iter().zip(b))
|
||||||
|
.map(|(p, q)| ((p[0] - q[0]).powi(2) + (p[1] - q[1]).powi(2)).sqrt())
|
||||||
|
.fold(0.0, f64::max);
|
||||||
|
done = k + 1;
|
||||||
|
if last < 1e-10 * h {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(done, last)
|
||||||
|
} else {
|
||||||
let report = winslow_smooth(&mut x, ns, 1e-10 * h, winslow_sweeps, Some(&outer_poly));
|
let report = winslow_smooth(&mut x, ns, 1e-10 * h, winslow_sweeps, Some(&outer_poly));
|
||||||
respace_rays(&mut x, ns, &eta);
|
respace_rays(&mut x, ns, &eta);
|
||||||
|
report
|
||||||
|
};
|
||||||
let mut xs = Vec::with_capacity((nn + 1) * (ns + 1));
|
let mut xs = Vec::with_capacity((nn + 1) * (ns + 1));
|
||||||
let mut ys = Vec::with_capacity(xs.capacity());
|
let mut ys = Vec::with_capacity(xs.capacity());
|
||||||
for row in &x {
|
for row in &x {
|
||||||
|
|||||||
@@ -417,3 +417,130 @@ fn dumped_edges_reproduce_the_classification() -> CfdResult<()> {
|
|||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The warm-started regeneration (P5's cost lever): along a sequence of
|
||||||
|
/// small bends, each patch built from the previous one's interior with a
|
||||||
|
/// few sweeps must pass the mesh gates, move its interior no more than a
|
||||||
|
/// few times the outline's motion (continuity — the ALE mesh velocity),
|
||||||
|
/// and stay within a cell of the cold build (neither is the smoother's
|
||||||
|
/// fixed point: the cold 500-sweep build still moves 1e-5 per sweep, so
|
||||||
|
/// the two drift apart by the smoother's own path, recorded not gated).
|
||||||
|
#[test]
|
||||||
|
fn warm_started_regeneration_tracks_the_cold_build() -> CfdResult<()> {
|
||||||
|
use rtx_cfd::mesh::patch_gen::cylinder_flag_patch_deformed_from;
|
||||||
|
let h = H / 41.0;
|
||||||
|
// The base: the smoother converged once (5 000 sweeps at rest), so the
|
||||||
|
// chain starts at its fixed point and each build tracks the outline
|
||||||
|
// only — from a 100-sweep base the first builds moved the interior
|
||||||
|
// 6.7× the wall (the smoother's own drift, 1e-2 h per 20 sweeps).
|
||||||
|
let t_base = std::time::Instant::now();
|
||||||
|
let (cold0, _) = deformed(0.0, h)?;
|
||||||
|
let (base, (bs, bl)) = cylinder_flag_patch_deformed_from(
|
||||||
|
Some(&cold0),
|
||||||
|
CENTRE,
|
||||||
|
R,
|
||||||
|
T,
|
||||||
|
&edges(0.0, 35, 2),
|
||||||
|
X1,
|
||||||
|
h,
|
||||||
|
FILLET,
|
||||||
|
6.0 * h,
|
||||||
|
12,
|
||||||
|
4.0,
|
||||||
|
5000,
|
||||||
|
)?;
|
||||||
|
println!(
|
||||||
|
" base: {bs} sweeps (last move {bl:.1e}) in {:.2} s",
|
||||||
|
t_base.elapsed().as_secs_f64()
|
||||||
|
);
|
||||||
|
let mut prev: Option<PatchMesh> = Some(base);
|
||||||
|
let (mut worst_gap, mut worst_ratio, mut t_warm, mut t_cold) = (0.0_f64, 0.0_f64, 0.0, 0.0);
|
||||||
|
for k in 0..=20 {
|
||||||
|
// A ramp to 40 mm in steps of 2 mm, then back — the flap's scale.
|
||||||
|
let a = 0.002 * if k <= 10 { k as f64 } else { (20 - k) as f64 };
|
||||||
|
let e = edges(a, 35, 2);
|
||||||
|
let t0 = std::time::Instant::now();
|
||||||
|
let (cold, _) =
|
||||||
|
cylinder_flag_patch_deformed(CENTRE, R, T, &e, X1, h, FILLET, 6.0 * h, 12, 4.0, 100)?;
|
||||||
|
t_cold += t0.elapsed().as_secs_f64();
|
||||||
|
let t1 = std::time::Instant::now();
|
||||||
|
let (warm, (sweeps, last)) = cylinder_flag_patch_deformed_from(
|
||||||
|
prev.as_ref(),
|
||||||
|
CENTRE,
|
||||||
|
R,
|
||||||
|
T,
|
||||||
|
&e,
|
||||||
|
X1,
|
||||||
|
h,
|
||||||
|
FILLET,
|
||||||
|
6.0 * h,
|
||||||
|
12,
|
||||||
|
4.0,
|
||||||
|
20,
|
||||||
|
)?;
|
||||||
|
t_warm += t1.elapsed().as_secs_f64();
|
||||||
|
warm.validate(80.0)
|
||||||
|
.unwrap_or_else(|m| panic!("warm k = {k}: {m}"));
|
||||||
|
let (min_s, wall, worst) = quality(&warm);
|
||||||
|
assert!(
|
||||||
|
min_s > 0.19 * h && wall < 0.5 * h && worst < 80.0,
|
||||||
|
"k = {k}: quality"
|
||||||
|
);
|
||||||
|
let mut gap = 0.0_f64;
|
||||||
|
for c in 0..cold.cell_count() {
|
||||||
|
let (p, q) = (cold.centre(c), warm.centre(c));
|
||||||
|
gap = gap.max(((p[0] - q[0]).powi(2) + (p[1] - q[1]).powi(2)).sqrt());
|
||||||
|
}
|
||||||
|
worst_gap = worst_gap.max(gap);
|
||||||
|
// Continuity: the interior's motion against the wall's.
|
||||||
|
if let Some(pm) = &prev {
|
||||||
|
let (mut wall_move, mut interior_move) = (0.0_f64, 0.0_f64);
|
||||||
|
for i in 0..=warm.ns() {
|
||||||
|
let (p, q) = (pm.node_xy(pm.node(0, i)), warm.node_xy(warm.node(0, i)));
|
||||||
|
wall_move = wall_move.max(((p[0] - q[0]).powi(2) + (p[1] - q[1]).powi(2)).sqrt());
|
||||||
|
for kk in 1..warm.nn() {
|
||||||
|
let (p, q) = (pm.node_xy(pm.node(kk, i)), warm.node_xy(warm.node(kk, i)));
|
||||||
|
interior_move =
|
||||||
|
interior_move.max(((p[0] - q[0]).powi(2) + (p[1] - q[1]).powi(2)).sqrt());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// With the wall at rest the interior must not move either
|
||||||
|
// (gated in cells); the ratio is read only when the wall moves.
|
||||||
|
let ratio = if wall_move > 1e-6 * h {
|
||||||
|
interior_move / wall_move
|
||||||
|
} else {
|
||||||
|
assert!(
|
||||||
|
interior_move < 1e-3 * h,
|
||||||
|
"k = {k}: the wall is at rest, the interior moved {:.2e} h",
|
||||||
|
interior_move / h
|
||||||
|
);
|
||||||
|
0.0
|
||||||
|
};
|
||||||
|
worst_ratio = worst_ratio.max(ratio);
|
||||||
|
if k % 5 == 0 {
|
||||||
|
println!(
|
||||||
|
" k = {k:2} a = {a:+.3}: warm {sweeps} sweeps (last move {last:.1e}): wall moved {:.2e} h, interior {:.2e} h ({ratio:.2}×); gap to cold-100 {:.2e} h; quality {:.3} h / {:.3} h / {worst:.1}°",
|
||||||
|
wall_move / h,
|
||||||
|
interior_move / h,
|
||||||
|
gap / h,
|
||||||
|
min_s / h,
|
||||||
|
wall / h
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
prev = Some(warm);
|
||||||
|
}
|
||||||
|
println!(
|
||||||
|
" warm-started regeneration: worst gap to cold-100 {:.2e} h, worst interior/wall motion ratio {worst_ratio:.2}, {:.3} s warm vs {:.3} s cold-100 ({:.1}×) over 21 builds",
|
||||||
|
worst_gap / h,
|
||||||
|
t_warm,
|
||||||
|
t_cold,
|
||||||
|
t_cold / t_warm.max(1e-9)
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
worst_ratio < 3.0,
|
||||||
|
"interior moves {worst_ratio:.2}× the wall"
|
||||||
|
);
|
||||||
|
// The gap to the cold-100 build is the smoother's path, recorded.
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|||||||
@@ -135,6 +135,10 @@ pub struct OversetFluid {
|
|||||||
pub correctors_total: Cell<usize>,
|
pub correctors_total: Cell<usize>,
|
||||||
/// The interface of the last `set_geometry`.
|
/// The interface of the last `set_geometry`.
|
||||||
pub last_d: Vec<f64>,
|
pub last_d: Vec<f64>,
|
||||||
|
/// The committed step's patch, the warm start of every regeneration
|
||||||
|
/// in the next step (`RTX_FSI2O_WARM_SWEEPS`); `None` = cold builds.
|
||||||
|
pub warm_base: Option<rtx_cfd::mesh::PatchMesh>,
|
||||||
|
pub warm_sweeps: usize,
|
||||||
/// The last fluid step's mass defects (patch acceptor ring, background
|
/// The last fluid step's mass defects (patch acceptor ring, background
|
||||||
/// fringe) and the patch's max divergence.
|
/// fringe) and the patch's max divergence.
|
||||||
pub last_defects: Cell<(f64, f64, f64)>,
|
pub last_defects: Cell<(f64, f64, f64)>,
|
||||||
@@ -196,7 +200,7 @@ impl OversetFluid {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let (patch_mesh, _) = cylinder_flag_patch_deformed(
|
let (cold_mesh, _) = cylinder_flag_patch_deformed(
|
||||||
CYL_CENTRE,
|
CYL_CENTRE,
|
||||||
CYL_R,
|
CYL_R,
|
||||||
FLAG_T,
|
FLAG_T,
|
||||||
@@ -209,6 +213,37 @@ impl OversetFluid {
|
|||||||
PATCH_STRETCH,
|
PATCH_STRETCH,
|
||||||
sweeps,
|
sweeps,
|
||||||
)?;
|
)?;
|
||||||
|
// With the warm-started regeneration (`RTX_FSI2O_WARM_SWEEPS`), the
|
||||||
|
// initial patch is the converged fixed point of the warm build's
|
||||||
|
// sweep + re-spacing map, so the chain starts where it stays.
|
||||||
|
let warm_sweeps: usize = std::env::var("RTX_FSI2O_WARM_SWEEPS")
|
||||||
|
.ok()
|
||||||
|
.and_then(|v| v.parse().ok())
|
||||||
|
.unwrap_or(0);
|
||||||
|
let patch_mesh = if warm_sweeps > 0 {
|
||||||
|
let t0 = std::time::Instant::now();
|
||||||
|
let (m, (it, last)) = rtx_cfd::mesh::patch_gen::cylinder_flag_patch_deformed_from(
|
||||||
|
Some(&cold_mesh),
|
||||||
|
CYL_CENTRE,
|
||||||
|
CYL_R,
|
||||||
|
FLAG_T,
|
||||||
|
&interface.edges(&zero_d),
|
||||||
|
FLAG_X1,
|
||||||
|
h,
|
||||||
|
FILLET,
|
||||||
|
6.0 * h,
|
||||||
|
PATCH_ROWS,
|
||||||
|
PATCH_STRETCH,
|
||||||
|
20_000,
|
||||||
|
)?;
|
||||||
|
println!(
|
||||||
|
" warm base: {it} sweep+respace iterations (last move {last:.1e}) in {:.1} s; {warm_sweeps} per regeneration",
|
||||||
|
t0.elapsed().as_secs_f64()
|
||||||
|
);
|
||||||
|
m
|
||||||
|
} else {
|
||||||
|
cold_mesh
|
||||||
|
};
|
||||||
// The patch's along-body explicit limit (its wall row is
|
// The patch's along-body explicit limit (its wall row is
|
||||||
// line-implicit) beside the background's combined criterion.
|
// line-implicit) beside the background's combined criterion.
|
||||||
let mut hs = f64::INFINITY;
|
let mut hs = f64::INFINITY;
|
||||||
@@ -290,6 +325,8 @@ impl OversetFluid {
|
|||||||
correctors_total: Cell::new(0),
|
correctors_total: Cell::new(0),
|
||||||
last_d: zero_d,
|
last_d: zero_d,
|
||||||
last_defects: Cell::new((0.0, 0.0, 0.0)),
|
last_defects: Cell::new((0.0, 0.0, 0.0)),
|
||||||
|
warm_base: None,
|
||||||
|
warm_sweeps,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -359,7 +396,12 @@ impl OversetFluid {
|
|||||||
/// The patch around the interface `d`.
|
/// The patch around the interface `d`.
|
||||||
pub fn patch_for(&self, d: &[f64]) -> CfdResult<rtx_cfd::mesh::PatchMesh> {
|
pub fn patch_for(&self, d: &[f64]) -> CfdResult<rtx_cfd::mesh::PatchMesh> {
|
||||||
let start = std::time::Instant::now();
|
let start = std::time::Instant::now();
|
||||||
let (mesh, _) = cylinder_flag_patch_deformed(
|
let (mesh, _) = rtx_cfd::mesh::patch_gen::cylinder_flag_patch_deformed_from(
|
||||||
|
if self.warm_sweeps > 0 {
|
||||||
|
self.warm_base.as_ref()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
CYL_CENTRE,
|
CYL_CENTRE,
|
||||||
CYL_R,
|
CYL_R,
|
||||||
FLAG_T,
|
FLAG_T,
|
||||||
@@ -370,7 +412,11 @@ impl OversetFluid {
|
|||||||
6.0 * self.h,
|
6.0 * self.h,
|
||||||
PATCH_ROWS,
|
PATCH_ROWS,
|
||||||
PATCH_STRETCH,
|
PATCH_STRETCH,
|
||||||
self.sweeps,
|
if self.warm_sweeps > 0 && self.warm_base.is_some() {
|
||||||
|
self.warm_sweeps
|
||||||
|
} else {
|
||||||
|
self.sweeps
|
||||||
|
},
|
||||||
)?;
|
)?;
|
||||||
self.regen_count.set(self.regen_count.get() + 1);
|
self.regen_count.set(self.regen_count.get() + 1);
|
||||||
self.regen_seconds
|
self.regen_seconds
|
||||||
@@ -576,14 +622,13 @@ impl OversetFluid {
|
|||||||
/// change at every face.
|
/// change at every face.
|
||||||
pub fn wall_roughness(&self) -> (f64, usize, f64, f64) {
|
pub fn wall_roughness(&self) -> (f64, usize, f64, f64) {
|
||||||
let mut tn: Vec<f64> = Vec::new();
|
let mut tn: Vec<f64> = Vec::new();
|
||||||
for (centre, normal, _, traction) in
|
for (centre, normal, _, traction) in self.solver.patch().wall_tractions(
|
||||||
self.solver
|
&self.field.patch,
|
||||||
.patch()
|
PatchSide::Inner,
|
||||||
.wall_tractions(&self.field.patch, PatchSide::Inner, self.solver.time())
|
self.solver.time(),
|
||||||
{
|
) {
|
||||||
let on_cylinder = ((centre[0] - CYL_CENTRE[0]).powi(2)
|
let on_cylinder =
|
||||||
+ (centre[1] - CYL_CENTRE[1]).powi(2))
|
((centre[0] - CYL_CENTRE[0]).powi(2) + (centre[1] - CYL_CENTRE[1]).powi(2)).sqrt()
|
||||||
.sqrt()
|
|
||||||
< CYL_R + 1e-9;
|
< CYL_R + 1e-9;
|
||||||
if !on_cylinder {
|
if !on_cylinder {
|
||||||
tn.push(traction[0] * normal[0] + traction[1] * normal[1]);
|
tn.push(traction[0] * normal[0] + traction[1] * normal[1]);
|
||||||
@@ -602,7 +647,12 @@ impl OversetFluid {
|
|||||||
prev_diff = d;
|
prev_diff = d;
|
||||||
}
|
}
|
||||||
let max = tn.iter().fold(0.0_f64, |m, v| m.max(v.abs()));
|
let max = tn.iter().fold(0.0_f64, |m, v| m.max(v.abs()));
|
||||||
(jumps / total.max(1e-300), flips, max, total / tn.len().max(1) as f64)
|
(
|
||||||
|
jumps / total.max(1e-300),
|
||||||
|
flips,
|
||||||
|
max,
|
||||||
|
total / tn.len().max(1) as f64,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The patch's pressure level (mean over its cells) and the wall's
|
/// The patch's pressure level (mean over its cells) and the wall's
|
||||||
@@ -634,6 +684,14 @@ impl OversetFluid {
|
|||||||
(level, flux, area, poly_flux, poly_area)
|
(level, flux, area, poly_flux, poly_area)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The current patch becomes the warm start of the next step's
|
||||||
|
/// regenerations (called after a committed step).
|
||||||
|
pub fn commit_base(&mut self) {
|
||||||
|
if self.warm_sweeps > 0 {
|
||||||
|
self.warm_base = Some(self.solver.patch().mesh().clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn snapshot(&self) -> (OversetSolverState, OversetField) {
|
pub fn snapshot(&self) -> (OversetSolverState, OversetField) {
|
||||||
(self.solver.snapshot(), self.field.clone())
|
(self.solver.snapshot(), self.field.clone())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -119,6 +119,7 @@ pub fn run_march_overset(case: BenchmarkCase, config: &OversetMarchConfig) -> Ov
|
|||||||
}
|
}
|
||||||
// The release time is the fluid's clock (the rigid step count rounds).
|
// The release time is the fluid's clock (the rigid step count rounds).
|
||||||
let t_release = fluid.time();
|
let t_release = fluid.time();
|
||||||
|
fluid.commit_base();
|
||||||
let (rigid_drag, rigid_lift) = fluid.measure_force();
|
let (rigid_drag, rigid_lift) = fluid.measure_force();
|
||||||
println!(
|
println!(
|
||||||
" {} OVERSET rigid phase: {rigid_steps} steps (dt {dt_fluid:.3e}) to t = {:.2} s in {:.0} s wall; wall drag {rigid_drag:.2} (rigid-flag reference {:.1}; the overset's own CFD2 at ny = 41: 137.8), lift {rigid_lift:.2}; rounds mean {:.2}",
|
" {} OVERSET rigid phase: {rigid_steps} steps (dt {dt_fluid:.3e}) to t = {:.2} s in {:.0} s wall; wall drag {rigid_drag:.2} (rigid-flag reference {:.1}; the overset's own CFD2 at ny = 41: 137.8), lift {rigid_lift:.2}; rounds mean {:.2}",
|
||||||
@@ -286,8 +287,7 @@ pub fn run_march_overset(case: BenchmarkCase, config: &OversetMarchConfig) -> Ov
|
|||||||
}
|
}
|
||||||
let (pd, bd, div) = fl.last_defects.get();
|
let (pd, bd, div) = fl.last_defects.get();
|
||||||
let (rough, flips, tn_max, tn_mean) = fl.wall_roughness();
|
let (rough, flips, tn_max, tn_mean) = fl.wall_roughness();
|
||||||
let (level, wall_flux, wall_area, poly_flux, poly_area) =
|
let (level, wall_flux, wall_area, poly_flux, poly_area) = fl.level_and_wall_flux();
|
||||||
fl.level_and_wall_flux();
|
|
||||||
let area_rate = (poly_area - prev_area.get()) / dt;
|
let area_rate = (poly_area - prev_area.get()) / dt;
|
||||||
println!(
|
println!(
|
||||||
" step {step} pass: |d_new − d_candidate| = {residual:.3e}, |d_new| = {:.3e}, nodal Σ|F| {load:.2} ΣF ({fx:+.2}, {fy:+.2}), power {power:+.3e} W/m, wall t_n roughness {rough:.2} ({flips} flips, max {tn_max:.1} mean {tn_mean:.1} Pa), patch p level {level:+.1} Pa, wall net flux {wall_flux:+.3e} m²/s over {wall_area:.3} m (polygon {poly_flux:+.3e}; flag area rate {area_rate:+.3e}), patch mass defect {pd:.2e} bg {bd:.2e} div {div:.1e}, {faces} faces",
|
" step {step} pass: |d_new − d_candidate| = {residual:.3e}, |d_new| = {:.3e}, nodal Σ|F| {load:.2} ΣF ({fx:+.2}, {fy:+.2}), power {power:+.3e} W/m, wall t_n roughness {rough:.2} ({flips} flips, max {tn_max:.1} mean {tn_mean:.1} Pa), patch p level {level:+.1} Pa, wall net flux {wall_flux:+.3e} m²/s over {wall_area:.3} m (polygon {poly_flux:+.3e}; flag area rate {area_rate:+.3e}), patch mass defect {pd:.2e} bg {bd:.2e} div {div:.1e}, {faces} faces",
|
||||||
@@ -363,6 +363,7 @@ pub fn run_march_overset(case: BenchmarkCase, config: &OversetMarchConfig) -> Ov
|
|||||||
flag_state = new_state;
|
flag_state = new_state;
|
||||||
committed_nodal = nodal;
|
committed_nodal = nodal;
|
||||||
prev_area.set(fluid.borrow().shared.read().unwrap().area());
|
prev_area.set(fluid.borrow().shared.read().unwrap().area());
|
||||||
|
fluid.borrow_mut().commit_base();
|
||||||
worst_conservation = worst_conservation.max(conservation);
|
worst_conservation = worst_conservation.max(conservation);
|
||||||
faces_used = faces;
|
faces_used = faces;
|
||||||
let ux = flag_state.displacement[a_dofs[0]];
|
let ux = flag_state.displacement[a_dofs[0]];
|
||||||
|
|||||||
Reference in New Issue
Block a user