
:html_theme.sidebar_secondary.remove:

.. py:currentmodule:: cantera


.. DO NOT EDIT.
.. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY.
.. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE:
.. "examples/python/reactors/continuous_reactor.py"
.. LINE NUMBERS ARE GIVEN BELOW.

.. only:: html

    .. note::
        :class: sphx-glr-download-link-note

        :ref:`Go to the end <sphx_glr_download_examples_python_reactors_continuous_reactor.py>`
        to download the full example code.

.. rst-class:: sphx-glr-example-title

.. _sphx_glr_examples_python_reactors_continuous_reactor.py:


Continuously Stirred Tank Reactor
=================================

In this example we will illustrate how Cantera can be used to simulate a
`continuously stirred tank reactor <https://en.wikipedia.org/wiki/Continuous_stirred-tank_reactor>`__
(CSTR), also interchangeably referred to as a perfectly stirred reactor (PSR), a well
stirred reactor (WSR), a jet stirred reactor (JSR), or a
`Longwell reactor <https://nap.nationalacademies.org/read/13160/chapter/48>`__ (and
there may well be more "aliases").

Requires: cantera >= 3.2, matplotlib >= 2.0, pandas

.. tags:: Python, combustion, reactor network, well-stirred reactor

Simulation of a CSTR/PSR/WSR
----------------------------

A diagram of a CSTR is shown below:

.. image:: /_static/images/samples/stirred-reactor-solo.svg
   :width: 50%
   :alt: A continuously stirred tank reactor with inflow and outflow
   :align: center

As the figure illustrates, this is an open system (unlike a batch reactor, which is
isolated). *P*, *V* and *T* are the reactor's pressure, volume and temperature
respectively. The mass flow rate at which reactants come in is the same as that of the
products which exit, and on average this mass stays in the reactor for a characteristic
time :math:`\tau`, called the *residence time*. This is a key quantity in sizing the
reactor and is defined as follows:

.. math::

   \tau = \frac{m}{\dot{m}}

where :math:`m` is the mass of the gas.

.. GENERATED FROM PYTHON SOURCE LINES 41-43

Import modules and set plotting defaults
----------------------------------------

.. GENERATED FROM PYTHON SOURCE LINES 43-53

.. code-block:: Python


    import time
    from io import StringIO
    import matplotlib.pyplot as plt
    import pandas as pd
    import cantera as ct

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






.. rst-class:: sphx-glr-script-out

 .. code-block:: none

    Running Cantera version: 3.2.0




.. GENERATED FROM PYTHON SOURCE LINES 54-60

Define the gas
--------------

In this example, we will work with n-C₇H₁₆/O₂/He mixtures, for which experimental data
can be found in the paper by Zhang et al. [1]_ We will use the same mechanism reported
in the paper. It consists of 1268 species and 5336 reactions.

.. GENERATED FROM PYTHON SOURCE LINES 60-63

.. code-block:: Python


    gas = ct.Solution("example_data/n-hexane-NUIG-2015.yaml")








.. GENERATED FROM PYTHON SOURCE LINES 64-69

Define initial conditions
-------------------------

Inlet conditions for the gas and reactor parameters
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

.. GENERATED FROM PYTHON SOURCE LINES 69-78

.. code-block:: Python


    reactor_temperature = 925  # Kelvin
    reactor_pressure = 1.046138 * ct.one_atm  # in atm. This equals 1.06 bars
    inlet_X = {"NC7H16": 0.005, "O2": 0.0275, "HE": 0.9675}
    gas.TPX = reactor_temperature, reactor_pressure, inlet_X

    residence_time = 2  # s
    reactor_volume = 30.5 * (1e-2) ** 3  # m3








.. GENERATED FROM PYTHON SOURCE LINES 79-81

Simulation parameters
^^^^^^^^^^^^^^^^^^^^^

.. GENERATED FROM PYTHON SOURCE LINES 81-85

.. code-block:: Python


    # Simulation termination criterion
    max_simulation_time = 50  # seconds








.. GENERATED FROM PYTHON SOURCE LINES 86-105

Reactor arrangement
-------------------

We showed a cartoon of the reactor in the first figure in this notebook, but to
actually simulate that, we need a few peripherals. A mass-flow controller upstream of
the stirred reactor will allow us to flow gases in, and in-turn, a "reservoir" which
simulates a gas tank is required to supply gases to the mass flow controller.
Downstream of the reactor, we install a pressure regulator which allows the reactor
pressure to stay within. Downstream of the regulator we will need another reservoir
which acts like a "sink" or capture tank to capture all exhaust gases (even our
simulations are environmentally friendly!). This arrangement is illustrated below:

.. image:: /_static/images/samples/stirred-reactor-network.svg
   :width: 80%
   :alt: A complete reactor network representing a continuously stirred tank reactor
   :align: center

Initialize the stirred reactor and connect all peripherals
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

.. GENERATED FROM PYTHON SOURCE LINES 105-155

.. code-block:: Python


    fuel_air_mixture_tank = ct.Reservoir(gas, clone=True)
    exhaust = ct.Reservoir(gas, clone=True)

    stirred_reactor = ct.IdealGasMoleReactor(gas, energy="off",
                                             clone=True, volume=reactor_volume)

    mass_flow_controller = ct.MassFlowController(
        upstream=fuel_air_mixture_tank,
        downstream=stirred_reactor,
        mdot=lambda t: stirred_reactor.mass / residence_time,
    )

    pressure_regulator = ct.PressureController(
        upstream=stirred_reactor,
        downstream=exhaust,
        primary=mass_flow_controller,
        K=1e-3,
    )

    reactor_network = ct.ReactorNet([stirred_reactor])

    # Create a SolutionArray to store the data
    time_history = ct.SolutionArray(gas, extra=["t"])

    # Set the maximum simulation time
    max_simulation_time = 50  # seconds

    # Start the stopwatch
    tic = time.time()

    # Set simulation start time to zero
    t = 0
    counter = 1
    while t < max_simulation_time:
        t = reactor_network.step()

        # We will store only every 10th value. Remember, we have 1200+ species, so there
        # will be 1200+ columns for us to work with
        if counter % 10 == 0:
            # Extract the state of the reactor
            time_history.append(stirred_reactor.phase.state, t=t)

        counter += 1

    # Stop the stopwatch
    toc = time.time()

    print(f"Simulation Took {toc-tic:3.2f}s to compute, with {counter} steps")





.. rst-class:: sphx-glr-script-out

 .. code-block:: none

    Simulation Took 98.85s to compute, with 329 steps




.. GENERATED FROM PYTHON SOURCE LINES 156-161

Plot the results
^^^^^^^^^^^^^^^^

As a test, we plot the mole fraction of CO and see if the simulation has converged. If
not, go back and adjust max. number of steps and/or simulation time.

.. GENERATED FROM PYTHON SOURCE LINES 161-168

.. code-block:: Python


    plt.figure()
    plt.semilogx(time_history.t, time_history("CO").X, "-o")
    plt.xlabel("Time (s)")
    plt.ylabel("Mole Fraction : $X_{CO}$")
    plt.show()




.. image-sg:: /examples/python/reactors/images/sphx_glr_continuous_reactor_001.png
   :alt: continuous reactor
   :srcset: /examples/python/reactors/images/sphx_glr_continuous_reactor_001.png, /examples/python/reactors/images/sphx_glr_continuous_reactor_001_2_00x.png 2.00x
   :class: sphx-glr-single-img





.. GENERATED FROM PYTHON SOURCE LINES 169-176

Illustration : Modeling experimental data
-----------------------------------------

Let us see if the reactor can reproduce actual experimental measurements.

We first load the data. This is also supplied in the paper by Zhang et al. [1]_ as an
excel sheet

.. GENERATED FROM PYTHON SOURCE LINES 176-209

.. code-block:: Python


    experimental_data_csv = """
    T,NC7H16,O2,CO,CO2
    500,5.07E-03,2.93E-02,0.00E+00,0.00E+00
    525,4.92E-03,2.86E-02,0.00E+00,0.00E+00
    550,4.66E-03,2.85E-02,0.00E+00,0.00E+00
    575,4.16E-03,2.63E-02,2.43E-04,1.01E-04
    600,3.55E-03,2.33E-02,9.68E-04,2.51E-04
    625,3.36E-03,2.31E-02,1.42E-03,2.67E-04
    650,3.67E-03,2.45E-02,9.16E-04,1.46E-04
    675,4.38E-03,2.77E-02,2.25E-04,0.00E+00
    700,4.79E-03,2.87E-02,0.00E+00,0.00E+00
    725,4.89E-03,2.93E-02,0.00E+00,0.00E+00
    750,4.91E-03,2.84E-02,0.00E+00,0.00E+00
    775,4.93E-03,2.80E-02,0.00E+00,0.00E+00
    800,4.78E-03,2.82E-02,0.00E+00,0.00E+00
    825,4.41E-03,2.80E-02,1.49E-05,0.00E+00
    850,3.68E-03,2.80E-02,4.18E-04,1.66E-04
    875,2.13E-03,2.45E-02,1.65E-03,2.22E-04
    900,1.03E-03,2.05E-02,5.51E-03,3.69E-04
    925,5.82E-04,1.79E-02,8.59E-03,6.78E-04
    950,3.88E-04,1.47E-02,1.05E-02,1.07E-03
    975,2.35E-04,1.28E-02,1.19E-02,1.36E-03
    1000,1.14E-04,1.16E-02,1.34E-02,1.82E-03
    1025,4.83E-05,9.88E-03,1.52E-02,2.41E-03
    1050,1.64E-05,8.16E-03,1.83E-02,2.97E-03
    1075,1.22E-06,5.48E-03,1.95E-02,3.67E-03
    1100,0.00E+00,3.24E-03,2.14E-02,4.38E-03
    """

    experimental_data = pd.read_csv(StringIO(experimental_data_csv))
    experimental_data.head()






.. raw:: html

    <div class="output_subarea output_html rendered_html output_result">
    <div>
    <style scoped>
        .dataframe tbody tr th:only-of-type {
            vertical-align: middle;
        }

        .dataframe tbody tr th {
            vertical-align: top;
        }

        .dataframe thead th {
            text-align: right;
        }
    </style>
    <table border="1" class="dataframe">
      <thead>
        <tr style="text-align: right;">
          <th></th>
          <th>T</th>
          <th>NC7H16</th>
          <th>O2</th>
          <th>CO</th>
          <th>CO2</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <th>0</th>
          <td>500</td>
          <td>0.00507</td>
          <td>0.0293</td>
          <td>0.000000</td>
          <td>0.000000</td>
        </tr>
        <tr>
          <th>1</th>
          <td>525</td>
          <td>0.00492</td>
          <td>0.0286</td>
          <td>0.000000</td>
          <td>0.000000</td>
        </tr>
        <tr>
          <th>2</th>
          <td>550</td>
          <td>0.00466</td>
          <td>0.0285</td>
          <td>0.000000</td>
          <td>0.000000</td>
        </tr>
        <tr>
          <th>3</th>
          <td>575</td>
          <td>0.00416</td>
          <td>0.0263</td>
          <td>0.000243</td>
          <td>0.000101</td>
        </tr>
        <tr>
          <th>4</th>
          <td>600</td>
          <td>0.00355</td>
          <td>0.0233</td>
          <td>0.000968</td>
          <td>0.000251</td>
        </tr>
      </tbody>
    </table>
    </div>
    </div>
    <br />
    <br />

.. GENERATED FROM PYTHON SOURCE LINES 210-218

.. code-block:: Python


    # Define all the temperatures at which we will run simulations. These should overlap
    # with the values reported in the paper as much as possible
    T = [650, 700, 750, 775, 825, 850, 875, 925, 950, 1075, 1100]

    # Create a SolutionArray to store values for the above points
    temp_dependence = ct.SolutionArray(gas)








.. GENERATED FROM PYTHON SOURCE LINES 219-220

Now we simply run the reactor code we used above for each temperature

.. GENERATED FROM PYTHON SOURCE LINES 220-258

.. code-block:: Python


    reactor_X = inlet_X

    for reactor_temperature in T:
        gas.TPX = reactor_temperature, reactor_pressure, inlet_X
        fuel_air_mixture_tank = ct.Reservoir(gas, clone=True)

        # Use composition from the previous iteration to speed up convergence
        gas.TPX = reactor_temperature, reactor_pressure, reactor_X
        stirred_reactor = ct.IdealGasReactor(gas, energy="off", clone=True,
                                             volume=reactor_volume)
        fuel_air_mixture_tank = ct.Reservoir(gas, clone=True)
        stirred_reactor = ct.IdealGasReactor(gas, energy="off", clone=True,
                                             volume=reactor_volume)
        mass_flow_controller = ct.MassFlowController(
            upstream=fuel_air_mixture_tank,
            downstream=stirred_reactor,
            mdot=lambda t: stirred_reactor.mass / residence_time,
        )
        pressure_regulator = ct.PressureController(
            upstream=stirred_reactor, downstream=exhaust, primary=mass_flow_controller,
            K=1e-3,
        )
        reactor_network = ct.ReactorNet([stirred_reactor])

        # Re-run the isothermal simulations
        tic = time.time()
        counter = 0
        while reactor_network.time < max_simulation_time:
            reactor_network.step()
            counter += 1
        toc = time.time()
        print(f"Simulation at T={reactor_temperature} K took {toc-tic:3.2f} s to compute "
              f"with {counter} steps")

        reactor_X = stirred_reactor.phase.X
        temp_dependence.append(stirred_reactor.phase.state)





.. rst-class:: sphx-glr-script-out

 .. code-block:: none

    Simulation at T=650 K took 162.47 s to compute with 1201 steps
    Simulation at T=700 K took 132.73 s to compute with 1083 steps
    Simulation at T=750 K took 78.53 s to compute with 713 steps
    Simulation at T=775 K took 74.65 s to compute with 590 steps
    Simulation at T=825 K took 112.44 s to compute with 869 steps
    Simulation at T=850 K took 113.92 s to compute with 844 steps
    Simulation at T=875 K took 98.34 s to compute with 827 steps
    Simulation at T=925 K took 97.88 s to compute with 867 steps
    Simulation at T=950 K took 88.47 s to compute with 754 steps
    Simulation at T=1075 K took 109.08 s to compute with 974 steps
    Simulation at T=1100 K took 92.50 s to compute with 715 steps




.. GENERATED FROM PYTHON SOURCE LINES 259-261

Compare the model results with experimental data
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

.. GENERATED FROM PYTHON SOURCE LINES 261-301

.. code-block:: Python


    plt.figure()
    plt.plot(
        temp_dependence.T, temp_dependence("NC7H16").X, color="C0", label="$nC_{7}H_{16}$"
    )
    plt.plot(temp_dependence.T, temp_dependence("CO").X, color="C1", label="CO")
    plt.plot(temp_dependence.T, temp_dependence("O2").X, color="C2", label="O$_{2}$")

    plt.plot(
        experimental_data["T"],
        experimental_data["NC7H16"],
        color="C0",
        marker="o",
        linestyle="none",
        label="$nC_{7}H_{16}$ (exp)",
    )
    plt.plot(
        experimental_data["T"],
        experimental_data["CO"],
        color="C1",
        marker="^",
        linestyle="none",
        label="CO (exp)",
    )
    plt.plot(
        experimental_data["T"],
        experimental_data["O2"],
        color="C2",
        marker="s",
        linestyle="none",
        label="O$_2$ (exp)",
    )

    plt.xlabel("Temperature (K)")
    plt.ylabel(r"Mole Fractions")

    plt.xlim([650, 1100])
    plt.legend(loc=1)
    plt.show()




.. image-sg:: /examples/python/reactors/images/sphx_glr_continuous_reactor_002.png
   :alt: continuous reactor
   :srcset: /examples/python/reactors/images/sphx_glr_continuous_reactor_002.png, /examples/python/reactors/images/sphx_glr_continuous_reactor_002_2_00x.png 2.00x
   :class: sphx-glr-single-img





.. GENERATED FROM PYTHON SOURCE LINES 302-308

References
----------

.. [1] K. Zhang, C. Banyon, C. Togbé, P. Dagaut, J. Bugler, H. J. Curran (2015). "An
       experimental and kinetic modeling study of n-hexane oxidation," *Combustion and
       Flame* 162:11, 4194-4207, https://doi.org/10.1016/j.combustflame.2015.08.001.


.. rst-class:: sphx-glr-timing

   **Total running time of the script:** (21 minutes 28.614 seconds)


.. _sphx_glr_download_examples_python_reactors_continuous_reactor.py:

.. only:: html

  .. container:: sphx-glr-footer sphx-glr-footer-example

    .. container:: sphx-glr-download sphx-glr-download-jupyter

      :download:`Download Jupyter notebook: continuous_reactor.ipynb <continuous_reactor.ipynb>`

    .. container:: sphx-glr-download sphx-glr-download-python

      :download:`Download Python source code: continuous_reactor.py <continuous_reactor.py>`

    .. container:: sphx-glr-download sphx-glr-download-zip

      :download:`Download zipped: continuous_reactor.zip <continuous_reactor.zip>`


.. only:: html

 .. rst-class:: sphx-glr-signature

    `Gallery generated by Sphinx-Gallery <https://sphinx-gallery.github.io>`_
