Source code for tespy.tools.fluid_properties.functions

# -*- coding: utf-8

"""Module for fluid property functions.


This file is part of project TESPy (github.com/oemof/tespy). It's copyrighted
by the contributors recorded in the version control history of the file,
available from its original location
tespy/tools/fluid_properties/functions.py

SPDX-License-Identifier: MIT
"""
from CoolProp.CoolProp import HAPropsSI

from tespy.tools.global_vars import FLUID_ALIASES
from tespy.tools.logger import logger

from .helpers import get_number_of_fluids
from .helpers import get_pure_fluid
from .helpers import inverse_temperature_mixture
from .mixtures import MIXING_RULES
from .mixtures import w_mix_ph_humidair
from .mixtures import w_mix_ps_humidair


[docs] def isentropic(p_1, h_1, p_2, fluid_data, mixing_rule=None, T0=None, T0_out=None): r""" Calculate specific enthalpy at isentropic outlet conditions. For a pure fluid delegates directly to the fluid wrapper. For mixtures the isentropic enthalpy is obtained by computing the inlet entropy, then inverting :func:`T_mix_ps` at the outlet pressure, and finally evaluating :func:`h_mix_pT`. Parameters ---------- p_1 : float Inlet pressure in Pa. h_1 : float Inlet specific enthalpy in J/kg. p_2 : float Outlet pressure in Pa. fluid_data : dict Fluid property data: :code:`{fluid: {"mass_fraction": float, "wrapper": FluidPropertyWrapper}}`. mixing_rule : str, optional Mixing rule identifier; ignored for pure fluids. T0 : float, optional Temperature starting value in K for the inlet entropy inversion. T0_out : float, optional Temperature starting value in K for the outlet enthalpy inversion. Returns ------- float Isentropic specific enthalpy at the outlet in J/kg. Notes ----- For mixtures: .. math:: h_2^\text{is} = h\!\left(p_2,\; T\!\left(p_2,\; s(p_1, h_1)\right)\right) """ if get_number_of_fluids(fluid_data) == 1: pure_fluid = get_pure_fluid(fluid_data) return pure_fluid["wrapper"].isentropic(p_1, h_1, p_2) else: s_1 = s_mix_ph(p_1, h_1, fluid_data, mixing_rule, T0=T0) T_2 = T_mix_ps(p_2, s_1, fluid_data, mixing_rule, T0=T0_out) return h_mix_pT(p_2, T_2, fluid_data, mixing_rule)
def _exergy_splitting_in_two_phase(h, s, p, pamb, Tamb, fluid_data): r""" Check for the special two-phase ambient-state case in exergy calculations. If the fluid is a real pure fluid in two-phase state at ambient temperature, the thermal exergy is zero and only mechanical exergy is returned. Returns :code:`None` for all other cases. Parameters ---------- h : float Specific enthalpy in J/kg. s : float Specific entropy in J/(kg K). p : float Pressure in Pa. pamb : float Ambient pressure in Pa. Tamb : float Ambient temperature in K. fluid_data : dict Fluid property data: :code:`{fluid: {"mass_fraction": float, "wrapper": FluidPropertyWrapper}}`. Returns ------- tuple or None :code:`(e_T, e_M)` in J/kg when the special case applies, otherwise :code:`None`. """ pure_fluid = get_pure_fluid(fluid_data) if pure_fluid["wrapper"].mixture_type is None: # real pure fluid if pure_fluid["wrapper"].phase_ph(p, h) == "tp": if round(pure_fluid["wrapper"].T_ph(p, h), 6) == round(Tamb, 6): h0 = h_mix_pT(pamb, Tamb, fluid_data) s0 = s_mix_pT(pamb, Tamb, fluid_data) ex_mech = (h - h0) - Tamb * (s - s0) return 0.0, ex_mech return None def _physical_exergy_at_min_temperature(h, s, pamb, Tamb, fluid_data, fluid): r""" Fallback for CoolProp domain errors at ambient conditions. Used when the fluid's minimum valid temperature exceeds the ambient temperature. Evaluates exergy at :math:`T_\text{min} + 10^{-6}` K and returns zero mechanical exergy. Parameters ---------- h : float Specific enthalpy in J/kg. s : float Specific entropy in J/(kg K). pamb : float Ambient pressure in Pa. Tamb : float Ambient temperature in K. fluid_data : dict Fluid property data: :code:`{fluid: {"mass_fraction": float, "wrapper": FluidPropertyWrapper}}`. fluid : dict Single-fluid entry from *fluid_data*. Returns ------- tuple :code:`(e_ph, 0.0)` - total physical exergy and zero mechanical share, both in J/kg. """ Tmin = fluid["wrapper"]._T_min logger.warning( "Physical exergy at ambient state (p0=%.3f, T0=%.2f) for %s " "is outside the FluidWrapper temperature limits --> evaluating at " f"the fluid's Tmin {Tmin} K as fallback.", pamb, Tamb, fluid["wrapper"].fluid ) # stay marginally above the hard limit to avoid further CoolProp errors Tmin_eval = Tmin + 1e-6 h_min = h_mix_pT(pamb, Tmin_eval, fluid_data) s_min = s_mix_pT(pamb, Tmin_eval, fluid_data) ex_ph = (h - h_min) - Tamb * (s - s_min) return ex_ph, 0.0
[docs] def calc_physical_exergy(h, s, p, pamb, Tamb, fluid_data, mixing_rule=None, T0=None): r""" Calculate specific physical exergy. Physical exergy is allocated to a thermal and a mechanical share according to :cite:`Morosuk2019`. Parameters ---------- h : float Specific enthalpy in J/kg. s : float Specific entropy in J/(kg K). p : float Pressure in Pa. pamb : float Ambient pressure in Pa. Tamb : float Ambient temperature in K. fluid_data : dict Fluid property data: :code:`{fluid: {"mass_fraction": float, "wrapper": FluidPropertyWrapper}}`. mixing_rule : str, optional Mixing rule identifier; ignored for pure fluids. T0 : float, optional Temperature starting value in K for iterative property inversions. Returns ------- tuple Specific thermal and mechanical exergy (:math:`e^\text{T}`, :math:`e^\text{M}`) in J/kg. Notes ----- .. math:: e^\text{T} = \left( h - h \left( p, T_0 \right) \right) - T_0 \cdot \left(s - s\left(p, T_0\right)\right) e^\text{M}=\left(h\left(p,T_0\right)-h\left(p_0,T_0\right)\right) -T_0\cdot\left(s\left(p, T_0\right)-s\left(p_0,T_0\right)\right) e^\text{PH} = e^\text{T} + e^\text{M} """ if get_number_of_fluids(fluid_data) == 1: pure_fluid = get_pure_fluid(fluid_data) if pure_fluid["wrapper"]._T_min > Tamb: return _physical_exergy_at_min_temperature( h, s, pamb, Tamb, fluid_data, pure_fluid ) else: ex = _exergy_splitting_in_two_phase(h, s, p, pamb, Tamb, fluid_data) if ex is not None: return ex[0], ex[1] h_T0_p = h_mix_pT(p, Tamb, fluid_data, mixing_rule) s_T0_p = s_mix_pT(p, Tamb, fluid_data, mixing_rule) ex_therm = (h - h_T0_p) - Tamb * (s - s_T0_p) h0 = h_mix_pT(pamb, Tamb, fluid_data, mixing_rule) s0 = s_mix_pT(pamb, Tamb, fluid_data, mixing_rule) ex_mech = (h_T0_p - h0) - Tamb * (s_T0_p - s0) return ex_therm, ex_mech
[docs] def T_mix_ph(p, h, fluid_data, mixing_rule=None, T0=None): r""" Calculate temperature from pressure and specific enthalpy. For a pure fluid delegates to the fluid wrapper. For humid air uses :func:`CoolProp.CoolProp.HAPropsSI`. For other mixtures inverts :func:`h_mix_pT` iteratively via :func:`.helpers.inverse_temperature_mixture`. Parameters ---------- p : float Pressure in Pa. h : float Specific enthalpy in J/kg. fluid_data : dict Fluid property data: :code:`{fluid: {"mass_fraction": float, "wrapper": FluidPropertyWrapper}}`. mixing_rule : str, optional Mixing rule identifier; ignored for pure fluids. T0 : float, optional Temperature starting value in K for the iterative inversion. Returns ------- float Temperature in K. """ if get_number_of_fluids(fluid_data) == 1: pure_fluid = get_pure_fluid(fluid_data) return pure_fluid["wrapper"].T_ph(p, h) else: if mixing_rule == "humidair": w = w_mix_ph_humidair(p, h, fluid_data) return HAPropsSI("T", "P", p, "H", h, "W", w) else: kwargs = { "p": p, "target_value": h, "fluid_data": fluid_data, "T0": T0, "f": MIXING_RULES.T_ph(mixing_rule) } return inverse_temperature_mixture(**kwargs)
[docs] def dT_mix_pdh(p, h, fluid_data, mixing_rule=None, T0=None): r""" Calculate the partial derivative of temperature with respect to enthalpy at constant pressure. Uses a central finite difference with step :math:`d = 0.1` J/kg. Parameters ---------- p : float Pressure in Pa. h : float Specific enthalpy in J/kg. fluid_data : dict Fluid property data: :code:`{fluid: {"mass_fraction": float, "wrapper": FluidPropertyWrapper}}`. mixing_rule : str, optional Mixing rule identifier; ignored for pure fluids. T0 : float, optional Temperature starting value in K for the iterative inversion. Returns ------- float :math:`\partial T / \partial h \big|_p` in K kg/J. Notes ----- .. math:: \frac{\partial T}{\partial h}\bigg|_p \approx \frac{T(p,\,h+d) - T(p,\,h-d)}{2\,d}, \quad d = 0.1 \text{ J/kg} """ d = 1e-1 upper = T_mix_ph(p, h + d, fluid_data, mixing_rule=mixing_rule, T0=T0) lower = T_mix_ph(p, h - d, fluid_data, mixing_rule=mixing_rule, T0=upper) return (upper - lower) / (2 * d)
[docs] def dT_mix_dph(p, h, fluid_data, mixing_rule=None, T0=None): r""" Calculate the partial derivative of temperature with respect to pressure at constant enthalpy. Uses a central finite difference with step :math:`d = 0.1` Pa. Parameters ---------- p : float Pressure in Pa. h : float Specific enthalpy in J/kg. fluid_data : dict Fluid property data: :code:`{fluid: {"mass_fraction": float, "wrapper": FluidPropertyWrapper}}`. mixing_rule : str, optional Mixing rule identifier; ignored for pure fluids. T0 : float, optional Temperature starting value in K for the iterative inversion. Returns ------- float :math:`\partial T / \partial p \big|_h` in K/Pa. Notes ----- .. math:: \frac{\partial T}{\partial p}\bigg|_h \approx \frac{T(p+d,\,h) - T(p-d,\,h)}{2\,d}, \quad d = 0.1 \text{ Pa} """ d = 1e-1 upper = T_mix_ph(p + d, h, fluid_data, mixing_rule=mixing_rule, T0=T0) lower = T_mix_ph(p - d, h, fluid_data, mixing_rule=mixing_rule, T0=upper) return (upper - lower) / (2 * d)
[docs] def dT_mix_ph_dfluid(p, h, fluid, fluid_data, mixing_rule=None, T0=None): r""" Calculate the partial derivative of temperature with respect to a fluid mass fraction. Perturbs the mass fraction of *fluid* by :math:`d = 10^{-5}` in each direction and applies a central finite difference. Parameters ---------- p : float Pressure in Pa. h : float Specific enthalpy in J/kg. fluid : str Name of the fluid whose mass fraction is perturbed. fluid_data : dict Fluid property data: :code:`{fluid: {"mass_fraction": float, "wrapper": FluidPropertyWrapper}}`. mixing_rule : str, optional Mixing rule identifier; ignored for pure fluids. T0 : float, optional Temperature starting value in K for the iterative inversion. Returns ------- float :math:`\partial T / \partial x_i \big|_{p,h}` in K. Notes ----- .. math:: \frac{\partial T}{\partial x_i}\bigg|_{p,h} \approx \frac{T(p,h,x_i+d) - T(p,h,x_i-d)}{2\,d}, \quad d = 10^{-5} """ d = 1e-5 fluid_data[fluid]["mass_fraction"] += d upper = T_mix_ph(p, h, fluid_data, mixing_rule=mixing_rule, T0=T0) fluid_data[fluid]["mass_fraction"] -= 2 * d lower = T_mix_ph(p, h, fluid_data, mixing_rule=mixing_rule, T0=upper) fluid_data[fluid]["mass_fraction"] += d return (upper - lower) / (2 * d)
[docs] def h_mix_pT(p, T, fluid_data, mixing_rule=None): r""" Calculate specific enthalpy from pressure and temperature. For a pure fluid delegates to the fluid wrapper. For mixtures dispatches to the mixing rule registered under *mixing_rule*. Parameters ---------- p : float Pressure in Pa. T : float Temperature in K. fluid_data : dict Fluid property data: :code:`{fluid: {"mass_fraction": float, "wrapper": FluidPropertyWrapper}}`. mixing_rule : str, optional Mixing rule identifier; ignored for pure fluids. Returns ------- float Specific enthalpy in J/kg. """ if get_number_of_fluids(fluid_data) == 1: pure_fluid = get_pure_fluid(fluid_data) return pure_fluid["wrapper"].h_pT(p, T) else: return MIXING_RULES.h_pT(mixing_rule)(p, T, fluid_data)
[docs] def h_mix_pQ(p, Q, fluid_data, mixing_rule=None): r""" Calculate specific enthalpy from pressure and vapour quality. Valid for single-component :code:`fluid_data`, including mixture-backend wrappers handled entirely inside the :class:`.FluidPropertyWrapper`. Raises :code:`ValueError` for multi-component :code:`fluid_data`. Parameters ---------- p : float Pressure in Pa. Q : float Vapour quality (0 = saturated liquid, 1 = saturated vapour). fluid_data : dict Fluid property data: :code:`{fluid: {"mass_fraction": float, "wrapper": FluidPropertyWrapper}}`. mixing_rule : str, optional Ignored. Returns ------- float Specific enthalpy in J/kg. Raises ------ ValueError If :code:`fluid_data` contains more than one fluid. """ return _single_fluid_wrapper(fluid_data).h_pQ(p, Q)
[docs] def dh_mix_dpQ(p, Q, fluid_data, mixing_rule=None): r""" Calculate the partial derivative of saturation enthalpy with respect to pressure. Uses a central finite difference with step :math:`d = 0.1` Pa. Parameters ---------- p : float Pressure in Pa. Q : float Vapour quality (0 = saturated liquid, 1 = saturated vapour). fluid_data : dict Fluid property data: :code:`{fluid: {"mass_fraction": float, "wrapper": FluidPropertyWrapper}}`. mixing_rule : str, optional Ignored. Returns ------- float :math:`\partial h / \partial p \big|_Q` in J/(kg Pa). Notes ----- .. math:: \frac{\partial h}{\partial p}\bigg|_Q \approx \frac{h(p+d,\,Q) - h(p-d,\,Q)}{2\,d}, \quad d = 0.1 \text{ Pa} """ d = 0.1 upper = h_mix_pQ(p + d, Q, fluid_data) lower = h_mix_pQ(p - d, Q, fluid_data) return (upper - lower) / (2 * d)
def _single_fluid_wrapper(fluid_data): r""" Return the fluid wrapper when :code:`fluid_data` contains exactly one entry. A single entry may represent either a chemically pure fluid or a mixture-backend fluid (e.g. a refrigerant blend handled entirely inside the :class:`.wrappers.FluidPropertyWrapper`). Both are treated identically by TESPy: no mixing rules are applied and all property calls are delegated directly to the wrapper. Parameters ---------- fluid_data : dict Fluid property data: :code:`{fluid: {"mass_fraction": float, "wrapper": FluidPropertyWrapper}}`. Returns ------- FluidPropertyWrapper The wrapper of the single fluid entry. Raises ------ ValueError If :code:`fluid_data` contains more than one fluid. """ if get_number_of_fluids(fluid_data) != 1: raise ValueError( "This function is not available for multi-component fluid data." ) return get_pure_fluid(fluid_data)["wrapper"]
[docs] def Q_mix_ph(p, h, fluid_data, mixing_rule=None): r""" Calculate vapour quality from pressure and specific enthalpy. Valid for single-component :code:`fluid_data`, including mixture-backend wrappers handled entirely inside the :class:`.FluidPropertyWrapper`. Raises :code:`ValueError` for multi-component :code:`fluid_data`. Parameters ---------- p : float Pressure in Pa. h : float Specific enthalpy in J/kg. fluid_data : dict Fluid property data: :code:`{fluid: {"mass_fraction": float, "wrapper": FluidPropertyWrapper}}`. mixing_rule : str, optional Ignored. Returns ------- float Vapour quality (0 = saturated liquid, 1 = saturated vapour). Raises ------ ValueError If :code:`fluid_data` contains more than one fluid. """ return _single_fluid_wrapper(fluid_data).Q_ph(p, h)
[docs] def phase_mix_ph(p, h, fluid_data, mixing_rule=None): r""" Return the thermodynamic phase state from pressure and specific enthalpy. Valid for single-component :code:`fluid_data`, including mixture-backend wrappers handled entirely inside the :class:`.FluidPropertyWrapper`. Raises :code:`ValueError` for multi-component :code:`fluid_data`. Parameters ---------- p : float Pressure in Pa. h : float Specific enthalpy in J/kg. fluid_data : dict Fluid property data: :code:`{fluid: {"mass_fraction": float, "wrapper": FluidPropertyWrapper}}`. mixing_rule : str, optional Ignored. Returns ------- str Phase identifier (e.g. :code:`"tp"` for two-phase, :code:`"g"` for gas, :code:`"l"` for liquid). Raises ------ ValueError If :code:`fluid_data` contains more than one fluid. """ if get_number_of_fluids(fluid_data) != 1: raise ValueError( "This function is not available for multi-component fluid data." ) return get_pure_fluid(fluid_data)["wrapper"].phase_ph(p, h)
[docs] def p_sat_T(T, fluid_data, mixing_rule=None): r""" Calculate saturation pressure from temperature. Valid for single-component :code:`fluid_data`, including mixture-backend wrappers handled entirely inside the :class:`.FluidPropertyWrapper`. Raises :code:`ValueError` for multi-component :code:`fluid_data`. Parameters ---------- T : float Temperature in K. fluid_data : dict Fluid property data: :code:`{fluid: {"mass_fraction": float, "wrapper": FluidPropertyWrapper}}`. mixing_rule : str, optional Ignored. Returns ------- float Saturation pressure in Pa. Raises ------ ValueError If :code:`fluid_data` contains more than one fluid. """ return _single_fluid_wrapper(fluid_data).p_sat(T)
[docs] def T_sat_p(p, fluid_data, mixing_rule=None): r""" Calculate saturation temperature from pressure. Valid for single-component :code:`fluid_data`, including mixture-backend wrappers handled entirely inside the :class:`.FluidPropertyWrapper`. Raises :code:`ValueError` for multi-component :code:`fluid_data`. Parameters ---------- p : float Pressure in Pa. fluid_data : dict Fluid property data: :code:`{fluid: {"mass_fraction": float, "wrapper": FluidPropertyWrapper}}`. mixing_rule : str, optional Ignored. Returns ------- float Saturation temperature in K. Raises ------ ValueError If :code:`fluid_data` contains more than one fluid. """ return _single_fluid_wrapper(fluid_data).T_sat(p)
[docs] def T_dew_p(p, fluid_data, mixing_rule=None): r""" Calculate dew point temperature from pressure. Valid for single-component :code:`fluid_data`, including mixture-backend wrappers handled entirely inside the :class:`.FluidPropertyWrapper`. Raises :code:`ValueError` for multi-component :code:`fluid_data`. Parameters ---------- p : float Pressure in Pa. fluid_data : dict Fluid property data: :code:`{fluid: {"mass_fraction": float, "wrapper": FluidPropertyWrapper}}`. mixing_rule : str, optional Ignored. Returns ------- float Dew point temperature in K. Raises ------ ValueError If :code:`fluid_data` contains more than one fluid. """ return _single_fluid_wrapper(fluid_data).T_dew(p)
[docs] def p_dew_T(T, fluid_data, mixing_rule=None): r""" Calculate dew point pressure from temperature. Valid for single-component :code:`fluid_data`, including mixture-backend wrappers handled entirely inside the :class:`.FluidPropertyWrapper`. Raises :code:`ValueError` for multi-component :code:`fluid_data`. Parameters ---------- T : float Temperature in K. fluid_data : dict Fluid property data: :code:`{fluid: {"mass_fraction": float, "wrapper": FluidPropertyWrapper}}`. mixing_rule : str, optional Ignored. Returns ------- float Dew point pressure in Pa. Raises ------ ValueError If :code:`fluid_data` contains more than one fluid. """ return _single_fluid_wrapper(fluid_data).p_dew(T)
[docs] def T_bubble_p(p, fluid_data, mixing_rule=None): r""" Calculate bubble point temperature from pressure. Valid for single-component :code:`fluid_data`, including mixture-backend wrappers handled entirely inside the :class:`.FluidPropertyWrapper`. Raises :code:`ValueError` for multi-component :code:`fluid_data`. Parameters ---------- p : float Pressure in Pa. fluid_data : dict Fluid property data: :code:`{fluid: {"mass_fraction": float, "wrapper": FluidPropertyWrapper}}`. mixing_rule : str, optional Ignored. Returns ------- float Bubble point temperature in K. Raises ------ ValueError If :code:`fluid_data` contains more than one fluid. """ return _single_fluid_wrapper(fluid_data).T_bubble(p)
[docs] def p_bubble_T(T, fluid_data, mixing_rule=None): r""" Calculate bubble point pressure from temperature. Valid for single-component :code:`fluid_data`, including mixture-backend wrappers handled entirely inside the :class:`.FluidPropertyWrapper`. Raises :code:`ValueError` for multi-component :code:`fluid_data`. Parameters ---------- T : float Temperature in K. fluid_data : dict Fluid property data: :code:`{fluid: {"mass_fraction": float, "wrapper": FluidPropertyWrapper}}`. mixing_rule : str, optional Ignored. Returns ------- float Bubble point pressure in Pa. Raises ------ ValueError If :code:`fluid_data` contains more than one fluid. """ return _single_fluid_wrapper(fluid_data).p_bubble(T)
[docs] def dT_sat_dp(p, fluid_data, mixing_rule=None): r""" Calculate the derivative of saturation temperature with respect to pressure. Uses a central finite difference with step :math:`d = 0.01` Pa. Parameters ---------- p : float Pressure in Pa. fluid_data : dict Fluid property data: :code:`{fluid: {"mass_fraction": float, "wrapper": FluidPropertyWrapper}}`. mixing_rule : str, optional Ignored. Returns ------- float :math:`dT_\text{sat} / dp` in K/Pa. Notes ----- .. math:: \frac{dT_\text{sat}}{dp} \approx \frac{T_\text{sat}(p+d) - T_\text{sat}(p-d)}{2\,d}, \quad d = 0.01 \text{ Pa} """ d = 0.01 upper = T_sat_p(p + d, fluid_data) lower = T_sat_p(p - d, fluid_data) return (upper - lower) / (2 * d)
[docs] def s_mix_ph(p, h, fluid_data, mixing_rule=None, T0=None): r""" Calculate specific entropy from pressure and specific enthalpy. For a pure fluid delegates to the fluid wrapper. For mixtures first determines the temperature via :func:`T_mix_ph`, then evaluates :func:`s_mix_pT`. Parameters ---------- p : float Pressure in Pa. h : float Specific enthalpy in J/kg. fluid_data : dict Fluid property data: :code:`{fluid: {"mass_fraction": float, "wrapper": FluidPropertyWrapper}}`. mixing_rule : str, optional Mixing rule identifier; ignored for pure fluids. T0 : float, optional Temperature starting value in K for the iterative inversion. Returns ------- float Specific entropy in J/(kg K). """ if get_number_of_fluids(fluid_data) == 1: pure_fluid = get_pure_fluid(fluid_data) return pure_fluid["wrapper"].s_ph(p, h) else: T = T_mix_ph(p, h, fluid_data, mixing_rule, T0) return s_mix_pT(p, T, fluid_data, mixing_rule)
[docs] def s_mix_pT(p, T, fluid_data, mixing_rule=None): r""" Calculate specific entropy from pressure and temperature. For a pure fluid delegates to the fluid wrapper. For mixtures dispatches to the mixing rule registered under *mixing_rule*. Parameters ---------- p : float Pressure in Pa. T : float Temperature in K. fluid_data : dict Fluid property data: :code:`{fluid: {"mass_fraction": float, "wrapper": FluidPropertyWrapper}}`. mixing_rule : str, optional Mixing rule identifier; ignored for pure fluids. Returns ------- float Specific entropy in J/(kg K). """ if get_number_of_fluids(fluid_data) == 1: pure_fluid = get_pure_fluid(fluid_data) return pure_fluid["wrapper"].s_pT(p, T) else: return MIXING_RULES.s_pT(mixing_rule)(p, T, fluid_data)
[docs] def T_mix_ps(p, s, fluid_data, mixing_rule=None, T0=None): r""" Calculate temperature from pressure and specific entropy. For a pure fluid delegates to the fluid wrapper. For humid air uses :func:`CoolProp.CoolProp.HAPropsSI`. For other mixtures inverts :func:`s_mix_pT` iteratively via :func:`.helpers.inverse_temperature_mixture`. Parameters ---------- p : float Pressure in Pa. s : float Specific entropy in J/(kg K). fluid_data : dict Fluid property data: :code:`{fluid: {"mass_fraction": float, "wrapper": FluidPropertyWrapper}}`. mixing_rule : str, optional Mixing rule identifier; ignored for pure fluids. T0 : float, optional Temperature starting value in K for the iterative inversion. Returns ------- float Temperature in K. """ if get_number_of_fluids(fluid_data) == 1: pure_fluid = get_pure_fluid(fluid_data) return pure_fluid["wrapper"].T_ps(p, s) else: if mixing_rule == "humidair": w = w_mix_ps_humidair(p, s, fluid_data) return HAPropsSI("T", "P", p, "S", s, "W", w) else: kwargs = { "p": p, "target_value": s, "fluid_data": fluid_data, "T0": T0, "f": MIXING_RULES.T_ps(mixing_rule) } return inverse_temperature_mixture(**kwargs)
[docs] def v_mix_ph(p, h, fluid_data, mixing_rule=None, T0=None): r""" Calculate specific volume from pressure and specific enthalpy. For a pure fluid returns the reciprocal of the wrapper density. For humid air uses :func:`CoolProp.CoolProp.HAPropsSI`. For other mixtures first determines the temperature via :func:`T_mix_ph`, then evaluates :func:`v_mix_pT`. Parameters ---------- p : float Pressure in Pa. h : float Specific enthalpy in J/kg. fluid_data : dict Fluid property data: :code:`{fluid: {"mass_fraction": float, "wrapper": FluidPropertyWrapper}}`. mixing_rule : str, optional Mixing rule identifier; ignored for pure fluids. T0 : float, optional Temperature starting value in K for the iterative inversion. Returns ------- float Specific volume in m³/kg. """ if get_number_of_fluids(fluid_data) == 1: pure_fluid = get_pure_fluid(fluid_data) return 1 / pure_fluid["wrapper"].d_ph(p, h) else: if mixing_rule == "humidair": w = w_mix_ph_humidair(p, h, fluid_data) return HAPropsSI("V", "P", p, "H", h, "W", w) else: T = T_mix_ph(p, h, fluid_data, mixing_rule, T0) return v_mix_pT(p, T, fluid_data, mixing_rule)
[docs] def dv_mix_dph(p, h, fluid_data, mixing_rule=None, T0=None): r""" Calculate the partial derivative of specific volume with respect to pressure at constant enthalpy. Uses a central finite difference with step :math:`d = 0.1` Pa. Parameters ---------- p : float Pressure in Pa. h : float Specific enthalpy in J/kg. fluid_data : dict Fluid property data: :code:`{fluid: {"mass_fraction": float, "wrapper": FluidPropertyWrapper}}`. mixing_rule : str, optional Mixing rule identifier; ignored for pure fluids. T0 : float, optional Temperature starting value in K for the iterative inversion. Returns ------- float :math:`\partial v / \partial p \big|_h` in m³/(kg Pa). Notes ----- .. math:: \frac{\partial v}{\partial p}\bigg|_h \approx \frac{v(p+d,\,h) - v(p-d,\,h)}{2\,d}, \quad d = 0.1 \text{ Pa} """ d = 1e-1 upper = v_mix_ph(p + d, h, fluid_data, mixing_rule=mixing_rule, T0=T0) lower = v_mix_ph(p - d, h, fluid_data, mixing_rule=mixing_rule, T0=upper) return (upper - lower) / (2 * d)
[docs] def dv_mix_pdh(p, h, fluid_data, mixing_rule=None, T0=None): r""" Calculate the partial derivative of specific volume with respect to enthalpy at constant pressure. Uses a central finite difference with step :math:`d = 0.1` J/kg. Parameters ---------- p : float Pressure in Pa. h : float Specific enthalpy in J/kg. fluid_data : dict Fluid property data: :code:`{fluid: {"mass_fraction": float, "wrapper": FluidPropertyWrapper}}`. mixing_rule : str, optional Mixing rule identifier; ignored for pure fluids. T0 : float, optional Temperature starting value in K for the iterative inversion. Returns ------- float :math:`\partial v / \partial h \big|_p` in m³ kg/J. Notes ----- .. math:: \frac{\partial v}{\partial h}\bigg|_p \approx \frac{v(p,\,h+d) - v(p,\,h-d)}{2\,d}, \quad d = 0.1 \text{ J/kg} """ d = 1e-1 upper = v_mix_ph(p, h + d, fluid_data, mixing_rule=mixing_rule, T0=T0) lower = v_mix_ph(p, h - d, fluid_data, mixing_rule=mixing_rule, T0=upper) return (upper - lower) / (2 * d)
[docs] def v_mix_pT(p, T, fluid_data, mixing_rule=None): r""" Calculate specific volume from pressure and temperature. For a pure fluid returns the reciprocal of the wrapper density. For mixtures dispatches to the mixing rule registered under *mixing_rule*. Parameters ---------- p : float Pressure in Pa. T : float Temperature in K. fluid_data : dict Fluid property data: :code:`{fluid: {"mass_fraction": float, "wrapper": FluidPropertyWrapper}}`. mixing_rule : str, optional Mixing rule identifier; ignored for pure fluids. Returns ------- float Specific volume in m³/kg. """ if get_number_of_fluids(fluid_data) == 1: pure_fluid = get_pure_fluid(fluid_data) return 1 / pure_fluid["wrapper"].d_pT(p, T) else: return MIXING_RULES.v_pT(mixing_rule)(p, T, fluid_data)
[docs] def viscosity_mix_ph(p, h, fluid_data, mixing_rule=None, T0=None): r""" Calculate dynamic viscosity from pressure and specific enthalpy. For a pure fluid delegates to the fluid wrapper. For humid air uses :func:`CoolProp.CoolProp.HAPropsSI`. For other mixtures first determines the temperature via :func:`T_mix_ph`, then evaluates :func:`viscosity_mix_pT`. Parameters ---------- p : float Pressure in Pa. h : float Specific enthalpy in J/kg. fluid_data : dict Fluid property data: :code:`{fluid: {"mass_fraction": float, "wrapper": FluidPropertyWrapper}}`. mixing_rule : str, optional Mixing rule identifier; ignored for pure fluids. T0 : float, optional Temperature starting value in K for the iterative inversion. Returns ------- float Dynamic viscosity in Pa s. """ if get_number_of_fluids(fluid_data) == 1: pure_fluid = get_pure_fluid(fluid_data) return pure_fluid["wrapper"].viscosity_ph(p, h) else: if mixing_rule == "humidair": w = w_mix_ph_humidair(p, h, fluid_data) return HAPropsSI("Visc", "P", p, "H", h, "W", w) else: T = T_mix_ph(p, h, fluid_data, mixing_rule, T0) return viscosity_mix_pT(p, T, fluid_data, mixing_rule)
[docs] def viscosity_mix_pT(p, T, fluid_data, mixing_rule=None): r""" Calculate dynamic viscosity from pressure and temperature. For a pure fluid delegates to the fluid wrapper. For mixtures dispatches to the mixing rule registered under *mixing_rule*. Parameters ---------- p : float Pressure in Pa. T : float Temperature in K. fluid_data : dict Fluid property data: :code:`{fluid: {"mass_fraction": float, "wrapper": FluidPropertyWrapper}}`. mixing_rule : str, optional Mixing rule identifier; ignored for pure fluids. Returns ------- float Dynamic viscosity in Pa s. """ if get_number_of_fluids(fluid_data) == 1: pure_fluid = get_pure_fluid(fluid_data) return pure_fluid["wrapper"].viscosity_pT(p, T) else: return MIXING_RULES.viscosity_pT(mixing_rule)(p, T, fluid_data)
[docs] def conductivity_mix_ph(p, h, fluid_data, mixing_rule=None, T0=None): r""" Calculate thermal conductivity from pressure and specific enthalpy. Only implemented for single-component :code:`fluid_data`. Raises :code:`NotImplementedError` for multi-component :code:`fluid_data`. Parameters ---------- p : float Pressure in Pa. h : float Specific enthalpy in J/kg. fluid_data : dict Fluid property data: :code:`{fluid: {"mass_fraction": float, "wrapper": FluidPropertyWrapper}}`. mixing_rule : str, optional Ignored. T0 : float, optional Ignored. Returns ------- float Thermal conductivity in W/(m K). Raises ------ NotImplementedError If :code:`fluid_data` contains more than one fluid. """ if get_number_of_fluids(fluid_data) == 1: pure_fluid = get_pure_fluid(fluid_data) return pure_fluid["wrapper"].conductivity_ph(p, h) else: msg = ( "Calculation of thermal conductivity is not implemented for " "TESPy based mixtures. You are happily invited to contribute it!" ) raise NotImplementedError(msg)