chombo-discharge
CD_KMCSolverImplem.H
Go to the documentation of this file.
1 /* chombo-discharge
2  * Copyright © 2022 SINTEF Energy Research.
3  * Please refer to Copyright.txt and LICENSE in the chombo-discharge root directory.
4  */
5 
12 #ifndef CD_KMCSolverImplem_H
13 #define CD_KMCSolverImplem_H
14 
15 // Std includes
16 #include <limits>
17 #include <unordered_set>
18 
19 // Chombo includes
20 #include <CH_Timer.H>
21 
22 // Our includes
23 #include <CD_Random.H>
24 #include <CD_KMCSolver.H>
25 #include <CD_NamespaceHeader.H>
26 
27 template <typename R, typename State, typename T>
29 {}
30 
31 template <typename R, typename State, typename T>
32 inline KMCSolver<R, State, T>::KMCSolver(const ReactionList& a_reactions) noexcept
33 {
34  CH_TIME("KMCSolver::KMCSolver");
35 
36  this->define(a_reactions);
37 }
38 
39 template <typename R, typename State, typename T>
41 {
42  CH_TIME("KMCSolver::~KMCSolver");
43 }
44 
45 template <typename R, typename State, typename T>
46 inline void
47 KMCSolver<R, State, T>::define(const ReactionList& a_reactions) noexcept
48 {
49  CH_TIME("KMCSolver::define");
50 
51  m_reactions = a_reactions;
52 
53  // Default settings. These are equivalent to ALWAYS using tau-leaping.
54  this->setSolverParameters(0, 0, std::numeric_limits<Real>::max(), 0.0);
55 }
56 
57 template <typename R, typename State, typename T>
58 inline void
60  const T a_numSSA,
61  const Real a_eps,
62  const Real a_SSAlim) noexcept
63 {
64  CH_TIME("KMCSolver::setSolverParameters");
65 
66  m_Ncrit = a_Ncrit;
67  m_numSSA = a_numSSA;
68  m_eps = a_eps;
69  m_SSAlim = a_SSAlim;
70 }
71 
72 template <typename R, typename State, typename T>
73 inline std::vector<Real>
74 KMCSolver<R, State, T>::propensities(const State& a_state) const noexcept
75 {
76  CH_TIME("KMCSolver::propensities(State)");
77 
78  return this->propensities(a_state, m_reactions);
79 }
80 
81 template <typename R, typename State, typename T>
82 inline std::vector<Real>
83 KMCSolver<R, State, T>::propensities(const State& a_state, const ReactionList& a_reactions) const noexcept
84 {
85  CH_TIME("KMCSolver::propensities(State, ReactionList)");
86 
87  std::vector<Real> A(a_reactions.size());
88 
89  const size_t numReactions = a_reactions.size();
90 
91  for (size_t i = 0; i < numReactions; i++) {
92  A[i] = a_reactions[i]->propensity(a_state);
93  }
94 
95  return A;
96 }
97 
98 template <typename R, typename State, typename T>
99 inline Real
100 KMCSolver<R, State, T>::totalPropensity(const State& a_state) const noexcept
101 {
102  CH_TIME("KMCSolver::totalPropensity(State)");
103 
104  return this->totalPropensity(a_state, m_reactions);
105 }
106 
107 template <typename R, typename State, typename T>
108 inline Real
109 KMCSolver<R, State, T>::totalPropensity(const State& a_state, const ReactionList& a_reactions) const noexcept
110 {
111  CH_TIME("KMCSolver::totalPropensity(State, ReactionList)");
112 
113  Real A = 0.0;
114 
115  const size_t numReactions = a_reactions.size();
116 
117  for (size_t i = 0; i < numReactions; i++) {
118  A += a_reactions[i]->propensity(a_state);
119  }
120 
121  return A;
122 }
123 
124 template <typename R, typename State, typename T>
125 inline std::pair<typename KMCSolver<R, State, T>::ReactionList, typename KMCSolver<R, State, T>::ReactionList>
126 KMCSolver<R, State, T>::partitionReactions(const State& a_state) const noexcept
127 {
128  CH_TIME("KMCSolver::partitionReactions(State)");
129 
130  return this->partitionReactions(a_state, m_reactions);
131 }
132 
133 template <typename R, typename State, typename T>
134 inline std::pair<typename KMCSolver<R, State, T>::ReactionList, typename KMCSolver<R, State, T>::ReactionList>
135 KMCSolver<R, State, T>::partitionReactions(const State& a_state, const ReactionList& a_reactions) const noexcept
136 {
137  CH_TIME("KMCSolver::partitionReactions(State, ReactionList)");
138 
139  ReactionList criticalReactions;
140  ReactionList nonCriticalReactions;
141 
142  const size_t numReactions = a_reactions.size();
143 
144  for (size_t i = 0; i < numReactions; i++) {
145  const T Lj = a_reactions[i]->computeCriticalNumberOfReactions(a_state);
146 
147  if (Lj < m_Ncrit) {
148  criticalReactions.emplace_back(a_reactions[i]);
149  }
150  else {
151  nonCriticalReactions.emplace_back(a_reactions[i]);
152  }
153  }
154 
155  return std::make_pair(criticalReactions, nonCriticalReactions);
156 }
157 
158 template <typename R, typename State, typename T>
159 inline Real
160 KMCSolver<R, State, T>::getCriticalTimeStep(const State& a_state) const noexcept
161 
162 {
163  return this->getCriticalTimeStep(a_state, m_reactions);
164 }
165 
166 template <typename R, typename State, typename T>
167 inline Real
169  const ReactionList& a_criticalReactions) const noexcept
170 {
171  CH_TIME("KMCSolver::getCriticalTimeStep");
172 
173  // TLDR: This computes the time until the firing of the next critical reaction.
174 
175  Real dt = std::numeric_limits<Real>::max();
176 
177  if (a_criticalReactions.size() > 0) {
178  // Add numeric_limits<Real>::min to A and u to avoid division by zero.
179  const Real A = std::numeric_limits<Real>::min() + this->totalPropensity(a_state, a_criticalReactions);
180 
181  dt = this->getCriticalTimeStep(A);
182  }
183 
184  return dt;
185 }
186 
187 template <typename R, typename State, typename T>
188 inline Real
189 KMCSolver<R, State, T>::getCriticalTimeStep(const std::vector<Real>& a_propensities) const noexcept
190 {
191  CH_TIME("KineticMonteCarlo::getCriticalTimeStep(std::vector<Real>)");
192 
193  // To avoid division by zero later on.
195 
196  for (const auto& p : a_propensities) {
197  A += p;
198  }
199 
200  return this->getCriticalTimeStep(A);
201 }
202 
203 template <typename R, typename State, typename T>
204 inline Real
205 KMCSolver<R, State, T>::getCriticalTimeStep(const Real& a_totalPropensity) const noexcept
206 {
207  CH_TIME("KineticMonteCarlo::getCriticalTimeStep(Real)");
208 
210 
211  return log(1.0 / u) / a_totalPropensity;
212 }
213 
214 template <typename R, typename State, typename T>
215 inline Real
216 KMCSolver<R, State, T>::getNonCriticalTimeStep(const State& a_state) const noexcept
217 {
218  CH_TIME("KMCSolver::getNonCriticalTimeStep(State");
219 
220  const auto& partitionedReactions = this->partitionReactions(a_state, m_reactions);
221 
222  return this->getNonCriticalTimeStep(a_state, partitionedReactions.second);
223 }
224 
225 template <typename R, typename State, typename T>
226 inline Real
227 KMCSolver<R, State, T>::getNonCriticalTimeStep(const State& a_state, const ReactionList& a_reactions) const noexcept
228 {
229  CH_TIME("KMCSolver::getNonCriticalTimeStep(State, ReactionList");
230 
231  const std::vector<Real> propensities = this->propensities(a_state, a_reactions);
232 
233  return this->getNonCriticalTimeStep(a_state, a_reactions, propensities);
234 }
235 
236 template <typename R, typename State, typename T>
237 inline Real
239  const ReactionList& a_nonCriticalReactions,
240  const std::vector<Real>& a_nonCriticalPropensities) const noexcept
241 {
242  CH_TIME("KMCSolver::getNonCriticalTimeStep(State, ReactionList, std::vector<Real>");
243 
244  CH_assert(a_nonCriticalReactions.size() == a_nonCriticalPropensities.size());
245 
246  constexpr Real one = 1.0;
247 
248  Real dt = std::numeric_limits<Real>::max();
249 
250  const size_t numReactions = a_nonCriticalReactions.size();
251 
252  if (numReactions > 0) {
253 
254  // 1. Gather a list of all reactants involved in the non-critical reactions.
255  auto allReactants = a_nonCriticalReactions[0]->getReactants();
256  for (size_t i = 1; i < numReactions; i++) {
257  const auto& curReactants = a_nonCriticalReactions[i]->getReactants();
258 
259  allReactants.insert(allReactants.end(), curReactants.begin(), curReactants.end());
260  }
261 
262  // Only do unique reactants.
263  const auto ip = std::unique(allReactants.begin(), allReactants.end());
264  allReactants.resize(std::distance(allReactants.begin(), ip));
265 
266  // 2. Iterate through all reactants and compute deviations.
267  for (const auto& reactant : allReactants) {
268 
269  // Xi is the population of the current reactant. It might seem weird that we are indexing this
270  // through the reactions rather then through the state. Which it is, but the reason for that is
271  // that it is the REACTION that determines how we index the state population. This is simply a
272  // design choice that permits the user to apply different type of reactions without changing
273  // the underlying state.
274  const T Xi = a_nonCriticalReactions[0]->population(reactant, a_state);
275 
276  if (Xi > (T)0) {
277  Real mu = 0.0;
278  Real sigma2 = 0.0;
279 
280  // Set gi to 4 for now -- this is equivalent to a third-order reaction Si*Si*Si with Xi = 3, I doubt
281  // that the Cao computation of gi is necessary because it becomes an "approximation within an approximation". So
282  // I'm just setting gi to the "worst-case" scenario and live with that.
283  constexpr Real gi = 4.0;
284 
285  for (size_t i = 0; i < numReactions; i++) {
286  const Real& p = a_nonCriticalPropensities[i];
287  const auto& muIJ = a_nonCriticalReactions[i]->getStateChange(reactant);
288 
289  mu += std::abs(muIJ * p);
290  sigma2 += std::abs(muIJ * muIJ * p);
291  }
292 
293  Real dt1 = std::numeric_limits<Real>::max();
294  Real dt2 = std::numeric_limits<Real>::max();
295 
296  const Real f = std::max(m_eps * Xi / gi, one);
297 
298  if (mu > std::numeric_limits<Real>::min()) {
299  dt1 = f / mu;
300  }
301  if (sigma2 > std::numeric_limits<Real>::min()) {
302  dt2 = (f * f) / sigma2;
303  }
304 
305  dt = std::min(dt, std::min(dt1, dt2));
306  }
307  }
308  }
309 
310  return dt;
311 }
312 
313 template <typename R, typename State, typename T>
314 inline void
315 KMCSolver<R, State, T>::stepTauPlain(State& a_state, const Real a_dt) const noexcept
316 {
317  CH_TIME("KMCSolver::stepTauPlain(State, Real)");
318 
319  this->stepTauPlain(a_state, m_reactions, a_dt);
320 }
321 
322 template <typename R, typename State, typename T>
323 inline void
324 KMCSolver<R, State, T>::stepTauPlain(State& a_state, const ReactionList& a_reactions, const Real a_dt) const noexcept
325 {
326  CH_TIME("KMCSolver::stepTauPlain(State, ReactionList, Real)");
327 
328  const std::vector<Real> propensities = this->propensities(a_state, a_reactions);
329 
330  this->stepTauPlain(a_state, a_reactions, propensities, a_dt);
331 }
332 
333 template <typename R, typename State, typename T>
334 inline void
336  const ReactionList& a_reactions,
337  const std::vector<Real>& a_propensities,
338  const Real a_dt) const noexcept
339 {
340  CH_TIME("KMCSolver::stepTauPlain(State, ReactionList, std::vector<Real>, Real)");
341 
342  CH_assert(a_reactions.size() == a_propensities.size());
343 
344  if (a_reactions.size() > 0) {
345  for (size_t i = 0; i < a_reactions.size(); i++) {
346 
347  // Number of reactions is always an integer -- draw from a Poisson distribution in long long. I'm just
348  // using a large integer type to avoid potential overflows.
349  const T numReactions = (T)Random::getPoisson<long long>(a_propensities[i] * a_dt);
350 
351  a_reactions[i]->advanceState(a_state, numReactions);
352  }
353  }
354 }
355 
356 template <typename R, typename State, typename T>
357 inline void
358 KMCSolver<R, State, T>::advanceTauPlain(State& a_state, const Real a_dt) const noexcept
359 {
360  CH_TIME("KMCSolver::advanceTauPlain(State, ReactionList)");
361 
362  this->advanceTauPlain(a_state, m_reactions, a_dt);
363 }
364 
365 template <typename R, typename State, typename T>
366 inline void
367 KMCSolver<R, State, T>::advanceTauPlain(State& a_state, const ReactionList& a_reactions, const Real a_dt) const noexcept
368 {
369  CH_TIME("KMCSolver::advanceTauPlain(State, ReactionList, Real)");
370 
371  if (a_reactions.size() > 0) {
372  Real curTime = 0.0;
373  Real curDt = a_dt;
374 
375  while (curTime < a_dt) {
376 
377  // Try big time step first.
378  curDt = a_dt - curTime;
379 
380  bool valid = false;
381 
382  // Substepping so we end up with a valid state.
383  while (!valid) {
384 
385  State state = a_state;
386 
387  // Do a tau-leaping step.
388  this->stepTauPlain(state, a_reactions, curDt);
389 
390  // If this was a valid step, accept it. Else reduce dt.
391  valid = state.isValidState();
392 
393  if (valid) {
394  a_state = state;
395 
396  curTime += curDt;
397  }
398  else {
399  curDt *= 0.5;
400  }
401  }
402  }
403  }
404 }
405 
406 template <typename R, typename State, typename T>
407 inline void
408 KMCSolver<R, State, T>::stepTauMidpoint(State& a_state, const Real a_dt) const noexcept
409 {
410  CH_TIME("KMCSolver::stepTauMidpoint(State&, const Real)");
411 
412  this->stepTauMidpoint(a_state, m_reactions, a_dt);
413 }
414 
415 template <typename R, typename State, typename T>
416 inline void
417 KMCSolver<R, State, T>::stepTauMidpoint(State& a_state, const ReactionList& a_reactions, const Real a_dt) const noexcept
418 {
419  CH_TIME("KMCSolver::stepTauMidpoint(State&, const ReactionList&, const Real)");
420 
421  const int numReactions = a_reactions.size();
422 
423  if (numReactions > 0) {
424 
425  std::vector<Real> propensities = this->propensities(a_state, a_reactions);
426 
427  State Xdagger = a_state;
428  for (size_t i = 0; i < numReactions; i++) {
429  // TLDR: Predict a midpoint state -- unfortunately this means that as a_dt->0 we end up with plain
430  // tau-leaping. I don't know of a way to fix this without introducing double fluctuations (yet).
431  a_reactions[i]->advanceState(Xdagger, (T)std::ceil(0.5 * propensities[i] * a_dt));
432  }
433 
434  propensities = this->propensities(Xdagger, a_reactions);
435  for (size_t i = 0; i < numReactions; i++) {
436  const T curReactions = (T)Random::getPoisson<long long>(propensities[i] * a_dt);
437 
438  a_reactions[i]->advanceState(a_state, curReactions);
439  }
440  }
441 }
442 
443 template <typename R, typename State, typename T>
444 inline void
445 KMCSolver<R, State, T>::advanceTauMidpoint(State& a_state, const Real a_dt) const noexcept
446 {
447  CH_TIME("KMCSolver::advanceTauMidpoint(State&, const Real)");
448 
449  this->advanceTauMidpoint(a_state, m_reactions, a_dt);
450 }
451 
452 template <typename R, typename State, typename T>
453 inline void
455  const ReactionList& a_reactions,
456  const Real a_dt) const noexcept
457 {
458  CH_TIME("KMCSolver::advanceTauMidpoint(State&, const ReactionList&, const Real)");
459 
460  if (a_reactions.size() > 0) {
461  Real curTime = 0.0;
462  Real curDt = a_dt;
463 
464  while (curTime < a_dt) {
465 
466  // Try big time step first.
467  curDt = a_dt - curTime;
468 
469  bool valid = false;
470 
471  // Substepping so we end up with a valid state.
472  while (!valid) {
473 
474  State state = a_state;
475 
476  // Do a tau-leaping step.
477  this->stepTauMidpoint(state, a_reactions, curDt);
478 
479  // If this was a valid step, accept it. Else reduce dt.
480  valid = state.isValidState();
481 
482  if (valid) {
483  a_state = state;
484 
485  curTime += curDt;
486  }
487  else {
488  curDt *= 0.5;
489  }
490  }
491  }
492  }
493 }
494 
495 template <typename R, typename State, typename T>
496 inline void
497 KMCSolver<R, State, T>::stepSSA(State& a_state) const noexcept
498 {
499  CH_TIME("KMCSolver::stepSSA(State, Real)");
500 
501  this->stepSSA(a_state, m_reactions);
502 }
503 
504 template <typename R, typename State, typename T>
505 inline void
506 KMCSolver<R, State, T>::stepSSA(State& a_state, const ReactionList& a_reactions) const noexcept
507 {
508  CH_TIME("KMCSolver::stepSSA(State, ReactionList, Real)");
509 
510  if (a_reactions.size() > 0) {
511 
512  // Compute all propensities.
513  const std::vector<Real> propensities = this->propensities(a_state, a_reactions);
514 
515  this->stepSSA(a_state, a_reactions, propensities);
516  }
517 }
518 
519 template <typename R, typename State, typename T>
520 inline void
522  const ReactionList& a_reactions,
523  const std::vector<Real>& a_propensities) const noexcept
524 {
525  CH_TIME("KMCSolver::stepSSA(State, ReactionList, std::vector<Real>)");
526 
527  CH_assert(a_reactions.size() == a_propensities.size());
528 
529  const size_t numReactions = a_reactions.size();
530 
531  if (numReactions > 0) {
532  constexpr T one = (T)1;
533 
534  // Determine the reaction type as per Gillespie algorithm.
535  Real A = 0.0;
536  for (size_t i = 0; i < numReactions; i++) {
537  A += a_propensities[i];
538  }
539 
540  const Real u = Random::getUniformReal01();
541 
543 
544  Real sumProp = 0.0;
545  for (size_t i = 0; i < a_reactions.size(); i++) {
546  sumProp += a_propensities[i];
547 
548  if (sumProp >= u * A) {
549  r = i;
550 
551  break;
552  }
553  }
554 
555  // Advance by one reaction.
556  a_reactions[r]->advanceState(a_state, one);
557  }
558 }
559 
560 template <typename R, typename State, typename T>
561 inline void
562 KMCSolver<R, State, T>::advanceSSA(State& a_state, const Real a_dt) const noexcept
563 {
564  CH_TIME("KMCSolver::advanceSSA(State, Real)");
565 
566  this->advanceSSA(a_state, m_reactions, a_dt);
567 }
568 
569 template <typename R, typename State, typename T>
570 inline void
571 KMCSolver<R, State, T>::advanceSSA(State& a_state, const ReactionList& a_reactions, const Real a_dt) const noexcept
572 {
573  CH_TIME("KMCSolver::advanceSSA(State, ReactionList, Real)");
574 
575  const size_t numReactions = a_reactions.size();
576 
577  if (numReactions > 0) {
578 
579  // Simulated time within the SSA.
580  Real curDt = 0.0;
581 
582  while (curDt <= a_dt) {
583 
584  // Compute the propensities and get the time to the next reaction.
585  const std::vector<Real> propensities = this->propensities(a_state, a_reactions);
586 
587  const Real nextDt = this->getCriticalTimeStep(propensities);
588 
589  // Fire one reaction if occurs within a_dt.
590  if (curDt + nextDt <= a_dt) {
591  this->stepSSA(a_state, a_reactions, propensities);
592  }
593 
594  curDt += nextDt;
595  }
596  }
597 }
598 
599 template <typename R, typename State, typename T>
600 inline void
602  const Real a_dt,
603  const KMCLeapPropagator& a_leapPropagator) const noexcept
604 {
605  CH_TIME("KMCSolver::advanceHybrid(State, Real, KMCLeapPropagator)");
606 
607  this->advanceHybrid(a_state, m_reactions, a_dt, a_leapPropagator);
608 }
609 
610 template <typename R, typename State, typename T>
611 inline void
613  const ReactionList& a_reactions,
614  const Real a_dt,
615  const KMCLeapPropagator& a_leapPropagator) const noexcept
616 {
617  CH_TIME("KMCSolver::advanceHybrid(State, ReactionList, Real, KMCLeapPropagator)");
618 
619  switch (a_leapPropagator) {
620  case KMCLeapPropagator::TauPlain: {
621  this->advanceHybrid(a_state, a_reactions, a_dt, [this](State& s, const ReactionList& r, const Real dt) {
622  this->stepTauPlain(s, r, dt);
623  });
624 
625  break;
626  }
627  case KMCLeapPropagator::TauMidpoint: {
628  this->advanceHybrid(a_state, a_reactions, a_dt, [this](State& s, const ReactionList& r, const Real dt) {
629  this->stepTauMidpoint(s, r, dt);
630  });
631 
632  break;
633  }
634  default: {
635  MayDay::Error("KMCSolver::advanceHybrid - unknown leap propagator requested");
636  }
637  }
638 }
639 
640 template <typename R, typename State, typename T>
641 inline void
643  State& a_state,
644  const ReactionList& a_reactions,
645  const Real a_dt,
646  const std::function<void(State&, const ReactionList& a_reactions, const Real a_dt)>& a_propagator) const noexcept
647 {
648  CH_TIME("KMCSolver::advanceHybrid(State, ReactionList, Real, std::function)");
649 
650  constexpr T one = (T)1;
651 
652  // Simulated time within the advancement algorithm.
653  Real curTime = 0.0;
654 
655  // Outer loop is for reactive substepping over a_dt.
656  while (curTime < a_dt) {
657 
658  // Partition reactions into critical and non-critical reactions and compute the critical and non-critical time steps.
659  const std::pair<ReactionList, ReactionList> partitionedReactions = this->partitionReactions(a_state, a_reactions);
660 
661  const ReactionList& criticalReactions = partitionedReactions.first;
662  const ReactionList& nonCriticalReactions = partitionedReactions.second;
663 
664  const std::vector<Real> propensitiesCrit = this->propensities(a_state, criticalReactions);
665  const std::vector<Real> propensitiesNonCrit = this->propensities(a_state, nonCriticalReactions);
666 
667  Real dtCrit = this->getCriticalTimeStep(propensitiesCrit);
668  Real dtNonCrit = this->getNonCriticalTimeStep(a_state, nonCriticalReactions, propensitiesNonCrit);
669 
670  // Try the various advancement algorithms; the loop is for step rejection in case we end up with an invalid state, e.g.
671  // a state with a negative number of particles.
672  bool validStep = false;
673 
674  while (!validStep) {
675 
676  // Do a backup of the advancement state to operate on. This is necessary because the tau-leaping
677  // algorithms advance the state but we may need to reject those steps if we end up with a thermodynamically
678  // invalid state.
679  State state = a_state;
680 
681  // Compute the time step to be used.
682  const Real curDt = std::min(a_dt - curTime, std::min(dtCrit, dtNonCrit));
683 
684  // Are we only doing non-critical reactions?
685  const bool nonCriticalOnly = (dtNonCrit < dtCrit) || (criticalReactions.size() == 0) ||
686  (dtCrit > (a_dt - curTime));
687 
688  if (nonCriticalOnly) {
689 
690  // Compute the total propensity because it might actually be better to run SSA steps.
691  Real A = this->totalPropensity(state, a_reactions);
692 
693  if (A * curDt < m_SSAlim) {
694  // TLDR: Tau-leaping is inefficient here so we switch to an SSA-based algorithm for the WHOLE reaction set.
695 
696  // Number of SSA steps taken and simulated time within the SSA. We will compute until either dtSSA < curDt or
697  // we've exceeded the maximum number of SSA steps that the user has permitted (m_numSSA).
698  Real dtSSA = 0.0;
699  T numSSA = 0;
700 
701  while (dtSSA < curDt && numSSA < m_numSSA) {
702 
703  // Recompute propensities for the full reaction set and advance everything using the SSA.
704  std::vector<Real> propensities = this->propensities(a_state, a_reactions);
705 
706  A = 0.0;
707  for (size_t i = 0; i < propensities.size(); i++) {
708  A += propensities[i];
709  }
710 
711  // Compute the time to the next reaction.
712  const Real dtReact = this->getCriticalTimeStep(A);
713 
714  if (dtSSA + dtReact < curDt) {
715  this->stepSSA(a_state, a_reactions, propensities);
716 
717  dtSSA += dtReact;
718  numSSA += one;
719  }
720  else {
721 
722  // Next reaction occured outside the substep -- break out of the loop.
723  dtSSA = curDt;
724  }
725  }
726 
727  validStep = true;
728  curTime += dtSSA;
729  }
730  else {
731  // Do the tau-leaping.
732  a_propagator(state, nonCriticalReactions, curDt);
733 
734  // Check if we need to reject the state and rather try again with a smaller non-critical time step.
735  validStep = state.isValidState();
736 
737  if (validStep) {
738  a_state = state;
739 
740  curTime += curDt;
741  }
742  else {
743  dtNonCrit *= 0.5;
744  }
745  }
746  }
747  else {
748  // TLDR: Here, one critical reaction fired. We advance the critical reaction using the SSA and the rest of the reactions
749  // using tau-leaping.
750  this->stepSSA(state, criticalReactions, propensitiesCrit);
751 
752  // Do the tau-leaping over the non-critical reactions.
753  a_propagator(state, nonCriticalReactions, curDt);
754 
755  // Check if we need to reject the state and rather try again with a smaller non-critical time step.
756  validStep = state.isValidState();
757 
758  if (validStep) {
759  a_state = state;
760 
761  curTime += curDt;
762  }
763  else {
764  dtNonCrit *= 0.5;
765  }
766  }
767  }
768  }
769 }
770 
771 #include <CD_NamespaceFooter.H>
772 
773 #endif
Class for running Kinetic Monte Carlo functionality.
KMCLeapPropagator
Supported propagators for hybrid tau leaping.
Definition: CD_KMCSolver.H:29
File containing some useful static methods related to random number generation.
void setSolverParameters(const T a_numCrit, const T a_numSSA, const Real a_eps, const Real a_SSAlim) noexcept
Set solver parameters.
Definition: CD_KMCSolverImplem.H:59
virtual ~KMCSolver() noexcept
Destructor.
Definition: CD_KMCSolverImplem.H:40
void advanceTauMidpoint(State &a_state, const Real a_dt) const noexcept
Perform one leaping step using the midpoint method all reactions over a time step a_dt.
Definition: CD_KMCSolverImplem.H:445
void define(const ReactionList &a_reactions) noexcept
Define function. Sets the reactions.
Definition: CD_KMCSolverImplem.H:47
KMCSolver() noexcept
Default constructor – must subsequently define the object.
Definition: CD_KMCSolverImplem.H:28
Real getCriticalTimeStep(const State &a_state) const noexcept
Get the time to the next critical reaction.
Definition: CD_KMCSolverImplem.H:160
void stepTauMidpoint(State &a_state, const Real a_dt) const noexcept
Perform one leaping step using the midpoint method for ALL reactions over a time step a_dt.
Definition: CD_KMCSolverImplem.H:408
void stepTauPlain(State &a_state, const Real a_dt) const noexcept
Perform one plain tau-leaping step using ALL reactions.
Definition: CD_KMCSolverImplem.H:315
void advanceSSA(State &a_state, const Real a_dt) const noexcept
Advance with the SSA over the input time. This can end up using substepping.
Definition: CD_KMCSolverImplem.H:562
std::pair< ReactionList, ReactionList > partitionReactions(const State &a_state) const noexcept
Partition reactions into critical and non-critical reactions.
Definition: CD_KMCSolverImplem.H:126
void advanceTauPlain(State &a_state, const Real a_dt) const noexcept
Advance with plain tau-leaping step over the input time. This can end up using substepping.
Definition: CD_KMCSolverImplem.H:358
void stepSSA(State &a_state) const noexcept
Perform a single SSA step.
Definition: CD_KMCSolverImplem.H:497
Real getNonCriticalTimeStep(const State &a_state) const noexcept
Get the non-critical time step.
Definition: CD_KMCSolverImplem.H:216
void advanceHybrid(State &a_state, const Real a_dt, const KMCLeapPropagator &a_leapPropagator=KMCLeapPropagator::TauPlain) const noexcept
Advance using Cao et. al. hybrid algorithm over the input time. This can end up using substepping.
Definition: CD_KMCSolverImplem.H:601
Real totalPropensity(const State &a_state) const noexcept
Compute the total propensity for ALL reactions.
Definition: CD_KMCSolverImplem.H:100
std::vector< Real > propensities(const State &a_state) const noexcept
Compute propensities for ALL reactions.
Definition: CD_KMCSolverImplem.H:74
static Real getUniformReal01()
Get a uniform real number on the interval [0,1].
Definition: CD_RandomImplem.H:147
Real max(const Real &a_input) noexcept
Get the maximum of the input, reduced over MPI ranks (in the Chombo communicator)
Definition: CD_ParallelOpsImplem.H:176
Real min(const Real &a_input) noexcept
Get the minimum of the input, reduced over MPI ranks (in the Chombo communicator)
Definition: CD_ParallelOpsImplem.H:58