mcell3_world_converter.cpp 71 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947
  1. /******************************************************************************
  2. *
  3. * Copyright (C) 2019 by
  4. * The Salk Institute for Biological Studies and
  5. * Pittsburgh Supercomputing Center, Carnegie Mellon University
  6. *
  7. * Use of this source code is governed by an MIT-style
  8. * license that can be found in the LICENSE file or at
  9. * https://opensource.org/licenses/MIT.
  10. *
  11. ******************************************************************************/
  12. #include <stdarg.h>
  13. #include <stdlib.h>
  14. #include <set>
  15. #include "bng/bng.h"
  16. #include "logging.h"
  17. #include "mcell_structs.h" // need all defines
  18. #include "mcell3_world_converter.h"
  19. #include "world.h"
  20. #include "release_event.h"
  21. #include "clamp_release_event.h"
  22. #include "diffuse_react_event.h"
  23. #include "viz_output_event.h"
  24. #include "mol_or_rxn_count_event.h"
  25. #include "count_buffer.h"
  26. #include "datamodel_defines.h"
  27. #include "dump_state.h"
  28. using namespace std;
  29. using namespace BNG;
  30. //#define EXTRA_LOGGING
  31. #ifdef EXTRA_LOGGING
  32. #define LOG(msg) cout << msg << "\n"
  33. #else
  34. #define LOG(msg) do { } while (0)
  35. #endif
  36. // checking major conversion blocks
  37. #define CHECK(cond) do { if(!(cond)) { mcell_log_conv_error("Returning from %s after conversion error.\n", __FUNCTION__); return false; } } while (0)
  38. // checking assumptions
  39. #define CHECK_PROPERTY(cond) do { if(!(cond)) { mcell_log_conv_error("Expected '%s' is false. (%s - %s:%d)\n", #cond, __FUNCTION__, __FILE__, __LINE__); assert(false); return false; } } while (0)
  40. // asserts - things that can never occur and will 'never' be supported
  41. void mcell_log_conv_warning(char const *fmt, ...) {
  42. va_list args;
  43. va_start(args, fmt);
  44. string fmt_w_warning = string ("Conversion warning: ") + fmt;
  45. mcell_logv_raw(fmt_w_warning.c_str(), args);
  46. va_end(args);
  47. }
  48. void mcell_log_conv_error(char const *fmt, ...) {
  49. va_list args;
  50. va_start(args, fmt);
  51. string fmt_w_warning = string ("Conversion error: ") + fmt;
  52. mcell_logv_raw(fmt_w_warning.c_str(), args);
  53. va_end(args);
  54. }
  55. namespace MCell {
  56. static const char* get_sym_name(const sym_entry *s) {
  57. assert(s != nullptr);
  58. assert(s->name != nullptr);
  59. return s->name;
  60. }
  61. static mat4x4 t_matrix_to_mat4x4(const double src[4][4]) {
  62. mat4x4 res;
  63. for (int x = 0; x < 4; x++) {
  64. for (int y = 0; y < 4; y++) {
  65. res[x][y] = src[x][y];
  66. }
  67. }
  68. return res;
  69. }
  70. void MCell3WorldConverter::reset() {
  71. delete world;
  72. world = nullptr;
  73. mcell3_species_id_map.clear();
  74. }
  75. bool MCell3WorldConverter::convert(volume* s) {
  76. world = new World(callbacks);
  77. CHECK(convert_simulation_setup(s));
  78. CHECK(convert_species(s));
  79. world->create_initial_surface_region_release_event(); // cannot fail
  80. CHECK(convert_rxns(s));
  81. mcell_log("Creating initial partition...");
  82. // at this point, we need to create the first (and for now the only) partition
  83. // create initial partition with center at 0,0,0
  84. partition_id_t index = world->add_partition(world->config.partition0_llf);
  85. assert(index == PARTITION_ID_INITIAL);
  86. mcell_log("Converting geometry...");
  87. // convert geometry already puts geometry objects into partitions
  88. CHECK(convert_geometry_objects(s));
  89. // release events require wall information
  90. CHECK(convert_release_events(s));
  91. CHECK(convert_viz_output_events(s));
  92. CHECK(convert_mol_or_rxn_count_events(s));
  93. update_and_schedule_concentration_clamps();
  94. return true;
  95. }
  96. static double get_largest_abs_value(const vector3& v) {
  97. double max = 0;
  98. if (fabs_f(v.x) > max) {
  99. max = fabs_f(v.x);
  100. }
  101. if (fabs_f(v.y) > max) {
  102. max = fabs_f(v.y);
  103. }
  104. if (fabs_f(v.z) > max) {
  105. max = fabs_f(v.z);
  106. }
  107. return max;
  108. }
  109. static double get_largest_distance_from_center(const vector3& llf, const vector3& urb) {
  110. double max1 = get_largest_abs_value(llf);
  111. double max2 = get_largest_abs_value(urb);
  112. return max1 > max2 ? max1 : max2;
  113. }
  114. static uint get_even_higher_or_same_value(const uint val) {
  115. if (val % 2 == 0) {
  116. return val;
  117. }
  118. else {
  119. return val + 1;
  120. }
  121. }
  122. static double get_partition_edge_length(const World* world, const double largest_mcell3_distance_from_center) {
  123. // some MCell models have their partition boundary set exactly. we need to add a bit of margin
  124. return (largest_mcell3_distance_from_center + (double)PARTITION_EDGE_EXTRA_MARGIN_UM / world->config.length_unit) * 2 ;
  125. }
  126. static API::WarningLevel convert_warning_level(enum warn_level_t l) {
  127. switch (l) {
  128. case WARN_COPE:
  129. return API::WarningLevel::IGNORE;
  130. case WARN_WARN:
  131. return API::WarningLevel::WARNING;
  132. case WARN_ERROR:
  133. return API::WarningLevel::ERROR;
  134. default:
  135. assert(false);
  136. return API::WarningLevel::ERROR;
  137. }
  138. }
  139. /**
  140. * Partition conversion greatly simplifies the variability in MCell3 where the partition can be an arbitrary box.
  141. * Here, it must be a cube and the first partition must be placed the way so that the coordinate origin
  142. * is in its corner.
  143. * The assumption (quite possibly premature) is that there will be big systems that are simulated
  144. * and the whole space will be split into multiple partitions anyway. And we do not care about the
  145. * number of subpartitions, they should only take up memory, not computation time.
  146. */
  147. bool MCell3WorldConverter::convert_simulation_setup(volume* s) {
  148. // TODO_CONVERSION: many items are not checked
  149. world->total_iterations = s->iterations;
  150. world->config.time_unit = s->time_unit;
  151. world->config.initial_time = TIME_SIMULATION_START;
  152. world->config.initial_iteration = 0;
  153. world->config.length_unit = s->length_unit;
  154. world->config.grid_density = s->grid_density;
  155. world->config.rxn_radius_3d = s->rx_radius_3d;
  156. world->config.vacancy_search_dist2 = s->vacancy_search_dist2; // unit was already recomputed
  157. world->config.initial_seed = s->seed_seq;
  158. world->config.molecule_placement_failure = convert_warning_level(s->notify->mol_placement_failure);
  159. world->rng = *s->rng;
  160. pos_t sp_len;
  161. // use partition settings supplied by user?
  162. if (s->partitions_initialized) {
  163. // using that the mcell's bounding box if it is bigger than the partition
  164. if (
  165. s->partition_llf.x >= s->bb_llf.x || s->bb_urb.x >= s->partition_urb.x ||
  166. s->partition_llf.y >= s->bb_llf.y || s->bb_urb.y >= s->partition_urb.y ||
  167. s->partition_llf.z >= s->bb_llf.z || s->bb_urb.z >= s->partition_urb.z
  168. ) {
  169. // just to inform the user
  170. mcell_log(
  171. "Warning: Partitioning was specified, but it is smaller than the automatically determined bounding box. "
  172. "You may need to increase the partition size if get an error later that a vertex does not fit any partition."
  173. );
  174. double lu = s->length_unit;
  175. mcell_log("Bounding box in microns: [ %f, %f, %f ], [ %f, %f, %f ]",
  176. s->bb_llf.x*lu, s->bb_llf.y*lu, s->bb_llf.z*lu, s->bb_urb.x*lu, s->bb_urb.y*lu, s->bb_urb.z*lu);
  177. mcell_log("Partition box in microns: [ %f, %f, %f ], [ %f, %f, %f ]",
  178. s->partition_llf.x*lu, s->partition_llf.y*lu, s->partition_llf.z*lu, s->partition_urb.x*lu, s->partition_urb.y*lu, s->partition_urb.z*lu);
  179. }
  180. CHECK_PROPERTY(s->partition_urb.x > s->partition_llf.x);
  181. CHECK_PROPERTY(s->partition_urb.y > s->partition_llf.y);
  182. CHECK_PROPERTY(s->partition_urb.z > s->partition_llf.z);
  183. // determine partition 0 llf origin
  184. // it must be placed in the way that subpartitions boundaries go through (0, 0, 0)
  185. // and in the same time the partition 0 llf origin is on a multiple of the partition length
  186. // first we need to determine the subpart length - use the value that user specified in the PARTITION_* section
  187. Vec3 margin = Vec3(PARTITION_EDGE_EXTRA_MARGIN_UM/world->config.length_unit);
  188. Vec3 mcell3_llf_w_margin = Vec3(s->partition_llf) - margin;
  189. Vec3 mcell3_urb_w_margin = Vec3(s->partition_urb) + margin;
  190. Vec3 mcell3_box_size = mcell3_urb_w_margin - mcell3_llf_w_margin;
  191. IVec3 num_subparts = IVec3(s->num_subparts);
  192. // the shortest subpart step will be used
  193. // value of world->config.subpartition_edge_length is set in SimulationConfig::init
  194. sp_len = min3(mcell3_box_size / Vec3(num_subparts));
  195. uint tentative_subparts = max3(mcell3_box_size) / sp_len;
  196. if (tentative_subparts > MAX_SUBPARTS_PER_PARTITION) {
  197. cout <<
  198. "Approximate number of subpartitions " << tentative_subparts <<
  199. " is too high, lowering it to a limit of " << MAX_SUBPARTS_PER_PARTITION << ".\n";
  200. sp_len = world->config.partition_edge_length / MAX_SUBPARTS_PER_PARTITION;
  201. }
  202. // origin of the initial partition
  203. world->config.partition0_llf = floor_to_multiple_allow_negative_p(mcell3_llf_w_margin, sp_len);
  204. Vec3 llf_moved = mcell3_llf_w_margin - world->config.partition0_llf;
  205. Vec3 box_size_enlarged = mcell3_box_size + llf_moved;
  206. // size of the partition
  207. Vec3 box_size_ceiled = ceil_to_multiple_p(box_size_enlarged, sp_len);
  208. world->config.partition_edge_length = max3(box_size_ceiled);
  209. mcell_log("Using manually specified partition size (with margin): %f.",
  210. (double)world->config.partition_edge_length * world->config.length_unit);
  211. // and how many subparts per dimension
  212. // the rounding is needed because we can get a result like .99999999 from the division
  213. world->config.num_subparts_per_partition_edge = round_f(world->config.partition_edge_length / sp_len);
  214. }
  215. else {
  216. CHECK_PROPERTY(s->bb_urb.x >= s->bb_llf.x);
  217. CHECK_PROPERTY(s->bb_urb.y >= s->bb_llf.y);
  218. CHECK_PROPERTY(s->bb_urb.z >= s->bb_llf.z);
  219. // use MCell's bounding box, however, we must make a cube out of it
  220. double largest_mcell3_distance_from_center = get_largest_distance_from_center(s->bb_llf, s->bb_urb);
  221. double auto_length = get_partition_edge_length(world, largest_mcell3_distance_from_center);
  222. if (auto_length > PARTITION_EDGE_LENGTH_DEFAULT_UM / world->config.length_unit) {
  223. world->config.partition_edge_length = auto_length;
  224. mcell_log("Automatically determined partition size: %f.",
  225. (double)auto_length * world->config.length_unit);
  226. }
  227. else {
  228. world->config.partition_edge_length = PARTITION_EDGE_LENGTH_DEFAULT_UM / world->config.length_unit;
  229. mcell_log("Automatically determined partition size %f is smaller than default %f, using default.",
  230. (double)auto_length * world->config.length_unit, PARTITION_EDGE_LENGTH_DEFAULT_UM);
  231. }
  232. // nx_parts counts the number of boundaries, not subvolumes, also, there are always 2 extra subvolumes on the sides in mcell3
  233. int max_n_p_parts = max3_i(IVec3(s->nx_parts, s->ny_parts, s->nz_parts));
  234. world->config.num_subparts_per_partition_edge = get_even_higher_or_same_value(max_n_p_parts - 3);
  235. world->config.partition0_llf = Vec3(-world->config.partition_edge_length/2);
  236. // temporary, for the check after init
  237. sp_len = world->config.partition_edge_length / world->config.num_subparts_per_partition_edge;
  238. }
  239. Vec3 partition0_llf_microns = world->config.partition0_llf * Vec3(s->length_unit);
  240. Vec3 partition0_urb_microns = partition0_llf_microns + Vec3(world->config.partition_edge_length * s->length_unit);
  241. mcell_log("MCell4 partition bounding box in microns: [ %f, %f, %f ], [ %f, %f, %f ], with %d subpartitions per dimension",
  242. partition0_llf_microns.x, partition0_llf_microns.y, partition0_llf_microns.z,
  243. partition0_urb_microns.x, partition0_urb_microns.y, partition0_urb_microns.z,
  244. (int)world->config.num_subparts_per_partition_edge
  245. );
  246. world->config.randomize_smol_pos = s->randomize_smol_pos; // set in MDL using negated value of CENTER_MOLECULES_ON_GRID
  247. CHECK_PROPERTY(s->dynamic_geometry_molecule_placement == 0
  248. && "DYNAMIC_GEOMETRY_MOLECULE_PLACEMENT '=' NEAREST_TRIANGLE is not supported yet"
  249. );
  250. world->config.use_expanded_list = s->use_expanded_list;
  251. // check is done in MCell3 initialization code
  252. world->config.check_overlapped_walls = s->with_checks_flag;
  253. // compute other constants
  254. // may change world->config.subpartition_edge_length
  255. world->config.init();
  256. if (world->config.rxn_radius_3d * 2 * POS_SQRT2 > world->config.subpart_edge_length) {
  257. mcell_error("Reaction radius multiplied by sqrt(2) %f must be less than half of subpartition edge length %f.",
  258. world->config.rxn_radius_3d * world->config.length_unit * POS_SQRT2,
  259. world->config.subpart_edge_length * world->config.length_unit / 2.0
  260. );
  261. }
  262. return true;
  263. }
  264. // "" as expected_name is that the name is not checked
  265. bool check_meta_object(geom_object* o, string expected_name = "") {
  266. assert(o != nullptr);
  267. if (expected_name != "") {
  268. CHECK_PROPERTY(get_sym_name(o->sym) == expected_name);
  269. }
  270. else {
  271. // just try that the name is set in debug mode
  272. get_sym_name(o->sym);
  273. }
  274. // root->last_name - not checked, contains some nonsense anyway
  275. CHECK_PROPERTY(o->object_type == META_OBJ);
  276. CHECK_PROPERTY(o->contents == nullptr);
  277. CHECK_PROPERTY(o->num_regions == 0);
  278. CHECK_PROPERTY(o->regions == nullptr);
  279. CHECK_PROPERTY(o->walls == nullptr);
  280. CHECK_PROPERTY(o->wall_p == nullptr);
  281. CHECK_PROPERTY(o->vertices == nullptr);
  282. CHECK_PROPERTY(o->total_area == 0);
  283. CHECK_PROPERTY(o->n_tiles == 0);
  284. CHECK_PROPERTY(o->n_occupied_tiles == 0);
  285. CHECK_PROPERTY(o->n_occupied_tiles == 0);
  286. CHECK_PROPERTY(t_matrix_to_mat4x4(o->t_matrix) == mat4x4(1) && "only identity matrix for now");
  287. // root->is_closed - not checked
  288. CHECK_PROPERTY(o->periodic_x == false);
  289. CHECK_PROPERTY(o->periodic_y == false);
  290. CHECK_PROPERTY(o->periodic_z == false);
  291. return true;
  292. }
  293. // inst must be a meta object, converts all contained geometrical objects
  294. bool MCell3WorldConverter::convert_geometry_meta_object_recursively(volume* s, geom_object* meta_inst) {
  295. CHECK_PROPERTY(check_meta_object(meta_inst));
  296. // this is the name of the INSTANTIATE section
  297. std::string instantiate_name = get_sym_name(meta_inst->sym);
  298. // walls reference each other, therefore we must first create
  299. // empty wall objects in partitions,
  300. geom_object* curr_obj = meta_inst->first_child;
  301. while (curr_obj != nullptr) {
  302. if (curr_obj->object_type == POLY_OBJ) {
  303. create_uninitialized_walls_for_polygonal_object(curr_obj);
  304. }
  305. curr_obj = curr_obj->next;
  306. }
  307. // once all wall were created and mapping established,
  308. // we can fill-in all objects
  309. curr_obj = meta_inst->first_child;
  310. while (curr_obj != nullptr) {
  311. if (curr_obj->object_type == POLY_OBJ) {
  312. CHECK(convert_polygonal_object(curr_obj, instantiate_name));
  313. }
  314. else if (curr_obj->object_type == META_OBJ) {
  315. convert_geometry_meta_object_recursively(s, curr_obj);
  316. }
  317. else if (curr_obj->object_type == REL_SITE_OBJ) {
  318. // ignored
  319. }
  320. else {
  321. mcell_error(
  322. "Unexpected type of object %d with name %s.",
  323. (int)curr_obj->object_type, get_sym_name(curr_obj->sym)
  324. );
  325. }
  326. curr_obj = curr_obj->next;
  327. }
  328. return true;
  329. }
  330. bool MCell3WorldConverter::convert_geometry_objects(volume* s) {
  331. geom_object* root_instance = s->root_instance;
  332. CHECK_PROPERTY(check_meta_object(root_instance, "WORLD_INSTANCE"));
  333. CHECK_PROPERTY(root_instance->next == nullptr);
  334. for (geom_object* instantiate_obj = root_instance->first_child; instantiate_obj != nullptr; instantiate_obj = instantiate_obj->next) {
  335. CHECK_PROPERTY(check_meta_object(instantiate_obj));
  336. convert_geometry_meta_object_recursively(s, instantiate_obj);
  337. } // for each scene/INSTANTIATE section
  338. // check that our reinit function works correctly
  339. Partition& p = world->get_partition(PARTITION_ID_INITIAL);
  340. #ifdef DEBUG_EXTRA_CHECKS
  341. for (wall_index_t i = 0; i < p.get_wall_count(); i++) {
  342. Wall& w = p.get_wall(i);
  343. for (edge_index_t k = 0; k < EDGES_IN_TRIANGLE; k++) {
  344. Edge& e = w.edges[k];
  345. e.debug_check_values_are_uptodate(p);
  346. }
  347. }
  348. #endif
  349. for (GeometryObject& obj: p.get_geometry_objects()) {
  350. obj.initialize_is_fully_transparent(p);
  351. }
  352. // check overlapped walls
  353. // uses random generator state
  354. if (world->config.check_overlapped_walls) {
  355. bool ok = world->check_for_overlapped_walls();
  356. if (!ok) {
  357. mcell_error("Walls in geometry overlap, more details were printed in the previous message.");
  358. }
  359. }
  360. world->get_partition(PARTITION_ID_INITIAL).finalize_walls();
  361. return true;
  362. }
  363. // we do not check anything that might not be supported from the mcell3 side,
  364. // the actual checks are in convert_polygonal_object
  365. void MCell3WorldConverter::create_uninitialized_walls_for_polygonal_object(const geom_object* o) {
  366. GeometryObject obj;
  367. // create objects for each wall
  368. for (int i = 0; i < o->n_walls; i++) {
  369. wall* w = o->wall_p[i];
  370. // which partition?
  371. partition_id_t partition_id = world->get_partition_index(*w->vert[0]);
  372. if (partition_id == PARTITION_ID_INVALID) {
  373. Vec3 v(*w->vert[0]);
  374. v = v * Vec3(world->config.length_unit);
  375. mcell_error("Vertex %s does not fit any partition.", v.to_string().c_str());
  376. }
  377. // check that the remaining vertices are in the same partition
  378. for (uint k = 1; k < VERTICES_IN_TRIANGLE; k++) {
  379. partition_id_t curr_partition_index = world->get_partition_index(*w->vert[k]);
  380. if (partition_id != curr_partition_index) {
  381. Vec3 pos(*w->vert[k]);
  382. pos = pos * Vec3(world->config.length_unit);
  383. mcell_error("Whole walls must be in a single partition is for now, vertex %s is out of bounds", pos.to_string().c_str());
  384. }
  385. }
  386. // create the wall in that partition but do not set anything else yet
  387. Partition& p = world->get_partition(partition_id);
  388. Wall& new_wall = p.add_uninitialized_wall(world->get_next_wall_id());
  389. // remember mapping
  390. add_mcell4_wall_index_mapping(w, PartitionWallIndexPair(partition_id, new_wall.index));
  391. }
  392. }
  393. bool MCell3WorldConverter::convert_wall_and_update_regions(
  394. const wall* w, GeometryObject& object,
  395. const region_list* rl
  396. ) {
  397. PartitionWallIndexPair wall_pindex = get_mcell4_wall_index(w);
  398. Partition& p = world->get_partition(wall_pindex.first);
  399. Wall& wall = p.get_wall(wall_pindex.second);
  400. // bidirectional mapping
  401. wall.object_id = object.id;
  402. wall.object_index = object.index;
  403. object.wall_indices.push_back(wall.index);
  404. wall.side = w->side;
  405. for (uint i = 0; i < VERTICES_IN_TRIANGLE; i++) {
  406. // this vertex was inserted into the same partition as the whole object
  407. PartitionVertexIndexPair vert_pindex = get_mcell4_vertex_index(w->vert[i]);
  408. assert(wall_pindex.first == vert_pindex.first);
  409. wall.vertex_indices[i] = vert_pindex.second;
  410. }
  411. wall.uv_vert1_u = w->uv_vert1_u;
  412. wall.uv_vert2 = w->uv_vert2;
  413. wall.area = w->area;
  414. wall.normal = w->normal;
  415. wall.unit_u = w->unit_u;
  416. wall.unit_v = w->unit_v;
  417. wall.distance_to_origin = w->d;
  418. wall.wall_constants_initialized = true;
  419. for (uint i = 0; i < EDGES_IN_TRIANGLE; i++) {
  420. edge* e = w->edges[i];
  421. assert(e != nullptr);
  422. Edge& edge = wall.edges[i];
  423. if (e->forward != nullptr) {
  424. edge.forward_index = get_mcell4_wall_index(e->forward).second;
  425. }
  426. else {
  427. edge.forward_index = WALL_INDEX_INVALID;
  428. }
  429. if (e->backward != nullptr) {
  430. edge.backward_index = get_mcell4_wall_index(e->backward).second;
  431. }
  432. else {
  433. edge.backward_index = WALL_INDEX_INVALID;
  434. }
  435. edge.set_translate(e->translate);
  436. edge.set_cos_theta(e->cos_theta);
  437. edge.set_sin_theta(e->sin_theta);
  438. // e->edge_num_used_for_init is valid only if both fwd and backw walls are present
  439. if (e->forward != nullptr && e->backward != nullptr) {
  440. edge.edge_num_used_for_init = e->edge_num_used_for_init; // added only for mcell4
  441. }
  442. else {
  443. edge.edge_num_used_for_init = EDGE_INDEX_INVALID;
  444. }
  445. }
  446. for (uint i = 0; i < EDGES_IN_TRIANGLE; i++) {
  447. if (w->nb_walls[i] != nullptr) {
  448. #ifndef NDEBUG
  449. PartitionWallIndexPair pindex = get_mcell4_wall_index(w->nb_walls[i]);
  450. assert(pindex.first == wall_pindex.first && "Neighbors must be in the same partition for now");
  451. #endif
  452. wall.nb_walls[i] = get_mcell4_wall_index(w->nb_walls[i]).second;
  453. }
  454. else {
  455. wall.nb_walls[i] = WALL_INDEX_INVALID;
  456. }
  457. }
  458. // CHECK_PROPERTY(w->grid == nullptr); // don't care, we will create grid if needed
  459. // walls use some of the flags used by species
  460. if (!
  461. (w->flags == COUNT_CONTENTS ||
  462. w->flags == COUNT_RXNS ||
  463. w->flags == (COUNT_CONTENTS | COUNT_RXNS) ||
  464. w->flags == (COUNT_CONTENTS | COUNT_ENCLOSED) ||
  465. w->flags == (COUNT_RXNS | COUNT_ENCLOSED) ||
  466. w->flags == (COUNT_CONTENTS | COUNT_RXNS | COUNT_ENCLOSED))
  467. ) {
  468. if ((w->flags | COUNT_TRIGGER) != 0) {
  469. mcell_error("A wall uses a COUNT_TRIGGER flag, this is not supported in MCell4. "
  470. "Use a wall hit or reaction callback in Python MCell4 code instead.", w->flags);
  471. }
  472. else {
  473. mcell_error("Unsupported combination of wall flags 0x%x for MCell4, see MCell code "
  474. "(species flags in mcell_structs.h) for more details.", w->flags);
  475. }
  476. }
  477. // now let's handle regions
  478. std::set<species_id_t> region_species_from_mcell3;
  479. for (const region_list *r = rl; r != NULL; r = r->next) {
  480. region* reg = r->reg;
  481. assert(reg != nullptr);
  482. // are we in this region?
  483. if (get_bit(reg->membership, wall.side)) {
  484. auto pindex = get_mcell4_region_index(reg);
  485. CHECK_PROPERTY(pindex.first == wall_pindex.first);
  486. region_index_t region_index = pindex.second;
  487. wall.regions.insert_unique(region_index);
  488. object.regions.insert(region_index);
  489. // add our wall to the region
  490. Region& mcell4_reg = p.get_region(region_index);
  491. if (mcell4_reg.species_id != SPECIES_ID_INVALID) {
  492. region_species_from_mcell3.insert(mcell4_reg.species_id);
  493. }
  494. // which of our walls are region edges?
  495. set<edge_index_t> edge_indices;
  496. if (reg->boundaries != nullptr) {
  497. for (uint i = 0; i < EDGES_IN_TRIANGLE; i++) {
  498. edge* e = w->edges[i];
  499. uint keyhash = (unsigned int)(intptr_t)(e);
  500. void* key = (void *)(e);
  501. if (pointer_hash_lookup(reg->boundaries, key, keyhash)) {
  502. edge_indices.insert(i);
  503. }
  504. }
  505. }
  506. assert(mcell4_reg.walls_and_edges.count(wall.index) == 0);
  507. mcell4_reg.walls_and_edges[wall.index] = edge_indices;
  508. }
  509. }
  510. // check that associated regions used the same 'surf_classes'/species
  511. std::set<species_id_t> wall_species_from_mcell3;
  512. for (surf_class_list *sl = w->surf_class_head; sl != nullptr; sl = sl->next) {
  513. CHECK_PROPERTY(sl->surf_class != nullptr);
  514. species_id_t surf_class_species_id = get_mcell4_species_id(sl->surf_class->species_id);
  515. wall_species_from_mcell3.insert(surf_class_species_id);
  516. // set concentration clamp walls (note: this might be a bit slow
  517. for (ClampReleaseEvent* clamp: concentration_clamps) {
  518. if (clamp->surf_class_species_id == surf_class_species_id) {
  519. // cummulative area is updated when the clamp events are scheduled at the end of conversion
  520. clamp->cumm_area_and_pwall_index_pairs.push_back(CummAreaPWallIndexPair(0, wall_pindex));
  521. }
  522. }
  523. }
  524. CHECK_PROPERTY(wall_species_from_mcell3 == region_species_from_mcell3);
  525. return true;
  526. }
  527. bool MCell3WorldConverter::convert_region(Partition& p, const region* r, region_index_t& region_index) {
  528. assert(r != nullptr);
  529. Region new_region;
  530. new_region.name = get_sym_name(r->sym);
  531. LOG("Converting region " << new_region.name);
  532. // u_int hashval; // ignored
  533. // char *region_last_name; // ignored
  534. // struct geom_object *parent; // ignored
  535. CHECK_PROPERTY(r->element_list_head == nullptr); // should be used only at parse time
  536. // r->membership - Each bit indicates whether the corresponding wall is in the region */
  537. // and r->boundaries - edges of region are handled in convert_wall_and_update_regions
  538. if (r->surf_class != nullptr) {
  539. new_region.species_id = get_mcell4_species_id(r->surf_class->species_id);
  540. }
  541. else {
  542. new_region.species_id = SPECIES_ID_INVALID;
  543. }
  544. CHECK_PROPERTY(r->region_has_all_elements || r->bbox == nullptr); // so far it can be set only to all-encopasing region
  545. // CHECK_PROPERTY(r->area == 0); // ignored for now
  546. // CHECK_PROPERTY(r->volume == 0); // ignored for now
  547. // CHECK_PROPERTY(r->flags == 0); // ignored for now
  548. // CHECK_PROPERTY(r->manifold_flag == 0); // ignored, do we care?
  549. string parent_name = get_sym_name(r->parent->sym);
  550. const GeometryObject* obj = world->get_partition(0).find_geometry_object_by_name(parent_name);
  551. CHECK_PROPERTY(obj != nullptr);
  552. new_region.geometry_object_id = obj->id;
  553. new_region.id = world->get_next_region_id();
  554. sm_dat* current_sm_dat = r->sm_dat_head;
  555. while (current_sm_dat != nullptr) {
  556. CHECK_PROPERTY(current_sm_dat->sm != nullptr);
  557. species_id_t species_id = get_mcell4_species_id(current_sm_dat->sm->species_id);
  558. if (current_sm_dat->quantity_type == SURFMOLNUM) {
  559. // inserting from front because the order in r->sm_dat_head is reversed
  560. new_region.initial_region_molecules.insert(
  561. new_region.initial_region_molecules.begin(),
  562. InitialSurfaceReleases(
  563. species_id, current_sm_dat->orientation, true, (uint)current_sm_dat->quantity
  564. )
  565. );
  566. }
  567. else if (current_sm_dat->quantity_type == SURFMOLDENS) {
  568. new_region.initial_region_molecules.insert(
  569. new_region.initial_region_molecules.begin(),
  570. InitialSurfaceReleases(
  571. species_id, current_sm_dat->orientation, false, (double)current_sm_dat->quantity
  572. )
  573. );
  574. }
  575. else {
  576. CHECK(false);
  577. }
  578. current_sm_dat = current_sm_dat->next;
  579. }
  580. region_index = p.add_region_and_set_its_index(new_region);
  581. return true;
  582. }
  583. bool MCell3WorldConverter::convert_polygonal_object(const geom_object* o, const std::string& instantiate_name) {
  584. // --- object ---
  585. // we already checked in create_uninitialized_walls_for_polygonal_object
  586. // that the specific walls of this fit into a single partition
  587. // TODO_CONVERSION: improve this check for the whole object
  588. partition_id_t partition_index = world->get_partition_index(*o->vertices[0]);
  589. Partition& p = world->get_partition(partition_index);
  590. GeometryObject& obj = p.add_uninitialized_geometry_object(world->get_next_geometry_object_id());
  591. // o->next - ignored
  592. // o->parent - ignored
  593. CHECK_PROPERTY(o->first_child == nullptr);
  594. CHECK_PROPERTY(o->last_child == nullptr);
  595. obj.name = get_sym_name(o->sym);
  596. obj.parent_name = instantiate_name;
  597. // o->last_name - ignored
  598. CHECK_PROPERTY(o->object_type == POLY_OBJ);
  599. CHECK_PROPERTY(o->contents != nullptr); // ignored for now, not sure what is contained
  600. CHECK_PROPERTY(o->n_walls == o->n_walls_actual); // ignored
  601. CHECK_PROPERTY(o->walls == nullptr); // this is null for some reason
  602. CHECK_PROPERTY(o->wall_p != nullptr);
  603. // --- regions ---
  604. uint reg_cnt = 0;
  605. for (region_list *r = o->regions; r != NULL; r = r->next) {
  606. region* reg = r->reg;
  607. assert(reg != nullptr);
  608. region_index_t region_index;
  609. CHECK(convert_region(p, reg, region_index));
  610. add_mcell4_region_index_mapping(reg, PartitionRegionIndexPair(partition_index, region_index));
  611. reg_cnt++;
  612. }
  613. CHECK_PROPERTY(o->num_regions == reg_cnt);
  614. // --- vertices ---
  615. // to stay identical to mcell3, will use the exact number of vertices as in mcell3, for this to work,
  616. // vector_ptr_to_vertex_index_map is a 'global' map for the whole conversion process
  617. // one of the reasons to not to copy vertex coordinates is that they are shared among triangles of an object
  618. // and when we move one vertex of the object, we transform all the triangles (walls) that use it
  619. for (int i = 0; i < o->n_verts; i++) {
  620. // insert vertex into the right partition and returns partition index and vertex index
  621. vertex_index_t new_vertex_index = p.add_geometry_vertex(*o->vertices[i]);
  622. add_mcell4_vertex_index_mapping(o->vertices[i], PartitionVertexIndexPair(partition_index, new_vertex_index));
  623. }
  624. // --- walls ---
  625. // vertex info contains also partition indices when it is inserted into the
  626. // world geometry
  627. for (int i = 0; i < o->n_walls; i++) {
  628. // uses precomputed map vector_ptr_to_vertex_index_map to transform vertices
  629. // also uses mcell3_region_to_mcell4_index mapping to set that it belongs to a given region
  630. CHECK(convert_wall_and_update_regions(o->wall_p[i], obj, o->regions));
  631. }
  632. // set encompassing region id - the region that has all the walls
  633. for (region_index_t index: obj.regions) {
  634. const Region& reg = p.get_region(index);
  635. string reg_suffix = reg.name.substr(obj.name.size(), reg.name.size() - obj.name.size());
  636. if (reg.walls_and_edges.size() == obj.wall_indices.size() &&
  637. DMUtils::get_region_name(reg.name) == "ALL") {
  638. assert(obj.encompassing_region_id == REGION_ID_INVALID);
  639. obj.encompassing_region_id = reg.id;
  640. }
  641. }
  642. release_assert(obj.encompassing_region_id != REGION_ID_INVALID);
  643. // --- back to object ---
  644. // CHECK_PROPERTY(o->n_tiles == 0); // don't care, we will create grid if needed
  645. // CHECK_PROPERTY(o->n_occupied_tiles == 0);
  646. // CHECK_PROPERTY(t_matrix_to_mat4x4(o->t_matrix) == mat4x4(1) && "only identity matrix for now"); // ignored, this tranformation was already applied
  647. // root->is_closed - not checked
  648. CHECK_PROPERTY(o->periodic_x == false);
  649. CHECK_PROPERTY(o->periodic_y == false);
  650. CHECK_PROPERTY(o->periodic_z == false);
  651. return true;
  652. }
  653. static bool is_species_superclass(volume* s, species* spec) {
  654. return spec == s->all_mols || spec == s->all_volume_mols || spec == s->all_surface_mols;
  655. }
  656. #ifdef SORT_MCELL4_SPECIES_BY_NAME
  657. static bool compare_species_name_less(species* s1, species* s2) {
  658. string n1 = get_sym_name(s1->sym);
  659. string n2 = get_sym_name(s2->sym);
  660. return n1 < n2;
  661. }
  662. #endif
  663. bool MCell3WorldConverter::convert_species(volume* s) {
  664. vector<species*> species_list;
  665. species_list.resize(s->n_species);
  666. for (int i = 0; i < s->n_species; i++) {
  667. species_list[i] = s->species_list[i];
  668. }
  669. #ifdef SORT_MCELL4_SPECIES_BY_NAME
  670. // copy all pointers except for superclasses and then sort
  671. uint std_species_idx = 3;
  672. for (int i = 0; i < s->n_species; i++) {
  673. species* spec = s->species_list[i];
  674. if (is_species_superclass(s, spec)) {
  675. string n = get_sym_name(spec->sym);
  676. if (n == ALL_MOLECULES) {
  677. species_list[0] = spec;
  678. }
  679. else if (n == ALL_VOLUME_MOLECULES) {
  680. species_list[1] = spec;
  681. }
  682. else if (n == ALL_SURFACE_MOLECULES) {
  683. species_list[2] = spec;
  684. }
  685. else {
  686. assert(false);
  687. }
  688. }
  689. else {
  690. assert(std_species_idx < species_list.size());
  691. species_list[std_species_idx] = spec;
  692. std_species_idx++;
  693. }
  694. }
  695. sort(species_list.begin() + 3, species_list.end(), compare_species_name_less);
  696. #endif
  697. // TODO_CONVERSION: many items are not checked
  698. for (int i = 0; i < s->n_species; i++) {
  699. species* spec = species_list[i];
  700. CHECK_PROPERTY(spec->sm_dat_head == nullptr &&
  701. "MOLECULE_DENSITY and MOLECULE_NUMBER are not supported in DEFINE_SURFACE_CLASSES.");
  702. Species new_species(world->bng_engine.get_data());
  703. new_species.name = get_sym_name(spec->sym);
  704. new_species.D = spec->D;
  705. new_species.space_step = spec->space_step;
  706. new_species.time_step = spec->time_step;
  707. // remove some flags for check that are known to work in all cases
  708. uint flags_check = spec->flags & ~REGION_PRESENT;
  709. flags_check = flags_check & ~CANT_INITIATE;
  710. flags_check = flags_check & ~COUNT_ENCLOSED;
  711. flags_check = flags_check & ~COUNT_CONTENTS;
  712. if (!(
  713. is_species_superclass(s, spec)
  714. || flags_check == 0
  715. || flags_check == SPECIES_CPLX_MOL_FLAG_SURF
  716. || flags_check == SPECIES_CPLX_MOL_FLAG_REACTIVE_SURFACE
  717. || flags_check == SPECIES_FLAG_CAN_VOLVOL
  718. || flags_check == SPECIES_FLAG_CAN_VOLWALL
  719. || flags_check == SPECIES_FLAG_CAN_VOLSURF
  720. || flags_check == (SPECIES_FLAG_CAN_VOLVOL | SPECIES_FLAG_CAN_VOLSURF)
  721. || flags_check == (SPECIES_FLAG_CAN_VOLVOL | SPECIES_FLAG_CAN_VOLWALL)
  722. || flags_check == (SPECIES_FLAG_CAN_VOLWALL | SPECIES_FLAG_CAN_VOLSURF)
  723. || flags_check == (SPECIES_FLAG_CAN_VOLVOL | SPECIES_FLAG_CAN_VOLSURF | SPECIES_FLAG_CAN_VOLWALL)
  724. || flags_check == (SPECIES_CPLX_MOL_FLAG_SURF | SPECIES_FLAG_CAN_SURFSURF)
  725. || flags_check == (SPECIES_CPLX_MOL_FLAG_SURF | SPECIES_FLAG_CAN_REGION_BORDER)
  726. || flags_check == (SPECIES_FLAG_CAN_VOLVOL | SPECIES_FLAG_CAN_VOLWALL)
  727. || flags_check == (SPECIES_FLAG_CAN_VOLSURF)
  728. || flags_check == (SPECIES_FLAG_CAN_VOLVOL | SPECIES_FLAG_CAN_VOLSURF | SPECIES_FLAG_CAN_VOLWALL)
  729. || flags_check == (SPECIES_CPLX_MOL_FLAG_SURF)
  730. || flags_check == (SPECIES_CPLX_MOL_FLAG_SURF)
  731. || flags_check == (SPECIES_CPLX_MOL_FLAG_SURF | SPECIES_FLAG_CAN_REGION_BORDER)
  732. || flags_check == (SPECIES_CPLX_MOL_FLAG_SURF | SPECIES_FLAG_CAN_VOLVOL)
  733. || flags_check == (SPECIES_FLAG_CAN_VOLWALL)
  734. || flags_check == (SPECIES_CPLX_MOL_FLAG_SURF | SPECIES_FLAG_CAN_SURFSURF)
  735. || flags_check == (SPECIES_CPLX_MOL_FLAG_SURF | SPECIES_FLAG_CAN_SURFSURF | SPECIES_FLAG_CAN_VOLWALL)
  736. || flags_check == (SPECIES_CPLX_MOL_FLAG_SURF | SPECIES_FLAG_CAN_SURFSURF | SPECIES_FLAG_CAN_REGION_BORDER)
  737. || flags_check == (SPECIES_CPLX_MOL_FLAG_SURF | SPECIES_FLAG_CAN_SURFWALL | SPECIES_FLAG_CAN_REGION_BORDER)
  738. || flags_check == (SPECIES_CPLX_MOL_FLAG_SURF | SPECIES_FLAG_CAN_SURFSURF | SPECIES_FLAG_CAN_SURFWALL | SPECIES_FLAG_CAN_REGION_BORDER)
  739. || flags_check == (SPECIES_CPLX_MOL_FLAG_SURF | SPECIES_FLAG_CAN_SURFWALL)
  740. || flags_check == (SPECIES_CPLX_MOL_FLAG_SURF | SPECIES_FLAG_CAN_SURFWALL | SPECIES_FLAG_CAN_SURFSURF)
  741. || flags_check == (SPECIES_CPLX_MOL_FLAG_SURF | SPECIES_FLAG_CAN_SURFWALL | SPECIES_FLAG_CAN_SURFSURF | SPECIES_FLAG_CAN_REGION_BORDER)
  742. )) {
  743. mcell_log("Warning: possibly unsupported combination of species flag for species %s: %s, it is recommended to check that MCell4 produces result similar to MCell3.\n",
  744. new_species.name.c_str(), get_species_flags_string(spec->flags).c_str());
  745. }
  746. // copy all the flags from mcell3 except for a few one that we really don't need
  747. uint cleaned_flags = spec->flags & ~REGION_PRESENT;
  748. cleaned_flags = cleaned_flags & ~COUNT_CONTENTS;
  749. cleaned_flags = cleaned_flags & ~COUNT_ENCLOSED;
  750. new_species.set_flags(cleaned_flags);
  751. CHECK_PROPERTY(spec->n_deceased == 0);
  752. CHECK_PROPERTY(spec->cum_lifetime_seconds == 0);
  753. if ((spec->flags & IS_SURFACE) != 0) {
  754. if (spec->refl_mols != nullptr) {
  755. // just one type for now
  756. CHECK_PROPERTY(spec->refl_mols == nullptr || spec->refl_mols->next == nullptr);
  757. // reflective surface, seems that this information is transformed into reactions, so we do no need to store anything else
  758. // CHECK_PROPERTY(spec->refl_mols->orient == ORIENTATION_NONE); // ignored
  759. }
  760. }
  761. else {
  762. CHECK_PROPERTY(spec->refl_mols == nullptr);
  763. }
  764. // don't care about these, they will be defined as reactive surface reactions
  765. // CHECK_PROPERTY(spec->transp_mols == nullptr);
  766. // CHECK_PROPERTY(spec->absorb_mols == nullptr);
  767. // CHECK_PROPERTY(spec->clamp_conc_mols == nullptr);
  768. // we must add a complex instance as the single molecule type in the new species
  769. // define a molecule type with no components
  770. ElemMolType mol_type;
  771. mol_type.name = new_species.name; // name of the mol type is the same as for our species
  772. mol_type.D = spec->D;
  773. if (spec->custom_time_step_from_mdl < 0) {
  774. mol_type.custom_space_step = -spec->custom_time_step_from_mdl;
  775. }
  776. else if (spec->custom_time_step_from_mdl > 0) {
  777. mol_type.custom_time_step = spec->custom_time_step_from_mdl;
  778. }
  779. mol_type.set_flag(BNG::SPECIES_MOL_FLAG_TARGET_ONLY, spec->flags & CANT_INITIATE);
  780. if ((spec->flags & ON_GRID) == 0 && (spec->flags & IS_SURFACE) == 0) { //FIXME: ALL_SURF_MOLS have wrong flag
  781. mol_type.set_is_vol();
  782. }
  783. else if ((spec->flags & ON_GRID) != 0) {
  784. mol_type.set_is_surf();
  785. }
  786. else if ((spec->flags & IS_SURFACE) != 0) {
  787. mol_type.set_is_reactive_surface();
  788. }
  789. else {
  790. assert(false);
  791. }
  792. mol_type.compute_space_and_time_step(world->bng_engine.get_config());
  793. elem_mol_type_id_t mol_type_id = world->bng_engine.get_data().find_or_add_elem_mol_type(mol_type);
  794. ElemMol mol_inst;
  795. mol_inst.elem_mol_type_id = mol_type_id;
  796. new_species.elem_mols.push_back(mol_inst);
  797. // and finally let's add our new species
  798. new_species.finalize_species(world->config);
  799. species_id_t new_species_id = world->get_all_species().find_or_add(new_species);
  800. // set all species 'superclasses' ids
  801. // these special species might be used in wall - surf|vol reactions
  802. if (spec == s->all_mols) {
  803. CHECK_PROPERTY(new_species.name == ALL_MOLECULES);
  804. world->get_all_species().set_all_molecules_ids(new_species_id, mol_type_id);
  805. }
  806. else if (spec == s->all_volume_mols) {
  807. CHECK_PROPERTY(new_species.name == ALL_VOLUME_MOLECULES);
  808. world->get_all_species().set_all_volume_molecules_ids(new_species_id, mol_type_id);
  809. }
  810. else if (spec == s->all_surface_mols) {
  811. CHECK_PROPERTY(new_species.name == ALL_SURFACE_MOLECULES);
  812. world->get_all_species().get(new_species_id).set_flag(SPECIES_CPLX_MOL_FLAG_SURF);
  813. world->get_all_species().set_all_surface_molecules_ids(new_species_id, mol_type_id);
  814. }
  815. // map for other conversion steps
  816. mcell3_species_id_map[spec->species_id] = new_species_id;
  817. }
  818. return true;
  819. }
  820. // variable_rates_per_pathway is already resized to the number of pathways
  821. static bool get_variable_rates(const t_func* prob_t, small_vector<small_vector<BNG::RxnRateInfo>>& variable_rates_per_pathway) {
  822. for (const t_func* curr = prob_t; curr != nullptr; curr = curr->next) {
  823. assert(curr->path >= 0 && curr->path < (int)variable_rates_per_pathway.size());
  824. BNG::RxnRateInfo info;
  825. info.time = curr->time;
  826. info.rate_constant = curr->value_from_file;
  827. assert(curr->path < (int)variable_rates_per_pathway.size());
  828. variable_rates_per_pathway[curr->path].push_back(info);
  829. }
  830. // make sure that they are sorted by time
  831. for (small_vector<BNG::RxnRateInfo>& rates: variable_rates_per_pathway) {
  832. sort(variable_rates_per_pathway.begin(), variable_rates_per_pathway.end());
  833. }
  834. return true;
  835. }
  836. void MCell3WorldConverter::create_clamp_release_event(
  837. const pathway* current_pathway, const RxnRule& rxn, const std::vector<species_id_t>& reactant_species_ids) {
  838. ClampReleaseEvent* clamp_event = new ClampReleaseEvent(world);
  839. if ((current_pathway->flags & PATHW_CLAMP_CONC) != 0) {
  840. clamp_event->type = ClampType::CONCENTRATION;
  841. }
  842. else if ((current_pathway->flags & PATHW_CLAMP_FLUX) != 0) {
  843. clamp_event->type = ClampType::FLUX;
  844. }
  845. else {
  846. assert(false);
  847. }
  848. // run each timestep
  849. clamp_event->event_time = 0;
  850. clamp_event->periodicity_interval = 1;
  851. // which species to clamp
  852. assert(!world->bng_engine.get_all_species().get(reactant_species_ids[0]).is_reactive_surface());
  853. clamp_event->species_id = reactant_species_ids[0];
  854. assert(world->bng_engine.get_all_species().get(reactant_species_ids[1]).is_reactive_surface());
  855. clamp_event->surf_class_species_id = reactant_species_ids[1];
  856. // on which side
  857. if (rxn.reactants[0].get_orientation() == 0 || rxn.reactants[1].get_orientation() == 0) {
  858. clamp_event->orientation = 0;
  859. }
  860. else {
  861. clamp_event->orientation =
  862. (rxn.reactants[0].get_orientation() == rxn.reactants[1].get_orientation()) ? ORIENTATION_UP : ORIENTATION_DOWN;
  863. }
  864. // use the rxn rate for concentration and the rxn that destroys molecules will happen always
  865. clamp_event->concentration = current_pathway->clamp_concentration;
  866. concentration_clamps.push_back(clamp_event);
  867. }
  868. bool MCell3WorldConverter::convert_single_reaction(const rxn *mcell3_rx) {
  869. // rx->next - handled in convert_reactions
  870. // rx->sym->name - ignored, name obtained from pathway
  871. CHECK_PROPERTY(
  872. mcell3_rx->n_pathways >= 1
  873. || mcell3_rx->n_pathways == RX_REFLEC // reflections for surf mols
  874. || mcell3_rx->n_pathways == RX_TRANSP
  875. || mcell3_rx->n_pathways == RX_ABSORB_REGION_BORDER
  876. ); // limited for now
  877. assert(mcell3_rx->cum_probs != nullptr);
  878. // int *product_idx_aux - ignored, post-processing information
  879. // u_int *product_idx - ignored, post-processing information
  880. // struct species **players - ignored, might check it but will contain the same info as pathways
  881. // NFSIM struct species ***nfsim_players; /* a matrix of the nfsim elements associated with each path */
  882. // NFSIM short *geometries; /* Geometries of reactants/products */
  883. // NFSIM short **nfsim_geometries; /* geometries of the nfsim geometries associated with each path */
  884. CHECK_PROPERTY(mcell3_rx->n_occurred == 0);
  885. CHECK_PROPERTY(mcell3_rx->n_skipped == 0);
  886. small_vector<small_vector<BNG::RxnRateInfo>> variable_rates_per_pathway;
  887. if (mcell3_rx->n_pathways > 0) {
  888. variable_rates_per_pathway.resize(mcell3_rx->n_pathways);
  889. get_variable_rates(mcell3_rx->prob_t, variable_rates_per_pathway);
  890. }
  891. else {
  892. CHECK_PROPERTY(mcell3_rx->prob_t == nullptr);
  893. }
  894. int pathway_index = 0;
  895. for (
  896. pathway* current_pathway = mcell3_rx->pathway_head;
  897. current_pathway != nullptr;
  898. current_pathway = current_pathway->next) {
  899. // --- pathway ---
  900. // there can be a single reaction for absorb, reflect and transparent reactions
  901. assert((pathway_index < mcell3_rx->n_pathways) || (pathway_index == 0 && mcell3_rx->n_pathways < 0));
  902. // pathway is renamed in MCell3 to reaction because pathway has a different meaning
  903. // MCell3 reaction is MCell4 reaction class
  904. RxnRule rxn(&world->bng_engine.get_data());
  905. if (mcell3_rx->prob_t == nullptr) {
  906. rxn.base_rate_constant = current_pathway->km;
  907. }
  908. else {
  909. CHECK_PROPERTY(mcell3_rx->n_pathways > 0);
  910. CHECK_PROPERTY(mcell3_rx->pb_factor != 0);
  911. // with variable rates, we may need to recompute the initial value for iteration 0
  912. double prob = (pathway_index == 0) ?
  913. mcell3_rx->cum_probs[0] : (mcell3_rx->cum_probs[pathway_index] - mcell3_rx->cum_probs[pathway_index-1]);
  914. rxn.base_rate_constant = prob / mcell3_rx->pb_factor;
  915. }
  916. if (!variable_rates_per_pathway.empty()) {
  917. assert(pathway_index < (int)variable_rates_per_pathway.size());
  918. rxn.base_variable_rates = variable_rates_per_pathway[pathway_index];
  919. }
  920. CHECK_PROPERTY(current_pathway->reactant1 != nullptr);
  921. CHECK_PROPERTY(current_pathway->orientation1 == 0 || current_pathway->orientation1 == 1 || current_pathway->orientation1 == -1);
  922. CHECK_PROPERTY(
  923. current_pathway->reactant2 == nullptr ||
  924. (current_pathway->orientation2 == 0 || current_pathway->orientation2 == 1 || current_pathway->orientation2 == -1)
  925. );
  926. CHECK_PROPERTY(
  927. current_pathway->reactant3 == nullptr ||
  928. (current_pathway->orientation3 == 0 || current_pathway->orientation3 == 1 || current_pathway->orientation3 == -1)
  929. );
  930. // reactants
  931. vector<species_id_t> reactant_species_ids;
  932. if (current_pathway->reactant1 != nullptr) {
  933. species_id_t reactant1_id = get_mcell4_species_id(current_pathway->reactant1->species_id);
  934. rxn.append_reactant(
  935. world->bng_engine.create_cplx_from_species(reactant1_id, current_pathway->orientation1, BNG::COMPARTMENT_ID_NONE));
  936. reactant_species_ids.push_back(reactant1_id);
  937. if (current_pathway->reactant2 != nullptr) {
  938. species_id_t reactant2_id = get_mcell4_species_id(current_pathway->reactant2->species_id);
  939. rxn.append_reactant(
  940. world->bng_engine.create_cplx_from_species(reactant2_id, current_pathway->orientation2, BNG::COMPARTMENT_ID_NONE));
  941. reactant_species_ids.push_back(reactant2_id);
  942. if (current_pathway->reactant3 != nullptr) {
  943. mcell_error("TODO_CONVERSION: reactions with 3 reactants are not supported");
  944. }
  945. }
  946. else {
  947. // reactant3 must be null if reactant2 is null
  948. assert(current_pathway->reactant3 == nullptr);
  949. }
  950. }
  951. else {
  952. assert(false && "No reactants?");
  953. }
  954. CHECK_PROPERTY(current_pathway->km_filename == nullptr);
  955. if (mcell3_rx->n_pathways == RX_ABSORB_REGION_BORDER) {
  956. CHECK_PROPERTY(current_pathway->flags == PATHW_ABSORP);
  957. // TODO: check and if really not used, completely remove AbsorbRegionBorder
  958. // CHECK_PROPERTY(false && "Check to see whether PATHW_ABSORP is really produced by MCell, probably not.");
  959. rxn.type = RxnType::AbsorbRegionBorder;
  960. }
  961. else if (mcell3_rx->n_pathways == RX_TRANSP) {
  962. CHECK_PROPERTY(current_pathway->flags == PATHW_TRANSP);
  963. rxn.type = RxnType::Transparent;
  964. }
  965. else if (mcell3_rx->n_pathways == RX_REFLEC) {
  966. CHECK_PROPERTY(current_pathway->flags == PATHW_REFLEC || current_pathway->flags == (PATHW_REFLEC | PATHW_CLAMP_FLUX));
  967. rxn.type = RxnType::Reflect;
  968. if ((current_pathway->flags & PATHW_CLAMP_FLUX) != 0) {
  969. // the rxn is in the form of: a + sc -> null
  970. rxn.set_flag(BNG::RXN_FLAG_CREATED_FOR_FLUX_CLAMP);
  971. create_clamp_release_event(current_pathway, rxn, reactant_species_ids);
  972. rxn.base_rate_constant = DBL_GIGANTIC;
  973. }
  974. }
  975. else {
  976. CHECK_PROPERTY(
  977. current_pathway->flags == 0 ||
  978. current_pathway->flags == PATHW_ABSORP ||
  979. current_pathway->flags == PATHW_CLAMP_CONC ||
  980. current_pathway->flags == PATHW_CLAMP_FLUX);
  981. rxn.type = RxnType::Standard;
  982. // products
  983. product *product_ptr = current_pathway->product_head;
  984. while (product_ptr != nullptr) {
  985. CHECK_PROPERTY(product_ptr->orientation == 0 || product_ptr->orientation == 1 || product_ptr->orientation == -1);
  986. species_id_t product_id = get_mcell4_species_id(product_ptr->prod->species_id);
  987. rxn.append_product(
  988. world->bng_engine.create_cplx_from_species(product_id, product_ptr->orientation, BNG::COMPARTMENT_ID_NONE));
  989. product_ptr = product_ptr->next;
  990. }
  991. // create conc clamp event
  992. assert(current_pathway->flags != PATHW_CLAMP_FLUX);
  993. if (current_pathway->flags == PATHW_CLAMP_CONC) {
  994. assert(rxn.is_bimol());
  995. // the rxn is in the form of: a + sc -> null
  996. rxn.set_flag(BNG::RXN_FLAG_CREATED_FOR_CONCENTRATION_CLAMP);
  997. create_clamp_release_event(current_pathway, rxn, reactant_species_ids);
  998. rxn.base_rate_constant = DBL_GIGANTIC;
  999. }
  1000. }
  1001. if (current_pathway->pathname != nullptr) {
  1002. assert(current_pathway->pathname->sym != nullptr);
  1003. rxn.name = get_sym_name(current_pathway->pathname->sym);
  1004. }
  1005. else if ((rxn.type != RxnType::Standard || rxn.is_absorptive_region_rxn()) && mcell3_rx->sym != nullptr) {
  1006. rxn.name = get_sym_name(mcell3_rx->sym);
  1007. }
  1008. else {
  1009. rxn.name = "";
  1010. }
  1011. pathway_index++;
  1012. // add our reaction, reaction classes are created on-the-fly
  1013. world->get_all_rxns().add_and_finalize(rxn);
  1014. }
  1015. return true;
  1016. }
  1017. bool MCell3WorldConverter::convert_rxns(volume* s) {
  1018. rxn** reaction_hash = s->reaction_hash;
  1019. int count = s->rx_hashsize;
  1020. for (int i = 0; i < count; ++i) {
  1021. rxn *rx = reaction_hash[i];
  1022. while (rx != nullptr) {
  1023. CHECK(convert_single_reaction(rx));
  1024. rx = rx->next;
  1025. }
  1026. }
  1027. // we can do this update with conversion from MCell3 here
  1028. // because all surface classes were defined when species were converted
  1029. world->get_all_rxns().update_all_mols_and_mol_type_compartments();
  1030. return true;
  1031. }
  1032. // returns nullptr if conversion failed
  1033. RegionExprNode* MCell3WorldConverter::create_release_region_terms_recursively(
  1034. release_evaluator* expr, ReleaseEvent& event_data, const bool is_vol_release
  1035. ) {
  1036. assert(expr != nullptr);
  1037. RegionExprOperator new_op = RegionExprOperator::INVALID;
  1038. uint op_masked = expr->op & REXP_MASK;
  1039. switch (op_masked) {
  1040. case REXP_UNION:
  1041. new_op = RegionExprOperator::UNION;
  1042. break;
  1043. case REXP_INTERSECTION:
  1044. new_op = RegionExprOperator::INTERSECT;
  1045. break;
  1046. case REXP_SUBTRACTION:
  1047. new_op = RegionExprOperator::DIFFERENCE;
  1048. break;
  1049. case REXP_NO_OP:
  1050. // do nothing, the mcell3 expression tree has no leaves and regions are stored directly
  1051. break;
  1052. default:
  1053. assert(false && "Invalid region release_evaluator operator");
  1054. }
  1055. RegionExprNode* new_left;
  1056. RegionExprNode* new_right;
  1057. if ((expr->op & REXP_LEFT_REGION) != 0) {
  1058. // left node is a region
  1059. const Region* reg = world->find_region_by_name(get_sym_name(((region*)expr->left)->sym));
  1060. release_assert(reg != nullptr);
  1061. if (is_vol_release) {
  1062. // volume regions always cover the whole object
  1063. new_left = event_data.region_expr.create_new_expr_node_leaf_geometry_object(reg->geometry_object_id);
  1064. }
  1065. else {
  1066. new_left = event_data.region_expr.create_new_expr_node_leaf_surface_region(reg->id);
  1067. }
  1068. }
  1069. else if (new_op != RegionExprOperator::INVALID){
  1070. // there is an operator so we assume that the left node is a subexpr
  1071. new_left = create_release_region_terms_recursively((release_evaluator*)expr->left, event_data, is_vol_release);
  1072. }
  1073. else {
  1074. new_left = nullptr;
  1075. }
  1076. if ((expr->op & REXP_RIGHT_REGION) != 0) {
  1077. const Region* reg = world->find_region_by_name(get_sym_name(((region*)expr->right)->sym));
  1078. release_assert(reg != nullptr);
  1079. if (is_vol_release) {
  1080. // volume regions always cover the whole object
  1081. new_right = event_data.region_expr.create_new_expr_node_leaf_geometry_object(reg->geometry_object_id);
  1082. }
  1083. else {
  1084. new_right = event_data.region_expr.create_new_expr_node_leaf_surface_region(reg->id);
  1085. }
  1086. }
  1087. else if (new_op != RegionExprOperator::INVALID) {
  1088. new_right = create_release_region_terms_recursively((release_evaluator*)expr->right, event_data, is_vol_release);
  1089. }
  1090. else {
  1091. new_right = nullptr;
  1092. }
  1093. if (new_left != nullptr && new_right != nullptr) {
  1094. assert(new_op != RegionExprOperator::INVALID);
  1095. return event_data.region_expr.create_new_region_expr_node_op(new_op, new_left, new_right);
  1096. }
  1097. else if (new_left != nullptr) {
  1098. return new_left;
  1099. }
  1100. else if (new_right != nullptr) {
  1101. return new_right;
  1102. }
  1103. else {
  1104. assert(false);
  1105. return nullptr;
  1106. }
  1107. }
  1108. bool MCell3WorldConverter::convert_release_events(volume* s) {
  1109. // -- schedule_helper -- (as volume.releaser)
  1110. for (schedule_helper* releaser = s->releaser; releaser != NULL; releaser = releaser->next_scale) {
  1111. // CHECK_PROPERTY(releaser->dt == 1); // it seems that we can safely ignore dt
  1112. // CHECK_PROPERTY(releaser->dt_1 == 1);
  1113. // CHECK_PROPERTY(releaser->now == 0); // and now as well?
  1114. //ok now: CHECK_PROPERTY(releaser->count == 1);
  1115. CHECK_PROPERTY(releaser->index == 0);
  1116. for (int i = -1; i < releaser->buf_len; i++) {
  1117. for (abstract_element *aep = (i < 0) ? releaser->current : releaser->circ_buf_head[i]; aep != NULL; aep = aep->next) {
  1118. ReleaseEvent* rel_event = new ReleaseEvent(world); // used only locally to capture the information
  1119. // -- release_event_queue --
  1120. release_event_queue *req = (release_event_queue *)aep;
  1121. // -- release_site --
  1122. release_site_obj* rel_site = req->release_site;
  1123. CHECK_PROPERTY(
  1124. rel_site->release_shape == SHAPE_SPHERICAL || rel_site->release_shape == SHAPE_REGION ||
  1125. rel_site->release_shape == SHAPE_LIST
  1126. );
  1127. if (rel_site->release_shape == SHAPE_SPHERICAL) {
  1128. rel_event->release_shape = ReleaseShape::SPHERICAL;
  1129. assert(rel_site->location != nullptr);
  1130. rel_event->location = Vec3(*rel_site->location);
  1131. }
  1132. else if (rel_site->release_shape == SHAPE_REGION) {
  1133. rel_event->release_shape = ReleaseShape::REGION;
  1134. assert(rel_site->region_data != nullptr);
  1135. release_region_data* region_data = rel_site->region_data;
  1136. rel_event->location = Vec3(POS_INVALID);
  1137. species_id_t species_id = get_mcell4_species_id(rel_site->mol_type->species_id);
  1138. const Species& species = world->get_all_species().get(species_id);
  1139. // CHECK_PROPERTY(region_data->in_release == nullptr); // ignored
  1140. if (rel_site->region_data->cum_area_list != nullptr) {
  1141. // surface molecules release onto region
  1142. release_region_data* region_data = rel_site->region_data;
  1143. for (int wall_i = 0; wall_i < region_data->n_walls_included; wall_i++) {
  1144. wall* w = region_data->owners[region_data->obj_index[wall_i]]->wall_p[region_data->wall_index[wall_i]];
  1145. PartitionWallIndexPair wall_index = get_mcell4_wall_index(w);
  1146. rel_event->cumm_area_and_pwall_index_pairs.push_back(
  1147. CummAreaPWallIndexPair(region_data->cum_area_list[wall_i], wall_index)
  1148. );
  1149. }
  1150. // also remember the expression, although the cum_area_and_pwall_index_pairs is what is currently used
  1151. CHECK_PROPERTY(region_data->expression != nullptr);
  1152. rel_event->region_expr.root = create_release_region_terms_recursively(region_data->expression, *rel_event, species.is_vol());
  1153. }
  1154. else {
  1155. // volume or surface molecules release into region
  1156. // this is quite limited for now, a single region is allowed
  1157. CHECK_PROPERTY(region_data->cum_area_list == nullptr);
  1158. CHECK_PROPERTY(region_data->wall_index == nullptr);
  1159. CHECK_PROPERTY(region_data->n_objects == -1);
  1160. CHECK_PROPERTY(region_data->owners == 0);
  1161. // CHECK_PROPERTY(region_data->walls_per_obj == 0); not sure what this does
  1162. rel_event->region_llf = region_data->llf;
  1163. rel_event->region_urb = region_data->urb;
  1164. CHECK_PROPERTY(region_data->expression != nullptr);
  1165. rel_event->region_expr.root = create_release_region_terms_recursively(region_data->expression, *rel_event, species.is_vol());
  1166. }
  1167. }
  1168. else if (rel_site->release_shape == SHAPE_LIST) {
  1169. rel_event->release_shape = ReleaseShape::LIST;
  1170. CHECK_PROPERTY(rel_site->mol_list != nullptr && "There must be at least one molecule to be released with MOLECULE_LIST");
  1171. // convert info on single molecule release
  1172. for (release_single_molecule* item = rel_site->mol_list; item != nullptr; item = item->next) {
  1173. SingleMoleculeReleaseInfo info;
  1174. info.species_id = get_mcell4_species_id(item->mol_type->species_id);
  1175. info.orientation = item->orient;
  1176. info.pos = item->loc;
  1177. rel_event->molecule_list.push_back(info);
  1178. }
  1179. }
  1180. else {
  1181. assert(false);
  1182. }
  1183. if (rel_site->release_shape != SHAPE_LIST) {
  1184. CHECK_PROPERTY(rel_site->mol_type != nullptr);
  1185. rel_event->species_id = get_mcell4_species_id(rel_site->mol_type->species_id);
  1186. assert(world->get_all_species().is_valid_id(rel_event->species_id));
  1187. }
  1188. CHECK_PROPERTY(
  1189. rel_site->release_number_method == CONSTNUM ||
  1190. rel_site->release_number_method == DENSITYNUM ||
  1191. rel_site->release_number_method == CCNNUM
  1192. );
  1193. switch(rel_site->release_number_method) {
  1194. case CONSTNUM:
  1195. rel_event->release_number_method = ReleaseNumberMethod::CONST_NUM;
  1196. CHECK_PROPERTY(rel_site->concentration == 0);
  1197. break;
  1198. case DENSITYNUM:
  1199. rel_event->release_number_method = ReleaseNumberMethod::DENSITY_NUM;
  1200. break;
  1201. case CCNNUM:
  1202. rel_event->release_number_method = ReleaseNumberMethod::CONCENTRATION_NUM;
  1203. break;
  1204. default:
  1205. assert(false);
  1206. }
  1207. CHECK_PROPERTY(rel_event->release_shape == ReleaseShape::REGION || rel_site->orientation == 0);
  1208. CHECK_PROPERTY(rel_site->mean_diameter == 0); // temporary
  1209. CHECK_PROPERTY(rel_site->standard_deviation == 0); // temporary
  1210. rel_event->orientation = rel_site->orientation;
  1211. rel_event->release_number = rel_site->release_number;
  1212. rel_event->concentration = rel_site->concentration;
  1213. if (rel_site->diameter != nullptr) {
  1214. rel_event->diameter = *rel_site->diameter;
  1215. }
  1216. else {
  1217. rel_event->diameter = Vec3(0); // this is the default needed for example for list release
  1218. }
  1219. CHECK_PROPERTY(rel_site->release_prob >= 0 && rel_site->release_prob <= 1);
  1220. rel_event->release_probability = rel_site->release_prob;
  1221. // rel_site->periodic_box - ignored
  1222. rel_event->release_site_name = rel_site->name;
  1223. // rel_site->graph_pattern - ignored
  1224. // -- release_event_queue -- (again)
  1225. CHECK_PROPERTY(t_matrix_to_mat4x4(req->t_matrix) == mat4x4(1) && "only identity matrix for now");
  1226. CHECK_PROPERTY(req->train_counter == 0);
  1227. //release_pattern
  1228. assert(rel_site->pattern != nullptr);
  1229. release_pattern* rp = rel_site->pattern;
  1230. if (rp->sym != nullptr) {
  1231. rel_event->release_pattern_name = get_sym_name(rp->sym);
  1232. }
  1233. else {
  1234. rel_event->release_pattern_name = NAME_NOT_SET;
  1235. }
  1236. assert(rp->delay == req->event_time && "Release pattern must specify the same delay as for which the event is scheduled");
  1237. assert(rp->delay == req->train_high_time && "Initial train_high_time must be the same as delay");
  1238. // all these variables affect scheduling and are handled internally in the event
  1239. rel_event->delay = rp->delay;
  1240. rel_event->train_interval = rp->train_interval;
  1241. rel_event->number_of_trains = rp->number_of_trains;
  1242. rel_event->train_duration = rp->train_duration;
  1243. rel_event->release_interval = rp->release_interval;
  1244. rel_event->update_event_time_for_next_scheduled_time(); // sets the first event_time according to the setup
  1245. world->scheduler.schedule_event(rel_event);
  1246. }
  1247. }
  1248. // -- schedule_helper -- (again)
  1249. CHECK_PROPERTY(releaser->current_count == 0);
  1250. CHECK_PROPERTY(releaser->current == nullptr);
  1251. CHECK_PROPERTY(releaser->current_tail == nullptr);
  1252. CHECK_PROPERTY(releaser->defunct_count == 0);
  1253. CHECK_PROPERTY(releaser->error == 0);
  1254. //CHECK_PROPERTY(releaser->depth == 0); // ignored
  1255. }
  1256. return true;
  1257. }
  1258. bool MCell3WorldConverter::convert_viz_output_events(volume* s) {
  1259. // -- viz_output_block --
  1260. viz_output_block* viz_blocks = s->viz_blocks;
  1261. if (viz_blocks == nullptr) {
  1262. return true; // no visualization data
  1263. }
  1264. CHECK_PROPERTY(viz_blocks->next == nullptr);
  1265. CHECK_PROPERTY(viz_blocks->viz_mode == NO_VIZ_MODE ||
  1266. viz_blocks->viz_mode == ASCII_MODE ||
  1267. viz_blocks->viz_mode == CELLBLENDER_MODE_V1); // just checking valid values
  1268. viz_mode_t viz_mode = viz_blocks->viz_mode;
  1269. // CHECK_PROPERTY(viz_blocks->viz_output_flag == VIZ_ALL_MOLECULES); // ignored (for now?)
  1270. uint_set<species_id_t> species_ids_to_visualize;
  1271. assert((int)mcell3_species_id_map.size() == s->n_species);
  1272. for (int i = 0; i < s->n_species; i++) {
  1273. if (viz_blocks->species_viz_states[i] == INCLUDE_OBJ) {
  1274. species_ids_to_visualize.insert_unique( get_mcell4_species_id(i) );
  1275. }
  1276. else if (viz_blocks->species_viz_states[i] == EXCLUDE_OBJ) {
  1277. continue; // this species is not counted
  1278. }
  1279. else {
  1280. assert(false);
  1281. }
  1282. }
  1283. // CHECK_PROPERTY(viz_blocks->default_mol_state == INCLUDE_OBJ); // seems to be used only internally
  1284. // -- frame_data_head --
  1285. frame_data_list* frame_data_head = viz_blocks->frame_data_head;
  1286. if (frame_data_head == nullptr) {
  1287. // no events to log
  1288. return true;
  1289. }
  1290. CHECK_PROPERTY(frame_data_head->next == nullptr);
  1291. CHECK_PROPERTY(frame_data_head->list_type == OUTPUT_BY_ITERATION_LIST); // limited for now
  1292. CHECK_PROPERTY(frame_data_head->type == ALL_MOL_DATA); // limited for now
  1293. CHECK_PROPERTY(frame_data_head->viz_iteration == 0); // must be zero at sim. start
  1294. // determine periodicity
  1295. CHECK_PROPERTY(frame_data_head->n_viz_iterations > 0);
  1296. double periodicity;
  1297. if (frame_data_head->n_viz_iterations > 1) {
  1298. num_expr_list* iteration_ptr = frame_data_head->iteration_list;
  1299. assert(iteration_ptr->next != nullptr);
  1300. periodicity = iteration_ptr->next->value - iteration_ptr->value;
  1301. // check that the first different in time is true for everything else
  1302. num_expr_list* curr_viz_iteration_ptr = frame_data_head->curr_viz_iteration;
  1303. for (long long i = 0; i < frame_data_head->n_viz_iterations; i++) {
  1304. assert(iteration_ptr != nullptr);
  1305. assert(curr_viz_iteration_ptr != nullptr);
  1306. assert(iteration_ptr->value == curr_viz_iteration_ptr->value);
  1307. if (iteration_ptr->next != nullptr) {
  1308. CHECK_PROPERTY(cmp_eq(periodicity, iteration_ptr->next->value - iteration_ptr->value));
  1309. }
  1310. iteration_ptr = iteration_ptr->next;
  1311. curr_viz_iteration_ptr = curr_viz_iteration_ptr->next;
  1312. }
  1313. }
  1314. else {
  1315. periodicity = 0;
  1316. }
  1317. VizOutputEvent* event = new VizOutputEvent(world);
  1318. event->event_time = frame_data_head->iteration_list->value;
  1319. event->periodicity_interval = periodicity;
  1320. event->viz_mode = viz_mode;
  1321. event->file_prefix_name = viz_blocks->file_prefix_name;
  1322. event->species_ids_to_visualize = species_ids_to_visualize; // copying the whole map
  1323. world->scheduler.schedule_event(event);
  1324. return true;
  1325. }
  1326. static const output_request* find_output_request_by_requester(const volume* s, const output_expression* expr) {
  1327. for (
  1328. const output_request* req = s->output_request_head;
  1329. req != nullptr;
  1330. req = req->next) {
  1331. // pointer comparison
  1332. if (req->requester == expr) {
  1333. return req;
  1334. }
  1335. }
  1336. return nullptr;
  1337. }
  1338. // returns false if conversion failed
  1339. static bool find_output_requests_terms_recursively(
  1340. const volume* s, const output_expression* expr, const int sign, const bool top_level,
  1341. std::vector< pair<const output_request*, int>>& requests_with_sign,
  1342. double& multiplier
  1343. ) {
  1344. assert(sign == -1 || sign == 1);
  1345. // operator must me one of +, -, #
  1346. // # - cast output_request to expr
  1347. double ignored;
  1348. switch (expr->oper) {
  1349. case '#': {
  1350. const output_request* req = find_output_request_by_requester(s, expr);
  1351. CHECK_PROPERTY(req != nullptr);
  1352. requests_with_sign.push_back(make_pair(req, sign));
  1353. }
  1354. break;
  1355. case '(': {
  1356. // parentheses are encoded explicitly, we can ignore them
  1357. bool left_ok = find_output_requests_terms_recursively(
  1358. s, (const output_expression*)expr->left, sign, false, requests_with_sign, ignored);
  1359. if (!left_ok) {
  1360. return false;
  1361. }
  1362. }
  1363. break;
  1364. case '+':
  1365. case '-': {
  1366. bool left_ok = find_output_requests_terms_recursively(
  1367. s, (const output_expression*)expr->left, sign, false, requests_with_sign, ignored);
  1368. if (!left_ok) {
  1369. return false;
  1370. }
  1371. int new_sign = sign;
  1372. if (expr->oper == '-') {
  1373. new_sign = -sign;
  1374. }
  1375. bool right_ok = find_output_requests_terms_recursively(
  1376. s, (const output_expression*)expr->right, new_sign, false, requests_with_sign, ignored);
  1377. if (!right_ok) {
  1378. return false;
  1379. }
  1380. }
  1381. break;
  1382. case '/':
  1383. case '*': {
  1384. CHECK_PROPERTY(top_level && "In a count term, only the whole count expression can be multiplied");
  1385. // which of the operands is the constant multiplier?
  1386. const output_expression* left = (const output_expression*)expr->left;
  1387. const output_expression* right = (const output_expression*)expr->right;
  1388. const output_expression* count_term;
  1389. if (left->oper == '#' || left->oper == '+' || left->oper == '-' || left->oper == '(') {
  1390. count_term = left;
  1391. CHECK_PROPERTY(right->oper == '=' || right->oper == '(');
  1392. if (expr->oper == '*') {
  1393. multiplier = right->value;
  1394. }
  1395. else {
  1396. multiplier = 1.0 / right->value;
  1397. }
  1398. }
  1399. else if (right->oper == '#' || right->oper == '+' || right->oper == '-' || right->oper == '(') {
  1400. count_term = right;
  1401. CHECK_PROPERTY(left->oper == '=' || left->oper == '(');
  1402. if (expr->oper == '*') {
  1403. multiplier = left->value;
  1404. }
  1405. else {
  1406. multiplier = 1.0 / left->value;
  1407. }
  1408. }
  1409. else {
  1410. CHECK_PROPERTY(false && "Could not process count expression");
  1411. }
  1412. assert(sign == +1);
  1413. bool right_ok = find_output_requests_terms_recursively(
  1414. s, count_term, sign, false, requests_with_sign, ignored);
  1415. if (!right_ok) {
  1416. return false;
  1417. }
  1418. }
  1419. break;
  1420. default:
  1421. CHECK_PROPERTY(false && "Invalid output request operator");
  1422. }
  1423. return true;
  1424. }
  1425. static bool ends_with(std::string const & value, std::string const & ending)
  1426. {
  1427. if (ending.size() > value.size()) {
  1428. return false;
  1429. }
  1430. return std::equal(ending.rbegin(), ending.rend(), value.rbegin());
  1431. }
  1432. bool MCell3WorldConverter::convert_mol_or_rxn_count_events(volume* s) {
  1433. output_block* output_blocks = s->output_block_head;
  1434. while (output_blocks != nullptr) {
  1435. MolOrRxnCountEvent* event = new MolOrRxnCountEvent(world);
  1436. CHECK_PROPERTY(output_blocks->timer_type == OUTPUT_BY_STEP);
  1437. CHECK_PROPERTY(output_blocks->t == 0);
  1438. event->event_time = 0;
  1439. event->periodicity_interval = output_blocks->step_time / world->config.time_unit;
  1440. size_t buffer_size = output_blocks->buffersize;
  1441. // we can check that the time_array contains expected values
  1442. for (
  1443. output_set* data_set = output_blocks->data_set_head;
  1444. data_set != nullptr;
  1445. data_set = data_set->next) {
  1446. CHECK_PROPERTY(data_set->outfile_name != nullptr);
  1447. count_buffer_id_t buffer_id =
  1448. world->create_dat_count_buffer(data_set->outfile_name, buffer_size);
  1449. // NOTE: FILE_SUBSTITUTE is interpreted in the same way as FILE_OVERWRITE
  1450. CHECK_PROPERTY(data_set->file_flags == FILE_OVERWRITE || data_set->file_flags == FILE_SUBSTITUTE);
  1451. CHECK_PROPERTY(data_set->chunk_count == 0);
  1452. CHECK_PROPERTY(data_set->header_comment == nullptr);
  1453. CHECK_PROPERTY(data_set->exact_time_flag == 1);
  1454. output_column* column_head = data_set->column_head;
  1455. CHECK_PROPERTY(column_head->next == nullptr);
  1456. CHECK_PROPERTY(column_head->initial_value == 0);
  1457. // TODO: we should better check column_head->expr contents
  1458. // this information is split in a weird way in MCell3,
  1459. // output request contains more information on what should be counted,
  1460. // there is a way how to get to the output_set from the expression
  1461. // but not how to the output_request
  1462. // we simplify it to a list of terms with their sign
  1463. // first is the output request, second is sign (-1 or +1)
  1464. std::vector< pair<const output_request*, int>> requests_with_sign;
  1465. double multiplier = 1;
  1466. CHECK(find_output_requests_terms_recursively(s, column_head->expr, +1, true, requests_with_sign, multiplier));
  1467. MolOrRxnCountItem info(buffer_id, 0); // always DAT format
  1468. info.multiplier = multiplier;
  1469. for (pair<const output_request*, int>& req_w_sign: requests_with_sign) {
  1470. MolOrRxnCountTerm term;
  1471. term.sign_in_expression = req_w_sign.second;
  1472. const output_request* req = req_w_sign.first;
  1473. CHECK_PROPERTY(req != 0);
  1474. int o = req->count_orientation;
  1475. if (o == ORIENT_NOT_SET) {
  1476. term.orientation = ORIENTATION_NOT_SET;
  1477. }
  1478. else {
  1479. CHECK_PROPERTY(o == ORIENTATION_UP || o == ORIENTATION_NONE || o == ORIENTATION_DOWN);
  1480. term.orientation = o;
  1481. }
  1482. // report type
  1483. CHECK_PROPERTY(
  1484. req->report_type == REPORT_CONTENTS ||
  1485. req->report_type == REPORT_RXNS ||
  1486. req->report_type == (REPORT_CONTENTS | REPORT_ENCLOSED) ||
  1487. req->report_type == (REPORT_CONTENTS | REPORT_WORLD) ||
  1488. req->report_type == (REPORT_RXNS | REPORT_WORLD) ||
  1489. req->report_type == (REPORT_RXNS | REPORT_ENCLOSED)
  1490. );
  1491. bool count_mols_not_rxns;
  1492. if ((req->report_type & REPORT_CONTENTS) != 0) {
  1493. count_mols_not_rxns = true;
  1494. }
  1495. else if ((req->report_type & REPORT_RXNS) != 0) {
  1496. count_mols_not_rxns = false;
  1497. }
  1498. else {
  1499. assert(false);
  1500. count_mols_not_rxns = true; // silence compilation warning
  1501. }
  1502. // count location
  1503. // only whole geom object for now
  1504. if ((req->report_type & REPORT_ENCLOSED) != 0) {
  1505. term.type = count_mols_not_rxns ? CountType::EnclosedInVolumeRegion : CountType::RxnCountInVolumeRegion;
  1506. string reg_name = get_sym_name(req->count_location);
  1507. CHECK_PROPERTY(ends_with(reg_name, ",ALL")); // enclused in object must be whole objects
  1508. const Region* reg = world->get_partition(0).find_region_by_name(reg_name);
  1509. if (reg == nullptr) {
  1510. mcell_error("Could not find a counted region with name %s.", reg_name.c_str());
  1511. }
  1512. assert(reg->geometry_object_id != GEOMETRY_OBJECT_ID_INVALID);
  1513. geometry_object_id_t geometry_object_id = reg->geometry_object_id;
  1514. // MDL version does not support region expressions
  1515. term.region_expr.root = term.region_expr.create_new_expr_node_leaf_geometry_object(geometry_object_id);
  1516. // set flag that we should include this object in counted volumes
  1517. GeometryObject& obj = world->get_partition(0).get_geometry_object(geometry_object_id);
  1518. obj.set_is_used_in_mol_rxn_counts();
  1519. }
  1520. else {
  1521. if (req->count_location == nullptr) {
  1522. term.type = count_mols_not_rxns ? CountType::EnclosedInWorld : CountType::RxnCountInWorld;
  1523. }
  1524. else {
  1525. CHECK_PROPERTY(req->report_type == REPORT_CONTENTS || req->report_type == REPORT_RXNS);
  1526. string reg_name = get_sym_name(req->count_location);
  1527. const Region* reg = world->get_partition(0).find_region_by_name(reg_name);
  1528. // MDL version does not support region expressions
  1529. term.region_expr.root = term.region_expr.create_new_expr_node_leaf_surface_region(reg->id);
  1530. term.type = count_mols_not_rxns ? CountType::PresentOnSurfaceRegion : CountType::RxnCountOnSurfaceRegion;
  1531. }
  1532. }
  1533. if (count_mols_not_rxns) {
  1534. // count target (species)
  1535. CHECK_PROPERTY(req->count_target != 0);
  1536. string species_name = get_sym_name(req->count_target);
  1537. term.species_pattern_type = SpeciesPatternType::SpeciesId;
  1538. term.species_id = world->get_all_species().find_by_name(species_name);
  1539. CHECK_PROPERTY(term.species_id != SPECIES_ID_INVALID);
  1540. }
  1541. else {
  1542. // set that the reaction must be counted
  1543. CHECK_PROPERTY(req->count_target != nullptr);
  1544. string rxn_name = get_sym_name(req->count_target);
  1545. BNG::RxnRule* rxn_rule = world->get_all_rxns().find_rxn_rule_by_name(rxn_name);
  1546. CHECK_PROPERTY(rxn_rule != nullptr && "Rxn rule with name was not found");
  1547. // set rxn counting flag
  1548. switch (term.type) {
  1549. case CountType::RxnCountInWorld:
  1550. rxn_rule->set_is_counted_in_world();
  1551. break;
  1552. case CountType::RxnCountInVolumeRegion:
  1553. rxn_rule->set_is_counted_in_volume_regions();
  1554. break;
  1555. case CountType::RxnCountOnSurfaceRegion:
  1556. rxn_rule->set_is_counted_on_surface_regions();
  1557. break;
  1558. default:
  1559. assert(false);
  1560. }
  1561. term.rxn_rule_id = rxn_rule->id;
  1562. }
  1563. info.terms.push_back(term);
  1564. }
  1565. event->add_mol_count_item(info);
  1566. }
  1567. world->scheduler.schedule_event(event);
  1568. // process next output block
  1569. output_blocks = output_blocks->next;
  1570. }
  1571. return true;
  1572. }
  1573. void MCell3WorldConverter::update_and_schedule_concentration_clamps() {
  1574. for (ClampReleaseEvent* clamp: concentration_clamps) {
  1575. clamp->update_cumm_areas_and_scaling();
  1576. world->scheduler.schedule_event(clamp);
  1577. }
  1578. }
  1579. } // namespace mcell