<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://joehannon.github.io/blog/feed.xml" rel="self" type="application/atom+xml" /><link href="https://joehannon.github.io/blog/" rel="alternate" type="text/html" /><updated>2026-07-29T17:03:19+00:00</updated><id>https://joehannon.github.io/blog/feed.xml</id><title type="html">Joe Hannon’s blog</title><subtitle>Mostly tech</subtitle><author><name>Github joehannon</name></author><entry><title type="html">12: Results for other test reaction systems</title><link href="https://joehannon.github.io/blog/2026/07/26/title_012.html" rel="alternate" type="text/html" title="12: Results for other test reaction systems" /><published>2026-07-26T00:00:00+00:00</published><updated>2026-07-26T00:00:00+00:00</updated><id>https://joehannon.github.io/blog/2026/07/26/title_012</id><content type="html" xml:base="https://joehannon.github.io/blog/2026/07/26/title_012.html"><![CDATA[<p><strong>Working paper</strong></p>

<p>I have written up both the theory and a series of applications of ray_limit to other mixing-sensitive chemistries in a ChemRxiv preprint you can download <a href="https://chemrxiv.org/doi/abs/10.26434/chemrxiv.15006522/v1">here</a>.  The DOI is <a href="https://doi.org/10.26434/chemrxiv.15006522/v1">here</a>.</p>

<p>Later in 2026, I will make the Closure code pip installable.</p>

<p><strong>What next?</strong></p>

<p>I have other stuff I need to get back to. But I may return to this topic in future and code a three-stream version of ray_limit, as well as add temperature as a function of f and activation energy to better handle strongly exothermic systems at high concentrations.</p>

<p>Lower hanging fruit will be a fed batch version, which I might compare with the simple zone model I developed years ago.</p>

<p>Get in touch if you have a particular interest or preference and let me know your application.</p>]]></content><author><name>Github joehannon</name></author><summary type="html"><![CDATA[Working paper]]></summary></entry><entry><title type="html">10: Differences between blend_fs and ray_limit</title><link href="https://joehannon.github.io/blog/2026/07/08/title_010.html" rel="alternate" type="text/html" title="10: Differences between blend_fs and ray_limit" /><published>2026-07-08T00:00:00+00:00</published><updated>2026-07-08T00:00:00+00:00</updated><id>https://joehannon.github.io/blog/2026/07/08/title_010</id><content type="html" xml:base="https://joehannon.github.io/blog/2026/07/08/title_010.html"><![CDATA[<p>I will discuss here some fundamental differences between the new ray_limit closure and the blend_fs / 2025 closure. I will illustrate those discussions with results generated by my implementations of both blend_fs and ray_limit.  Then I show that an extended version of the 2025 closure behaves better than blend_fs and agrees exactly with ray_limit.</p>

<p><strong>Clamping</strong></p>

<p>During integration of the ODEs using blend_fs, the ODE solution deviates from the limits defined in blend_fs (aligned with the 2025 paper). We can see that in the evolution of the average species profiles (red dashed line) versus time when overlaid with the current values of the limits (bold curves) <a href="/blog/docs/assets/fuller_BC_azo_coupling_kinetics_eps1_limit_averages_blend_fs.png">full-size</a>:</p>

<p><img src="/blog/docs/assets/fuller_BC_azo_coupling_kinetics_eps1_limit_averages_blend_fs.png" alt="ODE solve comparing ray_limit and blend_fs" width="1000" /></p>

<p>In the above plots, B and R clearly clamp/ stray outside limits.  This is also evident in snapshot plots of C(f) in which the ODE solution (flat line) does not always equal the beta-averaged C(f); that is because I clamp C(f) to remain at the physical limit even if the ODE solution has gone outside that limit <a href="/blog/docs/assets/fuller_BC_azo_coupling_kinetics_eps1_snapshots_blend_fs.png">full-size</a>:</p>

<p><img src="/blog/docs/assets/fuller_BC_azo_coupling_kinetics_eps1_snapshots_blend_fs.png" alt="ODE solve comparing ray_limit and blend_fs" width="1000" /></p>

<p>I generate plots showing the percentage of ODE timesteps for each species that were clamped with blend_fs <a href="/blog/docs/assets/blend_fs_clamps_vs_epsilon.png">full-size</a>:</p>

<p><img src="/blog/docs/assets/blend_fs_clamps_vs_epsilon.png" alt="ODE solve comparing ray_limit and blend_fs" width="1000" /></p>

<p>Clamping occurs to a significant extent over the whole range of epsilon and is most prevalent for species B and R (S is not relevant to calculation of rates in this example).</p>

<p>Clamping does not occur when using ray_limit in this example and in most other practical cases I have studied.  Clamping is avoided using ray_limit by definition / by construction. The infinitely fast reaction has always proceeded further along the ray than the current reaction with finite kinetics <a href="/blog/docs/assets/fuller_BC_azo_coupling_kinetics_eps1_limit_averages_ray_limit.png">full-size</a>:</p>

<p><img src="/blog/docs/assets/fuller_BC_azo_coupling_kinetics_eps1_limit_averages_ray_limit.png" alt="ODE solve comparing ray_limit and blend_fs" width="1000" /></p>

<p><strong>Position of fs</strong></p>

<p>blend_fs calculates the current (moving) fs from limits 20 (fs=0.1379) and 31 (fs=0.1935), the same limits referenced in the 2025 paper.  I generate plots of the evolution of fs versus time (black curve, secondary y-axis) in each ODE solution when using blend_fs <a href="/blog/docs/assets/fuller_BC_azo_coupling_kinetics_eps1_blend_fs_diag.png">full-size</a>:</p>

<p><img src="/blog/docs/assets/fuller_BC_azo_coupling_kinetics_eps1_blend_fs_diag.png" alt="ODE solve comparing ray_limit and blend_fs" width="1000" /></p>

<p>I also generate plots of the final fs at each epsilon during an epsilon sweep <a href="/blog/docs/assets/fs_versus_epsilon_blend_fs.png">full-size</a>:</p>

<p><img src="/blog/docs/assets/fs_versus_epsilon_blend_fs.png" alt="ODE solve comparing ray_limit and blend_fs" width="1000" /></p>

<p>It is clear that fs moves from near 0.1935 at low epsilon, reflecting slow mixing and high production of S, towards 0.1379 at intermediate and high epsilon, reflecting faster mixing and less production of both S and Q.</p>

<p>I believe that in this example, fs need to be allowed to move across a wider range than this, as with increasing epsilon, reaction 5 is no longer behaving similarly (in terms of speed) to reactions 1 and 3, with more R and T and less Q being produced.  I believe that additional limits should be considered in this closure that allow A and B to react more preferentially towards R and T than towards Q. In my terminology, those would be limit 7 (fs=0.0741) especially and possibly also limit 26 (fs=0.1379).</p>

<p>I also plot the evolution of fs (black curve, secondary y-axis) during simulation when using ray_limit <a href="/blog/docs/assets/fuller_BC_azo_coupling_kinetics_eps1_raylimit_diag.png">full-size</a>:</p>

<p><img src="/blog/docs/assets/fuller_BC_azo_coupling_kinetics_eps1_raylimit_diag.png" alt="ODE solve comparing ray_limit and blend_fs" width="1000" /></p>

<p>It is clear that ray_limit starts under these conditions at a lower fs than blend_fs can do and then varies significantly and smoothly as the reaction proceeds. The “selectivity” plotted on the main y-axis is a normalized version of the rate of each reaction and shows the shift in the relative balance of the reaction rates versus time.</p>

<p>I have compared the variation of the final fs in each simulation during an epsilon sweep using ray_limit and blend_fs and it is clear that ray_limit allows fs to adopt a smooth variation from almost 0.1935 at low epsilon down to near 0.0741 at high epsilon <a href="/blog/docs/assets/fs_versus_epsilon_blend_fs_and_ray_limit.png">full-size</a>:</p>

<p><img src="/blog/docs/assets/fs_versus_epsilon_blend_fs_and_ray_limit.png" alt="ODE solve comparing ray_limit and blend_fs" width="1000" /></p>

<p>The evolution of fs using ray_limit is automatic and smooth and the limits do not even need to be calculated up front.</p>]]></content><author><name>Github joehannon</name></author><summary type="html"><![CDATA[I will discuss here some fundamental differences between the new ray_limit closure and the blend_fs / 2025 closure. I will illustrate those discussions with results generated by my implementations of both blend_fs and ray_limit. Then I show that an extended version of the 2025 closure behaves better than blend_fs and agrees exactly with ray_limit.]]></summary></entry><entry><title type="html">11: Connecting ray limit to the underlying reactions</title><link href="https://joehannon.github.io/blog/2026/07/08/title_011.html" rel="alternate" type="text/html" title="11: Connecting ray limit to the underlying reactions" /><published>2026-07-08T00:00:00+00:00</published><updated>2026-07-08T00:00:00+00:00</updated><id>https://joehannon.github.io/blog/2026/07/08/title_011</id><content type="html" xml:base="https://joehannon.github.io/blog/2026/07/08/title_011.html"><![CDATA[<p>The change in molar amount of a species as a result of one or more underlying reactions is calculated classically from the extents of the underlying reactions occuring and the stoichiometric coefficients on the species in those underlying reactions:</p>

\[n_{i}-n_{i0}=\sum_{j}e_{j}\nu_{ij}\]

<p>In the case of the 5-reaction system we are using for illustration, the full scheme is: A+B&gt;R, A+R&gt;S, A+B&gt;T, A+T&gt;S, A+C&gt;Q and denoting the extents as $\alpha$, $\beta$, $\gamma$, $\delta$ and $\epsilon$ respectively, a one-line compsote reaction extent summary based on the extents of the underlying reactions is:</p>

\[(\alpha+\beta+\gamma+\delta+\epsilon) A + (\alpha+\gamma) B + \epsilon C &gt; (\alpha-\beta) R + (\gamma-\delta) T + (\beta+\delta) S + \epsilon Q\]

<p>from which:</p>

\[f_{s}=\frac{(\alpha+\beta+\gamma+\delta+\epsilon)\min(C_{B0}/(\alpha+\gamma), C_{C0}/\epsilon)}{C_{A0}+(\alpha+\beta+\gamma+\delta+\epsilon)\min(C_{B0}/(\alpha+\gamma), C_{C0}/\epsilon)}\]

<p>With $\alpha=1$, $\gamma=0$, $\delta=0$ and $\epsilon=1$ this becomes substantially equivalent to Equation (41) in the 2025 paper. I used the above extended closed form equation to calculate fs by hand for a range of values of $\alpha$, $\beta$, $\gamma$, $\delta$ and $\epsilon$ and tabulate below relevant results for the current illustration <a href="/blog/docs/assets/hand_calcs_fs_extend_2025.png">full-size</a>:</p>

<p><img src="/blog/docs/assets/hand_calcs_fs_extend_2025.png" alt="hand calculations of fs" width="1000" /></p>

<p>Colour coded formatting shows green for large values and red for small. It is clear that the broader fs formula above leads to a wider range of possible fs values than those shown in the 2025 paper.</p>

<p>A subtle point that is easy to miss (I missed it at first) when generating results manually with the extents of the 5-reaction system, is that when all of the reactions proceed, the true extents (Greek letters) cannot be equal for them all at fs; that is because there is now outright and equal direct competition along two pathways for each of A and B when forming R and T before they proceed to S. At the limit when all reactions are infinitely fast, the extent of the fifth reaction must be twice the extent of each of the others in order for the amount of C to reach zero. That is exactly how the ODE solution behaves.</p>

<p>You can think of this single line composite reaction summary as a report on what has happened in the underlying reactions up to the current time. Infinitely fast reaction limits can be obtained from the single summary reaction and used to bound the estimates we need of C(f) with the no-reaction limit / mixing line bounding C(f) on the other side.</p>

<p>The method to obtain the infinitely fast limit is to find the point, $f_{s}$ at which reactants from each stream cannot coexist. ray_limit does this by pushing its ray to the maximum possible extent.</p>

<p>This breadth of approach is not described in the 2025 paper or the prior 2009 paper or anywhere else that I have seen.  Nevertheless, it appears to be quite generally applicable, as any reaction system may be summarily written in this single-line form, replacing stoichiometric coefficients with overall reaction extents for each species.</p>]]></content><author><name>Github joehannon</name></author><summary type="html"><![CDATA[The change in molar amount of a species as a result of one or more underlying reactions is calculated classically from the extents of the underlying reactions occuring and the stoichiometric coefficients on the species in those underlying reactions:]]></summary></entry><entry><title type="html">9: Comparison of results with 2025 paper</title><link href="https://joehannon.github.io/blog/2026/07/06/title_009.html" rel="alternate" type="text/html" title="9: Comparison of results with 2025 paper" /><published>2026-07-06T00:00:00+00:00</published><updated>2026-07-06T00:00:00+00:00</updated><id>https://joehannon.github.io/blog/2026/07/06/title_009</id><content type="html" xml:base="https://joehannon.github.io/blog/2026/07/06/title_009.html"><![CDATA[<p><strong>Basis of overall comparison</strong></p>

<p>I have used Figure 11 b) from the 2025 paper as the main basis for comparison of overall results.  This shows final product fractions (or distibutions) versus epsilon over the range 1.0E-6 W/kg to 1.0E+6 W/kg. I have separately overlaid results for XQ and XS using blend_fs (plotted using dot symbols) and using ray_limit on the results with the full closure model in the 2025 paper (plotted using solid lines). The dashed lines shown are from the original Figure 11 b) and relate to calculations using a simplified version of Baldyga’s closure; the dashed lines may be ignored for the purposes of the present discussion.</p>

<p><strong>blend_fs results compared with 2025 paper</strong></p>

<p>Results from an epsilon sweep using blend_fs, compared to Figure 11 b) in the 2025 paper <a href="/blog/docs/assets/blend_fs_vs_Fig_11_b.png">full-size</a>:</p>

<p><img src="/blog/docs/assets/blend_fs_vs_Fig_11_b.png" alt="ODE solve comparing ray_limit and blend_fs" width="1000" /></p>

<p>The shapes of the curves from blend_fs (dotted) and the 2025 paper (solid) are very similar and generally the curves are close to each other and approach each other at both ends. (At the left end, mixing is almost fully rate-controlling and at the right end, kinetics are almost fully rate-controlling). There is a clear offset in the x-direction (epsilon), with the blend_fs peak in XQ occurring at a lower epsilon; XQ values are initially higher on the left, but XS values are lower and the net effect is that mixing appears “better” with blend_fs. The same type of offset persists at higher epsilon, with blend_fs giving XQ values that are below the 2025 paper. It is reasonable to think on this basis that in my blend_fs implementation, mixing is somehow more effective than in the 2025 paper’s implementation.  More information on possible implementation differences is given below and I am in discussion with Baldyga’s group to clarify those differences.</p>

<p><strong>ray_limit results compared with 2025 paper</strong></p>

<p>Results from an epsilon sweep using ray_limit, compared to Figure 11 b) in the 2025 paper <a href="/blog/docs/assets/ray_limit_vs_Fig_11_b.png">full-size</a>:</p>

<p><img src="/blog/docs/assets/ray_limit_vs_Fig_11_b.png" alt="ODE solve comparing ray_limit and blend_fs" width="1000" /></p>

<p>The shapes of the curves from ray_limit (dotted) and the 2025 paper (solid) are even more similar than the curves for blend_fs above and generally are closer to each other and approach each other more tightly at both ends. There is a small offset in the x-direction (epsilon) with XS values a little lower with ray_limit than in the 2025 paper. ray_limit and the 2025 paper (as well as blend_fs) use different closure methods to estimate C(f) and I expect this to be the major reason for any differences in these curves.  On the other hand, the similarity between ray_limit results and the 2025 paper is notable.</p>

<p><strong>Possible reasons for differences</strong></p>

<p>The following are potential differences in implementation between the 2025 paper and blend_fs:</p>
<ul>
  <li>Mixing parameters relating to Figure 11 b) are calculated differently; mixing (or more accurately variance) in these closure models is calculated from Intensity of Segregation.  I have checked that my code produces exactly the same Intensity of segregation profile as in Figure 4 a) of the 2025 paper when the same input values are used for the integral scale of turbulence ($\lambda$, $\nu$ and $Sc$):
– <code class="language-plaintext highlighter-rouge">
  m_lambda=0.006
  m_nu=1.0E-6
  m_Sc=4000
</code></li>
  <li>XQ and XS are calculated differently; I calculate XQ and XS after 99.9% conversion of the limiting reactant; I do not know at what point these are calculated in the 2025 paper</li>
  <li>The ODE solvers are different; my ODE simulations use the CVODE BDF stiff solver, with tight tolerances to ensure accurate integration <code class="language-plaintext highlighter-rouge">_ODE_RTOL = 1.0e-8
_ODE_ATOL = 1.0e-10</code>; I do not know what ODE solver or tolerances are used in the 2025 paper</li>
  <li>The kinetic parameters are different; I use the kinetic parameters from Section 2.0 in the 2025 paper; no reactions are assumed to be infinitely fast</li>
  <li>The ODEs solved are different; I solve all of the species ODEs in their dimensional and untransformed state; the 2025 paper solves the ODEs in dimensionless form</li>
  <li>If/when the ODE solution average drifts outside the calculated limits, this is handled differently; I set the reaction rate to use C(f) at the limit that has been breached. I call this “clamping”. I do not know if/how this is handled in the 2025 paper</li>
  <li>My calculation of the moving fs in blend_fs and the corresponding species concentrations C(f) is different to the beta-based reaction calculation in the 2025 paper (Equation 41 in the 2025 paper); I have checked the values produced by Equation 41 are identical to those in my blend_fs implementation (see plot below)</li>
  <li>Reaction rates weighted by the beta function pdf are calculated differently; I use the Scipy betainc function to integrate the piecewise linear C(f) functions in closed form, with no quadrature truncation error. I do not know how the beta function is integrated in the 2025 paper</li>
  <li>My calculations to date have been on Apple Silicon; I do not know the compute architecture used for by the 2025 paper.</li>
</ul>

<p>Plot of of $C_{R}(f)$ and $C_{S}(f)$ as calculated using blend_fs, at discrete values of beta (the ratio of S to (R+T+S)) <a href="/blog/docs/assets/blendfs_RT_S_tent_vs_ratio.png">full-size</a>:</p>

<p><img src="/blog/docs/assets/blendfs_RT_S_tent_vs_ratio.png" alt="ODE solve comparing ray_limit and blend_fs" width="1000" /></p>

<p>The values of $f_{s}$ and the concentrations correspond exactly with calculations by hand using Equation 41 from the 2025 paper; the concentrations correspond exactly with those presented in dimensionless form in Figures 6 and 7 of the 2025 paper.</p>

<p>I will report here when we know more about which of the above listed potential reasons or some other reasons explain the small differences between my implementation of blend_fs and the 2025 paper.  I would like to compare my blend_fs results against the full results of ODE simulations using the code that generated the plots in the 2025 paper, that is the profiles for all species versus time, the intensity of segregation and the reaction rates, over a range of epsilon values.</p>

<p>In subsequent posts I will discuss some fundamental differences between the new ray_limit closure and the blend_fs / 2025 closure. I will illustrate those discussions with results generated by my implementations of both blend_fs and ray_limit.</p>]]></content><author><name>Github joehannon</name></author><summary type="html"><![CDATA[Basis of overall comparison]]></summary></entry><entry><title type="html">7: A new closure method: ‘ray_limit’</title><link href="https://joehannon.github.io/blog/2026/07/04/title_007.html" rel="alternate" type="text/html" title="7: A new closure method: ‘ray_limit’" /><published>2026-07-04T00:00:00+00:00</published><updated>2026-07-04T00:00:00+00:00</updated><id>https://joehannon.github.io/blog/2026/07/04/title_007</id><content type="html" xml:base="https://joehannon.github.io/blog/2026/07/04/title_007.html"><![CDATA[<p><strong>A new closure method that appears to be generally applicable</strong></p>

<p>It became clear when considering the various methods described in post 6 (and others) that if the profile of C(f) is not well-bracketed and respecting physical limits, closures will not work well. Hoping that the solution will “find” the right bracketing limits on its own from the precomputed static subsets is not realistic and additional information needs to come from somewhere in order to navigate subset-space and f-space.  Rationally combined or blended and dynamic limits, such as those in the 2009 and 2025 papers from Baldyga’s group, looked more promising, although potentially internally inconsistent if the selected limits did not always bound the ODE average at all times. And it was not yet clear how such a combination could be determined and applied in a general or automatic way to a new and user-defined reaction system that differs significantly from the azo-coupling example we are using for illustration.</p>

<p>While thinking about the extra information that might help a closure to produce the correct solution in all cases, I often thought about how the kinetic parameters in the scheme contain useful information about which static limits might be most relevant to a solution. That is also true of the reaction conditions, especially the input concentrations of each substance, which determine to some degree what reaction pathways will be more active/relevant. None of the closures tried so far have made automated use of this information. In a bit of a light-bulb moment, I realized that it should be possible to extract from the ODE (or CFD) solution, which reflects all of these inputs, a trajectory that could be useful in defining the overall direction of the reactions in stoichiometric terms.</p>

<p>A mechanistically correct reaction system containing multiple reaction lines can be written in a single line that is stoichiometrically correct, even if the chemistry does not take place mechanistically in a single step. In fact, much traditional experimentation on reaction kinetics applies a similar idea when focused on determining the reaction order for each reactant.</p>

<p>I decided to use the ODE solution to determine the stoichiometric coefficients in that overall reaction and then push (or scale) conversion of the overall reaction to maximum extent based on the reactants currently available. The idea was that this would form a single limit that represented a dynamic combination of subset limits from the static set and would bound C(f) on one side while the mixing line would bound C(f) on the other. In principle this method would always ensure that the beta-integrated mean equalled the ODE average, with no clamping, as the maximum extent limit would by definition always be further along than the current extent.  A nice advantage was also that it was not necessary in this method to even compute all of the subsets up front, although that is advisable and informative.  Crucially, the method could be automated easily to apply to any reaction system and was not restricted in any way to specific types of chemistry. And it was not restricted in any way to a particular pair or set of $f_{s}$ values; instead, it determined its own $f_{s}$ depending on the progress of the particular reactions being simulated and all of their kinetic parameters and reaction conditions.</p>

<p>I called this method “ray_limit”, because in mathematical terms it is extracting from the ODE solution a “ray” (or vector/arrow) that points in the direction in which the overall chemistry is moving. When there are $N$ reactants and products, there are $N$ components in the ray.  I found some ways to visualize it in geometric terms and watch how it changes e.g. during reaction; it turns out that $f_{s}$ is quite sensitive to any shift in the ratios of the ray components. A detailed mathematical description of ray_limit is provided <a href="https://joehannon.github.io/blog/2026/07/04/title_008.html">here</a>.</p>

<p>Implementation in code is quite simple and generic, with a fresh infinitely fast reaction / maximum extent limit being calculated from the current ray at each integration step and used together with the mixing line and the species average from the ODE to interpolate C(f). When I saw how well this worked, it was quite exciting, as it offered for the first time the possibility to apply appropriate and well-behaved closure models for C(f) to any chemistry in an automated manner.</p>

<p><strong>Simulation results using ray_limit</strong></p>

<p>Once again, we compare key results with blend_fs (which uses subsets 20 and 31).</p>

<p>ODE solutions compared at epsilon=1 W/kg <a href="/blog/docs/assets/ray_and_blend_fuller_BC_azo_coupling_kinetics_eps1_ode.png">full-size</a>:</p>

<p><img src="/blog/docs/assets/ray_and_blend_fuller_BC_azo_coupling_kinetics_eps1_ode.png" alt="ODE solve comparing ray_limit and blend_fs" width="1000" /></p>

<p>The ODE solutions look quite similar overall with both methods under these conditions, but are not identical.  Reaction rates in general are subtly different and R2 is noticeably higher with ray_limit under these conditions.</p>

<p>Results of epsilon sweep <a href="/blog/docs/assets/ray_and_blend_fuller_BC_azo_coupling_kinetics_Xspecies_vs_epsilon.png">full-size</a>:</p>

<p><img src="/blog/docs/assets/ray_and_blend_fuller_BC_azo_coupling_kinetics_Xspecies_vs_epsilon.png" alt="ODE solve comparing ray_limit and blend_fs" width="1000" /></p>

<p>Both methods produce XQ and XS curves with similar smooth shapes that behave correctly at the extremes. ray_limit calculates higher values of XS at low epsilon and higher values of XQ at higher epsilon.</p>

<p>A significant difference between ray_limit and blend_fs is that ray_limit does not “clamp” during the ODE simulation, that is the beta-average of C(f) never strays outside the limits.  For blend_fs, e.g. at epsilon=1 W/kg, species B and R both clamp with blend_fs and the following plot illustrates and compares the behaviour with species B.</p>

<p>Comparing the ODE solution with the beta-averages of the limits <a href="/blog/docs/assets/ray_and_blend_limit_averages_B.png">full-size</a>:</p>

<p><img src="/blog/docs/assets/ray_and_blend_limit_averages_B.png" alt="ODE solve comparing ray_limit and blend_fs" width="1000" /></p>

<p>The general behaviour of both methods and their differences will be discussed in the following posts.</p>]]></content><author><name>Github joehannon</name></author><summary type="html"><![CDATA[A new closure method that appears to be generally applicable]]></summary></entry><entry><title type="html">8: Mathematical details of the ray_limit closure</title><link href="https://joehannon.github.io/blog/2026/07/04/title_008.html" rel="alternate" type="text/html" title="8: Mathematical details of the ray_limit closure" /><published>2026-07-04T00:00:00+00:00</published><updated>2026-07-04T00:00:00+00:00</updated><id>https://joehannon.github.io/blog/2026/07/04/title_008</id><content type="html" xml:base="https://joehannon.github.io/blog/2026/07/04/title_008.html"><![CDATA[<h2 id="setup">Setup</h2>

<p>Active species $i\in\mathcal{A}$, reactions $j=1,\dots,R$.  Net stoichiometry matrix</p>

\[N\in\mathbb{R}^{|\mathcal{A}|\times R},\qquad N_{ij}=\nu^{\mathrm{prod}}_{ij}-\nu^{\mathrm{reac}}_{ij}.\]

<p>Feed vectors $Y^{(1)},Y^{(2)}\in\mathbb{R}^{|\mathcal{A}|}$ (stream 1 at $f=1$, stream 2 at
$f=0$).  The <strong>no-reaction line</strong> at mixture fraction $f\in[0,1]$ is</p>

\[M_i(f)=f\,Y^{(1)}_i+(1-f)\,Y^{(2)}_i.\]

<p>Let $y$ be the current ODE state, $y^0$ its initial value, and $\langle\,\cdot\,\rangle$
the expectation under the step’s $\mathrm{Beta}(\alpha_t,\beta_t)$ pdf.</p>

<hr />

<h2 id="1-selectivity-ray">1. Selectivity ray</h2>

<p>The accumulated change $\Delta:=y-y^0$ lies in $\operatorname{range}(N)$.  The
<strong>selectivity ray</strong> is taken directly as this accumulated extent:</p>

\[d = \Delta = y - y^0.\]

<p>This preserves the true reaction direction without distortion from a nonnegativity clip —
important when products form by more than one route (parallel reactions, intermediates).
Near $y^0$ (where $\Delta\approx 0$ and the direction is ill-conditioned) the code falls
back to the mass-action projection $d = N\,r(y)$, where $r(y)$ is the vector of
instantaneous mass-action rates.</p>

<p>The selectivity ray $d=\Delta$ records the net moles
of each species consumed or produced per unit of accumulated reaction progress. Its
components $d_i$ are the net stoichiometric coefficients of a single effective lumped
reaction encoding the current mixture of all underlying reaction pathways.</p>

<p>The ray $d$ encodes the <strong>current selectivity</strong>: which species have been consumed or
produced and in what ratio.  It rotates as the ODE state evolves, tracking the shift
between competing pathways.</p>

<hr />

<h2 id="2-the-complete-reaction-limit-bf">2. The complete-reaction limit $B(f)$</h2>

<p>$B_i(f)$ is built by reacting the mixing line $M(f)$ to completion along the ray $d$
— advancing as far as possible without any species going negative.</p>

<p><strong>Feeds are pure.</strong> Every reactant is fed by exactly one stream — $Y^{(1)}_i=0$ or
$Y^{(2)}_i=0$.  This is a required assumption, checked at setup, and cannot be
violated.</p>

<p><strong>Maximum extent.</strong> Split the consumed species ($d_i&lt;0$) by which stream feeds them:</p>

\[\kappa_1 = \min_{\substack{i:\,d_i&lt;0\\ Y^{(2)}_i=0}} \frac{Y^{(1)}_i}{-d_i},
\qquad
\kappa_2 = \min_{\substack{i:\,d_i&lt;0\\ Y^{(1)}_i=0}} \frac{Y^{(2)}_i}{-d_i}\]

<p>($\kappa_1$: the extent achievable from the stream-1 side alone; $\kappa_2$: from
stream 2; a side with no consumed species imposes no constraint there).  The
complete-reaction kink and its peak extent are</p>

\[f_s^{\star} = \frac{\kappa_2}{\kappa_1+\kappa_2},
\qquad c^{\star} = f_s^{\star}\kappa_1 = (1-f_s^{\star})\kappa_2,\]

<p>giving the <strong>two-segment tent</strong>:</p>

\[B_i(f) = \begin{cases}
M_i(f) + \dfrac{f}{f_s^{\star}}\,c^{\star}\,d_i &amp; f \le f_s^{\star}\\[8pt]
M_i(f) + \dfrac{1-f}{1-f_s^{\star}}\,c^{\star}\,d_i &amp; f &gt; f_s^{\star}
\end{cases}\]

<p>with control-point peak value $v^{\mathrm{fs}}_i = M_i(f_s^{\star})+c^{\star}d_i$ and
pure-feed endpoints $B_i(0)=Y^{(2)}_i$, $B_i(1)=Y^{(1)}_i$.</p>

<p><strong>Intermediates are safe.</strong> A species absent from both feeds has $y^0_i=0$, and since
concentrations are non-negative, $d_i = y_i - y^0_i = y_i \ge 0$.  So an intermediate
can never have $d_i&lt;0$; it is never a candidate for $\kappa_1$ or $\kappa_2$.</p>

<hr />

<h2 id="3-closure-profile-c_if-and-scalar-lambda">3. Closure profile $C_i(f)$ and scalar $\lambda$</h2>

<p>The closure profile interpolates between the complete-reaction limit $B_i$ and the
no-reaction line $M_i$:</p>

\[\boxed{C_i(f) = B_i(f) + \bigl[M_i(f)-B_i(f)\bigr]\,\lambda_i}\]

<p>$\lambda_i=0$ gives the pure complete-reaction limit; $\lambda_i=1$ gives pure
no-reaction mixing.  $\lambda_i$ is set so that $\langle C_i\rangle = y_i$:</p>

\[\lambda_i = \operatorname{clip}_{[0,1]}\frac{y_i - \langle B_i\rangle}{\langle M_i\rangle - \langle B_i\rangle}.\]

<p>Segment integrals use the regularised incomplete Beta function $I_x(\alpha,\beta)$:</p>

\[\int_{f_a}^{f_b}(sf+b)\,p_\beta\,df
= s\,\frac{\alpha_t}{\alpha_t+\beta_t}
  \bigl[I_{f_b}(\alpha_t{+}1,\beta_t)-I_{f_a}(\alpha_t{+}1,\beta_t)\bigr]
+ b\,\bigl[I_{f_b}(\alpha_t,\beta_t)-I_{f_a}(\alpha_t,\beta_t)\bigr].\]

<p><strong>Global $\lambda$ for pure cross-stream.</strong> Since $M_i$ is linear,
$\langle M_i\rangle = M_i(\bar{f}) = y^0<em>i$, and $B_i - M_i = c</em>{\max}\,d_i$
by construction, so</p>

\[\lambda_i = \frac{y_i - \langle B_i\rangle}{y^0_i - \langle B_i\rangle}
= \frac{d_i(1-\langle c_{\max}\rangle)}{-\langle c_{\max}\rangle\,d_i}
= 1 - \frac{1}{\langle c_{\max}\rangle}\]

<p>— <strong>the same scalar for every species</strong> — and $\langle C_i\rangle = y_i$ exactly
(no clamp needed).  Per-species $\lambda_i$ diverge from this only when the clamp
$[0,1]$ binds.</p>

<hr />

<h2 id="4-worked-illustration-input_fuller_bc_azo_coupling_kinetics-varepsilon1-wkg">4. Worked illustration: <code class="language-plaintext highlighter-rouge">input_fuller_BC_azo_coupling_kinetics</code>, $\varepsilon=1$ W/kg</h2>

<p><strong>Scheme:</strong> R1: $A{+}B\to R$, R2: $A{+}R\to S$, R3: $A{+}B\to T$, R4: $A{+}T\to S$, R5: $A{+}C\to Q$.<br />
<strong>Feeds:</strong> $Y^{(1)}=15\,A$, $Y^{(2)}=1.2\,B+1.2\,C$; $\bar{f}=0.0625$.</p>

<p><strong>The effective single reaction.</strong> The selectivity ray $d=\Delta$ records the net moles
of each species consumed or produced per unit of accumulated reaction progress. Its
components $d_i$ are the net stoichiometric coefficients of a single effective lumped
reaction encoding the current mixture of all five pathways:</p>

\[|d_A|\,A + |d_B|\,B + |d_C|\,C \;\longrightarrow\; d_R\,R + d_T\,T + d_S\,S + d_Q\,Q\]

<p>The coefficients rotate as the ODE state evolves. The table gives them at each
trajectory’s output-grid time-midpoint ($t=t_{\mathrm{end}}/2$) for three dissipation
rates $\varepsilon$ — the actual $A$ conversion reached at that instant is $77\%$,
$70\%$, and $92\%$ respectively, not a fixed $50\%$; the extent of reaction still
differs substantially at the same <em>relative</em> point because the trajectories are far
from self-similar across $\varepsilon$:</p>

<table>
  <thead>
    <tr>
      <th> </th>
      <th>$\varepsilon=10^{-6}$</th>
      <th>$\varepsilon=1$</th>
      <th>$\varepsilon=10^{6}$</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>$(\alpha_t,\beta_t)$</td>
      <td>$(0.311,\,4.66)$</td>
      <td>$(0.372,\,5.58)$</td>
      <td>$(14.5,\,218)$</td>
    </tr>
    <tr>
      <td>kink $f_s^{\star}$</td>
      <td>$0.1927$</td>
      <td>$0.1366$</td>
      <td>$0.0781$</td>
    </tr>
    <tr>
      <td>$\lambda$ (global)</td>
      <td>$0.000^{\dagger}$</td>
      <td>$0.0008$</td>
      <td>$0.0541$</td>
    </tr>
    <tr>
      <td>$d_A$</td>
      <td>$-0.7225$</td>
      <td>$-0.6556$</td>
      <td>$-0.8616$</td>
    </tr>
    <tr>
      <td>$d_B$</td>
      <td>$-0.2422$</td>
      <td>$-0.3314$</td>
      <td>$-0.8135$</td>
    </tr>
    <tr>
      <td>$d_C$</td>
      <td>$-0.2421$</td>
      <td>$-0.2868$</td>
      <td>$-0.0470$</td>
    </tr>
    <tr>
      <td>$d_R$</td>
      <td>$+0.0040$</td>
      <td>$+0.2832$</td>
      <td>$+0.7560$</td>
    </tr>
    <tr>
      <td>$d_T$</td>
      <td>$+0.0000$</td>
      <td>$+0.0108$</td>
      <td>$+0.0564$</td>
    </tr>
    <tr>
      <td>$d_S$</td>
      <td>$+0.2382$</td>
      <td>$+0.0374$</td>
      <td>$+0.0011$</td>
    </tr>
    <tr>
      <td>$d_Q$</td>
      <td>$+0.2421$</td>
      <td>$+0.2868$</td>
      <td>$+0.0470$</td>
    </tr>
  </tbody>
</table>

<p>$^{\dagger}$At $\varepsilon=10^{-6}$ the mean has reached the complete-reaction average, so $\lambda$ clamps to $0$ and $\langle C_i\rangle=\langle B_i\rangle$.</p>

<p><strong>Building the limit at $\varepsilon=1$.</strong> The effective reaction at this step is</p>

\[0.6556\,A + 0.3314\,B + 0.2868\,C \;\longrightarrow\; 0.2832\,R + 0.0108\,T + 0.0374\,S + 0.2868\,Q.\]

<p>The complete-reaction limit $B(f)$ is found by advancing $M(f)$ along this reaction as
far as possible without a reactant going negative:</p>

\[c_{\max}(f) = \min\!\left(\frac{15f}{0.6556},\;\frac{1.2(1-f)}{0.3314},\;\frac{1.2(1-f)}{0.2868}\right).\]

<p>$A$ is the only stream-1 reactant, so $\kappa_1=15/0.6556=22.88$.  Of the stream-2
reactants, $B$ is tighter than $C$ (smaller value), so
$\kappa_2=\min(1.2/0.3314,\,1.2/0.2868)=\min(3.621,\,4.183)=3.621$, from $B$.</p>

<p>The kink is $f_s^{\star}=\kappa_2/(\kappa_1+\kappa_2)=3.621/26.50=0.1366$, with peak
extent $c^{\star}=f_s^{\star}\kappa_1=3.127$.  Peak values $v^{\mathrm{fs}}_i=M_i(f_s^{\star})+c^{\star}d_i$
(e.g.\ $v^{\mathrm{fs}}_R = 3.127\times0.2832=0.886$) and resulting averages and closure scalars:</p>

<table>
  <thead>
    <tr>
      <th>$\varepsilon=1$</th>
      <th>$\langle M_i\rangle=y^0_i$</th>
      <th>$v^{\mathrm{fs}}_i$ (mol/m³)</th>
      <th>$\langle B_i\rangle$ (mol/m³)</th>
      <th>$y_i$ (mol/m³)</th>
      <th>$\lambda_i$</th>
      <th>$\langle C_i\rangle$</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>$A$</td>
      <td>0.9375</td>
      <td>0.000</td>
      <td>0.2814</td>
      <td>0.2819</td>
      <td>0.0008</td>
      <td>0.2819</td>
    </tr>
    <tr>
      <td>$B$</td>
      <td>1.1250</td>
      <td>0.000</td>
      <td>0.7934</td>
      <td>0.7936</td>
      <td>0.0008</td>
      <td>0.7936</td>
    </tr>
    <tr>
      <td>$C$</td>
      <td>1.1250</td>
      <td>0.139</td>
      <td>0.8379</td>
      <td>0.8382</td>
      <td>0.0008</td>
      <td>0.8382</td>
    </tr>
    <tr>
      <td>$R$</td>
      <td>0.0000</td>
      <td>0.886</td>
      <td>0.2835</td>
      <td>0.2832</td>
      <td>0.0008</td>
      <td>0.2832</td>
    </tr>
    <tr>
      <td>$T$</td>
      <td>0.0000</td>
      <td>0.034</td>
      <td>0.0108</td>
      <td>0.0108</td>
      <td>0.0008</td>
      <td>0.0108</td>
    </tr>
    <tr>
      <td>$S$</td>
      <td>0.0000</td>
      <td>0.117</td>
      <td>0.0374</td>
      <td>0.0374</td>
      <td>0.0008</td>
      <td>0.0374</td>
    </tr>
    <tr>
      <td>$Q$</td>
      <td>0.0000</td>
      <td>0.897</td>
      <td>0.2871</td>
      <td>0.2868</td>
      <td>0.0008</td>
      <td>0.2868</td>
    </tr>
  </tbody>
</table>

<p>Every $\lambda_i$ equals the global scalar and $\langle C_i\rangle=y_i$ to the quoted digits.</p>]]></content><author><name>Github joehannon</name></author><summary type="html"><![CDATA[Setup]]></summary></entry><entry><title type="html">5: Mathematical details of my implementation of the Baldyga closure, as ‘blend_fs’</title><link href="https://joehannon.github.io/blog/2026/07/01/01-title_005.html" rel="alternate" type="text/html" title="5: Mathematical details of my implementation of the Baldyga closure, as ‘blend_fs’" /><published>2026-07-01T00:00:00+00:00</published><updated>2026-07-01T00:00:00+00:00</updated><id>https://joehannon.github.io/blog/2026/07/01/01-title_005</id><content type="html" xml:base="https://joehannon.github.io/blog/2026/07/01/01-title_005.html"><![CDATA[<h2 id="1-overview">1. Overview</h2>

<p><code class="language-plaintext highlighter-rouge">blend_fs</code> constructs a piecewise-linear concentration profile $C_i(f)$ over mixture
fraction $f \in [0,1]$ that tracks the <strong>reaction limit</strong> as the ODE composition evolves.
Rather than committing to a single stoichiometric subset, it <strong>blends</strong> $K \geq 1$
user-specified subset limits, weighting them by the concentrations of their
distinguishing products in the current ODE state.  The blend is converted to a single
kink position $f_{sb}$ and a peak height $v^{fs}_i$ for each species, and a scalar
$\lambda_i \in [0,1]$ interpolates between the no-reaction line and the limit profile so
that the Beta-weighted mean matches the ODE value.</p>

<p>The $K$ subsets are prescribed by the user via the <code class="language-plaintext highlighter-rouge">blend_subsets</code> key in the JSON
configuration file (e.g. <code class="language-plaintext highlighter-rouge">"blend_subsets": [20, 31]</code>).  Each entry is an integer index
into the enumeration of all reaction subsets; any number of subsets $K \geq 1$ may be
listed.</p>

<p>In the 2009 and 2025 papers, Baldyga’s group develop the closure in terms of a parameter “beta” that represents the fractional ratio of S to (R+T+S) in solution and also a stoichiometric coefficient in a reaction (1+beta) A + B &gt; (1-beta) R plus beta S. I have checked that the mathematics described below for calculating fs for a blend of limits give exactly the same results for C(f) as the approach described in the papers. The mathematical approach described below is kept general and uses different terminology to the papers.</p>

<hr />

<h2 id="2-background-mixture-fraction-and-beta-distribution">2. Background: mixture fraction and Beta distribution</h2>

<p>Mixture fraction $f$ is the mass (or molar) fraction originating from stream 1.  The
feed concentrations are</p>

<p>\(Y_1: \quad A = 15 \text{ mol/m^{3},\quad \text{others} = 0\)
\(Y_2: \quad B = 1.2,\quad C = 1.2 \text{ mol/m^{3}},\quad \text{others} = 0\)</p>

<p>with mean $\bar{f} = 0.0625$.  At time $t$ the mixture-fraction PDF is
$\text{Beta}(\alpha,\beta)$ with</p>

\[s = \frac{\bar{f}(1-\bar{f})}{\sigma^2(t)} - 1, \qquad \alpha = \bar{f}\,s, \qquad \beta = (1-\bar{f})\,s\]

<p>At the example point: $\sigma^2 = 0.01622$, $s = 2.613$, $\alpha = 0.1633$,
$\beta = 2.4498$, $\tau_s = 0.02476$ s.</p>

<hr />

<h2 id="3-subset-stoichiometric-limits">3. Subset stoichiometric limits</h2>

<p>For a reaction subset $k \in {1, \ldots, K}$, the <strong>stoichiometric mixture fraction</strong>
$f_{sk}$ is the value of $f$ at which the stream-2 reactant is exactly consumed if only
subset $k$’s reactions run.  At $f = f_{sk}$ each species $i$ attains a maximum
concentration $c^{fs}_{ki}$.</p>

<p>The <strong>no-reaction line</strong> (conservative mixing, no reaction) is</p>

\[M_i(f) = Y_{2i} + (Y_{1i} - Y_{2i})\,f\]

<p>The user prescribes the $K$ subset indices via <code class="language-plaintext highlighter-rouge">blend_subsets</code>; for this example $K = 2$
(subsets 20 and 31):</p>

<table>
  <thead>
    <tr>
      <th>Subset</th>
      <th>Reactions</th>
      <th>$f_{sk}$</th>
      <th>Distinguishing products</th>
      <th>$c^{fs}_{k}$ (non-zero, mol/m3)</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>20</td>
      <td>R1, R3, R5</td>
      <td>0.1379</td>
      <td>R, T</td>
      <td>R = T = 0.5172, Q = 1.0345</td>
    </tr>
    <tr>
      <td>31</td>
      <td>R1–R5</td>
      <td>0.1935</td>
      <td>S</td>
      <td>S = Q = 0.9677</td>
    </tr>
  </tbody>
</table>

<p>Distinguishing products for subset $k$ are those whose limit concentration is non-zero
in subset $k$’s limit but zero in all other subsets’ limits.</p>

<hr />

<h2 id="4-dynamic-blend-weights">4. Dynamic blend weights</h2>

<p>Each subset $k$ has a set of <strong>distinguishing products</strong> $\mathcal{D}_k$.  The weight for
$k = 1, \ldots, K$ is</p>

\[w_k = \frac{\sum_{i \in \mathcal{D}_k} y_i}{\sum_{k'=1}^{K} \sum_{i \in \mathcal{D}_{k'}} y_i}\]

<p>where $y_i$ is the ODE concentration at the current time step.  The weights sum to unity
and shift dynamically as the composition evolves: subsets whose distinguishing products
are more abundant receive higher weight.</p>

<p>For this example ($K = 2$) at the example point:</p>

\[w_{20} = \frac{y_R + y_T}{(y_R + y_T) + y_S} = \frac{0.2290 + 0.0083}{0.2290 + 0.0083 + 0.0187} = 0.9271\]

\[w_{31} = \frac{y_S}{(y_R + y_T) + y_S} = \frac{0.0187}{0.2560} = 0.0729\]

<hr />

<h2 id="5-blended-kink-position-f_sb">5. Blended kink position $f_{sb}$</h2>

<p>The kink is placed using <strong>odds ratios</strong> to remain in the stoichiometric frame:</p>

\[s_k = \frac{f_{sk}}{1 - f_{sk}}, \qquad
s_\text{blend} = \sum_{k=1}^{K} w_k\,s_k, \qquad
f_{sb} = \frac{s_\text{blend}}{1 + s_\text{blend}}\]

<p>The odds ratio $s_k = f_{sk}/(1-f_{sk})$ is proportional to the A:B molar consumption
ratio in subset $k$.  Averaging in odds-ratio space therefore averages the blended
stoichiometry, which is the physically correct quantity to interpolate.</p>

<p>For this example ($K = 2$) at the example point:</p>

\[s_{20} = \frac{0.1379}{0.8621} = 0.1600, \qquad s_{31} = \frac{0.1935}{0.8065} = 0.2400\]

\[s_\text{blend} = 0.9271 \times 0.1600 + 0.0729 \times 0.2400 = 0.16583\]

\[f_{sb} = \frac{0.16583}{1.16583} = 0.1422\]

<hr />

<h2 id="6-control-point-limit-profile-b_if">6. Control-point limit profile $B_i(f)$</h2>

<p>The limit profile is a <strong>tent function</strong> — piecewise linear with a single kink at $f_{sb}$:</p>

\[B_i(f) = \begin{cases}
v^0_i + (v^{fs}_i - v^0_i)\,\dfrac{f}{f_{sb}} &amp; f \le f_{sb}\\[6pt]
v^{fs}_i + (v^1_i - v^{fs}_i)\,\dfrac{f - f_{sb}}{1 - f_{sb}} &amp; f &gt; f_{sb}
\end{cases}\]

<p>The control-point values are formed from the weighted subset limits.  For the <strong>peak</strong>,
the blend is evaluated at the stoichiometric yield at $f_{sb}$:</p>

\[v^{fs}_i = (1 - f_{sb}) \sum_{k=1}^{K} w_k \frac{c^{fs}_{ki}}{1 - f_{sk}}\]

<p>In each term of the sum, $c_{ki}^{fs}/(1 - f_{sk})$ is the stoichiometric yield of
species <em>i</em> per mol of stream-2 reactant consumed in subset <em>k</em> (independent of feed
concentrations); the prefactor $(1 - f_{sb})$ is the stream-2 reactant available at the
blended kink.  The formula holds for any $K \geq 1$ because the subsets are
stoichiometrically independent.  The endpoint values at $f = 0$ and $f = 1$ are
weighted averages of the subset limits; for unfed product species both are zero.</p>

<p><strong>Example ($K = 2$) — species R and S:</strong></p>

\[v^{fs}_R = 0.8578 \times \left[0.9271 \times \frac{0.5172}{0.8621} + 0\right] = 0.4771 \text{ mol/m3}\]

\[v^{fs}_S = 0.8578 \times \left[0 + 0.0729 \times \frac{0.9677}{0.8065}\right] = 0.0751 \text{ mol/m3}\]

<hr />

<h2 id="7-species-profile-c_if-and-lambda_i">7. Species profile $C_i(f)$ and $\lambda_i$</h2>

<p>The full species profile starts from the blended limit and adds a correction toward the
no-reaction line:</p>

\[C_i(f) = B_i(f) + \bigl[M_i(f) - B_i(f)\bigr]\,\lambda_i\]

<p>$\lambda_i$ is chosen so that the Beta-weighted mean matches the ODE value $y_i$:</p>

\[\lambda_i = \frac{y_i - \mathbb{E}[B_i]}{\mathbb{E}[M_i] - \mathbb{E}[B_i]}, \qquad \lambda_i \in [0, 1]\]

<p>$\lambda_i = 0$ gives the pure limit profile $B_i$; $\lambda_i = 1$ gives the pure no-reaction line $M_i$.</p>

<p>For unfed product species $M_i(f) = 0$, so $\mathbb{E}[M_i] = 0$ and:</p>

\[\lambda_i = 1 - \frac{y_i}{\mathbb{E}[B_i]}\]

<table>
  <thead>
    <tr>
      <th>Species</th>
      <th>$y_i$ (mol/m3)</th>
      <th>$\mathbb{E}[B_i]$ (mol/m3)</th>
      <th>$\lambda_i$ (raw)</th>
      <th>$\lambda_i$ (clamped)</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>R</td>
      <td>0.2290</td>
      <td>0.1070</td>
      <td>−1.14</td>
      <td><strong>0.0</strong></td>
    </tr>
    <tr>
      <td>S</td>
      <td>0.0187</td>
      <td>0.01682</td>
      <td>−0.11</td>
      <td><strong>0.0</strong></td>
    </tr>
  </tbody>
</table>

<p>Both species are clamped to $\lambda = 0$: the ODE concentrations exceed the Beta-weighted
limit profile mean, so the closure uses the pure limit profile $C_i(f) = B_i(f)$.</p>

<hr />

<h2 id="8-rate-integrals">8. Rate integrals</h2>

<p>The turbulent reaction rate for reaction $j$ is</p>

\[r_j = \int_0^1 k_j \prod_i C_i(f)^{n_{ij}}\; \text{Beta}(f;\alpha,\beta)\, df\]

<p>Because each $C_i(f)$ is piecewise linear in $f$, the integrand on each segment
$[0, f_{sb}]$ and $[f_{sb}, 1]$ is a polynomial in $f$.  The integral over each segment
is evaluated analytically using the <strong>regularised incomplete Beta function</strong>
$I_x(a,b) = B(x;a,b)/B(a,b)$.</p>

<p>Rate constants (SI units): $k_1 = 12238$, $k_2 = 1.835$, $k_3 = 921$,
$k_4 = 22.25$, $k_5 = 124.5$.</p>]]></content><author><name>Github joehannon</name></author><summary type="html"><![CDATA[1. Overview]]></summary></entry><entry><title type="html">6: Other methods for bracketing C(f)</title><link href="https://joehannon.github.io/blog/2026/07/01/02-title_006.html" rel="alternate" type="text/html" title="6: Other methods for bracketing C(f)" /><published>2026-07-01T00:00:00+00:00</published><updated>2026-07-01T00:00:00+00:00</updated><id>https://joehannon.github.io/blog/2026/07/01/02-title_006</id><content type="html" xml:base="https://joehannon.github.io/blog/2026/07/01/02-title_006.html"><![CDATA[<p><strong>Motivation</strong></p>

<p>For the 5-reaction azo-coupling there are 32 limits or 31 excluding the no-reaction limit.  Blend_fs as described in the 2025 paper and previous posts uses only two of these 31 limits, in combination with each other. For general-purpose application of a closure, it is desirable to consider all of the limits as some may become relevant depending on kinetics, reactant amounts and mixing. This motivates the search for a general approach that in principle can adhere to any of the limits or a combination, depending on the current conditions; and that can be applied to any reaction scheme or system.</p>

<p><strong>Linear interpolation between nearest limits</strong></p>

<p>Idea: Baldyga’s closures use the average solution concentration to interpolate between pairs of limits. If the interpolation limits were closer together, it might be possible to obtain tighter bracketing and thereby a more accurate estimate of C(f).  This might be achieved by considering all possible subsets and their limits and interpolating using the average concentration between the limits that most closely bracket the average.</p>

<p>Outcome: This method produces species and reaction rate profiles that are continuous but may contain abrupt changes as the bracketing limits change during solution of the equations.  It never clamps.  It produces the correct XQ and XS at either end of an epsilon sweep, but the XQ curve contains unphysical bumps at intermediate epsilon.  It remains in the code as an illustration and a point of reference.</p>

<p>ODE solution at 1 W/kg compared with blend_fs <a href="/blog/docs/assets/linear_interp_and_blend_fs_fuller_BC_azo_coupling_kinetics_eps1_ode.png">full-size</a>:</p>

<p><img src="/blog/docs/assets/linear_interp_and_blend_fs_fuller_BC_azo_coupling_kinetics_eps1_ode.png" alt="ODE solve comparing linear_interp and blend_fs" width="1000" /></p>

<p>The linear-interp ODE solutions look similar to blend_fs but not identical by any means.  The (busy) reaction rate plots show vertical lines ehere linear_interp has changed one of its pair of interpolating limits and some of these coincide with changes in the rate that are not physical.</p>

<p>Results of an epsilon sweep, compared with blend_fs <a href="/blog/docs/assets/linear_interp_and_blend_fs_fuller_BC_azo_coupling_kinetics_Xspecies_vs_epsilon.png">full-size</a>:</p>

<p><img src="/blog/docs/assets/linear_interp_and_blend_fs_fuller_BC_azo_coupling_kinetics_Xspecies_vs_epsilon.png" alt="Epsilon sweep, comparing linear_interp and blend_fs" width="1000" /></p>

<p>Linear_interp looks like it behaves correctly at each extreme of low and high epsilon, where it may be easier for it to find the right limit pairs.  In the intermediate region, it is flatter and bumpier than the corresponding curves for blend_fs. It cannot capture the highest XQ values correctly and the bumps are not physical.</p>

<p><strong>Linear interpolation between maximum and minimum limits</strong></p>

<p>Idea: In order to give a closure maximum scope to “find” the right C(f) as it solves, one could use only the maximum and minimum limits for each species as bracketing values, ensuring where possible that the beta-averaged C(f) equals the average value from the ODE. By definition this method will not respect more than one correct fs limit at any time.</p>

<p>Outcome: Gave too much freedom to C(f) and left C(f) for some reactants well above zero at mixture fractions when it should be near zero.  When two poorly bracketed profiles are cross-multiplied to get reaction rate, deviations from physical limits become pronounced.  This method was not pursued.</p>

<p><strong>Linear combination of all limits</strong></p>

<p>Idea: In order to ensure that the closure keeps in mind all possible values of all limits at any time and might thereby “find” the correct limit, one could estimate C(f) as a linear combination of all of the limits based on the proximity of the species average value to the beta-average of each of those limits. A small iterative optimization (like sequential least squares, SLSQP) would be required to find the weights on each limit for each species.</p>

<p>Outcome: Produced C(f) line segments with multiple fs kinks and left C(f) for some reactants well above zero at mixture fractions when it should be near zero.  When two poorly bracketed profiles are cross-multiplied to get reaction rate, deviations from physical limits become pronounced.  This method was not pursued.</p>

<p><strong>Variations of blended limits</strong></p>

<p>Idea: One could generalize the Baldyga approach of blending a combination of subsets/limits. Possibilities include identifying subsets in any reaction system that are missing products and combining their limits based on the products they produce, to calculate a more general moving limit. When this specific (one short) idea was applied to the 5-reaction system, it used three limits (20, 27, 29) and did not select one of the limits used in the 2025 paper (20,31); that is because limit 31 is missing two products, R and T, rather than one product.</p>

<p>Outcome: An area worthy of some further investigation, especially if it is possible to find a general and automatable basis for selecting subsets to blend.</p>

<p>Results of an ODE solve, using blend_fs to automatically blend three limits (20, 27, 29) that are each one short of a product, compared to base case Blend_fs using subsets 20, 31 <a href="/blog/docs/assets/compare_blends_fuller_BC_azo_coupling_kinetics_eps1_ode.png">full-size</a>:</p>

<p><img src="/blog/docs/assets/compare_blends_fuller_BC_azo_coupling_kinetics_eps1_ode.png" alt="ODE solve using blend_fs and three one-short limits" width="1000" /></p>

<p>Under these conditions, the reaction rate profiles are smooth and well-behaved.  Diagnostics generated during the solve continue to show significant clamping of the solution with subsets 20, 27, 29 (denoted blend_auto).</p>

<p>Results of the corresponding epsilon sweep <a href="/blog/docs/assets/compare_blends_fuller_BC_azo_coupling_kinetics_Xspecies_vs_epsilon.png">full-size</a>:</p>

<p><img src="/blog/docs/assets/compare_blends_fuller_BC_azo_coupling_kinetics_Xspecies_vs_epsilon.png" alt="Epsilon sweep using one-short subsets" width="1000" /></p>

<p>The fractional product curves such as XQ and XS with 20, 27, 29 (blend_auto) are generally quite smooth and behave well at either end.</p>

<p>With this particular blend_auto selection of subsets (20, 27, 29), XQ rises slower and then a little more sharply than it did using blend_fs (subsets 20 and 31).  It is possible that with another basis for automated limit selection, results would be nearer to or even better than the results using subsets 20 and 31.</p>]]></content><author><name>Github joehannon</name></author><summary type="html"><![CDATA[Motivation]]></summary></entry><entry><title type="html">4: Baldyga’s limits and my test set-up</title><link href="https://joehannon.github.io/blog/2026/06/30/02-title_004.html" rel="alternate" type="text/html" title="4: Baldyga’s limits and my test set-up" /><published>2026-06-30T00:00:00+00:00</published><updated>2026-06-30T00:00:00+00:00</updated><id>https://joehannon.github.io/blog/2026/06/30/02-title_004</id><content type="html" xml:base="https://joehannon.github.io/blog/2026/06/30/02-title_004.html"><![CDATA[<p><strong>Tests</strong></p>

<p>The way to test closures is to solve the equations that use the closures and compare the results with reality (experimental data), other closures and references. As part of my open source species_limits.py project, I included the CVODE integrator from the excellent <sup id="fnref:7" role="doc-noteref"><a href="#fn:7" class="footnote" rel="footnote">1</a></sup> <a href="https://scikit-sundae.readthedocs.io/en/stable/">scikit-sundae</a> library to solve the ODEs for each species. I used the same mixing parameters and calculations as the 2025 paper from Baldyga’s group, including the analytical solution for intensity of segregation, allowing the variance of f to be calculated easily at each time (or position).  I also included the ability to perform an epsilon “sweep” from very slow mixing (1.0E-6 W/kg) to very fast mixing (1.0E+6 W/kg) with the same fixed reaction kinetics. That allows exploration of any closure over the full range of mixing timescales from almost completely segregated reaction conditions through to almost completely well-mixed reaction conditions.  We are looking for closures that behave correctly at both ends of this spectrum and especially in the middle where both mixing and kinetics are influential and interact strongly. Each integration proceeds to 99.9% consumption of the limiting reactant; that is A in this case, fed through stream 1 where f=1.  B and C are fed in stream 2 where f=0.  The volume ratio of the streams is 15, so the mean of f is 1/(15+1)=0.0625.  Feed concentrations (all $mol/m^{3}$ are 15, 1.2 and 1.2 for species A, B and C respectively. Both B and C are in overall excess compared to A.</p>

<p><strong>Baldyga’s limits</strong></p>

<p>Baldyga (1994) uses a closure interpolating between a pair of infinitely fast reaction subsets for both competitive-consecutive and competitive-parallel reactions.  In the competitive-consecutive case, he discusses how $f_{s}$ moves as the product of the first reaction is produced and the second reaction is enabled to proceed. In the 2009 and 2025 papers from his group on the full 5-reaction system, the movement of $f_{s}$ is directly calculated from the amount of R (or S) already produced during reaction and interpolation is between the no-reaction limit and a combination of two infinitely fast reaction limits: subset numbers 20 and 31. Subset 20 contains reactions 1, 3 and 5 running infinitely fast; subset 31 contains all 5 reactions running infinitely fast.</p>

<p>Combination is a powerful idea as a means of identifying a unique infinitely fast reaction limit. If the right limit is calculated, C(f) for all species can then be obtained by interpolation between that (in this case, moving) limit and the no-reaction / mixing line.</p>

<p>I implemented this method as one closure option called “blend_fs” in species_limits.py. Full mathematical details of my implementation are available <a href="https://joehannon.github.io/blog/2026/07/01/01-title_005.html">here</a>. I tried to keep my implementation general and for example, did not rely on any reaction in any system being infinitely fast. I included an option for the user to stipulate/ hardwire precisely which reaction subsets to combine in the manner of the 2009 and 2025 papers; that enabled me to reproduce the method in the papers, I believe.  (I am in contact with Magdalena Jasińska in Baldyga’s group and am checking and comparing computational details).</p>

<p>I stress that the following results are from the blend_fs closure in my implementation.  This is intended to be identical to the closure described in the 2009 and 2025 papers from Baldyga’s group.  Results using blend_fs will be compared later with Figure 11 b) in the 2025 publication. The results are very similar but not yet identical.</p>

<p><strong>Simulation results using blend_fs</strong></p>

<p>Simulations produce a wide array of plots that became important at various times during this work.  Only a few plots are needed to extract the main results; other plots are helpful to perform reality checks and notice or diagnose problems with the model or the integration of the ODEs.  Non-smooth behaviour in plots usually indicates a problem.  Not all of the plots will be described here, you can generate them all if you run the code, but the main ones are as follows.  These were obtained using epsilon=1 W/kg.</p>

<p>Profiles for species amounts versus time <a href="/blog/docs/assets/fuller_BC_azo_coupling_kinetics_eps1_ode.png">full-size</a>:</p>

<p><img src="/blog/docs/assets/fuller_BC_azo_coupling_kinetics_eps1_ode.png" alt="Species profiles from ODE solver" width="1000" /></p>

<p>When running blend_fs (or any one closure) alone, this set of plots includes coloured subplots for major and minor species, a subplot of Intensity of segregation and finally subplots showing each of the reaction rates, all versus time.</p>

<p>Snapshots of C(f) for each species at several times along the reaction trajectory <a href="/blog/docs/assets/fuller_BC_azo_coupling_kinetics_eps1_snapshots_blend_fs.png">full-size</a>:</p>

<p><img src="/blog/docs/assets/fuller_BC_azo_coupling_kinetics_eps1_snapshots_blend_fs.png" alt="Snapshots of C(f) versus f" width="1000" /></p>

<p>When running blend_fs alone, this array of plots show each species in its own row and each snapshot time in its own column. The timing of the snapshots in the first column is about 1% of the reaction time; the other three columns are equally spaced along the reaction time. Mixing (no-reaction) lines are dashed, and infinitely fast reaction lines are in bold black, with navy lines showing the interpolated C(f).  Flat lines on each plot indicate the average concentration from the ODE and the beta-averaged C(f), which should agree with each other.  Where two flat lines are visible, the average species concentration lies outside the pair of limits defined by blend_fs and the reaction rate is “clamped” to stay just within those limits; clamping reflects an internal inconsistency in the closure under clamped conditions.  Clamping may lead for example to unexpected changes in the shape of reaction rates that are not physical.</p>

<p>Profiles of the beta-averaged values of all known limits, compared with the species amounts from the ODE solution <a href="/blog/docs/assets/fuller_BC_azo_coupling_kinetics_eps1_limit_averages_blend_fs.png">full-size</a>:</p>

<p><img src="/blog/docs/assets/fuller_BC_azo_coupling_kinetics_eps1_limit_averages_blend_fs.png" alt="ODE trajectory versus beta-averages" width="1000" /></p>

<p>Here we have a subplot for each species versus time. The ODE trajectory on these plots is shown as a dashed red line; the other curves are the positions of the beta-integrated averages of the known limits for that species.  The labels of the known limits comprise the reaction subsets for which that curve is the correct limit, with undercores between the identifiers of each subsets.  Some curve labels are long because the species limit is the same for many subsets. The heavy black line is the beta-averaged combined limit calculated by blend_fs. When the ODE trajectory strays outside the range between the beta-averaged no-reaction limit (plot label contains 0) and the beta_averaged blend_fs moving limit, the solution is said to “clamp”, see above.  That occurs in this illustration especially for components B and R.</p>

<p>When running blend_fs, we generate snapshot plots showing the evolution of (the moving) fs and the (changing) shapes of the “tent” plots for each species at the blend_fs combined limit <a href="/blog/docs/assets/fuller_BC_azo_coupling_kinetics_eps1_blendfs_limit_grid.png">full-size</a>:</p>

<p><img src="/blog/docs/assets/fuller_BC_azo_coupling_kinetics_eps1_blendfs_limit_grid.png" alt="Evolving species limits during ODE solve" width="1000" /></p>

<p>Once again, the timing of these snapshots is about 1% of reaction time and then equally spaced. In this case, fs does not change a lot when modeled using the closure from the 2009 and 2025 publications implemented in blend_fs.</p>

<p>When running blend_fs, we show the full evolution of (the moving) fs at the combined limit <a href="/blog/docs/assets/fuller_BC_azo_coupling_kinetics_eps1_blendfs_diag.png">full-size</a>:</p>

<p><img src="/blog/docs/assets/fuller_BC_azo_coupling_kinetics_eps1_blendfs_diag.png" alt="Evolving fs during ODE solve" width="1000" /></p>

<p>fs may not move much using blend_fs if the reaction conditions keep it near one of the boundary values; in this reaction system, there is most movement using blend_fs at low mixing intensity, where the amount of S produced during reaction is highest.</p>

<p>Product fractions versus time <a href="/blog/docs/assets/fuller_BC_azo_coupling_kinetics_eps1_product_fractions_vs_time.png">full-size</a>:</p>

<p><img src="/blog/docs/assets/fuller_BC_azo_coupling_kinetics_eps1_product_fractions_vs_time.png" alt="Product fractions versus time" width="1000" /></p>

<p>These plots show the evolution of the fractional product amounts made while consuming the limiting reactant; of particular interest are XQ and XS.</p>

<p>Results of an epsilon sweep <a href="/blog/docs/assets/fuller_BC_azo_coupling_kinetics_Xspecies_vs_epsilon_blend_fs.png">full-size</a>:</p>

<p><img src="/blog/docs/assets/fuller_BC_azo_coupling_kinetics_Xspecies_vs_epsilon_blend_fs.png" alt="Product fractions versus epsilon" width="1000" /></p>

<p>The epsilon sweep plot shows behaviour of the closure over a broad range of mixing conditions from very mild to very intense.  At either end, XQ and XS tend towards limiting values corresponding with limitation by kinetics (at high epsilon) and mixing (at low epsilon).</p>

<p>Other diagnostics produced from a simulation include the CPU time (blend_fs is generally fast) and the number of times the ODE solution “clamped” C(f) to keep C(f) as used in the reaction rate calculation within the blend_fs pair of limits, even if the ODE solution average had driften outside those limits.  The consequence of such a clamp is an internal inconsistency in the model, whereby the beta-averaged C(f) no longer equals the average calculated by solving the ODEs.  That could lead to unexpected and likely unphysical changes in reaction rate during the simulations.  We found that clamps were frequent for blend_fs and will discuss that more later.</p>

<p><strong>Footnotes</strong></p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:7" role="doc-endnote">
      <p><a href="https://scikit-sundae.readthedocs.io/en/stable/">scikit-sundae</a> <a href="#fnref:7" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Github joehannon</name></author><summary type="html"><![CDATA[Tests]]></summary></entry><entry><title type="html">3: Calculation of limits/ extremes</title><link href="https://joehannon.github.io/blog/2026/06/29/01-title_003.html" rel="alternate" type="text/html" title="3: Calculation of limits/ extremes" /><published>2026-06-29T00:00:00+00:00</published><updated>2026-06-29T00:00:00+00:00</updated><id>https://joehannon.github.io/blog/2026/06/29/01-title_003</id><content type="html" xml:base="https://joehannon.github.io/blog/2026/06/29/01-title_003.html"><![CDATA[<p><strong>Single reactions</strong></p>

<p>The limits for a single reaction are fairly obvious: no reaction (k=0) and infinitely fast reaction (k=$\infty$).  Using Baldyga’s closure is straightforward as described in his 1994 publication.  For one reaction (A+B&gt;P) with (in this illustration) similar concentrations in each stream, the following limits are obtained <a href="/blog/docs/assets/one_rxn_species_by_subset_grid.png">full-size</a>:</p>

<p><img src="/blog/docs/assets/one_rxn_species_by_subset_grid.png" alt="A+B&gt;P" width="1000" /></p>

<p><strong>Multiple reactions</strong></p>

<p>The limits for multiple reactions are more numerous.  For example there are four possible limits (extremes) with two reactions and in general the number of possible extremes including the no-reaction limit (“mixing line”) is $2^{N_{rxns}}$. The formaulae for each line segment (between 0 and $f_{s}$ and between $f_{s}$ and 1) in these reactions can be worked out by hand using algebra, stoichiometric coefficients and feed concentrations.  The algebra becomes tedious quickly and more complex when many reactions compete for the same reactants, or reactions consume products of other reactions.</p>

<p>I have done my share of those algebraic manipulations and it does not take long to realize that there must be a better way using computational tools in 2026.  I have written a smallish open source python program (species_limits.py) that reads in the reaction scheme as a JSON file and generates the full set of limits/ extremes for more or less any reaction system.  If you are interested in the methodology, read the documentation that comes inside and with the source code.  For this article, its sufficient to say that I used optimization via linear programming (LP) to maximize the extent of all reactions at each limit/extreme, with some tweaks to ensure the correct maximum was found in a few difficult cases.  When writing the code, I found it very helpful to use Anthropic’s <a href="https://claude.com/product/claude-code">Claude Code</a> product inside Microsoft’s VS Code. I mainly used Claude Sonnet 4.6.</p>

<p>I hope the introductory posts on limits were clear enough to prepare you for a bit of an onslaught of plots for multiple reactions, presented below.  You will notice that my terminology refers to the concentrated fed limiting reactant as “A” - which for me chimes with “Added” and the bulk typically more dilute reactant as “B” which chimes with “Bulk”.  That is different to the terminology used generally by Baldyga and Bourne, in which “B” is the name they give to the limiting reactant.</p>

<p>The original azo-coupling (A+B&gt;R, A+R&gt;S), sometimes called the “first Bourne reaction”, with 4 limits <a href="/blog/docs/assets/original_azo_coupling_species_by_subset_grid.png">full-size</a>:</p>

<p><img src="/blog/docs/assets/original_azo_coupling_species_by_subset_grid.png" alt="A+B&gt;P, A+R&gt;S" width="1000" /></p>

<p>The full azo-coupling in 3-reaction format (A+B&gt;P, A+P&gt;S, A+C&gt;Q), with 8 limits <a href="/blog/docs/assets/2025_paper_kinetics_species_by_subset_grid.png">full-size</a>:</p>

<p><img src="/blog/docs/assets/2025_paper_kinetics_species_by_subset_grid.png" alt="azo-coupling, 3-reactions" width="1000" /></p>

<p>The fuller azocoupling in 5-reaction format (A+B&gt;R, A+R&gt;S, A+B&gt;T, A+T&gt;S, A+C&gt;Q), with 32 limits <a href="/blog/docs/assets/fuller_BC_azo_coupling_kinetics_species_by_subset_grid.png">full-size</a>:</p>

<p><img src="/blog/docs/assets/fuller_BC_azo_coupling_kinetics_species_by_subset_grid.png" alt="azo-coupling, 5-reactions" width="1000" /></p>

<p>I would encourage you to look at the extremes closely (click ‘full-size’ and zoom in), at least for one multiple reaction system.  Here are a few things worth noticing:</p>
<ul>
  <li>each subplot (apart from the first subplot containing the mixing line) considers a different subset of the reactions, all running infinitely fast; there is one subset for every possible combination of infinitely fast reactions</li>
  <li>the subsets are numbered starting at zero and the reactions considered in a subset are shown above the subplot</li>
  <li>with a fairly modest sized reaction scheme there are already a lot of subset limits to consider</li>
  <li>some subset solutions / extremes are duplicates of the no-reaction extreme; if the necessary reactants are not available (either fed or produced) at that extreme, some or all reaction lines will not run</li>
  <li>some extremes are duplicates of other extremes, for the same reason; that duplicatiom means that for each species there are fewer than $2^{N_{rxns}}$ unique limits</li>
  <li>when at least some reactions in a subset can run to completion, an $f_{s}$ is calculated and a vertical dashed line indicates its position along the f-axis.  $f_{s}$ is also noted in the title of the plot for that subset</li>
  <li>typically there are several subsets with distinct values of $f_{s}$ and distinct sets of reactant and product profiles in the now familiar “tent” shapes</li>
  <li>the last subset considered includes all reactions</li>
  <li>not all reaction products are produced in all subsets; even if the reaction line that produces a species is present in the subset, if another line consumes that species, that product amount will be zero at that limit.</li>
</ul>

<p>In principle and depending on the actual kinetics of each reaction line and the starting concentrations of all species, behaviour of a real complex reaction system could fall between the no-reaction line and any of these extremes.  As noted in the 2025 publication from Baldyga’s group, it is also possible for the behaviour of a real complex reaction system to fall between the no-reaction line and a <em>combination</em> of these extremes.</p>

<p><strong>So, which limits to use?</strong></p>

<p>If we are aiming for a closure model that is widely applicable to (more or less) any reaction system, with any set of kinetic parameters and input concentrations and any mixing intensity, it appears that we have to consider a broad range of potential behaviour/ limits/ extremes and/or their combinations. My journey through that somewhat complex terrain will be summarized in the next posts.</p>]]></content><author><name>Github joehannon</name></author><summary type="html"><![CDATA[Single reactions]]></summary></entry></feed>