Interface of saturation pressure functions seems to be used inconsistently
The interface for the computation of saturation pressure from temperature in this repository seems to be meant to be:
def es(T: "temperature") -> "saturation pressure"
This is a great interface and it's often used like this (e.g. here: es(T)
) throughout other parts of the code base. The general pattern seems to be:
def generic_usage(..., es, ...):
T = ...
foo = es(T)
However, other implementations (e.g. liq_analytic(T, lx, cl)
) have appeared in the meantime. Although lx
and cl
have default arguments in this case, so the function can also be used generically, there is no way to change these arguments in the generic_usage
setting, because there's no way for generic_usage
to know if there are additional parameres and if they are lx
and cx
or a
and b
(e.g. from tetens
).
I'd recommend to use closures, classes or factories in this case. An option based on closures might be to implement analytic like:
def analytic(lx, cx):
def es(T):
TvT = constants.temperature_water_vapor_triple_point
PvT = constants.pressure_water_vapor_triple_point
Rv = constants.water_vapor_gas_constant
c1 = (constants.cpv - cx) / Rv
c2 = lx / (Rv * TvT) - c1
return PvT * np.exp(c2 * (1.0 - TvT / T)) * (T / TvT) ** c1
return es
liq_analytic = analytic(lx=constants.lvT, cx=constants.cl)
ice_analytic = analytic(lx=constants.lsT, cx=constants.ci)
This would do mostly the same as the current implementation, but would also generalize further to things like:
bar = generic_usage(..., es=analytic(123, 456), ...)
The key idea here is, that any knowledge about customization should live outside of generic_usage
, and thus the interface of es(T)
must stay unchanged.
This reasoning extends to es_mxd
, which I'd probably implement as
def es_mxd(T):
return np.minimum(liq_wagner_pruss(T), ice_wagner_etal(T))
or even on the fly while using as:
bar = generic_usage(..., es=lambda T: np.minimum(liq_wagner_pruss(T), ice_wagner_etal(T)), ...)