rxn_utils.inl 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790
  1. /******************************************************************************
  2. *
  3. * Copyright (C) 2006-2017 by
  4. * The Salk Institute for Biological Studies and
  5. * Pittsburgh Supercomputing Center, Carnegie Mellon University
  6. *
  7. * Use of this source code is governed by an MIT-style
  8. * license that can be found in the LICENSE file or at
  9. * https://opensource.org/licenses/MIT.
  10. *
  11. ******************************************************************************/
  12. /**
  13. * This file is directly included into diffuse_react_event.cpp.
  14. * The reason why this is not a standard .cpp + .h file is to gove the compiler
  15. * the opportunity to inline these functions into methods of diffuse&react event.
  16. *
  17. * There is an exception, methods that schedule actions into the event's
  18. * new_diffuse_or_unimol_react_actions action queue stay in the diffuse_react_event_t
  19. * class.
  20. */
  21. #ifndef SRC4_RXN_UTILS_INC_
  22. #define SRC4_RXN_UTILS_INC_
  23. #include "bng/bng.h"
  24. #include "diffuse_react_event.h"
  25. #include "defines.h"
  26. #include "world.h"
  27. #include "partition.h"
  28. #include "geometry.h"
  29. #include "debug_config.h"
  30. using namespace std;
  31. namespace MCell {
  32. namespace RxnUtils {
  33. // ---------------------------------- bimolecular reactions ----------------------------------
  34. /*************************************************************************
  35. trigger_bimolecular:
  36. In: hash values of the two colliding molecules
  37. pointers to the two colliding molecules
  38. orientations of the two colliding molecules
  39. both zero away from a surface
  40. both nonzero (+-1) at a surface
  41. A is the moving molecule and B is the target
  42. array of pointers to the possible reactions
  43. Out: number of possible reactions for molecules reacA and reacB
  44. Also the first 'number' slots in the 'matching_rxns'
  45. array are filled with pointers to the possible reactions objects.
  46. Note: The target molecule is already scheduled and can be destroyed
  47. but not rescheduled. Assume we have or will check separately that
  48. the moving molecule is not inert!
  49. *************************************************************************/
  50. static void trigger_bimolecular(
  51. BNG::BNGEngine& bng_engine,
  52. const Molecule& reacA, const Molecule& reacB,
  53. orientation_t orientA, orientation_t orientB,
  54. BNG::RxnClassesVector& matching_rxn_classes // items are appended
  55. ) {
  56. BNG::RxnClass* rxn_class =
  57. bng_engine.get_all_rxns().get_bimol_rxn_class(reacA.species_id, reacB.species_id);
  58. if (rxn_class == nullptr) {
  59. // no reaction
  60. return;
  61. }
  62. assert(rxn_class->is_bimol() && "We already checked that there must be 2 reactants");
  63. /* Check to see if orientation classes are zero/different */
  64. int test_wall = 0;
  65. orientation_t geomA = rxn_class->get_reactant_orientation(0);
  66. orientation_t geomB = rxn_class->get_reactant_orientation(1);
  67. if (geomA == ORIENTATION_NONE || geomA == ORIENTATION_DEPENDS_ON_SURF_COMP ||
  68. geomB == ORIENTATION_NONE || geomB == ORIENTATION_DEPENDS_ON_SURF_COMP ||
  69. (geomA + geomB) * (geomA - geomB) != 0) {
  70. matching_rxn_classes.push_back(rxn_class);
  71. }
  72. else if (orientA != ORIENTATION_NONE && orientA * orientB * geomA * geomB > 0) {
  73. matching_rxn_classes.push_back(rxn_class);
  74. }
  75. }
  76. static void trigger_bimolecular_orientation_from_mols(
  77. BNG::BNGEngine& bng_engine,
  78. const Molecule& reacA, const Molecule& reacB,
  79. BNG::RxnClassesVector& matching_rxn_classes // items are appended
  80. ) {
  81. trigger_bimolecular(
  82. bng_engine,
  83. reacA, reacB,
  84. reacA.s.orientation, reacB.s.orientation,
  85. matching_rxn_classes
  86. );
  87. }
  88. /*************************************************************************
  89. *
  90. * find all surface reactions for any surface molecule with orientation
  91. * orientA on a surface class triggered via the ALL_MOLECULES and
  92. * ALL_SURFACE_MOLECULE keywords
  93. *
  94. * in: orientation of surface molecule
  95. * surface class species to test
  96. * number of matching reactions before the function call
  97. * flag signaling the presence of a transparent region border
  98. * flag signaling the presence of a reflective region border
  99. * flag signaling the presence of a absorbing region border
  100. * array holding matching reactions
  101. *
  102. * out: returns number of matching reactions
  103. * adds matching reactions to matching_rxns array
  104. *
  105. *************************************************************************/
  106. // to be called only from find_mol_reactions_with_surf_classes
  107. static void find_reactions_with_surf_classes_for_rxn_class_map(
  108. const Partition& p,
  109. const Molecule& reacA,
  110. const orientation_t reacA_orient,
  111. const Region& reg,
  112. const BNG::SpeciesRxnClassesMap& potential_reactions,
  113. const bool allow_rxn_transp_reflec_absorb_reg_border,
  114. BNG::RxnClassesVector& matching_rxn_classes
  115. ) {
  116. auto reactions_reacA_and_surface_it = potential_reactions.find(reg.species_id);
  117. if (reactions_reacA_and_surface_it == potential_reactions.end()) {
  118. // no reactions for this type of region
  119. return;
  120. }
  121. // NOTE: do we need to handle compartments here?
  122. BNG::RxnClass* rxn_class = reactions_reacA_and_surface_it->second;
  123. if (!allow_rxn_transp_reflec_absorb_reg_border &&
  124. rxn_class->is_reflect_transparent_or_absorb_region_border()
  125. ) {
  126. // do not allow this type
  127. return;
  128. }
  129. assert(rxn_class->is_bimol());
  130. if (rxn_class->get_num_reactions() == 0) {
  131. return;
  132. }
  133. orientation_t orient0 = rxn_class->get_reactant_orientation(0);
  134. orientation_t orient1 = rxn_class->get_reactant_orientation(1);
  135. // TODO: can we move this condition to some shared function?
  136. if ( (orient0 == 0) ||
  137. (orient1 == 0 || (orient0 + orient1) * (orient0 - orient1) != 0) ||
  138. (reacA_orient * orient0 * orient1 > 0) ) {
  139. matching_rxn_classes.push_back(rxn_class);
  140. }
  141. }
  142. /*************************************************************************
  143. *
  144. * find all volume reactions for any volume molecule with orientation
  145. * orientA with a surface class triggered via the ALL_MOLECULES and
  146. * ALL_VOLUME_MOLECULE keywords
  147. *
  148. * in: orientation of surface molecule
  149. * surface class species to test
  150. * number of matching reactions before the function call
  151. * flag signalling the presence of transparent region border
  152. * flag signalling the presence of a reflective region border
  153. * flag signalling the presence of a absorbing region border
  154. * array holding matching reactions
  155. *
  156. * out: returns number of matching reactions
  157. * adds matching reactions to matching_rxns array
  158. *
  159. *************************************************************************/
  160. template<typename WallOrObj>
  161. static void find_mol_reactions_with_surf_classes(
  162. Partition& p,
  163. const Molecule& reacA,
  164. const orientation_t reacA_orient,
  165. const WallOrObj& regions_list,
  166. const bool allow_rxn_transp_reflec_absorb_reg_border,
  167. BNG::RxnClassesVector& matching_rxns
  168. ) {
  169. // for all reactions applicable to reacA and and wall
  170. const BNG::SpeciesRxnClassesMap* species_rxns =
  171. p.get_all_rxns().get_bimol_rxns_for_reactant(reacA.species_id);
  172. const BNG::SpeciesRxnClassesMap* all_molecules_rxns =
  173. p.get_all_rxns().get_bimol_rxns_for_reactant(p.get_all_species().get_all_molecules_species_id());
  174. const BNG::SpeciesRxnClassesMap* all_vol_or_surf_rxns;
  175. if (reacA.is_vol()) {
  176. all_vol_or_surf_rxns = p.get_all_rxns().get_bimol_rxns_for_reactant(p.get_all_species().get_all_volume_molecules_species_id());
  177. }
  178. else {
  179. assert(reacA.is_surf());
  180. all_vol_or_surf_rxns = p.get_all_rxns().get_bimol_rxns_for_reactant(p.get_all_species().get_all_surface_molecules_species_id());
  181. }
  182. if (species_rxns == nullptr && all_molecules_rxns == nullptr && all_vol_or_surf_rxns == nullptr) {
  183. // no reactions at all for reacA
  184. return;
  185. }
  186. // for all reactive regions of a wall
  187. for (region_index_t region_index: regions_list.regions) {
  188. const Region& reg = p.get_region(region_index);
  189. if (!reg.is_reactive()) {
  190. continue;
  191. }
  192. if (species_rxns != nullptr) {
  193. find_reactions_with_surf_classes_for_rxn_class_map(
  194. p, reacA, reacA_orient, reg,
  195. *species_rxns,
  196. allow_rxn_transp_reflec_absorb_reg_border,
  197. matching_rxns
  198. );
  199. }
  200. if (all_molecules_rxns != nullptr) {
  201. find_reactions_with_surf_classes_for_rxn_class_map(
  202. p, reacA, reacA_orient, reg,
  203. *all_molecules_rxns,
  204. allow_rxn_transp_reflec_absorb_reg_border,
  205. matching_rxns
  206. );
  207. }
  208. if (all_vol_or_surf_rxns != nullptr) {
  209. find_reactions_with_surf_classes_for_rxn_class_map(
  210. p, reacA, reacA_orient, reg,
  211. *all_vol_or_surf_rxns,
  212. allow_rxn_transp_reflec_absorb_reg_border,
  213. matching_rxns
  214. );
  215. }
  216. }
  217. }
  218. /*************************************************************************
  219. trigger_intersect:
  220. In: hash value of molecule's species
  221. pointer to a molecule
  222. orientation of that molecule
  223. pointer to a wall
  224. array of matching reactions (placeholder for output)
  225. flags that tells whether we should include special reactions
  226. (REFL/TRANSP/ABSORB_REGION_BORDER) in the output array
  227. Out: number of matching reactions for this
  228. molecule/wall intersection, or for this mol/generic wall,
  229. or this wall/generic mol. All matching reactions are placed in
  230. the array "matching_rxns" in the first "number" slots.
  231. Note: Moving molecule may be inert.
  232. *************************************************************************/
  233. static void trigger_intersect(
  234. Partition& p,
  235. const Molecule& reacA,
  236. const orientation_t reacA_orient,
  237. const Wall& w,
  238. const bool allow_rxn_transp_reflec_absorb_reg_border,
  239. BNG::RxnClassesVector& matching_rxns
  240. ) {
  241. if (reacA.is_vol()) {
  242. find_mol_reactions_with_surf_classes(
  243. p, reacA, reacA_orient, w, allow_rxn_transp_reflec_absorb_reg_border,
  244. matching_rxns
  245. );
  246. }
  247. else if (reacA.is_surf()) {
  248. assert(reacA.s.orientation == reacA_orient);
  249. find_mol_reactions_with_surf_classes(
  250. p, reacA, reacA.s.orientation, w, allow_rxn_transp_reflec_absorb_reg_border,
  251. matching_rxns
  252. );
  253. }
  254. else {
  255. assert(false);
  256. }
  257. }
  258. /*************************************************************************
  259. binary_search_double
  260. In: A: A pointer to an array of doubles
  261. match: The value to match in the array
  262. max_idx: Initially, the size of the array
  263. mult: A multiplier for the comparison to the match.
  264. Set to 1 if not needed.
  265. Out: Returns the index of the match in the array
  266. Note: This should possibly be moved to util.c
  267. *************************************************************************/
  268. static int binary_search_double(const std::vector<double>& A, double match, int max_idx, double mult) {
  269. int min_idx = 0;
  270. while (max_idx - min_idx > 1) {
  271. int mid_idx = (max_idx + min_idx) / 2;
  272. if (match > (A[mid_idx] * mult)) {
  273. min_idx = mid_idx;
  274. }
  275. else {
  276. max_idx = mid_idx;
  277. }
  278. }
  279. if (match > A[min_idx] * mult) {
  280. return max_idx;
  281. }
  282. else {
  283. return min_idx;
  284. }
  285. }
  286. /*************************************************************************
  287. test_bimolecular
  288. In: the reaction we're testing
  289. a scaling coefficient depending on how many timesteps we've
  290. moved at once (1.0 means one timestep) and/or missing interaction area
  291. local probability factor (positive only for the reaction between two
  292. surface molecules, otherwise equal to zero)
  293. reaction partners
  294. Out: PATHWAY_INDEX_NO_RXN if no reaction occurs
  295. int containing which reaction pathway to take if one does occur
  296. Note: If this reaction does not return PATHWAY_INDEX_NO_RXN, then we update
  297. counters appropriately assuming that the reaction does take place.
  298. *************************************************************************/
  299. static int test_bimolecular(
  300. Partition& p,
  301. BNG::RxnClass* rxn_class,
  302. rng_state& rng,
  303. const Molecule& a1, // unused for now
  304. const Molecule& a2, // unused for now
  305. const double scaling,
  306. const double local_prob_factor,
  307. const double current_time
  308. ) {
  309. assert(rxn_class != nullptr);
  310. assert(rxn_class->get_num_reactions() != 0);
  311. rxn_class->update_rxn_rates_if_needed(current_time);
  312. /* rescale probabilities for the case of the reaction
  313. between two surface molecules */
  314. double max_fixed_p = rxn_class->get_max_fixed_p(); // local_prob_factor == 0
  315. if (local_prob_factor != 0) {
  316. max_fixed_p = rxn_class->get_max_fixed_p() * local_prob_factor;
  317. }
  318. double prob;
  319. if (max_fixed_p < scaling) {
  320. /* Instead of scaling rx->cum_probs array we scale random probability */
  321. prob = rng_dbl(&rng) * scaling;
  322. if (prob >= max_fixed_p) {
  323. return BNG::PATHWAY_INDEX_NO_RXN;
  324. }
  325. // continue below
  326. }
  327. else {
  328. float max_p = rxn_class->get_max_fixed_p(); //rx.cum_probs[rx->n_pathways - 1]; // TODO_PATHWAYS
  329. if (local_prob_factor > 0) {
  330. max_p *= local_prob_factor;
  331. }
  332. if (max_p >= scaling) /* we cannot scale enough. add missed rxns */
  333. {
  334. /* How may reactions will we miss? */
  335. // rxn class is created for a pairs of reactants so we can just take the first one
  336. double skipped;
  337. if (scaling == 0.0) {
  338. skipped = DBL_GIGANTIC;
  339. }
  340. else {
  341. skipped = (max_p / scaling) - 1.0;
  342. }
  343. p.stats.inc_rxn_skipped(p.get_all_rxns(), rxn_class, skipped);
  344. /* Keep the proportions of outbound pathways the same. */
  345. prob = rng_dbl(&rng) * max_p;
  346. }
  347. else /* we can scale enough */
  348. {
  349. /* Instead of scaling rx->cum_probs array we scale random probability */
  350. prob = rng_dbl(&rng) * scaling;
  351. if (prob >= max_p)
  352. return BNG::PATHWAY_INDEX_NO_RXN;
  353. }
  354. }
  355. #ifdef DEBUG_REACTION_PROBABILITIES
  356. mcell_log(
  357. "test_bimolecular: p = %.8f, scaling = %.8f, max_fixed_p = %.8f, local_prob_factor = %.8f",
  358. prob, scaling, max_fixed_p, local_prob_factor
  359. );
  360. #endif
  361. if (local_prob_factor > 0) {
  362. return rxn_class->get_pathway_index_for_probability(prob, local_prob_factor);
  363. }
  364. else {
  365. return rxn_class->get_pathway_index_for_probability(prob, 1);
  366. }
  367. }
  368. /*************************************************************************
  369. test_many_unimol:
  370. In: an array of reactions we're testing
  371. rng state
  372. Out: index of the selected rxn class
  373. *************************************************************************/
  374. static uint test_many_unimol(
  375. const BNG::RxnClassesVector& rxn_classes,
  376. rng_state& rng) {
  377. assert(!rxn_classes.empty());
  378. size_t n = rxn_classes.size();
  379. if (n == 1) {
  380. return 0;
  381. }
  382. std::vector<double> cum_rxn_class_probs(n); /* array of cumulative rxn probabilities */
  383. cum_rxn_class_probs[0] = rxn_classes[0]->get_max_fixed_p();
  384. for (size_t i = 1; i < n; i++) {
  385. cum_rxn_class_probs[i] = cum_rxn_class_probs[i - 1] + rxn_classes[i]->get_max_fixed_p();
  386. }
  387. double p = rng_dbl(&rng) * cum_rxn_class_probs[n - 1];
  388. /* Pick the reaction that happens */
  389. uint res = binary_search_double(cum_rxn_class_probs, p, cum_rxn_class_probs.size() - 1, 1);
  390. return res;
  391. }
  392. /*************************************************************************
  393. test_many_bimolecular:
  394. In: an array of reactions we're testing
  395. scaling coefficients depending on how many timesteps we've moved
  396. at once (1.0 means one timestep) and/or missing interaction areas
  397. local probability factor for the corresponding reactions
  398. the number of elements in the array of reactions
  399. placeholder for the chosen pathway in the reaction (works as return
  400. value)
  401. a flag to indicate if
  402. Out: PATHWAY_INDEX_NO_RXN if no reaction occurs
  403. index in the reaction array corresponding to which reaction occurs
  404. if one does occur
  405. Note: If this reaction does not return PATHWAY_INDEX_NO_RXN, then we update
  406. counters appropriately assuming that the reaction does take place.
  407. Note: this uses only one call to get a random double, so you can't
  408. effectively sample events that happen less than 10^-9 of the
  409. time (for 32 bit random number).
  410. NOTE: This function was merged with test_many_bimolecular_all_neighbors.
  411. These two functions were almost identical, and the behavior of the
  412. "all_neighbors" version is preserved with a flag that can be passed in.
  413. For reactions between two surface molecules, set this flag to 1. For
  414. such reactions local_prob_factor > 0.
  415. *************************************************************************/
  416. static int test_many_bimolecular(
  417. Partition& p,
  418. BNG::RxnClassesVector& rxn_classes,
  419. const small_vector<double>& scaling,
  420. const double local_prob_factor,
  421. rng_state& rng,
  422. const bool all_neighbors_flag,
  423. const double current_time,
  424. BNG::rxn_class_pathway_index_t& chosen_pathway_index
  425. ) {
  426. assert(rxn_classes.size() == scaling.size());
  427. uint n = rxn_classes.size();
  428. for (BNG::RxnClass* cls: rxn_classes) {
  429. cls->update_rxn_rates_if_needed(current_time);
  430. }
  431. /* array of cumulative rxn probabilities */
  432. std::vector<double> cum_rxn_class_probs; // rxn in mcell3
  433. cum_rxn_class_probs.resize(2 * n, 0.0);
  434. int m;
  435. double prob;
  436. if (all_neighbors_flag && local_prob_factor <= 0) {
  437. mcell_internal_error(
  438. "Local probability factor = %g in the function 'test_many_bimolecular_all_neighbors().",
  439. local_prob_factor
  440. );
  441. }
  442. if (n == 1) {
  443. Molecule dummy;
  444. if (all_neighbors_flag) {
  445. return test_bimolecular(p, rxn_classes[0], rng, dummy, dummy, scaling[0], local_prob_factor, current_time);
  446. }
  447. else {
  448. return test_bimolecular(p, rxn_classes[0], rng, dummy, dummy, scaling[0], 0, current_time);
  449. }
  450. }
  451. /* Note: lots of division here, if we're CPU-bound,could invert the
  452. definition of scaling_coefficients */
  453. if (all_neighbors_flag && local_prob_factor > 0) {
  454. cum_rxn_class_probs[0] = (rxn_classes[0]->get_max_fixed_p()) * local_prob_factor / scaling[0];
  455. }
  456. else {
  457. cum_rxn_class_probs[0] = rxn_classes[0]->get_max_fixed_p() / scaling[0];
  458. }
  459. for (uint i = 1; i < n; i++) {
  460. if (all_neighbors_flag && local_prob_factor > 0) {
  461. cum_rxn_class_probs[i] = cum_rxn_class_probs[i - 1] + (rxn_classes[i]->get_max_fixed_p()) * local_prob_factor / scaling[i];
  462. }
  463. else {
  464. cum_rxn_class_probs[i] = cum_rxn_class_probs[i - 1] + rxn_classes[i]->get_max_fixed_p() / scaling[i];
  465. }
  466. }
  467. if (cum_rxn_class_probs[n - 1] > 1.0) {
  468. double f = cum_rxn_class_probs[n - 1] - 1.0; /* Number of failed reactions */
  469. for (uint i = 0; i < n; i++) { /* Distribute failures */
  470. double skipped;
  471. if (all_neighbors_flag && local_prob_factor > 0) {
  472. skipped =
  473. f * rxn_classes[i]->get_max_fixed_p() * local_prob_factor / cum_rxn_class_probs[n - 1];
  474. } else {
  475. skipped =
  476. f * rxn_classes[i]->get_max_fixed_p() / cum_rxn_class_probs[n - 1];
  477. }
  478. p.stats.inc_rxn_skipped(p.get_all_rxns(), rxn_classes[i], skipped);
  479. }
  480. prob = rng_dbl(&rng) * cum_rxn_class_probs[n - 1];
  481. }
  482. else {
  483. prob = rng_dbl(&rng);
  484. if (prob > cum_rxn_class_probs[n - 1]) {
  485. return BNG::PATHWAY_INDEX_NO_RXN;
  486. }
  487. }
  488. /* Pick the reaction class that happens */
  489. int rxn_index = binary_search_double(cum_rxn_class_probs, prob, cum_rxn_class_probs.size() - 1, 1);
  490. assert(rxn_index >= 0);
  491. BNG::RxnClass* selected_rxn_class = rxn_classes[rxn_index];
  492. if (rxn_index > 0) {
  493. prob = (prob - cum_rxn_class_probs[rxn_index - 1]);
  494. }
  495. prob = prob * scaling[rxn_index];
  496. /* Now pick the pathway within that reaction */
  497. // NOTE: might optimize if there is just one rxn
  498. if (all_neighbors_flag && local_prob_factor > 0) {
  499. m = selected_rxn_class->get_pathway_index_for_probability(prob, local_prob_factor);
  500. }
  501. else {
  502. m = selected_rxn_class->get_pathway_index_for_probability(prob, 1);
  503. }
  504. chosen_pathway_index = m;
  505. return rxn_index;
  506. }
  507. /*************************************************************************
  508. test_intersect
  509. In: the reaction we're testing
  510. a probability multiplier depending on how many timesteps we've
  511. moved at once (1.0 means one timestep)
  512. Out: PATHWAY_INDEX_NO_RXN if no reaction occurs (assume reflection)
  513. int containing which reaction occurs if one does occur
  514. Note: If not PATHWAY_INDEX_NO_RXN, and not the trasparency shortcut, then we
  515. update counters assuming the reaction will take place.
  516. *************************************************************************/
  517. static BNG::rxn_class_pathway_index_t test_intersect(
  518. BNG::RxnClass* rxn_class,
  519. const double scaling,
  520. const double current_time,
  521. rng_state& rng) {
  522. double p;
  523. assert(rxn_class->type == BNG::RxnType::Standard &&
  524. "Reflect and Transparent should be handled elsewhere, AbsorbRegionBorder is not applicable here");
  525. rxn_class->update_rxn_rates_if_needed(current_time);
  526. double max_prob = rxn_class->get_max_fixed_p();
  527. if (max_prob > scaling) {
  528. p = rng_dbl(&rng) * max_prob;
  529. }
  530. else {
  531. p = rng_dbl(&rng) * scaling;
  532. if (p > max_prob) {
  533. return BNG::PATHWAY_INDEX_NO_RXN;
  534. }
  535. }
  536. if (p > rxn_class->get_max_fixed_p()) {
  537. return BNG::PATHWAY_INDEX_NO_RXN;
  538. }
  539. double match = rng_dbl(&rng);
  540. match = match * rxn_class->get_max_fixed_p();
  541. return rxn_class->get_pathway_index_for_probability(match, 1);
  542. }
  543. /*************************************************************************
  544. test_many_intersect:
  545. In: an array of reactions we're testing
  546. a probability multiplier depending on how many timesteps we've
  547. moved at once (1.0 means one timestep)
  548. the number of elements in the array of reactions
  549. placeholder for the chosen pathway in the reaction (return value)
  550. Out: PATHWAY_INDEX_NO_RXN if no reaction occurs (assume reflection)
  551. index in the reaction array if reaction does occur
  552. Note: If not PATHWAY_INDEX_NO_RXN, and not the trasparency shortcut, then we
  553. update counters assuming the reaction will take place.
  554. *************************************************************************/
  555. static BNG::rxn_class_pathway_index_t test_many_intersect(
  556. BNG::RxnClassesVector& rxn_classes,
  557. const double scaling,
  558. const double current_time,
  559. BNG::rxn_class_index_t& selected_rxn_class_index,
  560. rng_state& rng) {
  561. for (BNG::RxnClass* cls: rxn_classes) {
  562. cls->update_rxn_rates_if_needed(current_time);
  563. }
  564. uint num_classes = rxn_classes.size();
  565. if (num_classes == 1) {
  566. selected_rxn_class_index = 0;
  567. return test_intersect(rxn_classes[0], scaling, current_time, rng);
  568. }
  569. // array of cumulative rxn probabilities
  570. std::vector<double> rxn_probs;
  571. rxn_probs.resize(num_classes);
  572. rxn_probs[0] = rxn_classes[0]->get_max_fixed_p() / scaling;
  573. for (uint i = 1; i < num_classes; i++) {
  574. rxn_probs[i] = rxn_probs[i - 1] + rxn_classes[i]->get_max_fixed_p() / scaling;
  575. }
  576. double p;
  577. if (rxn_probs[num_classes - 1] > 1.0) {
  578. p = rng_dbl(&rng) * rxn_probs[num_classes - 1];
  579. } else {
  580. p = rng_dbl(&rng);
  581. if (p > rxn_probs[num_classes - 1]) {
  582. selected_rxn_class_index = BNG::RNX_CLASS_INDEX_INVALID;
  583. return BNG::PATHWAY_INDEX_NO_RXN;
  584. }
  585. }
  586. /* Pick the reaction that happens */
  587. selected_rxn_class_index = binary_search_double(rxn_probs, p, num_classes - 1, 1);
  588. BNG::RxnClass *selected_rxn_class = rxn_classes[selected_rxn_class_index];
  589. if (selected_rxn_class_index > 0) {
  590. p = (p - rxn_probs[selected_rxn_class_index - 1]);
  591. }
  592. p = p * scaling;
  593. /* Now pick the pathway within that reaction */
  594. return selected_rxn_class->get_pathway_index_for_probability(p, 1);
  595. }
  596. // might return nullptr if there is no unimolecular reaction for this species
  597. // based on pick_unimolecular_reaction
  598. static void pick_unimol_rxn_classes(
  599. Partition& p,
  600. const Molecule& m,
  601. const double current_time,
  602. BNG::RxnClassesVector& matching_rxn_classes
  603. ) {
  604. // MCell3 returns mol+surf class rxn(s) as the first one(s), then the true unimol rxns
  605. // maintaining order only for compatibility
  606. const BNG::Species& species = p.get_species(m.species_id);
  607. if (species.has_flag(BNG::SPECIES_FLAG_CAN_SURFWALL)) {
  608. assert(m.is_surf());
  609. const Wall& w = p.get_wall(m.s.wall_index);
  610. trigger_intersect(p, m, m.s.orientation, w, false, matching_rxn_classes);
  611. }
  612. // get unimol rxn class if exists
  613. BNG::RxnClass* rxn_class = p.get_all_rxns().get_unimol_rxn_class(m.species_id);
  614. if (rxn_class != nullptr) {
  615. rxn_class->update_rxn_rates_if_needed(current_time);
  616. matching_rxn_classes.push_back(rxn_class);
  617. }
  618. }
  619. // based on timeof_unimolecular
  620. static double time_of_unimol(BNG::RxnClass* rxn_class, rng_state& rng) {
  621. double k_tot = rxn_class->get_max_fixed_p();
  622. #ifdef MCELL4_NO_RNG_FOR_UNIMOL_RXN_P_0
  623. if (k_tot <= 0) {
  624. // not calling random generator when p is 0 - for bompatibility with MCell3R
  625. return TIME_FOREVER;
  626. }
  627. #endif
  628. double p = rng_dbl(&rng);
  629. if ((k_tot <= 0) || (!distinguishable_f(p, 0, EPS))) {
  630. return TIME_FOREVER;
  631. }
  632. return -log(p) / k_tot;
  633. }
  634. // based on compute_lifetime
  635. static double compute_unimol_lifetime(
  636. const Partition& p,
  637. rng_state& rng,
  638. BNG::RxnClass* rx,
  639. const double current_time,
  640. Molecule& m
  641. ) {
  642. assert(rx != nullptr);
  643. double unimol_time_from_now = time_of_unimol(rx, rng);
  644. #ifdef DEBUG_RXNS
  645. SimulationStats* world = &p.stats;
  646. DUMP_CONDITION4(
  647. m.id,
  648. // calling rng for unimolecular
  649. m.dump(p, "Assigned unimolecular time (prev rng):", "", p.stats.get_current_iteration(), unimol_time_from_now);
  650. );
  651. #endif
  652. // ignore if the is the next change if rxn probability closer than the scheduled time
  653. double update_time = rx->get_next_time_of_rxn_rate_update();
  654. if (current_time + unimol_time_from_now > update_time) {
  655. m.set_flag(MOLECULE_FLAG_RESCHEDULE_UNIMOL_RXN_ON_NEXT_RXN_RATE_UPDATE);
  656. unimol_time_from_now = update_time - current_time;
  657. }
  658. return unimol_time_from_now;
  659. }
  660. /*************************************************************************
  661. which_unimolecular:
  662. In: the reaction we're testing
  663. Out: int containing which unimolecular reaction occurs (one must occur)
  664. *************************************************************************/
  665. static BNG::rxn_class_pathway_index_t which_unimolecular(const Molecule& m, BNG::RxnClass *rxn_class, rng_state& rng) {
  666. assert(rxn_class != nullptr);
  667. if (rxn_class->get_num_reactions() == 1) {
  668. return 0;
  669. }
  670. double match = rng_dbl(&rng);
  671. match = match * rxn_class->get_max_fixed_p();
  672. return rxn_class->get_pathway_index_for_probability(match, 1);
  673. }
  674. } // namespace RxUtil
  675. } // namespace MCell
  676. #endif // SRC4_RXN_UTILS_INC_