R8-h: a flat-tipped flag for the 3D solver (default off, byte-identical when off)
DeviceSdf gains `tip_corner: Option<f64>` (None = the capsule, as before; Some(r_c) = a FLAT tip through the centreline's last point, normal to the last segment, corners rounded to r_c): the last segment becomes a ray for the lateral distance and the strip is cut by the tip plane with the span cut's rounded intersection. Host twin (plate.rs: closest/tip_axial/ flat_cap, polyline and plate bodies) and the device φ and velocity (e3_geom.cu geom_phi_at / body_velocity / plate_dist; GeomSdf flat_tip + tip_corner) expression for expression. Knobs: flag test RTX_E3_FLAG_TIP=flat + RTX_E3_FLAG_TIP_CORNER (default 0.00125 m; the tip inset defaults to 0 with the flat tip; the host φ is the device form's); R8-a harness RTX_E3FSI_TIP=flat + RTX_E3FSI_TIP_CORNER (the centreline gains node A as a 36th station). New host test embedded3_flat_tip (G2 geometry: tip plane at the last point, r_c = half = the capsule pulled back by half to 4e-17, cut volume and wall area vs the analytic rounded rectangle at ny 62/124/248). Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
171da41ed1
commit
02ab155022
@@ -56,6 +56,27 @@ const X0: f64 = 0.25;
|
||||
/// last 10 mm is the capsule's cap).
|
||||
const STATIONS: usize = 35;
|
||||
|
||||
/// R8-h: the flat tip (`RTX_E3FSI_TIP=flat`, corner radius
|
||||
/// `RTX_E3FSI_TIP_CORNER`, default 0.00125 m): the centreline gains the
|
||||
/// tip node A (x 0.60) as its last station and the body's tip is flat
|
||||
/// through it (`DeviceSdf::tip_corner`); unset = the capsule.
|
||||
pub fn flat_tip() -> Option<f64> {
|
||||
match std::env::var("RTX_E3FSI_TIP").as_deref() {
|
||||
Err(_) | Ok("capsule") => None,
|
||||
Ok("flat") => {
|
||||
let rc = env_f("RTX_E3FSI_TIP_CORNER", 0.00125);
|
||||
assert!((0.0..=HALF).contains(&rc), "RTX_E3FSI_TIP_CORNER {rc}");
|
||||
Some(rc)
|
||||
}
|
||||
Ok(v) => panic!("RTX_E3FSI_TIP={v}: flat or capsule"),
|
||||
}
|
||||
}
|
||||
|
||||
/// The centreline's stations (35, or 36 with the flat tip's A).
|
||||
fn stations() -> usize {
|
||||
STATIONS + usize::from(flat_tip().is_some())
|
||||
}
|
||||
|
||||
/// The flag's structure-side bookkeeping: the centreline nodes, the wetted
|
||||
/// edges with their reference coordinates, the tip node A.
|
||||
struct Flag {
|
||||
@@ -82,7 +103,7 @@ impl Flag {
|
||||
.expect("node")
|
||||
.0
|
||||
};
|
||||
let centre = (0..STATIONS)
|
||||
let centre = (0..stations())
|
||||
.map(|k| find(X0 + 0.01 * k as f64, 0.2))
|
||||
.collect();
|
||||
let interface = Interface::build(mesh);
|
||||
@@ -217,10 +238,10 @@ fn distribute(
|
||||
/// The fluid's centreline from the structure's centreline displacement `c`
|
||||
/// (2 per station) with velocities `(c − c_prev) / dt`.
|
||||
fn line_of(t: f64, c: &[f64], c_prev: &[f64], dt: f64) -> Line {
|
||||
let pts = (0..STATIONS)
|
||||
let pts = (0..stations())
|
||||
.map(|k| [X0 + 0.01 * k as f64 + c[2 * k], 0.2 + c[2 * k + 1]])
|
||||
.collect();
|
||||
let vel = (0..STATIONS)
|
||||
let vel = (0..stations())
|
||||
.map(|k| {
|
||||
[
|
||||
(c[2 * k] - c_prev[2 * k]) / dt,
|
||||
@@ -252,7 +273,7 @@ fn fsi2_on_embedded3() {
|
||||
|
||||
let mesh = flag_mesh(35, 2);
|
||||
let flag_geo = Flag::build(&mesh);
|
||||
let zero_c = vec![0.0; 2 * STATIONS];
|
||||
let zero_c = vec![0.0; 2 * stations()];
|
||||
let rest = line_of(0.0, &zero_c, &zero_c, 1.0);
|
||||
// `RTX_E3FSI_LOAD=<dir>`: continue from a saved coupled state (extruded
|
||||
// onto the full duct when the saved nz differs); `RTX_E3FSI_SAVE=<dir>`
|
||||
@@ -381,7 +402,7 @@ fn fsi2_on_embedded3() {
|
||||
.collect();
|
||||
let a_dofs = flag.borrow().node_dofs(flag_geo.a_node);
|
||||
let extract = |s: &DynamicState| -> Vec<f64> {
|
||||
let mut c = vec![0.0; 2 * STATIONS];
|
||||
let mut c = vec![0.0; 2 * stations()];
|
||||
for (k, d) in centre_dofs.iter().enumerate() {
|
||||
c[2 * k] = s.displacement[d[0]];
|
||||
c[2 * k + 1] = s.displacement[d[1]];
|
||||
@@ -486,8 +507,8 @@ fn fsi2_on_embedded3() {
|
||||
.sqrt();
|
||||
println!(
|
||||
" step {step} pass: |c_new − c_cand| {res:.3e}, tip cand ({:+.4e}, {:+.4e}), load flag ({:+.3}, {:+.3}) cyl ({:+.3}, {:+.3}) total ({:+.3}, {:+.3}), residual {:.1e}, fresh {}",
|
||||
cand[2 * STATIONS - 2],
|
||||
cand[2 * STATIONS - 1],
|
||||
cand[2 * stations() - 2],
|
||||
cand[2 * stations() - 1],
|
||||
on_flag[0],
|
||||
on_flag[1],
|
||||
on_cyl[0],
|
||||
|
||||
@@ -227,17 +227,9 @@ impl E3Fluid {
|
||||
}));
|
||||
VERSION.fetch_add(1, Ordering::AcqRel);
|
||||
let (l1, l2, l3) = (lines.clone(), lines.clone(), lines.clone());
|
||||
let body = Body::from_sdf(move |x, y, _z, t| cylinder(x, y).min(capsule(&l1, x, y, t).0))
|
||||
.with_surface_velocity(move |x, y, _z, t| {
|
||||
let (df, (vx, vy)) = capsule(&l2, x, y, t);
|
||||
if df <= cylinder(x, y) {
|
||||
(vx, vy, 0.0)
|
||||
} else {
|
||||
(0.0, 0.0, 0.0)
|
||||
}
|
||||
});
|
||||
let width = nz as f64 * h;
|
||||
let body = body.with_device_sdf(move |t| {
|
||||
let tip_corner = super::flat_tip();
|
||||
let device_sdf = move |t: f64| {
|
||||
let line = l3.read().expect("lines").at(t);
|
||||
DeviceSdf {
|
||||
cyl: [CX, CY, R_CYL],
|
||||
@@ -248,12 +240,48 @@ impl E3Fluid {
|
||||
r_edge: h,
|
||||
half: HALF,
|
||||
fillet: 0.0,
|
||||
tip_corner,
|
||||
poly: line.pts,
|
||||
vel: line.vel,
|
||||
// R8-c's plate body (merged alongside): the span-uniform harness keeps the polyline.
|
||||
plate: None,
|
||||
}
|
||||
});
|
||||
};
|
||||
let body = if tip_corner.is_some() {
|
||||
// R8-h: the flat tip's host φ and velocity are the device form's
|
||||
// (the kernel's arithmetic), cached per thread, time and lines.
|
||||
let ds = device_sdf.clone();
|
||||
let sdf_at = move |t: f64| -> Arc<DeviceSdf> {
|
||||
thread_local! {
|
||||
static SDF: RefCell<(u64, u64, Option<Arc<DeviceSdf>>)> =
|
||||
const { RefCell::new((u64::MAX, u64::MAX, None)) };
|
||||
}
|
||||
let ver = VERSION.load(Ordering::Acquire);
|
||||
SDF.with(|cell| {
|
||||
let mut c = cell.borrow_mut();
|
||||
if c.0 != t.to_bits() || c.1 != ver || c.2.is_none() {
|
||||
c.2 = Some(Arc::new(ds(t)));
|
||||
c.0 = t.to_bits();
|
||||
c.1 = ver;
|
||||
}
|
||||
c.2.clone().expect("sdf")
|
||||
})
|
||||
};
|
||||
let sdf_v = sdf_at.clone();
|
||||
Body::from_sdf(move |x, y, z, t| sdf_at(t).phi_host(x, y, z))
|
||||
.with_surface_velocity(move |x, y, z, t| sdf_v(t).velocity_host(x, y, z))
|
||||
} else {
|
||||
Body::from_sdf(move |x, y, _z, t| cylinder(x, y).min(capsule(&l1, x, y, t).0))
|
||||
.with_surface_velocity(move |x, y, _z, t| {
|
||||
let (df, (vx, vy)) = capsule(&l2, x, y, t);
|
||||
if df <= cylinder(x, y) {
|
||||
(vx, vy, 0.0)
|
||||
} else {
|
||||
(0.0, 0.0, 0.0)
|
||||
}
|
||||
})
|
||||
};
|
||||
let body = body.with_device_sdf(device_sdf);
|
||||
solver.set_moving_body(body);
|
||||
let g = Grid::cubic(nx, ny, nz, h);
|
||||
let mut field = Field::new(g);
|
||||
|
||||
Reference in New Issue
Block a user