Skip to content

Make rate constant calculation consistent with Kee, et al.#1084

Merged
speth merged 8 commits intoCantera:mainfrom
ischoegl:consistent-rate-constants
Aug 18, 2021
Merged

Make rate constant calculation consistent with Kee, et al.#1084
speth merged 8 commits intoCantera:mainfrom
ischoegl:consistent-rate-constants

Conversation

@ischoegl
Copy link
Copy Markdown
Member

@ischoegl ischoegl commented Aug 14, 2021

Changes proposed in this pull request

The current implementation of GasKinetics::getFwdRateConstants is not consistent with textbook definitions (see Eq. 9.75 in Kee, Coltrin and Glarborg, Chemically Reacting Flow). An implementation consistent with Eq. 9.75 multiplies the effective third-body concentration only for the calculation of rates-of-progress. Falloff reactions are a separate case as they use a modified rate constant, which does include third-body concentrations.

As implemented, the calculation is separate from updateROP (i.e. no change from previous approach). The affected methods mainly serve informational purposes (see comment in BulkKinetics::getRevRateConstants), so there is no expected impact on performance.

The deprecation uses a global flag to make a transition minimally intrusive. This is important, as rate constants are used extensively in the test suite.

If applicable, fill in the issue number this pull request is fixing

Closes #1083

If applicable, provide an example illustrating new features this pull request is introducing

The proposed change deprecates the old behavior and includes a global switch to facilitate the transition:

In [1]: import cantera as ct
   ...: gas = ct.Solution("h2o2.yaml")
   ...: gas.TPX = 300, ct.one_atm, 'H2:1, O2:3'
   ...: gas.equilibrate('HP')
   ...: 

In [2]: gas.forward_rate_constants
DeprecationWarning: GasKinetics::getFwdRateConstants: Behavior to change after Cantera 2.6;
results will no longer include third-body concentrations for ThreeBodyReaction objects.
Set 'use_legacy_rate_constants' to false for new behavior.
Out[2]: 
array([1.66615789e+06, 3.30877601e+06, 8.13054905e+09, 2.00000000e+10,
       1.63880887e+10, 2.03131173e+05, 6.47691445e+06, 5.46151483e+07,
       0.00000000e+00, 0.00000000e+00, 2.65917764e+09, 1.97800108e+06,
       1.07713432e+03, 6.84832674e+06, 5.01798085e+07, 3.38149587e+09,
       3.47031524e+10, 7.21665842e+10, 1.54550508e+10, 4.22813794e+09,
       9.91305045e+09, 2.98116515e+07, 5.58903179e+09, 1.63414930e+10,
       1.80587269e+09, 1.50070437e+12, 1.91961755e+08, 2.38275715e+10,
       7.93037160e+10])

In [3]: ct.use_legacy_rate_constants(False)

In [4]: gas.forward_rate_constants
Out[4]: 
array([5.70210643e+07, 2.37587768e+08, 8.13054905e+09, 2.00000000e+10,
       1.63880887e+10, 3.88376134e+09, 1.57529678e+09, 3.35697020e+10,
       1.96912098e+09, 1.53667243e+09, 2.65917764e+09, 4.75175536e+08,
       9.12756539e+08, 4.20938686e+09, 4.96741937e+09, 3.38149587e+09,
       3.47031524e+10, 7.21665842e+10, 1.54550508e+10, 4.22813794e+09,
       9.91305045e+09, 2.98116515e+07, 5.58903179e+09, 1.63414930e+10,
       1.80587269e+09, 1.50070437e+12, 1.91961755e+08, 2.38275715e+10,
       7.93037160e+10])

This change further clarifies a current discrepancy between outputs of ReactionRate and forward_rate_constants for three-body reactions, e.g.

In [5]: ix = 0
   ...: gas.reaction(ix)
   ...: 
Out[5]: <ThreeBodyReaction: 2 O + M <=> O2 + M>

In [6]: ct.use_legacy_rate_constants(True) # current implementation

In [7]: gas.reaction(ix).rate(gas.T) # this evaluates the standard Arrhenius reaction rate
Out[7]: 57021064.2631974

In [8]: gas.forward_rate_constants[ix]
Out[8]: 1666157.893731073

In [9]: ct.use_legacy_rate_constants(False) # proposed implementation

In [10]: gas.reaction(ix).rate(gas.T)
Out[10]: 57021064.2631974

In [11]: gas.forward_rate_constants[ix]
Out[11]: 57021064.2631974

Checklist

  • The pull request includes a clear description of this code change
  • Commit messages have short titles and reference relevant issues
  • Build passes (scons build & scons test) and unit tests address code coverage
  • Style & formatting of contributed code follows contributing guidelines
  • The pull request is ready for review

Additional thoughts

The default behavior would presumably switch to the standard definition with Cantera 3.0. There is no reason to remove the flag at that point in case there should be concerns about back-ward compatibility of existing scripts.

Further context

The current definition affects temperature derivatives for the calculation of Jacobians (e.g. #1081), where a spurious dependency is created due to the inclusion of M concentrations in the rate constant calculation.

@codecov
Copy link
Copy Markdown

codecov bot commented Aug 14, 2021

Codecov Report

Merging #1084 (0766a35) into main (375902b) will increase coverage by 0.00%.
The diff coverage is 66.66%.

Impacted file tree graph

@@           Coverage Diff           @@
##             main    #1084   +/-   ##
=======================================
  Coverage   73.45%   73.45%           
=======================================
  Files         362      364    +2     
  Lines       47554    47611   +57     
=======================================
+ Hits        34929    34972   +43     
- Misses      12625    12639   +14     
Impacted Files Coverage Δ
include/cantera/base/global.h 93.33% <ø> (ø)
include/cantera/kinetics/GasKinetics.h 100.00% <ø> (ø)
include/cantera/kinetics/Kinetics.h 55.88% <0.00%> (ø)
src/clib/ct.cpp 8.29% <0.00%> (-0.06%) ⬇️
src/kinetics/GasKinetics.cpp 92.61% <85.71%> (-0.43%) ⬇️
src/base/application.cpp 70.68% <100.00%> (+2.45%) ⬆️
src/base/application.h 76.92% <100.00%> (+4.19%) ⬆️
src/base/global.cpp 75.22% <100.00%> (+2.84%) ⬆️
src/kinetics/BulkKinetics.cpp 91.00% <100.00%> (-0.08%) ⬇️
test/general/string_processing.cpp 100.00% <100.00%> (ø)
... and 11 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 375902b...0766a35. Read the comment docs.

@ischoegl ischoegl requested a review from a team August 14, 2021 14:34
@ischoegl ischoegl changed the title Make rate constants calculation consistent with Kee, et al. Make rate constant calculation consistent with Kee, et al. Aug 14, 2021
@ischoegl
Copy link
Copy Markdown
Member Author

I guess this needs to be expanded to clib/MATLAB 😀 … I’d still appreciate feedback on the implementation approach.

@ischoegl ischoegl force-pushed the consistent-rate-constants branch from 73517f1 to 7003ffa Compare August 14, 2021 15:12
The previous implementation does not follow textbook definitions (e.g.
Kee/Coltrin/Glarborg, Chemically Reacting Flow, Eqs. 9.73 and 9.75). The
revised implementation is consistent with Eq. 9.75 and multiplies the
effective third-body concentration only for the calculation of rates-of-progress.
Falloff reactions are a separate case as they use a modified rate constant,
which does include third-body concentrations.
@ischoegl ischoegl force-pushed the consistent-rate-constants branch from 4902800 to 8203c3d Compare August 14, 2021 23:07
Copy link
Copy Markdown
Member

@decaluwe decaluwe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for putting this together, @ischoegl. I agree that, even if few are likely to run into it, having the reported rate constants match the established convention is a better outcome. Certainly we've run into a few users just in the past few months, trying to verify Cantera's approaches, compared to literature approaches, so I don't think this is just academic.

Anyway, from your report, the implementation looks pretty straightforward, and you seem to have a good method in place (via the use_legacy methods and flags) to handle the transition. Just so I'm sure I understand:
-Is the idea to switch the default for use_legacy_rate_constants to true, starting with Cantera 2.6?

  • Do you intend for the use_legacy_rates functionality to be a permanent feature?
  • in base/global, you have bool values defined for use_legacy_rate_constants and legacy_rate_constants_used--do I understand correctly that the former is the "set" method, whereas the latter is the "get?"

Anyway, I'm generally in favor of this change. The only thing I'd like to see is a simple unit test demonstrating the switching back and forth between legacy and non-legacy, as you have in your PR message, and confirming that they each match theory.

The one comment from @bryanwweber's original reply that hasn't really yet been addressed: can you point to any other theory or textbook, besides Kee, which says this is the authoritative approach? I tend to agree that the approach you suggest is more intuitive, but it is not that black and white, for me - I would not be surprised if others came down on the other side of the fence. I'd certainly like to have a better picture of how this theory has, historically been implemented.

@ischoegl ischoegl force-pushed the consistent-rate-constants branch from 03b9b5f to 950952d Compare August 16, 2021 18:41
@bryanwweber
Copy link
Copy Markdown
Member

Thanks for the review @decaluwe and thanks for the PR @ischoegl . Personally, I'm not in favor of changing this behavior, for the reasons I mentioned in the issue (even with the legacy flag added). I won't object, though, if @decaluwe or @speth decide to merge

@ischoegl
Copy link
Copy Markdown
Member Author

ischoegl commented Aug 16, 2021

@decaluwe ... thanks for your positive feedback. Regarding literature: Kee just happens to have an equation that is most clear-cut, but other texts are pretty consistent in treating M just like any other species. This implies that it is not commonly understood as being part of the reaction rate constant k. I can point to several examples where this is evident (and am not aware of any references that merge M with k outside of Falloff reactions, which are specifically not the objection here):

  • Glassman (3rd ed): Eqs 25/25a
  • Kuo: Section 2.3 (Third-Order Reactions)
  • Turns (3rd ed): Eqs. 4.24/4.25
  • Law: As part of the foundation of Lindemann, Eqs 2.3.3-2.3.6

So, I believe Cantera's inclusion of M in the rate constant of three-body reactions is non-intuitive.

Fwiw, CHEMKIN is consistent with Kee (perhaps not suprisingly): "When a third body is needed, the concentration of the effective third body must appear in the expression for the rate-of-progress variable. ", see Eq. 3-18 (Chemkin 17.0).

Regarding other questions:

Is the idea to switch the default for use_legacy_rate_constants to true, starting with Cantera 2.6?

It would be true for Cantera 2.6 (no change, but warning), and then switched to false after that. For those who want to adopt early and don't want to always switch, there's a scons flag.

Do you intend for the use_legacy_rates functionality to be a permanent feature?

I think the feature can remain, as it's pretty simple to switch. If there's a consensus to remove (after Cantera 2.7/3.0), that would be fine also.

In base/global, you have bool values defined for use_legacy_rate_constants and legacy_rate_constants_used--do I understand correctly that the former is the "set" method, whereas the latter is the "get?"

This is correct. I used the same terminology as the preexisting thermo_warnings feature. It would be simple to change the names.

PS: beyond, I compiled the MATLAB extension in the meantime and fixed a minor glitch.

PPS: just saw that you commented also @bryanwweber ... at this point, I believe it's up to @speth to weigh in. Fwiw, this plays into Cantera/enhancements#87, where the status of M has not been fully resolved.

@ischoegl ischoegl requested a review from speth August 16, 2021 19:54
@decaluwe
Copy link
Copy Markdown
Member

decaluwe commented Aug 16, 2021

Thanks, @ingmar - note that I accidentally wrote true in my "switch the default" question (50/50 chance of getting it right 😂 )... but your answer clarified it 100% regardless.

@bryanwweber, can we discuss your reservations a bit more? I'm not really keen on steamrolling over your concerns. To consolidate the convo, you listed:

  1. That it was based on 'anecdata' - see @ischoegl additional comments above.
  2. Computation speed. As @ischoegl mentioned, getFwdRateConstants merely duplicates a snippet of the updateROP code, rather than offloading calculations from updateROP. I.e. updateROP does not call the change.
  3. Code history and breaking any code that depends on the "legacy" way of doing it. Obviously, this is the most serious of the concerns, but as with any compatibility-breaking changes, people will have the deprecation period to make noise about it, if it's going to cause real problems (side note: I always feel like the deprecation message should explicitly mention this as an option). To me, this feels similar to our conversation on standard states, where we are discussing how our convention for a mostly internal routine comports with field conventions. There is "what is the right approach?" and then "If the current approach is not the right one, is it wrong enough to justify the cost of changing it?" To me the costs on this one feel small (but I'll note that I don't use these routines, so my word does not go terribly far, here). I will also note that we are not necessarily "breaking" scripts that have used the current approach. It is also possible that these scripts are using the rate constants as if they are consistent with @ischoegl's definition. In this case, we'd be fixing their scripts 😂

Agree with @ischoegl, though -- I'd like to hear @speth weigh in, since there is still disagreement.

@ischoegl
Copy link
Copy Markdown
Member Author

ischoegl commented Aug 16, 2021

There is "what is the right approach?" and then "If the current approach is not the right one, is it wrong enough to justify the cost of changing it?"

😆 ... to me personally, this always looked like a bug, and - at least to me - it looks wrong enough to be worth fixing; it's not the first time I stumbled across this implementation. Looking over references today confirmed that my recollections square with what I believe the theory says. There may be some other references, but what I listed in my previous post corresponds to my go-to references on my bookshelf. Unfortunately, I don't have Gardiner on hand, but I'd frankly be surprised if it said anything different (lumping the M concentrations together with k makes little intuitive sense to me).

Now, there may be one addition that may facilitate a decision to adopt what I believe is the correct implementation: at the moment, there is no interface to query third-body concentrations outside of C++ (which makes it rather hard to calculate anything based on collision efficiencies). I'd be happy to add this feature to this PR ...

@bryanwweber
Copy link
Copy Markdown
Member

Thanks @decaluwe and @ischoegl. I can provide some more thoughts, as they've evolved over the last few days of thinking about this:

  1. Agreed, this does seem to be a standard. Thanks for digging up more references @ischoegl!

  2. and 3. I have a few thoughts here still. I personally don't view this as a bug, or even really a problem. The software works as intended and the only downside is that it is somewhat harder to trace the calculations, but at a level that most (for some definition of 'most') people don't care about. As such, I prefer to stick to the "if it ain't broke don't fix it" axiom. I think the appropriate fix would be to document what's happening and why.

    That said, we don't know why the calculation is done this way. Since it goes against convention, I have to assume there was some reason for it when it was first implemented. If that reason is no longer valid, or we're willing to discard that consideration at this point, that's fine. But without knowing why, I'm really hesitant to change it.

One last thought I had in terms of the why is that it may simplify calculations for other phase types where [M] is not a factor. For instance, it may allow you to have a concrete rather than virtual updateROP at the Kinetics level, iff getFwdRateConstants includes [M] in the gas phase. Again, I don't know that this is the case - and that's exactly why I don't feel great about this change 😄 , without knowing why (to the best of our ability to answer that), we can't fully predict what would be unintended consequences. The test suite coverage of some of the other phases is not all that good, so it may miss something like this...

@ischoegl
Copy link
Copy Markdown
Member Author

ischoegl commented Aug 17, 2021

@bryanwweber ... thanks for your additional comments.

I personally don't view this as a bug, or even really a problem. The software works as intended and the only downside is that it is somewhat harder to trace the calculations, but at a level that most (for some definition of 'most') people don't care about. As such, I prefer to stick to the "if it ain't broke don't fix it" axiom. I think the appropriate fix would be to document what's happening and why.

I respectfully disagree here. I believe at this point we can all agree that Cantera output goes against accepted standards. I simply don't see a reason why we should cling to a 'feature' (aka bug) that can be fixed. I believe that the proposed deprecation would take care of it once and for all, instead of assuming that users will read the 'fine print' (which they usually don't). To @decaluwe's point, I believe most people don't even realize that the output is not what they would expect.

Regarding virtual vs concrete: the current implementation of updateROP depends on a few other features that are not visible to Kinetics (falloff, etc.), and is currently defined as a virtual function. The hypothetical case to make it concrete would still rely on other virtual functions, so I believe this is just moving the goal posts. However, this may explain why this historical deviation from the standard was used.

@bryanwweber
Copy link
Copy Markdown
Member

I respectfully disagree here.

Thanks @ischoegl, I'm happy to respectfully agree to disagree, at least until we figure out why this is the way it is (if we ever do) 😄 I certainly see all your points, and I think you're making a strong argument. I'm just personally balancing it a little different way, which is why we have a team to work on this!

Copy link
Copy Markdown
Member

@speth speth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks all, for the thorough discussion of the issues at hand here.

My initial take on this discrepancy was that the simplest fix would be to just define the rate constant for a three-body reaction as including the effective third body concentration. However, it seems that this is not a quantity of much particular interest, and doing so would be at odds with quite a few textbook descriptions of three-body reactions.

I sort of expect that most use of the get_forward_rate_constants function is done in a way where either the users are expecting the textbook definition and currently getting the wrong answer, or doing something where it doesn't really matter whether the third body concentration is included, like plotting them as a function of temperature. In any case, making the current behavior the default for the next version, with copious documentation of the upcoming change (as implemented in this PR) and then still having the flag to keep the old behavior in the following version is about the best we can do. I don't think history is enough of a reason not to change this. And there's something to be said for aligning the breaking change here with the 3.0 release, from a semantic versioning standpoint.

One wrinkle, related to Cantera/enhancements#87, is that if we kept the third-body term as part of the rate constant, that calculation could be folded into a particular ReactionRate class, such that there would eventually be no special behavior associated with the GasKinetics class and BulkKinetics would just be the generic Kinetics manager for all bulk phases. I don't know if there's a way to still achieve this while still making the rate constant definition textbook-consistent.

In any case, I'm 👍 on this change in general, and only had a few minor suggestions on the implementation.

@bryanwweber
Copy link
Copy Markdown
Member

Thanks @speth! My concerns are resolved then.

@ischoegl
Copy link
Copy Markdown
Member Author

ischoegl commented Aug 18, 2021

Thank you @speth, @decaluwe and @bryanwweber for your inputs on this, even if we couldn't find complete consensus. Based on @speth's review, I edited all docstrings as well as the warning message, which now reads:

DeprecationWarning: GasKinetics::getFwdRateConstants: Behavior to change after Cantera 2.6;
results will no longer include third-body concentrations for three-body reactions.
To switch to new behavior, use 'cantera.use_legacy_rate_constants(False)' (Python),
'useLegacyRateConstants(0)' (MATLAB), 'Cantera::use_legacy_rate_constants(false)' (C++),
or 'ct_use_legacy_rate_constants(0)' (clib).

Among the interfaces, Fortran is the only one where the switch isn't available: I haven't coded in Fortran since the mid 90's (last millenium!), so I probably shouldn't try.

@bryanwweber ... I hope I hit the mark in my deprecation message and docstring phrasing (I may not be the best word-smith). If you (or anyone else) should have suggestions for improvement, I'd be happy to incorporate.

@ischoegl ischoegl force-pushed the consistent-rate-constants branch from 171baf7 to 0766a35 Compare August 18, 2021 17:40
@bryanwweber
Copy link
Copy Markdown
Member

Thanks @ischoegl the message looks good to me. Will this be produced every time getFwdRateConstants() is called as long as the old behavior is used? If so, it'd be nice to have a way to suppress it but retain the old behavior. One way to do that would be to not print the message if the legacy behavior is explicitly chosen by the user, only showing the message if no option is explicitly specified. Another would be to have a separate option, like suppress_thermo_warnings.

One other comment, and I suppose this applies to all the warn_deprecated() calls in C++, I wonder if it's possible to catch those in Python and turn them into real Python DeprecationWarnings so they can be controlled by Python's warning interfaces? This would be an alternate way to address my comment above, as well, but probably is a lot more work, almost certainly outside the scope of this specific change.

@ischoegl
Copy link
Copy Markdown
Member Author

ischoegl commented Aug 18, 2021

@bryanwweber ... thanks for looking over the messages: this is much appreciated! Per your questions:

Will this be produced every time getFwdRateConstants() is called as long as the old behavior is used?

No, only the first time after each launch of Cantera. For subsequent calls, the deprecation warnings are suppressed.

I suppose this applies to all the warn_deprecated() calls in C++, I wonder if it's possible to catch those in Python and turn them into real Python DeprecationWarnings so they can be controlled by Python's warning interfaces?

That's a really interesting question, and yes, this would go well beyond this PR. It would be great to establish this interface (for any warning), but I don't think that this is a simple endeavor as C++ doesn't come with a warning framework like Python (from what I understand, there are some C++ loggers out there to differentiate exception levels, but that's not the issue I believe). In terms of translating warnings to Python, I am not entirely sure how this would work: as no exceptions are raised that can be caught, any Cython call into C++ would probably have to trigger a query whether there are some buffered logging messages (?) ... I am mostly speculating here as details go well beyond my level of familiarity. @speth has probably more insights here ...

@speth
Copy link
Copy Markdown
Member

speth commented Aug 18, 2021

In terms of translating warnings to Python, I am not entirely sure how this would work: as no exceptions are raised that can be caught, any Cython call into C++ would probably have to trigger a query whether there are some buffered logging messages

I think this would require something a bit like how we interface with Python for writing messages to stdout, that is, registering a handler in the Application class and in that handler making whatever the correct calls are to the Python C API. @bryanwweber, you're always welcome to write up an enhancement proposal 😁.

@speth speth merged commit e77148b into Cantera:main Aug 18, 2021
@ischoegl
Copy link
Copy Markdown
Member Author

@speth - thanks for the feedback/review!

@bryanwweber, you're always welcome to write up an enhancement proposal 😁.

I actually pre-empted this by a discussion here: Cantera/enhancements#112. Good to hear that there may be an easier route!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Cantera's definition/calculation of rate constants

4 participants