engine: add a small heated flow sample

Demonstrate temperature slices and signed face-average wall heat with cool air passing an isothermal bracket. Register the original CPU-rendered gallery thumbnail and document the coarse transient setup.

Part of #157.
This commit is contained in:
Christian Svensson authored and Irakli Kourtis committed 2026-10-17 14:08:50 -04:00
1 parent 1e5ff08c5f
commit e9ccf10692
7 files changed
+91 -1

No files matched your search

+1
View File
@@ -1971,6 +1971,7 @@ pub const SAMPLE: &[Field] = &[choice(
("drop-test", "Drop test"),
("rubber-seal", "Rubber seal"),
("flow-past", "Flow past a bracket"),
("heated-flow", "Heated flow past a bracket"),
("heated-bracket", "Heated bracket"),
("bracket-hex", "Hex-meshed bracket"),
("crank-arm", "Bicycle crank arm"),
+10
View File
@@ -18,6 +18,8 @@
use fieldcraft_geometry::Shape;
use serde_json::{Value, json};
mod heated_flow;
/// A sample the app offers (Help › Samples, the empty viewport, `file.sample`, `--sample NAME`).
pub struct Sample {
pub name: &'static str,
@@ -92,6 +94,14 @@ pub const SAMPLES: &[Sample] = &[
prepare: None,
script: |_| flow_past(),
},
Sample {
name: "heated-flow",
label: "Heated Flow Past a Bracket",
about: "Cool air passing an 80 °C bracket: temperature slices and signed wall heat, a small transient demonstration",
design: Some(UPRIGHT_BRACKET),
prepare: None,
script: heated_flow::script,
},
Sample {
name: "heated-bracket",
label: "Heated Bracket",
+58
View File
@@ -0,0 +1,58 @@
//! A small transient heated-flow demonstration using the original upright bracket.
//! Air at 20 °C passes an 80 °C bracket; no buoyancy or conjugate solid conduction.
use fieldcraft_geometry::Shape;
use serde_json::{Value, json};
pub(super) fn script(bodies: &[Shape]) -> Value {
let walls: Vec<Value> = bodies
.iter()
.filter(|b| b.name == "Bracket")
.flat_map(|b| {
b.faces().iter().map(move |f| {
json!({
"face": {"body": b.name, "face": f.name},
"condition": {"type": "temperature", "value": 80.0}
})
})
})
.collect();
let [density, viscosity] = super::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},
"thermal": {"conductivity": 0.026, "specific_heat": 1.005e9}}},
{"command": "model.fluid_domain.create", "params": {
"name": "WarmAir", "material": "Air", "min": [-30, -20, 0], "max": [100, 60, 70], "spacing": 5,
"faces": {"x_min": {"type": "velocity", "velocity": [1, 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": [1, 0, 0],
"thermal": {"initial_temperature": 20, "surfaces": walls,
"faces": {"x_min": {"type": "temperature", "value": 20}}}
}},
{"command": "model.step.create", "params": {"name": "HeatedFlow", "procedure": {
"type": "fluid", "domain": "WarmAir", "steps": 180, "frames": 6
}}}
])
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn heated_flow_sample_solves_and_exposes_temperature_and_wall_heat() {
let mut s = crate::Session::default();
s.execute("file.sample", &json!({"name": "heated-flow"})).unwrap();
s.execute("solve.fluid", &json!({"backend": "cpu"})).unwrap();
let report = s.execute("results.slice", &json!({"field": "temperature"})).unwrap();
let max = report["contour"]["range"]["max"]["value"].as_f64().unwrap();
let min = report["contour"]["range"]["min"]["value"].as_f64().unwrap();
assert!(max > 20.1 && max <= 80.01 && min >= 19.99, "{report}");
let report = s.execute("results.surface", &json!({"field": "wall_heat_flux"})).unwrap();
assert_eq!(report["contour"]["unit"], "mW/mm²");
assert!(report["wall_heat"]["faces"].as_array().unwrap().iter().any(|f| f["power"].as_f64().unwrap_or(0.0) < 0.0));
assert_eq!(report["frames"].as_array().unwrap().len(), 7);
}
}