Composition derivatives

For other mixture calculations composition derivatives of the form

Λxyzir=(1/T)x(ρ)y(x+y+zi(αr)(1/T)xρyZizi)

are needed. This function is exposed in teqp (as of version 0.19) as the function get_ATrhoXi. In order to limit the binary size and compilation time, x has a max of 2 and y does as well. z_i can be up to 3, and must be at least 1, otherwise you can use the other derivative functions that do not require any composition derivatives.

The mixed composition derivative of the form

Λxyzizjr=(1/T)x(ρ)y(x+y+zi+zj(αr)(1/T)xρyZiziZjzj)

supports x and y either 0 or 1, with at most two composition derivatives. The triple composition derivative of the form

Λxyzizjzkr=(1/T)x(ρ)y(x+y+zi+zj+zk(αr)(1/T)xρyZiziZjzjZkzk)

supports x and y either 0 or 1, with up to first derivatives in each composition variable. If this is not enough derivatives, open a feature request here : https://github.com/usnistgov/teqp/issues

τ and δ derivatives

In the multi-fluid modeling approach used in NIST REFPROP and the GERG-2004 GERG-2008 models, the derivatives are in the form

Λxyzir=τxδy(x+y+zi(αr)τxδyZizi)

with τ=Tred(Z)/T and δ=ρ/ρred(Z). The higher derivatives are similarly equal to

Λxyzizjr=τxδy(x+y+zi+zj(αr)τxδyZiziZjzj)
Λxyzizjzkr=τxδy(x+y+zi+zj+zk(αr)τxδyZiziZjzjZkzk)

The same limitations on the numbers of derivatives are used for the derivatives with (1/T) and ρ as independent variables.

The Python methods are documented here:

xN (in)dependent

Let’s suppose that some quantity Υ depends on mole fractions. If all the mole fractions are considered to be independent, the total differential is obtained from

dΥ=j(Υxj)xijdxj

If instead the last mole fraction is defined to be dependent on the others via

xN=1i=1N1xi

then the total differential is obtained from

dΥ=j=1N1(Υxj)xijdxj+(ΥxN)xijdxN

where xN is considered to be an independent variable in the derivative (ΥxN)xij. Thus derivatives with respect to one of the dependent mole fractions (xk with k<N) would be equal to

(Υxk)xjk=(Υxk)xik(ΥxN)xiN

because

(xNxi)=1

So if the library (e.g., CoolProp and TREND) allows for the fractions to be dependent (either option is allowed in CoolProp, TREND uses N-1 independent mole fractions), you can use the molar composition derivatives with the mole fractions treated as being independent to obtain derivatives with one of the mole fractions dependent on the other N1 fractions.

[1]:
import teqp, numpy as np
teqp.__version__
[1]:
'0.19.1'
[2]:
j = {
    'components': ["Methane", "Nitrogen", "Oxygen"],
    'root': teqp.get_datapath(),
    'BIP': '',
    'departure': ''
}
model = teqp.make_model({'kind':'multifluid', 'model': j})


T = 300 # K
rhomolar = 3000 # mol/m^3
z = np.array([0.3, 0.5, 0.2]) # mole fractions

Tr = model.get_Tr(z)
rhor = model.get_rhor(z)
tau = Tr/T
delta = rhomolar/rhor

Ntau = 0
Ndelta = 0
Nxi = 1
print(model.get_AtaudeltaXi(tau, Ntau, delta, Ndelta, z, 0, Nxi))

Ntau = 1
Ndelta = 0
Nxi = 1
print(model.get_AtaudeltaXi(tau, Ntau, delta, Ndelta, z, 0, Nxi))

Ntau = 0
Ndelta = 1
Nxi = 1
print(model.get_AtaudeltaXi(tau, Ntau, delta, Ndelta, z, 0, Nxi))

Ntau = 2
Ndelta = 0
Nxi = 1
print(model.get_AtaudeltaXi(tau, Ntau, delta, Ndelta, z, 0, Nxi))

Ntau = 1
Ndelta = 1
Nxi = 1
print(model.get_AtaudeltaXi(tau, Ntau, delta, Ndelta, z, 0, Nxi))

Ntau = 0
Ndelta = 2
Nxi = 1
print(model.get_AtaudeltaXi(tau, Ntau, delta, Ndelta, z, 0, Nxi))

Ntau = 1
Ndelta = 0
Nxi = 1
Nxj = 1
print(model.get_AtaudeltaXiXj(tau, Ntau, delta, Ndelta, z, 0, Nxi, 1, Nxj))

Ntau = 0
Ndelta = 1
Nxi = 1
Nxj = 1
print(model.get_AtaudeltaXiXj(tau, Ntau, delta, Ndelta, z, 0, Nxi, 1, Nxj))

Ntau = 0
Ndelta = 0
Nxi = 1
Nxj = 1
Nxk = 1
print(model.get_AtaudeltaXiXjXk(tau, Ntau, delta, Ndelta, z, 0, Nxi, 1, Nxj, 2, Nxk))
-0.043587384253511226
-0.2118857998812584
-0.03650566667904927
-0.07488856488580686
-0.2069389009652925
0.014468933385218782
-0.005978809921279949
-0.00279185550001082
0.0

With CoolProp, version 6.6.0, the following script in C++:

#include "AbstractState.h"
#include "Backends/Helmholtz/MixtureDerivatives.h"

int main(){
    std::shared_ptr<CoolProp::AbstractState> AS(
        CoolProp::AbstractState::factory("HEOS","Methane&Nitrogen&Oxygen")
    );
    AS->set_mole_fractions({0.3, 0.5, 0.2});
    AS->specify_phase(CoolProp::iphase_gas);
    AS->update(CoolProp::DmolarT_INPUTS, 3000, 300);
    auto& HEOS = *dynamic_cast<CoolProp::HelmholtzEOSMixtureBackend*>(AS.get());
    auto xN  = CoolProp::x_N_dependency_flag::XN_INDEPENDENT;
    using md = CoolProp::MixtureDerivatives;
    std::cout << md::dalphar_dxi(HEOS, 0, xN) << std::endl;

    std::cout << md::d2alphar_dxi_dTau(HEOS, 0, xN)*AS->tau() << std::endl;
    std::cout << md::d2alphar_dxi_dDelta(HEOS, 0, xN)*AS->delta() << std::endl;
    std::cout << md::d3alphar_dxi_dTau2(HEOS, 0, xN)*pow(AS->tau(), 2) << std::endl;
    std::cout << md::d3alphar_dxi_dDelta_dTau(HEOS, 0, xN)*AS->tau()*AS->delta() << std::endl;
    std::cout << md::d3alphar_dxi_dDelta2(HEOS, 0, xN)*pow(AS->delta(), 2) << std::endl;

    std::cout << md::d3alphar_dxi_dxj_dTau(HEOS, 0, 1, xN)*AS->tau() << std::endl;
    std::cout << md::d3alphar_dxi_dxj_dDelta(HEOS, 0, 1, xN)*AS->delta() << std::endl;
    std::cout << md::d3alphardxidxjdxk(HEOS, 0, 1, 2, xN) << std::endl;
}

yields the output:

-0.0435874
-0.211886
-0.0365057
-0.0748886
-0.206939
0.0144689
-0.00597881
-0.00279186
0

which is the same as the above