Source code for engineeringkit.thermodynamics

"""Classical thermodynamics and idealized power/refrigeration cycles."""

from dataclasses import dataclass

import numpy as np

from engineeringkit._validation import finite as _finite
from engineeringkit._validation import nonnegative as _nonnegative
from engineeringkit._validation import positive as _positive

R_UNIVERSAL = 8.31446261815324


[docs] def ideal_gas_pressure(n, T, V): """Ideal gas pressure. Parameters ---------- n : mol Input in the units stated above. T : K (absolute) Input in the units stated above. V : m^3 Input in the units stated above. Returns ------- result Pa. Notes ----- Assumptions, governing equations and validity limits are discussed in :doc:`/guides/thermodynamics`. SI inputs are required; raw numbers carry no dimensional metadata. """ _finite(n=n, T=T, V=V) _positive(n=n, T=T, V=V) return n * R_UNIVERSAL * T / V
[docs] def ideal_gas_temperature(P, V, n): """Ideal gas temperature. Parameters ---------- P : Pa Input in the units stated above. V : m^3 Input in the units stated above. n : mol Input in the units stated above. Returns ------- result K. Notes ----- Assumptions, governing equations and validity limits are discussed in :doc:`/guides/thermodynamics`. SI inputs are required; raw numbers carry no dimensional metadata. """ _finite(P=P, V=V, n=n) _positive(P=P, V=V, n=n) return P * V / (n * R_UNIVERSAL)
[docs] def first_law_heat(delta_u, work_by_system): """First law heat. Parameters ---------- delta_u : J Input in the units stated above. work_by_system : J Input in the units stated above. Returns ------- result J. Notes ----- Assumptions, governing equations and validity limits are discussed in :doc:`/guides/thermodynamics`. SI inputs are required; raw numbers carry no dimensional metadata. """ return delta_u + work_by_system
[docs] def entropy_change_isothermal_ideal_gas(n, T, V1, V2): """Entropy change isothermal ideal gas. Parameters ---------- n : mol Input in the units stated above. T : K (absolute) Input in the units stated above. V1 : m^3 Input in the units stated above. V2 : m^3 Input in the units stated above. Returns ------- result J/K. Notes ----- Assumptions, governing equations and validity limits are discussed in :doc:`/guides/thermodynamics`. SI inputs are required; raw numbers carry no dimensional metadata. """ _finite(n=n, T=T, V1=V1, V2=V2) _positive(n=n, T=T, V1=V1, V2=V2) return n * R_UNIVERSAL * np.log(V2 / V1)
[docs] def carnot_efficiency(T_hot, T_cold): """Carnot efficiency. Parameters ---------- T_hot : K (absolute) Input in the units stated above. T_cold : K (absolute) Input in the units stated above. Returns ------- result 1. Notes ----- Assumptions, governing equations and validity limits are discussed in :doc:`/guides/thermodynamics`. SI inputs are required; raw numbers carry no dimensional metadata. """ _finite(T_hot=T_hot, T_cold=T_cold) _positive(T_hot=T_hot, T_cold=T_cold) if T_hot <= T_cold or T_cold <= 0: raise ValueError("require T_hot > T_cold > 0") return 1 - T_cold / T_hot
[docs] def carnot_cop_refrigerator(T_hot, T_cold): """Carnot cop refrigerator. Parameters ---------- T_hot : K (absolute) Input in the units stated above. T_cold : K (absolute) Input in the units stated above. Returns ------- result 1. Notes ----- Assumptions, governing equations and validity limits are discussed in :doc:`/guides/thermodynamics`. SI inputs are required; raw numbers carry no dimensional metadata. """ _finite(T_hot=T_hot, T_cold=T_cold) _positive(T_hot=T_hot, T_cold=T_cold) if T_hot <= T_cold: raise ValueError("hot reservoir must be hotter") return T_cold / (T_hot - T_cold)
[docs] def carnot_cop_heat_pump(T_hot, T_cold): """Carnot cop heat pump. Parameters ---------- T_hot : K (absolute) Input in the units stated above. T_cold : K (absolute) Input in the units stated above. Returns ------- result 1. Notes ----- Assumptions, governing equations and validity limits are discussed in :doc:`/guides/thermodynamics`. SI inputs are required; raw numbers carry no dimensional metadata. """ _finite(T_hot=T_hot, T_cold=T_cold) _positive(T_hot=T_hot, T_cold=T_cold) if T_hot <= T_cold: raise ValueError("hot reservoir must be hotter") return T_hot / (T_hot - T_cold)
[docs] def otto_efficiency(compression_ratio, gamma=1.4): """Otto efficiency. Parameters ---------- compression_ratio : 1 Input in the units stated above. gamma : 1 Input in the units stated above. Returns ------- result 1. Notes ----- Assumptions, governing equations and validity limits are discussed in :doc:`/guides/thermodynamics`. SI inputs are required; raw numbers carry no dimensional metadata. """ _finite(compression_ratio=compression_ratio, gamma=gamma) _positive(compression_ratio=compression_ratio) if compression_ratio < 1 or gamma <= 1: raise ValueError("require r >= 1 and gamma > 1") return 1 - compression_ratio ** (1 - gamma)
[docs] def diesel_efficiency(r, cutoff, gamma=1.4): """Diesel efficiency. Parameters ---------- r : 1 Input in the units stated above. cutoff : 1 Input in the units stated above. gamma : 1 Input in the units stated above. Returns ------- result 1. Notes ----- Assumptions, governing equations and validity limits are discussed in :doc:`/guides/thermodynamics`. SI inputs are required; raw numbers carry no dimensional metadata. """ _finite(r=r, cutoff=cutoff, gamma=gamma) if not 1 <= cutoff <= r or gamma <= 1: raise ValueError("require 1 <= cutoff <= r and gamma > 1") if cutoff == 1: return otto_efficiency(r, gamma) return 1 - np.expm1(gamma * np.log(cutoff)) / (gamma * r ** (gamma - 1) * (cutoff - 1))
[docs] def brayton_efficiency(pressure_ratio, gamma=1.4): """Brayton efficiency. Parameters ---------- pressure_ratio : 1 Input in the units stated above. gamma : 1 Input in the units stated above. Returns ------- result 1. Notes ----- Assumptions, governing equations and validity limits are discussed in :doc:`/guides/thermodynamics`. SI inputs are required; raw numbers carry no dimensional metadata. """ _finite(pressure_ratio=pressure_ratio, gamma=gamma) if pressure_ratio < 1 or gamma <= 1: raise ValueError("require pressure ratio >= 1 and gamma > 1") return 1 - pressure_ratio ** ((1 - gamma) / gamma)
[docs] def isentropic_temperature_ratio(pressure_ratio, gamma=1.4): """Isentropic temperature ratio. Parameters ---------- pressure_ratio : 1 Input in the units stated above. gamma : 1 Input in the units stated above. Returns ------- result 1. Notes ----- Assumptions, governing equations and validity limits are discussed in :doc:`/guides/thermodynamics`. SI inputs are required; raw numbers carry no dimensional metadata. """ _finite(pressure_ratio=pressure_ratio, gamma=gamma) _positive(pressure_ratio=pressure_ratio) if gamma <= 1: raise ValueError("gamma must exceed 1") return pressure_ratio ** ((gamma - 1) / gamma)
[docs] def specific_internal_energy_ideal_gas(T, cv): """Specific internal energy ideal gas. Parameters ---------- T : K (absolute) Input in the units stated above. cv : J/(kg K) Input in the units stated above. Returns ------- result J/kg. Notes ----- Assumptions, governing equations and validity limits are discussed in :doc:`/guides/thermodynamics`. SI inputs are required; raw numbers carry no dimensional metadata. """ _finite(T=T, cv=cv) _nonnegative(T=T) _positive(cv=cv) return cv * T
[docs] def specific_enthalpy_ideal_gas(T, cp): """Specific enthalpy ideal gas. Parameters ---------- T : K (absolute) Input in the units stated above. cp : J/(kg K) Input in the units stated above. Returns ------- result J/kg. Notes ----- Assumptions, governing equations and validity limits are discussed in :doc:`/guides/thermodynamics`. SI inputs are required; raw numbers carry no dimensional metadata. """ _finite(T=T, cp=cp) _nonnegative(T=T) _positive(cp=cp) return cp * T
[docs] def exergy_heat(Q, T_source, T0): """Exergy heat. Parameters ---------- Q : J Input in the units stated above. T_source : K (absolute) Input in the units stated above. T0 : K (absolute) Input in the units stated above. Returns ------- result J. Notes ----- Assumptions, governing equations and validity limits are discussed in :doc:`/guides/thermodynamics`. SI inputs are required; raw numbers carry no dimensional metadata. """ _finite(Q=Q, T_source=T_source, T0=T0) _positive(T_source=T_source, T0=T0) return Q * (1 - T0 / T_source)
[docs] def heat_engine_work(Qin, Qout): """Heat engine work. Parameters ---------- Qin : J Input in the units stated above. Qout : J Input in the units stated above. Returns ------- result J. Notes ----- Assumptions, governing equations and validity limits are discussed in :doc:`/guides/thermodynamics`. SI inputs are required; raw numbers carry no dimensional metadata. """ _finite(Qin=Qin, Qout=Qout) _nonnegative(Qin=Qin, Qout=Qout) if Qout > Qin: raise ValueError("heat engine requires Qin >= Qout") return Qin - Qout
[docs] def thermal_efficiency(Qin, Qout): """Thermal efficiency. Parameters ---------- Qin : J Input in the units stated above. Qout : J Input in the units stated above. Returns ------- result 1. Notes ----- Assumptions, governing equations and validity limits are discussed in :doc:`/guides/thermodynamics`. SI inputs are required; raw numbers carry no dimensional metadata. """ _finite(Qin=Qin, Qout=Qout) _positive(Qin=Qin) _nonnegative(Qout=Qout) if Qout > Qin: raise ValueError("heat engine requires Qin >= Qout") return 1 - Qout / Qin
[docs] @dataclass(frozen=True) class OttoCycle: compression_ratio: float gamma: float = 1.4 def __post_init__(self): otto_efficiency(self.compression_ratio, self.gamma) @property def efficiency(self): return otto_efficiency(self.compression_ratio, self.gamma)
[docs] def summary(self): return {"compression_ratio": self.compression_ratio, "gamma": self.gamma, "efficiency": self.efficiency}
[docs] def clausius_inequality(cyclic_heat_over_temperature): """Return whether a discretized cyclic Clausius integral satisfies sum(dQ/T) <= 0. Parameters ---------- cyclic_heat_over_temperature : J/K Input in the units stated above. Returns ------- result bool. Notes ----- Assumptions, governing equations and validity limits are discussed in :doc:`/guides/thermodynamics`. SI inputs are required; raw numbers carry no dimensional metadata. """ return float(np.sum(cyclic_heat_over_temperature)) <= 1e-12
[docs] def reversible_isothermal_work_ideal_gas(n, T, V1, V2): """Reversible isothermal work ideal gas. Parameters ---------- n : mol Input in the units stated above. T : K (absolute) Input in the units stated above. V1 : m^3 Input in the units stated above. V2 : m^3 Input in the units stated above. Returns ------- result J. Notes ----- Assumptions, governing equations and validity limits are discussed in :doc:`/guides/thermodynamics`. SI inputs are required; raw numbers carry no dimensional metadata. """ _finite(n=n, T=T, V1=V1, V2=V2) _positive(n=n, T=T, V1=V1, V2=V2) return n * R_UNIVERSAL * T * np.log(V2 / V1)
[docs] def isentropic_pressure_volume(p1, v1, v2, gamma=1.4): """Isentropic pressure volume. Parameters ---------- p1 : Pa Input in the units stated above. v1 : consistent volume units (e.g. m^3/kg) Input in the units stated above. v2 : consistent volume units (e.g. m^3/kg) Input in the units stated above. gamma : 1 Input in the units stated above. Returns ------- result Pa. Notes ----- Assumptions, governing equations and validity limits are discussed in :doc:`/guides/thermodynamics`. SI inputs are required; raw numbers carry no dimensional metadata. """ _finite(p1=p1, v1=v1, v2=v2, gamma=gamma) _positive(p1=p1, v1=v1, v2=v2) if gamma <= 1: raise ValueError("gamma must exceed 1") return p1 * (v1 / v2) ** gamma
[docs] def rankine_ideal_efficiency(h1, h2, h3, h4): """Ideal Rankine efficiency from pump-in, boiler-out, turbine-out, condenser-out enthalpies. Parameters ---------- h1 : J/kg Input in the units stated above. h2 : J/kg Input in the units stated above. h3 : J/kg Input in the units stated above. h4 : J/kg Input in the units stated above. Returns ------- result 1. Notes ----- Assumptions, governing equations and validity limits are discussed in :doc:`/guides/thermodynamics`. SI inputs are required; raw numbers carry no dimensional metadata. """ _finite(h1=h1, h2=h2, h3=h3, h4=h4) if not h2 > h1 >= h4 or h2 < h3 or h3 < h4 or h2 - h3 < h1 - h4: raise ValueError("enthalpies must describe a power-producing cycle") wt = h2 - h3 wp = h1 - h4 qin = h2 - h1 return (wt - wp) / qin
[docs] def refrigeration_cop(q_cold, work_input): """Refrigeration cop. Parameters ---------- q_cold : J Input in the units stated above. work_input : J Input in the units stated above. Returns ------- result 1. Notes ----- Assumptions, governing equations and validity limits are discussed in :doc:`/guides/thermodynamics`. SI inputs are required; raw numbers carry no dimensional metadata. """ _finite(q_cold=q_cold, work_input=work_input) _nonnegative(q_cold=q_cold) _positive(work_input=work_input) return q_cold / work_input
[docs] def heat_pump_cop(q_hot, work_input): """Heat pump cop. Parameters ---------- q_hot : J Input in the units stated above. work_input : J Input in the units stated above. Returns ------- result 1. Notes ----- Assumptions, governing equations and validity limits are discussed in :doc:`/guides/thermodynamics`. SI inputs are required; raw numbers carry no dimensional metadata. """ _finite(q_hot=q_hot, work_input=work_input) _positive(work_input=work_input) if q_hot < work_input: raise ValueError("heat-pump heat output must be >= work input") return q_hot / work_input
[docs] def exergy_destruction(T0, entropy_generation): """Exergy destruction. Parameters ---------- T0 : K (absolute) Input in the units stated above. entropy_generation : J/K Input in the units stated above. Returns ------- result J. Notes ----- Assumptions, governing equations and validity limits are discussed in :doc:`/guides/thermodynamics`. SI inputs are required; raw numbers carry no dimensional metadata. """ _finite(T0=T0, entropy_generation=entropy_generation) _positive(T0=T0) _nonnegative(entropy_generation=entropy_generation) return T0 * entropy_generation