Cantera  2.2.1
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
ElectrodeKinetics.cpp
Go to the documentation of this file.
1 /**
2  * @file ElectrodeKinetics.cpp
3  */
4 
8 #include "cantera/base/global.h"
9 
10 #include <cstdio>
11 
12 using namespace std;
13 
14 namespace Cantera
15 {
16 //============================================================================================================================
17 ElectrodeKinetics::ElectrodeKinetics(thermo_t* thermo) :
18  InterfaceKinetics(thermo),
19  metalPhaseIndex_(npos),
20  solnPhaseIndex_(npos),
21  kElectronIndex_(npos)
22 {
23  warn_deprecated("class ElectrodeKinetics",
24  "To be removed after Cantera 2.2.");
25 }
26 //============================================================================================================================
28 {
29  for (size_t i = 0; i < rmcVector.size(); i++) {
30  delete rmcVector[i];
31  }
32 }
33 //============================================================================================================================
36 
37 {
38  /*
39  * Call the assignment operator
40  */
42 }
43 //============================================================================================================================
45 {
46  /*
47  * Check for self assignment.
48  */
49  if (this == &right) {
50  return *this;
51  }
52 
54 
58 
59  for (size_t i = 0; i < rmcVector.size(); i++) {
60  delete rmcVector[i];
61  }
62  rmcVector.resize(m_ii, 0);
63  for (size_t i = 0; i < m_ii; i++) {
64  if (right.rmcVector[i]) {
65  rmcVector[i] = new RxnMolChange(*(right.rmcVector[i]));
66  }
67  }
68 
69  return *this;
70 }
71 //============================================================================================================================
73 {
74  return cInterfaceKinetics;
75 }
76 //============================================================================================================================
77 Kinetics* ElectrodeKinetics::duplMyselfAsKinetics(const std::vector<thermo_t*> & tpVector) const
78 {
79  ElectrodeKinetics* iK = new ElectrodeKinetics(*this);
80  iK->assignShallowPointers(tpVector);
81  return iK;
82 }
83 //============================================================================================================================
84 // Identify the metal phase and the electron species
86 {
90  size_t np = nPhases();
91  //
92  // Identify the metal phase as the phase with the electron species (element index of 1 for element E
93  // Should probably also stipulate a charge of -1.
94  //
95  for (size_t iph = 0; iph < np; iph++) {
96  ThermoPhase* tp = m_thermo[iph];
97  size_t nSpecies = tp->nSpecies();
98  size_t nElements = tp->nElements();
99  size_t eElectron = tp->elementIndex("E");
100  if (eElectron != npos) {
101  for (size_t k = 0; k < nSpecies; k++) {
102  if (tp->nAtoms(k,eElectron) == 1) {
103  int ifound = 1;
104  for (size_t e = 0; e < nElements; e++) {
105  if (tp->nAtoms(k,e) != 0.0) {
106  if (e != eElectron) {
107  ifound = 0;
108  }
109  }
110  }
111  if (ifound == 1) {
112  metalPhaseIndex_ = iph;
113  kElectronIndex_ = m_start[iph] + k;
114  }
115  }
116  }
117  }
118  //
119  // Identify the solution phase as a 3D phase, with nonzero phase charge change
120  // in at least one reaction
121  //
122  /*
123  * Haven't filled in reactions yet when this is called, unlike previous treatment.
124  if (iph != metalPhaseIndex_) {
125  for (size_t i = 0; i < m_ii; i++) {
126  RxnMolChange* rmc = rmcVector[i];
127  if (rmc->m_phaseChargeChange[iph] != 0) {
128  if (rmc->m_phaseDims[iph] == 3) {
129  solnPhaseIndex_ = iph;
130  break;
131  }
132  }
133  }
134  }
135  */
136  //
137  // New method is to find the first multispecies 3D phase with charged species as the solution phase
138  //
139  if (iph != metalPhaseIndex_) {
140  ThermoPhase& tp =*( m_thermo[iph]);
141  size_t nsp = tp.nSpecies();
142  size_t nd = tp.nDim();
143  if (nd == 3 && nsp > 1) {
144  for (size_t k = 0; k < nsp; k++) {
145  if (tp.charge(k) != 0.0) {
146  solnPhaseIndex_ = iph;
147  string ss = tp.name();
148  // cout << "solution phase = "<< ss << endl;
149  break;
150  }
151  }
152  }
153  }
154 
155  }
156  //
157  // Right now, if we don't find an electron phase, we will not error exit. Some functions will
158  // be turned off and the object will behave as an InterfaceKinetics object. This is needed
159  // because downstream electrode objects have internal reaction surfaces that don't have
160  // electrons.
161  //
162  /*
163  if (metalPhaseIndex_ == npos) {
164  throw CanteraError("ElectrodeKinetics::identifyMetalPhase()",
165  "Can't find electron phase -> treating this as an error right now");
166  }
167  if (solnPhaseIndex_ == npos) {
168  throw CanteraError("ElectrodeKinetics::identifyMetalPhase()",
169  "Can't find solution phase -> treating this as an error right now");
170  }
171  */
172 }
173 //============================================================================================================================
174 // virtual from InterfaceKinetics
176 {
177  // evaluate rate constants and equilibrium constants at temperature and phi (electric potential)
178  _update_rates_T();
179  // get updated activities (rates updated below)
180  _update_rates_C();
181 
182  double TT = m_surf->temperature();
183  double rtdf = GasConstant * TT / Faraday;
184 
185  if (m_ROP_ok) {
186  return;
187  }
188  //
189  // Copy the reaction rate coefficients, m_rfn, into m_ropf
190  //
191  copy(m_rfn.begin(), m_rfn.end(), m_ropf.begin());
192  //
193  // Multiply by the perturbation factor
194  //
195  multiply_each(m_ropf.begin(), m_ropf.end(), m_perturb.begin());
196  //
197  // Copy the forward rate constants to the reverse rate constants
198  //
199  copy(m_ropf.begin(), m_ropf.end(), m_ropr.begin());
200 
201 
202  //
203  // For reverse rates computed from thermochemistry, multiply
204  // the forward rates copied into m_ropr by the reciprocals of
205  // the equilibrium constants
206  //
207  multiply_each(m_ropr.begin(), m_ropr.end(), m_rkcn.begin());
208  //
209  // multiply ropf by the activity concentration reaction orders to obtain
210  // the forward rates of progress.
211  //
213  //
214  // For reversible reactions, multiply ropr by the activity concentration products
215  //
217  //
218  // Fix up these calculations for cases where the above formalism doesn't hold
219  //
220  double OCV = 0.0;
221  for (size_t iBeta = 0; iBeta < m_beta.size(); iBeta++) {
222  size_t irxn = m_ctrxn[iBeta];
223 
224  int reactionType = m_rxntype[irxn];
225  if (reactionType == BUTLERVOLMER_RXN) {
226  //
227  // Get the beta value
228  //
229  double beta = m_beta[iBeta];
230  //
231  // OK, the reaction rate constant contains the current density rate constant calculation
232  // the rxnstoich calculation contained the dependence of the current density on the activity concentrations
233  // We finish up with the ROP calculation
234  //
235  int iECDFormulation = m_ctrxn_ecdf[iBeta];
236  if (iECDFormulation == 0) {
237  throw CanteraError(" ElectrodeKinetics::updateROP()",
238  "Straight kfwrd with BUTLERVOLMER_RXN not handled yet");
239  }
240  //
241  // Get the phase mole change structure
242  //
243  RxnMolChange* rmc = rmcVector[irxn];
244  //
245  // Calculate the stoichiometric eletrons for the reaction
246  // This is the number of electrons that are the net products of the reaction
247  //
248  AssertThrow(metalPhaseIndex_ != npos, "ElectrodeKinetics::updateROP()");
249 
250  double nStoichElectrons = - rmc->m_phaseChargeChange[metalPhaseIndex_];
251  //
252  // Calculate the open circuit voltage of the reaction
253  //
254  getDeltaGibbs(0);
255  if (nStoichElectrons != 0.0) {
256  OCV = m_deltaG[irxn]/Faraday/ nStoichElectrons;
257  } else {
258  OCV = 0.0;
259  }
260  //
261  // Calculate the voltage of the electrode.
262  //
263  double voltage = m_phi[metalPhaseIndex_] - m_phi[solnPhaseIndex_];
264  //
265  // Calculate the overpotential
266  //
267  double nu = voltage - OCV;
268 
269  //
270  // Find the product of the standard concentrations for ROP orders that we used above
271  //
272  const RxnOrders* ro_rop = m_ctrxn_ROPOrdersList_[iBeta];
273  if (ro_rop == 0) {
274  throw CanteraError("ElectrodeKinetics::", "ROP orders pointer is zero ?!?");
275  }
276  double tmp2 = 1.0;
277  const std::vector<size_t>& kinSpeciesIDs = ro_rop->kinSpeciesIDs_;
278  const std::vector<doublereal>& kinSpeciesOrders = ro_rop->kinSpeciesOrders_;
279  for (size_t j = 0; j < kinSpeciesIDs.size(); j++) {
280  size_t k = kinSpeciesIDs[j];
281  double oo = kinSpeciesOrders[j];
282  tmp2 *= pow(m_StandardConc[k], oo);
283  }
284  //
285  // Now have to divide this to get rid of standard concentrations. We should
286  // have used just the activities in the m_rxnstoich.multiplyReactants(DATA_PTR(m_actConc), DATA_PTR(m_ropf));
287  // calculation above!
288  // That is because the exchange current density rate constants have the correct units in the first place.
289  //
290  m_ropf[irxn] /= tmp2;
291  //
292  // Calculate the exchange current density
293  // m_ropf contains the exchange current reaction rate
294  //
295  double ioc = m_ropf[irxn] * nStoichElectrons;
296  //
297  // Add in the film resistance here
298  //
299  double resist = m_ctrxn_resistivity_[iBeta];
300  double exp1 = nu * nStoichElectrons * beta / rtdf;
301  double exp2 = - nu * nStoichElectrons * (1.0 - beta) / (rtdf);
302  double io = ioc * (exp(exp1) - exp(exp2));
303 
304  if (resist != 0.0) {
305  io = solveCurrentRes(nu, nStoichElectrons, ioc, beta, TT, resist, 0);
306  }
307 
308  m_ropnet[irxn] = io / (Faraday * nStoichElectrons);
309  //
310  // Need to resurrect the forwards rate of progress -> there is some need to
311  // calculate each direction individually
312  //
313  m_ropf[irxn] = calcForwardROP_BV(irxn, iBeta, ioc, nStoichElectrons, nu, io);
314  //
315  // Calculate the reverse rate of progress from the difference
316  //
317  m_ropr[irxn] = m_ropf[irxn] - m_ropnet[irxn];
318 
319  } else if (reactionType == BUTLERVOLMER_NOACTIVITYCOEFFS_RXN) {
320  //
321  // Get the beta value
322  //
323  double beta = m_beta[iBeta];
324  //
325  // OK, the reaction rate constant contains the current density rate constant calculation
326  // the rxnstoich calculation contained the dependence of the current density on the activity concentrations
327  // We finish up with the ROP calculation
328  //
329  int iECDFormulation = m_ctrxn_ecdf[iBeta];
330  if (iECDFormulation == 0) {
331  throw CanteraError("ElectrodeKinetics::updateROP()",
332  "Straight kfwrd with BUTLERVOLMER_NOACTIVITYCOEFFS_RXN not handled yet");
333  }
334  //
335  // Get the phase mole change structure
336  //
337  RxnMolChange* rmc = rmcVector[irxn];
338  //
339  // Calculate the stoichiometric eletrons for the reaction
340  // This is the number of electrons that are the net products of the reaction
341  //
342  double nStoichElectrons = - rmc->m_phaseChargeChange[metalPhaseIndex_];
343  //
344  // Calculate the open circuit voltage of the reaction
345  //
346  getDeltaGibbs(0);
347  if (nStoichElectrons != 0.0) {
348  OCV = m_deltaG[irxn]/Faraday/ nStoichElectrons;
349  } else {
350  OCV = 0.0;
351  }
352 
353  //
354  // Calculate the voltage of the electrode.
355  //
356  double voltage = m_phi[metalPhaseIndex_] - m_phi[solnPhaseIndex_];
357  //
358  // Calculate the overpotential
359  //
360  double nu = voltage - OCV;
361  //
362  // Unfortunately, we really need to recalculate everything from almost scratch
363  // for this case, since it widely diverges from the thermo norm.
364  //
365  // Start with the exchange current reaction rate constant, which should
366  // be located in m_rfn[].
367  //
368  double ioc = m_rfn[irxn] * nStoichElectrons * m_perturb[irxn];
369  //
370  // Now we need th mole fraction vector and we need the RxnOrders vector.
371  //
372  const RxnOrders* ro_fwd = m_ctrxn_ROPOrdersList_[iBeta];
373  if (ro_fwd == 0) {
374  throw CanteraError("ElectrodeKinetics::calcForwardROP_BV()", "forward orders pointer is zero ?!?");
375  }
376  double tmp = 1.0;
377  double mfS = 0.0;
378  const std::vector<size_t>& kinSpeciesIDs = ro_fwd->kinSpeciesIDs_;
379  const std::vector<doublereal>& kinSpeciesOrders = ro_fwd->kinSpeciesOrders_;
380  for (size_t j = 0; j < kinSpeciesIDs.size(); j++) {
381  size_t ks = kinSpeciesIDs[j];
382  thermo_t& th = speciesPhase(ks);
383  size_t n = speciesPhaseIndex(ks);
384  size_t klocal = ks - m_start[n];
385  mfS = th.moleFraction(klocal);
386 
387  double oo = kinSpeciesOrders[j];
388  tmp *= pow(mfS, oo);
389  }
390  ioc *= tmp;
391  //
392  // Add in the film resistance here, later
393  //
394  double resist = m_ctrxn_resistivity_[iBeta];
395  double exp1 = nu * nStoichElectrons * beta / rtdf;
396  double exp2 = - nu * nStoichElectrons * (1.0 - beta) / (rtdf);
397  double io = ioc * (exp(exp1) - exp(exp2));
398  if (resist != 0.0) {
399  io = solveCurrentRes(nu, nStoichElectrons, ioc, beta, TT, resist, 0);
400  }
401 
402  m_ropnet[irxn] = io / (Faraday * nStoichElectrons);
403  //
404  // Need to resurrect the forwards rate of progress -> there is some need to
405  // calculate each direction individually
406  //
407  m_ropf[irxn] = calcForwardROP_BV_NoAct(irxn, iBeta, ioc, nStoichElectrons, nu, io);
408  //
409  // Calculate the reverse rate of progress from the difference
410  //
411  m_ropr[irxn] = m_ropf[irxn] - m_ropnet[irxn];
412  }
413 
414  }
415 
416 
417 
418  for (size_t j = 0; j != m_ii; ++j) {
419  m_ropnet[j] = m_ropf[j] - m_ropr[j];
420  }
421 
422  /*
423  * For reactions involving multiple phases, we must check that the phase
424  * being consumed actually exists. This is particularly important for
425  * phases that are stoichiometric phases containing one species with a unity activity
426  */
427  if (m_phaseExistsCheck) {
428  for (size_t j = 0; j != m_ii; ++j) {
429  if ((m_ropr[j] > m_ropf[j]) && (m_ropr[j] > 0.0)) {
430  for (size_t p = 0; p < nPhases(); p++) {
431  if (m_rxnPhaseIsProduct[j][p]) {
432  if (! m_phaseExists[p]) {
433  m_ropnet[j] = 0.0;
434  m_ropr[j] = m_ropf[j];
435  if (m_ropf[j] > 0.0) {
436  for (size_t rp = 0; rp < nPhases(); rp++) {
437  if (m_rxnPhaseIsReactant[j][rp]) {
438  if (! m_phaseExists[rp]) {
439  m_ropnet[j] = 0.0;
440  m_ropr[j] = m_ropf[j] = 0.0;
441  }
442  }
443  }
444  }
445  }
446  }
447  if (m_rxnPhaseIsReactant[j][p]) {
448  if (! m_phaseIsStable[p]) {
449  m_ropnet[j] = 0.0;
450  m_ropr[j] = m_ropf[j];
451  }
452  }
453  }
454  } else if ((m_ropf[j] > m_ropr[j]) && (m_ropf[j] > 0.0)) {
455  for (size_t p = 0; p < nPhases(); p++) {
456  if (m_rxnPhaseIsReactant[j][p]) {
457  if (! m_phaseExists[p]) {
458  m_ropnet[j] = 0.0;
459  m_ropf[j] = m_ropr[j];
460  if (m_ropf[j] > 0.0) {
461  for (size_t rp = 0; rp < nPhases(); rp++) {
462  if (m_rxnPhaseIsProduct[j][rp]) {
463  if (! m_phaseExists[rp]) {
464  m_ropnet[j] = 0.0;
465  m_ropf[j] = m_ropr[j] = 0.0;
466  }
467  }
468  }
469  }
470  }
471  }
472  if (m_rxnPhaseIsProduct[j][p]) {
473  if (! m_phaseIsStable[p]) {
474  m_ropnet[j] = 0.0;
475  m_ropf[j] = m_ropr[j];
476  }
477  }
478  }
479  }
480  }
481  }
482 
483  m_ROP_ok = true;
484 }
485 //==================================================================================================================
486 //
487 // This version of takes the electrons out of the reaction rate expression
488 // (note: with proper specification of the phase, this shouldn't make a numerical difference (power of 1).
489 // But it certainly is a complication and unneeded work)
490 // (TODO: probably can take stoichiometric solids out of the reaction order expression as well.
491 // They all contribute powers of 1 as well)
492 //
493 void ElectrodeKinetics::determineFwdOrdersBV(ReactionData& rdata, std::vector<doublereal>& fwdFullorders)
494 {
495  //
496  // Start out with the full ROP orders vector.
497  // This vector will have the BV exchange current density orders in it.
498  //
499  fwdFullorders = rdata.forwardFullOrder_;
500  //
501  // forward and reverse beta values
502  //
503  double betaf = rdata.beta;
504  //double betar = 1.0 - betaf;
505  //
506  // Loop over the reactants doing away the BV terms.
507  // This should leave the reactant terms only, even if they are non-mass action.
508  //
509  for (size_t j = 0; j < rdata.reactants.size(); j++) {
510  size_t kkin = rdata.reactants[j];
511  double oo = rdata.rstoich[j];
512  if (kkin != kElectronIndex_) {
513  fwdFullorders[kkin] += betaf * oo;
514  if (abs(fwdFullorders[kkin]) < 0.00001) {
515  fwdFullorders[kkin] = 0.0;
516  }
517  } else {
518  fwdFullorders[kkin] = 0.0;
519  }
520  }
521  for (size_t j = 0; j < rdata.products.size(); j++) {
522  size_t kkin = rdata.products[j];
523  double oo = rdata.pstoich[j];
524  if (kkin != kElectronIndex_) {
525  fwdFullorders[kkin] -= betaf * oo;
526  if (abs(fwdFullorders[kkin]) < 0.00001) {
527  fwdFullorders[kkin] = 0.0;
528  }
529  } else {
530  fwdFullorders[kkin] = 0.0;
531  }
532  }
533 }
534 //==================================================================================================================
535 //
536 // When the BV form is used we still need to go backwards to calculate the forward rate of progress.
537 // This routine does that
538 //
539 double ElectrodeKinetics::calcForwardROP_BV(size_t irxn, size_t iBeta, double ioc, double nStoich, double nu, doublereal ioNet)
540 {
541  double ropf;
542  doublereal rt = GasConstant * thermo(0).temperature();
543  //
544  // Calculate gather the exchange current reaction rate constant (where does n_s appear?)
545  //
546  doublereal beta = m_beta[iBeta];
547 
548 #ifdef DEBUG_MODE
549  //
550  // Determine whether the reaction rate constant is in an exchange current density formulation format.
551  //
552  int iECDFormulation = m_ctrxn_ecdf[iBeta];
553 
554  if (!iECDFormulation) {
555  throw CanteraError("ElectrodeKinetics::calcForwardROP_BV", "not handled yet");
556  }
557  //
558  // Calculate the forward chemical and modify the forward reaction rate coefficient
559  //
560  const RxnOrders* ro_fwd = m_ctrxn_FwdOrdersList_[iBeta];
561  if (ro_fwd == 0) {
562  throw CanteraError("ElectrodeKinetics::calcForwardROP_BV()", "forward orders pointer is zero ?!?");
563  }
564  double tmp = exp(- m_beta[iBeta] * m_deltaG0[irxn] / rt);
565  double tmp2 = 1.0;
566  const std::vector<size_t>& kinSpeciesIDs = ro_fwd->kinSpeciesIDs_;
567  const std::vector<doublereal>& kinSpeciesOrders = ro_fwd->kinSpeciesOrders_;
568  for (size_t j = 0; j < kinSpeciesIDs.size(); j++) {
569  size_t k = kinSpeciesIDs[j];
570  double oo = kinSpeciesOrders[j];
571  tmp2 *= pow(m_StandardConc[k], oo);
572  }
573 
574  //double tmp2 = m_ProdStanConcReac[irxn];
575  tmp *= 1.0 / tmp2 / Faraday;
576  //
577  // Calculate the chemical reaction rate constant
578  //
579  double iorc = m_rfn[irxn] * m_perturb[irxn];
580  double kf = iorc * tmp;
581  //
582  // Calculate the electrochemical factor
583  //
584  double eamod = m_beta[iBeta] * deltaElectricEnergy_[irxn];
585  kf *= exp(- eamod / rt);
586  //
587  // Calculate the forward rate of progress
588  // -> get the pointer for the orders
589  //
590  tmp = 1.0;
591 
592  for (size_t j = 0; j < kinSpeciesIDs.size(); j++) {
593  size_t k = kinSpeciesIDs[j];
594  double oo = kinSpeciesOrders[j];
595  tmp *= pow(m_actConc[k], oo);
596  }
597  ropf = kf * tmp;
598 #endif
599  //
600  // Now calculate ropf in a separate but equivalent way.
601  // totally equivalent way if resistivity is zero, should be equal (HKM -> Proved exactly in one case)
602  //
603  double iof = ioc;
604  double resistivity = m_ctrxn_resistivity_[iBeta];
605  if (fabs(resistivity * ioNet) > fabs(nu)) {
606  ioNet = nu / resistivity;
607  }
608  if (nStoich > 0.0) {
609  double exp1 = nStoich * Faraday * beta * (nu - resistivity * ioNet)/ (rt);
610  iof *= exp(exp1);
611  } else {
612 #ifdef DEBUG_MODE
613  if (ioc > 0) {
614  throw CanteraError("ElectrodeKinetics::calcForwardROP_BV", "ioc should be less than zero here");
615  }
616 #endif
617  double exp2 = -nu * nStoich * Faraday * (1.0 - beta) / (rt);
618  iof = ioc * ( - exp(exp2));
619  }
620  ropf = iof / ( Faraday * nStoich);
621 
622  return ropf;
623 }
624 //==================================================================================================================
625 //
626 // When the BV form is used we still need to go backwards to calculate the forward rate of progress.
627 // This routine does that
628 //
629 double ElectrodeKinetics::calcForwardROP_BV_NoAct(size_t irxn, size_t iBeta, double ioc, double nStoich, double nu,
630  doublereal ioNet)
631 {
632  doublereal TT = thermo(0).temperature();
633  doublereal rt = GasConstant * TT;
634  //doublereal rrt = 1.0/rt;
635  doublereal beta = m_beta[iBeta];
636 
637 /*
638  //
639  // Calculate gather the exchange current reaction rate constant (where does n_s appear?)
640  //
641  double iorc = m_rfn[irxn] * m_perturb[irxn];
642  //
643  // Determine whether the reaction rate constant is in an exchange current density formulation format.
644  //
645  int iECDFormulation = m_ctrxn_ecdf[iBeta];
646 
647  if (!iECDFormulation) {
648  throw CanteraError("ElectrodeKinetics::calcForwardROP_BV_NoAct", "not handled yet");
649  }
650  //
651  // Calculate the forward chemical and modify the forward reaction rate coefficient
652  // (we don't use standard concentrations at all here);
653  //
654  double tmp = exp(- m_beta[iBeta] * m_deltaG0[irxn] * rrt);
655  double tmp2 = 1.0;
656  tmp *= 1.0 / tmp2 / Faraday;
657  //
658  // Calculate the chemical reaction rate constant
659  //
660  double kf = iorc * tmp;
661  //
662  // Calculate the electrochemical factor
663  //
664  double eamod = m_beta[iBeta] * deltaElectricEnergy_[irxn];
665  kf *= exp(- eamod * rrt);
666  //
667  // Calculate the forward rate of progress
668  // -> get the pointer for the orders
669  //
670  const RxnOrders* ro_fwd = m_ctrxn_FwdOrdersList_[iBeta];
671  if (ro_fwd == 0) {
672  throw CanteraError("ElectrodeKinetics::calcForwardROP_BV()", "forward orders pointer is zero ?!?");
673  }
674  tmp = 1.0;
675  const std::vector<size_t>& kinSpeciesIDs = ro_fwd->kinSpeciesIDs_;
676  const std::vector<doublereal>& kinSpeciesOrders = ro_fwd->kinSpeciesOrders_;
677  for (size_t j = 0; j < kinSpeciesIDs.size(); j++) {
678 
679  size_t ks = kinSpeciesIDs[j];
680  thermo_t& th = speciesPhase(ks);
681  size_t n = speciesPhaseIndex(ks);
682  size_t klocal = ks - m_start[n];
683  double mfS = th.moleFraction(klocal);
684  double oo = kinSpeciesOrders[j];
685  tmp *= pow(mfS, oo);
686  }
687  double ropf = kf * tmp;
688 */
689 /*
690  if (nStoich > 0) {
691  double ropf = ioc / ( Faraday * nStoich);
692  double exp1 = nu * nStoich * Faraday * beta / (rt);
693  ropf *= exp(exp1);
694  } else {
695  double ropf = ioc / ( Faraday * nStoich);
696  double exp1 = nu * nStoich * Faraday * beta / (rt);
697  ropf *= exp(exp1);
698  }
699 */
700  //
701  // With all of the thermo issues, I'm thinking this is the best we can do
702  // (it certainly maintains the forward and reverse rates of progress as being positive)
703  //
704  double iof = ioc;
705  double resistivity = m_ctrxn_resistivity_[iBeta];
706  if (fabs(resistivity * ioNet) > fabs(nu)) {
707  ioNet = nu / resistivity;
708  }
709  if (nStoich > 0) {
710  double exp1 = nStoich * Faraday * beta * (nu - resistivity * ioNet)/ (rt);
711  iof *= exp(exp1);
712  } else {
713 #ifdef DEBUG_MODE
714  if (ioc > 0) {
715  throw CanteraError("ElectrodeKinetics::calcForwardROP_BV_NoAct", "ioc should be less than zero here");
716  }
717 #endif
718  double exp2 = -nu * nStoich * Faraday * (1.0 - beta) / (rt);
719  iof = ioc * ( - exp(exp2));
720  }
721  double ropf = iof / ( Faraday * nStoich);
722  return ropf;
723 }
724 //==================================================================================================================
726 {
727  //
728  // Calculate deltaG for all reactions
729  //
730  getDeltaGibbs(0);
731  //
732  // Look up the net number of electrons that are products.
733  //
734  RxnMolChange* rmc = rmcVector[irxn];
735  double nStoichElectrons = - rmc->m_phaseChargeChange[metalPhaseIndex_];
736  double OCV = 0.0;
737  if (nStoichElectrons != 0.0) {
738  OCV = m_deltaG[irxn] / Faraday / nStoichElectrons;
739  }
740  return OCV;
741 }
742 //==================================================================================================================
743 //
744 // Returns the local exchange current density formulation parameters
745 //
746 bool ElectrodeKinetics::
747 getExchangeCurrentDensityFormulation(size_t irxn,
748  doublereal& nStoichElectrons, doublereal& OCV, doublereal& io,
749  doublereal& overPotential, doublereal& beta,
750  doublereal& resistivity)
751 {
752  size_t iBeta = npos;
753  beta = 0.0;
754  //
755  // Add logic to handle other reaction types -> return 0 if formulation isn't compatible
756  //
757 
758  // evaluate rate constants and equilibrium constants at temperature and phi (electric potential)
759  _update_rates_T();
760  // get updated activities (rates updated below)
761  _update_rates_C();
762 
764 
765  RxnMolChange* rmc = rmcVector[irxn];
766  // could also get this from reactant and product stoichiometry, maybe
767  if (metalPhaseIndex_ == npos) {
768  nStoichElectrons = 0;
769  OCV = 0.0;
770  return false;
771  } else {
772  nStoichElectrons = - rmc->m_phaseChargeChange[metalPhaseIndex_];
773  }
774 
775 
776  getDeltaGibbs(0);
777 
778  if (nStoichElectrons != 0.0) {
779  OCV = m_deltaG[irxn] / Faraday / nStoichElectrons;
780  }
781 
782  for (size_t i = 0; i < m_ctrxn.size(); i++) {
783  if (m_ctrxn[i] == irxn) {
784  iBeta = i;
785  break;
786  }
787  }
788  beta = m_beta[iBeta];
789 
790  doublereal rt = GasConstant*thermo(0).temperature();
791 
792 
793  double mG0 = m_deltaG0[irxn];
794  int reactionType = m_rxntype[irxn];
795 
796  //
797  // Start with the forward reaction rate
798  //
799  double iO = m_rfn[irxn] * m_perturb[irxn];
800  int iECDFormulation = m_ctrxn_ecdf[iBeta];
801  if (! iECDFormulation) {
802  iO = m_rfn[irxn] * Faraday * nStoichElectrons;
803  if (beta > 0.0) {
804  double fac = exp(mG0 / (rt));
805  iO *= pow(fac, beta);
806  // Need this step because m_rfn includes the inverse of this term, while the formulas
807  // only use the chemical reaction rate constant.
808  fac = exp( beta * deltaElectricEnergy_[irxn] / (rt));
809  iO *= fac;
810  }
811  } else {
812  iO *= nStoichElectrons;
813  }
814 
815  double omb = 1.0 - beta;
816  if (reactionType == BUTLERVOLMER_NOACTIVITYCOEFFS_RXN) {
817  const RxnOrders* ro_fwd = m_ctrxn_ROPOrdersList_[iBeta];
818  if (ro_fwd == 0) {
819  throw CanteraError("ElectrodeKinetics::calcForwardROP_BV()", "forward orders pointer is zero ?!?");
820  }
821  double tmp = 1.0;
822  const std::vector<size_t>& kinSpeciesIDs = ro_fwd->kinSpeciesIDs_;
823  const std::vector<doublereal>& kinSpeciesOrders = ro_fwd->kinSpeciesOrders_;
824  for (size_t j = 0; j < kinSpeciesIDs.size(); j++) {
825  size_t ks = kinSpeciesIDs[j];
826  thermo_t& th = speciesPhase(ks);
827  size_t n = speciesPhaseIndex(ks);
828  size_t klocal = ks - m_start[n];
829  double mfS = th.moleFraction(klocal);
830 
831  double oo = kinSpeciesOrders[j];
832  tmp *= pow(mfS, oo);
833  }
834  iO *= tmp;
835  } else if (reactionType == BUTLERVOLMER_RXN) {
836  const RxnOrders* ro_fwd = m_ctrxn_ROPOrdersList_[iBeta];
837  if (ro_fwd == 0) {
838  throw CanteraError("ElectrodeKinetics::calcForwardROP_BV()", "forward orders pointer is zero ?!?");
839  }
840  double tmp = 1.0;
841  const std::vector<size_t>& kinSpeciesIDs = ro_fwd->kinSpeciesIDs_;
842  const std::vector<doublereal>& kinSpeciesOrders = ro_fwd->kinSpeciesOrders_;
843  for (size_t j = 0; j < kinSpeciesIDs.size(); j++) {
844  size_t ks = kinSpeciesIDs[j];
845 
846  double oo = kinSpeciesOrders[j];
847  tmp *= pow((m_actConc[ks]/m_StandardConc[ks]), oo);
848  }
849  iO *= tmp;
850  } else {
851  for (size_t k = 0; k < m_kk; k++) {
852  doublereal reactCoeff = reactantStoichCoeff(k, irxn);
853  doublereal prodCoeff = productStoichCoeff(k, irxn);
854 
855  if (reactCoeff != 0.0) {
856  iO *= pow(m_actConc[k], reactCoeff*omb);
857  iO *= pow(m_StandardConc[k], reactCoeff*beta);
858  }
859  if (prodCoeff != 0.0) {
860  iO *= pow(m_actConc[k], prodCoeff*beta);
861  iO /= pow(m_StandardConc[k], prodCoeff*omb);
862  }
863  }
864  }
865  io = iO;
866  resistivity = m_ctrxn_resistivity_[iBeta];
867 
868  double phiMetal = m_thermo[metalPhaseIndex_]->electricPotential();
869  double phiSoln = m_thermo[solnPhaseIndex_]->electricPotential();
870  double E = phiMetal - phiSoln;
871  overPotential = E - OCV;
872 
873  return true;
874 }
875 //====================================================================================================================
876 double ElectrodeKinetics::calcCurrentDensity(double nu, double nStoich, double ioc, double beta, double temp,
877  doublereal resistivity) const
878 {
879  double exp1 = nu * nStoich * Faraday * beta / (GasConstant * temp);
880  double exp2 = -nu * nStoich * Faraday * (1.0 - beta) / (GasConstant * temp);
881  double val = ioc * (exp(exp1) - exp(exp2));
882  if (resistivity > 0.0) {
883  val = solveCurrentRes(nu, nStoich, ioc, beta, temp, resistivity, 0);
884  }
885  return val;
886 }
887 //==================================================================================================================
889 {
892 }
893 
895 {
897  // Malloc and calculate all of the quantities that go into the extra description of reactions
898  rmcVector.resize(m_ii, 0);
899  for (size_t i = 0; i < m_ii; i++) {
900  rmcVector[i] = new RxnMolChange(this, static_cast<int>(i));
901  }
902 }
903 
904 //==================================================================================================================
905 
906 double ElectrodeKinetics::solveCurrentRes(double nu, double nStoich, doublereal ioc, doublereal beta, doublereal temp,
907  doublereal resistivity, int iprob) const
908 {
909  // int nits = 0;
910  doublereal f, dfdi, deltai, eexp1, eexp2, exp1, exp2, icurr, deltai_damp;
911  doublereal nFRT = nStoich * Faraday / (GasConstant * temp);
912  if (iprob == 0) {
913  eexp1 = exp(nu * nFRT * beta);
914  eexp2 = exp(-nu * nFRT * (1.0 - beta)) ;
915 
916  } else {
917  eexp1 = exp(nu * nFRT * beta);
918  eexp2 = 0.0;
919  }
920  icurr = ioc * (eexp1 - eexp2);
921  double icurrDamp = icurr;
922  if (fabs(resistivity * icurr) > 0.9 * fabs(nu)) {
923  icurrDamp = 0.9 * nu / resistivity;
924  }
925  if (iprob == 0) {
926  eexp1 = exp( nFRT * beta * (nu - resistivity * icurrDamp));
927  eexp2 = exp(- nFRT * (1.0 - beta) * (nu - resistivity * icurrDamp));
928  } else {
929  eexp1 = exp( nFRT * beta * (nu - resistivity * icurrDamp));
930  eexp2 = 0.0;
931  }
932  icurr = ioc * (eexp1 - eexp2);
933  if (fabs(resistivity * icurr) > 0.99 * fabs(nu)) {
934  icurr = 0.99 * nu / resistivity;
935  }
936 
937  do {
938  // nits++;
939  if (iprob == 0) {
940  exp1 = nFRT * beta * (nu - resistivity * icurr);
941  exp2 = - nFRT * (1.0 - beta) * (nu - resistivity * icurr);
942  eexp1 = exp(exp1);
943  eexp2 = exp(exp2);
944  f = icurr - ioc * (eexp1 - eexp2);
945  dfdi = 1.0 - ioc * eexp1 * ( - beta * nFRT * resistivity ) +
946  ioc * eexp2 * ( (1.0 - beta) * nFRT * resistivity );
947  } else {
948  exp1 = nFRT * beta * (nu - resistivity * icurr);
949  eexp1 = exp(exp1);
950  f = icurr - ioc * (eexp1);
951  dfdi = 1.0 - ioc * eexp1 * ( - beta * nFRT * resistivity );
952  }
953  deltai = - f / dfdi;
954  if (fabs(deltai) > 0.1 * fabs(icurr)) {
955  deltai_damp = 0.1 * deltai;
956  if (fabs(deltai_damp) > 0.1 * fabs(icurr)) {
957  deltai_damp = 0.1 * icurr * (deltai_damp / fabs(deltai_damp));
958  }
959  } else if (fabs(deltai) > 0.01 * fabs(icurr)) {
960  deltai_damp = 0.3 * deltai;
961  } else if (fabs(deltai) > 0.001 * fabs(icurr)) {
962  deltai_damp = 0.5 * deltai;
963  } else {
964  deltai_damp = deltai;
965  }
966  icurr += deltai_damp;
967  if (fabs(resistivity * icurr) > fabs(nu)) {
968  icurr = 0.999 * nu / resistivity;
969  }
970 
971  } while((fabs(deltai/icurr)> 1.0E-14) && (fabs(deltai) > 1.0E-20));
972 
973  // printf(" its = %d\n", nits);
974 
975  return icurr;
976 }
977 //==================================================================================================================
978 }
doublereal nAtoms(size_t k, size_t m) const
Number of atoms of element m in species k.
Definition: Phase.cpp:243
StoichManagerN m_revProductStoich
Stoichiometry manager for the products of reversible reactions.
Definition: Kinetics.h:971
vector_fp m_deltaG0
Vector of delta G^0, the standard state Gibbs free energies for each reaction.
vector_fp m_ropr
Reverse rate-of-progress for each reaction.
Definition: Kinetics.h:1107
std::vector< thermo_t * > m_thermo
m_thermo is a vector of pointers to ThermoPhase objects that are involved with this kinetics operator...
Definition: Kinetics.h:1056
size_t metalPhaseIndex_
Index of the metal phase in the list of phases for this kinetics object. This is the electron phase...
A kinetics manager for heterogeneous reaction mechanisms.
Header for a simple thermodynamics model of a surface phase derived from ThermoPhase, assuming an ideal solution model (see Thermodynamic Properties and class SurfPhase).
size_t nElements() const
Number of elements.
Definition: Phase.cpp:167
virtual void assignShallowPointers(const std::vector< thermo_t * > &tpVector)
Reassign the pointers within the Kinetics object.
Definition: Kinetics.cpp:140
Various templated functions that carry out common vector operations (see Templated Utility Functions)...
thermo_t & thermo(size_t n=0)
This method returns a reference to the nth ThermoPhase object defined in this kinetics mechanism...
Definition: Kinetics.h:285
vector_fp forwardFullOrder_
Reaction order for the forward direction of the reaction.
Definition: ReactionData.h:87
size_t m_kk
The number of species in all of the phases that participate in this kinetics mechanism.
Definition: Kinetics.h:982
SurfPhase * m_surf
Pointer to the single surface phase.
virtual void finalize()
Finish adding reactions and prepare for use.
std::vector< size_t > m_start
m_start is a vector of integers specifying the beginning position for the species vector for the n'th...
Definition: Kinetics.h:1063
const size_t npos
index returned by functions to indicate "no position"
Definition: ct_defs.h:165
vector_fp m_phi
Vector of phase electric potentials.
doublereal beta
Forward value of the apparent Electrochemical transfer coefficient.
Definition: ReactionData.h:201
vector_fp pstoich
Product stoichiometric coefficients, in the order given by products.
Definition: ReactionData.h:101
vector_fp m_deltaG
Vector of deltaG[] of reaction, the delta Gibbs free energies for each reaction.
vector_fp m_beta
Electrochemical transfer coefficient for the forward direction.
std::vector< size_t > kinSpeciesIDs_
ID's of the kinetic species.
virtual int type() const
Identifies the kinetics manager type.
void warn_deprecated(const std::string &method, const std::string &extra)
Print a warning indicating that method is deprecated.
Definition: global.cpp:78
std::vector< std::vector< bool > > m_rxnPhaseIsProduct
Vector of vector of booleans indicating whether a phase participates in a reaction as a product...
vector_fp m_StandardConc
Vector of standard concentrations.
void identifyMetalPhase()
Identify the metal phase and the electrons species.
vector_fp m_ropnet
Net rate-of-progress for each reaction.
Definition: Kinetics.h:1110
This file contains definitions for utility functions and text for modules, inputfiles, logs, textlogs, (see Input File Handling, Diagnostic Output, and Writing messages to the screen).
std::vector< RxnMolChange * > rmcVector
Vector of additional information about each reaction.
forward orders
std::string name() const
Return the name of the phase.
Definition: Phase.cpp:157
void multiply_each(OutputIter x_begin, OutputIter x_end, InputIter y_begin)
Multiply each entry in x by the corresponding entry in y.
Definition: utilities.h:234
int m_phaseExistsCheck
Int flag to indicate that some phases in the kinetics mechanism are non-existent. ...
virtual void finalize()
Finish adding reactions and prepare for use.
std::vector< double > m_phaseChargeChange
Vector of mass changes for each phase in the Kinetics object due to the current reaction.
Definition: RxnMolChange.h:75
vector_fp deltaElectricEnergy_
Storage for the net electric energy change due to reaction.
ThermoPhase thermo_t
typedef for the ThermoPhase class
Definition: ThermoPhase.h:1660
vector_fp m_ropf
Forward rate-of-progress for each reaction.
Definition: Kinetics.h:1104
Base class for a phase with thermodynamic properties.
Definition: ThermoPhase.h:97
std::vector< size_t > products
Indices of product species.
Definition: ReactionData.h:64
virtual Kinetics * duplMyselfAsKinetics(const std::vector< thermo_t * > &tpVector) const
Duplication function.
vector_int m_ctrxn_ecdf
Vector of booleans indicating whether the charge transfer reaction rate constant is described by an e...
virtual void getDeltaGibbs(doublereal *deltaG)
Return the vector of values for the reaction Gibbs free energy change.
std::vector< RxnOrders * > m_ctrxn_ROPOrdersList_
Vector of booleans indicating whether the charge transfer reaction rate constant is described by an e...
virtual void init()
Prepare the class for the addition of reactions.
vector_fp kinSpeciesOrders_
Orders of the kinetic species.
size_t nPhases() const
The number of phases participating in the reaction mechanism.
Definition: Kinetics.h:225
A kinetics manager for heterogeneous reaction mechanisms.
double openCircuitVoltage(size_t irxn)
Calculate the open circuit voltage of a given reaction.
ElectrodeKinetics(thermo_t *thermo=0)
Constructor.
std::vector< bool > m_phaseExists
Vector of booleans indicating whether phases exist or not.
vector_fp m_rfn
Forward rate constant for each reaction.
Definition: Kinetics.h:1098
StoichManagerN m_reactantStoich
Stoichiometry manager for the reactants for each reaction.
Definition: Kinetics.h:968
thermo_t & speciesPhase(const std::string &nm)
This function looks up the name of a species and returns a reference to the ThermoPhase object of the...
Definition: Kinetics.cpp:402
void _update_rates_T()
Update properties that depend on temperature.
vector_fp m_actConc
Array of activity concentrations for each species in the kinetics object.
vector_fp m_perturb
Vector of perturbation factors for each reaction's rate of progress vector.
Definition: Kinetics.h:986
Public interface for kinetics managers.
Definition: Kinetics.h:128
void _update_rates_C()
Update properties that depend on the species mole fractions and/or concentration,.
Intermediate class which stores data about a reaction and its rate parameterization before adding the...
Definition: ReactionData.h:22
#define AssertThrow(expr, procedure)
Assertion must be true or an error is thrown.
Definition: ctexceptions.h:283
Base class for exceptions thrown by Cantera classes.
Definition: ctexceptions.h:99
virtual ~ElectrodeKinetics()
Destructor.
std::vector< std::vector< bool > > m_rxnPhaseIsReactant
Vector of vector of booleans indicating whether a phase participates in a reaction as a reactant...
vector_fp m_rkcn
Reciprocal of the equilibrium constant in concentration units.
Definition: Kinetics.h:1101
std::vector< size_t > reactants
Indices of reactant species.
Definition: ReactionData.h:63
vector_fp rstoich
Reactant stoichiometric coefficients, in the order given by reactants.
Definition: ReactionData.h:94
size_t nSpecies() const
Returns the number of species in the phase.
Definition: Phase.h:265
ElectrodeKinetics & operator=(const ElectrodeKinetics &right)
Assignment operator.
virtual void init()
Prepare the class for the addition of reactions.
doublereal moleFraction(size_t k) const
Return the mole fraction of a single species.
Definition: Phase.cpp:561
doublereal temperature() const
Temperature (K).
Definition: Phase.h:602
size_t nDim() const
Returns the number of spatial dimensions (1, 2, or 3)
Definition: Phase.h:586
void updateExchangeCurrentQuantities()
values needed to convert from exchange current density to surface reaction rate.
size_t elementIndex(const std::string &name) const
Return the index of element named 'name'.
Definition: Phase.cpp:192
size_t speciesPhaseIndex(size_t k)
This function takes as an argument the kineticsSpecies index (i.e., the list index in the list of spe...
Definition: Kinetics.cpp:417
std::vector< int > m_phaseIsStable
Vector of int indicating whether phases are stable or not.
const doublereal GasConstant
Universal Gas Constant. [J/kmol/K].
Definition: ct_defs.h:64
size_t m_ii
Number of reactions in the mechanism.
Definition: Kinetics.h:978
std::vector< size_t > m_ctrxn
Vector of reaction indexes specifying the id of the current transfer reactions in the mechanism...
#define DATA_PTR(vec)
Creates a pointer to the start of the raw data for a vector.
Definition: ct_defs.h:36
const int BUTLERVOLMER_NOACTIVITYCOEFFS_RXN
This is a surface reaction that is formulated using the Butler-Volmer formulation and using concentra...
Definition: reaction_defs.h:80
virtual double reactantStoichCoeff(size_t k, size_t i) const
Stoichiometric coefficient of species k as a reactant in reaction i.
Definition: Kinetics.cpp:428
size_t kElectronIndex_
Index of the electrons species in the list of species for this surface kinetics, if none set it to -1...
virtual void updateROP()
Internal routine that updates the Rates of Progress of the reactions.
const int BUTLERVOLMER_RXN
This is a surface reaction that is formulated using the Butler-Volmer formulation.
Definition: reaction_defs.h:86
size_t solnPhaseIndex_
Index of the solution phase in the list of phases for this surface.
Class that includes some bookeeping entries for a reaction or a global reaction defined on a surface...
Definition: RxnMolChange.h:29
virtual int reactionType(size_t i) const
Flag specifying the type of reaction.
Definition: Kinetics.h:686
virtual double productStoichCoeff(size_t k, size_t i) const
Stoichiometric coefficient of species k as a product in reaction i.
Definition: Kinetics.cpp:433
InterfaceKinetics & operator=(const InterfaceKinetics &right)
Assignment operator.
doublereal charge(size_t k) const
Dimensionless electrical charge of a single molecule of species k The charge is normalized by the the...
Definition: Phase.h:578
std::vector< RxnOrders * > m_ctrxn_FwdOrdersList_
Reaction Orders for the case where the forwards rate of progress is being calculated.