"""
Thermodynamic property and exergy calculations.
"""
from typing import TYPE_CHECKING, Literal
if TYPE_CHECKING:
import pandas as pd
import CoolProp.CoolProp as CP
[docs]
def generate_entropy_exergy_term(
fluid: str,
T: float,
P: float,
Q: float,
T0: float,
P0: float,
phase: Literal["gas", "liquid", "twophase"] = "gas",
) -> tuple[float, float, float]:
"""Calculate entropy, enthalpy, and exergy.
Parameters
----------
fluid : str
Fluid name.
T : float
Temperature [K].
P : float
Pressure [Pa].
Q : float
Quality (0 to 1).
T0 : float
Dead state temperature [K].
P0 : float
Dead state pressure [Pa].
phase : Literal['gas', 'liquid', 'twophase'], optional
Fluid phase. Default is 'gas'.
Returns
-------
tuple[float, float, float]
Entropy [J/kg-K], Enthalpy [J/kg], Exergy [J/kg].
"""
if phase == "twophase":
s = CP.PropsSI("S", "T", T, "Q", Q, fluid)
h = CP.PropsSI("H", "T", T, "Q", Q, fluid)
else:
s = CP.PropsSI("S", "T", T, "P", P, fluid)
h = CP.PropsSI("H", "T", T, "P", P, fluid)
s0 = CP.PropsSI("S", "T", T0, "P", P0, fluid)
h0 = CP.PropsSI("H", "T", T0, "P", P0, fluid)
exergy = (h - h0) - T0 * (s - s0)
return s, h, exergy
[docs]
def calc_energy_flow(G, T, T0):
"""Calculate energy flow rate.
Parameters
----------
G : float or pd.Series
Heat capacity flow rate (mass_flow * Cp) [W/K].
T : float or pd.Series
Current temperature [K].
T0 : float or pd.Series
Reference/dead state temperature [K].
Returns
-------
float or pd.Series
Energy flow rate [W].
"""
return G * (T - T0)
[docs]
def calc_exergy_flow(G, T, T0):
"""Calculate exergy flow rate.
Parameters
----------
G : float or pd.Series
Heat capacity flow rate (mass_flow * Cp) [W/K].
T : float or pd.Series
Current temperature [K].
T0 : float or pd.Series
Reference/dead state temperature [K].
Returns
-------
float or pd.Series
Exergy flow rate [W].
"""
import numpy as np
import pandas as pd
is_series = isinstance(T, pd.Series) or isinstance(T0, pd.Series) or isinstance(G, pd.Series)
if is_series:
# 벡터화 처리: T <= 0 또는 T0 <= 0인 경우 0으로 마스킹
invalid = (T <= 0) | (T0 <= 0)
T_safe = np.where(T <= 0, 1.0, T)
T0_safe = np.where(T0 <= 0, 1.0, T0)
result = G * ((T_safe - T0_safe) - T0_safe * np.log(T_safe / T0_safe))
if isinstance(result, pd.Series):
return result.mask(invalid, 0.0)
result = np.where(invalid, 0.0, result)
return pd.Series(result, index=T.index if isinstance(T, pd.Series) else None)
else:
if T <= 0 or T0 <= 0:
return 0.0
return float(G * ((T - T0) - T0 * np.log(T / T0)))
[docs]
def calc_refrigerant_exergy(
df: "pd.DataFrame",
ref: str,
T0_K: "pd.Series",
P0: float = 101325,
) -> "pd.DataFrame":
"""Calculate refrigerant state-point exergy using pre-computed properties.
Uses the entropy (``s_ref_*``) and enthalpy (``h_ref_*``) columns
already present in ``df`` (produced by ``calc_ref_state``) to compute
specific exergy and exergy flow rate for each refrigerant state point.
Dead-state properties (h0, s0) are evaluated at (T0, P0) for the
given refrigerant using CoolProp (vectorized via unique T0 values).
Parameters
----------
df : pd.DataFrame
DataFrame containing pre-computed enthalpy ``h_ref_* [J/kg]``,
entropy ``s_ref_* [J/(kg·K)]``, and mass-flow
``m_dot_ref [kg/s]`` columns.
ref : str
CoolProp refrigerant identifier (e.g. ``'R410A'``).
T0_K : pd.Series
Dead-state (environment) temperature per row [K].
P0 : float
Dead-state pressure [Pa] (default ``101325``).
Returns
-------
pd.DataFrame
``df`` with columns added per state point:
``x_ref_{name} [J/kg]``, ``X_ref_{name} [W]``.
Notes
-----
- Exergy equation: x = (h − h0) − T0·(s − s0) [J/kg]
- Exergy flow: X = ṁ · x [W]
- Rows with NaN enthalpy/entropy propagate NaN naturally.
"""
import numpy as np
import pandas as pd
# State points: (name, enthalpy_col, entropy_col)
_STATES = [
("cmp_in", "h_ref_cmp_in [J/kg]", "s_ref_cmp_in [J/(kg·K)]"),
("cmp_out", "h_ref_cmp_out [J/kg]", "s_ref_cmp_out [J/(kg·K)]"),
("exp_in", "h_ref_exp_in [J/kg]", "s_ref_exp_in [J/(kg·K)]"),
("exp_out", "h_ref_exp_out [J/kg]", "s_ref_exp_out [J/(kg·K)]"),
]
# Dead-state properties — vectorized via unique T0 values
t0_unique = T0_K.dropna().unique()
h0_map: dict[float, float] = {}
s0_map: dict[float, float] = {}
for t0 in t0_unique:
try:
h0_map[t0] = CP.PropsSI("H", "T", float(t0), "P", P0, ref)
s0_map[t0] = CP.PropsSI("S", "T", float(t0), "P", P0, ref)
except Exception:
h0_map[t0] = np.nan
s0_map[t0] = np.nan
h0: pd.Series = T0_K.map(h0_map)
s0: pd.Series = T0_K.map(s0_map)
m_dot: pd.Series = df["m_dot_ref [kg/s]"] if "m_dot_ref [kg/s]" in df.columns else pd.Series(np.nan, index=df.index)
for name, h_col, s_col in _STATES:
if h_col not in df.columns or s_col not in df.columns:
continue
h = df[h_col]
s = df[s_col]
# Specific exergy [J/kg]: x = (h - h0) - T0 * (s - s0)
x_val = (h - h0) - T0_K * (s - s0)
# Exergy flow [W]: X = m_dot * x
X_val = m_dot * x_val
df[f"x_ref_{name} [J/kg]"] = x_val
df[f"X_ref_{name} [W]"] = X_val
return df
[docs]
def convert_electricity_to_exergy(
df: "pd.DataFrame",
) -> "pd.DataFrame":
"""Copy all electricity columns (``E_*``) to exergy columns (``X_*``).
Electrical energy is 100 %% pure exergy, so ``X = E`` for all
electricity-consumption columns.
The function searches for columns matching the pattern
``E_xxx [W]`` and creates corresponding ``X_xxx [W]`` columns.
Parameters
----------
df : pd.DataFrame
DataFrame with ``E_xxx [W]`` columns.
Returns
-------
pd.DataFrame
``df`` with ``X_xxx [W]`` columns added.
"""
for col in df.columns:
if str(col).startswith("E_") and str(col).endswith(" [W]"):
# E_cmp [W] → X_cmp [W]
x_col: str = "X_" + str(col)[2:]
df[x_col] = df[col]
return df