Tabulated property fitting

Fit real-gas equation of state from tabulated properties.

This module turns a table of thermodynamic properties into the polynomial coefficient arrays required by ember.fluid.RealFluid, following the entropy-based formulation of Wheeler [1].

The user should perform the fitting once, offline, and then pass the resulting coefficients to ember.fluid.RealFluid at simulation runtime. Only sample_coolprop() needs a lazy import of CoolProp, so the rest of the code can be used without it.

The method requires two polynomial fits: compressibility factor as a two-dimensional surface in density and internal energy, and entropy along a reference isochor as a one-dimensional function of internal energy. Optionally, viscosity and thermal conductivity are fitted as two further two-dimensional surfaces over density and internal energy.

Polynomials are fitted in coordinates scaled onto \([-1, 1]\) by the bounds of a fit box. The reference isochor passes through the centre of the box, and viscosity and conductivity are normalised by their values at the box centre.

Example usage

With CoolProp installed, the pipeline is a single call:

from ember.realgas_fit import fit, sample_coolprop
from ember.fluid import RealFluid

result = fit(
    **sample_coolprop(
        "CO2", rho_lim=(2.0, 150.0), u_lim=(3.5e5, 5.0e5)
    )
)  # fit the equation of state and transport surfaces

fluid = RealFluid(**result.kwargs)               # hand to the solver
print(result.info_Z.rmse, result.info_s.rmse)    # inspect the fit residuals
class realgas_fit.FitInfo(rmse, R2)[source]

Bases: object

Quality of a single least-squares polynomial fit.

rmse

Root-mean-square residual, in the units of the fitted quantity.

Type:

float

R2

Coefficient of determination, one for a perfect fit.

Type:

float

class realgas_fit.FitResult(kwargs, info_Z, info_s, info_mu, info_kappa)[source]

Bases: object

Fitted coefficients together with the residuals that bound their accuracy.

kwargs

Keyword arguments defining the equation of state, ready to splat into ember.fluid.RealFluid. Contains alpha, beta, rho_lim, u_lim, Rgas, delta, gamma, mu_c and kappa_c.

Type:

dict

info_Z

Residual of the compressibility surface [–].

Type:

FitInfo

info_s

Residual of the entropy fit, in units of the gas constant [–].

Type:

FitInfo

info_mu, info_kappa

Residuals of the two transport surfaces, relative to the value each is normalised by [–], so that they read as fractional errors.

Type:

FitInfo

realgas_fit.fit(rho, u, P, T, s, mu, kappa, Rgas, rho_lim, u_lim, order=8, basis='total-order')[source]

Fit an equation of state to tabulated thermodynamic properties.

Fits the compressibility factor as a surface in density and internal energy, then recovers the entropy variation along the reference isochor by subtracting the analytic density integral from the tabulated entropy. What remains is a function of internal energy alone, so a one-dimensional fit closes the model.

The sample points need not lie on a grid, but they must all be inside the box given by rho_lim and u_lim, and must avoid the two-phase region, where the properties are not smooth and the fit would be poisoned. sample_coolprop() masks it, and returns every argument below but the two that select the basis, so fit(**sample_coolprop(...)) is the whole pipeline.

Parameters:
  • rho (array_like) – Sample densities [kg/m³].

  • u (array_like) – Sample specific internal energies [J/kg], on any datum.

  • P (array_like) – Pressure at the sample states [Pa].

  • T (array_like) – Temperature at the sample states [K].

  • s (array_like) – Specific entropy at the sample states [J/kg/K], on any datum.

  • mu (array_like) – Dynamic viscosity at the sample states [kg/m/s].

  • kappa (array_like) – Thermal conductivity at the sample states [W/m/K].

  • Rgas (float) – Specific gas constant [J/kg/K].

  • rho_lim (tuple) – (min, max) density bounds of the fit box [kg/m³].

  • u_lim (tuple) – (min, max) internal energy bounds of the fit box [J/kg], on the same datum as u.

  • order (int, optional) – Maximum polynomial order in each variable.

  • basis ({'total-order', 'tensor-grid'}, optional) – Which order combinations to retain. 'total-order' (the default) keeps only terms whose orders in the two variables sum to at most order; 'tensor-grid' keeps every combination up to order in each. The default drops the worst-conditioned high-order-in-both corners for roughly half the terms.

Returns:

result – Coefficients ready for ember.fluid.RealFluid, with the fit residuals that bound their accuracy.

Return type:

FitResult

realgas_fit.sample_coolprop(fluid_name, rho_lim, u_lim, ni=100)[source]

Sample thermodynamic properties over a fit box using CoolProp.

States that fail to converge or fall inside the two-phase dome are dropped: properties are not smooth across saturation, and including such points would poison the fit far more than any choice of basis or order. So are states whose transport properties CoolProp declines to report, which for a fluid with no transport model at all is every one of them — said so, rather than reported as an empty box.

Parameters:
  • fluid_name (str) – Fluid name in the CoolProp database.

  • rho_lim (tuple) – (min, max) density bounds [kg/m³].

  • u_lim (tuple) – (min, max) internal energy bounds [J/kg], on CoolProp’s datum.

  • ni (int, optional) – Number of sample points along each axis.

Returns:

Arrays rho, u, P, T, s, mu and kappa at the surviving states, Rgas, the specific gas constant [J/kg/K], and the rho_lim and u_lim that were sampled. That is every argument fit() needs, so the whole pipeline is fit(**sample_coolprop(...)).

The box is passed on rather than left to the caller to repeat because nothing downstream could catch it being repeated wrongly: a fit taken over a box other than the sampled one puts the normalised coordinates outside [-1, 1], where the Legendre basis loses its orthogonality and the fit its conditioning – and returns coefficients that are wrong without being nan.

Return type:

dict