mcell4_converter.cpp 67 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865
  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 <sstream>
  12. #include <iomanip>
  13. #include "mcell4_converter.h"
  14. #include "model.h"
  15. #include "world.h"
  16. #include "release_event.h"
  17. #include "clamp_release_event.h"
  18. #include "viz_output_event.h"
  19. #include "mol_or_rxn_count_event.h"
  20. #include "geometry.h"
  21. #include "rng.h"
  22. #include "isaac64.h"
  23. #include "mcell_structs_shared.h"
  24. #include "custom_function_call_event.h"
  25. #include "bng/bng.h"
  26. #include "api/mcell.h"
  27. #include "api/bindings.h"
  28. #include "api/compartment_utils.h"
  29. #include "api/rng_state.h"
  30. using namespace std;
  31. namespace MCell {
  32. namespace API {
  33. static viz_mode_t convert_viz_mode(const VizMode m) {
  34. switch (m) {
  35. case VizMode::ASCII:
  36. return ASCII_MODE;
  37. case VizMode::CELLBLENDER_V1:
  38. return CELLBLENDER_MODE_V1;
  39. case VizMode::CELLBLENDER:
  40. return CELLBLENDER_MODE_V2;
  41. default:
  42. throw ValueError("Invalid VizMode value " + to_string((int)m) + ".");
  43. }
  44. }
  45. MCell4Converter::MCell4Converter(Model* model_, World* world_) :
  46. model(model_), world(world_),
  47. bng_converter(world_->bng_engine.get_data(), world_->bng_engine.get_config()) {
  48. }
  49. void MCell4Converter::convert_before_init() {
  50. assert(model != nullptr);
  51. assert(world != nullptr);
  52. convert_simulation_setup();
  53. convert_compartments();
  54. convert_elementary_molecule_types();
  55. convert_species();
  56. convert_surface_classes();
  57. convert_rxns();
  58. // find out whether we have a vol vol rxn for all current species (for mcell3 compatibility)
  59. // in BNG mode this finds a reaction as well
  60. // this is an optimization to tell that we don't need to check the
  61. // surroundings of subpartitions in case when there are no vol-vol rxns
  62. // WARNING: must be done before geometry objects are converted because wall_subparts_collision_test
  63. // depends on it
  64. if (!world->get_all_rxns().has_bimol_vol_rxns()) {
  65. // the default is true (or read from user)
  66. world->config.use_expanded_list = false;
  67. }
  68. // at this point, we need to create the first (and for now the only) partition
  69. // create initial partition with center at 0,0,0
  70. partition_id_t index = world->add_partition(world->config.partition0_llf);
  71. assert(index == PARTITION_ID_INITIAL);
  72. convert_geometry_objects();
  73. // - update flags that tell whether we have reactions for all volume/surface species
  74. // and also update molecule type compartment flag
  75. // - must be done after geometry object conversions because
  76. // surface classes might have been defined
  77. world->get_all_rxns().update_all_mols_and_mol_type_compartments();
  78. // we need to schedule the initial release for surfaces before the other releases
  79. world->create_initial_surface_region_release_event();
  80. convert_release_events();
  81. convert_mol_or_rxn_count_events_and_init_counting_flags();
  82. convert_viz_output_events();
  83. // beside of the event checking for check ctrl-c, it is a periodic event for each
  84. // iteration so, in the following call, where time maybe be moved time for scheduler
  85. // when a checkpoint is resumed, we always end up at the starting iteration and do not skip it
  86. // so that later we are not inserting events to the past (from the scheduler's point of view)
  87. add_ctrl_c_termination_event();
  88. // sets up data loaded from checkpoint
  89. convert_initial_iteration_and_time_and_move_scheduler_time();
  90. // some general checks
  91. if (world->config.rxn_radius_3d * POS_SQRT2 >= world->config.subpart_edge_length / 2) {
  92. throw ValueError(S("Reaction radius multiplied by sqrt(2) ") +
  93. to_string(world->config.rxn_radius_3d * world->config.length_unit * POS_SQRT2) +
  94. " must be less than half of subpartition edge length " +
  95. to_string(world->config.subpart_edge_length * world->config.length_unit / 2) + ". " +
  96. "Increase the model's " + NAME_CONFIG + "." + NAME_SUBPARTITION_DIMENSION + ".");
  97. }
  98. check_all_mol_types_have_diffusion_const();
  99. }
  100. species_id_t MCell4Converter::get_species_id_for_complex(
  101. API::Complex& ci, const std::string error_msg, const bool check_orientation) {
  102. // check that the complex instance if fully qualified
  103. BNG::Cplx bng_ci = bng_converter.convert_complex(ci, true, !check_orientation);
  104. if (!bng_ci.is_fully_qualified()) {
  105. // TODO: add test
  106. throw ValueError(
  107. error_msg + ": " + NAME_COMPLEX + " '" + bng_ci.to_str() + "' must be fully qualified " +
  108. "(all components must be present and their state set).");
  109. }
  110. // check that all used elementary molecule types have diffusion constant
  111. for (const BNG::ElemMol& em: bng_ci.elem_mols) {
  112. const BNG::ElemMolType& emt = world->bng_engine.get_data().get_elem_mol_type(em.elem_mol_type_id);
  113. if (emt.D == FLT_INVALID) {
  114. throw RuntimeError("Molecule type '" + emt.name + "' does not have its diffusion constant specified.");
  115. }
  116. }
  117. // we need to define species for our complex instance
  118. BNG::Species s = BNG::Species(
  119. bng_ci,
  120. world->bng_engine.get_data(),
  121. world->bng_engine.get_config()
  122. );
  123. return world->bng_engine.get_all_species().find_or_add(s);
  124. }
  125. species_id_t MCell4Converter::get_species_id(
  126. API::Species& s, const std::string class_name, const std::string object_name) {
  127. if (s.species_id != SPECIES_ID_INVALID) {
  128. return s.species_id;
  129. }
  130. else {
  131. // we fist need to create a complex instance from our species
  132. API::Complex* s_as_cplx_inst = dynamic_cast<API::Complex*>(&s);
  133. return get_species_id_for_complex(*s_as_cplx_inst, class_name + " " + object_name);
  134. }
  135. }
  136. void MCell4Converter::get_geometry_bounding_box(Vec3& llf, Vec3& urb) {
  137. llf = Vec3(DBL_GIGANTIC);
  138. urb = Vec3(-DBL_GIGANTIC);
  139. for (std::shared_ptr<API::GeometryObject>& o: model->geometry_objects) {
  140. // go through all vertices
  141. for (auto& vert: o->vertex_list) {
  142. Vec3 v(vert);
  143. if (v.x < llf.x) {
  144. llf.x = v.x;
  145. }
  146. if (v.x > urb.x) {
  147. urb.x = v.x;
  148. }
  149. if (v.y < llf.y) {
  150. llf.y = v.y;
  151. }
  152. if (v.y > urb.y) {
  153. urb.y = v.y;
  154. }
  155. if (v.z < llf.z) {
  156. llf.z = v.z;
  157. }
  158. if (v.z > urb.z) {
  159. urb.z = v.z;
  160. }
  161. }
  162. }
  163. }
  164. void MCell4Converter::convert_simulation_setup() {
  165. // notifications and reports
  166. const API::Notifications& notifications = model->notifications;
  167. world->config.rxn_and_species_report = notifications.rxn_and_species_report;
  168. world->config.iteration_report = notifications.iteration_report;
  169. world->config.wall_overlap_report = notifications.wall_overlap_report;
  170. world->config.simulation_stats_every_n_iterations = notifications.simulation_stats_every_n_iterations;
  171. world->config.notifications.bng_verbosity_level = notifications.bng_verbosity_level;
  172. world->config.notifications.rxn_probability_changed = notifications.rxn_probability_changed;
  173. // warnings
  174. const API::Warnings& warnings = model->warnings;
  175. assert(warnings.high_reaction_probability != WarningLevel::ERROR);
  176. world->config.warnings.warn_on_bimol_rxn_probability_over_05_less_1 =
  177. warnings.high_reaction_probability == WarningLevel::WARNING;
  178. world->config.molecule_placement_failure = warnings.molecule_placement_failure;
  179. // config
  180. const API::Config& config = model->config;
  181. world->total_iterations = config.total_iterations;
  182. world->config.time_unit = config.time_step;
  183. world->config.use_bng_units = config.use_bng_units;
  184. world->config.initial_time = config.initial_time;
  185. world->config.initial_iteration = config.initial_iteration;
  186. pos_t grid_density = config.surface_grid_density;
  187. world->config.grid_density = grid_density;
  188. pos_t length_unit = 1/sqrt_f(config.surface_grid_density);
  189. world->config.length_unit = length_unit;
  190. if (is_set(config.interaction_radius)) {
  191. // NOTE: mcell3 does not convert the unit of the interaction radius in parser which
  192. // seems a bit weird
  193. world->config.rxn_radius_3d = config.interaction_radius / length_unit;
  194. }
  195. else {
  196. world->config.rxn_radius_3d = world->config.get_default_rxn_radius_3d();
  197. }
  198. if (is_set(config.intermembrane_interaction_radius)) {
  199. // NOTE: mcell3 does not convert the unit of the interaction radius in parser which
  200. // seems a bit weird
  201. world->config.intermembrane_rxn_radius_3d = config.intermembrane_interaction_radius / length_unit;
  202. }
  203. else {
  204. world->config.intermembrane_rxn_radius_3d = (1.0 / sqrt_f(MY_PI * grid_density)) / length_unit;
  205. }
  206. pos_t vacancy_search_dist = config.vacancy_search_distance / length_unit; // Convert units
  207. world->config.vacancy_search_dist2 = vacancy_search_dist * vacancy_search_dist; // and take square
  208. world->config.randomize_smol_pos = !config.center_molecules_on_grid;
  209. world->config.sort_mols_by_subpart = config.sort_molecules;
  210. world->config.check_overlapped_walls = config.check_overlapped_walls;
  211. world->config.initial_seed = config.seed;
  212. rng_init(&world->rng, world->config.initial_seed);
  213. Vec3 llf, urb;
  214. get_geometry_bounding_box(llf, urb);
  215. Vec3 llf_w_margin = llf - Vec3(PARTITION_EDGE_EXTRA_MARGIN_UM);
  216. Vec3 urb_w_margin = urb + Vec3(PARTITION_EDGE_EXTRA_MARGIN_UM);
  217. pos_t llf_partition_dimension_diff = 0;
  218. // TODO: simplify this code to set origin and partition dimensions
  219. pos_t auto_partition_dimension = max3(urb_w_margin) - min3(llf_w_margin);
  220. bool auto_origin_set = false;
  221. if (!is_set(config.initial_partition_origin) && auto_partition_dimension > config.partition_dimension) {
  222. cout <<
  223. "Info: Value of " << NAME_CLASS_MODEL << "." << NAME_CONFIG << "." << NAME_PARTITION_DIMENSION <<
  224. " " << config.partition_dimension <<
  225. " is smaller than the automatically determined value " << auto_partition_dimension <<
  226. ", using the automatic value.\n";
  227. world->config.partition_edge_length = auto_partition_dimension / length_unit;
  228. // we need to move the origin, if specified, by half of this increment
  229. llf_partition_dimension_diff =
  230. -(world->config.partition_edge_length - (config.partition_dimension / length_unit)) / 2;
  231. // place origin to the llf of the bounding box
  232. world->config.partition0_llf = (llf - Vec3(PARTITION_EDGE_EXTRA_MARGIN_UM)) / Vec3(length_unit);
  233. auto_origin_set = true;
  234. }
  235. else if (is_set(config.initial_partition_origin) &&
  236. (config.initial_partition_origin[0] > llf_w_margin.x ||
  237. config.initial_partition_origin[1] > llf_w_margin.y ||
  238. config.initial_partition_origin[2] > llf_w_margin.z ||
  239. config.initial_partition_origin[0] + config.partition_dimension < urb_w_margin.x ||
  240. config.initial_partition_origin[1] + config.partition_dimension < urb_w_margin.y ||
  241. config.initial_partition_origin[2] + config.partition_dimension < urb_w_margin.z
  242. )
  243. ) {
  244. Vec3 origin(config.initial_partition_origin);
  245. Vec3 llf_diff(0);
  246. if (origin.x > llf_w_margin.x) {
  247. llf_diff.x = origin.x - llf_w_margin.x;
  248. }
  249. if (origin.y > llf_w_margin.y) {
  250. llf_diff.y = origin.y - llf_w_margin.y;
  251. }
  252. if (origin.z > llf_w_margin.z) {
  253. llf_diff.z = origin.z - llf_w_margin.z;
  254. }
  255. llf_partition_dimension_diff = max3(llf_diff);
  256. Vec3 opposite(Vec3(config.initial_partition_origin) + Vec3(config.partition_dimension));
  257. Vec3 urb_diff(0);
  258. if (opposite.x < urb_w_margin.x) {
  259. urb_diff.x = urb_w_margin.x - opposite.x;
  260. }
  261. if (opposite.y < urb_w_margin.y) {
  262. urb_diff.y = urb_w_margin.y - opposite.y;
  263. }
  264. if (opposite.z < urb_w_margin.z) {
  265. urb_diff.z = urb_w_margin.z - opposite.z;
  266. }
  267. pos_t max_urb_diff = max3(urb_diff);
  268. world->config.partition_edge_length =
  269. (config.partition_dimension + llf_partition_dimension_diff + max_urb_diff)/ length_unit;
  270. // we need to move the origin, if specified, by half of this increment
  271. cout <<
  272. "Info: Value of " << NAME_INITIAL_PARTITION_ORIGIN << " " << origin <<
  273. " does not provide enough margin"
  274. " for model's geometry bounding box lower, left, front point " << llf << " "
  275. " and upper, right, back " << urb << "."
  276. " Moving the origin to " << origin - Vec3(llf_partition_dimension_diff) << " and increasing " <<
  277. NAME_PARTITION_DIMENSION << " to " <<
  278. world->config.partition_edge_length * length_unit << ".\n";
  279. }
  280. else {
  281. world->config.partition_edge_length = config.partition_dimension / length_unit;
  282. }
  283. if (is_set(config.initial_partition_origin)) {
  284. // origin set manually
  285. world->config.partition0_llf =
  286. (Vec3(config.initial_partition_origin) - Vec3(llf_partition_dimension_diff)) / Vec3(length_unit);
  287. }
  288. else if (!auto_origin_set) {
  289. // place the partition to the center
  290. world->config.partition0_llf = -Vec3(world->config.partition_edge_length) / Vec3(2);
  291. }
  292. // align the origin to a multiple of subpartition length
  293. pos_t sp_len = config.subpartition_dimension / length_unit;
  294. uint tentative_subparts = world->config.partition_edge_length / sp_len;
  295. if (tentative_subparts > MAX_SUBPARTS_PER_PARTITION) {
  296. cout <<
  297. "Info: Approximate number of subpartitions " << tentative_subparts <<
  298. " is too high, lowering it to a limit of " << MAX_SUBPARTS_PER_PARTITION << ".\n";
  299. sp_len = world->config.partition_edge_length / MAX_SUBPARTS_PER_PARTITION;
  300. }
  301. Vec3 orig_origin = world->config.partition0_llf;
  302. world->config.partition0_llf =
  303. floor_to_multiple_allow_negative_p(orig_origin, sp_len);
  304. // enlarge the partition by size we moved it in order to be aligned
  305. Vec3 llf_moved = orig_origin - world->config.partition0_llf;
  306. pos_t partition_edge_length_enlarged = world->config.partition_edge_length + max3(llf_moved);
  307. world->config.partition_edge_length = ceil_to_multiple_p(partition_edge_length_enlarged, sp_len);
  308. world->config.num_subparts_per_partition_edge =
  309. round_f(world->config.partition_edge_length / sp_len);
  310. // this option in MCell3 was removed in MCell4
  311. world->config.use_expanded_list = true;
  312. // TODO: check that the values are higher than 1
  313. world->config.rxn_class_cleanup_periodicity = config.reaction_class_cleanup_periodicity;
  314. world->config.species_cleanup_periodicity = config.species_cleanup_periodicity;
  315. world->config.molecules_order_random_shuffle_periodicity = config.molecules_order_random_shuffle_periodicity;
  316. world->config.memory_limit_gb = config.memory_limit_gb;
  317. world->config.continue_after_sigalrm = config.continue_after_sigalrm;
  318. // compute other constants and initialize reporting (if enabled)
  319. world->config.init();
  320. }
  321. void MCell4Converter::convert_elementary_molecule_types() {
  322. BNG::BNGData& bng_data = world->bng_engine.get_data();
  323. for (std::shared_ptr<API::ElementaryMoleculeType>& api_mt: model->elementary_molecule_types) {
  324. bng_converter.convert_elementary_molecule_type(*api_mt);
  325. }
  326. }
  327. void MCell4Converter::convert_species() {
  328. for (std::shared_ptr<API::Species>& s: model->species) {
  329. BNG::Species new_species(world->bng_engine.get_data());
  330. new_species.name = s->name;
  331. if (is_set(s->diffusion_constant_3d)) {
  332. new_species.D = s->diffusion_constant_3d;
  333. new_species.set_is_vol();
  334. }
  335. else if (is_set(s->diffusion_constant_2d)) {
  336. new_species.D = s->diffusion_constant_2d;
  337. new_species.set_is_surf();
  338. }
  339. else if (BNG::is_species_superclass(new_species.name)) {
  340. // these values are not really used, they are initialized for comparisons
  341. new_species.D = 0;
  342. new_species.space_step = 0;
  343. new_species.time_step = 0;
  344. }
  345. else {
  346. // this is a declaration of a possibly complex molecule, all used elementary molecules must be defined
  347. if (is_set(s->custom_time_step) || is_set(s->custom_space_step)) {
  348. throw ValueError("Species declaration " + new_species.name + " must not use " +
  349. NAME_CUSTOM_TIME_STEP + " nor " + NAME_CUSTOM_SPACE_STEP + " because it is derived from " +
  350. "its elementary molecule types."
  351. );
  352. }
  353. BNG::Cplx bng_cplx(&world->bng_engine.get_data());
  354. new_species.elem_mols = bng_converter.convert_complex(*s, false, false).elem_mols;
  355. new_species.finalize_species(world->config);
  356. if (!new_species.is_fully_qualified()) {
  357. throw ValueError("Species declaration " + new_species.name + " must use a fully qualified BNGL string for initialization.");
  358. }
  359. // check that all used elementary molecule types have their diffusion constant specified
  360. for (const BNG::ElemMol& em: new_species.elem_mols) {
  361. const BNG::ElemMolType& emt = world->bng_engine.get_data().get_elem_mol_type(em.elem_mol_type_id);
  362. if (emt.D == FLT_INVALID) {
  363. throw ValueError(S("Neither ") + NAME_DIFFUSION_CONSTANT_2D + " nor " +
  364. NAME_DIFFUSION_CONSTANT_3D + " was set for an elementary molecule " + emt.name +
  365. " used in " + NAME_CLASS_SPECIES + " " + s->to_bngl_str() + ".");
  366. }
  367. }
  368. // register and remember which species we created
  369. species_id_t new_species_id = world->get_all_species().find_or_add(new_species);
  370. s->species_id = new_species_id;
  371. // we completely converted this declaration
  372. continue;
  373. }
  374. new_species.set_flag(BNG::SPECIES_MOL_FLAG_TARGET_ONLY, s->target_only); // default is false
  375. // FIXME: the MolType below is created correctly only for simple species
  376. release_assert(s->elementary_molecules.size() <= 1 && "TODO: Complex species");
  377. for (auto& mi: s->elementary_molecules) {
  378. release_assert(mi->components.empty());
  379. }
  380. // find elementary molecule type for our species
  381. // must exist because it was added in Subsystem::unify_and_register_elementary_molecule_types
  382. BNG::elem_mol_type_id_t mol_type_id = world->bng_engine.get_data().find_elem_mol_type_id(s->name);
  383. release_assert(mol_type_id != BNG::ELEM_MOL_TYPE_ID_INVALID);
  384. BNG::ElemMol mol_inst;
  385. mol_inst.elem_mol_type_id = mol_type_id;
  386. new_species.elem_mols.push_back(mol_inst);
  387. new_species.finalize_species(world->config);
  388. species_id_t new_species_id = world->get_all_species().find_or_add(new_species);
  389. // remember which species we created
  390. s->species_id = new_species_id;
  391. // and also set superclass id for container if needed
  392. if (new_species.name == ALL_MOLECULES) {
  393. world->get_all_species().set_all_molecules_ids(new_species_id, mol_type_id);
  394. }
  395. else if (new_species.name == ALL_VOLUME_MOLECULES) {
  396. world->get_all_species().set_all_volume_molecules_ids(new_species_id, mol_type_id);
  397. }
  398. else if (new_species.name == ALL_SURFACE_MOLECULES) {
  399. world->get_all_species().set_all_surface_molecules_ids(new_species_id, mol_type_id);
  400. }
  401. }
  402. }
  403. void MCell4Converter::convert_surface_class_rxn(
  404. API::SurfaceProperty& sp, const BNG::Species& surface_reactant) {
  405. if (sp.type == SurfacePropertyType::REACTIVE) {
  406. // no need to add any reaction because reactions are defined manually
  407. return;
  408. }
  409. BNG::Cplx affected_pattern =
  410. bng_converter.convert_complex(*sp.affected_complex_pattern, false, true);
  411. BNG::RxnRule rxn(&world->bng_engine.get_data());
  412. rxn.name = affected_pattern.to_str() + "+" + surface_reactant.name;
  413. switch (sp.type) {
  414. case API::SurfacePropertyType::ABSORPTIVE:
  415. if (affected_pattern.is_surf()) {
  416. // we are using special type for surf + absorptive surf class
  417. rxn.type = BNG::RxnType::AbsorbRegionBorder;
  418. }
  419. else {
  420. // the type is standard for vol + absorptive surf class
  421. rxn.type = BNG::RxnType::Standard;
  422. }
  423. break;
  424. case API::SurfacePropertyType::CONCENTRATION_CLAMP:
  425. rxn.type = BNG::RxnType::Standard;
  426. rxn.set_flag(BNG::RXN_FLAG_CREATED_FOR_CONCENTRATION_CLAMP);
  427. break;
  428. case API::SurfacePropertyType::FLUX_CLAMP:
  429. rxn.type = BNG::RxnType::Reflect;
  430. rxn.set_flag(BNG::RXN_FLAG_CREATED_FOR_FLUX_CLAMP);
  431. break;
  432. case API::SurfacePropertyType::REFLECTIVE:
  433. rxn.type = BNG::RxnType::Reflect;
  434. break;
  435. case API::SurfacePropertyType::TRANSPARENT:
  436. rxn.type = BNG::RxnType::Transparent;
  437. break;
  438. default:
  439. throw ValueError("Invalid SurfaceProperty type for " + surface_reactant.name + ".");
  440. }
  441. // all these reactions happen always
  442. rxn.base_rate_constant = DBL_GIGANTIC;
  443. // any compartment of the
  444. affected_pattern.set_compartment_id(BNG::COMPARTMENT_ID_NONE);
  445. // NONE is ANY in rxns
  446. orientation_t orient = convert_api_orientation(sp.affected_complex_pattern->orientation, true);
  447. rxn.append_reactant(affected_pattern);
  448. rxn.append_reactant(surface_reactant); // copies the input reactant
  449. // this is the default orientation used for surfaces in a reaction
  450. rxn.reactants[1].set_orientation(ORIENTATION_UP);
  451. // add reaction and remember mapping
  452. sp.rxn_rule_id = world->get_all_rxns().add_and_finalize(rxn);
  453. }
  454. void MCell4Converter::convert_surface_classes() {
  455. for (std::shared_ptr<API::SurfaceClass>& sc: model->surface_classes) {
  456. // each surface class is represented by a species
  457. BNG::Species sc_species(world->bng_engine.get_data());
  458. sc_species.name = sc->name;
  459. sc_species.set_is_reactive_surface();
  460. // sets steps to 0
  461. sc_species.space_step = 0;
  462. sc_species.time_step = 0;
  463. sc_species.D = FLT_INVALID; // diffusion constant has no meaning for surface classes
  464. // we must add a complex instance as the single molecule type in the new species
  465. // define a molecule type with no components
  466. BNG::ElemMolType mol_type;
  467. mol_type.name = sc_species.name; // name of the mol type is the same as for our species
  468. mol_type.set_is_reactive_surface();
  469. BNG::elem_mol_type_id_t mol_type_id = world->bng_engine.get_data().find_or_add_elem_mol_type(mol_type);
  470. BNG::ElemMol mol_inst;
  471. mol_inst.elem_mol_type_id = mol_type_id;
  472. sc_species.elem_mols.push_back(mol_inst);
  473. // we do not care about compartments for surface classes
  474. sc_species.set_compartment_id(BNG::COMPARTMENT_ID_NONE);
  475. sc_species.finalize_species(world->config, false);
  476. species_id_t new_species_id = world->get_all_species().find_or_add(sc_species);
  477. // remember which species we created
  478. sc->species_id = new_species_id;
  479. // and we also need to add a reaction for each property
  480. if (sc->properties.empty()) {
  481. convert_surface_class_rxn(*dynamic_pointer_cast<SurfaceProperty>(sc), sc_species);
  482. }
  483. else {
  484. for (shared_ptr<SurfaceProperty>& sp: sc->properties) {
  485. convert_surface_class_rxn(*sp, sc_species);
  486. }
  487. }
  488. }
  489. }
  490. void MCell4Converter::check_intermembrane_surface_reaction(const BNG::RxnRule& rxn) {
  491. if (rxn.reactants.size() !=2 || rxn.products.size() != 2) {
  492. throw ValueError("Intermembrane reaction must have exactly 2 reactants and 2 products, error for " +
  493. rxn.to_str() + ".");
  494. }
  495. if (!rxn.reactants[0].is_surf() || !rxn.reactants[1].is_surf() ||
  496. !rxn.products[0].is_surf() || !rxn.products[1].is_surf()) {
  497. throw ValueError("Intermembrane reaction's reactants and products must be all surface complexes, error for " +
  498. rxn.to_str() + ".");
  499. }
  500. // TODO: check compartments?
  501. }
  502. void MCell4Converter::convert_rxns() {
  503. BNG::BNGData& bng_data = world->bng_engine.get_data();
  504. for (std::shared_ptr<API::ReactionRule>& r: model->reaction_rules) {
  505. bool is_reversible = is_set(r->rev_rate);
  506. BNG::RxnRule rxn(&bng_data);
  507. rxn.type = BNG::RxnType::Standard;
  508. if (is_set(r->fwd_rate)) {
  509. rxn.base_rate_constant = r->fwd_rate;
  510. }
  511. else {
  512. assert(!r->variable_rate.empty());
  513. for (auto& time_and_rate: r->variable_rate) {
  514. assert(time_and_rate.size() == 2);
  515. BNG::RxnRateInfo info;
  516. info.time = time_and_rate[0] / world->config.time_unit;
  517. info.rate_constant = time_and_rate[1];
  518. rxn.base_variable_rates.push_back(info);
  519. }
  520. rxn.update_variable_rxn_rate(0, nullptr);
  521. }
  522. for (std::shared_ptr<API::Complex>& rinst: r->reactants) {
  523. // convert to BNG::ComplexInstance using existing or new BNG::molecule_id
  524. BNG::Cplx reactant = bng_converter.convert_complex(*rinst, false, true);
  525. rxn.append_reactant(reactant);
  526. }
  527. for (std::shared_ptr<API::Complex>& pinst: r->products) {
  528. // convert to BNG::ComplexInstance using existing or new BNG::molecule_id
  529. BNG::Cplx product = bng_converter.convert_complex(*pinst, false, true);
  530. rxn.append_product(product);
  531. }
  532. // sets also flags for reactants and products
  533. rxn.finalize();
  534. if (r->is_intermembrane_surface_reaction) {
  535. check_intermembrane_surface_reaction(rxn);
  536. rxn.set_is_intermembrane_surf_rxn();
  537. // orientation is ANY in this case, we might need to update it later
  538. // TODO: add warning if user specified explicit orientation
  539. rxn.reactants[0].set_orientation(ORIENTATION_NONE);
  540. rxn.reactants[1].set_orientation(ORIENTATION_NONE);
  541. }
  542. string error_msg = BNG::process_compartments_and_set_orientations(bng_data, rxn);
  543. if (error_msg != "") {
  544. throw ValueError(error_msg);
  545. }
  546. if (is_set(r->name)) {
  547. rxn.name = r->name;
  548. }
  549. else {
  550. // must be called after conversion
  551. rxn.set_automatic_name(false);
  552. }
  553. // reverse reaction
  554. BNG::RxnRule rxn_rev(&bng_data);
  555. if (is_reversible) {
  556. rxn_rev.type = BNG::RxnType::Standard;
  557. rxn_rev.base_rate_constant = r->rev_rate;
  558. rxn_rev.reactants = rxn.products;
  559. rxn_rev.products = rxn.reactants;
  560. if (r->is_intermembrane_surface_reaction) {
  561. rxn.set_is_intermembrane_surf_rxn();
  562. rxn.reactants[0].set_orientation(ORIENTATION_NONE);
  563. rxn.reactants[1].set_orientation(ORIENTATION_NONE);
  564. }
  565. rxn_rev.finalize();
  566. string error_msg = BNG::process_compartments_and_set_orientations(bng_data, rxn_rev);
  567. if (error_msg != "") {
  568. throw ValueError(error_msg);
  569. }
  570. if (is_set(r->rev_name)) {
  571. rxn.name = r->rev_name;
  572. }
  573. else {
  574. rxn.set_automatic_name(false);
  575. }
  576. }
  577. // add reaction(s) and remember mapping
  578. r->fwd_rxn_rule_id = world->get_all_rxns().add_and_finalize(rxn);
  579. if (is_reversible) {
  580. r->rev_rxn_rule_id = world->get_all_rxns().add_and_finalize(rxn_rev);
  581. }
  582. // the ReactionRule object also need the world pointer
  583. r->world = world;
  584. }
  585. }
  586. MCell::wall_index_t MCell4Converter::convert_wall_and_add_to_geom_object(
  587. const API::GeometryObject& src_obj, const uint side,
  588. MCell::Partition& p, MCell::GeometryObject& dst_obj) {
  589. assert(src_obj.wall_list[side].size() == 3);
  590. // TODO LATER: there is really no reason to add walls in two steps,
  591. // can be simplified
  592. MCell::Wall& wall = p.add_uninitialized_wall(world->get_next_wall_id());
  593. wall.object_id = dst_obj.id;
  594. wall.object_index = dst_obj.index;
  595. wall.side = side;
  596. for (uint i = 0; i < VERTICES_IN_TRIANGLE; i++) {
  597. wall.vertex_indices[i] = src_obj.vertex_indices[src_obj.wall_list[side][i]];
  598. }
  599. wall.initialize_wall_constants(p);
  600. // add wall to subpartitions
  601. dst_obj.wall_indices.push_back(wall.index);
  602. return wall.index;
  603. }
  604. void MCell4Converter::convert_initial_surface_releases(
  605. const std::vector<std::shared_ptr<API::InitialSurfaceRelease>>& api_releases,
  606. std::vector<MCell::InitialSurfaceReleases>& mcell_releases
  607. ) {
  608. for (auto api_rel: api_releases) {
  609. species_id_t species_id =
  610. get_species_id_for_complex(*api_rel->complex, NAME_CLASS_INITIAL_SURFACE_RELEASE);
  611. orientation_t orientation = convert_api_orientation(api_rel->complex->orientation);
  612. if (is_set(api_rel->number_to_release)) {
  613. if (api_rel->number_to_release > (double)UINT32_MAX) {
  614. throw ValueError(S("Value for ") + NAME_NUMBER_TO_RELEASE + " of a " +
  615. NAME_CLASS_INITIAL_SURFACE_RELEASE + " " + to_string(api_rel->number_to_release) +
  616. " is too high, the maximum allowed is " + to_string(UINT32_MAX) + ".");
  617. }
  618. mcell_releases.push_back(
  619. InitialSurfaceReleases(species_id, orientation, true, (uint)api_rel->number_to_release)
  620. );
  621. }
  622. else if (is_set(api_rel->density)) {
  623. mcell_releases.push_back(
  624. InitialSurfaceReleases(species_id, orientation, false, (double)api_rel->density)
  625. );
  626. }
  627. else {
  628. assert(false);
  629. }
  630. }
  631. }
  632. void MCell4Converter::convert_concentration_clamp_release(
  633. const partition_id_t partition_id, const API::SurfaceClass& surface_class, const MCell::Region& mcell_region) {
  634. release_assert(surface_class.properties.empty() && "TODO");
  635. ClampReleaseEvent* clamp_event = new ClampReleaseEvent(world);
  636. if (surface_class.type == SurfacePropertyType::CONCENTRATION_CLAMP) {
  637. clamp_event->type = ClampType::CONCENTRATION;
  638. }
  639. else if (surface_class.type == SurfacePropertyType::FLUX_CLAMP) {
  640. clamp_event->type = ClampType::FLUX;
  641. }
  642. else {
  643. assert(false);
  644. }
  645. // run each timestep
  646. clamp_event->event_time = 0;
  647. clamp_event->periodicity_interval = 1;
  648. // which species to clamp
  649. clamp_event->species_id = get_species_id_for_complex(
  650. *surface_class.affected_complex_pattern,
  651. S(NAME_CLASS_SURFACE_CLASS) + ", attribute " + NAME_AFFECTED_COMPLEX_PATTERN,
  652. false);
  653. // on which side
  654. clamp_event->orientation =
  655. convert_api_orientation(surface_class.affected_complex_pattern->orientation, true, true);
  656. assert(world->bng_engine.get_all_species().get(surface_class.species_id).is_reactive_surface());
  657. clamp_event->surf_class_species_id = surface_class.species_id;
  658. clamp_event->concentration = surface_class.concentration;
  659. // walls where to release
  660. assert(!mcell_region.walls_and_edges.empty() && "Must be initialized");
  661. // we are inserting the walls ordered by their wall index
  662. for (const auto& pair_wi_edges: mcell_region.walls_and_edges) {
  663. clamp_event->cumm_area_and_pwall_index_pairs.push_back(
  664. CummAreaPWallIndexPair(0, PartitionWallIndexPair(partition_id, pair_wi_edges.first)));
  665. }
  666. clamp_event->update_cumm_areas_and_scaling();
  667. // and schedule
  668. world->scheduler.schedule_event(clamp_event);
  669. }
  670. MCell::region_index_t MCell4Converter::convert_surface_region(
  671. MCell::Partition& p,
  672. API::SurfaceRegion& surface_region, API::GeometryObject& o,
  673. MCell::GeometryObject& obj) {
  674. MCell::Region reg;
  675. reg.name = obj.name + "," + surface_region.name;
  676. reg.geometry_object_id = obj.id;
  677. // simply add all walls
  678. for (const int wall_in_object: surface_region.wall_indices) {
  679. wall_index_t wi = o.wall_indices[wall_in_object];
  680. reg.add_wall_to_walls_and_edges(wi, false);
  681. }
  682. if (is_set(surface_region.surface_class)) {
  683. assert(surface_region.surface_class->species_id != SPECIES_ID_INVALID);
  684. reg.species_id = surface_region.surface_class->species_id;
  685. // define releases for concentration clamp
  686. if (surface_region.surface_class->is_clamp()) {
  687. convert_concentration_clamp_release(p.id, *surface_region.surface_class, reg);
  688. }
  689. }
  690. if (is_set(surface_region.initial_surface_releases)) {
  691. convert_initial_surface_releases(
  692. surface_region.initial_surface_releases,
  693. reg.initial_region_molecules
  694. );
  695. }
  696. reg.init_surface_region_edges(p);
  697. reg.id = world->get_next_region_id();
  698. surface_region.region_id = reg.id;
  699. region_index_t ri = p.add_region_and_set_its_index(reg);
  700. return ri;
  701. }
  702. void MCell4Converter::convert_geometry_objects() {
  703. MCell::Partition& p = world->get_partition(PARTITION_ID_INITIAL); // only partition 0 is supported for now
  704. for (std::shared_ptr<API::GeometryObject>& o: model->geometry_objects) {
  705. // set surface region parents
  706. for (auto& sr: o->surface_regions) {
  707. // not using shared pointers here, any attempt so far resulted in bad_weak_ptr exception
  708. // this is safe because the geometry object (parent) has a reference to the surface region
  709. sr->parent = o.get();
  710. }
  711. MCell::GeometryObject& obj = p.add_uninitialized_geometry_object(world->get_next_geometry_object_id());
  712. obj.name = o->name;
  713. obj.parent_name = ""; // empty, we do not really care
  714. o->geometry_object_id = obj.id;
  715. if (is_set(o->initial_color)) {
  716. // set default color
  717. obj.default_color = o->initial_color->rgba;
  718. }
  719. // vertices
  720. // remember the "offset" from the first vertex in the target partition
  721. o->first_vertex_index = p.get_geometry_vertex_count();
  722. for (auto& v: o->vertex_list) {
  723. // add to partition and remember its index
  724. // must use rcp_length_unit to be identical to mcell4 with mdl
  725. vertex_index_t vi =
  726. p.add_geometry_vertex(Vec3(v[0], v[1], v[2]) * Vec3(world->config.rcp_length_unit));
  727. o->vertex_indices.push_back(vi);
  728. }
  729. // walls (validity of indices is checked in API::GeometryObject::check_semantics)
  730. o->first_wall_index = p.get_walls().size();
  731. for (size_t i = 0; i < o->wall_list.size(); i++) {
  732. wall_index_t wi = convert_wall_and_add_to_geom_object(*o, i, p, obj);
  733. o->wall_indices.push_back(wi);
  734. }
  735. // initialize edges
  736. obj.initialize_neighboring_walls_and_their_edges(p);
  737. vector<region_index_t> region_indices;
  738. // region "ALL"
  739. MCell::Region reg_all;
  740. reg_all.init_from_whole_geom_object(obj);
  741. reg_all.id = world->get_next_region_id();
  742. // TODO: move surf class handling code to a function and share with convert_surface_region
  743. if (is_set(o->surface_class)) {
  744. if (o->surface_class->species_id == SPECIES_ID_INVALID) {
  745. throw RuntimeError("Geometry object " + o->name + " is using surface class " + o->surface_class->name +
  746. " but this surface class was not added to the model. To add it, use method Model.add_surface_class or Subsystem.add_surface_class.");
  747. }
  748. reg_all.species_id = o->surface_class->species_id;
  749. // define releases for concentration clamp
  750. if (o->surface_class->is_clamp()) {
  751. convert_concentration_clamp_release(p.id, *o->surface_class, reg_all);
  752. }
  753. }
  754. if (is_set(o->initial_surface_releases)) {
  755. convert_initial_surface_releases(o->initial_surface_releases, reg_all.initial_region_molecules);
  756. }
  757. region_index_t ri_all = p.add_region_and_set_its_index(reg_all);
  758. region_indices.push_back(ri_all);
  759. // we must remember that this region represents the whole object
  760. obj.encompassing_region_id = reg_all.id;
  761. // regions from surface areas
  762. // mcell3 stores the regions in reverse, so we can too...
  763. // (maybe change this in the mcell3 converter)
  764. for (int k = (int)o->surface_regions.size() - 1; k >= 0; k--) {
  765. std::shared_ptr<SurfaceRegion> surface_region = o->surface_regions[k];
  766. region_index_t ri = convert_surface_region(p, *surface_region, *o, obj);
  767. region_indices.push_back(ri);
  768. // also set colors if they were specified
  769. if (is_set(surface_region->initial_color)) {
  770. rgba_t color = surface_region->initial_color->rgba;
  771. for (wall_index_t wi: surface_region->wall_indices) {
  772. obj.wall_specific_colors[o->get_partition_wall_index(wi)] = color;
  773. }
  774. }
  775. }
  776. // also set regions for walls
  777. for (wall_index_t wi: obj.wall_indices) {
  778. MCell::Wall& w = p.get_wall(wi);
  779. for (region_index_t ri: region_indices) {
  780. MCell::Region& reg = p.get_region(ri);
  781. if (reg.walls_and_edges.count(wi) == 1) {
  782. w.regions.insert(ri);
  783. obj.regions.insert(ri);
  784. }
  785. }
  786. }
  787. // set MCell:GeometryObject compartment ids
  788. if (o->is_bngl_compartment) {
  789. assert(o->vol_compartment_id != BNG::COMPARTMENT_ID_INVALID &&
  790. o->vol_compartment_id != BNG::COMPARTMENT_ID_NONE
  791. );
  792. obj.vol_compartment_id = o->vol_compartment_id;
  793. if (is_set(o->surface_compartment_name)) {
  794. assert(o->surf_compartment_id != BNG::COMPARTMENT_ID_INVALID);
  795. obj.surf_compartment_id = o->surf_compartment_id;
  796. }
  797. }
  798. }
  799. for (MCell::GeometryObject& obj: p.get_geometry_objects()) {
  800. obj.initialize_is_fully_transparent(p);
  801. }
  802. // check overlapped walls
  803. // uses random generator state
  804. if (world->config.check_overlapped_walls) {
  805. bool ok = world->check_for_overlapped_walls();
  806. if (!ok) {
  807. throw ValueError("Walls in geometry overlap, more details were printed in the previous message.");
  808. }
  809. }
  810. // check that no overlapped object is a BNGL compartment
  811. for (MCell::GeometryObject& obj: p.get_geometry_objects()) {
  812. if (obj.has_overlapped_walls && obj.vol_compartment_id != BNG::COMPARTMENT_ID_NONE) {
  813. throw ValueError("Overlapped geometry object " + obj.name + " is set to be a BNGL compartment, this is not allowed.");
  814. }
  815. }
  816. p.finalize_walls();
  817. }
  818. void MCell4Converter::check_surface_compartment_name_collision(const std::string& surface_compartment_name) {
  819. for (std::shared_ptr<API::GeometryObject>& o: model->geometry_objects) {
  820. for (std::shared_ptr<API::SurfaceRegion>& s: o->surface_regions) {
  821. // o->wall_indices contains all the walls (with values counted from 0), so if size is the same
  822. // the contents are the same
  823. if (s->name == surface_compartment_name && o->wall_list.size() != s->wall_indices.size()) {
  824. throw RuntimeError("Geometry object's " + o->name + " surface region " + s->name + " uses the same name "
  825. "as a compartment, but the surface region does not represent the whole surface of the object. "
  826. "This is not allowed because it would lead to inconsistencies when comparing to BNGL variant of the model. "
  827. "Please use a different name either for the surface region or for the compartment.");
  828. }
  829. }
  830. }
  831. }
  832. void MCell4Converter::convert_compartments() {
  833. // we determine the hierarchy of compartments here since all
  834. // we have are geometry objects
  835. // volume of compartments is not set
  836. BNG::BNGData& bng_data = world->bng_engine.get_data();
  837. vector<std::shared_ptr<API::GeometryObject>> compartment_objects;
  838. for (std::shared_ptr<API::GeometryObject>& o: model->geometry_objects) {
  839. if (o->is_bngl_compartment) {
  840. compartment_objects.push_back(o);
  841. }
  842. }
  843. // set hierarchy of compartments
  844. set_parent_and_children_compartments(compartment_objects);
  845. for (std::shared_ptr<API::GeometryObject>& o: compartment_objects) {
  846. BNG::Compartment bng_comp3d;
  847. bng_comp3d.name = o->name;
  848. bng_comp3d.is_3d = true;
  849. BNG::compartment_id_t comp3d_id = bng_data.add_compartment(bng_comp3d);
  850. o->vol_compartment_id = comp3d_id;
  851. // unlike as in BNG, we do not require that the only child of a 3d compartment is 2d compartment,
  852. // 2d compartments can be skipped completely
  853. if (is_set(o->surface_compartment_name)) {
  854. // check that there is no SurfaceRegion with this name
  855. // this might be supported one day but now the surface compartment must be the whole
  856. // surface of the object, reported in redmine as #28
  857. check_surface_compartment_name_collision(o->surface_compartment_name);
  858. BNG::Compartment bng_comp2d;
  859. bng_comp2d.name = o->surface_compartment_name;
  860. bng_comp2d.is_3d = false;
  861. // the only child of a 2D compartment is the 3D compartment it encompasses
  862. bng_comp2d.children_compartments.insert(comp3d_id);
  863. BNG::compartment_id_t comp2d_id = bng_data.add_compartment(bng_comp2d);
  864. o->surf_compartment_id = comp2d_id;
  865. // if a 2d compartment is defined, it is the parent of the 3D compartment
  866. bng_data.get_compartment(comp3d_id).parent_compartment_id = comp2d_id;
  867. }
  868. }
  869. // now define their parents and children
  870. for (std::shared_ptr<API::GeometryObject>& o: compartment_objects) {
  871. BNG::Compartment* bng_comp3d;
  872. bng_comp3d = bng_data.find_compartment(o->name);
  873. release_assert(bng_comp3d != nullptr);
  874. for (const std::shared_ptr<API::GeometryObject>& child: o->child_compartments) {
  875. BNG::Compartment* bng_comp_child;
  876. // the direct child is the 2d compartment if defined, 3d compartment otherwise
  877. if (is_set(child->surface_compartment_name)) {
  878. bng_comp_child = bng_data.find_compartment(child->surface_compartment_name);
  879. release_assert(bng_comp_child != nullptr);
  880. }
  881. else {
  882. bng_comp_child = bng_data.find_compartment(child->name);
  883. release_assert(bng_comp_child != nullptr);
  884. }
  885. bng_comp3d->children_compartments.insert(bng_comp_child->id);
  886. bng_comp_child->parent_compartment_id = bng_comp3d->id;
  887. }
  888. }
  889. }
  890. static MCell::RegionExprOperator convert_region_node_type(API::RegionNodeType t) {
  891. switch (t) {
  892. case API::RegionNodeType::LEAF_GEOMETRY_OBJECT:
  893. return MCell::RegionExprOperator::LEAF_GEOMETRY_OBJECT;
  894. case API::RegionNodeType::LEAF_SURFACE_REGION:
  895. return MCell::RegionExprOperator::LEAF_SURFACE_REGION;
  896. case API::RegionNodeType::UNION:
  897. return MCell::RegionExprOperator::UNION;
  898. case API::RegionNodeType::DIFFERENCE:
  899. return MCell::RegionExprOperator::DIFFERENCE;
  900. case API::RegionNodeType::INTERSECT:
  901. return MCell::RegionExprOperator::INTERSECT;
  902. default:
  903. assert(false);
  904. return MCell::RegionExprOperator::INVALID;
  905. }
  906. }
  907. RegionExprNode* MCell4Converter::convert_region_expr_recursively(
  908. const shared_ptr<API::Region>& region,
  909. const bool is_vol,
  910. const bool release_not_count, // for error message purposes
  911. MCell::RegionExpr& region_expr
  912. ) {
  913. string msg_detail;
  914. if (release_not_count) {
  915. msg_detail = S(" referenced by a ") + NAME_CLASS_RELEASE_SITE + " object";
  916. }
  917. else {
  918. msg_detail = S(" referenced by a ") + NAME_CLASS_COUNT + " object";
  919. }
  920. assert(is_set(region));
  921. if (region->node_type == RegionNodeType::LEAF_GEOMETRY_OBJECT) {
  922. shared_ptr<API::GeometryObject> geometry_object = dynamic_pointer_cast<API::GeometryObject>(region);
  923. assert(is_set(geometry_object));
  924. if (is_vol) {
  925. if (geometry_object->geometry_object_id == GEOMETRY_OBJECT_ID_INVALID) {
  926. throw RuntimeError("Could not find geometry object with name " + geometry_object->name + msg_detail +
  927. ", it might not have been added to the model.");
  928. }
  929. return region_expr.create_new_expr_node_leaf_geometry_object(geometry_object->geometry_object_id);
  930. }
  931. else {
  932. const MCell::Region* reg = world->find_region_by_name(geometry_object->name + MCell::REGION_ALL_SUFFIX_W_COMMA);
  933. if (reg == nullptr) {
  934. throw RuntimeError("Could not find region representing object with name " + geometry_object->name + msg_detail +
  935. ", it might not have been added to the model.");
  936. }
  937. return region_expr.create_new_expr_node_leaf_surface_region(reg->id);
  938. }
  939. }
  940. else if (region->node_type == RegionNodeType::LEAF_SURFACE_REGION) {
  941. if (is_vol) {
  942. // TODO: message needs more details
  943. string msg;
  944. if (release_not_count) {
  945. msg = "Cannot release volume molecule onto a surface region .";
  946. }
  947. else {
  948. msg = "Cannot count volume molecules or reactions on a surface region .";
  949. }
  950. throw RuntimeError(msg + "The problematic region is:\n" + region->to_str());
  951. }
  952. shared_ptr<API::SurfaceRegion> surface_region = dynamic_pointer_cast<API::SurfaceRegion>(region);
  953. assert(is_set(surface_region));
  954. const MCell::Region* reg = world->find_region_by_name(surface_region->parent->name + "," + surface_region->name);
  955. if (reg == nullptr) {
  956. throw RuntimeError("Could not find region with name " + surface_region->parent->name + msg_detail +
  957. ", it might not have been added to the model.");
  958. }
  959. return region_expr.create_new_expr_node_leaf_surface_region(reg->id);
  960. }
  961. else {
  962. return region_expr.create_new_region_expr_node_op(
  963. convert_region_node_type(region->node_type),
  964. convert_region_expr_recursively(region->left_node, is_vol, release_not_count, region_expr),
  965. convert_region_expr_recursively(region->right_node, is_vol, release_not_count, region_expr)
  966. );
  967. }
  968. }
  969. void MCell4Converter::convert_rel_site_region_expr(API::ReleaseSite& rel_site, MCell::ReleaseEvent* rel_event) {
  970. const BNG::Species& species = world->get_all_species().get(rel_event->species_id);
  971. if (rel_site.shape == Shape::COMPARTMENT && !is_set(rel_site.complex->compartment_name)) {
  972. // this should not happen
  973. throw RuntimeError("Compartment for release site " + rel_site.name + " was not set.");
  974. }
  975. if (rel_site.shape == Shape::REGION_EXPR) {
  976. if (!is_set(rel_site.region)) {
  977. throw RuntimeError("Region for release site " + rel_site.name + " was not set.");
  978. }
  979. rel_event->region_expr.root = convert_region_expr_recursively(
  980. rel_site.region, species.is_vol(), true, rel_event->region_expr);
  981. // add intersection with compartment if this is a volume compartment
  982. if (is_set(rel_site.complex->compartment_name)) {
  983. // TODO: how to handle surface compartment intersection?
  984. // obj is nullptr when this is not a volume compartment
  985. const auto& obj = model->find_volume_compartment_object(rel_site.complex->compartment_name);
  986. if (is_set(obj)) {
  987. auto region = model->get_compartment_region(rel_site.complex->compartment_name);
  988. RegionExprNode* compartment_region = convert_region_expr_recursively(
  989. region, species.is_vol(), true, rel_event->region_expr);
  990. // overwrite region expr with intersection with compartment
  991. rel_event->region_expr.root = rel_event->region_expr.create_new_region_expr_node_op(
  992. RegionExprOperator::INTERSECT,
  993. rel_event->region_expr.root,
  994. compartment_region
  995. );
  996. }
  997. }
  998. }
  999. else if (rel_site.shape == Shape::COMPARTMENT) {
  1000. if (!is_set(rel_site.complex->compartment_name)) {
  1001. throw RuntimeError("Compartment for release site " + rel_site.name + " was not set.");
  1002. }
  1003. // make region that represents the compartment
  1004. auto region = model->get_compartment_region(rel_site.complex->compartment_name);
  1005. if (!is_set(region)) {
  1006. throw RuntimeError("Compartment " + rel_site.complex->compartment_name + " for " + rel_site.name + " was not found.");
  1007. }
  1008. rel_event->region_expr.root = convert_region_expr_recursively(
  1009. region, species.is_vol(), true, rel_event->region_expr);
  1010. }
  1011. // also set llf and urb
  1012. // TODO: this does not check anything yet
  1013. bool ok = Geometry::compute_region_expr_bounding_box(world, rel_event->region_expr.root, rel_event->region_llf, rel_event->region_urb);
  1014. if (!ok) {
  1015. throw RuntimeError("Region for releases specified by " + rel_event->region_expr.root->to_string(world) + " is not closed.");
  1016. }
  1017. }
  1018. void MCell4Converter::convert_molecule_list(
  1019. const std::vector<std::shared_ptr<MoleculeReleaseInfo>>& molecule_list,
  1020. const std::string& rel_site_name,
  1021. MCell::ReleaseEvent* rel_event) {
  1022. for (auto& item: molecule_list) {
  1023. MCell::SingleMoleculeReleaseInfo info;
  1024. info.species_id = get_species_id_for_complex(
  1025. *item->complex, S(NAME_CLASS_RELEASE_SITE) + " '" + rel_site_name + "'");
  1026. assert(item->location.size() == 3);
  1027. info.pos.x = item->location[0] * world->config.rcp_length_unit;
  1028. info.pos.y = item->location[1] * world->config.rcp_length_unit;
  1029. info.pos.z = item->location[2] * world->config.rcp_length_unit;
  1030. bool is_vol = world->get_all_species().get(info.species_id).is_vol();
  1031. info.orientation = convert_api_orientation(item->complex->orientation, true, is_vol); // not set is not allowed
  1032. if (world->get_all_species().get(info.species_id).is_vol() &&
  1033. rel_event->orientation != ORIENTATION_NONE && rel_event->orientation != ORIENTATION_NOT_SET) {
  1034. throw ValueError(
  1035. S(NAME_CLASS_RELEASE_SITE) + " " + rel_site_name + " releases a volume molecule but orientation is set.");
  1036. }
  1037. rel_event->molecule_list.push_back(info);
  1038. }
  1039. }
  1040. MCell::ReleaseEvent* MCell4Converter::convert_single_release_event(
  1041. const std::shared_ptr<API::ReleaseSite>& r) {
  1042. MCell::ReleaseEvent* rel_event = new ReleaseEvent(world);
  1043. rel_event->release_site_name = r->name;
  1044. if (!is_set(r->molecule_list)) {
  1045. assert(is_set(r->complex));
  1046. rel_event->species_id = get_species_id_for_complex(
  1047. *r->complex, S(NAME_CLASS_RELEASE_SITE) + " '" + r->name + "'");
  1048. bool is_vol = world->get_all_species().get(rel_event->species_id).is_vol();
  1049. rel_event->orientation = convert_api_orientation(r->complex->orientation, true, is_vol);
  1050. if (world->get_all_species().get(rel_event->species_id).is_surf() &&
  1051. rel_event->orientation != ORIENTATION_UP && rel_event->orientation != ORIENTATION_DOWN &&
  1052. rel_event->orientation != ORIENTATION_NONE) { // none = random orientation
  1053. throw ValueError(
  1054. S(NAME_CLASS_RELEASE_SITE) + " " + r->name +
  1055. " releases a surface molecule but orientation is not set to a valid value.");
  1056. }
  1057. if (is_vol &&
  1058. rel_event->orientation != ORIENTATION_NONE && rel_event->orientation != ORIENTATION_NOT_SET) {
  1059. throw ValueError(
  1060. S(NAME_CLASS_RELEASE_SITE) + " " + r->name +
  1061. " releases a volume molecule but orientation is set.");
  1062. }
  1063. }
  1064. rel_event->delay = r->release_time / world->config.time_unit;
  1065. // release pattern
  1066. if (is_set(r->release_pattern)) {
  1067. API::ReleasePattern rp = *r->release_pattern;
  1068. rel_event->number_of_trains = rp.number_of_trains;
  1069. rel_event->train_interval = rp.train_interval / world->config.time_unit;
  1070. rel_event->train_duration = rp.train_duration / world->config.time_unit;
  1071. rel_event->release_interval = rp.release_interval / world->config.time_unit;
  1072. }
  1073. // release_number_method
  1074. if (is_set(r->number_to_release)) {
  1075. if (r->number_to_release > (double)UINT32_MAX) {
  1076. throw ValueError(S("Value ") + to_string(r->number_to_release) + " for " +
  1077. NAME_NUMBER_TO_RELEASE + " of a " + NAME_CLASS_RELEASE_SITE + " '" + r->name +
  1078. "' is too high, the maximum allowed is " + to_string(UINT32_MAX) + ".");
  1079. }
  1080. rel_event->release_number_method = ReleaseNumberMethod::CONST_NUM;
  1081. rel_event->release_number = r->number_to_release;
  1082. }
  1083. else if (is_set(r->density)) {
  1084. rel_event->release_number_method = ReleaseNumberMethod::DENSITY_NUM;
  1085. rel_event->concentration = r->density;
  1086. }
  1087. else if (is_set(r->concentration)) {
  1088. rel_event->release_number_method = ReleaseNumberMethod::CONCENTRATION_NUM;
  1089. rel_event->concentration = r->concentration;
  1090. }
  1091. else if (is_set(r->molecule_list)) {
  1092. rel_event->release_number_method = ReleaseNumberMethod::CONST_NUM;
  1093. convert_molecule_list(r->molecule_list, r->name, rel_event);
  1094. }
  1095. else {
  1096. throw RuntimeError(
  1097. S("The only supported release number type now is constant number specified with ") + NAME_NUMBER_TO_RELEASE + "."
  1098. );
  1099. }
  1100. // release_shape
  1101. switch (r->shape) {
  1102. case Shape::SPHERICAL:
  1103. rel_event->release_shape = ReleaseShape::SPHERICAL;
  1104. assert(r->location.size() == 3);
  1105. rel_event->location = Vec3(r->location) * world->config.rcp_length_unit;
  1106. rel_event->diameter = r->site_diameter * world->config.rcp_length_unit;
  1107. break;
  1108. case Shape::REGION_EXPR:
  1109. case Shape::COMPARTMENT: {
  1110. rel_event->release_shape = ReleaseShape::REGION;
  1111. convert_rel_site_region_expr(*r, rel_event);
  1112. bool ok = rel_event->initialize_walls_for_release();
  1113. if (!ok) {
  1114. throw RuntimeError("Only simple surface regions are supported for surface releases currently, error for " + r->name + ".");
  1115. }
  1116. }
  1117. break;
  1118. case Shape::LIST:
  1119. rel_event->release_shape = ReleaseShape::LIST;
  1120. rel_event->diameter = r->site_diameter * world->config.rcp_length_unit;
  1121. break;
  1122. rel_event->release_shape = ReleaseShape::REGION;
  1123. break;
  1124. default:
  1125. // should be caught earlier
  1126. throw RuntimeError(S("The only supported shapes now are ") +
  1127. NAME_EV_SPHERICAL + ", " + NAME_EV_REGION_EXPR + ", " + NAME_EV_COMPARTMENT + " and " + NAME_EV_LIST + ".");
  1128. }
  1129. rel_event->release_probability = r->release_probability;
  1130. return rel_event;
  1131. }
  1132. void MCell4Converter::convert_release_events() {
  1133. for (std::shared_ptr<API::ReleaseSite>& r: model->release_sites) {
  1134. MCell::ReleaseEvent* rel_event = convert_single_release_event(r);
  1135. // schedule it
  1136. rel_event->update_event_time_for_next_scheduled_time();
  1137. world->scheduler.schedule_event(rel_event);
  1138. }
  1139. }
  1140. static void set_geometry_objects_are_used_in_mol_rxn_counts_recursively(
  1141. World* world, MCell::RegionExprNode* node) {
  1142. if (node->op == RegionExprOperator::LEAF_GEOMETRY_OBJECT) {
  1143. world->get_geometry_object(node->geometry_object_id).set_is_used_in_mol_rxn_counts();
  1144. }
  1145. else if (node->has_binary_op()) {
  1146. set_geometry_objects_are_used_in_mol_rxn_counts_recursively(world, node->left);
  1147. set_geometry_objects_are_used_in_mol_rxn_counts_recursively(world, node->right);
  1148. }
  1149. else {
  1150. release_assert(false && "Surface regions cannot be used here");
  1151. }
  1152. }
  1153. // appends new term to the vector terms, does not clear it
  1154. void MCell4Converter::convert_count_term_leaf_and_init_counting_flags(
  1155. const std::shared_ptr<API::CountTerm> ct,
  1156. const int sign,
  1157. MolOrRxnCountTermVector& terms
  1158. ) {
  1159. MCell::MolOrRxnCountTerm res;
  1160. res.sign_in_expression = sign;
  1161. assert(is_set(ct));
  1162. assert(ct->node_type == API::ExprNodeType::LEAF);
  1163. if (is_set(ct->species_pattern) || is_set(ct->molecules_pattern)) {
  1164. if (is_set(ct->species_pattern)) {
  1165. res.species_pattern_type = SpeciesPatternType::SpeciesPattern;
  1166. res.species_molecules_pattern = bng_converter.convert_complex(*ct->species_pattern, true);
  1167. }
  1168. else {
  1169. res.species_pattern_type = SpeciesPatternType::MoleculesPattern;
  1170. res.species_molecules_pattern = bng_converter.convert_complex(*ct->molecules_pattern, true);
  1171. }
  1172. // to maintain compatibility with BioNetGen counting, we must remove the primary compartment
  1173. // from elementary molecules and keep it separately, e.g.: pattern @PM:V(s!1).S(v!1)
  1174. // which is after being read from BNGL in reality V(s!1)@PM.S(v!1)@PM must match
  1175. // V(s!1)@CP.S(v!1)@PM, therefore if we make the pattern to be V(s!1).S(v!1) + PM, it will match
  1176. res.primary_compartment_id = res.species_molecules_pattern.get_primary_compartment_id();
  1177. res.species_molecules_pattern.remove_compartment_from_elem_mols(res.primary_compartment_id);
  1178. bool is_vol = res.species_molecules_pattern.is_vol();
  1179. string name = res.species_molecules_pattern.to_str();
  1180. res.orientation = res.species_molecules_pattern.get_orientation();
  1181. if (is_set(ct->region)) {
  1182. if (is_vol) {
  1183. res.type = MCell::CountType::EnclosedInVolumeRegion;
  1184. res.region_expr.root = convert_region_expr_recursively(ct->region, is_vol, false, res.region_expr);
  1185. // and also mark the objects that we are counting molecules inside
  1186. set_geometry_objects_are_used_in_mol_rxn_counts_recursively(world, res.region_expr.root);
  1187. }
  1188. else {
  1189. // surf mols
  1190. res.type = MCell::CountType::PresentOnSurfaceRegion;
  1191. res.region_expr.root = convert_region_expr_recursively(ct->region, is_vol, false, res.region_expr);
  1192. // these are only surface regions and there is no need to set that they are counted
  1193. }
  1194. }
  1195. else {
  1196. res.type = MCell::CountType::EnclosedInWorld;
  1197. }
  1198. }
  1199. else if (is_set(ct->reaction_rule))
  1200. {
  1201. assert(!is_set(ct->reaction_rule->rev_rate));
  1202. res.rxn_rule_id = ct->reaction_rule->fwd_rxn_rule_id;
  1203. // is this a surface rxn? -> at least one of the reactants is a surface mol
  1204. BNG::RxnRule* rxn = world->get_all_rxns().get(res.rxn_rule_id);
  1205. if (is_set(ct->region)) {
  1206. // TODO: what about counting reactions with surface classes?
  1207. bool is_vol = !rxn->is_surf_rxn();
  1208. if (is_vol) {
  1209. // volume reaction
  1210. res.type = MCell::CountType::RxnCountInVolumeRegion;
  1211. rxn->set_is_counted_in_volume_regions();
  1212. res.region_expr.root = convert_region_expr_recursively(ct->region, is_vol, false, res.region_expr);
  1213. set_geometry_objects_are_used_in_mol_rxn_counts_recursively(world, res.region_expr.root);
  1214. }
  1215. else {
  1216. // surface reaction
  1217. res.type = MCell::CountType::RxnCountOnSurfaceRegion;
  1218. rxn->set_is_counted_on_surface_regions();
  1219. res.region_expr.root = convert_region_expr_recursively(ct->region, is_vol, false, res.region_expr);
  1220. // these are only surface regions and there is no need to set that they are counted
  1221. }
  1222. }
  1223. else {
  1224. res.type = MCell::CountType::RxnCountInWorld;
  1225. rxn->set_is_counted_in_world();
  1226. }
  1227. // remember the initial reactions count when resuming from a checkpoint, default is 0
  1228. res.initial_reactions_count = ct->initial_reactions_count;
  1229. }
  1230. else {
  1231. assert(false);
  1232. }
  1233. terms.push_back(res);
  1234. }
  1235. void MCell4Converter::convert_count_terms_recursively(
  1236. const std::shared_ptr<API::Count> count, // only for warning printouts
  1237. const std::shared_ptr<API::CountTerm> ct,
  1238. const int sign,
  1239. MCell::MolOrRxnCountItem& info
  1240. ) {
  1241. assert(is_set(ct));
  1242. if (ct->node_type == API::ExprNodeType::LEAF) {
  1243. convert_count_term_leaf_and_init_counting_flags(ct, sign, info.terms);
  1244. }
  1245. else if (ct->node_type == API::ExprNodeType::ADD || ct->node_type == API::ExprNodeType::SUB) {
  1246. if (is_set(ct->region)) {
  1247. warns() << "Object " << NAME_CLASS_COUNT << " with " << NAME_NAME << " '" << count->name <<
  1248. "' and " << NAME_FILE_NAME << " '" << count->file_name << "' uses a " << NAME_CLASS_COUNT_TERM <<
  1249. " that has a " << NAME_REGION << " set. This region will be ignored because regions are allowed" <<
  1250. " only for leaf " << NAME_CLASS_COUNT_TERM << " objects.\n" <<
  1251. "This is the problematic " << NAME_CLASS_COUNT << " object:\n" <<
  1252. count->to_str() << "\n" <<
  1253. "-- end of warning message reporting ignored region --\n";
  1254. }
  1255. convert_count_terms_recursively(count, ct->left_node, sign, info);
  1256. int next_sign;
  1257. if (ct->node_type == API::ExprNodeType::SUB) {
  1258. next_sign = -sign;
  1259. }
  1260. else {
  1261. next_sign = sign;
  1262. }
  1263. convert_count_terms_recursively(count, ct->right_node, next_sign, info);
  1264. }
  1265. else {
  1266. // cannot really happen
  1267. throw RuntimeError("Invalid node_type in CountTerm.");
  1268. }
  1269. }
  1270. void MCell4Converter::convert_mol_or_rxn_count_events_and_init_counting_flags() {
  1271. // collect counts with gdat format outputting to the same file
  1272. std::map<std::string, vector<uint>> gdat_filename_to_count_indices;
  1273. for (uint i = 0 ; i < model->counts.size(); i++) {
  1274. std::shared_ptr<API::Count>& c = model->counts[i];
  1275. // set default file_name if not set
  1276. if (!is_set(c->file_name)) {
  1277. c->file_name = "./react_data/" + get_seed_dir_name(model->config.seed) + "/" + c->name + ".dat";
  1278. }
  1279. if (c->output_format == CountOutputFormat::AUTOMATIC_FROM_EXTENSION) {
  1280. throw RuntimeError(S(NAME_CLASS_COUNT) + "'s " + NAME_OUTPUT_FORMAT + " must not be " +
  1281. NAME_ENUM_COUNT_OUTPUT_FORMAT + "." + NAME_EV_AUTOMATIC_FROM_EXTENSION + " when the model is initialized. "
  1282. "The automatic detection should have already happened.");
  1283. }
  1284. if (c->output_format != CountOutputFormat::GDAT) {
  1285. continue;
  1286. }
  1287. auto it = gdat_filename_to_count_indices.find(c->file_name);
  1288. if (it == gdat_filename_to_count_indices.end()) {
  1289. gdat_filename_to_count_indices[c->file_name].push_back(i);
  1290. }
  1291. else {
  1292. it->second.push_back(i);
  1293. }
  1294. }
  1295. // create GDAT output buffers
  1296. std::map<std::string, pair<count_buffer_id_t, uint>> gdat_count_name_to_buffer_id_and_column_index;
  1297. for (const auto& pair_fname_indices: gdat_filename_to_count_indices) {
  1298. // prepare names for this single gdat buffer
  1299. // also check that the sampling interval is the same
  1300. assert(pair_fname_indices.second.size() >= 1);
  1301. double every_n_timesteps = model->counts[pair_fname_indices.second[0]]->every_n_timesteps;
  1302. vector<string> names;
  1303. for (uint i: pair_fname_indices.second){
  1304. std::shared_ptr<API::Count>& c = model->counts[i];
  1305. if (c->every_n_timesteps != every_n_timesteps) {
  1306. throw RuntimeError(S("When multiple ") + NAME_CLASS_COUNT + " objects output to the same .gdat file, " +
  1307. "their sampling intervals set by " + NAME_EVERY_N_TIMESTEPS + " must be identical, error for " +
  1308. c->name + ".");
  1309. }
  1310. names.push_back(c->name);
  1311. }
  1312. count_buffer_id_t buffer_id =
  1313. world->create_gdat_count_buffer(
  1314. pair_fname_indices.first, names,
  1315. API::DEFAULT_COUNT_BUFFER_SIZE, model->config.append_to_count_output_data);
  1316. // these are local column indices and must start from 0
  1317. for (uint i = 0; i < pair_fname_indices.second.size(); i++){
  1318. // however tpo get the count we must use the global index
  1319. std::shared_ptr<API::Count>& c = model->counts[pair_fname_indices.second[i]];
  1320. gdat_count_name_to_buffer_id_and_column_index[c->name] = make_pair(buffer_id, i);
  1321. }
  1322. }
  1323. // and convert the count objects
  1324. for (const std::shared_ptr<API::Count>& c: model->counts) {
  1325. MCell::MolOrRxnCountEvent* count_event = new MCell::MolOrRxnCountEvent(world);
  1326. count_event->event_time = 0;
  1327. count_event->periodicity_interval = round_f(c->every_n_timesteps + EPS);
  1328. // create buffer
  1329. vector<string> column_names = {c->name};
  1330. count_buffer_id_t buffer_id;
  1331. uint column_index;
  1332. if (c->output_format == CountOutputFormat::DAT) {
  1333. buffer_id = world->create_dat_count_buffer(
  1334. c->file_name, API::DEFAULT_COUNT_BUFFER_SIZE, model->config.append_to_count_output_data);
  1335. column_index = 0;
  1336. }
  1337. else {
  1338. auto it = gdat_count_name_to_buffer_id_and_column_index.find(c->name);
  1339. assert(it != gdat_count_name_to_buffer_id_and_column_index.end());
  1340. buffer_id = it->second.first;
  1341. column_index = it->second.second;
  1342. }
  1343. MCell::MolOrRxnCountItem info(buffer_id, column_index);
  1344. // process count terms
  1345. convert_count_terms_recursively(c, c->expression, +1, info);
  1346. info.multiplier = c->multiplier;
  1347. // having multiple MolOrRxnCountInfo per MolOrRxnCountEvent
  1348. // was useful for MCell3 conversion, however for pymcell4 each count is a separate event
  1349. count_event->add_mol_count_item(info);
  1350. // remember for get_current_value calls
  1351. c->count_event = count_event;
  1352. if (c->every_n_timesteps > 0) {
  1353. // 0 means that the event won't be ever rescheduled,
  1354. // if it were added to the schedule it would be executed once and then deleted
  1355. world->scheduler.schedule_event(count_event);
  1356. }
  1357. else {
  1358. world->add_unscheduled_count_event(count_event);
  1359. }
  1360. }
  1361. }
  1362. void MCell4Converter::convert_viz_output_events() {
  1363. for (std::shared_ptr<API::VizOutput>& v: model->viz_outputs) {
  1364. MCell::VizOutputEvent* viz_event = new VizOutputEvent(world);
  1365. viz_event->event_time = 0.0;
  1366. viz_event->periodicity_interval = round_f(v->every_n_timesteps + EPS);
  1367. viz_event->viz_mode = convert_viz_mode(v->mode);
  1368. if (!is_set(v->output_files_prefix)) {
  1369. v->output_files_prefix = "./viz_data/" + get_seed_dir_name(model->config.seed) + "/Scene";
  1370. }
  1371. viz_event->file_prefix_name = v->output_files_prefix;
  1372. if (is_set(v->species_list)) {
  1373. for (std::shared_ptr<API::Species>& s: v->species_list) {
  1374. viz_event->species_ids_to_visualize.insert(
  1375. get_species_id(*s, NAME_CLASS_VIZ_OUTPUT, NAME_SPECIES_LIST));
  1376. }
  1377. }
  1378. else {
  1379. viz_event->visualize_all_species = true;
  1380. }
  1381. world->scheduler.schedule_event(viz_event);
  1382. }
  1383. }
  1384. // sets up data loaded from checkpoint, must be run after all events were added to the scheduler
  1385. // rng state is set after model initialization in Model::initialize
  1386. void MCell4Converter::convert_initial_iteration_and_time_and_move_scheduler_time() {
  1387. if ((model->config.initial_iteration != 0) != (model->config.initial_time != 0)) {
  1388. throw RuntimeError(S("Both ") + NAME_INITIAL_ITERATION + " and " + NAME_INITIAL_TIME +
  1389. " must be either 0 or both must be greater than 0.");
  1390. }
  1391. world->stats.set_current_iteration(model->config.initial_iteration);
  1392. // skips all events, fails if some periodic events are scheduled
  1393. world->scheduler.skip_events_up_to_time(
  1394. world->config.get_simulation_start_time()
  1395. );
  1396. }
  1397. void MCell4Converter::add_ctrl_c_termination_event() {
  1398. MCell::CustomFunctionCallEvent<World*>* event =
  1399. new CustomFunctionCallEvent<World*>(check_ctrl_c, world);
  1400. event->event_time = world->config.get_simulation_start_time();
  1401. event->periodicity_interval = 1;
  1402. world->scheduler.schedule_event(event);
  1403. }
  1404. void MCell4Converter::check_all_mol_types_have_diffusion_const() {
  1405. for (const BNG::ElemMolType& mt: world->bng_engine.get_data().get_elem_mol_types()) {
  1406. if (!mt.is_reactive_surface() && mt.D == FLT_INVALID) {
  1407. throw RuntimeError("Molecule type '" + mt.name + "' does not have its diffusion constant specified.");
  1408. }
  1409. }
  1410. }
  1411. // must be called after world initialization
  1412. void MCell4Converter::convert_after_init() {
  1413. convert_rng_state();
  1414. convert_checkpointed_molecules();
  1415. }
  1416. void MCell4Converter::convert_rng_state() {
  1417. if (!is_set(model->config.initial_rng_state)) {
  1418. return; // nothing to do
  1419. }
  1420. std::shared_ptr<RngState>& src = model->config.initial_rng_state;
  1421. rng_state& dst = world->rng;
  1422. dst.randcnt = src->randcnt;
  1423. dst.aa = src->aa;
  1424. dst.bb = src->bb;
  1425. dst.cc = src->cc;
  1426. assert(RANDSIZ == RNG_SIZE);
  1427. assert(src->randslr.size() == RNG_SIZE);
  1428. std::copy(src->randslr.begin(), src->randslr.end(), dst.randrsl);
  1429. assert(src->mm.size() == RNG_SIZE);
  1430. std::copy(src->mm.begin(), src->mm.end(), dst.mm);
  1431. dst.rngblocks = src->rngblocks;
  1432. }
  1433. void MCell4Converter::convert_checkpointed_molecules() {
  1434. // single partition for now
  1435. Partition& p = world->get_partition(PARTITION_ID_INITIAL);
  1436. uint_set<MCell::molecule_id_t> used_mol_ids;
  1437. // add each mol
  1438. for (const shared_ptr<BaseChkptMol>& m: model->checkpointed_molecules) {
  1439. MCell::Molecule res_m;
  1440. // base data
  1441. if (used_mol_ids.count(m->id) != 0) {
  1442. throw RuntimeError("Checkpointed molecule with ID " + to_string(m->id) + " was already added.");
  1443. }
  1444. res_m.id = m->id;
  1445. if (m->species->species_id == SPECIES_ID_INVALID) {
  1446. throw RuntimeError("Species " + m->species->name + " for checkpointed molecule is not present in the model.");
  1447. }
  1448. res_m.species_id = m->species->species_id;
  1449. // TODO: check that the times are not in the past
  1450. res_m.diffusion_time = m->diffusion_time / world->config.time_unit;
  1451. res_m.birthday = m->birthday / world->config.time_unit;
  1452. // TODO: check that the flags make sense
  1453. res_m.flags = m->flags;
  1454. if (is_set(m->unimol_rxn_time)) {
  1455. res_m.unimol_rxn_time = m->unimol_rxn_time / world->config.time_unit;
  1456. }
  1457. else {
  1458. res_m.unimol_rxn_time = TIME_INVALID;
  1459. }
  1460. if (m->type == MoleculeType::VOLUME) {
  1461. res_m.reset_vol_data();
  1462. const shared_ptr<ChkptVolMol>& vm = dynamic_pointer_cast<ChkptVolMol>(m);
  1463. res_m.v.pos = vm->pos * Vec3(world->config.rcp_length_unit);
  1464. p.add_volume_molecule(res_m, 0);
  1465. }
  1466. else {
  1467. res_m.reset_surf_data();
  1468. assert(m->type == MoleculeType::SURFACE);
  1469. const shared_ptr<ChkptSurfMol>& sm = dynamic_pointer_cast<ChkptSurfMol>(m);
  1470. res_m.s.pos = sm->pos * Vec2(world->config.rcp_length_unit);
  1471. res_m.s.orientation = convert_api_orientation(sm->orientation, false);
  1472. res_m.s.wall_index = sm->geometry_object->get_partition_wall_index(sm->wall_index);
  1473. res_m.s.grid_tile_index = sm->grid_tile_index;
  1474. // we must initialize grid and register the molecule there
  1475. MCell::Wall& w = p.get_wall(res_m.s.wall_index);
  1476. if (!w.has_initialized_grid()) {
  1477. w.initialize_grid(p);
  1478. }
  1479. w.grid.set_molecule_tile(res_m.s.grid_tile_index, res_m.id);
  1480. p.add_surface_molecule(res_m, 0);
  1481. }
  1482. }
  1483. }
  1484. } // namespace API
  1485. } // namespace MCell