Real-gas thermophysical properties, fast enough to sit inside a solver.
Everything above this package asks a :class:`Fluid` for numbers and never
touches an equation-of-state library::
from feedtwin.props import Fluid
n2 = Fluid("nitrogen")
n2.get("rho", p=3.1e7, T=293.0) # 44.5 kg/m3, ~0.13 us
n2.get("Z", p=3.1e7, T=293.0) # 1.150 -- a COPV is not an ideal gas
n2.state(p=5e5, q=0.5).phase # Phase.TWO_PHASE
Three ideas carry the whole package:
**A chain, not a branch.** A fluid holds an ordered list of backends. Tabulated
interpolation answers ordinary states in ~0.13 us; the Helmholtz equation of
state behind it answers everything the tables refuse. Put measured data at the
front and it wins wherever it has coverage. Nobody writes the routing.
**Refuse, never extrapolate.** A backend outside its envelope raises
:class:`OutOfRange` and the chain moves on. No backend in this package will
return a plausible number for a state point it cannot actually reach.
**Fluids are data.** Species live in ``species.toml``. Adding a propellant is a
data edit, and :func:`register_species` adds one at runtime without touching
this package at all.
SI throughout: Pa, K, kg/m3, Pa.s, J/kg, J/(kg.K), W/(m.K), m/s.