Transport properties#
[1]:
%load_ext autoreload
%autoreload 2
[2]:
from majordome.common import standard_plot
import majordome.common as mc
import majordome.transport as mt
from tabulate import tabulate
import cantera as ct
import numpy as np
EffectiveThermalConductivity#
Class EffectiveThermalConductivity
implements static methods for the evaluation of properties; its name is quite long, let’s start by getting an alias before evaluating the desired models:
[3]:
etc = mt.EffectiveThermalConductivity()
Using Maxwell approximation, we could estimate the the effective thermal conductivity of a packed bed of particles in a matrix of air; assume particles loosly embeded in air and the following properties; the computed effective thermal conductivity is shown to approach the air limit:
[4]:
phi = 0.30 # Loosely packed solids [30%v]
k_g = 0.025 # Air thermal conductivity [W/(m.K)]
k_s = 1.000 # Solids thermal conductivity [W/(m.K)]
etc.maxwell_garnett(phi, k_g, k_s)
[4]:
0.05396039603960396
For the limit of high temperatures, it is usually important to account for particle-particle radiation heat transfer; this introduces a \(T^3\) dependence on temperature, as one should expect by linearizing Stefan-Boltzmann law.
Please notice that these models compute different things; while Maxwell approximation computes the medium properties (to approximate matrix-inclusion as a single domain), Singh’s model accounts only for solids properties. One might wish to combine them (warning: unverified validity!) to evaluate overall medium thermal conductivity. See the references in the class documentation for further discussion, specially the extension proposed by Kiradjiev (2019), which leads to a result similar to the assymptotic behavior displayed below.
[5]:
@standard_plot(shape=(1, 2))
def plot_etc(data, fig, ax):
T, k_s, k_m = data
ax[0].plot(T, k_s)
ax[1].plot(T, k_m)
ax[0].set_xlim(mc.bounds(T))
ax[1].set_xlim(mc.bounds(T))
ax[0].set_title("Solids property")
ax[1].set_title("Medium property")
ax[0].set_xlabel("Temperature [K]")
ax[1].set_xlabel("Temperature [K]")
ax[0].set_ylabel("Thermal conductivity [W/(m.K)]")
ax[1].set_ylabel("Thermal conductivity [W/(m.K)]")
d_p = 0.005
eps = 0.9
T = np.linspace(300, 1500, 50)
k_eff_s = etc.singh1994(T, phi, d_p, k_s, eps)
k_eff_m = etc.maxwell_garnett(phi, k_g, k_eff_s)
plot_etc((T, k_eff_s, k_eff_m)).resize(10, 5)

Due to kinetic theory implications, gas thermal conductivity tend to have a positive slope in temperature; the following illustrates how this can be accounted for and the roughly linear behaviour introduced when computing medium properties within air.
[6]:
@standard_plot(shape=(1, 2))
def plot_etc(data, fig, ax):
T, k_g, k_m = data
ax[0].plot(T, k_g)
ax[1].plot(T, k_m)
ax[0].set_xlim(mc.bounds(T))
ax[1].set_xlim(mc.bounds(T))
ax[0].set_title("Fluid property")
ax[1].set_title("Medium property")
ax[0].set_xlabel("Temperature [K]")
ax[1].set_xlabel("Temperature [K]")
ax[0].set_ylabel("Thermal conductivity [W/(m.K)]")
ax[1].set_ylabel("Thermal conductivity [W/(m.K)]")
gas = ct.Solution("airish.yaml")
sol = ct.SolutionArray(gas, (T.shape[0],))
sol.TP = T, None
k_gas = sol.thermal_conductivity
k_eff_m = etc.maxwell_garnett(phi, k_gas, k_eff_s)
plot_etc((T, k_gas, k_eff_m)).resize(10, 5)

Dimensionless numbers#
A dimensionless numbers calculator is provided for gas flows; it currently has a certain number of groups which are all evaluated by definition (which might change according to your field, please check the docs). The mechanics of using the class can be resumed to:
loading a mechanism file (Cantera YAML)
setting the state of the solution
evaluating the required properties
(optional) displaying a report
The meaning of the tuple of arguments provided to set_state
is specified by tuple_name="TPX"
, which defaults to temperature, pressure, and molar proportions. Any triplet allowed by Cantera can be specified here.
[7]:
Tw = 1000.0 # Wall temperature [K]
U = 10.0 # Characteristic velocity [m/s]
D = 0.05 # Pipe diameter [m]
L = 1.0 # Pipe length [m]
calculator = mt.SolutionDimless("airish.yaml")
calculator.set_state(300.0, 101325.0, "N2: 1", tuple_name="TPX")
Re = calculator.reynolds(U, D)
Pr = calculator.prandtl()
Sc = calculator.schmidt()
print(calculator.report())
-------- ------------ ---------------
Reynolds 31460.9 U=10.0, L=0.05
Prandtl 0.709327
Schmidt 0.761752 mix_diff_coeffs
-------- ------------ ---------------
If you prefer to have direct access to the internal solution, you can set properties as usual in Cantera, but you need to keep in mind to call update()
to refresh the internal state of the calculator. Every time the properties are updated, the internal buffer of computed dimensionless numbers is refreshed, as you migth notice in the following table.
[8]:
calculator.solution.TPX = 300.0, 101325.0, "N2: 1"
calculator.update()
Pe_m = calculator.peclet_mass(U, L)
Pe_h = calculator.peclet_heat(U, L)
Gr = calculator.grashof(Tw, D)
Ra = calculator.rayleigh(Tw, D)
print(calculator.report())
------------- ---------------- ------------------------------
Péclet (mass) 479308 U=10.0, L=1.0, mix_diff_coeffs
Péclet (heat) 446321 U=10.0, L=1.0
Grashof 1.13242e+07 Tw=1000.0, H=0.05, g=9.80665
Rayleigh 8.03259e+06 Tw=1000.0, H=0.05, g=9.80665
------------- ---------------- ------------------------------
Sutherland fitting#
To the author’s knowledge, there is no standard tool to convert Cantera transport data to Sutherland parameters for use with OpenFOAM, what led to the motivation to develop SutherlandFitting
. This simple class wraps a Cantera solution object, which it makes use for fitting Sutherland parameters to export as a table (to be used elswhere), and also allows for retrieving a converted database in OpenFOAM compatible format. The following example should be self-explanatory:
[9]:
T = np.linspace(500, 2500, 100)
sutherland = mt.SutherlandFitting("airish.yaml")
sutherland.fit(T, species_names=["O2", "N2"])
coef = sutherland.coefs_table
coef
[9]:
species | As [uPa.s] | Ts [K] | RMSE [uPa.s] | |
---|---|---|---|---|
0 | O2 | 1.873876 | 229.321211 | 0.527484 |
1 | N2 | 1.619350 | 226.141806 | 0.470152 |
If needed, it is also possible to have direct access to the viscosity data used in parameter fitting:
[10]:
sutherland.viscosity.head()
[10]:
T | O2 | N2 | |
---|---|---|---|
0 | 500.000000 | 30.045456 | 26.123142 |
1 | 520.202020 | 30.886884 | 26.845049 |
2 | 540.404040 | 31.713820 | 27.554780 |
3 | 560.606061 | 32.527146 | 28.253075 |
4 | 580.808081 | 33.327662 | 28.940602 |
Because RMSE compresses all the error in a single value, you can check graphically where the deviations happen in the interval:
[11]:
plot = sutherland.plot_species("N2")

Finally, it also responds to its initial goal of exporting values in OpenFOAM format:
[12]:
print(sutherland.to_openfoam())
/* --- RMSE 0.5274842993902649 ---*/
"O2"
{
transport
{
As 1.8738763185e-06;
Ts 2.2932121082e+02;
}
}
/* --- RMSE 0.4701517988286822 ---*/
"N2"
{
transport
{
As 1.6193495479e-06;
Ts 2.2614180620e+02;
}
}