model.cpp 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723
  1. /******************************************************************************
  2. *
  3. * Copyright (C) 2020 by
  4. * The Salk Institute for Biological Studies
  5. *
  6. * Use of this source code is governed by an MIT-style
  7. * license that can be found in the LICENSE file or at
  8. * https://opensource.org/licenses/MIT.
  9. *
  10. ******************************************************************************/
  11. #include "model.h"
  12. #include <string>
  13. #include "api/mcell4_converter.h"
  14. #include "api/python_exporter.h"
  15. #include "api/api_utils.h"
  16. #include "api/molecule.h"
  17. #include "api/species.h"
  18. #include "api/reaction_rule.h"
  19. #include "api/release_site.h"
  20. #include "api/geometry_object.h"
  21. #include "api/viz_output.h"
  22. #include "api/count.h"
  23. #include "api/wall.h"
  24. #include "api/wall_wall_hit_info.h"
  25. #include "api/checkpoint_signals.h"
  26. #include "world.h"
  27. #include "scheduler.h"
  28. #include "diffuse_react_event.h"
  29. #include "release_event.h"
  30. #include "rxn_utils.inl"
  31. #include "molecule.h"
  32. #include "viz_output_event.h"
  33. #include "custom_function_call_event.h"
  34. #include "bngl_exporter.h"
  35. #include "bng/rxn_class.h"
  36. using namespace std;
  37. namespace MCell {
  38. namespace API {
  39. class RngState;
  40. Model::~Model() {
  41. unset_checkpoint_signals(this); // may be safely called multiple times
  42. delete world;
  43. }
  44. void Model::add_subsystem(std::shared_ptr<Subsystem> subsystem) {
  45. error_if_initialized(NAME_CLASS_SUBSYSTEM);
  46. append_vec_to_vec_canonical_name(elementary_molecule_types, subsystem->elementary_molecule_types);
  47. append_vec_to_vec_canonical_name(species, subsystem->species);
  48. append_vec_to_vec(surface_classes, subsystem->surface_classes);
  49. append_vec_to_vec_canonical_name(reaction_rules, subsystem->reaction_rules);
  50. }
  51. void Model::add_instantiation(std::shared_ptr<Instantiation> instantiation) {
  52. error_if_initialized(NAME_CLASS_INSTANTIATION);
  53. append_vec_to_vec(release_sites, instantiation->release_sites);
  54. append_vec_to_vec(geometry_objects, instantiation->geometry_objects);
  55. }
  56. void Model::add_observables(std::shared_ptr<Observables> observables) {
  57. error_if_initialized(NAME_CLASS_OBSERVABLES);
  58. append_vec_to_vec(viz_outputs, observables->viz_outputs, true);
  59. append_vec_to_vec(counts, observables->counts, true);
  60. }
  61. void Model::initialize(const bool print_copyright) {
  62. if (world != nullptr) {
  63. throw RuntimeError("Model.initialize() can be called only once");
  64. }
  65. // semantic checks of the following classes must be called manually because
  66. // their contents may have changed and check_semantics is automatically called only from their ctor
  67. config.check_semantics();
  68. warnings.check_semantics();
  69. notifications.check_semantics();
  70. // first add species superclasses
  71. std::vector<std::shared_ptr<Species>> superspecies = { AllMolecules, AllVolumeMolecules, AllSurfaceMolecules };
  72. species.insert(species.begin(), superspecies.begin(), superspecies.end());
  73. // Species objects might have created their own ElementaryMoleculeType
  74. // objects, we must unify it first (required for python export)
  75. unify_and_register_elementary_molecule_types();
  76. world = new World(callbacks);
  77. // semantic checks are done during conversion
  78. MCell4Converter converter(this, world);
  79. converter.convert_before_init();
  80. // set that all used objects were initialized
  81. vec_set_initialized(species);
  82. vec_set_initialized(reaction_rules);
  83. vec_set_initialized(release_sites);
  84. vec_set_initialized(geometry_objects);
  85. vec_set_initialized(viz_outputs);
  86. vec_set_initialized(counts);
  87. world->init_simulation(world->config.get_simulation_start_time());
  88. // convert also rng state checkpointed molecules,
  89. // must be done after world initialization
  90. converter.convert_after_init();
  91. initialize_introspection(this);
  92. // set action for SIGUSR1 and SIGUSR2
  93. set_checkpoint_signals(this);
  94. initialized = true;
  95. if (print_copyright) {
  96. cout <<
  97. "Copyright (C) 2006-2021 by\n"
  98. " The National Center for Multiscale Modeling of Biological Systems,\n"
  99. " The Salk Institute for Biological Studies, and\n"
  100. " Pittsburgh Supercomputing Center, Carnegie Mellon University,\n"
  101. "\n"
  102. "**********************************************************************\n"
  103. "MCell development is supported by the NIGMS-funded (P41GM103712)\n"
  104. "National Center for Multiscale Modeling of Biological Systems (MMBioS).\n"
  105. "Please acknowledge MCell in your publications.\n"
  106. "**********************************************************************\n\n";
  107. }
  108. }
  109. uint64_t Model::run_iterations(const double iterations) {
  110. // release GIL before calling into long-running C++ code,
  111. // necessary for other Python threads to be allowed to run (e.g. a timer)
  112. py::gil_scoped_release release;
  113. if (world == nullptr) {
  114. throw RuntimeError("Model was not initialized, call Model.initialize() first");
  115. }
  116. return world->run_n_iterations(iterations, false);
  117. }
  118. void Model::end_simulation(const bool print_final_report) {
  119. // the first argument specifies that the last mol/rxn count and viz_output events will be run
  120. // cannot call callbacks anymore (no diffusion event is executed)
  121. world->end_simulation(print_final_report);
  122. // unset action for SIGUSR1 and SIGUSR2
  123. unset_checkpoint_signals(this);
  124. }
  125. void Model::dump_internal_state(const bool with_geometry) {
  126. world->dump(with_geometry);
  127. }
  128. void Model::export_geometry(const std::string& output_files_prefix) {
  129. if (world == nullptr) {
  130. throw RuntimeError(S("Model must be initialized before a call to ") + NAME_EXPORT_DATA_MODEL + ".");
  131. }
  132. if (is_set(output_files_prefix)) {
  133. world->export_geometry_to_obj(output_files_prefix);
  134. }
  135. else {
  136. stringstream prefix;
  137. prefix <<
  138. get_first_viz_output_files_prefix(NAME_EXPORT_GEOMETRY) << ".geom." <<
  139. VizOutputEvent::iterations_to_string(world->stats.get_current_iteration(), config.total_iterations);
  140. world->export_geometry_to_obj(prefix.str());
  141. }
  142. }
  143. void Model::export_data_model_viz_or_full(
  144. const std::string& file,
  145. const bool only_for_visualization,
  146. const char* method_name) {
  147. if (world == nullptr) {
  148. throw RuntimeError(S("Model must be initialized before a call to ") + NAME_EXPORT_DATA_MODEL + ".");
  149. }
  150. if (is_set(file)) {
  151. world->export_data_model(file, only_for_visualization);
  152. }
  153. else {
  154. world->export_data_model_to_dir(get_first_viz_output_files_prefix(method_name), only_for_visualization);
  155. }
  156. }
  157. void Model::release_molecules(std::shared_ptr<ReleaseSite> release_site) {
  158. if (!initialized) {
  159. throw RuntimeError(S("Model must be initialized before calling ") + NAME_RELEASE_MOLECULES + ".");
  160. }
  161. // check that time is now or in the future
  162. double iteration_start_time = world->stats.get_current_iteration() * world->config.time_unit;
  163. if (release_site->release_time < iteration_start_time) {
  164. throw ValueError("Cannot release molecules for time " + to_string(release_site->release_time) +
  165. " before the start time of the current iteration " + to_string(iteration_start_time) + ".");
  166. }
  167. if (is_set(release_site->release_pattern)) {
  168. throw ValueError(S("Cannot release molecules with a release pattern, method ") + NAME_RELEASE_MOLECULES +
  169. " may be used only for immediate releases.");
  170. }
  171. // convert to a ReleaseEvent
  172. MCell4Converter converter(this, world);
  173. MCell::ReleaseEvent* rel_event = converter.convert_single_release_event(release_site);
  174. assert(rel_event != nullptr);
  175. // TODO: we must improve handling of cases when the release is in the future
  176. rel_event->event_time = release_site->release_time / world->config.time_unit;
  177. // figure out whether the DiffuseAndReactEvent is running
  178. MCell::BaseEvent* current_event = world->scheduler.get_event_being_executed();
  179. MCell::DiffuseReactEvent* diffuse_event = nullptr;
  180. if (current_event != nullptr && current_event->type_index == EVENT_TYPE_INDEX_DIFFUSE_REACT) {
  181. diffuse_event = dynamic_cast<MCell::DiffuseReactEvent*>(current_event);
  182. assert(diffuse_event != nullptr);
  183. }
  184. // and execute the release
  185. rel_event->release_immediatelly(diffuse_event);
  186. delete rel_event;
  187. }
  188. std::vector<int> Model::run_reaction(
  189. std::shared_ptr<ReactionRule> reaction_rule,
  190. const std::vector<int> reactant_ids,
  191. const double time) {
  192. // need to release lock because we might be calling callbacks that are locking back
  193. py::gil_scoped_release release;
  194. if (!initialized) {
  195. throw RuntimeError(S("Model must be initialized before calling ") + NAME_RUN_REACTION + ".");
  196. }
  197. if (reaction_rule->reactants.size() != reactant_ids.size()) {
  198. throw RuntimeError("Reaction expects " + to_string(reaction_rule->reactants.size()) +
  199. " reactants but " + to_string(reactant_ids.size()) + " reactants were provided.");
  200. }
  201. if (reaction_rule->fwd_rxn_rule_id == BNG::RXN_RULE_ID_INVALID) {
  202. throw RuntimeError("Reaction rule is not present in model and was not initialized.");
  203. }
  204. if (reaction_rule->rev_rxn_rule_id != BNG::RXN_RULE_ID_INVALID) {
  205. throw RuntimeError(S("Method ") + NAME_RUN_REACTION + " can be used only with irreversible reactions.");
  206. }
  207. const BNG::RxnRule* rxn = world->get_all_rxns().get(reaction_rule->fwd_rxn_rule_id);
  208. if (!rxn->is_unimol()) {
  209. throw RuntimeError(S("Method ") + NAME_RUN_REACTION + " currently supports only unimolecular reactions.");
  210. }
  211. MCell::Partition& p = world->get_partition(PARTITION_ID_INITIAL);
  212. molecule_id_t id1 = reactant_ids[0];
  213. if (!p.does_molecule_exist(id1)) {
  214. throw RuntimeError("Molecule with id " + to_string(id1) + " does not exist.");
  215. }
  216. MCell::Molecule& m1 = p.get_m(id1);
  217. if (m1.is_defunct()) {
  218. throw RuntimeError("Molecule with id " + to_string(id1) + " was removed.");
  219. }
  220. std::vector<int> res;
  221. if (rxn->is_unimol()) {
  222. // check if the requested rxn is applicable for our reactant
  223. // also determine pathway index
  224. BNG::RxnClass* rxn_class = world->bng_engine.get_all_rxns().get_unimol_rxn_class(m1.species_id);
  225. rxn_class->init_rxn_pathways_and_rates(); // initialize if needed
  226. BNG::rxn_class_pathway_index_t index = 0;
  227. while (index < (BNG::rxn_class_pathway_index_t)rxn_class->get_num_pathways() &&
  228. rxn_class->get_rxn_for_pathway(index)->id != rxn->id) {
  229. index++;
  230. }
  231. if (index >= (BNG::rxn_class_pathway_index_t)rxn_class->get_num_pathways()) {
  232. const BNG::Species& species = world->get_all_species().get(m1.species_id);
  233. throw RuntimeError("Reaction rule " + reaction_rule->to_bngl_str() +
  234. " cannot be applied on molecule with species " + species.name);
  235. }
  236. // if we are in a callback, we are probably in a diffuse react event
  237. BaseEvent* current_event = world->scheduler.get_event_being_executed();
  238. DiffuseReactEvent* diffuse_react_event;
  239. bool using_temporary_event;
  240. if (current_event != nullptr && current_event->type_index == EVENT_TYPE_INDEX_DIFFUSE_REACT) {
  241. diffuse_react_event = dynamic_cast<DiffuseReactEvent*>(current_event);
  242. using_temporary_event = false;
  243. }
  244. else {
  245. // otherwise we will instantiate a new event
  246. diffuse_react_event = new DiffuseReactEvent(world);
  247. using_temporary_event = true;
  248. }
  249. MoleculeIdsVector product_ids;
  250. diffuse_react_event->outcome_unimolecular(p, m1, time / world->config.time_unit, rxn_class, index, &product_ids);
  251. if (using_temporary_event) {
  252. delete diffuse_react_event;
  253. }
  254. res.insert(res.begin(), product_ids.begin(), product_ids.end());
  255. }
  256. else {
  257. // TODO
  258. release_assert(false);
  259. }
  260. return res;
  261. }
  262. void Model::add_vertex_move(
  263. std::shared_ptr<GeometryObject> object, const int vertex_index, const std::vector<double> displacement
  264. ) {
  265. // - currently, it is not expected that the user will have access to the scheduled vertex moves
  266. // - later we can use the object id to determine the partition
  267. object->check_is_initialized();
  268. release_assert(
  269. object->first_vertex_index + vertex_index <
  270. world->get_partition(PARTITION_ID_INITIAL).get_geometry_vertex_count()
  271. );
  272. if (displacement.size() != 3) {
  273. throw ValueError(S("Argument ") + NAME_DISPLACEMENT + " must be a list of exactly three floats.");
  274. }
  275. Vec3 disp_vec = Vec3(displacement);
  276. vertex_moves.push_back(
  277. VertexMoveInfo(
  278. PARTITION_ID_INITIAL,
  279. object->geometry_object_id,
  280. object->get_partition_vertex_index(vertex_index),
  281. disp_vec * Vec3(world->config.rcp_length_unit) // convert to internal units
  282. )
  283. );
  284. }
  285. void Model::update_api_vertex_position_using_vertex_move(
  286. const VertexMoveInfo& move_info) {
  287. Partition& p = world->get_partition(PARTITION_ID_INITIAL);
  288. // get the new vertex coordinates
  289. assert(move_info.partition_id == PARTITION_ID_INITIAL);
  290. const Vec3& pos = p.get_geometry_vertex(move_info.vertex_index);
  291. // API object whose vertex we need to update
  292. std::shared_ptr<API::GeometryObject> obj =
  293. get_geometry_object_with_id(move_info.geometry_object_id);
  294. // API object's vertex index
  295. int obj_vertex_index = obj->get_object_vertex_index(move_info.vertex_index);
  296. Vec3 pos_um = pos * Vec3(world->config.length_unit);
  297. auto& pos_to_update = obj->vertex_list[obj_vertex_index];
  298. pos_to_update[0] = pos_um.x;
  299. pos_to_update[1] = pos_um.y;
  300. pos_to_update[2] = pos_um.z;
  301. }
  302. std::vector<std::shared_ptr<WallWallHitInfo>> Model::apply_vertex_moves(
  303. const bool collect_wall_wall_hits,
  304. const bool randomize_order) {
  305. // run the actual vertex update
  306. std::set<GeometryObjectWallUnorderedPair> colliding_walls;
  307. Partition& p = world->get_partition(PARTITION_ID_INITIAL);
  308. // may update vertex_move displacements due to collisions
  309. vector<VertexMoveInfo*> vertex_moves_due_to_paired_molecules;
  310. p.apply_vertex_moves(
  311. randomize_order, vertex_moves,
  312. colliding_walls, vertex_moves_due_to_paired_molecules);
  313. std::vector<std::shared_ptr<WallWallHitInfo>> res;
  314. // convert information on processed hits
  315. if (collect_wall_wall_hits) {
  316. for (const auto& wall_pair: colliding_walls) {
  317. auto obj1 = get_geometry_object_with_id(wall_pair.geometry_object_id1);
  318. int obj_wall_index1 = obj1->get_object_wall_index(wall_pair.wall_index1);
  319. auto obj2 = get_geometry_object_with_id(wall_pair.geometry_object_id2);
  320. int obj_wall_index2 = obj2->get_object_wall_index(wall_pair.wall_index2);
  321. auto pair = make_shared<WallWallHitInfo>(DefaultCtorArgType());
  322. pair->wall1 = get_wall(obj1, obj_wall_index1);
  323. pair->wall2 = get_wall(obj2, obj_wall_index2);
  324. res.push_back(pair);
  325. }
  326. }
  327. // also update the API copy of the geometry
  328. for (const VertexMoveInfo& move_info: vertex_moves) {
  329. update_api_vertex_position_using_vertex_move(move_info);
  330. }
  331. // also take into account the vertices moved due to paired molecules
  332. for (VertexMoveInfo* move_info: vertex_moves_due_to_paired_molecules) {
  333. update_api_vertex_position_using_vertex_move(*move_info);
  334. delete move_info;
  335. }
  336. // we processed all the moves
  337. vertex_moves.clear();
  338. return res;
  339. }
  340. void Model::pair_molecules(const int id1, const int id2) {
  341. Partition& p = world->get_partition(PARTITION_ID_INITIAL);
  342. string res = p.pair_molecules(id1, id2);
  343. if (res != "") {
  344. throw RuntimeError(res);
  345. }
  346. }
  347. void Model::unpair_molecules(const int id1, const int id2) {
  348. Partition& p = world->get_partition(PARTITION_ID_INITIAL);
  349. string res = p.unpair_molecules(id1, id2);
  350. if (res != "") {
  351. throw RuntimeError(res);
  352. }
  353. }
  354. int Model::get_paired_molecule(const int id) {
  355. Partition& p = world->get_partition(PARTITION_ID_INITIAL);
  356. molecule_id_t res = p.get_paired_molecule(id);
  357. if (res == MOLECULE_ID_INVALID) {
  358. return ID_INVALID;
  359. }
  360. else {
  361. return res;
  362. }
  363. }
  364. std::map<uint, uint> Model::get_paired_molecules() {
  365. Partition& p = world->get_partition(PARTITION_ID_INITIAL);
  366. return p.get_paired_molecules();
  367. }
  368. void Model::register_mol_wall_hit_callback(
  369. const std::function<void(std::shared_ptr<MolWallHitInfo>, py::object)> function,
  370. py::object context,
  371. std::shared_ptr<GeometryObject> object,
  372. std::shared_ptr<Species> species
  373. ) {
  374. if (!initialized) {
  375. throw RuntimeError("Model must be initialized before registering callbacks.");
  376. }
  377. geometry_object_id_t geometry_object_id = GEOMETRY_OBJECT_ID_INVALID;
  378. if (is_set(object)) {
  379. if (object->geometry_object_id == GEOMETRY_OBJECT_ID_INVALID) {
  380. throw RuntimeError("Geometry object " + object->name + " is not present in model.");
  381. }
  382. geometry_object_id = object->geometry_object_id;
  383. }
  384. species_id_t species_id = SPECIES_ID_INVALID;
  385. if (is_set(species)) {
  386. if (species->species_id == SPECIES_ID_INVALID) {
  387. throw RuntimeError("Species object " + object->name + " is not present in model.");
  388. }
  389. species_id = species->species_id;
  390. }
  391. callbacks.register_mol_wall_hit_callback(function, context, geometry_object_id, species_id);
  392. }
  393. void Model::register_reaction_callback(
  394. const std::function<bool(std::shared_ptr<ReactionInfo>, py::object)> function,
  395. py::object context,
  396. std::shared_ptr<ReactionRule> reaction_rule
  397. ) {
  398. if (!initialized) {
  399. throw RuntimeError("Model must be initialized before registering callbacks.");
  400. }
  401. if (reaction_rule->is_reversible()) {
  402. throw RuntimeError(S("Reaction callback cannot be registered for reversible reactions. ") +
  403. "Split the reaction rule's forward and reverese direction into separate " +
  404. NAME_REACTION_RULE + " objects.");
  405. }
  406. BNG::rxn_rule_id_t rxn_id = reaction_rule->fwd_rxn_rule_id;
  407. if (rxn_id == BNG::RXN_RULE_ID_INVALID) {
  408. throw RuntimeError(S(NAME_REACTION_RULE) + reaction_rule->name + " with its BNGL representation " +
  409. reaction_rule->to_bngl_str() + " is not present in model.");
  410. }
  411. callbacks.register_rxn_callback(function, context, rxn_id);
  412. }
  413. void Model::load_bngl(
  414. const std::string& file_name,
  415. const std::string& observables_path_or_file,
  416. std::shared_ptr<Region> default_release_region,
  417. const std::map<std::string, double>& parameter_overrides,
  418. const CountOutputFormat observables_output_format) {
  419. BNG::BNGData bng_data;
  420. int num_errors = BNG::parse_bngl_file(file_name, bng_data, parameter_overrides);
  421. if (num_errors != 0) {
  422. throw RuntimeError("Could not parse BNGL file " + file_name + ".");
  423. }
  424. // now convert everything we parsed into the API classes so that the user can
  425. // inspect or manipulate it if needed
  426. convert_bng_data_to_subsystem_data(bng_data);
  427. // needs subsystem data created in the last step
  428. convert_bng_data_to_instantiation(bng_data, default_release_region);
  429. convert_bng_data_to_observables_data(
  430. bng_data,
  431. get_count_output_format(observables_output_format, observables_path_or_file),
  432. observables_path_or_file);
  433. }
  434. void Model::export_to_bngl(const std::string& file_name,
  435. const BNGSimulationMethod simulation_method) {
  436. if (!initialized) {
  437. throw RuntimeError("Model must be initialized for BNGL export.");
  438. }
  439. if (simulation_method != BNGSimulationMethod::NONE &&
  440. simulation_method != BNGSimulationMethod::ODE &&
  441. simulation_method != BNGSimulationMethod::SSA &&
  442. simulation_method != BNGSimulationMethod::PLA &&
  443. simulation_method != BNGSimulationMethod::NF) {
  444. throw ValueError(S("Invalid ") + NAME_SIMULATION_METHOD + " argument value.");
  445. }
  446. MCell::BNGLExporter bngl_exporter;
  447. string err_msg = bngl_exporter.export_to_bngl(world, file_name, simulation_method);
  448. if (err_msg != "") {
  449. throw RuntimeError("BNGL export failed: " + err_msg);
  450. }
  451. }
  452. void Model::save_checkpoint(const std::string& custom_dir) {
  453. if (!initialized) {
  454. throw RuntimeError(S("Model must be initialized for ") + NAME_SAVE_CHECKPOINT + ".");
  455. }
  456. // prepare output directory name
  457. string dir;
  458. if (is_set(custom_dir)) {
  459. dir = custom_dir;
  460. }
  461. else {
  462. // TODO: move the VizOutputEvent::iterations_to_string to api_utils
  463. dir =
  464. world->config.get_default_checkpoint_dir_prefix() +
  465. VizOutputEvent::iterations_to_string(world->stats.get_current_iteration(), config.total_iterations) +
  466. BNG::PATH_SEPARATOR;
  467. }
  468. PythonExporter exporter(this);
  469. exporter.save_checkpoint(dir);
  470. }
  471. // can be called asynchronously at any point of simulation, only single-threaded
  472. // cannot be called from signal handlers
  473. void Model::schedule_checkpoint(
  474. const uint64_t iteration,
  475. const bool continue_simulation,
  476. const std::string& custom_dir) {
  477. // NOTE: accessing current iteration value asynchronously,
  478. // it is a 64-bit integer so write is done atomically with a single instruction
  479. uint64_t current_it = world->stats.get_current_iteration();
  480. if (iteration != 0 && iteration < current_it) {
  481. throw RuntimeError(S("Method ") + NAME_SCHEDULE_CHECKPOINT + " - cannot schedule checkpoint to the past.");
  482. }
  483. // same as above - 'initialized' uses a simple type, therefore writes are atomic
  484. if (!initialized) {
  485. throw RuntimeError(S("Method ") + NAME_SCHEDULE_CHECKPOINT + " cannot be called before model initialization.");
  486. }
  487. // prepare context for callback
  488. CheckpointSaveEventContext ctx;
  489. ctx.model = this;
  490. if (is_set(custom_dir)) {
  491. ctx.dir_prefix = custom_dir;
  492. ctx.append_it_to_dir = false;
  493. }
  494. else {
  495. ctx.dir_prefix = world->config.get_default_checkpoint_dir_prefix();
  496. ctx.append_it_to_dir = true;
  497. }
  498. world->schedule_checkpoint_event(iteration, continue_simulation, ctx);
  499. }
  500. // overrides from derived classes Subsystem, Instantiation, and Observables,
  501. // in .cpp because implementation in .h file would need too many headers to be included
  502. void Model::add_species(std::shared_ptr<Species> s) {
  503. error_if_initialized(NAME_CLASS_SPECIES);
  504. Subsystem::add_species(s);
  505. }
  506. void Model::add_reaction_rule(std::shared_ptr<ReactionRule> r) {
  507. error_if_initialized(NAME_CLASS_REACTION_RULE);
  508. Subsystem::add_reaction_rule(r);
  509. }
  510. void Model::add_surface_class(std::shared_ptr<SurfaceClass> sc) {
  511. error_if_initialized(NAME_CLASS_SURFACE_CLASS);
  512. Subsystem::add_surface_class(sc);
  513. }
  514. void Model::add_release_site(std::shared_ptr<ReleaseSite> s) {
  515. error_if_initialized(NAME_CLASS_RELEASE_SITE);
  516. Instantiation::add_release_site(s);
  517. }
  518. void Model::add_geometry_object(std::shared_ptr<GeometryObject> o) {
  519. error_if_initialized(NAME_CLASS_GEOMETRY_OBJECT);
  520. Instantiation::add_geometry_object(o);
  521. }
  522. void Model::add_viz_output(std::shared_ptr<VizOutput> viz_output) {
  523. error_if_initialized(NAME_CLASS_VIZ_OUTPUT);
  524. Observables::add_viz_output(viz_output);
  525. };
  526. void Model::add_count(std::shared_ptr<Count> count) {
  527. error_if_initialized(NAME_CLASS_OBSERVABLES);
  528. Observables::add_count(count);
  529. };
  530. std::shared_ptr<GeometryObject> Model::get_geometry_object_with_id(const geometry_object_id_t id) {
  531. // map for caching
  532. auto it = geometry_object_id_to_obj_cache.find(id);
  533. if (it != geometry_object_id_to_obj_cache.end()) {
  534. return it->second;
  535. }
  536. for (auto o: geometry_objects) {
  537. if (o->geometry_object_id == id) {
  538. geometry_object_id_to_obj_cache[o->geometry_object_id] = o;
  539. return o;
  540. }
  541. }
  542. return std::shared_ptr<GeometryObject>(nullptr);
  543. }
  544. std::shared_ptr<ReactionRule> Model::get_reaction_rule_with_fwd_id(const BNG::rxn_rule_id_t id) {
  545. // not very efficient, we may need some caching/map later
  546. for (auto r: reaction_rules) {
  547. if (r->fwd_rxn_rule_id == id) {
  548. return r;
  549. }
  550. }
  551. return std::shared_ptr<ReactionRule>(nullptr);
  552. }
  553. void Model::dump() const {
  554. cout << to_str();
  555. }
  556. }
  557. }