13#ifndef CD_KMCSOLVERIMPLEM_H
14#define CD_KMCSOLVERIMPLEM_H
18#include <unordered_set>
29#include <CD_NamespaceHeader.H>
31template <
typename R,
typename State,
typename T>
34 this->setSolverParameters(0, 0, 100, std::numeric_limits<Real>::max(), 0.0, 1.E-6);
37template <
typename R,
typename State,
typename T>
40 this->define(a_reactions);
43template <
typename R,
typename State,
typename T>
47template <
typename R,
typename State,
typename T>
51 m_reactions = a_reactions;
54 this->setSolverParameters(0, 0, 100, std::numeric_limits<Real>::max(), 0.0, 1.E-6);
57template <
typename R,
typename State,
typename T>
64 const Real a_exitTol)
noexcept
68 m_maxIter = a_maxIter;
71 m_exitTol = a_exitTol;
74template <
typename R,
typename State,
typename T>
75inline std::vector<std::vector<T>>
78 std::vector<std::vector<T>> ret(a_reactions.size());
80 for (
int j = 0; j < a_reactions.size(); j++) {
81 std::vector<T>& nu = ret[j];
84 State state = a_state;
86 std::vector<T> preState = state.linearOut();
87 for (
auto& x : preState) {
88 x =
static_cast<T
>(0);
90 state.linearIn(preState);
93 a_reactions[j]->advanceState(state,
static_cast<T
>(1));
95 std::vector<T> postState = state.linearOut();
98 nu.resize(preState.size());
99 for (
int i = 0; i < preState.size(); i++) {
100 nu[i] = postState[i] - preState[i];
107template <
typename R,
typename State,
typename T>
108inline std::vector<Real>
111 return this->propensities(a_state, m_reactions);
114template <
typename R,
typename State,
typename T>
115inline std::vector<Real>
118 std::vector<Real> A(a_reactions.size());
120 const size_t numReactions = a_reactions.size();
122 for (
size_t i = 0; i < numReactions; i++) {
123 A[i] = a_reactions[i]->propensity(a_state);
129template <
typename R,
typename State,
typename T>
133 return this->totalPropensity(a_state, m_reactions);
136template <
typename R,
typename State,
typename T>
142 const size_t numReactions = a_reactions.size();
144 for (
size_t i = 0; i < numReactions; i++) {
145 A += a_reactions[i]->propensity(a_state);
151template <
typename R,
typename State,
typename T>
155 return this->partitionReactions(a_state, m_reactions);
158template <
typename R,
typename State,
typename T>
165 const size_t numReactions = a_reactions.size();
169 criticalReactions.reserve(numReactions);
170 nonCriticalReactions.reserve(numReactions);
172 for (
size_t i = 0; i < numReactions; i++) {
173 const T Lj = a_reactions[i]->computeCriticalNumberOfReactions(a_state);
176 criticalReactions.emplace_back(a_reactions[i]);
179 nonCriticalReactions.emplace_back(a_reactions[i]);
185 return std::make_pair(std::move(criticalReactions), std::move(nonCriticalReactions));
188template <
typename R,
typename State,
typename T>
193 return this->getCriticalTimeStep(a_state, m_reactions);
196template <
typename R,
typename State,
typename T>
203 Real dt = std::numeric_limits<Real>::max();
205 if (a_criticalReactions.size() > 0) {
207 const Real A = std::numeric_limits<Real>::min() + this->totalPropensity(a_state, a_criticalReactions);
209 dt = this->getCriticalTimeStep(A);
215template <
typename R,
typename State,
typename T>
220 Real A = std::numeric_limits<Real>::min();
222 for (
const auto& p : a_propensities) {
226 return this->getCriticalTimeStep(A);
229template <
typename R,
typename State,
typename T>
235 return log(1.0 / u) / a_totalPropensity;
238template <
typename R,
typename State,
typename T>
242 const auto& partitionedReactions = this->partitionReactions(a_state, m_reactions);
244 const std::vector<Real> propensities = this->propensities(a_state, partitionedReactions.second);
246 return this->getNonCriticalTimeStep(a_state, m_reactions, partitionedReactions.second, propensities);
249template <
typename R,
typename State,
typename T>
253 const std::vector<Real> propensities = this->propensities(a_state, a_reactions);
255 return this->getNonCriticalTimeStep(a_state, a_reactions, a_reactions, propensities);
258template <
typename R,
typename State,
typename T>
264 size_t numSpecies = 0;
266 for (
const auto& reaction : a_reactions) {
267 for (
const size_t reactant : reaction->getReactants()) {
268 numSpecies = std::max(numSpecies, reactant + 1);
272 if (m_seenScratch.size() < numSpecies) {
273 m_seenScratch.resize(numSpecies, 0);
274 m_muScratch.resize(numSpecies, 0.0);
275 m_sigmaScratch.resize(numSpecies, 0.0);
276 m_lossScratch.resize(numSpecies, 0.0);
281 m_reactantScratch.clear();
283 for (
const auto& reaction : a_reactions) {
284 for (
const size_t reactant : reaction->getReactants()) {
285 if (!m_seenScratch[reactant]) {
286 m_seenScratch[reactant] = 1;
288 m_reactantScratch.push_back(reactant);
294 for (
const size_t reactant : m_reactantScratch) {
295 m_seenScratch[reactant] = 0;
299template <
typename R,
typename State,
typename T>
304 const std::vector<Real>& a_nonCriticalPropensities)
const noexcept
306 CH_assert(a_nonCriticalReactions.size() == a_nonCriticalPropensities.size());
307 CH_assert(a_nonCriticalReactions.size() <= a_reactions.size());
309 constexpr Real one = 1.0;
311 Real dt = std::numeric_limits<Real>::max();
313 const size_t numReactions = a_nonCriticalReactions.size();
315 if (numReactions > 0) {
320 this->gatherDistinctReactants(a_reactions);
322 for (
const size_t reactant : m_reactantScratch) {
323 m_muScratch[reactant] = 0.0;
324 m_sigmaScratch[reactant] = 0.0;
325 m_lossScratch[reactant] = 0.0;
333 for (
size_t i = 0; i < numReactions; i++) {
334 const Real& p = a_nonCriticalPropensities[i];
336 for (
const size_t reactant : m_reactantScratch) {
337 const auto muIJ = a_nonCriticalReactions[i]->getStateChange(reactant);
339 m_muScratch[reactant] += muIJ * p;
340 m_sigmaScratch[reactant] += muIJ * muIJ * p;
343 m_lossScratch[reactant] -= muIJ * p;
349 for (
const size_t reactant : m_reactantScratch) {
356 const T Xi = a_nonCriticalReactions[0]->population(reactant, a_state);
360 constexpr Real gi = 1.0;
362 Real dt1 = std::numeric_limits<Real>::max();
363 Real dt2 = std::numeric_limits<Real>::max();
364 Real dt3 = std::numeric_limits<Real>::max();
369 const Real f = std::max(m_eps * Xi / gi, one);
371 const Real mu = std::abs(m_muScratch[reactant]);
372 const Real sigma2 = std::abs(m_sigmaScratch[reactant]);
373 const Real loss = m_lossScratch[reactant];
376 if (mu > std::numeric_limits<Real>::min()) {
379 if (sigma2 > std::numeric_limits<Real>::min()) {
380 dt2 = (f * f) / sigma2;
386 if (loss > std::numeric_limits<Real>::min()) {
390 dt = std::min(dt, std::min(dt1, std::min(dt2, dt3)));
397template <
typename R,
typename State,
typename T>
401 const std::vector<Real>& a_propensities,
402 const Real a_epsilon)
const noexcept
404 CH_assert(a_reactions.size() == a_propensities.size());
406 constexpr Real one = 1.0;
408 Real dt = std::numeric_limits<Real>::max();
410 const size_t numReactions = a_reactions.size();
412 if (numReactions > 0) {
415 this->gatherDistinctReactants(a_reactions);
417 for (
const size_t reactant : m_reactantScratch) {
418 m_muScratch[reactant] = 0.0;
424 for (
size_t i = 0; i < numReactions; i++) {
425 const Real& p = a_propensities[i];
427 for (
const size_t reactant : m_reactantScratch) {
428 const auto muIJ = a_reactions[i]->getStateChange(reactant);
430 m_muScratch[reactant] += muIJ * p;
435 for (
const size_t reactant : m_reactantScratch) {
442 const T Xi = R::population(reactant, a_state);
448 constexpr Real gi = 1.0;
450 const Real f = std::max(a_epsilon * Xi / gi, one);
451 const Real mu = std::abs(m_muScratch[reactant]);
453 if (mu > std::numeric_limits<Real>::min()) {
454 dt = std::min(dt, f / mu);
463template <
typename R,
typename State,
typename T>
467 this->stepSSA(a_state, m_reactions);
470template <
typename R,
typename State,
typename T>
474 if (a_reactions.size() > 0) {
477 const std::vector<Real> propensities = this->propensities(a_state, a_reactions);
479 this->stepSSA(a_state, a_reactions, propensities);
483template <
typename R,
typename State,
typename T>
487 const std::vector<Real>& a_propensities)
const noexcept
489 CH_assert(a_reactions.size() == a_propensities.size());
491 const size_t numReactions = a_reactions.size();
493 if (numReactions > 0) {
494 constexpr T one = (T)1;
498 for (
size_t i = 0; i < numReactions; i++) {
499 A += a_propensities[i];
504 size_t r = numReactions - 1;
507 for (
size_t i = 0; i + 1 < numReactions; i++) {
508 sumProp += a_propensities[i];
510 if (sumProp >= u * A) {
517 CH_assert(r < a_reactions.size());
520 a_reactions[r]->advanceState(a_state, one);
524template <
typename R,
typename State,
typename T>
528 this->advanceSSA(a_state, m_reactions, a_dt);
531template <
typename R,
typename State,
typename T>
535 const size_t numReactions = a_reactions.size();
537 if (numReactions > 0) {
542 while (curDt <= a_dt) {
545 const std::vector<Real> propensities = this->propensities(a_state, a_reactions);
547 const Real nextDt = this->getCriticalTimeStep(propensities);
550 if (curDt + nextDt <= a_dt) {
551 this->stepSSA(a_state, a_reactions, propensities);
559template <
typename R,
typename State,
typename T>
563 this->stepExplicitEuler(a_state, m_reactions, a_dt);
566template <
typename R,
typename State,
typename T>
570 const Real a_dt)
const noexcept
572 CH_assert(a_dt > 0.0);
574 if (a_reactions.size() > 0) {
575 const std::vector<Real> propensities = this->propensities(a_state, a_reactions);
577 for (
size_t i = 0; i < a_reactions.size(); i++) {
581 const T numReactions = (T)Random::getPoisson<long long>(propensities[i] * a_dt);
583 a_reactions[i]->advanceState(a_state, numReactions);
588template <
typename R,
typename State,
typename T>
592 this->stepMidpoint(a_state, m_reactions, a_dt);
595template <
typename R,
typename State,
typename T>
599 const int numReactions = a_reactions.size();
601 if (numReactions > 0) {
603 std::vector<Real> propensities = this->propensities(a_state, a_reactions);
605 State Xdagger = a_state;
607 for (
size_t i = 0; i < numReactions; i++) {
610 a_reactions[i]->advanceState(Xdagger, (T)std::round(0.5 * propensities[i] * a_dt));
613 propensities = this->propensities(Xdagger, a_reactions);
615 for (
size_t i = 0; i < numReactions; i++) {
616 const T curReactions = (T)Random::getPoisson<long long>(propensities[i] * a_dt);
618 a_reactions[i]->advanceState(a_state, curReactions);
623template <
typename R,
typename State,
typename T>
627 this->stepPRC(a_state, m_reactions, a_dt);
630template <
typename R,
typename State,
typename T>
634 const int numReactions = a_reactions.size();
636 if (numReactions > 0) {
638 std::vector<Real> aj = this->propensities(a_state, a_reactions);
640 const std::vector<Real> ak = aj;
642 for (
int j = 0; j < numReactions; j++) {
643 for (
int k = 0; k < numReactions; k++) {
646 a_reactions[k]->advanceState(x, (T)1);
648 const Real etajk = a_reactions[j]->propensity(x) - ak[j];
650 aj[j] += 0.5 * a_dt * ak[k] * etajk;
654 for (
size_t i = 0; i < numReactions; i++) {
655 const T nr = (T)Random::getPoisson<long long>(aj[i] * a_dt);
657 a_reactions[i]->advanceState(a_state, nr);
662template <
typename R,
typename State,
typename T>
666 this->stepImplicitEuler(a_state, m_reactions, a_dt);
669template <
typename R,
typename State,
typename T>
673 const Real a_dt)
const noexcept
685 const std::vector<T> inputState = a_state.linearOut();
686 const std::vector<std::vector<T>> nu = this->getNu(a_state, a_reactions);
687 const std::vector<Real> ajX = this->propensities(a_state, a_reactions);
690 const int N = inputState.size();
691 const int M = a_reactions.size();
694 auto compConstantTerm = [&](
double* C,
double* X,
const State& state,
const Real a_dt) ->
void {
698 State explicitEulerState = state;
702 const std::vector<T> eulerOut = explicitEulerState.linearOut();
705 for (
int i = 0; i < N; i++) {
706 C[i] = 1.0 * eulerOut[i];
707 X[i] = 1.0 * eulerOut[i];
711 for (
int j = 0; j < M; j++) {
712 const std::vector<T>& nuj = nu[j];
714 for (
int i = 0; i < N; i++) {
715 C[i] -= nuj[i] * ajX[j] * a_dt;
721 auto computeF = [&](
double* F,
const double* Xit,
const double* C) ->
void {
723 State stateXit = a_state;
725 std::vector<T> linState(N);
726 for (
int i = 0; i < N; i++) {
727 linState[i] =
static_cast<T
>(llround(Xit[i]));
730 stateXit.linearIn(linState);
732 const std::vector<Real> ajXit = this->propensities(stateXit);
735 for (
int i = 0; i < N; i++) {
736 F[i] = Xit[i] - C[i];
739 for (
int j = 0; j < M; j++) {
740 const std::vector<T>& nuj = nu[j];
742 for (
int i = 0; i < N; i++) {
743 F[i] -= a_dt * nuj[i] * ajXit[j];
749 auto computeNorm = [&](
double* F,
const double* X,
const double* C) -> Real {
754 for (
int i = 0; i < N; i++) {
755 norm = std::max(norm, std::abs(F[i]));
763 std::vector<double> J(
static_cast<size_t>(N * N));
764 std::vector<double> X(
static_cast<size_t>(N));
765 std::vector<double> F(
static_cast<size_t>(N));
766 std::vector<double> C(
static_cast<size_t>(N));
769 std::vector<double> X1(
static_cast<size_t>(N));
770 std::vector<double> X2(
static_cast<size_t>(N));
771 std::vector<double> F2(
static_cast<size_t>(N));
776 std::vector<int> IPIV(
static_cast<size_t>(N));
779 compConstantTerm(C.data(), X.data(), a_state, a_dt);
782 for (
int i = 0; i < N; i++) {
786 const Real initNorm = computeNorm(F.data(), X1.data(), C.data());
788 bool converged =
true;
790 for (
int k = 0; k < m_maxIter; k++) {
795 computeF(F.data(), X.data(), C.data());
799 for (
int j = 0; j < N; j++) {
801 for (
int s = 0; s < N; s++) {
805 X2[j] += std::max(0.01 * X[j], 1.0);
807 computeF(F2.data(), X2.data(), C.data());
809 for (
int i = 0; i < N; i++) {
810 J[i + j * N] = (F2[i] - F[i]) / (X2[j] - X[j]);
816 dgesv_((
int*)&N, &NRHS, J.data(), (
int*)&N, IPIV.data(), F.data(), (
int*)&N, &INFO);
820 const std::string err =
"KMCSolver<R, State, T>::stepImplicitEuler -- could not solve A*x = b";
822 pout() << err << endl;
830 for (
int i = 0; i < N; i++) {
836 const Real norm = computeNorm(F.data(), X.data(), C.data());
838 if (norm / initNorm < m_exitTol) {
844 std::vector<T> outputState(N);
847 for (
int i = 0; i < N; i++) {
848 outputState[i] =
static_cast<T
>(llround(X[i]));
853 for (
int i = 0; i < N; i++) {
854 outputState[i] =
static_cast<T
>(-1);
858 a_state.linearIn(outputState);
861template <
typename R,
typename State,
typename T>
867 this->advanceTau(a_state, m_reactions, a_dt, a_leapPropagator);
870template <
typename R,
typename State,
typename T>
877 if (a_reactions.size() > 0) {
880 while (curTime < a_dt) {
885 const std::vector<Real> propensities = this->propensities(a_state, a_reactions);
887 const Real dtLeap = this->getNonCriticalTimeStep(a_state, a_reactions, a_reactions, propensities);
889 Real curDt = std::min(a_dt - curTime, dtLeap);
896 State state = a_state;
899 switch (a_leapPropagator) {
901 this->stepExplicitEuler(state, a_reactions, curDt);
906 this->stepMidpoint(state, a_reactions, curDt);
911 this->stepPRC(state, a_reactions, curDt);
916 this->stepImplicitEuler(state, a_reactions, curDt);
926 valid = state.isValidState();
941template <
typename R,
typename State,
typename T>
947 this->advanceHybrid(a_state, m_reactions, a_dt, a_leapPropagator);
950template <
typename R,
typename State,
typename T>
957 switch (a_leapPropagator) {
959 this->advanceHybrid(a_state, a_reactions, a_dt, [
this](State& s,
const ReactionList& r,
const Real dt) {
960 this->stepExplicitEuler(s, r, dt);
966 this->advanceHybrid(a_state, a_reactions, a_dt, [
this](State& s,
const ReactionList& r,
const Real dt) {
967 this->stepMidpoint(s, r, dt);
973 this->advanceHybrid(a_state, a_reactions, a_dt, [
this](State& s,
const ReactionList& r,
const Real dt) {
974 this->stepPRC(s, r, dt);
980 this->advanceHybrid(a_state, a_reactions, a_dt, [
this](State& s,
const ReactionList& r,
const Real dt) {
981 this->stepImplicitEuler(s, r, dt);
987 MayDay::Error(
"KMCSolver::advanceHybrid - unknown leap propagator requested");
992template <
typename R,
typename State,
typename T>
998 const std::function<
void(State&,
const ReactionList& a_reactions,
const Real a_dt)>& a_propagator)
const noexcept
1000 constexpr T one = (T)1;
1006 while (curTime < a_dt) {
1011 const std::pair<ReactionList, ReactionList> partitionedReactions = this->partitionReactions(a_state, a_reactions);
1013 const ReactionList& criticalReactions = partitionedReactions.first;
1014 const ReactionList& nonCriticalReactions = partitionedReactions.second;
1016 const std::vector<Real> propensitiesCrit = this->propensities(a_state, criticalReactions);
1017 const std::vector<Real> propensitiesNonCrit = this->propensities(a_state, nonCriticalReactions);
1020 Real dtNonCrit = this->getNonCriticalTimeStep(a_state, a_reactions, nonCriticalReactions, propensitiesNonCrit);
1023 const Real A = this->totalPropensity(a_state, a_reactions);
1027 bool validStep =
false;
1029 while (!validStep) {
1033 const Real dtLeap = std::min(a_dt - curTime, dtNonCrit);
1039 const bool useSSA = (m_numSSA >= one) && (A * dtLeap < m_SSAlim);
1048 while (dtSSA < dtLeap && numSSA < m_numSSA) {
1051 const std::vector<Real> propensities = this->propensities(a_state, a_reactions);
1053 const Real dtReact = this->getCriticalTimeStep(propensities);
1055 if (dtSSA + dtReact < dtLeap) {
1056 this->stepSSA(a_state, a_reactions, propensities);
1072 const Real dtCrit = (criticalReactions.size() > 0) ? this->getCriticalTimeStep(propensitiesCrit)
1073 : std::numeric_limits<Real>::max();
1077 const bool fireCritical = (dtCrit < dtLeap);
1079 const Real curDt = fireCritical ? dtCrit : dtLeap;
1082 State state = a_state;
1089 a_propagator(state, nonCriticalReactions, curDt);
1092 this->stepSSA(state, criticalReactions, propensitiesCrit);
1096 validStep = state.isValidState();
1111#include <CD_NamespaceFooter.H>
Class for running Kinetic Monte Carlo functionality.
KMCLeapPropagator
Supported propagators for hybrid tau leaping.
Definition CD_KMCSolver.H:35
@ ImplicitEuler
Implicit Euler tau leaping.
@ Midpoint
Gillespie's midpoint method.
@ ExplicitEuler
Regular tau leaping.
@ PRC
Hu and Li's Poisson random correction method.
Interface to some LaPack routines.
File containing some useful static methods related to random number generation.
std::vector< std::shared_ptr< const R > > ReactionList
Alias for the list of reactions.
Definition CD_KMCSolver.H:66
void setSolverParameters(T a_numCrit, T a_numSSA, T a_maxIter, Real a_eps, Real a_SSAlim, Real a_exitTol) noexcept
Set solver parameters.
Definition CD_KMCSolverImplem.H:59
Real computeDt(const State &a_state, const ReactionList &a_reactions, const std::vector< Real > &a_propensities, Real a_epsilon) const noexcept
Compute a time step using the leap condition on the mean value.
Definition CD_KMCSolverImplem.H:399
virtual ~KMCSolver() noexcept
Destructor.
Definition CD_KMCSolverImplem.H:44
void define(const ReactionList &a_reactions) noexcept
Define function. Sets the reactions.
Definition CD_KMCSolverImplem.H:49
KMCSolver() noexcept
Default constructor. Must subsequently call define.
Definition CD_KMCSolverImplem.H:32
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:76
Real getCriticalTimeStep(const State &a_state) const noexcept
Get the time to the next critical reaction.
Definition CD_KMCSolverImplem.H:190
void advanceSSA(State &a_state, Real a_dt) const noexcept
Advance with the SSA over the input time. This can end up using substepping.
Definition CD_KMCSolverImplem.H:526
void gatherDistinctReactants(const ReactionList &a_reactions) const noexcept
Fill m_reactantScratch with the distinct reactants of the input reactions.
Definition CD_KMCSolverImplem.H:260
void stepImplicitEuler(State &a_state, Real a_dt) const noexcept
Perform one implicit Euler tau-leaping step using ALL reactions.
Definition CD_KMCSolverImplem.H:664
void advanceHybrid(State &a_state, 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:943
void stepPRC(State &a_state, Real a_dt) const noexcept
Perform one leaping step using the PRC method for ALL reactions.
Definition CD_KMCSolverImplem.H:625
void stepMidpoint(State &a_state, Real a_dt) const noexcept
Perform one leaping step using the midpoint method for ALL reactions.
Definition CD_KMCSolverImplem.H:590
std::pair< ReactionList, ReactionList > partitionReactions(const State &a_state) const noexcept
Partition reactions into critical and non-critical reactions.
Definition CD_KMCSolverImplem.H:153
void stepSSA(State &a_state) const noexcept
Perform a single SSA step.
Definition CD_KMCSolverImplem.H:465
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:863
Real getNonCriticalTimeStep(const State &a_state) const noexcept
Get the non-critical time step.
Definition CD_KMCSolverImplem.H:240
Real totalPropensity(const State &a_state) const noexcept
Compute the total propensity for ALL reactions.
Definition CD_KMCSolverImplem.H:131
void stepExplicitEuler(State &a_state, Real a_dt) const noexcept
Perform one plain tau-leaping step using ALL reactions.
Definition CD_KMCSolverImplem.H:561
std::vector< Real > propensities(const State &a_state) const noexcept
Compute propensities for ALL reactions.
Definition CD_KMCSolverImplem.H:109
static Real getUniformReal01()
Get a uniform real number on the interval [0,1].
Definition CD_RandomImplem.H:158