UDF: junction boundary conditions¶
Status: a fixed selector, not an arbitrary path. py_junction_bc is
reachable from TOML today, but it chooses from a registered set rather
than importing your module.
Read the chapter overview for the shared contract.
What exists today¶
[[junction.ports]]
pipe = "runner1"
py_junction_bc = "learned_npz"
py_junction_bc_npz = "weights.npz"
Registered kinds:
| kind | code | what it is |
|---|---|---|
none |
0 | no override |
resistive_demo |
1 | a worked example of the seam |
learned_npz |
2 | an ML surrogate loaded from an .npz |
learned_npz is the interesting one: it already does what a UDF is for —
substituting a learned model for an analytic port closure — but it moves
data, not code. The weights file is inert; loading it cannot execute
anything.
Why it is not a dotted path¶
That inertness is the feature. A junction BC runs inside the network Picard/Newton loop, where a callable that is slow, non-deterministic, or raises on some iterate does not merely give a wrong answer — it can stop the coupled solve from converging at all, in a way that is very hard to attribute back to the closure.
Extending this seam to arbitrary user code is therefore a bigger step than friction, and would want:
- a contract on determinism (same inputs → same outputs; the Picard iteration re-evaluates the same state repeatedly);
- cost awareness — this is called per port per iteration, not once per step;
- a failure policy inside a non-linear solve, where raising mid-Newton and unwinding cleanly is not free.
See src/aeolus1d/closures/learned_junction_bc.py for the existing
implementation and the shape a surrogate must have.