
: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/onedim/diffusion_flame_continuation.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_onedim_diffusion_flame_continuation.py>`
        to download the full example code.

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

.. _sphx_glr_examples_python_onedim_diffusion_flame_continuation.py:


Diffusion flame unstable branch
===============================

This example uses the two-point flame control feature to march solutions
down the stable and unstable burning branch for a counterflow diffusion flame.
A hydrogen-oxygen diffusion flame at 1 bar is studied.

Requires: cantera >= 3.1, matplotlib >= 2.0

.. tags:: Python, combustion, 1D flow, diffusion flame, strained flame, extinction,
          saving output, plotting

.. GENERATED FROM PYTHON SOURCE LINES 14-33

.. code-block:: Python


    import logging
    import sys
    from pathlib import Path

    import matplotlib.pyplot as plt
    import numpy as np
    import pandas as pd

    import cantera as ct

    logger = logging.getLogger(__name__)
    logger.setLevel(logging.INFO)
    logging.basicConfig(stream=sys.stdout)

    # Workaround to support both Numpy 1.x and 2.4.0+
    # TODO: Replace when dropping Numpy 1.x support
    trapezoid = getattr(np, "trapezoid", None) or np.trapz








.. GENERATED FROM PYTHON SOURCE LINES 34-36

Flame Initialization
--------------------

.. GENERATED FROM PYTHON SOURCE LINES 36-65

.. code-block:: Python


    # Set up an initial hydrogen-oxygen counterflow flame at 1 bar and low strain
    # rate (maximum axial velocity gradient = 2414 1/s)

    reaction_mechanism = 'h2o2.yaml'
    gas = ct.Solution(reaction_mechanism)
    width = 18.e-3  # 18mm wide
    f = ct.CounterflowDiffusionFlame(gas, width=width)

    # Define the operating pressure and boundary conditions
    f.P = 1.0e5  # 1 bar
    f.fuel_inlet.mdot = 0.5  # kg/m^2/s
    f.fuel_inlet.X = 'H2:1'
    f.fuel_inlet.T = 300  # K
    f.oxidizer_inlet.mdot = 3.0  # kg/m^2/s
    f.oxidizer_inlet.X = 'O2:1'
    f.oxidizer_inlet.T = 500  # K

    # Set refinement parameters
    f.set_refine_criteria(ratio=4.0, slope=0.1, curve=0.2, prune=0.05)

    # Initialize and solve
    logger.info('Creating the initial solution')
    f.solve(loglevel=0, auto=True)

    # Define output locations
    output_path = Path() / "diffusion_flame_continuation_data"
    output_path.mkdir(parents=True, exist_ok=True)





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

 .. code-block:: none

    INFO:__main__:Creating the initial solution




.. GENERATED FROM PYTHON SOURCE LINES 66-68

Flame Continuation
------------------

.. GENERATED FROM PYTHON SOURCE LINES 68-186

.. code-block:: Python


    # Maximum number of steps to take
    n_max = 1000

    # Relative temperature defining control point locations, with 1 being the peak
    # temperature and 0 being the inlet temperature. Lower values tend to avoid solver
    # failures early on, while using higher values on the unstable branch tend to help
    # with finding solutions where the peak temperature is very low.
    initial_spacing = 0.6
    unstable_spacing = 0.95

    # Amount to adjust temperature at the control point each step [K]
    temperature_increment = 20.0
    max_increment = 100

    # Try to keep T_max from changing more than this much each step [K]
    target_delta_T_max = 20

    # Stop after this many successive errors
    max_error_count = 3
    error_count = 0

    # Stop after any failure if the strain rate has dropped to this fraction of the maximum
    strain_rate_tol = 0.10

    f.two_point_control_enabled = True

    # Prevent two point control from finding solutions with negative inlet velocities
    f.flame.set_bounds(spread_rate=(-1e-5, 1e20))

    f.max_time_step_count = 100
    T_max = max(f.T)
    a_max = strain_rate = f.strain_rate('max')
    data = []  # integral output quantities for each step
    logger.info('Starting two-point control')

    for i in range(n_max):
        if strain_rate > 0.98 * a_max:
            spacing = initial_spacing
        else:
            spacing = unstable_spacing
        control_temperature = np.min(f.T) + spacing*(np.max(f.T) - np.min(f.T))

        # Store the flame state in case the iteration fails and we need to roll back
        backup_state = f.to_array()

        logger.debug(f'Iteration {i}: Control temperature = {control_temperature:.2f} K')
        f.set_left_control_point(control_temperature)
        f.set_right_control_point(control_temperature)

        # This decrement is what drives the two-point control. If failure
        # occurs, try decreasing the decrement.
        f.left_control_point_temperature -= temperature_increment
        f.right_control_point_temperature -= temperature_increment
        f.clear_stats()

        if (f.left_control_point_temperature < f.fuel_inlet.T + 100
            or f.right_control_point_temperature < f.oxidizer_inlet.T + 100
        ):
            logger.info("SUCCESS! Stopping because control point temperature is "
                        "sufficiently close to inlet temperature.")
            break

        try:
            f.solve(loglevel=0)
            if abs(max(f.T) - T_max) < 0.8 * target_delta_T_max:
                # Max temperature is changing slowly. Try a larger increment next step
                temperature_increment = min(temperature_increment + 3, max_increment)
            elif abs(max(f.T) - T_max) > target_delta_T_max:
                # Max temperature is changing quickly. Scale down increment for next step
                temperature_increment *= 0.9 * target_delta_T_max / (abs(max(f.T) - T_max))
            error_count = 0
        except ct.CanteraError as err:
            logger.debug(err)
            if strain_rate / a_max < strain_rate_tol:
                logger.info('SUCCESS! Traversed unstable branch down to '
                            f'{100 * strain_rate / a_max:.2f}% of the maximum strain rate.')
                break

            # Restore the previous solution and try a smaller temperature increment for the
            # next iteration
            f.from_array(backup_state)
            temperature_increment = 0.7 * temperature_increment
            error_count += 1
            logger.warning(f"Solver did not converge on iteration {i}. Trying again with "
                           f"dT = {temperature_increment:.2f}")

        if ct.hdf_support():
            f.save(output_path / 'flame_profiles.h5', name=f'iteration{i}', overwrite=True)

        # Collect output stats
        T_max = max(f.T)
        T_mid = 0.5 * (min(f.T) + max(f.T))
        s = np.where(f.T > T_mid)[0][[0,-1]]
        width = f.grid[s[1]] - f.grid[s[0]]
        strain_rate = f.strain_rate('max')
        a_max = max(strain_rate, a_max)

        data.append({
            'T_max': max(f.T),
            'strain_rate': strain_rate,
            'heat_release_rate': trapezoid(f.heat_release_rate, f.grid),
            'n_points': len(f.grid),
            'flame_width': width,
            'Tc_increment': temperature_increment,
            'time_steps': sum(f.time_step_stats),
            'eval_count': sum(f.eval_count_stats),
            'cpu_time': sum(f.jacobian_time_stats + f.eval_time_stats),
            'errors': error_count
        })

        if error_count >= max_error_count:
            logger.warning(f'FAILURE! Stopping after {error_count} successive solver '
                           'errors.')
            break

    logger.info(f'Stopped after {i} iterations')





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

 .. code-block:: none

    INFO:__main__:Starting two-point control
    INFO:__main__:SUCCESS! Traversed unstable branch down to 4.13% of the maximum strain rate.
    INFO:__main__:Stopped after 134 iterations




.. GENERATED FROM PYTHON SOURCE LINES 187-189

Combine data
------------

.. GENERATED FROM PYTHON SOURCE LINES 189-193

.. code-block:: Python

    df = pd.DataFrame.from_records(data)
    df.to_csv(output_path / f'integral_data.csv')
    df






.. 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_max</th>
          <th>strain_rate</th>
          <th>heat_release_rate</th>
          <th>n_points</th>
          <th>flame_width</th>
          <th>Tc_increment</th>
          <th>time_steps</th>
          <th>eval_count</th>
          <th>cpu_time</th>
          <th>errors</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <th>0</th>
          <td>3052.622619</td>
          <td>2830.268704</td>
          <td>2.443940e+06</td>
          <td>122</td>
          <td>0.002475</td>
          <td>23.000000</td>
          <td>10</td>
          <td>123</td>
          <td>0.289236</td>
          <td>0</td>
        </tr>
        <tr>
          <th>1</th>
          <td>3052.372463</td>
          <td>2878.030550</td>
          <td>2.462708e+06</td>
          <td>123</td>
          <td>0.002363</td>
          <td>26.000000</td>
          <td>0</td>
          <td>10</td>
          <td>0.084765</td>
          <td>0</td>
        </tr>
        <tr>
          <th>2</th>
          <td>3052.504270</td>
          <td>2931.641049</td>
          <td>2.487768e+06</td>
          <td>125</td>
          <td>0.002363</td>
          <td>29.000000</td>
          <td>0</td>
          <td>12</td>
          <td>0.088660</td>
          <td>0</td>
        </tr>
        <tr>
          <th>3</th>
          <td>3051.760996</td>
          <td>3001.161239</td>
          <td>2.511980e+06</td>
          <td>128</td>
          <td>0.002363</td>
          <td>32.000000</td>
          <td>0</td>
          <td>14</td>
          <td>0.091317</td>
          <td>0</td>
        </tr>
        <tr>
          <th>4</th>
          <td>3051.465638</td>
          <td>3064.930235</td>
          <td>2.539557e+06</td>
          <td>130</td>
          <td>0.002363</td>
          <td>35.000000</td>
          <td>0</td>
          <td>12</td>
          <td>0.091724</td>
          <td>0</td>
        </tr>
        <tr>
          <th>...</th>
          <td>...</td>
          <td>...</td>
          <td>...</td>
          <td>...</td>
          <td>...</td>
          <td>...</td>
          <td>...</td>
          <td>...</td>
          <td>...</td>
          <td>...</td>
        </tr>
        <tr>
          <th>129</th>
          <td>1132.410191</td>
          <td>143030.555265</td>
          <td>3.647654e+06</td>
          <td>235</td>
          <td>0.000188</td>
          <td>11.974316</td>
          <td>0</td>
          <td>24</td>
          <td>0.249103</td>
          <td>0</td>
        </tr>
        <tr>
          <th>130</th>
          <td>1113.385369</td>
          <td>112450.067944</td>
          <td>3.045192e+06</td>
          <td>241</td>
          <td>0.000207</td>
          <td>11.974316</td>
          <td>0</td>
          <td>26</td>
          <td>0.261252</td>
          <td>0</td>
        </tr>
        <tr>
          <th>131</th>
          <td>1091.717465</td>
          <td>80884.851274</td>
          <td>2.373516e+06</td>
          <td>247</td>
          <td>0.000236</td>
          <td>9.947326</td>
          <td>0</td>
          <td>42</td>
          <td>0.374025</td>
          <td>0</td>
        </tr>
        <tr>
          <th>132</th>
          <td>1071.194618</td>
          <td>54512.663770</td>
          <td>1.763724e+06</td>
          <td>256</td>
          <td>0.000279</td>
          <td>8.724514</td>
          <td>0</td>
          <td>57</td>
          <td>0.564619</td>
          <td>0</td>
        </tr>
        <tr>
          <th>133</th>
          <td>1047.374370</td>
          <td>27370.073988</td>
          <td>1.107232e+06</td>
          <td>266</td>
          <td>0.000363</td>
          <td>6.592763</td>
          <td>0</td>
          <td>41</td>
          <td>0.488103</td>
          <td>0</td>
        </tr>
      </tbody>
    </table>
    <p>134 rows × 10 columns</p>
    </div>
    </div>
    <br />
    <br />

.. GENERATED FROM PYTHON SOURCE LINES 194-196

Plot the maximum temperature versus the maximum axial velocity gradient
-----------------------------------------------------------------------

.. GENERATED FROM PYTHON SOURCE LINES 196-202

.. code-block:: Python

    plt.figure()
    plt.plot(df.strain_rate, df.T_max)
    plt.xlabel('Maximum Axial Velocity Gradient [1/s]')
    plt.ylabel('Maximum Temperature [K]')
    plt.savefig(output_path / "figure_max_temperature_vs_max_velocity_gradient.png")




.. image-sg:: /examples/python/onedim/images/sphx_glr_diffusion_flame_continuation_001.png
   :alt: diffusion flame continuation
   :srcset: /examples/python/onedim/images/sphx_glr_diffusion_flame_continuation_001.png, /examples/python/onedim/images/sphx_glr_diffusion_flame_continuation_001_2_00x.png 2.00x
   :class: sphx-glr-single-img





.. GENERATED FROM PYTHON SOURCE LINES 203-205

Plot maximum_temperature against number of iterations
-----------------------------------------------------

.. GENERATED FROM PYTHON SOURCE LINES 205-211

.. code-block:: Python

    plt.figure()
    plt.plot(df.T_max)
    plt.xlabel('Number of Continuation Steps')
    plt.ylabel('Maximum Temperature [K]')
    plt.savefig(output_path / "figure_max_temperature_iterations.png")
    plt.show()



.. image-sg:: /examples/python/onedim/images/sphx_glr_diffusion_flame_continuation_002.png
   :alt: diffusion flame continuation
   :srcset: /examples/python/onedim/images/sphx_glr_diffusion_flame_continuation_002.png, /examples/python/onedim/images/sphx_glr_diffusion_flame_continuation_002_2_00x.png 2.00x
   :class: sphx-glr-single-img






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

   **Total running time of the script:** (0 minutes 37.401 seconds)


.. _sphx_glr_download_examples_python_onedim_diffusion_flame_continuation.py:

.. only:: html

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

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

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

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

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

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

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


.. only:: html

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

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