UDF: porous drag¶
Status: available. kind = "python" on any [pipe.porous] block.
Read the chapter overview first — the permission model, the
PipeState object and the return-validation rules are shared with
friction.
[pipe.porous]
kind = "python"
path = "my_models:darcy_forch"
import numpy as np
def darcy_forch(state):
mu, K, C_F = 1.8e-5, 1e-9, 0.5
s_mom = -(mu / K) * state.u - C_F * state.rho * np.abs(state.u) * state.u
return s_mom, np.zeros_like(s_mom)
Same (s_mom, s_E) return as friction, same length-N validation, same
PipeState. The adapter is literally the same function — kept shared on
purpose, because two adapters that "obviously do the same thing" is how
one of them ends up with the shape check and the other without.
The QS refusal bites harder here¶
Friction under solver = "quasi_steady" is refused because the QS network
folds it into a constant drag. Porous is refused for the same mechanism
and a worse consequence.
_qs_alpha_beta reads pipe.porous.params for alpha, beta and mu
and folds them into the QS drag coefficient. A user callable has neither
an alpha nor a beta, so the pipe would emerge with no porous resistance
at all. For a monolith or a filter that is not a small error — it is the
entire pressure drop, and the run would look plausible while being
completely wrong about flow split.
So the build raises. Use explicit / strang, or supply constants via
kind = "darcy_forchheimer".
Provenance¶
pipe.porous is set to a PorousState with kind = 3 (POROUS_PYTHON)
and a params array whose tail slot still carries R_eff — every other
porous kind puts the gas constant there, and anything reading that slot
must still find it.
_py_closure_kinds hands the @njit kernel NONE because py_porous is
set, so the drag is applied exactly once, by the Python overlay.
Both native-only JIT overlay paths in implicit_kernels are gated on all
three py_* seams being absent, so the provenance code can never reach a
compiled dispatcher. That is pinned by a test, because the failure mode is
silent: the @njit if/elif chains have no else, so an unknown kind
would add no source and the bug would look like "my closure ran once"
rather than an error.