Equations of State

Equations of State

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).

Thermodynamic properties

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.

Pressure and volume

Pressure:

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

Volume:

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

Residual properties at (n, V, T)

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.

Fugacity coefficients (V,T):

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

Fugacity (V,T):

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

Residual entropy (V,T):

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

Residual enthalpy (V,T):

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

Residual Gibbs free energy (V,T):

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:

Residual internal energy (V,T):

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

Residual constant volume heat capacity (V,T):

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

Residual constant pressure heat capacity (V,T):

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

Residual properties at (n, P, T)

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…

Fugacity coefficients (P,T):

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

Residual Helmholtz free energy (P, T):

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

Residual entropy (P, T):

MM - Chapter 1 - Table 6 (Equation IX)

The residual entropy at can be calculated as follows:

And its derivatives:

Residual Enthalpy (P, T):

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

Residual Gibbs free energy (P, T):

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

Residual internal energy (P, T):

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

Residual constant volume heat capacity (P,T):

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

Residual constant pressure heat capacity (P,T):

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 (n, P, T)

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.

Activity coefficients (P, T):

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

Excess Gibbs free energy (P, T):

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

Excess Enthalpy (P, T):

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

Excess volume (P, T):

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

Excess entropy (P, T):

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

Excess Helmholtz free energy (P, T):

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

Excess Internal Energy (P, T):

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

References

[1] 1. Michelsen, M. L., & Mollerup, J. M. (2007). Thermodynamic models: Fundamentals & computational aspects (2. ed). Tie-Line Publications.