yaeos is a library based on Equation-of-State calculations. An Equation of
State (EOS) is a mathematical model that describes the relationship between
thermodynamic properties of a system, typically pressure (P), volume (V), and
temperature (T). These equations are fundamental in physics, chemistry, and
engineering, especially in thermodynamics and fluid mechanics.
EOS models are used to predict phase behavior, thermodynamic properties, and equilibrium conditions of gases, liquids, solids, and mixtures. They are widely applied in areas such as chemical engineering, petroleum engineering, and material science. From the ideal gas law, to cubic equations of state and SAFT, to more complex models, EOS are essential tools for the study and design of processes.
The classic way of defining an EOS is though a mathematical equation that express the pressure as a function of the volume and temperature. For example, the Van der Waals EOS is defined as:
Where is the pressure, is the molar volume, is the temperature, is the gas constant, and and are parameters that depend on the substance. For mixtures, the and parameters are calculated from the pure components parameters using a mixing rule.
The modern way of defining an EOS is through a mathematical model that express
the residual Helmholtz free energy as a function of the mole number, volume and
temperature. This approach is more general and allows for a more flexible
definition of the EOS in the context of implementing it in code. All
thermodynamic properties can be calculated from the residual Helmholtz free
energy and its derivatives. yaeos is based on this approach.
This section of the documentation will guide you through the thermodynamic properties that can be calculated with yaeos using Helmholtz free energy models. Additionally, this page serves as a summary of how all these properties can be derived from the Helmholtz free energy and its derivatives.
First, you might be used to the classic way of defining an EOS. This is normal since it is the most common way of explaining them in undergraduate thermodynamics courses. So, a very reasonable question is: “how can I obtain the expression for the residual Helmholtz free energy from the expression?”. The answer is the following:
We are ready to go now. We leave an example of how to instantiate a Peng-Robinson EOS model in the next code block if you want to start evaluating properties right away.
program main
use yaeos
! We will instantiate a Peng-Robinson EOS model with two components
! Set the variable `model` as a generic `ArModel`
class(ArModel), allocatable :: model
! Set the variables that we're going to use as variable length arrays
real(pr) :: tc(2), pc(2), w(2)
tc = [190, 310] ! Critical temperatures [K]
pc = [14, 30] ! Critical pressures [bar]
w = [0.001, 0.03] ! Acentric factors [-]
! Now we set our model as the PengRobinson76 equation of state.
model = PengRobinson76(tc, pc, w)
end program main
The examples provided for each property must be treated as a continuation of the previous code block (before the end program statement).
The reference used for the equations of each property is “Thermodynamic Models: Fundamentals and Computational Aspects” by Michael L. Michelsen and Jørgen M. Mollerup - 2th edition (abbreviated as: MM). For each property, we are going to refer to the chapter and equation for a rapid search.
The MM book sometimes express the equations in terms of (reduced residual Helmholtz free energy):
On this documentation section we are going to express all the properties in terms of so a little algebraic work is required.
Finally, we are going to use the MM notation, for that:
$n$ is the total number of moles, and is the number of moles of component . Therefore:
The moles number vector is denoted as .
On partial derivatives, the subscript indicates the variables that are kept constant. For example:
means the partial derivative of pressure with respect to volume keeping temperature and moles vector constant. With a partial derivative respect to the mole number of component we have:
With a rigorous notation, the partial derivative of pressure with respect to the mole number of component keeping volume and temperature constant should be:
On the notation the subscript means that all the mole numbers of the other components are kept constant. This is omitted for simplicity.
MM - Chapter 2 - Equations 7, 9, 10, and 11
Pressure can be calculated from the residual Helmholtz free energy as follows:
pressure: block
real(pr) :: n(2), T, V, P, dPdV, dPdT, dPdn(2)
n = [3.0_pr, 7.0_pr] ! Number of moles of each component [mol]
T = 300.0_pr ! Set temperature to 300 K
V = 0.1_pr ! Set volume to 0.1 L
! Calculate pressure and its derivatives
call model%pressure(n, V, T, P=P, dPdV=dPdV, dPdT=dPdT, dPdn=dPdn)
print *, "Pressure: ", P
print *, "dPdV: ", dPdV
print *, "dPdT: ", dPdT
print *, "dPdn: ", dPdn
end block pressure
As you must know, given a temperature and pressure, the volume has not a unique solution. In the case of a cubic EOS, there are three possible solutions for the volume at a given temperature, pressure and composition. For this reason, the volume is calculated iteratively. Provided , , and we fix and and iterate over the volume until the pressure is the same as the input pressure.
To learm how the initial guess for is obtained, please refer to the book “Thermodynamic Models: Fundamentals and Computational Aspects” by Michael L. Michelsen and Jørgen M. Mollerup. Volume derivatives can be calculated in terms of pressure derivatives, this will appear later on this documentation.
volume: block
real(pr) :: n(2), T, P, V
n = [3.0_pr, 7.0_pr] ! Number of moles of each component [mol]
T = 300.0_pr ! Set temperature to 300 K
P = 1.0_pr ! Set pressure to 1 bar
! Calculate different volume roots
call model%volume(n, P, T, V=V, root="liquid")
print *, "Liquid volume: ", V
call model%volume(n, P, T, V=V, root="vapor")
print *, "Vapor volume: ", V
call model%volume(n, P, T, V=V, root="stable")
print *, "Stable volume root: ", V
end block volume
As you may know, residual properties are the difference between the actual property and the ideal gas property at the same temperature and volume (in this case). For that, for a generic residual property , we have:
Notice that the ideal gas property is calculated at the same volume of the real fluid. If the ideal gas is at the same temperature and volume of the real fluid, the ideal gas is not at the same pressure as the real fluid. For that, two different residual properties can be calculated:
There is a relation between both residual properties. In this section we are going to calculate all the residual properties.
MM - Chapter 2 - Equations 13, 14, 15, and 16
Natural logarithm of fugacity coefficients specifing and are calculated as follows:
Remember that the compressibility factor is calculated as:
Next, the derivatives:
lnphi_vt: block
real(pr) :: n(2), T, V, lnPhi(2), dlnPhidP(2), dlnPhidT(2), dlnPhidn(2,2)
n = [3.0_pr, 7.0_pr] ! Number of moles of each component [mol]
T = 300.0_pr ! Set temperature to 300 K
V = 1.0_pr ! Set volume to 1 liter
call eos%lnphi_vt(&
n, V, T, lnPhi=lnPhi, &
dlnPhidP=dlnPhidP, dlnPhidT=dlnPhidT, dlnPhidn=dlnPhidn &
)
end block lnphi_vt
The subroutine lnphi_vt also allows to obtain the value of pressure and its
derivatives at the specified volume and temperature. Since those values are
needed to calculate the fugacity coefficients, you can take advantage of this
feature to avoid calculating them twice.
lnphi_vt_p: block
real(pr) :: n(2), T, V, lnPhi(2), P, dPdV, dPdT, dPdn(2)
n = [3.0_pr, 7.0_pr] ! Number of moles of each component [mol]
T = 300.0_pr ! Set temperature to 300 K
V = 1.0_pr ! Set volume to 1 liter
call eos%lnphi_vt(&
n, V, T, lnPhi=lnPhi, P=P, dPdV=dPdV, dPdT=dPdT, dPdn=dPdn &
)
end block lnphi_vt_p
Alternative way of calculating fugacity directly from the residual Helmholtz free energy:
fugacity_vt: block
real(pr) :: n(2), T, V, lnf(2), dlnfdV(2), dlnfdT(2), dlnfdn(2,2)
n = [3.0_pr, 7.0_pr] ! Number of moles of each component [mol]
T = 300.0_pr ! Set temperature to 300 K
V = 1.0_pr ! Set volume to 1 liter
call eos%lnfug_vt(&
n, V, T, lnf, &
dlnfdV, dlnfdT, dlnfdn, &
)
end block fugacity_vt
MM - Chapter 2 - Equation 17
We start explaining the residual entropy because it is the easiest property to calculate. Also, helps us to understand how to calculate the next properties.
The residual entropy is calculated as follows:
And its derivatives:
residual_entropy: block
real(pr) :: n(2), T, V, S, dSdV, dSdT, dSdn(2)
n = [3.0_pr, 7.0_pr] ! Number of moles of each component [mol]
T = 300.0_pr ! Set temperature to 300 K
V = 1.0_pr ! Set volume to 1 liter
! Calculate residual entropy and its derivatives
call eos%residual_entropy(n, V, T, Sr=Sr, SrT=SrT, SrV=SrV, Srn=Srn)
print *, "Residual entropy: ", Sr
print *, "SrV: ", SrV
print *, "SrT: ", SrT
print *, "Srn: ", Srn
end block residual_entropy
MM - Chapter 1 - Table 6 (Equation XII)
MM - Chapter 2 - Equation 20
The residual enthalpy is calculated as follows:
We know that pressure can be calculated as:
Then, we can obtain:
And we also know how to calculate residual entropy as:
Then, we can obtain the residual enthalpy as:
And its derivatives:
residual_enthalpy: block
real(pr) :: n(2), T, V, Hr, HrV, HrT, Hrn(2)
n = [3.0_pr, 7.0_pr] ! Number of moles of each component [mol]
T = 300.0_pr ! Set temperature to 300 K
V = 1.0_pr ! Set volume to 1 liter
! Calculate residual enthalpy and its derivatives
call eos%residual_enthalpy(n, V, T, Hr=Hr, HrV=HrV, HrT=HrT, Hrn=Hrn)
print *, "Residual enthalpy: ", Hr
print *, "HrV: ", HrV
print *, "HrT: ", HrT
print *, "Hrn: ", Hrn
end block residual_enthalpy
MM - Chapter 1 - Table 6 (Equation VI)
The residual Gibbs free energy is calculated as follows:
As with residual enthalpy we can easily deduce:
For that:
And its derivatives:
residual_gibbs: block
real(pr) :: n(2), T, V, Gr, GrV, GrT, Grn(2)
n = [3.0_pr, 7.0_pr] ! Number of moles of each component [mol]
T = 300.0_pr ! Set temperature to 300 K
V = 1.0_pr ! Set volume to 1 liter
! Calculate residual Gibbs free energy and its derivatives
call eos%residual_gibbs(n, V, T, Gr=Gr, GrV=GrV, GrT=GrT, Grn=Grn)
print *, "Residual Gibbs free energy: ", Gr
print *, "GrV: ", GrV
print *, "GrT: ", GrT
print *, "Grn: ", Grn
end block residual_gibbs
Little check of algebraic transformations:
From MM - Chapter 2 - Equation 22 we know that:
From MM - Chapter 1 - Table 6 (Equations IX, XII, and XIII) we know that:
Replacing on the first equation we have:
Which of course lead us to:
And we have established that:
Replacing all the properties in the equation lead us to:
With a very little algebra the equality is satisfied:
MM - Chapter 1 - Table 6 (Equation IV)
The residual internal energy is calculated as follows:
Therefore:
And its derivatives:
residual_internal_energy: block
real(pr) :: n(2), T, V, Ur, UrV, UrT, Urn(2)
n = [3.0_pr, 7.0_pr] ! Number of moles of each component [mol]
T = 300.0_pr ! Set temperature to 300 K
V = 1.0_pr ! Set volume to 1 liter
! Calculate residual internal energy and its derivatives
call eos%internal_energyresidual(n, V, T, Ur=Ur, UrV=UrV, UrT=UrT, Urn=Urn)
print *, "Residual internal energy: ", Ur
print *, "UrV: ", UrV
print *, "UrT: ", UrT
print *, "Urn: ", Urn
end block residual_internal_energy
MM - Chapter 1 - Table 6 (Equation III)
The residual constant volume heat capacity is calculated as follows:
residual_cv: block
real(pr) :: n(2),T, V, Cv
n = [3.0_pr, 7.0_pr] ! Number of moles of each component [mol]
T = 300.0_pr ! Set temperature to 300 K
V = 1.0_pr ! Set volume to 1 liter
! Calculate residual constant volume heat capacity
call eos%residual_cv(n, V, T, Cv=Cv)
print *, "Residual constant volume heat capacity: ", Cv
end block residual_cv
MM - Chapter 1 - Table 6 (Equation VII)
The residual constant pressure heat capacity is calculated as follows:
From MM - Chapter 1 - Equation A37 we know that:
With a direct replacement:
We can replace all the terms to obtain an expression completely expressed in terms of residual Helmholtz (\mathbf{n},V,T).
residual_cp: block
real(pr) :: n(2), T, V, Cp
n = [3.0_pr, 7.0_pr] ! Number of moles of each component [mol]
T = 300.0_pr ! Set temperature to 300 K
V = 1.0_pr ! Set volume to 1 liter
! Calculate residual constant pressure heat capacity
call eos%residual_cp(n, V, T, Cp=Cp)
print *, "Residual constant pressure heat capacity: ", Cp
end block residual_cp
As explained in the “Residual properties at ” section, a residual property is the difference between the actual property and the ideal gas property. At , the difference is made against an ideal gas at the same pressure of the real fluid, for that, the real fluid and the ideal gas are at different volumes:
Some properties change because of that, other stays the same. To obtain the expression of these properties in term of the properties we will need the derivatives of :
If desired, all the volume derivatives can be expressed in terms of as:
From MM - Chapter 1 - Equation A39
From MM - Chapter 1 - Equation A36
Finally, we have to understand the chain rule…
There is no need for a direct way of calculating natural logarithm the fugacity coefficients specifying and since we can solve volume from those specifications and then calculate the fugacity coefficients using the method. For that reason you can choose the root of the volume that you want to use to calculate the fugacity coefficients in the same way as the volume method.
lnphi_pt: block
real(pr) :: n(2), T, P, lnPhi(2), dlnPhidP(2), dlnPhidT(2), dlnPhidn(2,2)
n = [3.0_pr, 7.0_pr] ! Number of moles of each component [mol]
T = 300.0_pr ! Set temperature to 300 K
P = 1.0_pr ! Set volume to 1 bar
call eos%lnphi_pt(&
n, P, T, root_type="liquid", lnPhi=lnPhi, &
dlnPhidP=dlnPhidP, dlnPhidT=dlnPhidT, dlnPhidn=dlnPhidn &
)
call eos%lnphi_pt(&
n, P, T, root_type="vapor", lnPhi=lnPhi, &
dlnPhidP=dlnPhidP, dlnPhidT=dlnPhidT, dlnPhidn=dlnPhidn &
)
call eos%lnphi_pt(&
n, P, T, root_type="stable", lnPhi=lnPhi, &
dlnPhidP=dlnPhidP, dlnPhidT=dlnPhidT, dlnPhidn=dlnPhidn &
)
end block lnphi_pt
As in the method you can take advantage and the retrieve the value of the calculated volume and the pressure derivatives.
lnphi_pt_v: block
real(pr) :: n(2), T, P, V, lnPhi(2), dPdV, dPdT, dPdn(2)
n = [3.0_pr, 7.0_pr] ! Number of moles of each component [mol]
T = 300.0_pr ! Set temperature to 300 K
P = 1.0_pr ! Set volume to 1 bar
call eos%lnphi_pt(&
n, P, T, V=V, root_type="stable", lnPhi=lnPhi, &
dPdV=dPdV, dPdT=dPdT, dPdn=dPdn &
)
end block lnphi_pt_v
MM - Chapter 1 - Table 6 (Equation VIII)
The can be calculated as follows:
And its derivatives:
ar_pt: block
real(pr) :: n(2), T, P, Ar, ArP, ArT, Arn(2)
n = [3.0_pr, 7.0_pr] ! Number of moles of each component [mol]
T = 300.0_pr ! Set temperature to 300 K
P = 1.0_pr ! Set volume to 1 bar
call eos%helmholtz_residual_pt(&
n, P, T, root_type="stable", Ar=Ar, ArP=ArP, ArT=ArT, Arn=Arn &
)
end block ar_pt
MM - Chapter 1 - Table 6 (Equation IX)
The residual entropy at can be calculated as follows:
And its derivatives:
MM - Chapter 1 - Table 6 (Equation XII)
In this case we have a property that doesn’t change its value with the specification.
For that, its derivatives can be obtained as:
hr_pt: block
real(pr) :: n(2), T, P, Hr, HrP, HrT, Hrn(2)
n = [3.0_pr, 7.0_pr] ! Number of moles of each component [mol]
T = 300.0_pr ! Set temperature to 300 K
P = 1.0_pr ! Set volume to 1 bar
call eos%enthalpy_residual_pt(&
n, P, T, root_type="stable", Hr=Hr, HrP=HrP, HrT=HrT, Hrn=Hrn &
)
end block hr_pt
MM - Chapter 1 - Table 6 (Equation XIII)
Residual Gibbs free energy can be obtained as follows:
Therefore, the derivatives are the same as the residual Helholtz free energy :
gr_pt: block
real(pr) :: n(2), T, P, Gr, GrP, GrT, Grn(2)
n = [3.0_pr, 7.0_pr] ! Number of moles of each component [mol]
T = 300.0_pr ! Set temperature to 300 K
P = 1.0_pr ! Set volume to 1 bar
call eos%gibbs_residual_pt(&
n, P, T, root_type="stable", Gr=Gr, GrP=GrP, GrT=GrT, Grn=Grn &
)
end block gr_pt
MM - Chapter 1 - Table 6 (Equation XI)
Residual internal energy can be obtained as follows:
Therefore, its derivatives are the same as enthalpy :
ur_pt: block
real(pr) :: n(2), T, P, Ur, UrP, UrT, Urn(2)
n = [3.0_pr, 7.0_pr] ! Number of moles of each component [mol]
T = 300.0_pr ! Set temperature to 300 K
P = 1.0_pr ! Set volume to 1 bar
call eos%gibbs_residual_pt(&
n, P, T, root_type="stable", Ur=Ur, UrP=UrP, UrT=UrT, Urn=Urn &
)
end block ur_pt
MM - Chapter 1 - Table 6 (Equation X)
Residual constant volume heat capacity can be obtained as follows:
cv_pt: block
real(pr) :: n(2), T, P, Cv
n = [3.0_pr, 7.0_pr] ! Number of moles of each component [mol]
T = 300.0_pr ! Set temperature to 300 K
P = 1.0_pr ! Set volume to 1 bar
call eos%Cv_residual_pt(n, P, T, root_type="stable", Cv=Cv)
end block cv_pt
MM - Chapter 1 - Table 6 (Equation XIV)
Residual constant pressure heat capacity can be obtained as follows:
cp_pt: block
real(pr) :: n(2), T, P, Cp
n = [3.0_pr, 7.0_pr] ! Number of moles of each component [mol]
T = 300.0_pr ! Set temperature to 300 K
P = 1.0_pr ! Set volume to 1 bar
call eos%Cp_residual_pt(n, P, T, root_type="stable", Cp=Cp)
end block cp_pt
Excess properties (or mixing properties) are the difference between the real property and the property of the ideal mixture:
The ideal mixture property is equal to the linear combination of the pure components properties:
Where is the molar property of the pure component at the same pressure and temperature as the mixture.
MM - Chapter 1 - Table 9 (Equation 1)
The natural logarithm of the activity coefficient is given by:
Where:
is the fugacity coefficient of component in the mixture
is the fugacity coefficient of component in the pure state at the same pressure and temperature of the mixture.
And its derivatives:
MM - Chapter 1 - Table 9 (Equations V and VI)
Where:
and is the partial molar volume of component in the pure state at the same pressure and temperature of the mixture.
ln_gamma: block
real(pr) :: n(2), T, P, lngamma(2)
real(pr) :: dlngammadT(2), dlngammadP(2), dlngammadn(2,2)
n = [3.0_pr, 7.0_pr] ! Number of moles of each component [mol]
T = 300.0_pr ! Set temperature to 300 K
P = 1.0_pr ! Set volume to 1 bar
call eos%ln_activity_coefficient(&
n, P, T, root_type="stable", &
lngamma=lngamma, dlngammadT=dlngammadT, dlngammadP=dlngammadP, &
dlngammadn=dlngammadn &
)
end block ln_gamma
MM - Chapter 1 - Table 9 (Equation II)
The excess Gibbs free energy can be calculated in terms of the activity coefficients as follows:
and its derivatives:
gibbs_excess: block
real(pr) :: n(2), T, P, GE, dGEdT(2), dGEdP(2), dGE_dn(2,2)
n = [3.0_pr, 7.0_pr] ! Number of moles of each component [mol]
T = 300.0_pr ! Set temperature to 300 K
P = 1.0_pr ! Set volume to 1 bar
call eos%gibbs_excess(&
n, P, T, root_type="stable", GE=GE, dGEdT=dGEdT, dGEdP=dGEdP, &
dGE_dn=dGE_dn &
)
end block gibbs_excess
MM - Chapter 1 - Table 9 (Equation III)
The excess enthalpy can be calculated in terms of the activity coefficients as follows:
enthalpy_excess: block
real(pr) :: n(2), T, P, HE
n = [3.0_pr, 7.0_pr] ! Number of moles of each component [mol]
T = 300.0_pr ! Set temperature to 300 K
P = 1.0_pr ! Set volume to 1 bar
call eos%enthalpy_excess(n, P, T, root_type="stable", HE=HE)
end block enthalpy_excess
MM - Chapter 1 - Table 9 (Equation IV)
The excess volume can be calculated in terms of the activity coefficients as follows:
volume_excess: block
real(pr) :: n(2), T, P, VE
n = [3.0_pr, 7.0_pr] ! Number of moles of each component [mol]
T = 300.0_pr ! Set temperature to 300 K
P = 1.0_pr ! Set volume to 1 bar
call eos%volume_excess(&
n, P, T, root_type="stable", VE=VE &
)
end block volume_excess
The excess entropy can be calculated in terms of the activity coefficients as follows:
replacing and
entropy_excess: block
real(pr) :: n(2), T, P, SE
n = [3.0_pr, 7.0_pr] ! Number of moles of each component [mol]
T = 300.0_pr ! Set temperature to 300 K
P = 1.0_pr ! Set volume to 1 bar
call eos%entropy_excess(n, P, T, root_type="stable", SE=SE)
end block entropy_excess
MM - Chapter 5 - Equation 1
The excess Helmholtz free energy can be calculated in terms of the activity coefficients as follows:
helmholtz_excess: block
real(pr) :: n(2), T, P, AE
n = [3.0_pr, 7.0_pr] ! Number of moles of each component [mol]
T = 300.0_pr ! Set temperature to 300 K
P = 1.0_pr ! Set volume to 1 bar
call eos%helmholtz_excess(n, P, T, root_type="stable", AE=AE)
end block helmholtz_excess
MM - Chapter 5 - Equation 4
The excess internal energy can be calculated in terms of the activity coefficients as follows:
Replacing:
And with very little algebra:
internal_energy_excess: block
real(pr) :: n(2), T, P, UE
n = [3.0_pr, 7.0_pr] ! Number of moles of each component [mol]
T = 300.0_pr ! Set temperature to 300 K
P = 1.0_pr ! Set volume to 1 bar
call eos%internal_energy_excess(n, P, T, root_type="stable", UE=UE)
end block internal_energy_excess
[1] 1. Michelsen, M. L., & Mollerup, J. M. (2007). Thermodynamic models: Fundamentals & computational aspects (2. ed). Tie-Line Publications.