P5-1 instruments and discriminators: RTX_FSI2O_NO_WALL_VEL, _FREEZE_PATCH, _VOLUME_PRESERVE (the imposed wall velocity's net flux removed uniformly along the wall), the pass trace carries the fluid's power on the flag, the wall-pressure roughness, the patch's pressure level, the wall's net volume flux against the FEA polygon's area rate, and the mass defects — the s = 1 runaway is the flag's thickness breathing (ν = 0.4 Quad8) seen by the body-fitted wall as a volume flux equal to the polygon's area rate, answered by the projection's pressure level at ρ/dt
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
Documentation / Build API Documentation (push) Canceled after 0s
Documentation / Build User Guide (push) Canceled after 0s
CI / Python Bindings (maturin) (macos-latest) (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 / 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
CI / CI Success (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
Documentation / Build API Documentation (push) Canceled after 0s
Documentation / Build User Guide (push) Canceled after 0s
CI / Python Bindings (maturin) (macos-latest) (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 / 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
CI / CI Success (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
2ed36c3a8c
commit
565215baf9
@@ -40,6 +40,41 @@ const PATCH_STRETCH: f64 = 4.0;
|
||||
pub struct WallMotion {
|
||||
pub polygon: Vec<[f64; 2]>,
|
||||
pub velocity: Vec<[f64; 2]>,
|
||||
/// The net normal velocity removed from every segment (the imposed
|
||||
/// velocity's volume flux over the wall length) when the wall is
|
||||
/// made volume-preserving; zero otherwise.
|
||||
pub q: f64,
|
||||
}
|
||||
|
||||
impl WallMotion {
|
||||
/// The imposed velocity's net volume flux INTO the fluid over the
|
||||
/// polygon (counter-clockwise around the body: outward normal
|
||||
/// `(dy, −dx)/L`), and the polygon's length.
|
||||
pub fn net_flux(&self) -> (f64, f64) {
|
||||
let (mut flux, mut len) = (0.0, 0.0);
|
||||
for i in 0..self.polygon.len().saturating_sub(1) {
|
||||
let (a, b) = (self.polygon[i], self.polygon[i + 1]);
|
||||
let (dx, dy) = (b[0] - a[0], b[1] - a[1]);
|
||||
let (va, vb) = (self.velocity[i], self.velocity[i + 1]);
|
||||
let (vx, vy) = (0.5 * (va[0] + vb[0]), 0.5 * (va[1] + vb[1]));
|
||||
flux += vx * dy - vy * dx;
|
||||
len += (dx * dx + dy * dy).sqrt();
|
||||
}
|
||||
(flux, len)
|
||||
}
|
||||
|
||||
/// The polygon's enclosed area (shoelace; the walk is closed across
|
||||
/// the cylinder by its anchors, so this is the flag's area up to a
|
||||
/// constant).
|
||||
pub fn area(&self) -> f64 {
|
||||
let n = self.polygon.len();
|
||||
let mut a = 0.0;
|
||||
for i in 0..n {
|
||||
let (p, q) = (self.polygon[i], self.polygon[(i + 1) % n]);
|
||||
a += p[0] * q[1] - q[0] * p[1];
|
||||
}
|
||||
0.5 * a
|
||||
}
|
||||
}
|
||||
|
||||
impl WallMotion {
|
||||
@@ -64,7 +99,11 @@ impl WallMotion {
|
||||
if d < best_d {
|
||||
best_d = d;
|
||||
let (va, vb) = (self.velocity[i], self.velocity[i + 1]);
|
||||
best = (va[0] + t * (vb[0] - va[0]), va[1] + t * (vb[1] - va[1]));
|
||||
let l = l2.sqrt().max(1e-300);
|
||||
best = (
|
||||
va[0] + t * (vb[0] - va[0]) - self.q * dy / l,
|
||||
va[1] + t * (vb[1] - va[1]) + self.q * dx / l,
|
||||
);
|
||||
}
|
||||
}
|
||||
best
|
||||
@@ -96,6 +135,9 @@ pub struct OversetFluid {
|
||||
pub correctors_total: Cell<usize>,
|
||||
/// The interface of the last `set_geometry`.
|
||||
pub last_d: Vec<f64>,
|
||||
/// The last fluid step's mass defects (patch acceptor ring, background
|
||||
/// fringe) and the patch's max divergence.
|
||||
pub last_defects: Cell<(f64, f64, f64)>,
|
||||
}
|
||||
|
||||
impl OversetFluid {
|
||||
@@ -189,6 +231,7 @@ impl OversetFluid {
|
||||
.map(|&(x, y)| [x, y])
|
||||
.collect(),
|
||||
velocity: vec![[0.0, 0.0]; interface.walk.len()],
|
||||
q: 0.0,
|
||||
}));
|
||||
let wall = shared.clone();
|
||||
let mut patch = CurvilinearPisoSolver::new(
|
||||
@@ -201,8 +244,15 @@ impl OversetFluid {
|
||||
},
|
||||
patch_mesh,
|
||||
)?;
|
||||
// Discriminators (P5-1 matrix): `RTX_FSI2O_NO_WALL_VEL` keeps the
|
||||
// wall at rest in the fluid while the geometry moves.
|
||||
let no_wall_vel = std::env::var("RTX_FSI2O_NO_WALL_VEL").is_ok();
|
||||
patch.set_side_velocity(PatchSide::Inner, move |x, y, _| {
|
||||
wall.read().unwrap().velocity_at(x, y)
|
||||
if no_wall_vel {
|
||||
(0.0, 0.0)
|
||||
} else {
|
||||
wall.read().unwrap().velocity_at(x, y)
|
||||
}
|
||||
});
|
||||
let mut patch_field = PatchField::new(patch.mesh());
|
||||
patch.initialize(&mut patch_field, |_, _| (0.0, 0.0));
|
||||
@@ -239,6 +289,7 @@ impl OversetFluid {
|
||||
rounds_total: Cell::new(0),
|
||||
correctors_total: Cell::new(0),
|
||||
last_d: zero_d,
|
||||
last_defects: Cell::new((0.0, 0.0, 0.0)),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -343,6 +394,21 @@ impl OversetFluid {
|
||||
.iter()
|
||||
.map(|&(u, v)| [u, v])
|
||||
.collect();
|
||||
// `RTX_FSI2O_VOLUME_PRESERVE`: the fluid sees a volume-preserving
|
||||
// wall — the imposed velocity's net flux (the flag's thickness
|
||||
// breathing under the pressure step, a ~300 Hz mode of the
|
||||
// ν = 0.4 solid) is removed uniformly along the wall.
|
||||
w.q = 0.0;
|
||||
if std::env::var("RTX_FSI2O_VOLUME_PRESERVE").is_ok() {
|
||||
let (flux, len) = w.net_flux();
|
||||
w.q = flux / len.max(1e-300);
|
||||
}
|
||||
}
|
||||
// `RTX_FSI2O_FREEZE_PATCH`: the undeformed patch throughout (the
|
||||
// wall velocity still moves) — is the regeneration the driver?
|
||||
if std::env::var("RTX_FSI2O_FREEZE_PATCH").is_ok() {
|
||||
self.last_d = d.to_vec();
|
||||
return Ok(());
|
||||
}
|
||||
let mesh = match self.patch_for(d) {
|
||||
Ok(m) => m,
|
||||
@@ -389,6 +455,11 @@ impl OversetFluid {
|
||||
return Err(e);
|
||||
}
|
||||
};
|
||||
self.last_defects.set((
|
||||
r.patch_mass_defect,
|
||||
r.background_mass_defect,
|
||||
r.patch_max_divergence,
|
||||
));
|
||||
self.reclassified_total
|
||||
.set(self.reclassified_total.get() + r.reclassified_cells);
|
||||
self.fresh_total.set(self.fresh_total.get() + r.fresh_cells);
|
||||
@@ -498,6 +569,71 @@ impl OversetFluid {
|
||||
)
|
||||
}
|
||||
|
||||
/// The wall pressure's roughness along the flag: over the flag's
|
||||
/// wall faces in order, `Σ|t_n(f+1) − t_n(f)| / Σ|t_n(f)|` with `t_n`
|
||||
/// the normal traction, and the count of sign changes of the
|
||||
/// face-to-face difference — a checkerboard reads ≫ 1 with a sign
|
||||
/// change at every face.
|
||||
pub fn wall_roughness(&self) -> (f64, usize, f64, f64) {
|
||||
let mut tn: Vec<f64> = Vec::new();
|
||||
for (centre, normal, _, traction) in
|
||||
self.solver
|
||||
.patch()
|
||||
.wall_tractions(&self.field.patch, PatchSide::Inner, self.solver.time())
|
||||
{
|
||||
let on_cylinder = ((centre[0] - CYL_CENTRE[0]).powi(2)
|
||||
+ (centre[1] - CYL_CENTRE[1]).powi(2))
|
||||
.sqrt()
|
||||
< CYL_R + 1e-9;
|
||||
if !on_cylinder {
|
||||
tn.push(traction[0] * normal[0] + traction[1] * normal[1]);
|
||||
}
|
||||
}
|
||||
let total: f64 = tn.iter().map(|v| v.abs()).sum();
|
||||
let mut jumps = 0.0;
|
||||
let mut flips = 0usize;
|
||||
let mut prev_diff = 0.0;
|
||||
for w in tn.windows(2) {
|
||||
let d = w[1] - w[0];
|
||||
jumps += d.abs();
|
||||
if prev_diff * d < 0.0 {
|
||||
flips += 1;
|
||||
}
|
||||
prev_diff = d;
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
/// The patch's pressure level (mean over its cells) and the wall's
|
||||
/// net volume flux from the imposed velocity (`Σ u_wall · S_out` over
|
||||
/// the Inner faces, positive = fluid leaving the patch through the
|
||||
/// wall), against the inner ring's enclosed-area rate would be the
|
||||
/// mesh's own; both must match for a consistent moving wall.
|
||||
pub fn level_and_wall_flux(&self) -> (f64, f64, f64, f64, f64) {
|
||||
let mesh = self.solver.patch().mesh();
|
||||
let p = &self.field.patch.p;
|
||||
let level = p.iter().sum::<f64>() / p.len().max(1) as f64;
|
||||
let wall = self.shared.read().unwrap();
|
||||
let (poly_flux, _) = wall.net_flux();
|
||||
let poly_area = wall.area();
|
||||
let (mut flux, mut area) = (0.0, 0.0);
|
||||
for (f, face) in mesh.faces().iter().enumerate() {
|
||||
if mesh.side(f) != Some(PatchSide::Inner) {
|
||||
continue;
|
||||
}
|
||||
// Inner side: S points from the body into the fluid (the
|
||||
// neighbour is the cell), so −S is out of the fluid.
|
||||
let sign = if face.neigh.is_some() { 1.0 } else { -1.0 };
|
||||
let s_in = [sign * face.s[0], sign * face.s[1]];
|
||||
let (u, v) = wall.velocity_at(face.centre[0], face.centre[1]);
|
||||
// Volume flux INTO the fluid = u_wall · S_into_fluid.
|
||||
flux += u * s_in[0] + v * s_in[1];
|
||||
area += (s_in[0] * s_in[0] + s_in[1] * s_in[1]).sqrt();
|
||||
}
|
||||
(level, flux, area, poly_flux, poly_area)
|
||||
}
|
||||
|
||||
pub fn snapshot(&self) -> (OversetSolverState, OversetField) {
|
||||
(self.solver.snapshot(), self.field.clone())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user