chombo-discharge
Loading...
Searching...
No Matches
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_LaPackUtils.H>
26#include <CD_NamespaceHeader.H>
27
28template <typename R, typename State, typename T>
30{
31 CH_TIME("KMCSolver::KMCSolver");
32
33 this->setSolverParameters(0, 0, 100, std::numeric_limits<Real>::max(), 0.0, 1.E-6);
34}
35
36template <typename R, typename State, typename T>
37inline KMCSolver<R, State, T>::KMCSolver(const ReactionList& a_reactions) noexcept
38{
39 CH_TIME("KMCSolver::KMCSolver");
40
41 this->define(a_reactions);
42}
43
44template <typename R, typename State, typename T>
46{
47 CH_TIME("KMCSolver::~KMCSolver");
48}
49
50template <typename R, typename State, typename T>
51inline void
52KMCSolver<R, State, T>::define(const ReactionList& a_reactions) noexcept
53{
54 CH_TIME("KMCSolver::define");
55
56 m_reactions = a_reactions;
57
58 // Default settings. These are equivalent to ALWAYS using tau-leaping.
59 this->setSolverParameters(0, 0, 100, std::numeric_limits<Real>::max(), 0.0, 1.E-6);
60}
61
62template <typename R, typename State, typename T>
63inline void
65 const T a_numSSA,
66 const T a_maxIter,
67 const Real a_eps,
68 const Real a_SSAlim,
69 const Real a_exitTol) noexcept
70{
71 CH_TIME("KMCSolver::setSolverParameters");
72
73 m_Ncrit = a_Ncrit;
74 m_numSSA = a_numSSA;
75 m_maxIter = a_maxIter;
76 m_eps = a_eps;
77 m_SSAlim = a_SSAlim;
78 m_exitTol = a_exitTol;
79}
80
81template <typename R, typename State, typename T>
83KMCSolver<R, State, T>::getNu(const State& a_state, const ReactionList& a_reactions) const noexcept
84{
85 CH_TIME("KMCSolver::getNu(State)");
86
88
89 for (int j = 0; j < a_reactions.size(); j++) {
91
92 // Linearize a trivial state
93 State state = a_state;
94
95 std::vector<T> preState = state.linearOut();
96 for (auto& x : preState) {
97 x = static_cast<T>(0);
98 }
99 state.linearIn(preState);
100
101 // Advance with exactly one reaction.
102 a_reactions[j]->advanceState(state, static_cast<T>(1));
103
104 std::vector<T> postState = state.linearOut();
105
106 // Compute the state change vector.
107 nu.resize(preState.size());
108 for (int i = 0; i < preState.size(); i++) {
109 nu[i] = postState[i] - preState[i];
110 }
111 }
112
113 return ret;
114}
115
116template <typename R, typename State, typename T>
119{
120 CH_TIME("KMCSolver::propensities(State)");
121
122 return this->propensities(a_state, m_reactions);
123}
124
125template <typename R, typename State, typename T>
127KMCSolver<R, State, T>::propensities(const State& a_state, const ReactionList& a_reactions) const noexcept
128{
129 CH_TIME("KMCSolver::propensities(State, ReactionList)");
130
132
133 const size_t numReactions = a_reactions.size();
134
135 for (size_t i = 0; i < numReactions; i++) {
136 A[i] = a_reactions[i]->propensity(a_state);
137 }
138
139 return A;
140}
141
142template <typename R, typename State, typename T>
143inline Real
145{
146 CH_TIME("KMCSolver::totalPropensity(State)");
147
148 return this->totalPropensity(a_state, m_reactions);
149}
150
151template <typename R, typename State, typename T>
152inline Real
153KMCSolver<R, State, T>::totalPropensity(const State& a_state, const ReactionList& a_reactions) const noexcept
154{
155 CH_TIME("KMCSolver::totalPropensity(State, ReactionList)");
156
157 Real A = 0.0;
158
159 const size_t numReactions = a_reactions.size();
160
161 for (size_t i = 0; i < numReactions; i++) {
162 A += a_reactions[i]->propensity(a_state);
163 }
164
165 return A;
166}
167
168template <typename R, typename State, typename T>
169inline std::pair<typename KMCSolver<R, State, T>::ReactionList, typename KMCSolver<R, State, T>::ReactionList>
171{
172 CH_TIME("KMCSolver::partitionReactions(State)");
173
174 return this->partitionReactions(a_state, m_reactions);
175}
176
177template <typename R, typename State, typename T>
178inline std::pair<typename KMCSolver<R, State, T>::ReactionList, typename KMCSolver<R, State, T>::ReactionList>
179KMCSolver<R, State, T>::partitionReactions(const State& a_state, const ReactionList& a_reactions) const noexcept
180{
181 CH_TIME("KMCSolver::partitionReactions(State, ReactionList)");
182
183 ReactionList criticalReactions;
184 ReactionList nonCriticalReactions;
185
186 const size_t numReactions = a_reactions.size();
187
188 for (size_t i = 0; i < numReactions; i++) {
189 const T Lj = a_reactions[i]->computeCriticalNumberOfReactions(a_state);
190
191 if (Lj < m_Ncrit) {
192 criticalReactions.emplace_back(a_reactions[i]);
193 }
194 else {
195 nonCriticalReactions.emplace_back(a_reactions[i]);
196 }
197 }
198
200}
201
202template <typename R, typename State, typename T>
203inline Real
205
206{
207 return this->getCriticalTimeStep(a_state, m_reactions);
208}
209
210template <typename R, typename State, typename T>
211inline Real
213 const ReactionList& a_criticalReactions) const noexcept
214{
215 CH_TIME("KMCSolver::getCriticalTimeStep");
216
217 // TLDR: This computes the time until the firing of the next critical reaction.
218
219 Real dt = std::numeric_limits<Real>::max();
220
221 if (a_criticalReactions.size() > 0) {
222 // Add numeric_limits<Real>::min to A and u to avoid division by zero.
223 const Real A = std::numeric_limits<Real>::min() + this->totalPropensity(a_state, a_criticalReactions);
224
225 dt = this->getCriticalTimeStep(A);
226 }
227
228 return dt;
229}
230
231template <typename R, typename State, typename T>
232inline Real
234{
235 CH_TIME("KineticMonteCarlo::getCriticalTimeStep(std::vector<Real>)");
236
237 // To avoid division by zero later on.
238 Real A = std::numeric_limits<Real>::min();
239
240 for (const auto& p : a_propensities) {
241 A += p;
242 }
243
244 return this->getCriticalTimeStep(A);
245}
246
247template <typename R, typename State, typename T>
248inline Real
250{
251 CH_TIME("KineticMonteCarlo::getCriticalTimeStep(Real)");
252
253 const Real u = std::numeric_limits<Real>::min() + Random::getUniformReal01();
254
255 return log(1.0 / u) / a_totalPropensity;
256}
257
258template <typename R, typename State, typename T>
259inline Real
261{
262 CH_TIME("KMCSolver::getNonCriticalTimeStep(State");
263
264 const auto& partitionedReactions = this->partitionReactions(a_state, m_reactions);
265
266 return this->getNonCriticalTimeStep(a_state, partitionedReactions.second);
267}
268
269template <typename R, typename State, typename T>
270inline Real
271KMCSolver<R, State, T>::getNonCriticalTimeStep(const State& a_state, const ReactionList& a_reactions) const noexcept
272{
273 CH_TIME("KMCSolver::getNonCriticalTimeStep(State, ReactionList");
274
275 const std::vector<Real> propensities = this->propensities(a_state, a_reactions);
276
277 return this->getNonCriticalTimeStep(a_state, a_reactions, propensities);
278}
279
280template <typename R, typename State, typename T>
281inline Real
283 const ReactionList& a_nonCriticalReactions,
284 const std::vector<Real>& a_nonCriticalPropensities) const noexcept
285{
286 CH_TIME("KMCSolver::getNonCriticalTimeStep(State, ReactionList, std::vector<Real>");
287
289
290 constexpr Real one = 1.0;
291
292 Real dt = std::numeric_limits<Real>::max();
293
294 const size_t numReactions = a_nonCriticalReactions.size();
295
296 if (numReactions > 0) {
297
298 // 1. Gather a list of all reactants involved in the non-critical reactions.
299 auto allReactants = a_nonCriticalReactions[0]->getReactants();
300 for (size_t i = 1; i < numReactions; i++) {
301 const auto& curReactants = a_nonCriticalReactions[i]->getReactants();
302
303 allReactants.insert(allReactants.end(), curReactants.begin(), curReactants.end());
304 }
305
306 // Only do unique reactants.
307 const auto ip = std::unique(allReactants.begin(), allReactants.end());
308 allReactants.resize(std::distance(allReactants.begin(), ip));
309
310 // 2. Iterate through all reactants and compute deviations.
311 for (const auto& reactant : allReactants) {
312
313 // Xi is the population of the current reactant. It might seem weird that we are indexing this
314 // through the reactions rather then through the state. Which it is, but the reason for that is
315 // that it is the REACTION that determines how we index the state population. This is simply a
316 // design choice that permits the user to apply different type of reactions without changing
317 // the underlying state.
318 const T Xi = a_nonCriticalReactions[0]->population(reactant, a_state);
319
320 if (Xi > (T)0) {
321 Real mu = 0.0;
322 Real sigma2 = 0.0;
323
324 // Set gi to 1 for now. A more complex version would parse this through an input parameter
325 // where the user has inspected the highest-order-reaction.
326 constexpr Real gi = 1.0;
327
328 for (size_t i = 0; i < numReactions; i++) {
329 const Real& p = a_nonCriticalPropensities[i];
330 const auto& muIJ = a_nonCriticalReactions[i]->getStateChange(reactant);
331
332 mu += muIJ * p;
333 sigma2 += muIJ * muIJ * p;
334 }
335
336 Real dt1 = std::numeric_limits<Real>::max();
337 Real dt2 = std::numeric_limits<Real>::max();
338
339 const Real f = std::max(m_eps * Xi / gi, one);
340
341 mu = std::abs(mu);
343
344 if (mu > std::numeric_limits<Real>::min()) {
345 dt1 = f / mu;
346 }
347 if (sigma2 > std::numeric_limits<Real>::min()) {
348 dt2 = (f * f) / sigma2;
349 }
350
351 dt = std::min(dt, std::min(dt1, dt2));
352 }
353 }
354 }
355
356 return dt;
357}
358
359template <typename R, typename State, typename T>
360inline Real
362 const ReactionList& a_reactions,
364 const Real a_epsilon) const noexcept
365{
366 CH_TIME("KMCSolver::computeDt(State, ReactionList, std::vector<Real>, Real");
367
368 CH_assert(a_reactions.size() == a_propensities.size());
369
370 constexpr Real one = 1.0;
371
372 Real dt = std::numeric_limits<Real>::max();
373
374 const size_t numReactions = a_reactions.size();
375
376 if (numReactions > 0) {
377
378 // 1. Gather a list of all reactants involved in the input reactions
379 auto allReactants = a_reactions[0]->getReactants();
380 for (size_t i = 1; i < numReactions; i++) {
381 const auto& curReactants = a_reactions[i]->getReactants();
382
383 allReactants.insert(allReactants.end(), curReactants.begin(), curReactants.end());
384 }
385
386 // Only do unique reactants.
387 const auto ip = std::unique(allReactants.begin(), allReactants.end());
388 allReactants.resize(std::distance(allReactants.begin(), ip));
389
390 // 2. Iterate through all reactants and compute deviations.
391 for (const auto& reactant : allReactants) {
392
393 // Xi is the population of the current reactant. It might seem weird that we are indexing this
394 // through the reactions rather then through the state. Which it is, but the reason for that is
395 // that it is the REACTION that determines how we index the state population. This is simply a
396 // design choice that permits the user to apply different type of reactions without changing
397 // the underlying state.
398 const T Xi = R::population(reactant, a_state);
399
400 if (Xi > (T)0) {
401 Real mu = 0.0;
402
403 // Set gi to 1 for now. A more complex version would parse this through an input parameter
404 // where the user has inspected the highest-order-reaction.
405 constexpr Real gi = 1.0;
406
407 for (size_t i = 0; i < numReactions; i++) {
408 const Real& p = a_propensities[i];
409 const auto& muIJ = a_reactions[i]->getStateChange(reactant);
410
411 mu += muIJ * p;
412 }
413
414 const Real f = std::max(a_epsilon * Xi / gi, one);
415
416 mu = std::abs(mu);
417
418 if (mu > std::numeric_limits<Real>::min()) {
419 dt = std::min(dt, f / mu);
420 }
421 }
422 }
423 }
424
425 return dt;
426}
427
428template <typename R, typename State, typename T>
429inline void
431{
432 CH_TIME("KMCSolver::stepSSA(State, Real)");
433
434 this->stepSSA(a_state, m_reactions);
435}
436
437template <typename R, typename State, typename T>
438inline void
439KMCSolver<R, State, T>::stepSSA(State& a_state, const ReactionList& a_reactions) const noexcept
440{
441 CH_TIME("KMCSolver::stepSSA(State, ReactionList, Real)");
442
443 if (a_reactions.size() > 0) {
444
445 // Compute all propensities.
446 const std::vector<Real> propensities = this->propensities(a_state, a_reactions);
447
448 this->stepSSA(a_state, a_reactions, propensities);
449 }
450}
451
452template <typename R, typename State, typename T>
453inline void
455 const ReactionList& a_reactions,
456 const std::vector<Real>& a_propensities) const noexcept
457{
458 CH_TIME("KMCSolver::stepSSA(State, ReactionList, std::vector<Real>)");
459
460 CH_assert(a_reactions.size() == a_propensities.size());
461
462 const size_t numReactions = a_reactions.size();
463
464 if (numReactions > 0) {
465 constexpr T one = (T)1;
466
467 // Determine the reaction type as per Gillespie algorithm.
468 Real A = 0.0;
469 for (size_t i = 0; i < numReactions; i++) {
470 A += a_propensities[i];
471 }
472
474
475 size_t r = std::numeric_limits<size_t>::max();
476
477 Real sumProp = 0.0;
478 for (size_t i = 0; i < a_reactions.size(); i++) {
480
481 if (sumProp >= u * A) {
482 r = i;
483
484 break;
485 }
486 }
487
488 // Advance by one reaction.
489 a_reactions[r]->advanceState(a_state, one);
490 }
491}
492
493template <typename R, typename State, typename T>
494inline void
496{
497 CH_TIME("KMCSolver::advanceSSA(State, Real)");
498
499 this->advanceSSA(a_state, m_reactions, a_dt);
500}
501
502template <typename R, typename State, typename T>
503inline void
504KMCSolver<R, State, T>::advanceSSA(State& a_state, const ReactionList& a_reactions, const Real a_dt) const noexcept
505{
506 CH_TIME("KMCSolver::advanceSSA(State, ReactionList, Real)");
507
508 const size_t numReactions = a_reactions.size();
509
510 if (numReactions > 0) {
511
512 // Simulated time within the SSA.
513 Real curDt = 0.0;
514
515 while (curDt <= a_dt) {
516
517 // Compute the propensities and get the time to the next reaction.
518 const std::vector<Real> propensities = this->propensities(a_state, a_reactions);
519
520 const Real nextDt = this->getCriticalTimeStep(propensities);
521
522 // Fire one reaction if occurs within a_dt.
523 if (curDt + nextDt <= a_dt) {
524 this->stepSSA(a_state, a_reactions, propensities);
525 }
526
527 curDt += nextDt;
528 }
529 }
530}
531
532template <typename R, typename State, typename T>
533inline void
535{
536 CH_TIME("KMCSolver::stepExplicitEuler(State, Real)");
537
538 this->stepExplicitEuler(a_state, m_reactions, a_dt);
539}
540
541template <typename R, typename State, typename T>
542inline void
544 const ReactionList& a_reactions,
545 const Real a_dt) const noexcept
546{
547 CH_TIME("KMCSolver::stepExplicitEuler(State, ReactionList, std::vector<Real>, Real)");
548
549 CH_assert(a_dt > 0.0);
550
551 if (a_reactions.size() > 0) {
552 const std::vector<Real> propensities = this->propensities(a_state, a_reactions);
553
554 for (size_t i = 0; i < a_reactions.size(); i++) {
555
556 // Number of reactions is always an integer -- draw from a Poisson distribution in long long. I'm just
557 // using a large integer type to avoid potential overflows.
558 const T numReactions = (T)Random::getPoisson<long long>(propensities[i] * a_dt);
559
560 a_reactions[i]->advanceState(a_state, numReactions);
561 }
562 }
563}
564
565template <typename R, typename State, typename T>
566inline void
568{
569 CH_TIME("KMCSolver::stepMidpoint(State&, const Real)");
570
571 this->stepMidpoint(a_state, m_reactions, a_dt);
572}
573
574template <typename R, typename State, typename T>
575inline void
576KMCSolver<R, State, T>::stepMidpoint(State& a_state, const ReactionList& a_reactions, const Real a_dt) const noexcept
577{
578 CH_TIMERS("KMCSolver::stepMidpoint(State&, const ReactionList&, const Real)");
579 CH_TIMER("KMCSolver::stepTauMidPoint::first_loop", t1);
580 CH_TIMER("KMCSolver::stepTauMidPoint::second_loop", t2);
581
582 const int numReactions = a_reactions.size();
583
584 if (numReactions > 0) {
585
586 std::vector<Real> propensities = this->propensities(a_state, a_reactions);
587
588 State Xdagger = a_state;
589
590 CH_START(t1);
591 for (size_t i = 0; i < numReactions; i++) {
592 // TLDR: Predict a midpoint state -- unfortunately this means that as a_dt->0 we end up with plain
593 // tau-leaping. I don't know of a way to fix this without introducing double fluctuations (yet).
594 a_reactions[i]->advanceState(Xdagger, (T)std::round(0.5 * propensities[i] * a_dt));
595 }
596 CH_STOP(t1);
597
598 propensities = this->propensities(Xdagger, a_reactions);
599
600 CH_START(t2);
601 for (size_t i = 0; i < numReactions; i++) {
602 const T curReactions = (T)Random::getPoisson<long long>(propensities[i] * a_dt);
603
604 a_reactions[i]->advanceState(a_state, curReactions);
605 }
606 CH_STOP(t2);
607 }
608}
609
610template <typename R, typename State, typename T>
611inline void
613{
614 CH_TIME("KMCSolver::stepPRC(State&, const Real)");
615
616 this->stepPRC(a_state, m_reactions, a_dt);
617}
618
619template <typename R, typename State, typename T>
620inline void
621KMCSolver<R, State, T>::stepPRC(State& a_state, const ReactionList& a_reactions, const Real a_dt) const noexcept
622{
623 CH_TIMERS("KMCSolver::stepPRC(State&, const ReactionList&, const Real)");
624 CH_TIMER("KMCSolver::stepPRC::first_loop", t1);
625 CH_TIMER("KMCSolver::stepPRC::second_loop", t2);
626
627 const int numReactions = a_reactions.size();
628
629 if (numReactions > 0) {
630
631 std::vector<Real> aj = this->propensities(a_state, a_reactions);
632
633 const std::vector<Real> ak = aj;
634
635 CH_START(t1);
636 for (int j = 0; j < numReactions; j++) {
637 for (int k = 0; k < numReactions; k++) {
638 State x = a_state;
639
640 a_reactions[k]->advanceState(x, (T)1);
641
642 const Real etajk = a_reactions[j]->propensity(x) - ak[j];
643
644 aj[j] += 0.5 * a_dt * ak[k] * etajk;
645 }
646 }
647 CH_STOP(t1);
648
649 CH_START(t2);
650 for (size_t i = 0; i < numReactions; i++) {
651 const T nr = (T)Random::getPoisson<long long>(aj[i] * a_dt);
652
653 a_reactions[i]->advanceState(a_state, nr);
654 }
655 CH_STOP(t2);
656 }
657}
658
659template <typename R, typename State, typename T>
660inline void
662{
663 CH_TIME("KMCSolver::stepImplicitEuler(State, a_dt)");
664
665 this->stepImplicitEuler(a_state, m_reactions, a_dt);
666}
667
668template <typename R, typename State, typename T>
669inline void
671 const ReactionList& a_reactions,
672 const Real a_dt) const noexcept
673{
674 CH_TIME("KMCSolver::stepImplicitEuler(State, ReactionList, a_dt)");
675
676 // TLDR: The implicit Euler tau-leaping scheme is equivalent to the solution of
677 //
678 // F(X) = X - (x + sum_j (nu_j * (P(a(x)*dt) - a(x)*dt)]) - dt*sum_j nu_j a_j(X)
679 // = X - c - dt*sum_j nu_j * a_j(X),
680 // = 0
681 //
682 // where c = (x + sum_j (nu_j * (P(a(x)*dt) - a(x)*dt)]) is a constant term throughout the Newton iterations. This term is presampled
683 // before the Newton iterations begin.
684
685 // Linearize input state onto something understandable to LAPACK
686 const std::vector<T> inputState = a_state.linearOut();
687 const std::vector<std::vector<T>> nu = this->getNu(a_state, a_reactions);
688 const std::vector<Real> ajX = this->propensities(a_state, a_reactions);
689
690 // Number of equations and number of reactions.
691 const int N = inputState.size();
692 const int M = a_reactions.size();
693
694 // Lambda that computes the constant term c and the initial guess.
695 auto compConstantTerm = [&](double* C, double* X, const State& state, const Real a_dt) -> void {
696 // TLDR: To compute the constant term we perform a Poisson sampling and then linearize the output
697 // state.
698
700
701 this->advanceTau(explicitEulerState, a_reactions, a_dt, KMCLeapPropagator::ExplicitEuler);
702
703 const std::vector<T> eulerOut = explicitEulerState.linearOut();
704
705 // Make c = (x + sum_j (nu_j * (P(a(x)*dt) - a(x)*dt)])
706 for (int i = 0; i < N; i++) {
707 C[i] = 1.0 * eulerOut[i];
708 X[i] = 1.0 * eulerOut[i];
709 }
710
711 // Subtract the mean.
712 for (int j = 0; j < M; j++) {
713 const std::vector<T>& nuj = nu[j];
714
715 for (int i = 0; i < N; i++) {
716 C[i] -= nuj[i] * ajX[j] * a_dt;
717 }
718 }
719 };
720
721 // Lambda that computes the each equation.
722 auto computeF = [&](double* F, const double* Xit, const double* C) -> void {
723 // Compute the propensities for the Xit state. This requires us to round to the nearest integer.
724 State stateXit = a_state;
725
727 for (int i = 0; i < N; i++) {
728 linState[i] = static_cast<T>(llround(Xit[i]));
729 }
730
731 stateXit.linearIn(linState);
732
733 const std::vector<Real> ajXit = this->propensities(stateXit);
734
735 // Compute Xit - c - dt * sum_j nu_j * aj(round(Xit))
736 for (int i = 0; i < N; i++) {
737 F[i] = Xit[i] - C[i];
738 }
739
740 for (int j = 0; j < M; j++) {
741 const std::vector<T>& nuj = nu[j];
742
743 for (int i = 0; i < N; i++) {
744 F[i] -= a_dt * nuj[i] * ajXit[j];
745 }
746 }
747 };
748
749 // Compute the max-norm
750 auto computeNorm = [&](double* F, const double* X, const double* C) -> Real {
751 computeF(F, X, C);
752
753 Real norm = 0.0;
754
755 for (int i = 0; i < N; i++) {
756 norm = std::max(norm, std::abs(F[i]));
757 }
758
759 return norm;
760 };
761
762 // Allocate memory for the Jacobian (J), the Newton increment (X), and F(X) (F). Also include the constant term from the Poisson sampling (c)
763 double* J = new double[N * N];
764 double* X = new double[N];
765 double* F = new double[N];
766 double* C = new double[N];
767
768 // Temporary storage used for computing the Jacobian matrix.
769 double* X1 = new double[N];
770 double* X2 = new double[N];
771 double* F1 = new double[N];
772 double* F2 = new double[N];
773
774 // Things that are required by LAPACK
775 int INFO = 0;
776 int NRHS = 1;
777 int* IPIV = new int[N];
778
779 // Compute the constant term.
781
782 // Compute the max-norm of F(0) to use as an exit criterion.
783 for (int i = 0; i < N; i++) {
784 X1[i] = 0.0;
785 }
786
787 const Real initNorm = computeNorm(F, X1, C);
788
789 bool converged = true;
790
791 for (int k = 0; k < m_maxIter; k++) {
792
793 // Numerically computed the Jacobian using finite differences. The Jacobian is given by
794 // Jij = dF_i/dx_j
795 for (int j = 0; j < N; j++) {
796
797 for (int s = 0; s < N; s++) {
798 X1[s] = X[s];
799 X2[s] = X[s];
800 }
801
802 X2[j] += std::max(0.01 * X[j], 1.0);
803
804 computeF(F1, X1, C);
805 computeF(F2, X2, C);
806
807 for (int i = 0; i < N; i++) {
808 J[i + j * N] = (F2[i] - F1[i]) / (X2[j] - X1[j]);
809 }
810 }
811
812 // Compute F
813 computeF(F, X, C);
814
815 // Solve J*dX = F, but note that the true system is J*dX = -F, so we invert the
816 // dX vector below.
817 dgesv_((int*)&N, (int*)&NRHS, J, (int*)&N, IPIV, F, (int*)&N, &INFO);
818
819 if (INFO != 0) {
820#if 1 // Could not solve
821 const std::string err = "KMCSolver<R, State, T>::stepImplicitEuler -- could not solve A*x = b";
822
823 pout() << err << endl;
824#endif
825 converged = false;
826
827 break;
828 }
829 else {
830 // Increment and move on to next iteration if necessary. Note that F = -dX as per the comment above.
831 for (int i = 0; i < N; i++) {
832 X[i] = X[i] - F[i];
833 }
834 }
835
836 // Recompute the norm and exit if necessary.
837 const Real norm = computeNorm(F, X, C);
838
839 if (norm / initNorm < m_exitTol) {
840 break;
841 }
842 }
843
844 // Turn X into an integer state.
846
847 if (converged) {
848 for (int i = 0; i < N; i++) {
849 outputState[i] = static_cast<T>(llround(X[i]));
850 }
851 }
852 else {
853 // Set X to (what is hopefully) an invalid state and rely on step rejection.
854 for (int i = 0; i < N; i++) {
855 outputState[i] = static_cast<T>(-1);
856 }
857 }
858
859 a_state.linearIn(outputState);
860
861 // Cleanup
862 delete[] J;
863 delete[] X;
864 delete[] F;
865 delete[] C;
866
867 delete[] X1;
868 delete[] X2;
869 delete[] F1;
870 delete[] F2;
871
872 delete[] IPIV;
873
874 return;
875}
876
877template <typename R, typename State, typename T>
878inline void
880 const Real& a_dt,
881 const KMCLeapPropagator& a_leapPropagator) const noexcept
882{
883 CH_TIME("KMCSolver::advanceTau(State, ReactionList, Real, KMCLeapPropagator)");
884
885 this->advanceTau(a_state, m_reactions, a_dt, a_leapPropagator);
886}
887
888template <typename R, typename State, typename T>
889inline void
891 const ReactionList& a_reactions,
892 const Real& a_dt,
893 const KMCLeapPropagator& a_leapPropagator) const noexcept
894{
895 CH_TIME("KMCSolver::advanceTau(State, ReactionList, Real, KMCLeapPropagator)");
896
897 if (a_reactions.size() > 0) {
898 Real curTime = 0.0;
899 Real curDt = a_dt;
900
901 while (curTime < a_dt) {
902
903 // Try big time step first.
904 curDt = a_dt - curTime;
905
906 bool valid = false;
907
908 // Substepping so we end up with a valid state.
909 while (!valid) {
910
911 State state = a_state;
912
913 // Do a tau-leaping step.
914 switch (a_leapPropagator) {
915 case KMCLeapPropagator::ExplicitEuler: {
916 this->stepExplicitEuler(state, a_reactions, curDt);
917
918 break;
919 }
920 case KMCLeapPropagator::Midpoint: {
921 this->stepMidpoint(state, a_reactions, curDt);
922
923 break;
924 }
925 case KMCLeapPropagator::PRC: {
926 this->stepPRC(state, a_reactions, curDt);
927
928 break;
929 }
930 case KMCLeapPropagator::ImplicitEuler: {
931 this->stepImplicitEuler(state, a_reactions, curDt);
932
933 break;
934 }
935 default: {
936 break;
937 }
938 }
939
940 // If this was a valid step, accept it. Else reduce dt.
941 valid = state.isValidState();
942
943 if (valid) {
944 a_state = state;
945
946 curTime += curDt;
947 }
948 else {
949 curDt *= 0.5;
950 }
951 }
952 }
953 }
954}
955
956template <typename R, typename State, typename T>
957inline void
959 const Real a_dt,
960 const KMCLeapPropagator& a_leapPropagator) const noexcept
961{
962 CH_TIME("KMCSolver::advanceHybrid(State, Real, KMCLeapPropagator)");
963
964 this->advanceHybrid(a_state, m_reactions, a_dt, a_leapPropagator);
965}
966
967template <typename R, typename State, typename T>
968inline void
970 const ReactionList& a_reactions,
971 const Real a_dt,
972 const KMCLeapPropagator& a_leapPropagator) const noexcept
973{
974 CH_TIME("KMCSolver::advanceHybrid(State, ReactionList, Real, KMCLeapPropagator)");
975
976 switch (a_leapPropagator) {
977 case KMCLeapPropagator::ExplicitEuler: {
978 this->advanceHybrid(a_state, a_reactions, a_dt, [this](State& s, const ReactionList& r, const Real dt) {
979 this->stepExplicitEuler(s, r, dt);
980 });
981
982 break;
983 }
984 case KMCLeapPropagator::Midpoint: {
985 this->advanceHybrid(a_state, a_reactions, a_dt, [this](State& s, const ReactionList& r, const Real dt) {
986 this->stepMidpoint(s, r, dt);
987 });
988
989 break;
990 }
991 case KMCLeapPropagator::PRC: {
992 this->advanceHybrid(a_state, a_reactions, a_dt, [this](State& s, const ReactionList& r, const Real dt) {
993 this->stepPRC(s, r, dt);
994 });
995
996 break;
997 }
998 case KMCLeapPropagator::ImplicitEuler: {
999 this->advanceHybrid(a_state, a_reactions, a_dt, [this](State& s, const ReactionList& r, const Real dt) {
1000 this->stepImplicitEuler(s, r, dt);
1001 });
1002
1003 break;
1004 }
1005 default: {
1006 MayDay::Error("KMCSolver::advanceHybrid - unknown leap propagator requested");
1007 }
1008 }
1009}
1010
1011template <typename R, typename State, typename T>
1012inline void
1014 State& a_state,
1015 const ReactionList& a_reactions,
1016 const Real a_dt,
1017 const std::function<void(State&, const ReactionList& a_reactions, const Real a_dt)>& a_propagator) const noexcept
1018{
1019 CH_TIME("KMCSolver::advanceHybrid(State, ReactionList, Real, std::function)");
1020
1021 constexpr T one = (T)1;
1022
1023 // Simulated time within the advancement algorithm.
1024 Real curTime = 0.0;
1025
1026 // Outer loop is for reactive substepping over a_dt.
1027 while (curTime < a_dt) {
1028
1029 // Partition reactions into critical and non-critical reactions and compute the critical and non-critical time steps.
1031
1032 const ReactionList& criticalReactions = partitionedReactions.first;
1033 const ReactionList& nonCriticalReactions = partitionedReactions.second;
1034
1035 const std::vector<Real> propensitiesCrit = this->propensities(a_state, criticalReactions);
1037
1038 Real dtCrit = this->getCriticalTimeStep(propensitiesCrit);
1039 Real dtNonCrit = this->getNonCriticalTimeStep(a_state, nonCriticalReactions, propensitiesNonCrit);
1040
1041 // Try the various advancement algorithms; the loop is for step rejection in case we end up with an invalid state, e.g.
1042 // a state with a negative number of particles.
1043 bool validStep = false;
1044
1045 while (!validStep) {
1046
1047 // Do a backup of the advancement state to operate on. This is necessary because the tau-leaping
1048 // algorithms advance the state but we may need to reject those steps if we end up with a thermodynamically
1049 // invalid state.
1050 State state = a_state;
1051
1052 // Compute the time step to be used.
1053 const Real curDt = std::min(a_dt - curTime, std::min(dtCrit, dtNonCrit));
1054
1055 // Are we only doing non-critical reactions?
1056 const bool nonCriticalOnly = (dtNonCrit < dtCrit) || (criticalReactions.size() == 0) ||
1057 (dtCrit > (a_dt - curTime));
1058
1059 if (nonCriticalOnly) {
1060
1061 // Compute the total propensity because it might actually be better to run SSA steps.
1062 Real A = this->totalPropensity(state, a_reactions);
1063
1064 if (A * curDt < m_SSAlim) {
1065 // TLDR: Tau-leaping is inefficient here so we switch to an SSA-based algorithm for the WHOLE reaction set.
1066
1067 // Number of SSA steps taken and simulated time within the SSA. We will compute until either dtSSA < curDt or
1068 // we've exceeded the maximum number of SSA steps that the user has permitted (m_numSSA).
1069 Real dtSSA = 0.0;
1070 T numSSA = 0;
1071
1072 while (dtSSA < curDt && numSSA < m_numSSA) {
1073
1074 // Recompute propensities for the full reaction set and advance everything using the SSA.
1075 std::vector<Real> propensities = this->propensities(a_state, a_reactions);
1076
1077 A = 0.0;
1078 for (size_t i = 0; i < propensities.size(); i++) {
1079 A += propensities[i];
1080 }
1081
1082 // Compute the time to the next reaction.
1083 const Real dtReact = this->getCriticalTimeStep(A);
1084
1085 if (dtSSA + dtReact < curDt) {
1086 this->stepSSA(a_state, a_reactions, propensities);
1087
1088 dtSSA += dtReact;
1089 numSSA += one;
1090 }
1091 else {
1092
1093 // Next reaction occured outside the substep -- break out of the loop.
1094 dtSSA = curDt;
1095 }
1096 }
1097
1098 validStep = true;
1099 curTime += dtSSA;
1100 }
1101 else {
1102 // Do the tau-leaping.
1104
1105 // Check if we need to reject the state and rather try again with a smaller non-critical time step.
1106 validStep = state.isValidState();
1107
1108 if (validStep) {
1109 a_state = state;
1110
1111 curTime += curDt;
1112 }
1113 else {
1114 dtNonCrit *= 0.5;
1115 }
1116 }
1117 }
1118 else {
1119 // TLDR: Here, one critical reaction fired. We advance the critical reaction using the SSA and the rest of the reactions
1120 // using tau-leaping.
1121 this->stepSSA(state, criticalReactions, propensitiesCrit);
1122
1123 // Do the tau-leaping over the non-critical reactions.
1125
1126 // Check if we need to reject the state and rather try again with a smaller non-critical time step.
1127 validStep = state.isValidState();
1128
1129 if (validStep) {
1130 a_state = state;
1131
1132 curTime += curDt;
1133 }
1134 else {
1135 dtNonCrit *= 0.5;
1136 }
1137 }
1138 }
1139 }
1140}
1141
1142#include <CD_NamespaceFooter.H>
1143
1144#endif
Class for running Kinetic Monte Carlo functionality.
KMCLeapPropagator
Supported propagators for hybrid tau leaping.
Definition CD_KMCSolver.H:33
Interface to some LaPack routines.
void dgesv_(int *N, int *NRHS, double *A, int *LDA, int *IPIV, double *B, int *LDB, int *INFO)
Interface to LaPack for computing solution to Ax=b.
File containing some useful static methods related to random number generation.
virtual ~KMCSolver() noexcept
Destructor.
Definition CD_KMCSolverImplem.H:45
void advanceHybrid(State &a_state, const Real a_dt, const KMCLeapPropagator &a_leapPropagator=KMCLeapPropagator::ExplicitEuler) const noexcept
Advance using Cao et. al. hybrid algorithm over the input time. This can end up using substepping.
Definition CD_KMCSolverImplem.H:958
void stepPRC(State &a_state, const Real a_dt) const noexcept
Perform one leaping step using the PRC method for ALL reactions over a time step a_dt.
Definition CD_KMCSolverImplem.H:612
void stepMidpoint(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:567
Real computeDt(const State &a_state, const ReactionList &a_reactions, const std::vector< Real > &a_propensities, const Real a_epsilon) const noexcept
Compute a time step, using the leap condition on the mean value with eps = 1.
Definition CD_KMCSolverImplem.H:361
void define(const ReactionList &a_reactions) noexcept
Define function. Sets the reactions.
Definition CD_KMCSolverImplem.H:52
void setSolverParameters(const T a_numCrit, const T a_numSSA, const T a_maxIter, const Real a_eps, const Real a_SSAlim, const Real a_exitTol) noexcept
Set solver parameters.
Definition CD_KMCSolverImplem.H:64
KMCSolver() noexcept
Default constructor – must subsequently define the object.
Definition CD_KMCSolverImplem.H:29
std::vector< std::vector< T > > getNu(const State &a_state, const ReactionList &a_reactions) const noexcept
Compute the state vector changes for all reactions.
Definition CD_KMCSolverImplem.H:83
Real getCriticalTimeStep(const State &a_state) const noexcept
Get the time to the next critical reaction.
Definition CD_KMCSolverImplem.H:204
void stepExplicitEuler(State &a_state, const Real a_dt) const noexcept
Perform one plain tau-leaping step using ALL reactions.
Definition CD_KMCSolverImplem.H:534
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:495
std::pair< ReactionList, ReactionList > partitionReactions(const State &a_state) const noexcept
Partition reactions into critical and non-critical reactions.
Definition CD_KMCSolverImplem.H:170
void stepSSA(State &a_state) const noexcept
Perform a single SSA step.
Definition CD_KMCSolverImplem.H:430
void advanceTau(State &a_state, const Real &a_dt, const KMCLeapPropagator &a_leapPropagator=KMCLeapPropagator::ExplicitEuler) const noexcept
Advance using a specified tau-leaping algorithm.
Definition CD_KMCSolverImplem.H:879
Real getNonCriticalTimeStep(const State &a_state) const noexcept
Get the non-critical time step.
Definition CD_KMCSolverImplem.H:260
Real totalPropensity(const State &a_state) const noexcept
Compute the total propensity for ALL reactions.
Definition CD_KMCSolverImplem.H:144
void stepImplicitEuler(State &a_state, const Real a_dt) const noexcept
Perform one implicit Euler tau-leaping step using ALL reactions.
Definition CD_KMCSolverImplem.H:661
std::vector< Real > propensities(const State &a_state) const noexcept
Compute propensities for ALL reactions.
Definition CD_KMCSolverImplem.H:118
static Real getUniformReal01()
Get a uniform real number on the interval [0,1].
Definition CD_RandomImplem.H:152
Base class for a tracer particle solver. This solver can advance particles in a pre-defined velocity ...
Definition CD_TracerParticleSolver.H:37
TracerParticleSolver()
Default constructor.
Definition CD_TracerParticleSolverImplem.H:25