Path: blob/main/src/equations/equation_of_state_peng_robinson.jl
5586 views
# By default, Julia/LLVM does not use fused multiply-add operations (FMAs).1# Since these FMAs can increase the performance of many numerical algorithms,2# we need to opt-in explicitly.3# See https://ranocha.de/blog/Optimizing_EC_Trixi for further details.4@muladd begin5#! format: noindent67@doc raw"""8PengRobinson{RealT <: Real} <: AbstractEquationOfState910This defines the Peng-Robinson equation of state11given by the pressure and internal energy relations12```math13p = \frac{R T}{V - b} - \frac{a(T)}{V^2 + 2bV - b^2}, \quad e_{\text{internal}} = c_{v,0} T + K(a(T) - Ta'(T))14```15where ``V = \rho^{-1}`` and auxiliary expressions for ``a(T)`` and ``K`` are given by16```math17a(T) = a_0\left(1 + \kappa \left(1 - \sqrt{\frac{T}{T_0}}\right)\right)^2, \quad18K = \frac{1}{b 2\sqrt{2}} \log\left( \frac{V + (1 - b \sqrt{2})}{V + (1 + b\sqrt{2})}\right).19```20Moreover, ``c_v = c_{v,0} - K T a''(T)``.2122All expressions used here are taken from the following references:2324- P. Ma, Y. Lv, M. Ihme (2017)25An entropy-stable hybrid scheme for simulations of transcritical real-fluid flows26[DOI: 10.1016/j.jcp.2017.03.022](https://doi.org/10.1016/j.jcp.2017.03.022)2728- V. Michel-Dansac, A. Thomann (2024)29Towards a fully well-balanced and entropy-stable scheme for the Euler equations with30gravity: preserving isentropic steady solutions31[DOI: 10.1016/j.compfluid.2025.106853](https://doi.org/10.1016/j.compfluid.2025.106853)3233"""34struct PengRobinson{RealT <: Real} <: AbstractEquationOfState35R::RealT36a0::RealT37b::RealT38cv0::RealT39kappa::RealT40Tc::RealT41inv2sqrt2b::RealT42one_minus_sqrt2_b::RealT43one_plus_sqrt2_b::RealT44end4546"""47PengRobinson(a0, b, cv0, kappa, Tc, R = 8.31446261815324)4849Initializes a Peng-Robinson equation of state given values for physical constants.50Here, `R` is the universal gas constant, and the constants `a0, b, cv0, kappa, Tc`51follow the naming conventions in Section 2.2 of the following reference:5253- V. Michel-Dansac, A. Thomann (2025)54Towards a fully well-balanced and entropy-stable scheme for the Euler equations55with gravity: General equations of state56[DOI: 10.1016/j.compfluid.2025.106853](https://doi.org/10.1016/j.compfluid.2025.106853)57"""58function PengRobinson(a0, b, cv0, kappa, Tc, R = 8.31446261815324)59inv2sqrt2b = inv(2 * sqrt(2) * b)60one_minus_sqrt2_b = (1 - sqrt(2)) * b61one_plus_sqrt2_b = (1 + sqrt(2)) * b62return PengRobinson{typeof(a0)}(R, a0, b, cv0, kappa, Tc,63inv2sqrt2b, one_minus_sqrt2_b, one_plus_sqrt2_b)64end6566"""67PengRobinson(; RealT = Float64)6869By default, the units for the Peng-Robinson parameters are in mass basis70(such as kg / m^3) as opposed to molar basis units (such as kg / mol).7172The default parameters are for N2.73"""74function PengRobinson(; RealT = Float64)75Rgas = 8.3144626181532476molar_mass = 0.02801 * 1000 # kg/m377R = Rgas * 1000 / molar_mass78pc = 3.40e679Tc = 126.280omega = 0.037281cv0 = 743.282b = 0.077796 * R * Tc / pc83a0 = 0.457236 * (R * Tc)^2 / pc84kappa = 0.37464 + 1.54226 * omega - 0.26992 * omega^285return PengRobinson(RealT.((a0, b, cv0, kappa, Tc, R))...)86end8788# the default tolerance of 10 * eps() does not converge for most Peng-Robinson examples,89# so we choose a looser tolerance here. Researchers at the US Naval Research Lab noted90# that they typically just use 8 fixed Newton iterations for Peng-Robinson.91eos_newton_tol(eos::PengRobinson) = 1e-89293"""94pressure(V, T, eos::PengRobinson)9596Computes pressure for a Peng-Robinson gas from specific volume `V` and temperature `T`,97see also [`NonIdealCompressibleEulerEquations1D`](@ref).98"""99function pressure(V, T, eos::PengRobinson)100(; R, b) = eos101p = R * T / (V - b) - peng_robinson_a(T, eos) / (V^2 + 2 * b * V - b^2)102return p103end104105@doc raw"""106energy_internal_specific(V, T, eos::PengRobinson)107108Computes specific internal energy for a Peng-Robinson gas from specific volume `V` and temperature `T` as109``e_{\text{internal}} = c_{v,0} T + K_1 (a(T) - T a'(T))``.110"""111function energy_internal_specific(V, T, eos::PengRobinson)112(; cv0) = eos113K1 = calc_K1(V, eos)114e_internal = cv0 * T + K1 * (peng_robinson_a(T, eos) - T * peng_robinson_da(T, eos))115return e_internal116end117118@inline function heat_capacity_constant_volume(V, T, eos::PengRobinson)119(; cv0) = eos120K1 = calc_K1(V, eos)121cv = cv0 - K1 * T * peng_robinson_d2a(T, eos)122return cv123end124125function entropy_specific(V, T, eos::PengRobinson)126(; cv0, R, b) = eos127128# The specific entropy is defined up to some reference value s0, which is129# arbitrarily set to zero here.130K1 = calc_K1(V, eos)131return cv0 * log(T) + R * log(V - b) - peng_robinson_da(T, eos) * K1132end133134function speed_of_sound(V, T, eos::PengRobinson)135(; cv0, R, b) = eos136137dpdT_V, dpdV_T = calc_pressure_derivatives(V, T, eos)138139# calculate ratio of specific heats140K1 = calc_K1(V, eos)141d2aT = peng_robinson_d2a(T, eos)142cp0 = cv0 + R143cv = cv0 - K1 * T * d2aT144cp = cp0 - R - K1 * T * d2aT - T * dpdT_V^2 / dpdV_T145gamma = cp / cv146147# calculate bulk modulus, which should be positive148# for admissible thermodynamic states.149inv_kappa_T = -(V * dpdV_T)150c2 = gamma * V * inv_kappa_T151return sqrt(c2)152end153154function calc_pressure_derivatives(V, T, eos::PengRobinson)155(; R, b) = eos156denom = (V^2 + 2 * b * V - b^2)157a_T = peng_robinson_a(T, eos)158inv_V_minus_b = inv(V - b)159RdivVb = R * inv_V_minus_b160dpdT_V = RdivVb - peng_robinson_da(T, eos) / denom161dpdV_T = -RdivVb * T * inv_V_minus_b *162(1 - 2 * a_T / (R * T * (V + b) * (denom / (V^2 - b^2))^2))163return dpdT_V, dpdV_T164end165166# The following are auxiliary functions used in calculating the PR EOS167@inline function peng_robinson_a(T, eos::PengRobinson)168(; a0, kappa, Tc) = eos169return a0 * (1 + kappa * (1 - sqrt(T / Tc)))^2170end171@inline peng_robinson_da(T, eos) = ForwardDiff.derivative(T -> peng_robinson_a(T, eos),172T)173@inline peng_robinson_d2a(T, eos) = ForwardDiff.derivative(T -> peng_robinson_da(T, eos),174T)175176@inline function calc_K1(V, eos::PengRobinson)177(; inv2sqrt2b, one_minus_sqrt2_b, one_plus_sqrt2_b) = eos178K1 = inv2sqrt2b * log((V + one_minus_sqrt2_b) / (V + one_plus_sqrt2_b))179return K1180end181end # @muladd182183184