1.2.14. Custom Tabulation Example: 4D Coal Combustion Model

This demo is part of Spitfire, with licensing and copyright info here.

Highlights

  • building a table with user-defined continuation loops

  • varying the fuel stream and incorporating nonadiabatic effects in a quasisteady manner

  • stacking contour plots to visualize a 3D field

This example showcases a flamelet library built with manual loops over the tabulation dimensions. We build a four-dimensional table of states over the mixture fraction, scalar dissipation rate (stoichiometric value), heat transfer coefficient, and fraction of volatiles and char (from coal particle gasification) in the fuel stream. The heat transfer dimension is expanded in a quasisteady manner, with steady states calcalated at each heat transfer coefficient (or, at each enthalpy defect), as an alternative to transient heat loss calculations in the provided build_nonadiabatic_defect_transient_slfm_library method (but very similar to the build_nonadiabatic_defect_steady_slfm_library method).

Here we manually loop over table dimensions and use the Flamelet class directly instead of the higher-level convenience methods provided in the spitfire.chemistry.tabulation module. While the tabulation methods are much more convenient, this provides a finer level of control and customizability for tabulation techniques not currently wrapped in a high-level interface.

import numpy as np
import matplotlib.pyplot as plt
import pickle
from time import perf_counter
from spitfire import ChemicalMechanismSpec, Flamelet, FlameletSpec, Dimension, Library

First we define a helper function for mixing char and volatiles and load some stream data for later use.

def get_fuel_stream(coal_fuels, alpha, mechanism, pressure=101325.):
    """
    Fuel streams representative of coal combustion, spanning char to volatiles, with volatiles fraction alpha.
    Personal communication with Josh McConnell, University of Utah, 2018.
    """
    volatiles = mechanism.stream('TPY', (coal_fuels['volatiles']['T'], pressure, coal_fuels['volatiles']['Y']))
    char = mechanism.stream('TPY', (coal_fuels['char']['T'], pressure, coal_fuels['char']['Y']))
    return mechanism.mix_streams([(volatiles, alpha), (char, 1. - alpha)], 'mass', 'HP')

with open('coalflamelet_bcs.pkl', 'rb') as bcs_src:
    coal_fuels = pickle.load(bcs_src)

Next we load up a wrapper of the GRI-3.0 methane combustion mechanism and make an air stream.

mechanism = ChemicalMechanismSpec(cantera_input='methane-gri30.yaml', group_name='gri30_mix')

pressure = 101325.

oxy = mechanism.stream(stp_air=True)
oxy.TP = 350., pressure

Next we specify the table dimensions for the char/volatiles fraction, dissipation rate, heat transfer coefficient, and mixture fraction. From the set of values of each variable we make a Library object. Then for each property to be tabulated we set up an empty dataset in the library to fill later as we perform continuation across each library dimension.

mixfrac_vec = np.linspace(0., 1., 96)
alpha_vec = np.array([0.0, 0.1, 0.3, 0.5, 0.7])
chist_vec = np.logspace(-1., 3., 8)
h_vec = np.hstack([0., np.logspace(-8, 1, 24)])

zdim = Dimension('mixture_fraction', mixfrac_vec)
xdim = Dimension('dissipation_rate_stoich', chist_vec, log_scaled=True)
hdim = Dimension('heat_transfer_coefficient', h_vec)
adim = Dimension('alpha', alpha_vec)

l = Library(adim, hdim, xdim, zdim)
l.extra_attributes['mech_spec'] = mechanism

for q in ['temperature', 'pressure'] + ['mass fraction ' + s for s in mechanism.species_names]:
    l[q] = l.get_empty_dataset()

Now we make a specifications for the flamelet.

particle_temperature = 350.

flamelet_specs = {'mech_spec': mechanism,
                  'oxy_stream': oxy,
                  'fuel_stream': get_fuel_stream(coal_fuels, 0., mechanism, pressure),
                  'grid': mixfrac_vec,
                  'heat_transfer': 'nonadiabatic',
                  'convection_temperature': particle_temperature,
                  'convection_coefficient': 0.,
                  'scale_heat_loss_by_temp_range': False,
                  'scale_convection_by_dissipation': False,
                  'use_linear_ref_temp_profile': False,
                  'radiation_temperature': particle_temperature,
                  'radiative_emissivity': 0.,
                  'rates_sensitivity_type': 'sparse'}

Now we enter into the loops. In continuation calculations it is critical to get the best initial guess, as this determines convergence rates and often the solution that is ultimately computed. We treat different values of \(\alpha\) (the char/volatiles fraction) as separate problems entirely, not using the solution from one value to inform the next, as the chemistry is so different between them given our coarse resolution of the \(\alpha\) line.

For each \(\alpha\), then, we build a three-dimensional library over the dissipation rate (\(\chi_{\rm st}\)), heat transfer coefficient (\(h\)), and mixture fraction (\(\mathcal{Z}\)). Note that each three-dimensional table could be generated in parallel with the multiprocessing module. A relevant approach to parallelization is taken in the build_nonadiabatic_defect_*_slfm_library methods in Spitfire. An annoyance that Cantera solution objects cannot be directly pickled (a form of serialization required by multiprocessing). However ChemicalMechanismSpec instances can be pickled and so incorporation of multiprocessing isn’t too bad.

In building the three-dimensional library, we first iterate over serial calculations in the dissipation rate direction with zero heat loss (adiabatic flamelets), where each prior solution provides an excellent initial guess for the next dissipation rate. From this line of solutions, then, we have even more parallel work in the heat loss dimension, which may be solved for each dissipation rate independently of the others. Again, each extension of the heat loss dimension is treated serially, using the prior solution as the initial guess.

Here we do not exploit the available parallelism in any way to keep things simple, but factors of ten or more speedup could be easily obtained with a bit of extra work for either Python-based multiprocessing or simply running multiple scripts and combining the libraries after the fact.

flamelet_specs['initial_condition'] = 'equilibrium'
if 'library_slice' in flamelet_specs:
    flamelet_specs.pop('library_slice')

cput000 = perf_counter()
for ia, alpha in enumerate(alpha_vec):
    print(f'Running alpha = {alpha:4.2f} ...')
    flamelet_specs.update({'fuel_stream': get_fuel_stream(coal_fuels, alpha, mechanism)})

    adiabatic_solutions = list()
    cput00 = perf_counter()
    for ichi, chist in enumerate(chist_vec):
        flamelet_specs.update({'stoich_dissipation_rate': chist})
        if ichi > 0:
            flamelet_specs['library_slice'] = Library.squeeze(l[ia, 0, ichi - 1, :])
        else:
            if 'library_slice' in flamelet_specs:
                flamelet_specs.pop('library_slice')
        f = Flamelet(**flamelet_specs)
        steady_adiabatic_lib = f.compute_steady_state()
        for quantity in steady_adiabatic_lib.props:
            l[quantity][ia, 0, ichi, :] = steady_adiabatic_lib[quantity].ravel()
    cput1 = perf_counter()
    print(f'Converged adiabatic solutions in {cput1-cput00:6.2f} s {"":24} | total cpu time is {cput1 - cput000:6.2f} s')

    for ichi, chist in enumerate(chist_vec):
        flamelet_specs.update({'stoich_dissipation_rate': chist})
        cput0 = perf_counter()
        for ih, h in enumerate(h_vec):
            flamelet_specs.update({'convection_coefficient': h})
            flamelet_specs['library_slice'] = Library.squeeze(l[ia, ih - 1 if ih > 0 else 0, ichi, :])
            f = Flamelet(**flamelet_specs)
            output = f.compute_steady_state()

            for quantity in output.props:
                l[quantity][ia, ih, ichi, :] = output[quantity].ravel()
        cput1 = perf_counter()
        print(f'{"":9} heat loss solutions for chi_st = {chist:7.2e} Hz in {cput1-cput0:6.2f} s | {"":14} is {cput1 - cput000:6.2f} s')

    cput1 = perf_counter()
    print(f'Completed alpha = {alpha:4.2f} in {cput1-cput00:6.2f} s')
    print('-' * 95)
Running alpha = 0.00 ...
Converged adiabatic solutions in   4.35 s                          | total cpu time is   4.35 s
          heat loss solutions for chi_st = 1.00e-01 Hz in   3.87 s |                is   8.22 s
          heat loss solutions for chi_st = 3.73e-01 Hz in   1.02 s |                is   9.24 s
          heat loss solutions for chi_st = 1.39e+00 Hz in   0.92 s |                is  10.16 s
          heat loss solutions for chi_st = 5.18e+00 Hz in   1.41 s |                is  11.57 s
          heat loss solutions for chi_st = 1.93e+01 Hz in   0.89 s |                is  12.46 s
          heat loss solutions for chi_st = 7.20e+01 Hz in   0.87 s |                is  13.33 s
          heat loss solutions for chi_st = 2.68e+02 Hz in   1.02 s |                is  14.35 s
          heat loss solutions for chi_st = 1.00e+03 Hz in   0.76 s |                is  15.12 s
Completed alpha = 0.00 in  15.12 s
-----------------------------------------------------------------------------------------------
Running alpha = 0.10 ...
Converged adiabatic solutions in  11.43 s                          | total cpu time is  26.55 s
          heat loss solutions for chi_st = 1.00e-01 Hz in  34.36 s |                is  60.91 s
          heat loss solutions for chi_st = 3.73e-01 Hz in  16.77 s |                is  77.69 s
          heat loss solutions for chi_st = 1.39e+00 Hz in   1.66 s |                is  79.35 s
          heat loss solutions for chi_st = 5.18e+00 Hz in   1.09 s |                is  80.44 s
          heat loss solutions for chi_st = 1.93e+01 Hz in   0.86 s |                is  81.30 s
          heat loss solutions for chi_st = 7.20e+01 Hz in   0.83 s |                is  82.12 s
          heat loss solutions for chi_st = 2.68e+02 Hz in   0.86 s |                is  82.99 s
          heat loss solutions for chi_st = 1.00e+03 Hz in   0.77 s |                is  83.76 s
Completed alpha = 0.10 in  68.64 s
-----------------------------------------------------------------------------------------------
Running alpha = 0.30 ...
Converged adiabatic solutions in   7.68 s                          | total cpu time is  91.44 s
          heat loss solutions for chi_st = 1.00e-01 Hz in  37.23 s |                is 128.67 s
          heat loss solutions for chi_st = 3.73e-01 Hz in  21.49 s |                is 150.16 s
          heat loss solutions for chi_st = 1.39e+00 Hz in   1.48 s |                is 151.65 s
          heat loss solutions for chi_st = 5.18e+00 Hz in   0.91 s |                is 152.55 s
          heat loss solutions for chi_st = 1.93e+01 Hz in   1.01 s |                is 153.56 s
          heat loss solutions for chi_st = 7.20e+01 Hz in   0.98 s |                is 154.54 s
          heat loss solutions for chi_st = 2.68e+02 Hz in   0.92 s |                is 155.45 s
          heat loss solutions for chi_st = 1.00e+03 Hz in   0.78 s |                is 156.24 s
Completed alpha = 0.30 in  72.48 s
-----------------------------------------------------------------------------------------------
Running alpha = 0.50 ...
Converged adiabatic solutions in   8.09 s                          | total cpu time is 164.33 s
          heat loss solutions for chi_st = 1.00e-01 Hz in  35.17 s |                is 199.50 s
          heat loss solutions for chi_st = 3.73e-01 Hz in  17.21 s |                is 216.71 s
          heat loss solutions for chi_st = 1.39e+00 Hz in   1.47 s |                is 218.18 s
          heat loss solutions for chi_st = 5.18e+00 Hz in   0.94 s |                is 219.12 s
          heat loss solutions for chi_st = 1.93e+01 Hz in   0.94 s |                is 220.06 s
          heat loss solutions for chi_st = 7.20e+01 Hz in   0.81 s |                is 220.87 s
          heat loss solutions for chi_st = 2.68e+02 Hz in   0.86 s |                is 221.73 s
          heat loss solutions for chi_st = 1.00e+03 Hz in   0.86 s |                is 222.59 s
Completed alpha = 0.50 in  66.36 s
-----------------------------------------------------------------------------------------------
Running alpha = 0.70 ...
Converged adiabatic solutions in  10.38 s                          | total cpu time is 232.98 s
          heat loss solutions for chi_st = 1.00e-01 Hz in  31.82 s |                is 264.80 s
          heat loss solutions for chi_st = 3.73e-01 Hz in  11.55 s |                is 276.35 s
          heat loss solutions for chi_st = 1.39e+00 Hz in   1.10 s |                is 277.45 s
          heat loss solutions for chi_st = 5.18e+00 Hz in   0.84 s |                is 278.30 s
          heat loss solutions for chi_st = 1.93e+01 Hz in   0.88 s |                is 279.17 s
          heat loss solutions for chi_st = 7.20e+01 Hz in   1.06 s |                is 280.23 s
          heat loss solutions for chi_st = 2.68e+02 Hz in   0.89 s |                is 281.12 s
          heat loss solutions for chi_st = 1.00e+03 Hz in   0.87 s |                is 281.99 s
Completed alpha = 0.70 in  59.39 s
-----------------------------------------------------------------------------------------------

1.2.14.1. Saving a library file for later use

Saving a library file to disk to be reloaded later is straightforward.

The rest of the notebook consists of some visualizations of the library data.

l.save_to_file('coal_library.pkl')
library = Library.load_from_file('coal_library.pkl')
z_dim = library.dim('mixture_fraction')
x_dim = library.dim('dissipation_rate_stoich')
h_dim = library.dim('heat_transfer_coefficient')
a_dim = library.dim('alpha')

Here are the adiabatic temperature profiles at minimium and moderate \(\chi_{\rm st}\) for various mixtures of volatiles and char.

for ia, a in enumerate(a_dim.values):
    plt.plot(z_dim.values, library['temperature'][ia, 0, 0, :], label='$\\' + a_dim.name + '$' + f'={a:.2f}')
plt.legend(loc='best')
plt.xlabel(z_dim.name)
plt.ylabel('temperature')
plt.show()

for ia, a in enumerate(a_dim.values):
    plt.plot(z_dim.values, library['temperature'][ia, 0, 6, :], label='$\\' + a_dim.name + '$' + f'={a:.2f}')
plt.legend(loc='best')
plt.xlabel(z_dim.name)
plt.ylabel('temperature')
plt.show()
../../_images/example_coal_combustion_model_16_0.png ../../_images/example_coal_combustion_model_16_1.png

And here are some contour plots of adiabatic profiles over \((\mathcal{Z},\chi_{\rm st})\) for each \(\alpha\) value. Note the effect of \(\alpha\) on the availablity of acetylene, a key ingredient in soot formation.

from mpl_toolkits.mplot3d import axes3d
from matplotlib.colors import Normalize

fig = plt.figure()
ax = fig.add_subplot(projection='3d')
z = library.mixture_fraction_grid[0, 0, :, :]
x = np.log10(library.dissipation_rate_stoich_grid[0, 0, :, :])
for ia, alpha in enumerate(adim.values):
    ax.contourf(z, x, l['temperature'][ia, 0, :, :], offset=alpha,
                cmap='inferno', levels=30, norm=Normalize(vmin=300, vmax=2400))
ax.set_zlim([0, 0.7])
ax.set_xlabel('$\\mathcal{Z}$')
ax.set_ylabel('$\\log_{10}\\chi_{\\rm st}$ (Hz)')
ax.set_zlabel('$\\alpha$')
ax.set_zticks([0, 0.1, 0.3, 0.5, 0.7])
ax.set_title('gas temperature (K)')
fig.set_size_inches(8, 8)
plt.show()

fig = plt.figure()
ax = fig.add_subplot(projection='3d')
for ia, alpha in enumerate(adim.values):
    ax.contourf(z, x, l['mass fraction OH'][ia, 0, :, :], offset=alpha,
                cmap='Purples', norm=Normalize(vmin=0, vmax=1e-2), alpha=0.8)
ax.set_zlim([0, 0.7])
ax.set_xlabel('$\\mathcal{Z}$')
ax.set_ylabel('$\\log_{10}\\chi_{\\rm st}$ (Hz)')
ax.set_zlabel('$\\alpha$')
ax.set_zticks([0, 0.1, 0.3, 0.5, 0.7])
ax.set_title('mass fraction OH')
fig.set_size_inches(8, 8)
plt.show()

fig = plt.figure()
ax = fig.add_subplot(projection='3d')
for ia, alpha in enumerate(adim.values):
    ax.contourf(z, x, l['mass fraction C2H2'][ia, 0, :, :], offset=alpha,
                cmap='Oranges', norm=Normalize(vmin=0, vmax=1e-2), alpha=0.8)
ax.set_zlim([0, 0.7])
ax.set_xlabel('$\\mathcal{Z}$')
ax.set_ylabel('$\\log_{10}\\chi_{\\rm st}$ (Hz)')
ax.set_zlabel('$\\alpha$')
ax.set_zticks([0, 0.1, 0.3, 0.5, 0.7])
ax.set_title('mass fraction C2H2')
fig.set_size_inches(8, 8)
plt.show()
../../_images/example_coal_combustion_model_18_0.png ../../_images/example_coal_combustion_model_18_1.png ../../_images/example_coal_combustion_model_18_2.png