python_exporter.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413
  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 "api/python_exporter.h"
  12. #include "api/python_export_constants.h"
  13. #include "api/python_export_utils.h"
  14. #include "api/model.h"
  15. #include "api/species.h"
  16. #include "api/geometry_object.h"
  17. #include "api/chkpt_vol_mol.h"
  18. #include "api/chkpt_surf_mol.h"
  19. #include "api/rng_state.h"
  20. #include "api/checkpoint_signals.h"
  21. #include "src/util.h"
  22. #include "src4/world.h"
  23. #include "src4/partition.h"
  24. #include "src4/molecule.h"
  25. #include "src4/custom_function_call_event.h"
  26. #include "src4/mol_or_rxn_count_event.h"
  27. using namespace std;
  28. namespace MCell {
  29. namespace API {
  30. PythonExporter::PythonExporter(Model* model_) :
  31. model(model_) {
  32. assert(model != nullptr);
  33. world = model->get_world();
  34. assert(world != nullptr);
  35. }
  36. void PythonExporter::open_and_check_file(
  37. const std::string file_name, std::ofstream& out,
  38. const bool for_append,
  39. const bool bngl) {
  40. open_and_check_file_w_prefix(output_dir, file_name, out, for_append, bngl);
  41. }
  42. void PythonExporter::save_checkpoint(const std::string& output_dir_) {
  43. output_dir = output_dir_;
  44. if (output_dir.back() != BNG::PATH_SEPARATOR) {
  45. output_dir += BNG::PATH_SEPARATOR;
  46. }
  47. ::make_parent_dir(output_dir.c_str());
  48. PythonExportContext ctx;
  49. // parameters - later, once we will maintain the association,
  50. string subsystem_name = export_subsystem(ctx);
  51. string geometry_objects_name = export_geometry(ctx);
  52. string instantiation_name = export_instantiation(ctx, geometry_objects_name);
  53. // need to append - some config flag?
  54. string observables_name = export_observables(ctx);
  55. // molecules
  56. // - volume
  57. // - surface
  58. // rng state,
  59. // starting, ending iterations, ...
  60. // all generated variables are prefixed with state_
  61. std::map<std::string, std::string> config_variable_names;
  62. export_simulation_state(ctx, config_variable_names);
  63. // model
  64. // - config
  65. // - warnings
  66. // - notifications
  67. export_model(ctx, subsystem_name, instantiation_name, observables_name, config_variable_names);
  68. }
  69. std::string PythonExporter::export_subsystem(PythonExportContext& ctx) {
  70. ofstream out;
  71. open_and_check_file(SUBSYSTEM, out);
  72. out << IMPORT_MCELL_AS_M;
  73. string res_name = model->Subsystem::export_to_python(out, ctx);
  74. out.close();
  75. return res_name;
  76. }
  77. std::string PythonExporter::export_geometry(PythonExportContext& ctx) {
  78. ofstream out;
  79. open_and_check_file(GEOMETRY, out);
  80. out << IMPORT_MCELL_AS_M;
  81. out << get_import_star(SUBSYSTEM);
  82. string res_name = model->export_vec_geometry_objects(out, ctx, "");
  83. out.close();
  84. return res_name;
  85. }
  86. std::string PythonExporter::export_instantiation(PythonExportContext& ctx, const std::string& geometry_objects_name) {
  87. // prints out everything, even past releases
  88. // for checkpointing, we always need to fully finish the current iteration and then start the new one
  89. std::ofstream out;
  90. open_and_check_file(INSTANTIATION, out);
  91. out << IMPORT_MCELL_AS_M;
  92. out << get_import_star(GEOMETRY);
  93. out << "\n";
  94. string release_sites_name = model->export_vec_release_sites(out, ctx, "");
  95. gen_ctor_call(out, INSTANTIATION, NAME_CLASS_INSTANTIATION);
  96. gen_param_id(out, NAME_RELEASE_SITES, release_sites_name, true);
  97. gen_param_id(out, NAME_GEOMETRY_OBJECTS, geometry_objects_name, false);
  98. out << CTOR_END;
  99. out.close();
  100. return INSTANTIATION;
  101. }
  102. std::string PythonExporter::export_observables(PythonExportContext& ctx) {
  103. ofstream out;
  104. open_and_check_file(OBSERVABLES, out);
  105. out << IMPORT_MCELL_AS_M;
  106. out << get_import_star(SUBSYSTEM);
  107. out << get_import_star(GEOMETRY);
  108. out << get_import_star(INSTANTIATION);
  109. out << "\n";
  110. // we need to set the initial_reactions_count of reaction counts here
  111. // reaction counts are counted from the beginning of the simulation
  112. for (auto& count: model->counts) {
  113. assert(is_set(count->expression));
  114. assert(count->count_event != nullptr);
  115. assert(count->count_event->mol_rxn_count_items.size() == 1);
  116. auto& mol_rxn_count_items = count->count_event->mol_rxn_count_items;
  117. for (size_t i = 0; i < mol_rxn_count_items.size(); i++) {
  118. if (mol_rxn_count_items[i].terms.size() == 1) {
  119. if (count->expression->node_type == ExprNodeType::LEAF) {
  120. count->expression->initial_reactions_count_export_override = count->get_current_value();
  121. }
  122. else {
  123. assert(false && "There is just one term so there must be just one leaf node");
  124. }
  125. }
  126. else {
  127. for (const auto& count_term: mol_rxn_count_items[i].terms) {
  128. if (count_term.is_rxn_count()) {
  129. throw RuntimeError(
  130. "Checkpointing of Count objects that use expressions containing reaction counts is not supported yet.");
  131. }
  132. }
  133. }
  134. }
  135. }
  136. string res_name = model->Observables::export_to_python(out, ctx);
  137. out.close();
  138. return res_name;
  139. }
  140. // state_variable_names are indexed by Config class attribute names
  141. void PythonExporter::export_simulation_state(
  142. PythonExportContext& ctx,
  143. std::map<std::string, std::string>& config_variable_names
  144. ) {
  145. ofstream out;
  146. open_and_check_file(SIMULATION_STATE, out);
  147. out << IMPORT_MCELL_AS_M;
  148. out << get_import_star(SUBSYSTEM);
  149. out << get_import_star(GEOMETRY);
  150. out << "\n";
  151. // current iteration and time
  152. gen_assign(out, NAME_INITIAL_ITERATION, world->stats.get_current_iteration());
  153. config_variable_names[NAME_INITIAL_ITERATION] = NAME_INITIAL_ITERATION;
  154. gen_assign(out, NAME_INITIAL_TIME, world->stats.get_current_iteration() * world->config.time_unit);
  155. config_variable_names[NAME_INITIAL_TIME] = NAME_INITIAL_TIME;
  156. out << "\n";
  157. // molecules
  158. export_molecules(out, ctx);
  159. // rng state
  160. RngState rng_state = RngState(world->rng);
  161. config_variable_names[NAME_INITIAL_RNG_STATE] = rng_state.export_to_python(out, ctx);
  162. }
  163. void PythonExporter::export_molecules(std::ostream& out, PythonExportContext& ctx) {
  164. // first export used species
  165. stringstream species_out;
  166. species_out << "# species used by molecules but not defined in subsystem\n";
  167. species_out << NAME_SPECIES << " = m.Vector" << NAME_CLASS_SPECIES << "([ ";
  168. int num_exported_species = 0;
  169. // prepare species map
  170. IdSpeciesMap id_species_map;
  171. for (const BNG::Species* species: world->get_all_species().get_species_vector()) {
  172. assert(id_species_map.count(species->id) == 0);
  173. if (species->get_num_instantiations() > 0) {
  174. std::shared_ptr<Species> subsystem_species = model->get_species_with_id(species->id);
  175. if (is_set(subsystem_species)) {
  176. // use existing object
  177. id_species_map[species->id] = subsystem_species;
  178. }
  179. else if (!species->is_reactive_surface()){
  180. // - reactive surfaces/surface classes must be defined in subsystem
  181. // - create a new object and export it directly so that all the newly used species are
  182. // in the beginning of the simulation_state module file
  183. shared_ptr<API::Species> new_species = make_shared<API::Species>(species->name);
  184. std::string name = new_species->export_to_python(out, ctx);
  185. id_species_map[species->id] = new_species;
  186. // add it to the list of species to be used
  187. if (num_exported_species != 0 && num_exported_species % 8 == 0) {
  188. species_out << "\n ";
  189. }
  190. species_out << name << ", ";
  191. }
  192. }
  193. }
  194. species_out << "\n])\n\n";
  195. out << species_out.str();
  196. // prepare geometry objects map
  197. IdGeometryObjectMap id_geometry_object_map;
  198. for (const auto& obj: model->geometry_objects) {
  199. assert(obj->geometry_object_id != GEOMETRY_OBJECT_ID_INVALID);
  200. id_geometry_object_map[obj->geometry_object_id] = obj;
  201. }
  202. // for each partition
  203. stringstream dummy_out;
  204. out << NAME_CHECKPOINTED_MOLECULES << " = [\n";
  205. for (const MCell::Partition& p: world->get_partitions()) {
  206. // for each molecule
  207. for (const MCell::Molecule& m: p.get_molecules()) {
  208. if (m.is_defunct()) {
  209. continue;
  210. }
  211. assert(id_species_map.count(m.species_id) != 0);
  212. // vol
  213. PythonExportContext dummy_ctx; // we do not want to use the pointer comparison checks in export
  214. if (m.is_vol()) {
  215. ChkptVolMol vm = ChkptVolMol(
  216. m, id_species_map, world->config.time_unit, world->config.length_unit);
  217. out << IND4 << vm.export_to_python(dummy_out, ctx) << ",\n";
  218. }
  219. else {
  220. assert(m.is_surf());
  221. ChkptSurfMol sm = ChkptSurfMol(
  222. m, id_species_map, world->config.time_unit, world->config.length_unit,
  223. p, id_geometry_object_map);
  224. out << IND4 << sm.export_to_python(dummy_out, ctx) << ",\n";
  225. }
  226. }
  227. }
  228. assert(dummy_out.str() == "" && "No other code must be exported.");
  229. out << "]\n\n";
  230. }
  231. std::string PythonExporter::export_model(
  232. PythonExportContext& ctx,
  233. const std::string& subsystem_name,
  234. const std::string& instantiation_name,
  235. const std::string& observables_name,
  236. const std::map<std::string, std::string>& config_variable_names) {
  237. // prints out everything, even past releases
  238. // for checkpointing, we always need to fully finish the current iteration and then start the new one
  239. std::ofstream out;
  240. open_and_check_file(MODEL, out);
  241. // imports
  242. out << INTERPRETER;
  243. out << IMPORT_SYS_OS;
  244. out << "\n";
  245. out << MCELL_PATH_SETUP;
  246. out << "\n";
  247. out << IMPORT_MCELL_AS_M;
  248. // TODO: version check, warning
  249. out << make_section_comment("import model and saved simulation state");
  250. out << get_import(SUBSYSTEM);
  251. out << get_import(INSTANTIATION);
  252. out << get_import(OBSERVABLES);
  253. out << get_import(SIMULATION_STATE);
  254. out << "\n";
  255. // create model object
  256. out << make_section_comment("model setup");
  257. gen_ctor_call(out, MODEL, NAME_CLASS_MODEL, false);
  258. out << "\n";
  259. // config, notifications, warnings
  260. gen_assign(out, MODEL, NAME_CONFIG, model->config.export_to_python(out, ctx));
  261. out << "\n";
  262. gen_assign(out, MODEL, NAME_NOTIFICATIONS, model->notifications.export_to_python(out, ctx));
  263. out << "\n";
  264. gen_assign(out, MODEL, NAME_WARNINGS, model->warnings.export_to_python(out, ctx));
  265. out << "\n";
  266. // subsystem
  267. string subsystem_prefix = S(SUBSYSTEM) + "." + subsystem_name + ".";
  268. gen_assign(out, MODEL, NAME_SPECIES, subsystem_prefix + NAME_SPECIES);
  269. gen_assign(out, MODEL, NAME_REACTION_RULES, subsystem_prefix + NAME_REACTION_RULES);
  270. gen_assign(out, MODEL, NAME_SURFACE_CLASSES, subsystem_prefix + NAME_SURFACE_CLASSES);
  271. gen_assign(out, MODEL, NAME_ELEMENTARY_MOLECULE_TYPES, subsystem_prefix + NAME_ELEMENTARY_MOLECULE_TYPES);
  272. out << "\n";
  273. // instantiation
  274. string instantiation_prefix = S(INSTANTIATION) + "." + instantiation_name + ".";
  275. gen_assign(out, MODEL, NAME_RELEASE_SITES, instantiation_prefix + NAME_RELEASE_SITES);
  276. gen_assign(out, MODEL, NAME_GEOMETRY_OBJECTS, instantiation_prefix + NAME_GEOMETRY_OBJECTS);
  277. out << "\n";
  278. // observables
  279. string observables_prefix = S(OBSERVABLES) + "." + observables_name + ".";
  280. gen_assign(out, MODEL, NAME_VIZ_OUTPUTS, observables_prefix + NAME_VIZ_OUTPUTS);
  281. gen_assign(out, MODEL, NAME_COUNTS, observables_prefix + NAME_COUNTS);
  282. out << "\n";
  283. // checkpoint-specific config
  284. out << make_section_comment("saved simulation state and checkpoint config");
  285. // - time step (explicit)?
  286. vector<string> config_vars = { NAME_INITIAL_ITERATION, NAME_INITIAL_TIME, NAME_INITIAL_RNG_STATE };
  287. for (string& var: config_vars) {
  288. auto it = config_variable_names.find(var);
  289. release_assert(it != config_variable_names.end());
  290. gen_assign(out, MODEL, NAME_CONFIG, it->first, S(SIMULATION_STATE) + "." + it->second);
  291. }
  292. gen_assign(out, MODEL, NAME_CONFIG, NAME_APPEND_TO_COUNT_OUTPUT_DATA, true);
  293. out << "# internal type VectorSpecies does not provide operator += yet\n";
  294. out << "for s in " << SIMULATION_STATE << "." << NAME_SPECIES << ":\n";
  295. out << IND4 << MODEL << "." << NAME_SPECIES << ".append(s)\n";
  296. gen_assign(out, MODEL, NAME_CHECKPOINTED_MOLECULES, S(SIMULATION_STATE) + "." + NAME_CHECKPOINTED_MOLECULES);
  297. out << "\n";
  298. out << make_section_comment("resume simulation");
  299. gen_method_call(out, MODEL, NAME_INITIALIZE);
  300. out << "\n";
  301. export_checkpoint_iterations(out);
  302. gen_method_call(
  303. out, MODEL, NAME_RUN_ITERATIONS,
  304. S(MODEL) + "." + NAME_CONFIG + "." + NAME_TOTAL_ITERATIONS + " - " + S(SIMULATION_STATE) + "." + NAME_INITIAL_ITERATION);
  305. gen_method_call(out, MODEL, NAME_END_SIMULATION);
  306. out.close();
  307. return MODEL;
  308. }
  309. void PythonExporter::export_checkpoint_iterations(std::ostream& out) {
  310. vector<BaseEvent*> checkpoint_events;
  311. world->scheduler.get_all_events_with_type_index(
  312. EVENT_TYPE_INDEX_CALL_START_ITERATION_CHECKPOINT, checkpoint_events);
  313. for (auto be: checkpoint_events) {
  314. auto ce = dynamic_cast<CustomFunctionCallEvent<CheckpointSaveEventContext>*>(be);
  315. bool arg2_nondefault = !ce->return_from_run_n_iterations;
  316. bool arg3_nondefault = !ce->function_arg.append_it_to_dir; // with custom dir, this is false
  317. out << MODEL << "." << NAME_SCHEDULE_CHECKPOINT << "(\n";
  318. gen_param(out, NAME_ITERATION, ce->event_time, arg2_nondefault);
  319. if (arg2_nondefault) {
  320. gen_param(out, NAME_CONTINUE_SIMULATION, !ce->return_from_run_n_iterations, arg3_nondefault);
  321. }
  322. if (arg3_nondefault) {
  323. gen_param(out, NAME_CUSTOM_DIR, ce->function_arg.dir_prefix, true);
  324. }
  325. out << ")\n";
  326. }
  327. }
  328. } // namespace API
  329. } // namespace MCell