—

Interactive Reaction Path Diagrams for CH₄ plasma chemistry.#

This example uses ipywidgets to create interactive displays of reaction path diagrams from Cantera simulations.

Inspired by https://cantera.org/3.1/examples/python/kinetics/interactive_path_diagram.html.

Requires: cantera >= 3.0.0, matplotlib >= 2.0, ipywidgets, graphviz, scipy

Import the required libraries.#

import cantera as ct
import graphviz
import matplotlib.pyplot as plt
import numpy as np

from bloc.paths import get_mechanism_path

print(f"Using Cantera version: {ct.__version__}")

# Determine if we're running in a Jupyter Notebook. If so, we can enable the interactive
# diagrams. Otherwise, just draw output for a single set of inputs.
try:
    from IPython.core.getipython import get_ipython

    config = get_ipython()
    if config is None:
        raise ImportError("console")

except (ImportError, AttributeError):
    is_interactive = False
else:
    is_interactive = True

if is_interactive:
    from IPython.display import display
    from ipywidgets import interact, widgets

print(
    "Running in interactive mode"
    if is_interactive
    else "Running in non-interactive mode"
)

Create the gas object and set the initial conditions.#

# Load the mechanism.
mechanism = get_mechanism_path("Caltech.yaml")
gas = ct.Solution(mechanism)

# Set the initial temperature, pressure and composition.
gas.TPX = 300, ct.one_atm, "CH4:1.0"
gas.HPX = gas.enthalpy_mass + 12e6, ct.one_atm, "CH4:1.0"

# Set the residence time for the reactor network.
residence_time = 1e0  # s

# Create an ideal gas constant pressure reactor and set solver tolerances.
reactor = ct.IdealGasConstPressureMoleReactor(gas, energy="on")
reactor_network = ct.ReactorNet([reactor])
precon = ct.AdaptivePreconditioner()  # Create the preconditioner
reactor_network.preconditioner = precon  # Add it to the network

# Plot options.
xlim = (1e-12, residence_time)

# Wanted species.
wanted_species = "C2H2"

# Store the reactions and reaction equations.
gas_reactions = gas.reactions()
gas_reaction_equations = gas.reaction_equations()

Store time, pressure, temperature and mole fractions.#

# Store the states in a SolutionArray.
states = ct.SolutionArray(gas, 1, extra={"t": [0.0]})

# Run the simulation
steps = 0
time_steps = np.logspace(-6, np.log10(residence_time), 100)
for time_step in time_steps:
    reactor_network.advance(time_step)
    states.append(gas.state, t=reactor_network.time)
    print(f"Step {steps} at time {reactor_network.time} s")
    steps += 1


time = states.t  # type: ignore

Plot temperature evolution with time.#

fig, ax = plt.subplots()
ax.plot(
    time,
    states.T,
)
ax.set_ylabel("Temperature [K]")
ax.set_title("Temperature evolution")
ax.set_xlabel("Time [s]")
ax.set_xscale("log")

Plot species evolution with time.#

fig, ax = plt.subplots()

species_to_plot = set(["CH4", "H2", "C2H2", "C2H4", "C2H6", "H", wanted_species])
for species in species_to_plot:
    X = states(species).X
    ax.plot(time, X, label=species)

    x = time[np.argmax(X)]
    y = X[np.argmax(X)]
    if x < 1e-11:
        x = 1e-11
        y = X[np.where(time >= 1e-11)[0][0]]


ax.legend()
ax.set_xlabel("Time [s]")
ax.set_ylabel("Mole fraction [%]")
ax.set_title("Mole fraction evolution with time.")
ax.set_xscale("log")
ax.set_yscale("log")
ax.set_xlim(xlim)
ax.set_ylim(1e-6, 1)

Interactive reaction path diagram.#

When executed as a Jupyter Notebook, the plotted time step, threshold and element can be changed using the slider provided by IPyWidgets.

def plot_reaction_path_diagrams(plot_step, threshold, details, element):
    P = states.P[plot_step]
    T = states.T[plot_step]
    X = states.X[plot_step]
    gas.TPX = T, P, X

    diagram = ct.ReactionPathDiagram(gas, element)
    diagram.threshold = threshold
    diagram.title = f"time = {time[plot_step]:.2g} s"

    diagram.show_details = details
    if is_interactive:
        graph = graphviz.Source(diagram.get_dot())
        display(graph)
    else:
        graph = graphviz.Source(diagram.get_dot(), format="svg")
        return graph


if is_interactive:
    interact(
        plot_reaction_path_diagrams,
        plot_step=widgets.IntSlider(value=100, min=0, max=steps - 1, step=10),
        threshold=widgets.FloatSlider(value=0.1, min=0.001, max=0.4, step=0.01),
        details=widgets.ToggleButton(),
        element=widgets.Dropdown(
            options=gas.element_names,
            value="C",
            description="Element",
            disabled=False,
        ),
    )
    diagram = ""
else:
    # For non-interactive use, just draw the diagram for a specified time step.
    diagram = plot_reaction_path_diagrams(
        plot_step=520, threshold=0.1, details=False, element="C"
    )


class PlotGraphviz:
    # See https://stackoverflow.com/questions/65008861/capturing-graphviz-figures-in-sphinx-gallery.
    def __init__(self, dot_string):
        self.dot_string = dot_string

    def _repr_html_(self):
        return graphviz.Source(self.dot_string)._repr_image_svg_xml()


PlotGraphviz(str(diagram))