Thermodynamic Derivatives

Helmholtz energy derivatives

Thermodynamic derivatives are at the very heart of teqp. All models are defined in the form αr(T,ρ,z), where ρ is the molar density, and z are mole fractions. There are exceptions for models for which the independent variables are in simulation units (Lennard-Jones and its ilk).

Thereofore, to obtain the residual pressure, it is obtained as a derivative:

pr=ρRT(ρ(αrρ)T)

and other residual thermodynamic properties are defined likewise.

We can define the concise derivative

Λxyr=(1/T)x(ρ)y(x+y(αr)(1/T)xρy)

so we can re-write the derivative above as

pr=ρRTΛ01r

Similar definitions apply for all the other thermodynamic properties, with the tot superscript indicating it is the sum of the residual and ideal-gas (not included in teqp) contributions:

pρRT=1+Λ01r

Internal energy (u=a+Ts):

uRT=Λ10tot

Enthalpy (h=u+p/ρ):

hRT=1+Λ01r+Λ10tot

Entropy (s(a/T)v):

sR=Λ10totΛ00tot

Gibbs energy (g=hTs):

gRT=1+Λ01r+Λ00tot

Derivatives of pressure:

(pρ)T=RT(1+2Λ01r+Λ02r)
(pT)ρ=Rρ(1+Λ01rΛ11r)

Isochoric specific heat (cv(u/T)v):

cvR=Λ20tot

Isobaric specific heat (cp(h/T)p; see Eq. 3.56 from Span for the derivation):

cpR=Λ20tot+(1+Λ01rΛ11r)21+2Λ01r+Λ02r

In teqp, these derivatives are obtained from methods like

where the A in this context indicates the variable Λ above. This naming is perhaps not ideal, since A is sometimes the total Helmholtz energy, but it was a close visual mnemonic to the character Λ.

[1]:
import teqp
teqp.__version__
[1]:
'0.22.0'
[2]:
import numpy as np
[3]:
Tc_K = [300]
pc_Pa = [4e6]
acentric = [0.01]
model = teqp.canonical_PR(Tc_K, pc_Pa, acentric)
[4]:
z = np.array([1.0])
model.get_Ar01(300,300,z)
[4]:
-0.06836660379313926

And there are additional methods to obtain all the derivatives up to a given order:

[5]:
model.get_Ar06n(300,300,z) # derivatives 00, 01, 02, ... 06
[5]:
array([-6.96613834e-02, -6.83666038e-02,  2.53578225e-03, -1.57011622e-04,
        1.68186288e-05, -2.23059409e-06,  3.82592585e-07])

But more derivatives are slower than fewer:

[6]:
%timeit model.get_Ar01(300,300,z)
%timeit model.get_Ar04n(300,300,z)
567 ns ± 0.747 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
1.11 μs ± 4.12 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)

Note: calling overhead is usually on the order of 1 microsecond

Virial coefficients

Virial coefficients represent the thermodynamics of the interaction of two-, three-, … bodies interacting with each other. They can be obtained rigorously if the potential energy surface of interaction is fully known. In general, such a surface can only be constructed for small rigid molecules. Many simple thermodynamic models do a poor job of predicting the thermodynamics captured by the virial coefficients.

The i-th virial coefficient is defined by

Bi=(αr)(i1)(i2)!

with the concise derivative term

(αr)(i)=limρ0(iαrρi)T,x

teqp supports the virial coefficient directly, there is the get_B2vir method for the second virial coefficient:

[7]:
model.get_B2vir(300, z)
[7]:
-0.00023661263734465424

And the get_Bnvir method that allows for the calculation of higher virial coefficients:

[8]:
model.get_Bnvir(7, 300, z)
[8]:
{2: -0.0002366126373446542,
 3: 3.001768410777936e-08,
 4: -3.2409760373816364e-12,
 5: 3.961781646633723e-16,
 6: -4.5529239838367004e-20,
 7: 5.375927851118494e-24}

The get_Bnvir method was implemented because when doing automatic differentiation, all the intermediate derivatives are also retained.

There is also a method to calculate temperature derivatives of a given virial coefficient

[9]:
model.get_dmBnvirdTm(2, 3, 300, z) # third temperature derivative of the second virial coefficient
[9]:
1.0095625628421257e-10

Isochoric Thermodynamics Derivatives

In the isochoric thermodynamics formalism, the EOS is expressed in the Helmholtz energy density Ψ as a function of temperature and molar densities ρ. This formalism is handy because it allows for a concise mathematical structure, well suited to implementation in teqp. For instance the pressure is obtained from (see https://doi.org/10.1002/aic.16074):

p=Ψ+i=1Nρiμi

with the chemical potential μi obtained from

μi=(Ψρi)T,ρji

The molar densities ρi are related to the total density and the mole fractions:

ρi=xiρ

In teqp, the isochoric derivative functions like get_fugacity_coefficients, get_partial_molar_volumes take as arguments the temperature T and the vector of molar concentrations rhovec=ρ, which are obtained by multiplying the mole fractions by the total density.

Example:

[10]:
model = teqp.build_multifluid_model(["CO2","Argon"], teqp.get_datapath())
T, rhovec = 300, np.array([0.3,0.4])*300 # K, mol/m^3
display(model.get_fugacity_coefficients(T, rhovec))
display(model.get_partial_molar_volumes(T, rhovec))
array([0.97884567, 0.99866748])
array([0.00470644, 0.00480351])