Cantera / enhancements

Repository for proposed and ongoing enhancements to Cantera
11 stars 5 forks source link

Improve handling of electrochemical reactions #38

Open speth opened 4 years ago

speth commented 4 years ago

Abstract

There are several ways of specifying the rate constant for electrochemical reactions which are used in practice. The aim of this enhancement is to support input of rate constants using those formulations that are convenient for users, while keeping the underlying implementation reasonably clean & efficient.

Motivation

Cantera currently has had an incomplete and not fully-implemented set of options for defining electrochemical reaction rates. The non-functional options (namely, those related to the Butler-Volmer formulation) were deprecated in Cantera 2.5 (See Cantera/cantera#749 and Cantera/cantera#750).

In the discussion of that issue, @decaluwe provided a summary of the theoretical bases for electrochemical reaction types, which can be found here: Charge.Transfer.in.Cantera.pdf.

Some additional context provided by @wbessler in the above-linked issue is also worth highlighting: https://github.com/Cantera/cantera/issues/749#issuecomment-559727736

Possible Solutions

To summarize the suggestions from both @decaluwe and @wbessler, it makes sense to support two general types of electrochemical reactions, one corresponding to Marcus theory / elementary reactions, and one for implementing different variants of the Butler-Volmer equation. In more detail:

Marcus theory:

Butler-Volmer:

Inputs: The user should be able to:

Outputs: The user should also be able to query, for a given state:

@ischoegl has also raised concerns about the fact that checking whether a reaction involves charge transfer currently involves parsing the reaction equation from its string form twice. If desired, this duplicate parsing could be eliminated by making parsing of the reaction equation a separate step from instantiation of a Reaction object.

decaluwe commented 3 years ago

Just a brief update: we have started some work on this, and have working modules in place for three different charge transfer mechanisms: Butler-Volmer, Marcus theory, and Marcus-Hush-Chidsey.

They are in a pretty basic state, at the moment: concentration dependence is only partially implemented, and setting other properties like reaction orders is yet-to-be-implemented. But it is probably a good time to write up a basic test suite and submit an initial PR, before adding in the "next layer" of features.

speth commented 2 years ago

As a concrete example, some of this has been implemented in a branch (https://github.com/CSM-Offenburg/cantera/blob/a91ed333a0f6448f0ab99fa76c9be0f1368ce03a/src/kinetics/InterfaceKinetics.cpp#L349-L389). The following is a part of InterfaceKinetics::updateROP():

    for (size_t i = 0; i < m_beta.size(); i++) {
        // Get the reaction index, relative to the array of all interface 
        // reactions:
        size_t irxn = m_ctrxn[i];

        // Update electrochemical potentials. This is equal to the 
        //  overpotential, multiplied by the elementary charge transfered and 
        //  the Farday constant, for each reaction:
        vector_fp echemPotentials(nReactions(), 0.0);
        getDeltaElectrochemPotentials(&echemPotentials[0]);

        // Reciprocal of RT:
        double rrt = 1.0 / thermo(reactionPhaseIndex()).RT();
        // Calculate the BV-type form directly, if specified:
        if (m_ctrxn_form[i] > 0 && m_ctrxn_form[i] < 4) {
            // Scale the concentration dependence by the refeence
            // concentrations:
            m_ropf[irxn] *= m_ctrxn_concScaleFactor[i];

            double beta = m_beta[i];
            if (m_ctrxn_form[i] == 3){ // marcus theory
                beta += echemPotentials[irxn]/4/m_lambda[i];
            } 
            // Calculate rop_f and rop_r:
            m_ropf[irxn] *= exp(-beta*echemPotentials[irxn]*rrt)/Faraday;
            m_ropr[irxn] = m_ropf[irxn]*exp(echemPotentials[irxn]*rrt);
        } else if (m_ctrxn_form[i] == 4) {
            // Scale the concentration dependence by the reference
            // concentrations:
            double i_o = m_ropf[irxn] * m_ctrxn_concScaleFactor[i];
            double eta_RT = echemPotentials[irxn]*rrt;
            double lambda_RT = m_lambda[i]*rrt;
            double erfc_term = erfc((lambda_RT - sqrt(1.0 + sqrt(lambda_RT)
                            + eta_RT*eta_RT))/2/sqrt(lambda_RT));
            m_ropf[irxn] = i_o*sqrt(Pi*lambda_RT)
                           * erfc_term/(1. + exp(eta_RT))/Faraday;
            m_ropr[irxn] = i_o*sqrt(Pi*lambda_RT)
                           * erfc_term/(1. + exp(-eta_RT))/Faraday;
        } 
    }
decaluwe commented 1 year ago

Okay, so moving discussion from #cantera1416 over here.

@korffdm has an implementation that works with the current ReactionRateFactory format. It is not the cleanest implementation, in terms of making the theory easy to trace and debug, but it is accurate (it passes local tests we have developed and will make part of the PR, if we stick with the current development), and appears to be the shortest development path to getting these capabilities into Cantera.

One note, w/r/t the theory described above.

All that aside, I would like to discuss if an implementation closer to the code snippets in @speth's comment on Feb 15 is possible. From the refactorization, it looks as though all production rates currently go through updateROP, where the procedure is:

  1. Update the rate coefficients for the current state (temperature, concentrations, electric potential effects)
  2. Calculate reverse rate coefficient from k_fwd/K_eq
  3. Multiply the fwd rate coefficient by the product of the activity concentrations raised to reactant species orders to obtain the forward ROP
  4. Multiply the ref rate coefficient by the product of the activity concentrations raised to product species orders to obtain the reverse ROP

Before I speculate further, let me make sure I understand the situation correctly: am I correct that all reactions go through this procedure?