mcell4_generator.cpp 44 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367
  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 <algorithm>
  12. #include "generator_utils.h"
  13. #include "mcell4_generator.h"
  14. #include "bng/bng_defines.h"
  15. #include "src/util.h"
  16. using namespace std;
  17. using namespace MCell::API;
  18. namespace MCell {
  19. using Json::Value;
  20. string MCell4Generator::get_module_name(const string file_suffix) {
  21. return get_module_name_w_prefix(data.output_files_prefix, file_suffix);
  22. }
  23. string MCell4Generator::make_import(const string file_suffix) {
  24. return "from " + get_module_name(file_suffix) + " import *\n";
  25. }
  26. void MCell4Generator::open_and_check_file(
  27. const string file_suffix, ofstream& out,
  28. const bool for_append, const bool bngl) {
  29. open_and_check_file_w_prefix(data.output_files_prefix, file_suffix, out, for_append, bngl);
  30. }
  31. void MCell4Generator::reset() {
  32. data.reset();
  33. geometry_generated = false;
  34. observables_generated = false;
  35. bng_gen = nullptr;
  36. python_gen = nullptr;
  37. }
  38. // the aim is to generate as much of output as possible,
  39. // therefore we are using exceptions
  40. bool MCell4Generator::generate(const SharedGenData& opts) {
  41. reset();
  42. bool failed = false;
  43. data = opts; // copy options
  44. // load json file
  45. ifstream file;
  46. file.open(opts.input_file);
  47. if (!file.is_open()) {
  48. cerr << "Could not open file '" << opts.input_file << "' for reading.\n";
  49. return false;
  50. }
  51. Value root;
  52. file >> root;
  53. file.close();
  54. data.mcell = get_node(KEY_ROOT, root, KEY_MCELL);
  55. // create generators
  56. if (data.bng_mode) {
  57. open_and_check_file(MODEL, bng_out, false, true);
  58. bng_out << GENERATED_WARNING;
  59. bng_gen = new BNGLGenerator(
  60. get_filename(data.output_files_prefix, MODEL, BNGL_EXT), bng_out, data);
  61. bng_gen->generate_units_information_header();
  62. }
  63. python_gen = new PythonGenerator(data);
  64. CHECK(generate_customization(), failed);
  65. CHECK(generate_shared(), failed);
  66. CHECK(generate_parameters(), failed);
  67. CHECK(generate_subsystem(), failed);
  68. std::vector<std::string> geometry_names;
  69. CHECK(geometry_names = generate_geometry(), failed);
  70. CHECK(generate_instantiation(geometry_names), failed);
  71. CHECK(generate_observables(), failed);
  72. CHECK(generate_model(failed), failed);
  73. CHECK(generate_customization_template(), failed);
  74. // delete generators
  75. if (data.bng_mode) {
  76. delete bng_gen;
  77. bng_gen = nullptr;
  78. bng_out.close();
  79. }
  80. delete python_gen;
  81. python_gen = nullptr;
  82. return !failed;
  83. }
  84. static std::string get_file_base_name(const std::string& path) {
  85. size_t pos = path.find_last_of("/\\");
  86. if (pos != string::npos) {
  87. return path.substr(pos + 1);
  88. }
  89. else {
  90. return path;
  91. }
  92. }
  93. void MCell4Generator::generate_customization() {
  94. // first check unsupported MCell3 scripting
  95. Value& scripting = get_node(data.mcell, KEY_SCRIPTING);
  96. if (scripting.isMember(KEY_SCRIPTING_LIST)) {
  97. Value& scripting_list = get_node(scripting, KEY_SCRIPTING_LIST);
  98. if (!scripting_list.empty()) {
  99. ERROR("Data model contains MCell3 scripting. To convert this model: 1) disable MCell3 mode, 2) generate MDL, "
  100. "3) convert MDL to data model and 4) use this converter. "
  101. "Conversion will now continue, but it will be missing the scripted code."
  102. );
  103. }
  104. }
  105. // copy files from MCell4 scripting
  106. if (scripting.isMember(KEY_MCELL4_SCRIPTING_LIST)) {
  107. Value& mcell4_scripting_list = get_node(scripting, KEY_MCELL4_SCRIPTING_LIST);
  108. for (Value::ArrayIndex i = 0; i < mcell4_scripting_list.size(); i++) {
  109. Value& item = mcell4_scripting_list[i];
  110. bool internal;
  111. string internal_external = get_node(item, KEY_INTERNAL_EXTERNAL).asString();
  112. if (internal_external == VALUE_INTERNAL) {
  113. internal = true;
  114. }
  115. else if (internal_external == VALUE_EXTERNAL) {
  116. internal = false;
  117. }
  118. else {
  119. ERROR(S(KEY_INTERNAL_EXTERNAL) + " may be either " + VALUE_INTERNAL + " or " + VALUE_EXTERNAL +
  120. ", not " + internal_external + ".");
  121. }
  122. if (internal) {
  123. // create a file from internal script
  124. string fname = get_node(item, KEY_INTERNAL_FILE_NAME).asString();
  125. Value& script_texts = get_node(scripting, KEY_SCRIPT_TEXTS);
  126. if (!script_texts.isMember(fname)) {
  127. ERROR("Internal script " + fname + " was not found.");
  128. }
  129. // expecting the the file can be represented as text (it must be since it is in a JSON format)
  130. string code = get_node(script_texts, fname).asString();
  131. ofstream fout;
  132. fout.open(fname);
  133. if (!fout.is_open()) {
  134. ERROR("Could not open file " + fname + " for writing for internal script export.");
  135. }
  136. cout << "Creating " + fname + " from internal script file.\n";
  137. fout.write(code.c_str(), code.size());
  138. fout.close();
  139. }
  140. else {
  141. // copy external file
  142. string fname = get_node(item, KEY_EXTERNAL_FILE_NAME).asString();
  143. ifstream fin;
  144. fin.open(fname);
  145. if (!fin.is_open()) {
  146. ERROR("Could not open file " + fname + " for reading for external script export.");
  147. }
  148. // get base path
  149. string exported_fname = get_file_base_name(fname);
  150. cout << "exported_fname: " << exported_fname << "\n";
  151. ofstream fout;
  152. fout.open(exported_fname);
  153. if (!fout.is_open()) {
  154. fin.close();
  155. ERROR("Could not open file " + fname + " for writing for external script export.");
  156. }
  157. // copy data
  158. cout << "Copying " + fname + " to " + exported_fname + ".\n";
  159. fout << fin.rdbuf();
  160. fin.close();
  161. fout.close();
  162. }
  163. }
  164. }
  165. }
  166. void MCell4Generator::generate_shared() {
  167. ofstream out;
  168. open_and_check_file_w_prefix("", SHARED, out);
  169. out << GENERATED_WARNING << "\n";
  170. out <<
  171. "# This is an auxiliary module containing only a dictionary for parameter overrides.\n" <<
  172. "# It has to be a separate module so that it can be shared easily among all modules.\n";
  173. out << PARAMETER_OVERRIDES << " = {}\n";
  174. out.close();
  175. }
  176. void MCell4Generator::generate_simulation_setup_parameter(
  177. std::ostream& out, const string& name, const string& value) {
  178. string ind;
  179. if (!data.not_overridable_python_params) {
  180. // allow params to be overridden
  181. out << "if " << NOT_DEFINED << "('" << name << "'):\n";
  182. ind = IND4;
  183. }
  184. out << ind << name << " = " << value << "\n\n";
  185. }
  186. void MCell4Generator::generate_parameters() {
  187. ofstream out;
  188. open_and_check_file(PARAMETERS, out);
  189. out << GENERATED_WARNING << "\n";
  190. out << IMPORT_SYS_OS;
  191. out << IMPORT_MATH;
  192. out << IMPORT_SHARED;
  193. out << IMPORT_MCELL_AS_M;
  194. out << MODEL_PATH_SETUP << "\n";
  195. out << make_section_comment("model parameters");
  196. out <<
  197. "# declare all items from parameter_overrides as variables\n" <<
  198. "for parameter_name, value in " << SHARED << "." << PARAMETER_OVERRIDES << ".items():\n" <<
  199. " setattr(sys.modules[__name__], parameter_name, value)\n" <<
  200. "\n" <<
  201. "# auxiliary function used to determine whether a parameter was defined\n" <<
  202. "def " << NOT_DEFINED << "(parameter_name):\n" <<
  203. " return parameter_name not in globals()\n\n";
  204. if (data.mcell.isMember(KEY_PARAMETER_SYSTEM)) {
  205. if (data.bng_mode) {
  206. bng_gen->generate_parameters(out);
  207. }
  208. else {
  209. python_gen->generate_parameters(out);
  210. }
  211. }
  212. out << make_section_comment("simulation setup");
  213. generate_simulation_setup_parameter(out, PARAM_ITERATIONS, data.mcell[KEY_INITIALIZATION][KEY_ITERATIONS].asString());
  214. generate_simulation_setup_parameter(out, PARAM_TIME_STEP, data.mcell[KEY_INITIALIZATION][KEY_TIME_STEP].asString());
  215. generate_simulation_setup_parameter(out, PARAM_DUMP, (data.debug_mode ? "True" : "False"));
  216. generate_simulation_setup_parameter(out, PARAM_EXPORT_DATA_MODEL, "True");
  217. generate_simulation_setup_parameter(out, PARAM_SEED, "1");
  218. out << "\n";
  219. out.close();
  220. }
  221. std::string MCell4Generator::generate_species_and_mol_types(
  222. ostream& out, vector<SpeciesOrMolType>& species_and_mt_info) {
  223. // skip if there are no species/mol types
  224. if (!data.mcell.isMember(KEY_DEFINE_MOLECULES)) {
  225. return "";
  226. }
  227. if (data.bng_mode) {
  228. // molecule types are optional but they allow for better BNGL semantic checks
  229. // - we also need to generate code that sets diffusion constant,
  230. // custom time/space step and other parameters to molecule types,
  231. // - it must be executed after bngl file is loaded, so we should not be putting it into
  232. // subsystem, the new file can be called bngl_molecule_types_info.py
  233. // - all this extra information should be put preferably into the BNGL file
  234. // but for now we would like it to be BNGL compatible and parsing comments or MCELL_ parameters is not really nice
  235. stringstream mt_info_out;
  236. bng_gen->generate_mol_types(mt_info_out);
  237. return mt_info_out.str();
  238. }
  239. else {
  240. python_gen->generate_species_and_mol_types(out, species_and_mt_info);
  241. return "";
  242. }
  243. }
  244. static bool rxn_uses_mcell_orientation(Value& reaction_list_item) {
  245. string substances =
  246. reaction_list_item[KEY_REACTANTS].asString() + " " +
  247. reaction_list_item[KEY_PRODUCTS].asString();
  248. size_t pos_in = substances.find(S("@") + BNG::COMPARTMENT_NAME_IN);
  249. size_t pos_out = substances.find(S("@") + BNG::COMPARTMENT_NAME_OUT);
  250. bool has_in_out_compartments = pos_in != string::npos || pos_out != string::npos;
  251. // looking for ',; outside of parentheses
  252. bool in_paren = false;
  253. size_t i = 0;
  254. while (i < substances.size()) {
  255. char c = substances[i];
  256. if (c == '(') {
  257. CHECK_PROPERTY(!in_paren && "Malformed reaction definition - embedded parentheses");
  258. in_paren = true;
  259. }
  260. else if (c == ')') {
  261. CHECK_PROPERTY(in_paren && "Malformed reaction definition - unexpected closing parenthesis");
  262. in_paren = false;
  263. }
  264. else if (!in_paren && (c == ',' || c == '\'' || c == ';')) {
  265. // UP is allowed when compartment class @IN or @OUT is used
  266. if (!(has_in_out_compartments && c == '\'')) {
  267. return true;
  268. }
  269. }
  270. i++;
  271. }
  272. return false;
  273. }
  274. static bool rxn_has_variable_rate(Value& reaction_list_item) {
  275. return reaction_list_item[KEY_VARIABLE_RATE_SWITCH].asBool();
  276. }
  277. static bool rxn_uses_surf_class(
  278. Value& reaction_list_item,
  279. const std::vector<std::string>& surf_class_names) {
  280. vector<string> substances;
  281. vector<string> orientations;
  282. Value& reactants = reaction_list_item[KEY_REACTANTS];
  283. parse_rxn_rule_side(reactants, substances, orientations);
  284. for (const string& reac: substances) {
  285. if (find(surf_class_names.begin(), surf_class_names.end(), reac) != surf_class_names.end()) {
  286. return true;
  287. }
  288. }
  289. return false;
  290. }
  291. // returns true if the rxn name might be referenced by counts
  292. static bool is_rxn_used_in_observables(Value& mcell, const string& rxn_name) {
  293. if (rxn_name == "") {
  294. return false;
  295. }
  296. Value& reaction_data_output = get_node(mcell, KEY_REACTION_DATA_OUTPUT);
  297. Value& reaction_output_list = get_node(reaction_data_output, KEY_REACTION_OUTPUT_LIST);
  298. for (Value::ArrayIndex i = 0; i < reaction_output_list.size(); i++) {
  299. Value& reaction_output_item = reaction_output_list[i];
  300. string count_mdl_string = reaction_output_item[KEY_MDL_STRING].asString();
  301. string count_rxn_name = reaction_output_item[KEY_REACTION_NAME].asString();
  302. string string_to_check = count_mdl_string + " " +count_rxn_name;
  303. if (string_to_check.find(rxn_name) != string::npos) {
  304. return true;
  305. }
  306. }
  307. return false;
  308. }
  309. vector<IdLoc> MCell4Generator::generate_reaction_rules(ostream& out, const std::vector<std::string>& surf_class_names) {
  310. vector<IdLoc> rxn_names_w_loc;
  311. if (!data.mcell.isMember(KEY_DEFINE_REACTIONS)) {
  312. return rxn_names_w_loc;
  313. }
  314. if (data.bng_mode) {
  315. // put into BNG all rxn rules that,
  316. // 1) don't use MCell orientation or
  317. // 2) variable rxn rates
  318. // rest goes into python
  319. Value& define_reactions = get_node(data.mcell, KEY_DEFINE_REACTIONS);
  320. check_version(KEY_DEFINE_REACTIONS, define_reactions, VER_DM_2014_10_24_1638);
  321. bng_gen->open_reaction_rules_section();
  322. Value& reaction_list = get_node(define_reactions, KEY_REACTION_LIST);
  323. for (Value::ArrayIndex i = 0; i < reaction_list.size(); i++) {
  324. Value& reaction_list_item = reaction_list[i];
  325. check_version(KEY_MOLECULE_LIST, reaction_list_item, VER_DM_2018_01_11_1330);
  326. if (!rxn_uses_mcell_orientation(reaction_list_item) &&
  327. !rxn_has_variable_rate(reaction_list_item) &&
  328. !rxn_uses_surf_class(reaction_list_item, surf_class_names)) {
  329. bool used_in_observables = is_rxn_used_in_observables(data.mcell, reaction_list_item[KEY_RXN_NAME].asString());
  330. bool rxn_has_name = (reaction_list_item[KEY_RXN_NAME].asString() != "");
  331. string name = bng_gen->generate_single_reaction_rule(reaction_list_item, rxn_has_name);
  332. // string name = bng_gen->generate_single_reaction_rule(reaction_list_item, used_in_observables);
  333. rxn_names_w_loc.push_back(IdLoc(name, false));
  334. if (used_in_observables) {
  335. data.bngl_reaction_rules_used_in_observables.push_back(name);
  336. }
  337. }
  338. else {
  339. string name = python_gen->generate_single_reaction_rule(out, reaction_list_item);
  340. bng_gen->add_comment(
  341. S(IND) + "reaction '" + reaction_list_item[KEY_RXN_NAME].asString() +
  342. "' was generated as Python code because it contains features not supported by BNGL");
  343. rxn_names_w_loc.push_back(IdLoc(name, true));
  344. }
  345. }
  346. bng_gen->close_reaction_rules_section();
  347. }
  348. else {
  349. python_gen->generate_reaction_rules(out, rxn_names_w_loc);
  350. }
  351. return rxn_names_w_loc;
  352. }
  353. static void insert_compartments_in_string(const std::string& str_w_compartments, std::set<std::string>& compartments) {
  354. regex exp("@([0-9a-zA-Z_]+)");
  355. smatch res;
  356. string str = str_w_compartments;
  357. while (regex_search(str, res, exp)) {
  358. // not sure how else to cut off the leading '@'
  359. compartments.insert(string(res[0]).substr(1));
  360. str = res.suffix();
  361. }
  362. }
  363. void MCell4Generator::find_required_compartments(std::set<std::string>& compartments) {
  364. // rxns and release sites in data model may use compartments,
  365. // releases and observables use directly objects
  366. if (data.mcell.isMember(KEY_DEFINE_REACTIONS)) {
  367. Value& define_reactions = get_node(data.mcell, KEY_DEFINE_REACTIONS);
  368. Value& reaction_list = get_node(define_reactions, KEY_REACTION_LIST);
  369. for (Value::ArrayIndex i = 0; i < reaction_list.size(); i++) {
  370. Value& reaction_list_item = reaction_list[i];
  371. insert_compartments_in_string(reaction_list_item[KEY_REACTANTS].asString(), compartments);
  372. insert_compartments_in_string(reaction_list_item[KEY_PRODUCTS].asString(), compartments);
  373. }
  374. }
  375. if (data.mcell.isMember(KEY_RELEASE_SITES)) {
  376. Value& release_sites = get_node(data.mcell, KEY_RELEASE_SITES);
  377. Value& release_site_list = get_node(release_sites, KEY_RELEASE_SITE_LIST);
  378. for (Value::ArrayIndex i = 0; i < release_site_list.size(); i++) {
  379. Value& release_site_item = release_site_list[i];
  380. if (release_site_item.isMember(KEY_MOLECULE)) {
  381. insert_compartments_in_string(release_site_item[KEY_MOLECULE].asString(), compartments);
  382. }
  383. }
  384. }
  385. // and add all compartments used as parents & membranes
  386. Value& model_objects = get_node(data.mcell, KEY_MODEL_OBJECTS);
  387. Value& model_object_list = get_node(model_objects, KEY_MODEL_OBJECT_LIST);
  388. for (Value::ArrayIndex i = 0; i < model_object_list.size(); i++) {
  389. Value& model_object = model_object_list[i];
  390. const string& name = model_object[KEY_NAME].asString();
  391. const string& membrane_name = model_object[KEY_MEMBRANE_NAME].asString();
  392. const string& parent_object = model_object[KEY_PARENT_OBJECT].asString();
  393. if (compartments.count(name) != 0) {
  394. compartments.insert(membrane_name);
  395. compartments.insert(parent_object);
  396. }
  397. }
  398. }
  399. void MCell4Generator::analyze_and_generate_bngl_compartments(std::ostream& out) {
  400. // 1) figure out whether what compartments we need
  401. // not every named object must be a compartment
  402. find_required_compartments(data.used_compartments);
  403. // 2) add to BNGL file if needed
  404. if (data.bng_mode) {
  405. bng_gen->generate_compartments();
  406. }
  407. }
  408. void MCell4Generator::generate_subsystem() {
  409. ofstream out;
  410. open_and_check_file(SUBSYSTEM, out);
  411. out << GENERATED_WARNING << "\n";
  412. out << IMPORT_OS;
  413. out << IMPORT_SHARED;
  414. out << IMPORT_MCELL_AS_M;
  415. out << make_import(PARAMETERS);
  416. out << "\n";
  417. out << make_section_comment(SUBSYSTEM);
  418. out << MODEL_PATH_SETUP << "\n";
  419. string bngl_mol_types_initialization =
  420. generate_species_and_mol_types(out, data.all_species_and_mol_type_names);
  421. analyze_and_generate_bngl_compartments(out);
  422. vector<string> surface_class_names;
  423. python_gen->generate_surface_classes(out, surface_class_names);
  424. data.all_reaction_rules_names = generate_reaction_rules(out, surface_class_names);
  425. out << make_section_comment("create subsystem object and add components");
  426. gen_ctor_call(out, SUBSYSTEM, NAME_CLASS_SUBSYSTEM, false);
  427. for (SpeciesOrMolType& info: data.all_species_and_mol_type_names) {
  428. if (info.is_species) {
  429. gen_method_call(out, SUBSYSTEM, NAME_ADD_SPECIES, info.name);
  430. }
  431. else {
  432. gen_method_call(out, SUBSYSTEM, NAME_ADD_ELEMENTARY_MOLECULE_TYPE, info.name);
  433. }
  434. }
  435. for (string& sc: surface_class_names) {
  436. gen_method_call(out, SUBSYSTEM, NAME_ADD_SURFACE_CLASS, sc);
  437. }
  438. for (IdLoc& r_loc: data.all_reaction_rules_names) {
  439. if (r_loc.in_python) {
  440. gen_method_call(out, SUBSYSTEM, NAME_ADD_REACTION_RULE, r_loc.name);
  441. }
  442. }
  443. if (data.bng_mode) {
  444. // each part of the bngl file is loaded separately from specific modules,
  445. // there is a single bngl file because later it will contain all the information needed
  446. // to run in it BioNetGen
  447. out << "\n# load subsystem information from bngl file\n";
  448. gen_method_call(
  449. out, SUBSYSTEM,
  450. NAME_LOAD_BNGL_MOLECULE_TYPES_AND_REACTION_RULES,
  451. get_abs_path(get_filename(data.output_files_prefix, MODEL, BNGL_EXT)),
  452. S(SHARED) + "." + PARAMETER_OVERRIDES
  453. );
  454. out << "\n" << bngl_mol_types_initialization;
  455. out << SET_BNGL_MOLECULE_TYPES_INFO << "(" << SUBSYSTEM << ")\n";
  456. }
  457. out.close();
  458. }
  459. vector<string> MCell4Generator::generate_geometry() {
  460. vector<string> geometry_objects;
  461. // TODO: check versions
  462. if (!data.mcell.isMember(KEY_GEOMETRICAL_OBJECTS)) {
  463. return geometry_objects;
  464. }
  465. Value& geometrical_objects = get_node(data.mcell, KEY_GEOMETRICAL_OBJECTS);
  466. if (!geometrical_objects.isMember(KEY_OBJECT_LIST)) {
  467. return geometry_objects;
  468. }
  469. ofstream out;
  470. open_and_check_file(GEOMETRY, out);
  471. out << GENERATED_WARNING << "\n";
  472. out << IMPORT_MCELL_AS_M;
  473. python_gen->generate_geometry(out, geometry_objects);
  474. // NOTE: we can generate BNGL compartments from geometry here
  475. out.close();
  476. geometry_generated = true;
  477. return geometry_objects;
  478. }
  479. void MCell4Generator::generate_release_sites(std::ostream& out, std::vector<std::string>& release_site_names) {
  480. if (!data.mcell.isMember(KEY_RELEASE_SITES)) {
  481. return;
  482. }
  483. Value& release_sites = data.mcell[KEY_RELEASE_SITES];
  484. check_version(KEY_RELEASE_SITES, release_sites, VER_DM_2014_10_24_1638);
  485. Value& release_site_list = release_sites[KEY_RELEASE_SITE_LIST];
  486. if (release_site_list.empty()) {
  487. return;
  488. }
  489. if (data.bng_mode) {
  490. bool can_express_all_releases_with_bngl = true;
  491. for (Value::ArrayIndex i = 0; i < release_site_list.size(); i++) {
  492. // simulation result differs based on the order of releases so we must either generate all
  493. // releases into BNGL or none
  494. Value& release_site_item = release_site_list[i];
  495. if (!bng_gen->can_express_release_with_bngl(release_site_item)) {
  496. can_express_all_releases_with_bngl = false;
  497. break;
  498. }
  499. }
  500. if (can_express_all_releases_with_bngl) {
  501. bng_gen->open_seed_species_section();
  502. for (Value::ArrayIndex i = 0; i < release_site_list.size(); i++) {
  503. // simulation result differs based on the order of releases so we must either generate all
  504. // releases into BNGL or none
  505. const Value& release_site_item = release_site_list[i];
  506. check_not_empty(release_site_item, KEY_QUANTITY, "Release site");
  507. bng_gen->generate_single_release_site(
  508. release_site_item[KEY_MOLECULE].asString(),
  509. release_site_item[KEY_QUANTITY].asString(),
  510. get_description(release_site_item));
  511. }
  512. bng_gen->close_seed_species_section();
  513. // all done
  514. return;
  515. }
  516. }
  517. // python variant - used either without bng mode or as a fallback for bng mode
  518. python_gen->generate_release_sites(out, release_site_names);
  519. }
  520. void MCell4Generator::generate_instantiation(const vector<string>& geometry_objects) {
  521. ofstream out;
  522. open_and_check_file(INSTANTIATION, out);
  523. out << GENERATED_WARNING << "\n";
  524. out << IMPORT_OS;
  525. out << IMPORT_SHARED;
  526. out << IMPORT_MCELL_AS_M;
  527. out << make_import(PARAMETERS);
  528. out << make_import(SUBSYSTEM);
  529. if (geometry_generated) {
  530. out << make_import(GEOMETRY);
  531. }
  532. out << MODEL_PATH_SETUP << "\n";
  533. out << "\n";
  534. out << make_section_comment(INSTANTIATION);
  535. out << make_section_comment("release sites");
  536. out << make_section_comment("surface classes assignment");
  537. python_gen->generate_surface_classes_assignments(out);
  538. out << make_section_comment("compartments assignment");
  539. python_gen->generate_compartment_assignments(out);
  540. vector<string> release_sites;
  541. generate_release_sites(out, release_sites);
  542. out << make_section_comment("create instantiation object and add components");
  543. gen_ctor_call(out, INSTANTIATION, NAME_CLASS_INSTANTIATION, false);
  544. for (const string& s: geometry_objects) {
  545. gen_method_call(out, INSTANTIATION, NAME_ADD_GEOMETRY_OBJECT, s);
  546. }
  547. for (const string& r: release_sites) {
  548. gen_method_call(out, INSTANTIATION, NAME_ADD_RELEASE_SITE, r);
  549. }
  550. if (data.bng_mode) {
  551. out << "\n# load seed species information from bngl file\n";
  552. gen_method_call(
  553. out, INSTANTIATION,
  554. NAME_LOAD_BNGL_COMPARTMENTS_AND_SEED_SPECIES,
  555. get_abs_path(get_filename(data.output_files_prefix, MODEL, BNGL_EXT)),
  556. data.has_default_compartment_object ? BNG::DEFAULT_COMPARTMENT_NAME : "None",
  557. S(SHARED) + "." + PARAMETER_OVERRIDES
  558. );
  559. out << "\n";
  560. }
  561. out.close();
  562. }
  563. static size_t find_closing_bracket_pos(const string& s, const size_t start_pos) {
  564. if (start_pos == string::npos) {
  565. return string::npos;
  566. }
  567. assert(s[start_pos] == '[');
  568. size_t pos = start_pos + 1;
  569. int bracket_count = 1;
  570. while (pos < s.size() && bracket_count != 0) {
  571. switch (s[pos]) {
  572. case '[':
  573. bracket_count++;
  574. break;
  575. case ']':
  576. bracket_count--;
  577. break;
  578. default:
  579. // skip
  580. break;
  581. }
  582. pos++;
  583. }
  584. if (bracket_count != 0) {
  585. return string::npos;
  586. }
  587. else {
  588. return pos - 1;
  589. }
  590. }
  591. // also checks that the MDLString is correctly formed
  592. static string check_mdlstring_count_and_get_mult_or_div(const string& mdl_string) {
  593. const string format_msg =
  594. ". MDL string supported by MCell4 must be in format 'COUNT[pattern,region] *|/ const_expr' or "
  595. "{(} COUNT[pattern,region] { +|- {(} COUNT[pattern,region] {)} } {)} *|/ const_expr' "
  596. "({x} means 0..n of repetitions of x, and x|y means x or y).";
  597. string mdl_string_wo_comment = remove_c_comment(mdl_string);
  598. string str = remove_whitespace(mdl_string_wo_comment);
  599. int num_parens = 0;
  600. size_t pos = 0;
  601. const string COUNT = "COUNT";
  602. // check until we process all COUNTs with all parentheses
  603. while (pos < str.size() && !(str.find(COUNT, pos) == string::npos && num_parens == 0)) {
  604. switch (str[pos]) {
  605. case 'C': {
  606. // this must be COUNT[...]
  607. size_t count_pos = str.find(COUNT, pos);
  608. size_t opening_bracket_pos = str.find('[', pos);
  609. // brackets are allowed inside e,g, such as here: COUNT[sm1,Scene.Cube[ALL]]
  610. size_t closing_bracket_pos = find_closing_bracket_pos(str, opening_bracket_pos);
  611. if (pos != count_pos || opening_bracket_pos == string::npos || closing_bracket_pos == string::npos) {
  612. ERROR("Could not parse COUNT specifier in " + mdl_string_wo_comment + format_msg);
  613. }
  614. pos = closing_bracket_pos + 1;
  615. }
  616. break;
  617. case '(':
  618. num_parens++;
  619. pos++;
  620. break;
  621. case ')':
  622. num_parens--;
  623. pos++;
  624. break;
  625. case '+':
  626. case '-':
  627. // allowed
  628. pos++;
  629. break;
  630. default:
  631. ERROR("Unexpected character '" + str[pos] + "' in MDL string " +
  632. mdl_string_wo_comment + format_msg);
  633. break;
  634. }
  635. }
  636. if (num_parens != 0) {
  637. ERROR("Not matching parentheses in MDL string " +
  638. mdl_string_wo_comment + format_msg);
  639. }
  640. if (pos == str.size()) {
  641. // all checked, nothing more to process
  642. return "";
  643. }
  644. if (pos < str.size() && str.find(COUNT, pos) != string::npos) {
  645. ERROR("Unexpected COUNT specifier in MDL string " +
  646. mdl_string_wo_comment + format_msg);
  647. }
  648. // what optionally follows must be * or /
  649. string mul_or_div_expr_str = str.substr(pos);
  650. char first_expr_char = mul_or_div_expr_str[0];
  651. if (first_expr_char != '*' && first_expr_char != '/') {
  652. ERROR("The only allowed operator following COUNT expression(s) is '*' or '/', error for '" + first_expr_char + "' in " +
  653. mdl_string_wo_comment + format_msg);
  654. }
  655. return mul_or_div_expr_str;
  656. }
  657. // sets is_gdat, may return empty string meaning that the name must be determined automatically
  658. static string get_count_file_name(Value& reaction_output_item, bool& is_gdat) {
  659. if (reaction_output_item.isMember(KEY_OUTPUT_FILE_OVERRIDE)) {
  660. string res = reaction_output_item[KEY_OUTPUT_FILE_OVERRIDE].asString();
  661. string gdat = "gdat";
  662. is_gdat = res.size() > gdat.size() && res.substr(res.size() - gdat.size()) == gdat;
  663. return res;
  664. }
  665. else {
  666. // default
  667. string mdl_file_prefix = reaction_output_item[KEY_MDL_FILE_PREFIX].asString();
  668. is_gdat = false;
  669. if (mdl_file_prefix == "") {
  670. // not set
  671. return "";
  672. }
  673. else {
  674. return mdl_file_prefix + ".dat";
  675. }
  676. }
  677. }
  678. void MCell4Generator::generate_counts(
  679. std::ostream& out, std::vector<std::string>& python_counts, bool& has_bng_observables) {
  680. if (!data.mcell.isMember(KEY_REACTION_DATA_OUTPUT)) {
  681. return;
  682. }
  683. if (data.bng_mode) {
  684. python_gen->generate_all_bngl_reaction_rules_used_in_observables(out);
  685. }
  686. has_bng_observables = false;
  687. Value& reaction_data_output = get_node(data.mcell, KEY_REACTION_DATA_OUTPUT);
  688. check_version(KEY_DEFINE_MOLECULES, reaction_data_output, VER_DM_2016_03_15_1800);
  689. string rxn_step = reaction_data_output[KEY_RXN_STEP].asString();
  690. Value& reaction_output_list = get_node(reaction_data_output, KEY_REACTION_OUTPUT_LIST);
  691. for (Value::ArrayIndex i = 0; i < reaction_output_list.size(); i++) {
  692. Value& reaction_output_item = reaction_output_list[i];
  693. string observable_name = reaction_output_item[KEY_MDL_FILE_PREFIX].asString();
  694. bool is_gdat = false;
  695. string file_name = get_count_file_name(reaction_output_item, is_gdat);
  696. bool must_be_in_python = false;
  697. bool rxn_not_mol;
  698. bool molecules_not_species;
  699. bool single_term;
  700. string what_to_count;
  701. string where_to_count; // empty for WORLD
  702. string orientation;
  703. string count_location = reaction_output_item[KEY_COUNT_LOCATION].asString();
  704. string rxn_or_mol = reaction_output_item[KEY_RXN_OR_MOL].asString();
  705. string mul_div_str = "";
  706. if (rxn_or_mol == VALUE_MDLSTRING) {
  707. // first check whether we need to generate count_terms
  708. string mdl_string = reaction_output_item[KEY_MDL_STRING].asString();
  709. uint num_counts = get_num_counts_in_mdl_string(mdl_string);
  710. if (num_counts == 0) {
  711. ERROR("There is no 'COUNT' in mdl_string for output with filename " + observable_name + ".");
  712. }
  713. else if (num_counts == 1) {
  714. single_term = true;
  715. process_single_count_term(
  716. data, mdl_string, rxn_not_mol, molecules_not_species,
  717. what_to_count, where_to_count, orientation);
  718. }
  719. else {
  720. must_be_in_python = true;
  721. single_term = false;
  722. }
  723. mul_div_str = check_mdlstring_count_and_get_mult_or_div(mdl_string);
  724. assert(mul_div_str.empty() || mul_div_str[0] == '*' || mul_div_str[0] == '/');
  725. }
  726. else if (rxn_or_mol == VALUE_REACTION) {
  727. single_term = true;
  728. rxn_not_mol = true;
  729. what_to_count = reaction_output_item[KEY_REACTION_NAME].asString();
  730. if (count_location == VALUE_COUNT_LOCATION_OBJECT) {
  731. where_to_count = reaction_output_item[KEY_OBJECT_NAME].asString();
  732. }
  733. else if (count_location == VALUE_COUNT_LOCATION_REGION) {
  734. where_to_count = reaction_output_item[KEY_REGION_NAME].asString();
  735. }
  736. else {
  737. assert(count_location == VALUE_COUNT_LOCATION_WORLD);
  738. }
  739. }
  740. else if (rxn_or_mol == VALUE_MOLECULE) {
  741. single_term = true;
  742. rxn_not_mol = false;
  743. what_to_count = reaction_output_item[KEY_MOLECULE_NAME].asString();
  744. if (count_location == VALUE_COUNT_LOCATION_OBJECT) {
  745. where_to_count = reaction_output_item[KEY_OBJECT_NAME].asString();
  746. }
  747. else if (count_location == VALUE_COUNT_LOCATION_REGION) {
  748. where_to_count = reaction_output_item[KEY_REGION_NAME].asString();
  749. }
  750. else {
  751. assert(count_location == VALUE_COUNT_LOCATION_WORLD);
  752. }
  753. }
  754. else {
  755. ERROR("Invalid rxn_or_mol '" + rxn_or_mol + "' in reaction_output_list for output with filename " +
  756. observable_name + ".");
  757. }
  758. if (observable_name == "") {
  759. // this is a case where mdl_string is empty
  760. string where = where_to_count;
  761. if (where == "") {
  762. where = WORLD_FIRST_UPPER;
  763. }
  764. // using underscore instead of '.' that was used in MCell3, '.' cannot be used in BNGL
  765. observable_name = what_to_count + "_" + where;
  766. if (mul_div_str != "") {
  767. observable_name += '_' + fix_id(mul_div_str);
  768. }
  769. // TODO: this might need further checks
  770. if (observable_name.find_first_of(" ,+*/\\") != string::npos) {
  771. cout << "Warning: count file prefix '" + observable_name + "' is probably invalid.\n";
  772. }
  773. if (file_name == "") {
  774. file_name = observable_name + ".dat";
  775. }
  776. }
  777. if (data.bng_mode && !must_be_in_python &&
  778. bng_gen->can_express_count_with_bngl(
  779. single_term, rxn_not_mol, where_to_count, orientation, mul_div_str, rxn_step)) {
  780. if (!has_bng_observables) {
  781. bng_gen->open_observables_section();
  782. }
  783. bng_gen->generate_single_count(
  784. observable_name,
  785. what_to_count,
  786. get_description(reaction_output_item),
  787. molecules_not_species);
  788. has_bng_observables = true;
  789. if (is_gdat) {
  790. if (data.bng_observables_output_gdat_file == "") {
  791. data.bng_observables_output_gdat_file = file_name;
  792. }
  793. else if (file_name != data.bng_observables_output_gdat_file) {
  794. ERROR("Cannot use multiple GDAT output files with BNGL mode, error for " + file_name);
  795. }
  796. }
  797. }
  798. else {
  799. string name = fix_id(COUNT_PREFIX + observable_name);
  800. // prepare count terms
  801. string mdl_string = reaction_output_item[KEY_MDL_STRING].asString();
  802. string count_term_name =
  803. python_gen->generate_count_terms_for_expression(
  804. out, mdl_string, what_to_count, where_to_count, orientation, rxn_not_mol);
  805. where_to_count = "";
  806. gen_description(out, reaction_output_item);
  807. python_gen->generate_single_count(
  808. out,
  809. name,
  810. observable_name,
  811. file_name,
  812. count_term_name,
  813. mul_div_str,
  814. rxn_step
  815. );
  816. python_counts.push_back(name);
  817. }
  818. }
  819. }
  820. void MCell4Generator::generate_observables() {
  821. ofstream out;
  822. open_and_check_file(OBSERVABLES, out);
  823. out << GENERATED_WARNING << "\n";
  824. out << IMPORT_OS;
  825. out << IMPORT_SHARED;
  826. out << IMPORT_MCELL_AS_M;
  827. out << make_import(PARAMETERS);
  828. out << make_import(SUBSYSTEM);
  829. if (geometry_generated) {
  830. out << make_import(GEOMETRY);
  831. }
  832. out << MODEL_PATH_SETUP << "\n";
  833. out << "\n";
  834. out << make_section_comment(OBSERVABLES);
  835. bool use_cellblender_output;
  836. if (data.mcell[KEY_INITIALIZATION].isMember(KEY_EXPORT_ALL_ASCII)) {
  837. use_cellblender_output = !data.mcell[KEY_INITIALIZATION][KEY_EXPORT_ALL_ASCII].asBool();
  838. }
  839. else {
  840. use_cellblender_output = false;
  841. }
  842. vector<string> viz_outputs;
  843. python_gen->generate_viz_outputs(out, use_cellblender_output, viz_outputs);
  844. vector<string> counts;
  845. bool has_bngl_observables;
  846. generate_counts(out, counts, has_bngl_observables);
  847. out << make_section_comment("create observables object and add components");
  848. gen_ctor_call(out, OBSERVABLES, NAME_CLASS_OBSERVABLES, false);
  849. for (const string& s: viz_outputs) {
  850. gen_method_call(out, OBSERVABLES, NAME_ADD_VIZ_OUTPUT, s);
  851. }
  852. for (const string& r: counts) {
  853. gen_method_call(out, OBSERVABLES, NAME_ADD_COUNT, r);
  854. }
  855. if (has_bngl_observables) {
  856. out << "\n# load observables information from bngl file\n";
  857. gen_method_call(
  858. out, OBSERVABLES,
  859. NAME_LOAD_BNGL_OBSERVABLES,
  860. get_abs_path(get_filename(data.output_files_prefix, MODEL, BNGL_EXT)) + ", " +
  861. "'" + DEFAULT_RXN_OUTPUT_FILENAME_PREFIX + data.bng_observables_output_gdat_file + "'",
  862. S(SHARED) + "." + PARAMETER_OVERRIDES
  863. );
  864. bng_gen->close_observables_section();
  865. }
  866. observables_generated = true;
  867. out.close();
  868. }
  869. // returns true for "ON and false for "OFF, fails when a different value is used
  870. static std::string convert_warning_level(const std::string& value) {
  871. if (value == VALUE_ERROR) {
  872. return S(MDOT) + NAME_ENUM_WARNING_LEVEL + "." + NAME_EV_ERROR;
  873. }
  874. else if (value == VALUE_WARNING) {
  875. return S(MDOT) + NAME_ENUM_WARNING_LEVEL + "." + NAME_EV_WARNING;
  876. }
  877. else if (value == VALUE_IGNORED) {
  878. return S(MDOT) + NAME_ENUM_WARNING_LEVEL + "." + NAME_EV_IGNORE;
  879. }
  880. else {
  881. ERROR("Invalid value " + value + ", expected only WARNING or IGNORED.\n");
  882. }
  883. }
  884. void MCell4Generator::generate_config(ostream& out) {
  885. out << make_section_comment("configuration");
  886. // using values from generated parameters.py where applicable
  887. gen_assign(out, MODEL, NAME_CONFIG, NAME_TIME_STEP, PARAM_TIME_STEP);
  888. if (data.mcell.isMember(KEY_USE_BNG_UNITS) && data.mcell[KEY_USE_BNG_UNITS].asBool()) {
  889. gen_assign(out, MODEL, NAME_CONFIG, NAME_USE_BNG_UNITS, true);
  890. }
  891. gen_assign(out, MODEL, NAME_CONFIG, NAME_SEED, PARAM_SEED);
  892. gen_assign(out, MODEL, NAME_CONFIG, NAME_TOTAL_ITERATIONS, PARAM_ITERATIONS);
  893. out << "\n";
  894. Value& initialization = data.mcell[KEY_INITIALIZATION];
  895. if (initialization.isMember(KEY_WARNINGS)) {
  896. Value& warnings = initialization[KEY_WARNINGS];
  897. if (warnings.isMember(KEY_HIGH_REACTION_PROBABILITY)) {
  898. gen_assign(out, MODEL, NAME_WARNINGS, NAME_HIGH_REACTION_PROBABILITY,
  899. convert_warning_level(warnings[KEY_HIGH_REACTION_PROBABILITY].asString())
  900. );
  901. }
  902. if (warnings.isMember(KEY_MOLECULE_PLACEMENT_FAILURE)) {
  903. gen_assign(out, MODEL, NAME_WARNINGS, NAME_MOLECULE_PLACEMENT_FAILURE,
  904. convert_warning_level(warnings[KEY_HIGH_REACTION_PROBABILITY].asString())
  905. );
  906. }
  907. }
  908. if (initialization.isMember(KEY_NOTIFICATIONS)) {
  909. Value& notifications = initialization[KEY_NOTIFICATIONS];
  910. if (notifications.isMember(KEY_SPECIES_REACTIONS_REPORT)) {
  911. gen_assign(out, MODEL, NAME_NOTIFICATIONS, NAME_RXN_AND_SPECIES_REPORT,
  912. notifications[KEY_SPECIES_REACTIONS_REPORT].asBool()
  913. );
  914. }
  915. if (notifications.isMember(KEY_VARYING_PROBABILITY_REPORT)) {
  916. gen_assign(out, MODEL, NAME_NOTIFICATIONS, NAME_RXN_PROBABILITY_CHANGED,
  917. notifications[KEY_VARYING_PROBABILITY_REPORT].asBool()
  918. );
  919. }
  920. }
  921. out << "\n";
  922. if (!data.mcell.isMember(KEY_INITIALIZATION)) {
  923. ERROR(S("Data model does not contain key ") + KEY_INITIALIZATION + ".");
  924. }
  925. Value& partitions = initialization[KEY_PARTITIONS];
  926. // choose the largest value for partition size and the smallest step
  927. double x_start = stod(partitions[KEY_X_START].asString());
  928. double x_end = stod(partitions[KEY_X_END].asString());
  929. double x_step = stod(partitions[KEY_X_STEP].asString());
  930. double y_start = stod(partitions[KEY_Y_START].asString());
  931. double y_end = stod(partitions[KEY_Y_END].asString());
  932. double y_step = stod(partitions[KEY_Y_STEP].asString());
  933. double z_start = stod(partitions[KEY_Z_START].asString());
  934. double z_end = stod(partitions[KEY_Z_END].asString());
  935. double z_step = stod(partitions[KEY_Z_STEP].asString());
  936. double partition_dimension;
  937. if (!cmp_eq(x_start, -x_end) || !cmp_eq(y_start, -y_end) || !cmp_eq(y_start, -y_end) ||
  938. !cmp_eq(x_start, y_start) || !cmp_eq(y_start, z_start) ||
  939. !cmp_eq(x_end, y_end) || !cmp_eq(y_end, z_end)
  940. ) {
  941. gen_assign_vec3(out, MODEL, NAME_CONFIG, NAME_INITIAL_PARTITION_ORIGIN, x_start, y_start, z_start);
  942. // select the largest difference as partition dimension
  943. Vec3 dims = Vec3(x_end - x_start, y_end - y_start, z_end - z_start);
  944. partition_dimension = max3(dims);
  945. }
  946. else {
  947. partition_dimension = x_end * 2;
  948. }
  949. double partition_step;
  950. if (!cmp_eq(x_step, y_step) || !cmp_eq(y_step, z_step)) {
  951. cout << "Message: Individual partition step sizes are different, changing the step to be the smallest of them.\n";
  952. partition_step = min3d(x_step, y_step, z_step);
  953. }
  954. else {
  955. partition_step = x_step;
  956. }
  957. gen_assign(out, MODEL, NAME_CONFIG, NAME_PARTITION_DIMENSION, partition_dimension);
  958. gen_assign(out, MODEL, NAME_CONFIG, NAME_SUBPARTITION_DIMENSION, partition_step);
  959. out << "\n";
  960. out << make_section_comment("default configuration overrides");
  961. // FIXME: check the value against the default in the yaml API file
  962. bool center_molecules_on_grid = initialization[KEY_CENTER_MOLECULES_ON_GRID].asBool();
  963. if (center_molecules_on_grid) {
  964. gen_assign(out, MODEL, NAME_CONFIG, NAME_CENTER_MOLECULES_ON_GRID, center_molecules_on_grid);
  965. }
  966. string radius = initialization[KEY_INTERACTION_RADIUS].asString();
  967. if (radius != "") {
  968. gen_assign(out, MODEL, NAME_CONFIG, NAME_INTERACTION_RADIUS, radius);
  969. }
  970. string surf_grid_density = initialization[KEY_SURFACE_GRID_DENSITY].asString();
  971. if (surf_grid_density != "" && surf_grid_density != "10000") {
  972. gen_assign(out, MODEL, NAME_CONFIG, NAME_SURFACE_GRID_DENSITY, surf_grid_density);
  973. }
  974. string vacancy_search_distance = initialization[KEY_VACANCY_SEARCH_DISTANCE].asString();
  975. if (vacancy_search_distance != "" && vacancy_search_distance != "10") {
  976. gen_assign(out, MODEL, NAME_CONFIG, NAME_VACANCY_SEARCH_DISTANCE, vacancy_search_distance);
  977. }
  978. }
  979. void MCell4Generator::generate_model(const bool print_failed_marker) {
  980. ofstream out;
  981. open_and_check_file(MODEL, out);
  982. out << INTERPRETER;
  983. out << GENERATED_WARNING << "\n";
  984. if (print_failed_marker) {
  985. out <<
  986. "ERROR: Conversion from data model failed, these generated sources are result of a "
  987. "best-effort conversion and will contain errors that might need to be fixed manually.\n\n";
  988. }
  989. out << IMPORT_SYS_OS;
  990. out << get_import("importlib.util");
  991. out << "\n";
  992. out << MODEL_PATH_SETUP;
  993. out << "\n";
  994. out << MCELL_PATH_SETUP;
  995. out << "\n";
  996. out << IMPORT_MCELL_AS_M;
  997. string customization_module = CUSTOMIZATION;
  998. string shared_module = SHARED;
  999. out << "\n" << make_section_comment("customization and argument processing");
  1000. out << "# this module is used to hold any overrides of parameter values\n";
  1001. out << IMPORT_SHARED;
  1002. out << "# import the customization.py module if it exists\n";
  1003. out << get_customization_import(customization_module);
  1004. out << "\n";
  1005. if (data.testing_mode) {
  1006. out << CHECKPOINT_ITERATION << " = None\n\n";
  1007. }
  1008. out << "# process command-line arguments\n";
  1009. out << get_argparse_w_customization_begin(customization_module);
  1010. if (data.testing_mode) {
  1011. out << get_argparse_checkpoint_iteration();
  1012. }
  1013. out << get_argparse_w_customization_end();
  1014. out << "\n";
  1015. out << S("\n# the module parameters uses ") + SHARED + "." + PARAMETER_OVERRIDES + " to override parameter values\n";
  1016. out << make_import(PARAMETERS);
  1017. out << "\n\n";
  1018. out << get_resume_from_checkpoint_code();
  1019. out << "\n" << make_section_comment("model creation and simulation");
  1020. out << "# create main model object\n";
  1021. gen_ctor_call(out, MODEL, NAME_CLASS_MODEL, false);
  1022. out << "\n";
  1023. generate_config(out);
  1024. if (data.testing_mode) {
  1025. out << make_section_comment("testing-specific configuration");
  1026. gen_assign(out, MODEL, NAME_CONFIG, NAME_REACTION_CLASS_CLEANUP_PERIODICITY, TESTING_RXN_CLASS_CLEANUP_PERIODICITY);
  1027. gen_assign(out, MODEL, NAME_CONFIG, NAME_SPECIES_CLEANUP_PERIODICITY, TESTING_SPECIES_CLEANUP_PERIODICITY);
  1028. out << "\n";
  1029. }
  1030. out << make_section_comment("add components");
  1031. out << get_import(get_module_name(SUBSYSTEM));
  1032. out << get_import(get_module_name(INSTANTIATION));
  1033. if (observables_generated) {
  1034. out << get_import(get_module_name(OBSERVABLES));
  1035. }
  1036. out << "\n";
  1037. gen_method_call(out, MODEL, NAME_ADD_SUBSYSTEM, get_module_name(SUBSYSTEM) + "." + SUBSYSTEM);
  1038. gen_method_call(out, MODEL, NAME_ADD_INSTANTIATION, get_module_name(INSTANTIATION) + "." + INSTANTIATION);
  1039. if (observables_generated) {
  1040. gen_method_call(out, MODEL, NAME_ADD_OBSERVABLES, get_module_name(OBSERVABLES) + "." + OBSERVABLES);
  1041. }
  1042. out << "\n";
  1043. out << get_user_defined_configuration(customization_module);
  1044. out << "\n";
  1045. out << make_section_comment("initialization and execution");
  1046. out <<
  1047. "if " << customization_module << " and '" << CUSTOM_INIT_AND_RUN << "' in dir(" << customization_module << "):\n" <<
  1048. IND4 << customization_module << "." << CUSTOM_INIT_AND_RUN << "(" << MODEL << ")\n" <<
  1049. "else:\n"
  1050. ;
  1051. out << IND4;
  1052. gen_method_call(out, MODEL, NAME_INITIALIZE);
  1053. out << "\n";
  1054. if (!data.checkpoint_iterations.empty()) {
  1055. out << make_section_comment("checkpoint iterations");
  1056. for (int it: data.checkpoint_iterations) {
  1057. out << IND4;
  1058. gen_method_call(out, MODEL, NAME_SCHEDULE_CHECKPOINT, to_string(it));
  1059. }
  1060. out << "\n";
  1061. }
  1062. out << IND4 << "if " << PARAM_DUMP << ":\n";
  1063. out << IND8;
  1064. gen_method_call(out, MODEL, NAME_DUMP_INTERNAL_STATE);
  1065. out << "\n";
  1066. // method export_data_model uses target directory from viz_outputs
  1067. out << IND4 << "if " << PARAM_EXPORT_DATA_MODEL << " and " << MODEL << "." << NAME_VIZ_OUTPUTS << ":\n";
  1068. out << IND8;
  1069. gen_method_call(out, MODEL, NAME_EXPORT_DATA_MODEL);
  1070. out << "\n";
  1071. out << IND4;
  1072. gen_method_call(out, MODEL, NAME_RUN_ITERATIONS, PARAM_ITERATIONS);
  1073. out << IND4;
  1074. gen_method_call(out, MODEL, NAME_END_SIMULATION);
  1075. }
  1076. void MCell4Generator::generate_customization_template() {
  1077. // check if file exists, do not overwrite
  1078. string cust_filename = S(CUSTOMIZATION) + PY_EXT;
  1079. ifstream tmp;
  1080. tmp.open(cust_filename);
  1081. if (tmp.is_open()) {
  1082. cout << "Message: custom file " + cust_filename + " already exists, keeping it as it is.\n";
  1083. tmp.close();
  1084. return;
  1085. }
  1086. ofstream out;
  1087. open_and_check_file_w_prefix("", CUSTOMIZATION, out);
  1088. out <<
  1089. "# This file contains hooks to override default MCell4 model\n"
  1090. "# code behavior for models generated from CellBlender\n"
  1091. "# WARNING: do not import file parameters.py at the top level of this file,\n"
  1092. "# only from individual functions if needed, parameters such as SEED\n"
  1093. "# will not be set correctly if parameters are imported too early\n";
  1094. out << IMPORT_SYS_OS;
  1095. out << IMPORT_SHARED;
  1096. out << IMPORT_MCELL_AS_M;
  1097. out << TEMPLATE_CUSTOM_ARGPARSE_AND_PARAMETERS << "\n";
  1098. out << TEMPLATE_CUSTOM_CONFIG << "\n";
  1099. out << get_template_custom_init_and_run(get_module_name(PARAMETERS)) << "\n";
  1100. }
  1101. } // namespace MCell