bng_data_to_datamodel_converter.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426
  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 <exception>
  12. #include <string>
  13. #include "bng_data_to_datamodel_converter.h"
  14. #include "datamodel_defines.h"
  15. #include "bng/bng.h"
  16. #include "viz_output_event.h"
  17. #include "scheduler.h"
  18. #include "world.h"
  19. using namespace std;
  20. using namespace DMUtils;
  21. using namespace BNG;
  22. using Json::Value;
  23. namespace MCell {
  24. typedef std::invalid_argument ConversionError;
  25. #define CHECK(stmt) \
  26. do { \
  27. try { \
  28. (stmt); \
  29. } \
  30. catch (exception& e) { \
  31. cerr << e.what() << "\n"; \
  32. cerr << "Exception caught in '" << __FUNCTION__ << "' after conversion error.\n"; \
  33. conversion_failed = true; \
  34. } \
  35. } while (0)
  36. #define CHECK_PROPERTY(cond) \
  37. do { \
  38. if(!(cond)) { \
  39. throw ConversionError("Expected '" + #cond + "' is false. (" + __FUNCTION__ + " - " + __FILE__ + ":" + to_string(__LINE__) + ")"); \
  40. } \
  41. } while (0)
  42. #define CHECK_PROPERTY_W_NAME(cond, name) \
  43. do { \
  44. if(!(cond)) { \
  45. throw ConversionError(string("Conversion of ") + name + ": Expected '" + #cond + "' is false. (" + __FUNCTION__ + " - " + __FILE__ + ":" + to_string(__LINE__) + ")"); \
  46. } \
  47. } while (0)
  48. BngDataToDatamodelConverter::BngDataToDatamodelConverter() {
  49. reset();
  50. // list from https://www.rapidtables.com/web/color/RGB_Color.html
  51. colors.push_back(Vec3(1, 0, 0));
  52. colors.push_back(Vec3(0, 1, 0));
  53. colors.push_back(Vec3(0, 0, 1));
  54. colors.push_back(Vec3(1, 1, 0));
  55. colors.push_back(Vec3(0, 1, 1));
  56. colors.push_back(Vec3(1, 0, 1));
  57. // ...
  58. }
  59. void BngDataToDatamodelConverter::reset() {
  60. world = nullptr;
  61. bng_engine = nullptr;
  62. next_color_index = 0;
  63. rxn_counter = 0;
  64. conversion_failed = false;
  65. processed_surface_classes.clear();
  66. }
  67. void BngDataToDatamodelConverter::to_data_model(
  68. const World* world_, Value& mcell_node, const bool only_for_viz) {
  69. reset();
  70. world = world_;
  71. bng_engine = &(world->bng_engine);
  72. // similarly as in pymcell converter, maximum effort is given to conversion and
  73. // if some parts won't go through, we are trying to generate
  74. CHECK(convert_molecules(mcell_node));
  75. // compartments are converted in GeometryObject into the KEY_MODEL_OBJECTS section
  76. if (!only_for_viz) {
  77. CHECK(convert_rxns(mcell_node));
  78. }
  79. return;
  80. }
  81. Vec3 BngDataToDatamodelConverter::get_next_color() {
  82. Vec3 res = colors[next_color_index];
  83. next_color_index++;
  84. if (next_color_index == colors.size()) {
  85. next_color_index = 0;
  86. }
  87. return res;
  88. }
  89. void BngDataToDatamodelConverter::convert_molecules(Value& mcell_node) {
  90. Value& define_molecules = mcell_node[KEY_DEFINE_MOLECULES];
  91. add_version(define_molecules, VER_DM_2014_10_24_1638);
  92. Value& molecule_list = define_molecules[KEY_MOLECULE_LIST];
  93. molecule_list = Value(Json::arrayValue); // empty array
  94. // we are converting molecule types because that's the information we need in
  95. // the datamodel, we do not care about species
  96. for (const ElemMolType& mt: bng_engine->get_data().get_elem_mol_types()) {
  97. // - ALL_* species are ignored
  98. // - reactive surfaces are converted as surface classes
  99. // -
  100. if (is_species_superclass(mt.name) || mt.is_reactive_surface()) {
  101. continue;
  102. }
  103. Value molecule_node;
  104. CHECK(convert_single_mol_type(mt, molecule_node));
  105. molecule_list.append(molecule_node);
  106. }
  107. }
  108. void BngDataToDatamodelConverter::convert_single_mol_type(const BNG::ElemMolType& mt, Value& molecule_node) {
  109. add_version(molecule_node, VER_DM_2018_10_16_1632);
  110. Value& display = molecule_node[KEY_DISPLAY];
  111. display[KEY_EMIT] = 0.0;
  112. Value& color_value = display[KEY_COLOR];
  113. if (mt.color_set) {
  114. color_value.append(mt.color_r);
  115. color_value.append(mt.color_g);
  116. color_value.append(mt.color_b);
  117. }
  118. else {
  119. // automatic color
  120. Vec3 c = get_next_color();
  121. color_value.append(c.r);
  122. color_value.append(c.g);
  123. color_value.append(c.b);
  124. }
  125. display[KEY_GLYPH] = VALUE_GLYPH_SPHERE_1;
  126. display[KEY_SCALE] = mt.scale;
  127. molecule_node[KEY_BNGL_COMPONENT_LIST] = Value(Json::arrayValue); // empty array
  128. molecule_node[KEY_MOL_BNGL_LABEL] = "";
  129. molecule_node[KEY_DESCRIPTION] = "";
  130. molecule_node[KEY_MOL_TYPE] = mt.is_vol() ? VALUE_MOL_TYPE_3D : VALUE_MOL_TYPE_2D;
  131. const VizOutputEvent* viz =
  132. dynamic_cast<const VizOutputEvent*>(world->scheduler.find_next_event_with_type_index(EVENT_TYPE_INDEX_VIZ_OUTPUT));
  133. if (viz == nullptr || viz->should_visualize_all_species()) {
  134. // no visualization/or enabled globally
  135. molecule_node[KEY_EXPORT_VIZ] = false;
  136. }
  137. else {
  138. // simple species based on this molecule type set to be visualized?
  139. // the simple species have always the same name
  140. species_id_t species_id = bng_engine->get_all_species().find_by_name(mt.name);
  141. molecule_node[KEY_EXPORT_VIZ] = viz->species_ids_to_visualize.count(species_id) == 1;
  142. }
  143. if (mt.custom_space_step != 0) {
  144. molecule_node[KEY_CUSTOM_SPACE_STEP] = f_to_str(mt.custom_space_step);
  145. }
  146. else {
  147. molecule_node[KEY_CUSTOM_SPACE_STEP] = "";
  148. }
  149. if (mt.custom_time_step != 0) {
  150. molecule_node[KEY_CUSTOM_TIME_STEP] = f_to_str(mt.custom_time_step);
  151. }
  152. else {
  153. molecule_node[KEY_CUSTOM_TIME_STEP] = "";
  154. }
  155. molecule_node[KEY_MAXIMUM_STEP_LENGTH] = "";
  156. molecule_node[KEY_TARGET_ONLY] = mt.cant_initiate();
  157. molecule_node[KEY_DIFFUSION_CONSTANT] = f_to_str(mt.D);
  158. molecule_node[KEY_SPATIAL_STRUCTURE] = "None";
  159. molecule_node[KEY_MOL_NAME] = mt.name;
  160. if (!mt.component_type_ids.empty()) {
  161. // generate info on components
  162. Value& bngl_component_list = molecule_node[KEY_BNGL_COMPONENT_LIST];
  163. bngl_component_list = Value(Json::arrayValue);
  164. for (component_type_id_t ct_id: mt.component_type_ids) {
  165. Value bngl_component;
  166. const ComponentType& ct = bng_engine->get_data().get_component_type(ct_id);
  167. bngl_component[KEY_CNAME] = ct.name;
  168. Value& cstates = bngl_component[KEY_CSTATES];
  169. cstates = Value(Json::arrayValue);
  170. for (state_id_t s_id: ct.allowed_state_ids) {
  171. const string& state_name = bng_engine->get_data().get_state_name(s_id);
  172. cstates.append(state_name);
  173. }
  174. // additional empty data required by cellblender
  175. bngl_component[KEY_IS_KEY] = false;
  176. bngl_component[KEY_ROT_INDEX] = 0;
  177. bngl_component[KEY_ROT_ANG] = "0";
  178. bngl_component[KEY_ROT_X] = "0";
  179. bngl_component[KEY_ROT_Y] = "0";
  180. bngl_component[KEY_ROT_Z] = "0";
  181. bngl_component[KEY_LOC_X] = "0";
  182. bngl_component[KEY_LOC_Y] = "0";
  183. bngl_component[KEY_LOC_Z] = "0";
  184. bngl_component_list.append(bngl_component);
  185. }
  186. }
  187. }
  188. void BngDataToDatamodelConverter::convert_single_rxn_rule(const BNG::RxnRule& r, Value& rxn_node) {
  189. assert(r.type == RxnType::Standard);
  190. add_version(rxn_node, VER_DM_2018_01_11_1330);
  191. // name is put into rnx_name and name is the string of the reaction
  192. rxn_node[KEY_RXN_NAME] = r.name;
  193. rxn_node[KEY_DESCRIPTION] = "";
  194. // LATER: maybe find an opposite reaction and generate it as reversible
  195. rxn_node[KEY_RXN_TYPE] = VALUE_IRREVERSIBLE;
  196. string reactants = r.reactants_to_str();
  197. rxn_node[KEY_REACTANTS] = reactants;
  198. string products = r.products_to_str();
  199. if (products == "") {
  200. products = VALUE_NULL;
  201. }
  202. rxn_node[KEY_PRODUCTS] = products;
  203. // generated directly by data_model_to_mdl converter,
  204. // reversible rxns were cloned
  205. rxn_node[KEY_NAME] = reactants + " -> " + products;
  206. if (r.base_variable_rates.empty()) {
  207. rxn_node[KEY_FWD_RATE] = f_to_str(r.base_rate_constant);
  208. rxn_node[KEY_BKWD_RATE] = "";
  209. CHECK_PROPERTY_W_NAME(r.base_variable_rates.empty(), r.name);
  210. rxn_node[KEY_VARIABLE_RATE_VALID] = false;
  211. rxn_node[KEY_VARIABLE_RATE_SWITCH] = false;
  212. rxn_node[KEY_VARIABLE_RATE] = "";
  213. rxn_node[KEY_VARIABLE_RATE_TEXT] = "";
  214. }
  215. else {
  216. rxn_node[KEY_FWD_RATE] = "0";
  217. rxn_node[KEY_BKWD_RATE] = "";
  218. rxn_node[KEY_VARIABLE_RATE_VALID] = true;
  219. rxn_node[KEY_VARIABLE_RATE_SWITCH] = true;
  220. rxn_node[KEY_VARIABLE_RATE] = "var_rate_" + DMUtils::to_id(r.name) + "_" + to_string(rxn_counter);
  221. rxn_counter++;
  222. stringstream text;
  223. // initial value for time 0
  224. text << 0.0 << "\t" << r.base_rate_constant << "\n";
  225. for (const RxnRateInfo& ri: r.base_variable_rates) {
  226. text << ri.time * world->config.time_unit << "\t" << ri.rate_constant << "\n";
  227. }
  228. rxn_node[KEY_VARIABLE_RATE_TEXT] = text.str();
  229. }
  230. }
  231. string BngDataToDatamodelConverter::get_surface_class_name(const BNG::RxnRule& r) {
  232. assert(r.is_bimol() && r.is_reactive_surface_rxn());
  233. uint idx = (r.reactants[0].is_reactive_surface()) ? 0 : 1;
  234. assert(r.reactants[idx].is_simple());
  235. // the name of the surface class is the name of the species/elem mol type
  236. return
  237. bng_engine->get_data().get_elem_mol_type(r.reactants[idx].get_simple_species_mol_type_id()).name;
  238. }
  239. void BngDataToDatamodelConverter::convert_single_surface_class(const BNG::RxnRule& base_rxn, Value& surface_class) {
  240. surface_class[KEY_DESCRIPTION] = "";
  241. add_version(surface_class, VER_DM_2018_01_11_1330);
  242. string name = get_surface_class_name(base_rxn);
  243. assert(processed_surface_classes.count(name) == 0 && "This surface class was already processed");
  244. processed_surface_classes.insert(name);
  245. surface_class[KEY_NAME] = name;
  246. Value& surface_class_prop_list = surface_class[KEY_SURFACE_CLASS_PROP_LIST];
  247. surface_class_prop_list = Value(Json::arrayValue);
  248. // find all rxn rules with the same name
  249. vector<const RxnRule*> rxns_belonging_to_surf_class;
  250. for (const RxnRule* rxn_rule: bng_engine->get_all_rxns().get_rxn_rules_vector()) {
  251. if (rxn_rule->is_reactive_surface_rxn() && get_surface_class_name(*rxn_rule) == name) {
  252. rxns_belonging_to_surf_class.push_back(rxn_rule);
  253. }
  254. }
  255. // and convert them all as properties of this surface class
  256. for (const RxnRule* rxn_rule: rxns_belonging_to_surf_class) {
  257. Value sc_property;
  258. add_version(sc_property, VER_DM_2015_11_08_1756);
  259. CONVERSION_CHECK(rxn_rule->reactants[0].is_simple(), "Surface class reactant must be simple for now.");
  260. bool has_type = true;
  261. switch (rxn_rule->type) {
  262. case RxnType::Transparent:
  263. sc_property[KEY_SURF_CLASS_TYPE] = VALUE_TRANSPARENT;
  264. break;
  265. case RxnType::Reflect:
  266. sc_property[KEY_SURF_CLASS_TYPE] = VALUE_REFLECTIVE;
  267. break;
  268. case RxnType::AbsorbRegionBorder:
  269. sc_property[KEY_SURF_CLASS_TYPE] = VALUE_ABSORPTIVE;
  270. break;
  271. case RxnType::Standard:
  272. if (rxn_rule->is_absorptive_region_rxn()) {
  273. sc_property[KEY_SURF_CLASS_TYPE] = VALUE_ABSORPTIVE;
  274. }
  275. else {
  276. // do not generate any property if type is nto set
  277. continue;
  278. }
  279. break;
  280. default:
  281. CONVERSION_UNSUPPORTED("Unexpected reaction type");
  282. }
  283. const string& reactant_name = bng_engine->get_data().get_elem_mol_type(rxn_rule->reactants[0].get_simple_species_mol_type_id()).name;
  284. if (is_species_superclass(reactant_name)) {
  285. sc_property[KEY_AFFECTED_MOLS] = reactant_name;
  286. sc_property[KEY_MOLECULE] = "";
  287. }
  288. else {
  289. sc_property[KEY_AFFECTED_MOLS] = VALUE_SINGLE;
  290. sc_property[KEY_MOLECULE] = reactant_name;
  291. }
  292. sc_property[KEY_SURF_CLASS_ORIENT] = DMUtils::orientation_to_str(rxn_rule->reactants[0].get_orientation());
  293. sc_property[KEY_CLAMP_VALUE] = "0";
  294. sc_property[KEY_NAME] = ""; // name is ignored by the datamodel to mdl converter anyway
  295. surface_class_prop_list.append(sc_property);
  296. }
  297. }
  298. void BngDataToDatamodelConverter::convert_rxns(Value& mcell_node) {
  299. // --- define_reactions --- (this section is always present)
  300. Value& define_reactions = mcell_node[KEY_DEFINE_REACTIONS];
  301. add_version(define_reactions, VER_DM_2014_10_24_1638);
  302. Value& reaction_list = define_reactions[KEY_REACTION_LIST];
  303. reaction_list = Value(Json::arrayValue);
  304. // --- define_surface_classes --- (only when needed)
  305. Value& define_surface_classes = mcell_node[KEY_DEFINE_SURFACE_CLASSES];
  306. add_version(define_surface_classes, VER_DM_2014_10_24_1638);
  307. Value& surface_class_list = define_surface_classes[KEY_SURFACE_CLASS_LIST];
  308. if (!surface_class_list.isArray()) {
  309. // do not overwrite if it was already set
  310. surface_class_list = Value(Json::arrayValue);
  311. }
  312. for (const RxnRule* rxn_rule: bng_engine->get_all_rxns().get_rxn_rules_vector()) {
  313. if (rxn_rule->has_flag(RXN_FLAG_CREATED_FOR_CONCENTRATION_CLAMP) ||
  314. rxn_rule->has_flag(RXN_FLAG_CREATED_FOR_FLUX_CLAMP) ) {
  315. // concentration clamp info is generated from ConcentrationClampReleaseEvent
  316. continue;
  317. }
  318. if (rxn_rule->type == RxnType::Standard) {
  319. // this might be a special case of absorptive reaction
  320. if (!rxn_rule->is_absorptive_region_rxn()) {
  321. Value rxn_node;
  322. CHECK(convert_single_rxn_rule(*rxn_rule, rxn_node));
  323. reaction_list.append(rxn_node);
  324. }
  325. // also register the surface class
  326. if (rxn_rule->is_reactive_surface_rxn() &&
  327. processed_surface_classes.count(get_surface_class_name(*rxn_rule)) == 0) {
  328. Value surface_class;
  329. CHECK(convert_single_surface_class(*rxn_rule, surface_class));
  330. surface_class_list.append(surface_class);
  331. }
  332. }
  333. else if (rxn_rule->type == RxnType::Transparent ||
  334. rxn_rule->type == RxnType::Reflect ||
  335. rxn_rule->type == RxnType::AbsorbRegionBorder) {
  336. // this rxn rule defines a surface class
  337. if (processed_surface_classes.count(get_surface_class_name(*rxn_rule)) == 0) {
  338. Value surface_class;
  339. CHECK(convert_single_surface_class(*rxn_rule, surface_class));
  340. surface_class_list.append(surface_class);
  341. }
  342. }
  343. else {
  344. CONVERSION_UNSUPPORTED("Invalid reaction type");
  345. }
  346. }
  347. }
  348. } // namespace MCell