Skip to content

UDF: wall heat transfer

Status: seam live, no user path yet. The solver consumes pipe.py_heat on every driver, but the only things that write it are built-in closures — there is no kind = "python" for [pipe.heat].

Read the chapter overview for the shared contract.

What exists today

_overlay_py_sources applies pipe.py_heat(rho, u, p) -> s_E alongside friction and porous. Five built-in closures produce one:

producer selected by
make_egr_cooler_py_heat [pipe.heat] kind = "egr_cooler"
make_charge_air_cooler_py_heat kind = "charge_air_cooler"
make_flow_nu_py_heat kind = "flow_nu"
make_wall_coupled_py_heat a [[wall]] thermal coupling
make_multilayer_wall_py_heat a multilayer wall

So unlike friction, this seam is already carrying load. That is exactly why it is not first: a kind = "python" arm here shares a seam with five existing producers, and the interaction (what happens when a wall coupling and a user closure both want to write py_heat) is a design question, not a plumbing one.

What a user path would need to settle

  • Conflict rule. A [[wall]] coupling sets py_heat on the gas pipe during network assembly, after _attach_closures. Last writer wins today, silently. A user closure must either refuse to coexist, or the seam must become a list.
  • Energy accounting. py_heat returns s_E only, and the energy audit (analysis/energy_audit.py) enumerates heat closures to form its control volume. A user closure that the audit cannot enumerate would make CV-B unformable — the audit is written to refuse rather than silently mis-attribute, so this must be wired deliberately.
  • Sign convention. s_E < 0 removes energy from the gas. The built-ins are consistent; a user-facing contract should state it once, in the units the audit expects (W/m³).

Until those are answered, use kind = "flow_nu" (correlation-driven, Re/Pr-dependent) which covers most of what a hand-written heat closure would do.