forked from 1b-agent/fieldcraft-upstream
engine: flow-past sample, air past an L-bracket on the floor
file.sample flow-past: air at 25 mm/s past the upright L-bracket in a box of 2.5 mm lattice cells (Re 83), inlet, outlet, a no-slip floor and slip walls; the fluid step reports the force on each named face. Its test holds the drag coefficient on the upright's frontal area between 1 and 3.5 (2.3) and the lattice's mass change under 1 %. No picture: the viewport does not draw flow results yet (M12). Part of #164 (its streamlines wait for a flow display in the viewport) Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
This commit is contained in:
1 parent
cf8ed71e4f
commit
ecdff11e72
3 files changed
+79
-1
No files matched your search
@@ -809,7 +809,7 @@ pub const RECOVERY_LIST: &[Field] = &[path("dir", "Folder", false).hint("the rec
|
||||
pub const RECOVER: &[Field] = &[text("id", "Autosave").req(), path("dir", "Folder", false).hint("the recovery folder")];
|
||||
pub const RECOVERY_DISCARD: &[Field] = &[text("id", "Autosave"), flag("all", "All"), path("dir", "Folder", false).hint("the recovery folder")];
|
||||
pub const SAMPLE: &[Field] =
|
||||
&[choice("name", "Sample", &[("cantilever", "Cantilever"), ("impact", "Impact"), ("parts", "Parts"), ("drop-test", "Drop test"), ("rubber-seal", "Rubber seal")]).req().def("\"cantilever\"")];
|
||||
&[choice("name", "Sample", &[("cantilever", "Cantilever"), ("impact", "Impact"), ("parts", "Parts"), ("drop-test", "Drop test"), ("rubber-seal", "Rubber seal"), ("flow-past", "Flow past a bracket")]).req().def("\"cantilever\"")];
|
||||
pub const COUNT: &[Field] = &[int("count", "Count", 1, 1_000_000).def("1")];
|
||||
|
||||
pub const DECK: &[Field] = &[
|
||||
|
||||
@@ -68,6 +68,13 @@ pub const SAMPLES: &[Sample] = &[
|
||||
design: None,
|
||||
script: |_| rubber_seal(),
|
||||
},
|
||||
Sample {
|
||||
name: "flow-past",
|
||||
label: "Flow Past a Bracket",
|
||||
about: "Air flowing past an L-bracket on the floor: lattice Boltzmann flow and the force on each face",
|
||||
design: Some(UPRIGHT_BRACKET),
|
||||
script: |_| flow_past(),
|
||||
},
|
||||
];
|
||||
|
||||
/// The sample called `name`.
|
||||
@@ -333,6 +340,33 @@ pub fn rubber_seal() -> Value {
|
||||
Value::Array(steps)
|
||||
}
|
||||
|
||||
/// The flow sample's free-stream speed, mm/s.
|
||||
pub const FLOW_SPEED: f64 = 25.0;
|
||||
/// Air at 20 °C: density (t/mm³) and dynamic viscosity (MPa s).
|
||||
const AIR: [f64; 2] = [1.2e-12, 1.8e-11];
|
||||
|
||||
/// Air at [`FLOW_SPEED`] past [`UPRIGHT_BRACKET`] standing on the floor, the upright's back face
|
||||
/// to the wind: a 240 × 120 × 100 mm box of 2.5 mm cells (184 320), a uniform inlet at x_min, an
|
||||
/// outlet at zero pressure at x_max, a no-slip floor and slip walls at the sides and top. Reynolds
|
||||
/// number ρ U h / μ = 83 on the upright's 50 mm height: laminar, a steady wake. Step `Flow` (16 s,
|
||||
/// about 6.7 flow-throughs; ten frames) reports the force on each named face of the bracket.
|
||||
pub fn flow_past() -> Value {
|
||||
let [density, viscosity] = AIR;
|
||||
json!([
|
||||
{"command": "model.units", "params": {"system": "mm-t-s-N-MPa"}},
|
||||
{"command": "model.material.create", "params": {"name": "Air", "density": density, "fluid": {"type": "newtonian", "viscosity": viscosity}}},
|
||||
{"command": "model.fluid_domain.create", "params": {
|
||||
"name": "Wind", "material": "Air", "min": [-60, -40, 0], "max": [180, 80, 100], "spacing": 2.5,
|
||||
"faces": {
|
||||
"x_min": {"type": "velocity", "velocity": [FLOW_SPEED, 0, 0]}, "x_max": {"type": "pressure", "pressure": 0},
|
||||
"y_min": {"type": "slip"}, "y_max": {"type": "slip"}, "z_min": {"type": "wall"}, "z_max": {"type": "slip"}
|
||||
},
|
||||
"obstacles": ["Bracket"], "initial_velocity": [FLOW_SPEED, 0, 0]
|
||||
}},
|
||||
{"command": "model.step.create", "params": {"name": "Flow", "procedure": {"type": "fluid", "domain": "Wind", "end_time": 16, "frames": 10}}},
|
||||
])
|
||||
}
|
||||
|
||||
/// Six times the signed volume of the tetrahedron `p[0..4]` (positive when p3 lies on the side
|
||||
/// p0 → p1 → p2 turns counter-clockwise around).
|
||||
fn signed_volume(p: &[[f64; 3]]) -> f64 {
|
||||
@@ -448,6 +482,29 @@ mod tests {
|
||||
assert!(stiffer > 1.05 && stiffer < 2.5, "bonded {bonded} N, frictionless {free} N");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_flow_sample_opens_ready_to_solve() {
|
||||
let s = open("flow-past");
|
||||
assert_eq!(s.model.fluid_domains().len(), 1);
|
||||
}
|
||||
|
||||
/// The drag on the bracket, as a coefficient on the upright's frontal area (40 × 50 mm), is
|
||||
/// that of a bluff plate standing on a wall at a Reynolds number of 83, raised by the 17 %
|
||||
/// blockage of the box: between 1 and 3.5 (a flat plate broadside to the flow is about 1.2 at
|
||||
/// high Reynolds number and rises as it falls). The lattice conserves mass.
|
||||
#[test]
|
||||
#[ignore = "heavy: cargo xtask test --heavy (CI heavy job)"]
|
||||
fn the_flow_past_the_bracket_has_a_bluff_body_drag() {
|
||||
let mut s = open("flow-past");
|
||||
let r = s.execute("solve.run", &json!({})).unwrap();
|
||||
let r = &r["steps"][0]["summary"];
|
||||
let drag: f64 = r["forces"].as_array().unwrap().iter().filter(|f| f["body"] == "Bracket").map(|f| f["mean"][0].as_f64().unwrap()).sum();
|
||||
let [density, _] = AIR;
|
||||
let cd = drag / (0.5 * density * FLOW_SPEED * FLOW_SPEED * 40.0 * 50.0);
|
||||
assert!(cd > 1.0 && cd < 3.5, "Cd {cd}: {r}");
|
||||
assert!(r["mass"]["relative_change"].as_f64().unwrap().abs() < 0.01, "{r}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_drop_test_opens_ready_to_solve() {
|
||||
let s = open("drop-test");
|
||||
|
||||
Reference in new issue
Block a user