openCARP
Doxygen code documentation for the open cardiac electrophysiology simulator openCARP
sim_utils.cc
Go to the documentation of this file.
1 // ----------------------------------------------------------------------------
2 // openCARP is an open cardiac electrophysiology simulator.
3 //
4 // Copyright (C) 2020 openCARP project
5 //
6 // This program is licensed under the openCARP Academic Public License (APL)
7 // v1.0: You can use and redistribute it and/or modify it in non-commercial
8 // academic environments under the terms of APL as published by the openCARP
9 // project v1.0, or (at your option) any later version. Commercial use requires
10 // a commercial license (info@opencarp.org).
11 //
12 // This program is distributed without any warranty; see the openCARP APL for
13 // more details.
14 //
15 // You should have received a copy of the openCARP APL along with this program
16 // and can find it online: http://www.opencarp.org/license
17 // ----------------------------------------------------------------------------
18 
27 #include "basics.h"
28 #include "sim_utils.h"
29 #include "fem.h"
30 #include "physics.h"
31 #include "async_io.h"
32 #include "SF_init.h"
33 #include "opencarp_types.h"
34 #ifdef WITH_LEADFIELD
35 #include "leadfield.h"
36 #endif
37 #ifdef WITH_POWERCAPPING
38 #include <vector>
39 #include "powercapping.h"
40 #endif
41 
42 #include <cctype>
43 #include <cstdlib>
44 #include <libgen.h>
45 #include <iomanip>
46 #include <map>
47 #include <set>
48 #include <sstream>
49 #include <string>
50 #include <sys/wait.h>
51 #include <unistd.h>
52 #include <vector>
53 
54 #include "openCARP_schema.hpp"
55 #include "runtime.hpp"
56 #include "snapshot_file_io.hpp"
57 
58 namespace opencarp {
59 
60 namespace {
61 
62 enum class ParserCompareMode {
63  Off,
64  Warn,
65  Strict,
66 };
67 
68 enum class ParserFallbackMode {
69  Off,
70  Legacy,
71 };
72 
73 struct RuntimeCompatOptions {
74  ParserFallbackMode fallback_mode = ParserFallbackMode::Off;
75 };
76 
77 struct LegacyCompareInput {
78  bool available = false;
79  bool warn_when_unavailable = true;
80  std::vector<std::string> runtime_args;
81  std::string unavailable_reason;
82 };
83 
84 struct LegacySnapshotHelperRunResult {
85  bool exited = false;
86  int exit_status = -1;
87  bool signaled = false;
88  int signal = 0;
89 };
90 
91 std::vector<paramschema::CitationSuggestion> active_citation_suggestions;
92 
93 std::string trim_copy(const std::string& value);
94 std::string to_lower_ascii(std::string value);
95 bool build_legacy_compare_input(int argc, char** argv, LegacyCompareInput* input, std::string* error);
96 
97 std::string join_paths_for_message(const std::vector<std::string>& paths)
98 {
99  if (paths.empty()) {
100  return std::string();
101  }
102 
103  std::ostringstream joined;
104  for (std::size_t i = 0; i < paths.size(); ++i) {
105  if (i != 0) {
106  joined << ", ";
107  }
108  joined << paths[i];
109  }
110  return joined.str();
111 }
112 
113 bool match_long_option(const std::string& token, const char* option, std::string* attached_value)
114 {
115  *attached_value = std::string();
116  if (token == option) {
117  return true;
118  }
119 
120  const std::string prefix = std::string(option) + "=";
121  if (token.size() > prefix.size() && token.compare(0, prefix.size(), prefix) == 0) {
122  *attached_value = token.substr(prefix.size());
123  return true;
124  }
125 
126  return false;
127 }
128 
129 bool is_help_topic_candidate(const char* token)
130 {
131  return token != NULL && token[0] != '\0' && token[0] != '-' && token[0] != '+';
132 }
133 
134 bool is_long_option_argument_error(const std::string& token,
135  const char* option,
136  const std::string& attached_value,
137  std::string* error)
138 {
139  if (attached_value.empty()) {
140  return false;
141  }
142 
143  *error = "Unexpected argument for " + std::string(option) + " in '" + token + "'";
144  return true;
145 }
146 
147 bool normalize_runtime_args(int argc,
148  char** argv,
149  std::vector<std::string>* normalized,
150  RuntimeCompatOptions* compat,
151  std::string* error)
152 {
153  normalized->clear();
154  compat->fallback_mode = ParserFallbackMode::Off;
155 
156  if (argc <= 0 || argv == NULL || argv[0] == NULL) {
157  *error = "Missing program name";
158  return false;
159  }
160 
161  normalized->push_back(argv[0]);
162 
163  for (int i = 1; i < argc; ++i) {
164  const std::string token = argv[i];
165  if (token == "+") {
166  break;
167  }
168 
169  std::string attached_value;
170 
171  if (token == "+Help" || match_long_option(token, "--help", &attached_value)) {
172  std::string topic = "PrM";
173  if (!attached_value.empty()) {
174  topic = attached_value;
175  } else if (i + 1 < argc && is_help_topic_candidate(argv[i + 1])) {
176  topic = argv[++i];
177  }
178  normalized->push_back("+Help");
179  normalized->push_back(topic);
180  break;
181  }
182 
183  if (token == "+Doc" || match_long_option(token, "--doc", &attached_value)) {
184  std::string topic = "ALL";
185  if (!attached_value.empty()) {
186  topic = attached_value;
187  } else if (i + 1 < argc && is_help_topic_candidate(argv[i + 1])) {
188  topic = argv[++i];
189  }
190  normalized->push_back("+Doc");
191  normalized->push_back(topic);
192  break;
193  }
194 
195  if (token == "+Default" || match_long_option(token, "--default", &attached_value)) {
196  if (token != "+Default" && is_long_option_argument_error(token, "--default", attached_value, error)) {
197  return false;
198  }
199  normalized->push_back("+Default");
200  break;
201  }
202 
203  if (token == "+Run" || match_long_option(token, "--run", &attached_value)) {
204  if (token != "+Run" && is_long_option_argument_error(token, "--run", attached_value, error)) {
205  return false;
206  }
207  normalized->push_back("+Run");
208  continue;
209  }
210 
211  if (token == "+I" || match_long_option(token, "--interactive", &attached_value)) {
212  if (!attached_value.empty()) {
213  *error = "Unexpected argument for --interactive in '" + token + "'";
214  } else {
215  *error = "Unsupported option " + token + " (interactive mode is not available)";
216  }
217  return false;
218  }
219 
220  if (match_long_option(token, "--param-fallback", &attached_value)) {
221  std::string mode = attached_value;
222  if (mode.empty()) {
223  if (i + 1 >= argc) {
224  *error = "Missing argument after --param-fallback";
225  return false;
226  }
227  mode = argv[++i];
228  }
229 
230  if (to_lower_ascii(trim_copy(mode)) != "legacy") {
231  *error = "Unsupported value '" + mode + "' for --param-fallback (expected legacy)";
232  return false;
233  }
234 
235  compat->fallback_mode = ParserFallbackMode::Legacy;
236  continue;
237  }
238 
239  if (token == "+F" || match_long_option(token, "--file", &attached_value)) {
240  std::string filename = attached_value;
241  if (filename.empty()) {
242  if (i + 1 >= argc) {
243  *error = "Missing filename after " + token;
244  return false;
245  }
246  filename = argv[++i];
247  }
248  normalized->push_back("+F");
249  normalized->push_back(filename);
250  continue;
251  }
252 
253  if (token == "+Save" || match_long_option(token, "--save", &attached_value)) {
254  std::string filename = attached_value;
255  if (filename.empty()) {
256  if (i + 1 >= argc) {
257  *error = "Missing argument after " + token;
258  return false;
259  }
260  filename = argv[++i];
261  }
262  normalized->push_back("+Save");
263  normalized->push_back(filename);
264  continue;
265  }
266 
267  normalized->push_back(token);
268  }
269 
270  return true;
271 }
272 
273 bool parse_option_argument(int* index,
274  int argc,
275  char** argv,
276  const std::string& token,
277  const std::string& attached_value,
278  const char* option_name,
279  std::string* value,
280  std::string* error)
281 {
282  if (!attached_value.empty()) {
283  *value = attached_value;
284  return true;
285  }
286  if (*index + 1 >= argc) {
287  *error = "Missing argument after " + std::string(option_name);
288  return false;
289  }
290  *value = argv[++(*index)];
291  return true;
292 }
293 
294 bool filename_has_suffix(const std::string& filename, const char* suffix)
295 {
296  const std::string normalized_filename = to_lower_ascii(trim_copy(filename));
297  const std::string normalized_suffix = to_lower_ascii(std::string(suffix == NULL ? "" : suffix));
298  return normalized_filename.size() >= normalized_suffix.size() &&
299  normalized_filename.compare(normalized_filename.size() - normalized_suffix.size(),
300  normalized_suffix.size(),
301  normalized_suffix) == 0;
302 }
303 
304 bool build_legacy_compare_input(int argc, char** argv, LegacyCompareInput* input, std::string* error)
305 {
306  input->available = false;
307  input->warn_when_unavailable = true;
308  input->runtime_args.clear();
309  input->unavailable_reason.clear();
310 
311  if (argc <= 0 || argv == NULL || argv[0] == NULL) {
312  *error = "Missing program name";
313  return false;
314  }
315 
316  input->runtime_args.push_back(argv[0]);
317  bool saw_passthrough_token_before_file = false;
318 
319  for (int i = 1; i < argc; ++i) {
320  const std::string token = argv[i];
321  if (token == "+") {
322  break;
323  }
324 
325  std::string attached_value;
326 
327  if (match_long_option(token, "--param-fallback", &attached_value)) {
328  std::string ignored;
329  if (!parse_option_argument(&i, argc, argv, token, attached_value, "--param-fallback", &ignored, error)) {
330  return false;
331  }
332  continue;
333  }
334 
335  if (token == "+Save" || match_long_option(token, "--save", &attached_value)) {
336  std::string ignored;
337  if (!parse_option_argument(&i, argc, argv, token, attached_value, token.c_str(), &ignored, error)) {
338  return false;
339  }
340  continue;
341  }
342 
343  if (token == "+Help" || match_long_option(token, "--help", &attached_value)) {
344  std::string topic = "PrM";
345  if (!attached_value.empty()) {
346  topic = attached_value;
347  } else if (i + 1 < argc && is_help_topic_candidate(argv[i + 1])) {
348  topic = argv[++i];
349  }
350  input->runtime_args.push_back("+Help");
351  input->runtime_args.push_back(topic);
352  input->available = true;
353  return true;
354  }
355 
356  if (token == "+Doc" || match_long_option(token, "--doc", &attached_value)) {
357  std::string topic = "ALL";
358  if (!attached_value.empty()) {
359  topic = attached_value;
360  } else if (i + 1 < argc && is_help_topic_candidate(argv[i + 1])) {
361  topic = argv[++i];
362  }
363  input->runtime_args.push_back("+Doc");
364  input->runtime_args.push_back(topic);
365  input->available = true;
366  return true;
367  }
368 
369  if (token == "+Default" || match_long_option(token, "--default", &attached_value)) {
370  if (token != "+Default" && is_long_option_argument_error(token, "--default", attached_value, error)) {
371  return false;
372  }
373  input->runtime_args.push_back("+Default");
374  continue;
375  }
376 
377  if (token == "+Run" || match_long_option(token, "--run", &attached_value)) {
378  if (token != "+Run" && is_long_option_argument_error(token, "--run", attached_value, error)) {
379  return false;
380  }
381  input->runtime_args.push_back("+Run");
382  continue;
383  }
384 
385  if (token == "+F" || match_long_option(token, "--file", &attached_value)) {
386  std::string filename;
387  if (!parse_option_argument(&i, argc, argv, token, attached_value, token.c_str(), &filename, error)) {
388  return false;
389  }
390 
391  if (saw_passthrough_token_before_file) {
392  input->unavailable_reason =
393  "legacy compare is unavailable when direct parameter arguments precede a .par input file";
394  input->runtime_args.clear();
395  input->runtime_args.push_back(argv[0]);
396  return true;
397  }
398 
399  if (!filename_has_suffix(filename, ".par")) {
400  input->unavailable_reason =
401  "legacy compare is unavailable for non-.par input file '" + filename + "'";
402  input->runtime_args.clear();
403  input->runtime_args.push_back(argv[0]);
404  return true;
405  }
406 
407  input->runtime_args.push_back("+F");
408  input->runtime_args.push_back(filename);
409  continue;
410  }
411 
412  input->runtime_args.push_back(token);
413  saw_passthrough_token_before_file = true;
414  }
415 
416  input->available = input->runtime_args.size() > 1;
417  if (!input->available && input->unavailable_reason.empty()) {
418  input->unavailable_reason = "unable to reconstruct a legacy-compatible parameter input";
419  }
420  return true;
421 }
422 
423 std::string trim_copy(const std::string& value)
424 {
425  std::string::size_type first = 0;
426  while (first < value.size() && std::isspace(static_cast<unsigned char>(value[first]))) {
427  ++first;
428  }
429 
430  std::string::size_type last = value.size();
431  while (last > first && std::isspace(static_cast<unsigned char>(value[last - 1]))) {
432  --last;
433  }
434 
435  return value.substr(first, last - first);
436 }
437 
438 std::string to_lower_ascii(std::string value)
439 {
440  for (std::string::size_type i = 0; i < value.size(); ++i) {
441  value[i] = static_cast<char>(std::tolower(static_cast<unsigned char>(value[i])));
442  }
443  return value;
444 }
445 
446 ParserCompareMode parser_compare_mode()
447 {
448  const char* raw = std::getenv("OPENCARP_PARAM_COMPARE");
449  if (raw == NULL) {
450  return ParserCompareMode::Strict;
451  }
452 
453  const std::string normalized = to_lower_ascii(trim_copy(raw));
454  if (normalized.empty() || normalized == "1" || normalized == "on" || normalized == "true" ||
455  normalized == "yes" || normalized == "strict" || normalized == "fail" || normalized == "error") {
456  return ParserCompareMode::Strict;
457  }
458  if (normalized == "warn") {
459  return ParserCompareMode::Warn;
460  }
461  if (normalized == "0" || normalized == "off" || normalized == "false" || normalized == "no") {
462  return ParserCompareMode::Off;
463  }
464 
465  return ParserCompareMode::Strict;
466 }
467 
468 ParserFallbackMode parser_fallback_mode()
469 {
470  const char* raw = std::getenv("OPENCARP_PARAM_FALLBACK");
471  if (raw == NULL) {
472  return ParserFallbackMode::Off;
473  }
474 
475  const std::string normalized = to_lower_ascii(trim_copy(raw));
476  if (normalized.empty() || normalized == "0" || normalized == "off" || normalized == "false" ||
477  normalized == "no") {
478  return ParserFallbackMode::Off;
479  }
480  if (normalized == "legacy") {
481  return ParserFallbackMode::Legacy;
482  }
483 
484  return ParserFallbackMode::Off;
485 }
486 
487 void populate_arg_pointers(const std::vector<std::string>& values, std::vector<char*>* argv)
488 {
489  argv->assign(values.size(), NULL);
490  for (std::size_t i = 0; i < values.size(); ++i) {
491  (*argv)[i] = const_cast<char*>(values[i].c_str());
492  }
493 }
494 
495 void print_lines(FILE* stream, const char* label, const std::vector<std::string>& lines)
496 {
497  for (std::size_t i = 0; i < lines.size(); ++i) {
498  fprintf(stream, "%s%s\n", label, lines[i].c_str());
499  }
500 }
501 
502 void clear_active_citation_suggestions()
503 {
504  active_citation_suggestions.clear();
505 }
506 
507 void store_active_citation_suggestions(const std::vector<paramschema::CitationSuggestion>& suggestions)
508 {
509  active_citation_suggestions = suggestions;
510 }
511 
512 void print_active_citation_suggestions()
513 {
514  if (active_citation_suggestions.empty() || get_rank() != 0) {
515  return;
516  }
517 
518  const std::vector<std::string> lines =
519  paramschema::format_citation_suggestions(active_citation_suggestions);
520  if (lines.empty()) {
521  return;
522  }
523 
524  std::fprintf(stdout, "\nIf you publish studies based on this simulation, the following references are likely relevant:\n");
525  for (std::size_t i = 0; i < lines.size(); ++i) {
526  std::fprintf(stdout, " %s\n", lines[i].c_str());
527  }
528  std::fprintf(stdout, " You can use https://citation.doi.org to format references in your preferred format or https://www.doi2bib.org to turn the DOIs into BibTeX format.\n");
529  std::fprintf(stdout, "\nWe'd be happy to also see your experiment published to boost its impact and foster reproducibility.\nSee https://opencarp.org/community/upload-experiment for details.\n\n");
530 
531  std::fflush(stdout);
532 }
533 
534 void print_parser_runtime_state(const std::vector<std::string>& runtime_args)
535 {
536  const auto& schema = paramschema::openCARP_schema();
537  const std::string program_name =
538  runtime_args.empty() || runtime_args[0].empty() ? "openCARP" : runtime_args[0];
539 
540  std::string rendered;
541  std::vector<std::string> errors;
542  if (!paramschema::render_save_text(schema, program_name, &rendered, &errors)) {
543  print_lines(stderr, "parameter parser warning: ", errors);
544  return;
545  }
546 
547  fputs(rendered.c_str(), stderr);
548 }
549 
550 paramschema::ExecutionResult execute_parser_runtime_args(const std::vector<std::string>& runtime_args,
551  const bool allow_save)
552 {
553  std::vector<char*> argv;
554  populate_arg_pointers(runtime_args, &argv);
555 
556  const auto& schema = paramschema::openCARP_schema();
557 
558  paramschema::ExecutionOptions options;
559  options.allow_save = allow_save;
560  return paramschema::execute_legacy_cli(schema, static_cast<int>(argv.size()), argv.data(), options);
561 }
562 
563 bool apply_parser_runtime_args(const std::vector<std::string>& runtime_args,
564  const bool allow_save,
565  paramschema::ExecutionResult* executed_out)
566 {
567  clear_active_citation_suggestions();
568  const paramschema::ExecutionResult executed = execute_parser_runtime_args(runtime_args, allow_save);
569  if (executed_out != NULL) {
570  *executed_out = executed;
571  }
572  print_lines(stderr, "parameter parser warning: ", executed.warnings);
573  if (!executed.rendered_output.empty()) {
574  fputs(executed.rendered_output.c_str(), stdout);
575  }
576  if (!executed.errors.empty() || executed.status == paramschema::ExecutionStatus::Fatal) {
577  print_lines(stderr, "parameter parser error: ", executed.errors);
578  return false;
579  }
580 
581  if (param_globals::output_setup) {
582  print_parser_runtime_state(runtime_args);
583  }
584 
585  if (executed.status == paramschema::ExecutionStatus::Help) {
586  exit(EXIT_SUCCESS);
587  }
588 
589  store_active_citation_suggestions(executed.citations);
590  return true;
591 }
592 
593 std::string parent_directory(const std::string& path)
594 {
595  const std::string::size_type slash = path.rfind('/');
596  if (slash == std::string::npos) {
597  return ".";
598  }
599  if (slash == 0) {
600  return "/";
601  }
602  return path.substr(0, slash);
603 }
604 
605 std::string join_path(const std::string& left, const std::string& right)
606 {
607  if (left.empty() || left == ".") {
608  return right;
609  }
610  if (!left.empty() && left[left.size() - 1] == '/') {
611  return left + right;
612  }
613  return left + "/" + right;
614 }
615 
616 bool find_executable_on_path(const std::string& name, std::string* resolved_path)
617 {
618  if (name.empty() || name.find('/') != std::string::npos) {
619  return false;
620  }
621 
622  const char* path_env = std::getenv("PATH");
623  if (path_env == NULL || path_env[0] == '\0') {
624  return false;
625  }
626 
627  const std::string path_list = path_env;
628  std::string::size_type start = 0;
629  while (start <= path_list.size()) {
630  std::string::size_type end = path_list.find(':', start);
631  if (end == std::string::npos) {
632  end = path_list.size();
633  }
634 
635  const std::string directory = path_list.substr(start, end - start);
636  const std::string candidate = join_path(directory.empty() ? "." : directory, name);
637  if (access(candidate.c_str(), X_OK) == 0) {
638  *resolved_path = candidate;
639  return true;
640  }
641 
642  if (end == path_list.size()) {
643  break;
644  }
645  start = end + 1;
646  }
647 
648  return false;
649 }
650 
651 bool resolve_legacy_snapshot_helper(const std::string& program_path, std::string* helper_path)
652 {
653  std::vector<std::string> resolved_program_paths;
654  if (!program_path.empty()) {
655  resolved_program_paths.push_back(program_path);
656  }
657 
658  if (program_path.find('/') == std::string::npos) {
659  std::string resolved_program_path;
660  if (find_executable_on_path(program_path, &resolved_program_path)) {
661  resolved_program_paths.push_back(resolved_program_path);
662  }
663  }
664 
665  for (std::size_t i = 0; i < resolved_program_paths.size(); ++i) {
666  const std::string program_dir = parent_directory(resolved_program_paths[i]);
667  const std::string parent_dir = parent_directory(program_dir);
668 
669  const std::vector<std::string> candidates = {
670  join_path(program_dir, "param-parser-legacy-snapshot"),
671  join_path(join_path(parent_dir, "simulator"), "param-parser-legacy-snapshot"),
672  };
673 
674  for (std::size_t j = 0; j < candidates.size(); ++j) {
675  if (access(candidates[j].c_str(), X_OK) == 0) {
676  *helper_path = candidates[j];
677  return true;
678  }
679  }
680  }
681 
682  return find_executable_on_path("param-parser-legacy-snapshot", helper_path);
683 }
684 
685 bool create_temp_output_path(const char* suffix, std::string* path)
686 {
687  char temp_path[128];
688  if (suffix != NULL && suffix[0] != '\0') {
689  std::snprintf(temp_path, sizeof temp_path, "/tmp/opencarp-parser-compare-XXXXXX%s", suffix);
690  } else {
691  std::snprintf(temp_path, sizeof temp_path, "/tmp/opencarp-parser-compare-XXXXXX");
692  }
693 
694  const int fd = suffix != NULL && suffix[0] != '\0' ? mkstemps(temp_path, static_cast<int>(std::strlen(suffix))) :
695  mkstemp(temp_path);
696  if (fd < 0) {
697  return false;
698  }
699  close(fd);
700  *path = temp_path;
701  return true;
702 }
703 
704 bool capture_current_parser_snapshot(paramschema::SnapshotResult* snapshot)
705 {
706  *snapshot = paramschema::snapshot_schema_state(paramschema::openCARP_schema());
707  print_lines(stderr, "parameter compare warning: ", snapshot->warnings);
708  if (!snapshot->errors.empty()) {
709  print_lines(stderr, "parameter compare error: ", snapshot->errors);
710  return false;
711  }
712  return true;
713 }
714 
715 void maybe_force_test_mismatch(paramschema::SnapshotResult* snapshot)
716 {
717  const char* raw = std::getenv("OPENCARP_PARAM_TEST_FORCE_MISMATCH");
718  if (raw == NULL) {
719  return;
720  }
721 
722  const std::string normalized = to_lower_ascii(trim_copy(raw));
723  if (normalized.empty() || normalized == "0" || normalized == "off" || normalized == "false" ||
724  normalized == "no") {
725  return;
726  }
727 
728  if (!snapshot->entries.empty()) {
729  snapshot->entries[0].value += "__forced_parser_compare_mismatch__";
730  return;
731  }
732 
733  paramschema::SnapshotEntry entry;
734  entry.path = "buildinfo";
735  entry.value = "__forced_parser_compare_mismatch__";
736  snapshot->entries.push_back(entry);
737 }
738 
739 LegacySnapshotHelperRunResult run_legacy_snapshot_helper(const std::string& helper_path,
740  const std::vector<std::string>& runtime_args,
741  const std::string& output_path)
742 {
743  LegacySnapshotHelperRunResult result;
744  std::vector<std::string> child_values;
745  child_values.reserve(runtime_args.size() + 4);
746  child_values.push_back(helper_path);
747  child_values.push_back("--snapshot-out");
748  child_values.push_back(output_path);
749  child_values.push_back("--");
750  child_values.insert(child_values.end(), runtime_args.begin(), runtime_args.end());
751 
752  std::vector<char*> child_argv;
753  child_argv.reserve(child_values.size() + 1);
754  for (std::size_t i = 0; i < child_values.size(); ++i) {
755  child_argv.push_back(const_cast<char*>(child_values[i].c_str()));
756  }
757  child_argv.push_back(NULL);
758 
759  const pid_t pid = fork();
760  if (pid < 0) {
761  std::perror("fork");
762  return result;
763  }
764 
765  if (pid == 0) {
766  execv(helper_path.c_str(), child_argv.data());
767  std::perror("execv");
768  _exit(127);
769  }
770 
771  int status = 0;
772  if (waitpid(pid, &status, 0) < 0) {
773  std::perror("waitpid");
774  return result;
775  }
776 
777  if (WIFSIGNALED(status)) {
778  result.signaled = true;
779  result.signal = WTERMSIG(status);
780  return result;
781  }
782 
783  if (WIFEXITED(status)) {
784  result.exited = true;
785  result.exit_status = WEXITSTATUS(status);
786  }
787 
788  return result;
789 }
790 
791 bool legacy_snapshot_helper_rejected_input(const LegacySnapshotHelperRunResult& result)
792 {
793  return result.exited && (result.exit_status == 3 || result.exit_status == 4);
794 }
795 
796 bool legacy_fallback_requested(const RuntimeCompatOptions& compat)
797 {
798  return compat.fallback_mode == ParserFallbackMode::Legacy ||
799  parser_fallback_mode() == ParserFallbackMode::Legacy;
800 }
801 
802 void print_legacy_fallback_workaround()
803 {
804  std::fprintf(stderr,
805  "parameter compare error: rerun with OPENCARP_PARAM_FALLBACK=legacy to continue with the legacy parameter state\n");
806  std::fprintf(stderr,
807  "parameter compare error: or add --param-fallback=legacy to the openCARP command line\n");
808 }
809 
810 bool restore_legacy_snapshot_state(const paramschema::SnapshotResult& legacy_snapshot)
811 {
812  const paramschema::SnapshotRestoreResult restored =
813  paramschema::restore_snapshot_state(paramschema::openCARP_schema(), legacy_snapshot);
814  print_lines(stderr, "parameter compare warning: ", restored.warnings);
815  if (!restored.errors.empty()) {
816  print_lines(stderr, "parameter compare error: ", restored.errors);
817  return false;
818  }
819  return true;
820 }
821 
822 bool run_parser_legacy_compare(const std::vector<std::string>& runtime_args,
823  const LegacyCompareInput& legacy_input,
824  const RuntimeCompatOptions& compat)
825 {
826  const ParserCompareMode mode = parser_compare_mode();
827  if (mode == ParserCompareMode::Off) {
828  return true;
829  }
830  const bool fallback_to_legacy = legacy_fallback_requested(compat);
831 
832  if (runtime_args.empty()) {
833  std::fprintf(stderr, "parameter compare error: unable to reconstruct simulator argv\n");
834  if (fallback_to_legacy) {
835  print_legacy_fallback_workaround();
836  }
837  return mode != ParserCompareMode::Strict;
838  }
839 
840  paramschema::SnapshotResult parser_snapshot;
841  if (!capture_current_parser_snapshot(&parser_snapshot)) {
842  std::fprintf(stderr, "parameter compare error: unable to snapshot the parser runtime state\n");
843  if (fallback_to_legacy) {
844  print_legacy_fallback_workaround();
845  }
846  return mode != ParserCompareMode::Strict;
847  }
848  maybe_force_test_mismatch(&parser_snapshot);
849 
850  std::string helper_path;
851  if (!resolve_legacy_snapshot_helper(runtime_args[0], &helper_path)) {
852  std::fprintf(stderr, "parameter compare error: unable to locate param-parser-legacy-snapshot\n");
853  if (fallback_to_legacy) {
854  print_legacy_fallback_workaround();
855  } else {
856  std::fprintf(stderr,
857  "parameter compare error: rerun with OPENCARP_PARAM_COMPARE=0 to bypass this temporary compatibility gate\n");
858  }
859  return mode != ParserCompareMode::Strict;
860  }
861 
862  if (!legacy_input.available) {
863  if (legacy_input.warn_when_unavailable && !legacy_input.unavailable_reason.empty()) {
864  std::fprintf(stderr, "parameter compare warning: %s\n", legacy_input.unavailable_reason.c_str());
865  }
866  if (fallback_to_legacy) {
867  std::fprintf(stderr,
868  "parameter compare warning: legacy fallback is unavailable because no legacy baseline exists for this input\n");
869  }
870  return true;
871  }
872 
873  std::string snapshot_path;
874  if (!create_temp_output_path("", &snapshot_path)) {
875  std::fprintf(stderr, "parameter compare error: unable to create temporary snapshot file\n");
876  if (fallback_to_legacy) {
877  print_legacy_fallback_workaround();
878  } else {
879  std::fprintf(stderr,
880  "parameter compare error: rerun with OPENCARP_PARAM_COMPARE=0 to bypass this temporary compatibility gate\n");
881  }
882  return mode != ParserCompareMode::Strict;
883  }
884 
885  const LegacySnapshotHelperRunResult helper_result =
886  run_legacy_snapshot_helper(helper_path, legacy_input.runtime_args, snapshot_path);
887  const bool helper_ok = helper_result.exited && helper_result.exit_status == 0;
888  paramschema::SnapshotResult legacy_snapshot;
889  std::string read_error;
890  const bool loaded = helper_ok &&
891  paramschema::snapshotio::read_snapshot_file(snapshot_path, &legacy_snapshot, &read_error);
892  unlink(snapshot_path.c_str());
893 
894  if (!loaded) {
895  if (legacy_snapshot_helper_rejected_input(helper_result)) {
896  std::fprintf(stderr,
897  "parameter compare error: legacy param() rejected the normalized input while the parser runtime accepted it\n");
898  std::fprintf(stderr,
899  "parameter compare error: this indicates a parser/legacy validation mismatch, not snapshot helper infrastructure\n");
900  std::fprintf(stderr,
901  "parameter compare error: please open an issue and include the triggering command line and parameter files\n");
902  std::fprintf(stderr,
903  "https://git.opencarp.org/openCARP/openCARP/-/issues/new?type=ISSUE&initialCreationContext=list-route\n");
904  if (fallback_to_legacy) {
905  std::fprintf(stderr,
906  "parameter compare error: legacy fallback is unavailable because legacy produced no valid parameter state\n");
907  }
908  } else if (!read_error.empty()) {
909  std::fprintf(stderr, "parameter compare error: %s\n", read_error.c_str());
910  } else if (helper_result.signaled) {
911  std::fprintf(stderr, "parameter compare error: legacy snapshot helper terminated with signal %d\n",
912  helper_result.signal);
913  } else if (helper_result.exited) {
914  std::fprintf(stderr, "parameter compare error: legacy snapshot helper exited with status %d\n",
915  helper_result.exit_status);
916  } else {
917  std::fprintf(stderr, "parameter compare error: unable to capture the legacy parameter state\n");
918  }
919  if (fallback_to_legacy && !legacy_snapshot_helper_rejected_input(helper_result)) {
920  print_legacy_fallback_workaround();
921  } else if (!legacy_snapshot_helper_rejected_input(helper_result)) {
922  std::fprintf(stderr,
923  "parameter compare error: rerun with OPENCARP_PARAM_COMPARE=0 to bypass this temporary compatibility gate\n");
924  }
925  return mode != ParserCompareMode::Strict;
926  }
927 
928  const paramschema::SnapshotComparisonResult comparison =
929  paramschema::compare_snapshot_results(paramschema::openCARP_schema(), parser_snapshot, legacy_snapshot);
930  if (!comparison.errors.empty() || !comparison.mismatches.empty()) {
931  print_lines(stderr, "", paramschema::format_snapshot_comparison_report(comparison, "parser compare"));
932  std::fprintf(stderr,
933  "parameter compare error: the parser runtime and legacy param() produced different parameter states\n");
934  std::fprintf(stderr,
935  "parameter compare error: please open an issue and include the triggering command line and parameter files\n");
936  std::fprintf(stderr,
937  "https://git.opencarp.org/openCARP/openCARP/-/issues/new?type=ISSUE&initialCreationContext=list-route\n");
938 
939  if (fallback_to_legacy) {
940  if (!restore_legacy_snapshot_state(legacy_snapshot)) {
941  print_legacy_fallback_workaround();
942  return false;
943  }
944  clear_active_citation_suggestions();
945  std::fprintf(stderr, "parameter compare warning: continuing with the legacy parameter state\n");
946  return true;
947  }
948 
949  print_legacy_fallback_workaround();
950  return mode != ParserCompareMode::Strict;
951  }
952 
953  return true;
954 }
955 
956 } // namespace
957 
958 static char input_dir[1024], // directory from which to read input
959  output_dir[1024], // directory to which to write results
960  postproc_dir[1024], // postprocessing directory
961  current_dir[1024]; // current directory
962 
963 namespace {
964 
965 void localize_gridout_nodes(sf_mesh& mesh,
966  SF::vector<mesh_int_t>& selected_nodes,
967  SF::vector<mesh_int_t>& output_idx,
968  bool async)
969 {
970  const int mpi_rank = get_rank();
971  const int mpi_size = get_size();
972  const SF::vector<mesh_int_t>& nbr = mesh.get_numbering(SF::NBR_SUBMESH);
973 
974  for(mesh_int_t& node : selected_nodes)
975  node = nbr[node];
976 
977  binary_sort(selected_nodes);
978  unique_resize(selected_nodes);
979 
980  output_idx.resize(0);
981 
982  if(async) {
983  const SF::vector<mesh_int_t>& alg_nod = mesh.pl.algebraic_nodes();
985  alg_map.reserve(alg_nod.size());
986 
987  for(mesh_int_t local_node : alg_nod)
988  alg_map[nbr[local_node]] = local_node;
989 
990  SF::vector<mesh_int_t> recv_nodes;
991  size_t buffsize = 0;
992  for(int pid = 0; pid < mpi_size; pid++) {
993  if(mpi_rank == pid) {
994  recv_nodes = selected_nodes;
995  buffsize = recv_nodes.size();
996  }
997 
998  MPI_Bcast(&buffsize, sizeof(size_t), MPI_BYTE, pid, mesh.comm);
999  recv_nodes.resize(buffsize);
1000  MPI_Bcast(recv_nodes.data(), buffsize * sizeof(mesh_int_t), MPI_BYTE, pid, mesh.comm);
1001 
1002  for(mesh_int_t node : recv_nodes) {
1003  auto it = alg_map.find(node);
1004  if(it != alg_map.end())
1005  output_idx.push_back(it->second);
1006  }
1007  }
1008  } else {
1009  const SF::vector<mesh_int_t>& layout = mesh.pl.algebraic_layout();
1010  const mesh_int_t start = layout[mpi_rank];
1011  const mesh_int_t stop = layout[mpi_rank + 1];
1012 
1013  SF::vector<mesh_int_t> recv_nodes;
1014  size_t buffsize = 0;
1015  for(int pid = 0; pid < mpi_size; pid++) {
1016  if(mpi_rank == pid) {
1017  recv_nodes = selected_nodes;
1018  buffsize = recv_nodes.size();
1019  }
1020 
1021  MPI_Bcast(&buffsize, sizeof(size_t), MPI_BYTE, pid, mesh.comm);
1022  recv_nodes.resize(buffsize);
1023  MPI_Bcast(recv_nodes.data(), buffsize * sizeof(mesh_int_t), MPI_BYTE, pid, mesh.comm);
1024 
1025  for(mesh_int_t node : recv_nodes) {
1026  if(node >= start && node < stop)
1027  output_idx.push_back(node - start);
1028  }
1029  }
1030  }
1031 
1032  binary_sort(output_idx);
1033  unique_resize(output_idx);
1034 }
1035 
1036 bool mesh_has_gridout_tags(const sf_mesh& mesh,
1037  const hashmap::unordered_set<int>& output_tags)
1038 {
1039  bool local_has_tag = false;
1040  for(size_t eidx = 0; eidx < mesh.l_numelem; eidx++) {
1041  if(output_tags.count(mesh.tag[eidx]) != 0) {
1042  local_has_tag = true;
1043  break;
1044  }
1045  }
1046 
1047  int local = local_has_tag ? 1 : 0;
1048  int global = 0;
1049  MPI_Allreduce(&local, &global, 1, MPI_INT, MPI_MAX, mesh.comm);
1050  return global != 0;
1051 }
1052 
1053 void extract_gridout_tag_mesh(const sf_mesh& mesh,
1054  const hashmap::unordered_set<int>& output_tags,
1055  sf_mesh& out_mesh)
1056 {
1057  SF::vector<bool> keep_elem(mesh.l_numelem, false);
1058  for(size_t eidx = 0; eidx < mesh.l_numelem; eidx++)
1059  keep_elem[eidx] = output_tags.count(mesh.tag[eidx]) != 0;
1060 
1061  out_mesh.name = mesh.name;
1062  extract_mesh(keep_elem, mesh, out_mesh);
1063  if(out_mesh.g_numelem == 0) {
1064  log_msg(0, 5, ECHO, "gridout_tags selected no elements in \"%s\".", mesh.name.c_str());
1065  EXIT(EXIT_FAILURE);
1066  }
1067 
1068  rebalance_mesh(out_mesh);
1069 
1070  SF::vector<mesh_real_t> pts(mesh.xyz);
1071  SF::vector<mesh_int_t> ptsidx(mesh.get_numbering(SF::NBR_REF));
1072  std::list<sf_mesh*> meshlist;
1073  meshlist.push_back(&out_mesh);
1074  insert_points(pts, ptsidx, meshlist);
1075 
1077  numbering(out_mesh);
1078  out_mesh.generate_par_layout();
1079 }
1080 
1084 void extract_gridout_surface_mesh(const sf_mesh& mesh, sf_mesh& out_mesh)
1085 {
1086  compute_surface_mesh(mesh, SF::NBR_REF, out_mesh);
1087  out_mesh.name = mesh.name;
1088 
1089  // the surface connectivity refers to the nodes of the parent mesh. Turn it into a
1090  // node numbering of its own, so that the surface has points independent of the parent.
1091  out_mesh.localize(SF::NBR_REF);
1092 
1093  // surface elements are created here and carry no reference numbering of their own
1094  SF::vector<mesh_int_t>& ref_eidx = out_mesh.register_numbering(SF::NBR_ELEM_REF);
1095  ref_eidx.resize(out_mesh.l_numelem);
1096 
1097  long int num_local = out_mesh.l_numelem, offset = 0;
1098  MPI_Exscan(&num_local, &offset, 1, MPI_LONG, MPI_SUM, out_mesh.comm);
1099  if(get_rank() == 0) offset = 0;
1100  for(size_t eidx = 0; eidx < out_mesh.l_numelem; eidx++)
1101  ref_eidx[eidx] = offset + eidx;
1102 
1103  SF::vector<mesh_real_t> pts(mesh.xyz);
1104  SF::vector<mesh_int_t> ptsidx(mesh.get_numbering(SF::NBR_REF));
1105  std::list<sf_mesh*> meshlist;
1106  meshlist.push_back(&out_mesh);
1107  insert_points(pts, ptsidx, meshlist);
1108 
1110  numbering(out_mesh);
1111  out_mesh.generate_par_layout();
1112 }
1113 
1117 void write_gridout_surface(const sf_mesh& mesh, const std::string& basename)
1118 {
1119  sf_mesh surf;
1120  extract_gridout_surface_mesh(mesh, surf);
1121 
1122  write_points_parallel(surf, false, basename);
1123 
1124  sf_mesh surf_global = surf;
1125  const SF::vector<mesh_int_t>& nbr = surf.get_numbering(SF::NBR_SUBMESH);
1126  for(size_t i = 0; i < surf_global.con.size(); i++)
1127  surf_global.con[i] = nbr[surf_global.con[i]];
1128 
1129  write_surface(surf_global, basename + ".surf");
1130 }
1131 
1132 } // namespace
1133 
1134 void parse_params_cpy(int argc, char** argv)
1135 {
1136  LegacyCompareInput legacy_input;
1137  std::vector<std::string> normalized_args;
1138  RuntimeCompatOptions compat;
1139  std::string normalize_error;
1140  if (!build_legacy_compare_input(argc, argv, &legacy_input, &normalize_error) ||
1141  !normalize_runtime_args(argc, argv, &normalized_args, &compat, &normalize_error)) {
1142  fprintf(stderr, "\n*** %s\n\n", normalize_error.c_str());
1143  exit(EXIT_FAILURE);
1144  }
1145 
1146  paramschema::ExecutionResult executed;
1147  if (!apply_parser_runtime_args(normalized_args, true, &executed)) {
1148  exit(EXIT_FAILURE);
1149  }
1150 
1151  if (legacy_input.available) {
1152  for (std::size_t i = 0; i < executed.validation.assignments.size(); ++i) {
1153  if (!executed.validation.assignments[i].synthesized) {
1154  continue;
1155  }
1156  legacy_input.available = false;
1157  legacy_input.runtime_args.clear();
1158  legacy_input.runtime_args.push_back(normalized_args[0]);
1159  legacy_input.unavailable_reason =
1160  "legacy compare is unavailable because the parser inferred optional controller counts from the original input";
1161  break;
1162  }
1163  }
1164 
1165  if (legacy_input.available && !executed.validation.legacy_compare_incompatible_paths.empty()) {
1166  legacy_input.available = false;
1167  legacy_input.warn_when_unavailable = false;
1168  legacy_input.runtime_args.clear();
1169  legacy_input.runtime_args.push_back(normalized_args[0]);
1170  legacy_input.unavailable_reason =
1171  "legacy compare is unavailable because the original input uses aggregate ID syntax without a legacy baseline for " +
1172  join_paths_for_message(executed.validation.legacy_compare_incompatible_paths);
1173  }
1174 
1175  if (!run_parser_legacy_compare(normalized_args, legacy_input, compat)) {
1176  exit(EXIT_FAILURE);
1177  }
1178 }
1179 
1180 
1182 {
1183  // here all the physics can be registered to the physics registry
1184  // then they should be processed automatically
1187  if(phys_defined(PHYSREG_EMI)) {
1188 #if WITH_EMI_MODEL
1189  user_globals::physics_reg[emi_phys] = new EMI();
1190 #else
1191  log_msg(NULL, 5, ECHO, "The EMI model was not compiled for this binary.\n");
1192  exit(EXIT_FAILURE);
1193 #endif
1194  }
1195 
1198 
1201 }
1202 
1204 {
1205  log_msg(0,0,0, "\n *** Initializing physics ***\n");
1206 
1207  //load in the external imp modules
1208 #ifdef HAVE_DLOPEN
1209  for (int ii = 0; ii < param_globals::num_external_imp; ii++) {
1210  int loading_succeeded = limpet::load_ionic_module(param_globals::external_imp[ii]);
1211  assert(loading_succeeded);
1212  }
1213 #else
1214  if(param_globals::num_external_imp)
1215  log_msg(NULL, 4, ECHO,"Loading of external LIMPET modules not enabled.\n"
1216  "Recompile with DLOPEN set.\n" );
1217 #endif
1218 
1219  // init physics via Basic_physic interface
1220  for(auto it : user_globals::physics_reg) {
1221  Basic_physic* p = it.second;
1222  log_msg(NULL, 0, 0, "Initializing %s ..", p->name);
1223  p->initialize();
1224  }
1225 }
1226 
1228 {
1229  log_msg(0,0,0, "\n *** Destroying physics ***\n");
1230 
1231  for(auto it : user_globals::physics_reg) {
1232  Basic_physic* p = it.second;
1233  log_msg(NULL, 0, 0, "Destroying %s ..", p->name);
1234  p->destroy();
1235  }
1236 }
1237 
1238 // ignore_extracellular stim must be moved to stimulate.cc to be able
1239 // to use all defined set operations instead of defines
1240 
1254 void ignore_extracellular_stim(Stimulus *st, int ns, int ignore)
1255 {
1256  // needs to be switch to stim enum types defined in stimulate.h
1257  for ( int i=0; i<ns; i++ ) {
1258  int turn_off = 0;
1259  turn_off += (st[i].stimtype == Extracellular_Ground) && (ignore & NO_EXTRA_GND);
1260  turn_off += (IsExtraV(st[i])) && (ignore & NO_EXTRA_V);
1261  turn_off += (st[i].stimtype==Extracellular_I) && (ignore & NO_EXTRA_I);
1262 
1263  if (turn_off) {
1264  st[i].stimtype = Ignore_Stim;
1265  log_msg( NULL, 1, 0, "Extracellular stimulus %d ignored for monodomain", i );
1266  } else if ( st[i].stimtype==Intracellular_I ) {
1267  st[i].stimtype = Transmembrane_I;
1268  log_msg( NULL, 1, 0, "Intracellular stimulus %d converted to transmembrane", i );
1269  }
1270  }
1271 }
1272 
1280 int set_ignore_flags( int mode )
1281 {
1282  if(mode==MONODOMAIN)
1283  return STM_IGNORE_MONODOMAIN;
1284  if(mode==BIDOMAIN)
1285  return STM_IGNORE_BIDOMAIN;
1286  if(mode==PSEUDO_BIDM)
1287  return STM_IGNORE_PSEUDO_BIDM;
1288 
1289  return IGNORE_NONE;
1290 }
1291 
1292 
1302 {
1303  Stimulus* s = param_globals::stimulus;
1304 
1305  for(int i=0; i < param_globals::num_stim; i++) {
1306  if(s[i].stimtype == Extracellular_Ground ||
1307  s[i].stimtype == Extracellular_V ||
1308  s[i].stimtype == Extracellular_V_OL)
1309  return;
1310  }
1311 
1312  // for now we only warn, although we should actually stop the run
1313  log_msg( NULL, 4, 0,"Elliptic system is singular!\n"
1314  "Use an explicit ground:voltage (stimulus[X].stimtype=3)\n"
1315  "Do not trust the elliptic solution of this simulation run!\n");
1316 }
1317 
1319 {
1320  const char* real_precision = OPENCARP_REAL_BITS == 32 ? "single" : "double";
1321  printf("\n""*** GIT tag: %s\n", GIT_COMMIT_TAG);
1322  printf( "*** GIT hash: %s\n", GIT_COMMIT_HASH);
1323  printf( "*** GIT repo: %s\n", GIT_PATH);
1324  printf( "*** local index bits: %d\n", OPENCARP_LOCAL_INDEX_BITS);
1325  printf( "*** global index bits: %d\n", OPENCARP_GLOBAL_INDEX_BITS);
1326  printf( "*** real precision: %s (%d-bit)\n", real_precision, OPENCARP_REAL_BITS);
1327  printf( "*** dependency commits: %s\n\n", SUBREPO_COMMITS);
1328 }
1329 
1331 {
1332  // convert time steps to milliseconds
1333  param_globals::dt /= 1000.;
1334 
1335  // check parab-solve solution method
1336  // explicit integration inverts the mass matrix directly, which is only feasible if it is lumped
1337  if(param_globals::mass_lumping == 0 && param_globals::parab_solve==0) {
1338  log_msg(0,5,0, "parab_solve = 0 (explicit) requires mass_lumping = 1. "
1339  "Either enable mass lumping or choose an implicit parab_solve method.");
1340  EXIT(1);
1341  }
1342 
1343  // check if we have to modify stimuli based on used bidomain setting
1344  if(!param_globals::extracell_monodomain_stim)
1345  ignore_extracellular_stim(param_globals::stimulus, param_globals::num_stim,
1346  set_ignore_flags(param_globals::bidomain));
1347 
1348  // check nullspace if necessary
1349  // if((param_globals::bidomain==BIDOMAIN) ||
1350  // (param_globals::bidomain==PSEUDO_BIDM))
1351  // check_nullspace_ok();
1352 
1353  if(param_globals::t_sentinel > 0 && param_globals::sentinel_ID < 0 ) {
1354  log_msg(0,4,0, "Warning: t_sentinel is set but no sentinel_ID has been specified; check_quiescence() behavior may not be as expected");
1355  }
1356 
1357  if(param_globals::num_external_imp > 0 ) {
1358  for(int ext_imp_i = 0; ext_imp_i < param_globals::num_external_imp; ext_imp_i++) {
1359  if(param_globals::external_imp[ext_imp_i][0] != '/') {
1360  log_msg(0,5,0, "external_imp[%d] error: absolute paths must be used for .so file loading (\'%s\')",
1361  ext_imp_i, param_globals::external_imp[ext_imp_i]);
1362  EXIT(1);
1363  }
1364  }
1365  }
1366 
1367  if(param_globals::experiment == EXP_LAPLACE && param_globals::bidomain != 1) {
1368  log_msg(0,4,0, "Warning: Laplace experiment mode requires bidomain = 1. Setting bidomain = 1.");
1369  param_globals::bidomain = 1;
1370  }
1371 
1372  if(param_globals::num_phys_regions == 0) {
1373  log_msg(0,4,0, "Warning: No physics region defined! Please set phys_region parameters to correctly define physics.");
1374 
1375  if(param_globals::experiment != EXP_LAPLACE) {
1376  log_msg(0,4,0, "Intra-elec and Extra-elec domains will be derived from fibers.\n");
1377  param_globals::num_phys_regions = param_globals::bidomain ? 2 : 1;
1378  param_globals::phys_region = (p_region*) calloc(param_globals::num_phys_regions, sizeof(p_region));
1379  param_globals::phys_region[0].ptype = PHYSREG_INTRA_ELEC;
1380  param_globals::phys_region[0].name = strdup("Autogenerated intracellular Electrics");
1381  param_globals::phys_region[0].num_IDs = 0;
1382 
1383  if(param_globals::bidomain) {
1384  param_globals::phys_region[1].ptype = PHYSREG_EXTRA_ELEC;
1385  param_globals::phys_region[1].name = strdup("Autogenerated extracellular Electrics");
1386  param_globals::phys_region[1].num_IDs = 0;
1387  }
1388  } else {
1389  log_msg(0,4,0, "Laplace domain will be derived from fibers.\n");
1390  param_globals::num_phys_regions = 1;
1391  param_globals::phys_region = (p_region*) calloc(param_globals::num_phys_regions, sizeof(p_region));
1392  param_globals::phys_region[0].ptype = PHYSREG_LAPLACE;
1393  param_globals::phys_region[0].name = strdup("Autogenerated Laplace");
1394  param_globals::phys_region[0].num_IDs = 0;
1395  }
1396  }
1397 
1398  if(param_globals::experiment == EXP_LAPLACE && !phys_defined(PHYSREG_LAPLACE)) {
1399  log_msg(0,4,0, "Warning: Laplace experiment mode requires a laplace physics regions defined.");
1400 
1401  int idx = -1;
1402  if((idx = get_phys_index(PHYSREG_EXTRA_ELEC)) > -1) {
1403  log_msg(0,4,0, "Converting the defined extracellular-electrics-region to laplace-region.");
1404  param_globals::phys_region[idx].ptype = PHYSREG_LAPLACE;
1405  } else if ((idx = get_phys_index(PHYSREG_INTRA_ELEC)) > -1) {
1406  log_msg(0,4,0, "Converting the defined intracellular-electrics-region to laplace-region.");
1407  param_globals::phys_region[idx].ptype = PHYSREG_LAPLACE;
1408  } else {
1409  param_globals::num_phys_regions += 1;
1410  param_globals::phys_region = (p_region*) realloc(param_globals::phys_region, param_globals::num_phys_regions * sizeof(p_region));
1411 
1412  param_globals::phys_region[param_globals::num_phys_regions - 1].ptype = PHYSREG_LAPLACE;
1413  param_globals::phys_region[param_globals::num_phys_regions - 1].name = strdup("Autogenerated Laplace");
1414  param_globals::phys_region[param_globals::num_phys_regions - 1].num_IDs = 0;
1415  }
1416  }
1417 
1418 #ifndef WITH_PARMETIS
1419  if(param_globals::pstrat == 1) {
1420  log_msg(0,3,0, "openCARP was built without Parmetis support. Swithing to KDtree.");
1421  param_globals::pstrat = 2;
1422  }
1423 #endif
1424 
1425  // check if we have the legacy stimuli or the new stimuli defined by the user
1426  bool legacy_stim_set = false, new_stim_set = false;
1427 
1428  for(int i=0; i<param_globals::num_stim; i++) {
1429  Stimulus & legacy_stim = param_globals::stimulus[i];
1430  Stim & new_stim = param_globals::stim[i];
1431 
1432  if(legacy_stim.stimtype || legacy_stim.strength)
1433  legacy_stim_set = true;
1434 
1435  if(new_stim.crct.type || new_stim.pulse.strength)
1436  new_stim_set = true;
1437  }
1438 
1439  if(legacy_stim_set || new_stim_set) {
1440  if(legacy_stim_set && new_stim_set) {
1441  log_msg(0,4,0, "Warning: Legacy stimuli and default stimuli are defined. Only default stimuli will be used!");
1442  }
1443  else if (legacy_stim_set) {
1444  log_msg(0,1,0, "Warning: Legacy stimuli defined. Please consider switching to stimulus definition \"stim[]\"!");
1446  }
1447  }
1448  else {
1449  log_msg(0,4,0, "Warning: No potential or current stimuli found!");
1450  }
1451 }
1452 
1453 void set_io_dirs(char *sim_ID, char *pp_ID, IO_t init)
1454 {
1455  int flg = 0, err = 0, rank = get_rank();
1456 
1457  char *ptr = getcwd(current_dir, 1024);
1458  if (ptr == NULL) err++;
1459  ptr = getcwd(input_dir, 1024);
1460  if (ptr == NULL) err++;
1461  //if (param_globals::experiment == 4 && post_processing_opts == MECHANIC_POSTPROCESS)
1462  // { sim_ID = param_globals::ppID; param_globals::ppID = "POSTPROC_DIR"; }
1463 
1464  // output directory
1465  if (rank == 0) {
1466  if (strcmp(sim_ID, "OUTPUT_DIR")) {
1467  if (mkdir(sim_ID, 0775)) { // rwxrwxr-x
1468  if (errno == EEXIST ) {
1469  log_msg(NULL, 2, 0, "Output directory exists: %s\n", sim_ID);
1470  } else {
1471  log_msg(NULL, 5, 0, "Unable to make output directory\n");
1472  flg = 1;
1473  }
1474  }
1475  } else if (mkdir(sim_ID, 0775) && errno != EEXIST) {
1476  log_msg(NULL, 5, 0, "Unable to make output directory\n");
1477  flg = 1;
1478  }
1479  }
1480 
1481  // terminate?
1482  if(get_global(flg, MPI_SUM)) { EXIT(-1); }
1483 
1484  err += chdir(sim_ID);
1485  ptr = getcwd(output_dir, 1024);
1486  if (ptr == NULL) err++;
1487 
1488  // terminate?
1489  if(get_global(err, MPI_SUM)) { EXIT(-1); }
1490 
1491  err += chdir(output_dir);
1492 
1493  // postprocessing directory
1494  if (rank == 0 && ((param_globals::experiment==EXP_POSTPROCESS) || (param_globals::post_processing_opts & LEADFIELD))) {
1495 
1496  if (strcmp(param_globals::ppID, "POSTPROC_DIR")) {
1497  if (mkdir(param_globals::ppID, 0775)) { // rwxrwxr-x
1498  if (errno == EEXIST ) {
1499  log_msg(NULL, 2, ECHO, "Postprocessing directory exists: %s\n\n", param_globals::ppID);
1500  } else {
1501  log_msg(NULL, 5, ECHO, "Unable to make postprocessing directory\n\n");
1502  flg = 1;
1503  }
1504  }
1505  } else if (mkdir(param_globals::ppID, 0775) && errno != EEXIST) {
1506  log_msg(NULL, 5, ECHO, "Unable to make postprocessing directory\n\n");
1507  flg = 1;
1508  }
1509 
1510  }
1511 
1512  if(get_global(flg, MPI_SUM)) { EXIT(-1); }
1513 
1514  err += chdir(param_globals::ppID);
1515  ptr = getcwd(postproc_dir, 1024);
1516  if (ptr == NULL) err++;
1517  err = chdir(output_dir);
1518  if(get_global(err, MPI_SUM)) { EXIT(-1); }
1519 
1520  err = set_dir(init);
1521  if(get_global(err, MPI_SUM)) { EXIT(-1); }
1522 }
1523 
1524 bool setup_IO(int argc, char **argv)
1525 {
1526  bool io_node = false;
1527  int psize = get_size(), prank = get_rank();
1528 
1529  if (param_globals::num_io_nodes > 0) {
1530  // Can't do async IO with only one core
1531  if (get_size() == 1) {
1532  log_msg(NULL, 5, 0, "You cannot run with async IO on only one core.\n");
1533  EXIT(EXIT_FAILURE);
1534  }
1535  // Can't do async IO with more IO cores than compute cores
1536  if (2 * param_globals::num_io_nodes >= psize) {
1537  log_msg(NULL, 5, 0, "The number of IO cores be less " "than the number of compute cores.");
1538  EXIT(EXIT_FAILURE);
1539  }
1540 #if 0
1541  if (param_globals::num_PS_nodes && param_globals::num_io_nodes > param_globals::num_PS_nodes) {
1542  LOG_MSG(NULL, 5, 0,
1543  "The number of IO cores (%d) should not "
1544  "exceed the number of PS compute cores (%d).\n",
1545  param_globals::num_io_nodes, param_globals::num_PS_nodes);
1546  EXIT(-1);
1547  }
1548 #endif
1549  // root IO node is global node 0
1550  io_node = prank < param_globals::num_io_nodes;
1551 
1552  MPI_Comm comm;
1553  MPI_Comm_split(PETSC_COMM_WORLD, io_node, get_rank(), &comm);
1554  MPI_Comm_set_name(comm, io_node ? "IO" : "compute");
1555 
1556  PETSC_COMM_WORLD = comm; // either the compute world or IO world
1557 
1558  prank = get_rank();
1559 
1560  MPI_Intercomm_create(comm, 0, MPI_COMM_WORLD, io_node ? param_globals::num_io_nodes : 0,
1562 
1563  if(prank != get_rank(user_globals::IO_Intercomm))
1564  log_msg(NULL, 4, 0, "Global node %d, Comm rank %d != Intercomm rank %d\n",
1565  get_rank(MPI_COMM_WORLD), get_rank(PETSC_COMM_WORLD),
1567  } else
1568  MPI_Comm_set_name(PETSC_COMM_WORLD, "compute");
1569 
1570  set_io_dirs(param_globals::simID, param_globals::ppID, OUTPUT);
1571 
1572  if((io_node || !param_globals::num_io_nodes) && !prank)
1573  output_parameter_file("parameters.par", argc, argv);
1574 
1575  return io_node;
1576 }
1578 {
1579  getcwd(current_dir, 1024);
1580 }
1581 
1582 int set_dir(IO_t dest)
1583 {
1584  int err;
1585 
1586  if (dest==OUTPUT) err = chdir(output_dir);
1587  else if (dest==POSTPROC) err = chdir(postproc_dir);
1588  else if (dest==CURDIR) err = chdir(current_dir);
1589  else err = chdir(input_dir);
1590 
1591  return err;
1592 }
1593 
1595 {
1596  // if we restart from a checkpoint, the timer_manager will be notified at a later stage
1597  double start_time = 0.0;
1598  user_globals::tm_manager = new timer_manager(param_globals::dt, start_time, param_globals::tend);
1599 
1600  double end_time = param_globals::tend;
1602 
1603  if(param_globals::experiment == EXP_LAPLACE) {
1604  tm.initialize_singlestep_timer(tm.time, 0, iotm_console, "IO (console)", nullptr);
1605  tm.initialize_singlestep_timer(tm.time, 0, iotm_state_var, "IO (state vars)", nullptr);
1606  tm.initialize_singlestep_timer(tm.time, 0, iotm_spacedt, "IO (spacedt)", nullptr);
1607  }
1608  else {
1609  tm.initialize_eq_timer(tm.time, end_time, 0, param_globals::timedt, 0, iotm_console, "IO (console)");
1610  tm.initialize_eq_timer(tm.time, end_time, 0, param_globals::spacedt, 0, iotm_state_var, "IO (state vars)");
1611  tm.initialize_eq_timer(tm.time, end_time, 0, param_globals::spacedt, 0, iotm_spacedt, "IO (spacedt)");
1612  }
1613 
1614  if(param_globals::num_tsav) {
1615  std::vector<double> trig(param_globals::num_tsav);
1616  for(size_t i=0; i<trig.size(); i++) trig[i] = param_globals::tsav[i];
1617 
1618  tm.initialize_neq_timer(trig, 0, iotm_chkpt_list, "instance checkpointing");
1619  }
1620 
1621  if(param_globals::chkpt_intv)
1622  tm.initialize_eq_timer(param_globals::chkpt_start, param_globals::chkpt_stop, 0,
1623  param_globals::chkpt_intv, 0, iotm_chkpt_intv, "interval checkpointing");
1624 
1625  if(param_globals::num_trace)
1626  tm.initialize_eq_timer(tm.time, end_time, 0, param_globals::tracedt, 0, iotm_trace, "IO (node trace)");
1627 }
1628 
1629 #ifdef WITH_POWERCAPPING
1630 void basic_powercapping_setup()
1631 {
1632  user_globals::pc_manager = new powercapping_manager(param_globals::powcap_backend, param_globals::powcap_backend_policy, param_globals::powcap_metrics_backend, param_globals::powcap_policy);
1633 }
1634 
1635 void basic_powercapping_cleanup()
1636 {
1637  /* Must be called explicitly to ensure the orderly termination of potential
1638  * background daemons */
1639  delete user_globals::pc_manager; user_globals::pc_manager = nullptr;
1640 }
1641 #endif
1642 
1643 void get_protocol_column_widths(std::vector<int> & col_width, std::vector<int> & used_timer_ids)
1644 {
1645  char buff[256];
1646  const short padding = 4;
1647  Electrics* elec = (Electrics*) get_physics(elec_phys, false);
1648 
1649  do {
1650  snprintf(buff, sizeof buff, "%.3lf", user_globals::tm_manager->time);
1651  if(col_width[0] < int(strlen(buff)+padding))
1652  col_width[0] = strlen(buff)+padding;
1653 
1654  snprintf(buff, sizeof buff, "%.3ld", user_globals::tm_manager->d_time);
1655  if(col_width[1] < int(strlen(buff)+padding))
1656  col_width[1] = strlen(buff)+padding;
1657 
1658  int col = 2;
1659  for (size_t tid = 0; tid < used_timer_ids.size(); tid++)
1660  {
1661  int timer_id = used_timer_ids[tid];
1662  base_timer* t = user_globals::tm_manager->timers[timer_id];
1663 
1664  if(t->d_trigger_dur && elec) {
1665  // figure out value of signal linked to this timer
1666  double val = 0.;
1667 
1668  // determine timer linked to which physics, for now we deal with electrics only
1669  val = elec->timer_val(timer_id);
1670 
1671  snprintf(buff, sizeof buff, "%.3lf", val);
1672  if(col_width[col] < int(strlen(buff)+padding))
1673  col_width[col] = strlen(buff)+padding;
1674  }
1675  col++;
1676  }
1677 
1678  // advance time
1680  } while (!user_globals::tm_manager->elapsed());
1681 
1683 }
1686 int plot_protocols(const char *fname)
1687 {
1688  int err = {0};
1689  std::ofstream fh;
1690  const char* smpl_endl = "\n";
1691 
1692  if(!get_rank()) {
1693  fh.open(fname);
1694 
1695  // If we couldn't open the output file stream for writing
1696  if (!fh) {
1697  // Print an error and exit
1698  log_msg(0,5,0,"Protocol file %s could not be opened for writing!\n", fname);
1699  err = -1;
1700  }
1701  }
1702 
1703  // broadcast and return if err
1704  if(get_global(err, MPI_SUM))
1705  return err;
1706 
1707  // only rank 0 writes
1708  if(!get_rank()) {
1709 
1710  // collect timer information, label, short label, unit
1711  std::vector<std::string> col_labels = {"time", "tick"};
1712  std::vector<std::string> col_short_labels = {"A", "B"};
1713  std::vector<std::string> col_unit_labels = {"ms", "--" };
1714  std::vector<int> col_width = {4, 4};
1715 
1716  char c_label = {'C'};
1717  std::string label = {""};
1718  std::string unit = {""};
1719 
1720  // here we store the IDs of the timers that we care about. currently this are the IO and TS timers
1721  // and the electricts timers
1722  std::vector<int> used_timer_ids;
1723  std::vector<int> used_stim_ids;
1724 
1725  Electrics* elec = (Electrics*) get_physics(elec_phys, false);
1726  if(elec) {
1727  int sidx = 0;
1728  for(const stimulus & s : elec->stimuli) {
1729  if(s.ptcl.timer_id > -1) {
1731  if(t) {
1732  used_timer_ids.push_back(s.ptcl.timer_id);
1733  used_stim_ids.push_back(sidx);
1734  }
1735  }
1736 
1737  sidx++;
1738  }
1739  }
1740 
1741  // determine longest timer label
1742  int mx_llen = 0;
1743  for (size_t tid = 0; tid < used_timer_ids.size(); tid++)
1744  {
1745  int timer_id = used_timer_ids[tid];
1746  base_timer* t = user_globals::tm_manager->timers[timer_id];
1747 
1748  int llen = strlen(t->name);
1749  mx_llen = llen > mx_llen ? llen : mx_llen;
1750  }
1751 
1752  for (size_t tid = 0; tid < used_timer_ids.size(); tid++)
1753  {
1754  int timer_id = used_timer_ids[tid];
1755  base_timer* t = user_globals::tm_manager->timers[timer_id];
1756 
1757  col_labels.push_back(t->name);
1758  label = c_label;
1759  col_short_labels.push_back(label);
1760 
1761  if(elec) {
1762  // search physics for signals linked to timer
1763  unit = elec->timer_unit(timer_id);
1764  if(unit.empty()) unit = "--";
1765  col_unit_labels.push_back(unit);
1766  col_width.push_back(4);
1767  }
1768  c_label++;
1769  }
1770 
1771  get_protocol_column_widths(col_width, used_timer_ids);
1772 
1773  // print header + legend first
1774  fh << "# Protocol header\n#\n" << "# Legend:\n";
1775  for(size_t i = 0; i<col_short_labels.size(); i++)
1776  {
1777  fh << "#" << std::setw(2) << col_short_labels[i] << " = " << std::setw(mx_llen) << col_labels[i];
1778  fh << " [" << std::setw(10) << col_unit_labels[i] << "]";
1779 
1780  if(i >= 2 && used_stim_ids[i-2] > -1) {
1781  stimulus & s = elec->stimuli[used_stim_ids[i-2]];
1782 
1783  if (is_potential(s.phys.type)) {
1784  if(s.phys.type == GND_ex)
1785  fh << " ground stim" << smpl_endl;
1786  else
1787  fh << " applied: " << std::to_string(s.pulse.strength) << smpl_endl;
1788  } else {
1789  fh << smpl_endl;
1790  }
1791  } else {
1792  fh << smpl_endl;
1793  }
1794  }
1795 
1796  // plot column short labels
1797  fh << "#";
1798  for(size_t i = 0; i<col_short_labels.size(); i++)
1799  fh << std::setw(col_width[i] - 3) << col_short_labels[i].c_str() << std::setw(3) << " ";
1800 
1801  // plot column units
1802  fh << smpl_endl << "#";
1803  for(size_t i = 0; i<col_unit_labels.size(); i++)
1804  fh << "[" << std::setw(col_width[i]-2) << col_unit_labels[i].c_str() << "]";
1805 
1806  // step through simulated time period
1807  fh << smpl_endl << std::fixed;
1808  do {
1809  // time and discrete time
1810  fh << std::setw(col_width[0]) << std::setprecision(3) << user_globals::tm_manager->time;
1811  fh << std::setw(col_width[1]) << user_globals::tm_manager->d_time;
1812 
1813  // iterate over all timers
1814  int col = 2;
1815  for (size_t tid = 0; tid < used_timer_ids.size(); tid++)
1816  {
1817  int timer_id = used_timer_ids[tid];
1818  base_timer* t = user_globals::tm_manager->timers[timer_id];
1819 
1820  // type of timer: plain trigger or trigger linked to signal
1821  if(!t->d_trigger_dur) {
1822  int On = t->triggered ? 1 : 0;
1823  fh << std::setw(col_width[col]) << On;
1824  } else if(elec) {
1825  // figure out value of signal linked to this timer
1826  double val = 0.;
1827 
1828  // determine timer linked to which physics, for now we deal with electrics only
1829  val = elec->timer_val(timer_id);
1830 
1831  fh << std::setw(col_width[col]) << std::setprecision(3) << val;
1832  }
1833  col++;
1834  }
1835 
1836  fh << smpl_endl;
1837 
1838  // advance time
1840  } while (!user_globals::tm_manager->elapsed());
1841 
1842  fh.close();
1843 
1844  // reset timer to start before actual simulation
1846  }
1847 
1848  return err;
1849 }
1850 
1852 {
1853  const char* h1_prog = "PROG\t----- \t----\t-------\t-------|";
1854  const char* h2_prog = "time\t%%comp\ttime\t ctime \t ETA |";
1855  const char* h1_wc = "\tELAPS |";
1856  const char* h2_wc = "\twc |";
1857 
1858  p.start = get_time();
1859  p.last = p.start;
1860 
1861  log_msg(NULL, 0, 0, "%s", h1_prog );
1862  log_msg(NULL, 0, NONL, "%s", h2_prog );
1863  log_msg(NULL, 0, 0, "" );
1864 }
1865 
1866 
1867 void time_to_string(float time, char* str, short str_size)
1868 {
1869  int req_hours = ((int)(time)) / 3600;
1870  int req_min = (((int)(time)) % 3600) / 60;
1871  int req_sec = (((int)(time)) % 3600) % 60;
1872 
1873  snprintf(str, str_size, "%d:%02d:%02d", req_hours, req_min, req_sec);
1874 }
1875 
1877 {
1878 
1879  float progress = 100.*(tm.time - tm.start) / (tm.end - tm.start);
1880  float elapsed_time = timing(p.curr, p.start);
1881  float req_time = (elapsed_time / progress) * (100.0f - progress);
1882 
1883  if(progress == 0.0f)
1884  req_time = 0.0f;
1885 
1886  char elapsed_time_str[256];
1887  char req_time_str[256];
1888  time_to_string(elapsed_time, elapsed_time_str, 255);
1889  time_to_string(req_time, req_time_str, 255);
1890 
1891  log_msg( NULL, 0, NONL, "%.2f\t%.1f\t%.1f\t%s\t%s",
1892  tm.time,
1893  progress,
1894  (float)(p.curr - p.last),
1895  elapsed_time_str,
1896  req_time_str);
1897 
1898  p.last = p.curr;
1899 
1900  // we add an empty string for newline and flush
1901  log_msg( NULL, 0, ECHO | FLUSH, "");
1902 }
1903 
1904 void simulate()
1905 {
1906  // here we want to include all time dependent physics, to check if we have any of those
1907  bool have_timedependent_phys = (phys_defined(PHYSREG_INTRA_ELEC) || phys_defined(PHYSREG_EIKONAL) || phys_defined(PHYSREG_EMI));
1908 
1909  if(!have_timedependent_phys) {
1910  log_msg(0,0,0, "\n no time-dependent physics region registered, skipping simulate loop..\n");
1911  return;
1912  }
1913 
1914  log_msg(0,0,0, "\n *** Launching simulation ***\n");
1915 
1916  set_dir(OUTPUT);
1917 
1918  if(param_globals::dump_protocol)
1919  plot_protocols("protocol.trc");
1920 
1921  prog_stats prog;
1923  init_console_output(tm, prog);
1924 
1925 #ifdef WITH_POWERCAPPING
1926  basic_powercapping_setup();
1927  powercapping_manager *pc = user_globals::pc_manager;
1928 #endif
1929 
1930  // main loop
1931  do {
1932  // console output
1933  if(tm.trigger(iotm_console)) {
1934  // print console
1935  update_console_output(tm, prog);
1936  }
1937 
1938 #ifdef WITH_POWERCAPPING
1939  std::vector<int> flops_per_rank;
1940 
1941  // TODO: fill vector with expected flops per MPI rank for upcoming iteration
1942 #endif
1943 
1944 #ifdef WITH_POWERCAPPING
1945  pc->iteration_begin(flops_per_rank);
1946 #endif
1947 
1948  // in order to be closer to carpentry we first do output and then compute the solution
1949  // for the next time slice ..
1950  if (tm.trigger(iotm_spacedt)) {
1951  for(const auto & it : user_globals::physics_reg) {
1952  it.second->output_step();
1953  }
1954  }
1955 #ifdef WITH_POWERCAPPING
1956  pc->sample("output step");
1957 #endif
1958 
1959  // compute step
1960  for(const auto & it : user_globals::physics_reg) {
1961  Basic_physic* p = it.second;
1962  if (tm.trigger(p->timer_idx))
1963  p->compute_step();
1964 #ifdef WITH_POWERCAPPING
1965  pc->sample(p->name);
1966 #endif
1967  }
1968 
1969 #ifdef WITH_POWERCAPPING
1970  pc->iteration_end(flops_per_rank);
1971 #endif
1972 
1973  // advance time
1974  tm.update_timers();
1975  } while (!tm.elapsed());
1976 
1977  log_msg(0,0,0, "\n\nTimings of individual physics:");
1978  log_msg(0,0,0, "------------------------------\n");
1979 
1980  for(const auto & it : user_globals::physics_reg) {
1981  Basic_physic* p = it.second;
1982  p->output_timings();
1983  }
1984 
1985 #ifdef WITH_POWERCAPPING
1986  pc = nullptr;
1987  basic_powercapping_cleanup();
1988 #endif
1989 }
1990 
1992 {
1993  if(param_globals::post_processing_opts & RECOVER_PHIE) {
1994  log_msg(NULL,0,ECHO,"\nPOSTPROCESSOR: Recovering Phie ...");
1995  log_msg(NULL,0,ECHO, "----------------------------------\n");
1996 
1997  // do postprocessing
1998  int err = postproc_recover_phie();
1999 
2000  if(!err) {
2001  log_msg(NULL,0,ECHO,"\n-----------------------------------------");
2002  log_msg(NULL,0,ECHO, "POSTPROCESSOR: Successfully recoverd Phie.\n");
2003  }
2004  }
2005 
2006  if(param_globals::post_processing_opts & LEADFIELD) {
2007 #ifdef WITH_LEADFIELD
2008  log_msg(NULL,0,ECHO,"\nPOSTPROCESSOR: Computing leadfields ...");
2009  log_msg(NULL,0,ECHO, "-------------------------------------\n");
2010 
2011  Electrics* elec = static_cast<Electrics*>(get_physics(elec_phys));
2012  if(!elec) {
2013  log_msg(NULL, 5, 0, "Error: Leadfield requires active EP physics. Aborting.");
2014  EXIT(1);
2015  }
2016  Leadfield leadfield;
2017  int err = leadfield.run(*elec);
2018 
2019  if(!err) {
2020  log_msg(NULL,0,ECHO,"\n------------------------------------------");
2021  log_msg(NULL,0,ECHO, "POSTPROCESSOR: Successfully computed leadfields.\n");
2022  }
2023 #else
2024  log_msg(NULL, 5, 0, "Error: leadfield support not compiled in. Rebuild with -DENABLE_LEADFIELD=ON.");
2025  EXIT(1);
2026 #endif
2027  }
2028 }
2029 
2030 Basic_physic* get_physics(physic_t p, bool error_if_missing)
2031 {
2032  auto it = user_globals::physics_reg.find(p);
2033 
2034  if(it != user_globals::physics_reg.end()) {
2035  return it->second;
2036  } else {
2037  if(error_if_missing) {
2038  log_msg(0,5,0, "%s error: required physic is not active! Usually this is due to an inconsistent experiment configuration. Aborting!", __func__);
2039  EXIT(EXIT_FAILURE);
2040  }
2041 
2042  return NULL;
2043  }
2044 }
2045 
2047 {
2048  sf_vec* ret = NULL;
2049 
2051  ret = user_globals::datavec_reg[d];
2052 
2053  return ret;
2054 }
2055 
2057 {
2058  if(user_globals::datavec_reg.count(d) == 0) {
2059  user_globals::datavec_reg[d] = dat;
2060  }
2061  else {
2062  log_msg(0,5,0, "%s warning: trying to register already registered data vector.", __func__);
2063  }
2064 }
2065 
2067 {
2068  std::map<mesh_t, sf_mesh> & mesh_registry = user_globals::mesh_reg;
2069 
2070  // This is the initial grid we read the hard-disk data into
2071  mesh_registry[reference_msh] = sf_mesh();
2072  // we specify the MPI communicator for the reference mesh,
2073  // all derived meshes will get this comminicator automatically
2074  mesh_registry[reference_msh].comm = PETSC_COMM_WORLD;
2075 
2076  auto register_new_mesh = [&] (mesh_t mt, int pidx) {
2077  if(!mesh_registry.count(mt)) {
2078  mesh_registry[mt] = sf_mesh();
2079  mesh_registry[mt].name = param_globals::phys_region[pidx].name;
2080  }
2081  return &mesh_registry[mt];
2082  };
2083 
2084  // based on cli parameters we determine which grids need to be defined
2085  for(int i=0; i<param_globals::num_phys_regions; i++)
2086  {
2087  sf_mesh* curmesh = NULL;
2088  // register mesh type
2089  switch(param_globals::phys_region[i].ptype) {
2090  case PHYSREG_EIKONAL:
2091  case PHYSREG_INTRA_ELEC:
2092  curmesh = register_new_mesh(intra_elec_msh, i);
2093  break;
2094 
2095  case PHYSREG_LAPLACE:
2096  case PHYSREG_EXTRA_ELEC:
2097  curmesh = register_new_mesh(extra_elec_msh, i);
2098  break;
2099 #if WITH_EMI_MODEL
2100  case PHYSREG_EMI:
2101  curmesh = register_new_mesh(emi_msh, i);
2102  break;
2103 #endif
2104 
2105  default:
2106  log_msg(0,5,0, "Unsupported mesh type %d! Aborting!", param_globals::phys_region[i].ptype);
2107  EXIT(EXIT_FAILURE);
2108  }
2109 
2110  if(curmesh) {
2111  // set mesh unique tags
2112  for(int j=0; j<param_globals::phys_region[i].num_IDs; j++)
2113  curmesh->extr_tag.insert(param_globals::phys_region[i].ID[j]);
2114  }
2115  }
2116 }
2117 
2128 void retag_elements(sf_mesh & mesh, TagRegion *tagRegs, int ntr)
2129 {
2130  if(ntr == 0) return;
2131  // checkTagRegDefs(ntr, tagRegs);
2132 
2134 
2135  for (int i=0; i<ntr; i++) {
2136  tagreg_t type = tagreg_t(tagRegs[i].type);
2137  SF::vector<mesh_int_t> elem_indices;
2138 
2139  if (type == tagreg_list)
2140  read_indices(elem_indices, tagRegs[i].elemfile, ref_eidx, mesh.comm);
2141  else {
2142  geom_shape shape;
2143  shape.type = geom_shape::shape_t(tagRegs[i].type);
2144  shape.p0.x = tagRegs[i].p0[0];
2145  shape.p0.y = tagRegs[i].p0[1];
2146  shape.p0.z = tagRegs[i].p0[2];
2147  shape.p1.x = tagRegs[i].p1[0];
2148  shape.p1.y = tagRegs[i].p1[1];
2149  shape.p1.z = tagRegs[i].p1[2];
2150  shape.radius = tagRegs[i].radius;
2151 
2152  bool nodal = false;
2153  indices_from_geom_shape(elem_indices, mesh, shape, nodal);
2154  }
2155 
2156  if(get_global((long int)elem_indices.size(), MPI_SUM, mesh.comm) == 0)
2157  log_msg(0,3,0,"Tag region %d is empty", i);
2158 
2159  for(size_t j=0; j<elem_indices.size(); j++)
2160  mesh.tag[elem_indices[j]] = tagRegs[i].tag;
2161  }
2162 
2163  // output the vector?
2164  if(strlen(param_globals::retagfile))
2165  {
2166  update_cwd();
2167  set_dir(OUTPUT);
2168 
2169  int dpn = 1;
2170  SF::write_data_ascii(mesh.comm, ref_eidx, mesh.tag, param_globals::retagfile, dpn);
2171 
2172  // Set dir back to what is was prior to retagfile output
2173  set_dir(CURDIR);
2174  }
2175 }
2176 
2177 size_t renormalise_fibres(SF::vector<mesh_real_t> &fib, size_t l_numelem)
2178 {
2179  size_t renormalised_count = 0;
2180 
2181  // using pragma omp without global OMP control can lead to massive compute stalls,
2182  // as all cores may be already occupied by MPI, thus they become oversubscribed. Once
2183  // there is a global OMP control in place, we can activate this parallel for again. -Aurel, 20.01.2022
2184  // #pragma omp parallel for schedule(static) reduction(+ : renormalised_count)
2185  for (size_t i = 0; i < l_numelem; i++)
2186  {
2187  const mesh_real_t f0 = fib[3*i+0], f1 = fib[3*i+1], f2 = fib[3*i+2];
2188  mesh_real_t fibre_len = sqrt(f0*f0 + f1*f1 + f2*f2);
2189 
2190  if (fibre_len && fabs(fibre_len - 1) > 1e-3) {
2191  fib[3 * i + 0] /= fibre_len;
2192  fib[3 * i + 1] /= fibre_len;
2193  fib[3 * i + 2] /= fibre_len;
2194  renormalised_count++;
2195  }
2196  }
2197 
2198  return renormalised_count;
2199 }
2200 
2201 void setup_meshes(bool require_fibers=true)
2202 {
2203  log_msg(0,0,0, "\n *** Processing meshes ***\n");
2204 
2205  const std::string basename = param_globals::meshname;
2206  const int verb = param_globals::output_level;
2207  std::map<mesh_t, sf_mesh> & mesh_registry = user_globals::mesh_reg;
2208  assert(mesh_registry.count(reference_msh) == 1); // There must be a reference mesh
2209 
2210  set_dir(INPUT);
2211 
2212  // we always read into the reference mesh
2213  sf_mesh & ref_mesh = mesh_registry[reference_msh];
2214  MPI_Comm comm = ref_mesh.comm;
2215 
2216  int size, rank;
2217  double t1, t2, s1, s2;
2218  MPI_Comm_size(comm, &size); MPI_Comm_rank(comm, &rank);
2219 
2221  SF::vector<mesh_int_t> ptsidx;
2222 
2223  // we add pointers to the meshes that need vertex cooridnates to this list
2224  std::list< sf_mesh* > ptsread_list;
2225 
2226  // read element mesh data
2227  t1 = MPI_Wtime(); s1 = t1;
2228  if(verb) log_msg(NULL, 0, 0,"Reading reference mesh: %s.*", basename.c_str());
2229 
2230  SF::read_elements(ref_mesh, basename, require_fibers);
2231  SF::read_points(basename, comm, pts, ptsidx);
2232 
2233  if (strlen(param_globals::tagfile)) {
2234  if(verb) log_msg(NULL, 0, 0, "Overriding element tags from: %s", param_globals::tagfile);
2235  SF::read_element_tags(ref_mesh, param_globals::tagfile);
2236  }
2237 
2238  t2 = MPI_Wtime();
2239  if(verb) log_msg(NULL, 0, 0, "Done in %f sec.", float(t2 - t1));
2240 
2241  bool check_fibre_normality = true;
2242  if (check_fibre_normality and ref_mesh.fib.size()>0) {
2243  t1 = MPI_Wtime();
2244 
2245  // make sure that all fibre vectors have unit length
2246  size_t l_num_fixed_fib = renormalise_fibres(ref_mesh.fib, ref_mesh.l_numelem);
2247 
2248  size_t l_num_fixed_she = 0;
2249  if (ref_mesh.she.size() > 0)
2250  l_num_fixed_she = renormalise_fibres(ref_mesh.she, ref_mesh.l_numelem);
2251 
2252  unsigned long fixed[2] = {(unsigned long) l_num_fixed_fib, (unsigned long) l_num_fixed_she};
2253  MPI_Allreduce(MPI_IN_PLACE, fixed, 2, MPI_UNSIGNED_LONG, MPI_SUM, comm);
2254 
2255  if (fixed[0] + fixed[1] > 0)
2256  log_msg(NULL, 0, 0, "Renormalised %ld longitudinal and %ld sheet-transverse fibre vectors.", fixed[0], fixed[1]);
2257 
2258  t2 = MPI_Wtime();
2259  if(verb) log_msg(NULL, 0, 0, "Done in %f sec.", float(t2 - t1));
2260  }
2261 
2262  if(param_globals::numtagreg > 0) {
2263  log_msg(0, 0, 0, "Re-tagging reference mesh");
2264 
2265  // the retagging requires vertex coordinates, as such we need to read them into
2266  // the reference mesh
2267  ptsread_list.push_back(&ref_mesh);
2268  SF::insert_points(pts, ptsidx, ptsread_list);
2269 
2270  retag_elements(ref_mesh, param_globals::tagreg, param_globals::numtagreg);
2271 
2272  // we clear the list of meshet to receive vertices
2273  ptsread_list.clear();
2274  }
2275 
2276  if(verb) log_msg(NULL, 0, 0, "Processing submeshes");
2277 
2278  bool have_emi_mesh = false;
2279  bool have_non_emi_mesh = false;
2280 
2281  for(auto it = mesh_registry.begin(); it != mesh_registry.end(); ++it) {
2282  mesh_t grid_type = it->first;
2283  sf_mesh & submesh = it->second;
2284 
2285  if(grid_type != reference_msh) {
2286  if(grid_type == emi_msh)
2287  have_emi_mesh = true;
2288  else
2289  have_non_emi_mesh = true;
2290 
2291  if(verb > 1) log_msg(NULL, 0, 0, "\nSubmesh name: %s", submesh.name.c_str());
2292  t1 = MPI_Wtime();
2293 
2294  if(submesh.extr_tag.size() && grid_type != emi_msh)
2295  extract_tagbased(ref_mesh, submesh);
2296  else {
2297  // all submeshes should be defined on sets of tags, for backwards compatibility
2298  // we do a fiber based intra_elec_msh extraction if no tags are provided. Also, we
2299  // could do special treatments of any other physics type here. It would defeat
2300  // the purpose of the tag-based design, though. -Aurel
2301  switch(grid_type) {
2302  case emi_msh:
2303  case intra_elec_msh: extract_myocardium(ref_mesh, submesh, require_fibers); break;
2304  default: extract_tagbased(ref_mesh, submesh); break;
2305  }
2306  }
2307  t2 = MPI_Wtime();
2308  if(verb > 1) log_msg(NULL, 0, 0, "Extraction done in %f sec.", float(t2 - t1));
2309 
2310  ptsread_list.push_back(&submesh);
2311  }
2312  }
2313 
2314  if(have_emi_mesh && have_non_emi_mesh) {
2315  log_msg(NULL, 5, ECHO, "EMI and non-EMI submeshes cannot be mixed during mesh setup.");
2316  EXIT(EXIT_FAILURE);
2317  }
2318 
2319  // KDtree partitioning requires the coordinates to be present in the mesh data
2320  if(param_globals::pstrat == 2 && have_non_emi_mesh)
2321  SF::insert_points(pts, ptsidx, ptsread_list);
2322 
2323  for(auto it = mesh_registry.begin(); it != mesh_registry.end(); ++it)
2324  {
2325  mesh_t grid_type = it->first;
2326  sf_mesh & submesh = it->second;
2327  if(grid_type != reference_msh && grid_type!=emi_msh) {
2328  if(verb > 2) log_msg(NULL, 0, 0, "\nSubmesh name: %s", submesh.name.c_str());
2330 
2331  // generate partitioning vector
2332  t1 = MPI_Wtime();
2333  switch(param_globals::pstrat) {
2334  case 0:
2335  if(verb > 2) log_msg(NULL, 0, 0, "Using linear partitioning ..");
2336  break;
2337 
2338 #ifdef WITH_PARMETIS
2339  case 1:
2340  {
2341  if(verb > 2) log_msg(NULL, 0, 0, "Using Parmetis partitioner ..");
2342  SF::parmetis_partitioner<mesh_int_t, mesh_real_t> partitioner(param_globals::pstrat_imbalance, 2);
2343  partitioner(submesh, part);
2344  break;
2345  }
2346 #endif
2347  default:
2348  case 2: {
2349  if(verb > 2) log_msg(NULL, 0, 0, "Using KDtree partitioner ..");
2351  partitioner(submesh, part);
2352  break;
2353  }
2354  }
2355  t2 = MPI_Wtime();
2356  if(verb > 2) log_msg(NULL, 0, 0, "Partitioning done in %f sec.", float(t2 - t1));
2357 
2358  if(param_globals::pstrat > 0) {
2359  if(param_globals::gridout_p) {
2360  std::string out_name = get_basename(param_globals::meshname);
2361  if(grid_type == intra_elec_msh) out_name += "_i.part.dat";
2362  else if(grid_type == extra_elec_msh) out_name += "_e.part.dat";
2363 
2364  set_dir(OUTPUT);
2365  log_msg(0,0,0, "Writing \"%s\" partitioning to: %s", submesh.name.c_str(), out_name.c_str());
2366  write_data_ascii(submesh.comm, submesh.get_numbering(SF::NBR_ELEM_REF), part, out_name);
2367  }
2368 
2369  t1 = MPI_Wtime();
2370  SF::redistribute_elements(submesh, part);
2371  t2 = MPI_Wtime();
2372  if(verb > 2) log_msg(NULL, 0, 0, "Redistributing done in %f sec.", float(t2 - t1));
2373  }
2374 
2375  t1 = MPI_Wtime();
2377  sm_numbering(submesh);
2378  t2 = MPI_Wtime();
2379  if(verb > 2) log_msg(NULL, 0, 0, "Canonical numbering done in %f sec.", float(t2 - t1));
2380 
2381  t1 = MPI_Wtime();
2382  submesh.generate_par_layout();
2383  SF::petsc_numbering<mesh_int_t, mesh_real_t> p_numbering(submesh.pl);
2384  p_numbering(submesh);
2385  t2 = MPI_Wtime();
2386  if(verb > 2) log_msg(NULL, 0, 0, "PETSc numbering done in %f sec.", float(t2 - t1));
2387  if(verb > 2) print_DD_info(submesh);
2388  }
2389  }
2390 
2391  if(have_non_emi_mesh)
2392  SF::insert_points(pts, ptsidx, ptsread_list);
2393 
2394  ref_mesh.clear_data();
2395 
2396  s2 = MPI_Wtime();
2397  if(verb) log_msg(NULL, 0, 0, "All done in %f sec.", float(s2 - s1));
2398 }
2399 
2401 {
2402  static const char* parameter_name = "gridout_tags";
2403  const std::string spec = param_globals::gridout_tags ? param_globals::gridout_tags : "";
2404 
2405  std::vector<int> tags;
2406  std::string error;
2407  if(!paramschema::parse_idset_spec(spec, &tags, &error)) {
2408  log_msg(0, 5, ECHO, "Could not parse %s: %s.", parameter_name, error.c_str());
2409  EXIT(EXIT_FAILURE);
2410  }
2411 
2412  output_tags.clear();
2413  output_tags.insert(tags.begin(), tags.end());
2414 
2415  if(output_tags.size() == 0) return false;
2416 
2417  log_msg(0, 0, 0, "Restricting grid output to %zu tag(s) from %s.",
2418  output_tags.size(), parameter_name);
2419  return true;
2420 }
2421 
2423  const hashmap::unordered_set<int>& output_tags,
2424  SF::vector<mesh_int_t>& output_idx,
2425  bool async)
2426 {
2427  SF::vector<mesh_int_t> selected_nodes;
2428 
2429  for(size_t eidx = 0; eidx < mesh.l_numelem; eidx++) {
2430  if(output_tags.count(mesh.tag[eidx]) == 0) continue;
2431 
2432  for(mesh_int_t j = mesh.dsp[eidx]; j < mesh.dsp[eidx + 1]; j++)
2433  selected_nodes.push_back(mesh.con[j]);
2434  }
2435 
2436  localize_gridout_nodes(mesh, selected_nodes, output_idx, async);
2437 }
2438 
2440  const SF::vector<mesh_t>& mesh_ids)
2441 {
2442  hashmap::unordered_set<int> local_seen;
2443 
2444  for(mesh_t mesh_id : mesh_ids) {
2445  if(!mesh_is_registered(mesh_id)) continue;
2446 
2447  const sf_mesh& mesh = get_mesh(mesh_id);
2448  for(size_t eidx = 0; eidx < mesh.l_numelem; eidx++) {
2449  if(output_tags.count(mesh.tag[eidx]) != 0)
2450  local_seen.insert(mesh.tag[eidx]);
2451  }
2452  }
2453 
2454  SF::vector<int> sorted_tags;
2455  sorted_tags.assign(output_tags.begin(), output_tags.end());
2456  binary_sort(sorted_tags);
2457 
2458  SF::vector<int> missing_tags;
2459  for(int tag : sorted_tags) {
2460  int local_found = local_seen.count(tag) ? 1 : 0;
2461  int global_found = 0;
2462  MPI_Allreduce(&local_found, &global_found, 1, MPI_INT, MPI_MAX, PETSC_COMM_WORLD);
2463  if(global_found == 0)
2464  missing_tags.push_back(tag);
2465  }
2466 
2467  if(missing_tags.size()) {
2468  binary_sort(missing_tags);
2469 
2470  std::stringstream msg;
2471  for(size_t i = 0; i < missing_tags.size(); i++) {
2472  if(i) msg << ", ";
2473  msg << missing_tags[i];
2474  }
2475 
2476  log_msg(0, 3, ECHO, "Warning: ignoring gridout_tags not present in the selected output mesh(es): %s.",
2477  msg.str().c_str());
2478 
2479  for(int tag : missing_tags)
2480  output_tags.erase(tag);
2481 
2482  if(output_tags.size() == 0) {
2483  log_msg(0, 5, ECHO, "gridout_tags did not match any tag in the selected output mesh(es).");
2484  EXIT(EXIT_FAILURE);
2485  }
2486  }
2487 }
2488 
2490  const SF::vector<mesh_int_t>& selected_idx)
2491 {
2492  if(restr == NULL) {
2493  restr = new SF::vector<mesh_int_t>(selected_idx);
2494  return;
2495  }
2496 
2497  binary_sort(*restr);
2498  SF::vector<mesh_int_t> selected(selected_idx);
2499  binary_sort(selected);
2500 
2501  SF::vector<mesh_int_t> intersection;
2502  size_t lhs = 0, rhs = 0;
2503  while(lhs < restr->size() && rhs < selected.size()) {
2504  if((*restr)[lhs] == selected[rhs]) {
2505  intersection.push_back((*restr)[lhs]);
2506  lhs++;
2507  rhs++;
2508  } else if((*restr)[lhs] < selected[rhs]) {
2509  lhs++;
2510  } else {
2511  rhs++;
2512  }
2513  }
2514 
2515  *restr = intersection;
2516 }
2517 
2518 
2520 {
2521  bool write_intra_elec = mesh_is_registered(intra_elec_msh) && param_globals::gridout_i;
2522  bool write_extra_elec = mesh_is_registered(extra_elec_msh) && param_globals::gridout_e;
2523 
2524  set_dir(OUTPUT);
2525  std::string output_base = get_basename(param_globals::meshname);
2526  hashmap::unordered_set<int> output_tags;
2527  const bool restrict_gridout =
2528  (write_intra_elec || write_extra_elec) && parse_gridout_tags(output_tags);
2529  if(restrict_gridout) {
2530  SF::vector<mesh_t> mesh_ids;
2531  if(write_intra_elec) mesh_ids.push_back(intra_elec_msh);
2532  if(write_extra_elec) mesh_ids.push_back(extra_elec_msh);
2533  validate_gridout_tags(output_tags, mesh_ids);
2534  }
2535 
2536  if(write_intra_elec) {
2537  sf_mesh & mesh = get_mesh(intra_elec_msh);
2538  sf_mesh restricted_mesh;
2539  sf_mesh* output_mesh = &mesh;
2540  if(restrict_gridout) {
2541  if(!mesh_has_gridout_tags(mesh, output_tags)) {
2542  log_msg(0, 5, ECHO, "gridout_tags selected no intracellular grid elements.");
2543  EXIT(EXIT_FAILURE);
2544  }
2545  extract_gridout_tag_mesh(mesh, output_tags, restricted_mesh);
2546  output_mesh = &restricted_mesh;
2547  }
2548 
2549  if(param_globals::gridout_i & 1) {
2550  if(param_globals::output_level > 1)
2551  log_msg(0,0,0, "Computing \"%s\" surface ..", output_mesh->name.c_str());
2552 
2553  std::string output_file = output_base + "_i.surf";
2554  log_msg(0,0,0, "Writing \"%s\" surface: %s", output_mesh->name.c_str(), output_file.c_str());
2555 
2556  if(param_globals::gridout_i & 2) {
2557  // the surface indexes into the points of the volume mesh written below
2558  sf_mesh surfmesh;
2559  compute_surface_mesh(*output_mesh, SF::NBR_SUBMESH, surfmesh);
2560  write_surface(surfmesh, output_file);
2561  }
2562  else {
2563  // no volume mesh is written, so the surface needs points of its own
2564  write_gridout_surface(*output_mesh, output_base + "_i");
2565  }
2566  }
2567  if(param_globals::gridout_i & 2) {
2568  bool write_binary = false;
2569 
2570  std::string output_file = output_base + "_i";
2571  log_msg(0,0,0, "Writing \"%s\" mesh: %s", output_mesh->name.c_str(), output_file.c_str());
2572  write_mesh_parallel(*output_mesh, write_binary, output_file.c_str());
2573  }
2574  }
2575 
2576  if(write_extra_elec) {
2577  sf_mesh & mesh = get_mesh(extra_elec_msh);
2578  sf_mesh restricted_mesh;
2579  sf_mesh* output_mesh = &mesh;
2580  if(restrict_gridout) {
2581  if(!mesh_has_gridout_tags(mesh, output_tags)) {
2582  log_msg(0, 5, ECHO, "gridout_tags selected no extracellular grid elements.");
2583  EXIT(EXIT_FAILURE);
2584  }
2585  extract_gridout_tag_mesh(mesh, output_tags, restricted_mesh);
2586  output_mesh = &restricted_mesh;
2587  }
2588 
2589  if(param_globals::gridout_e & 1) {
2590  if(param_globals::output_level > 1)
2591  log_msg(0,0,0, "Computing \"%s\" surface ..", output_mesh->name.c_str());
2592 
2593  std::string output_file = output_base + "_e.surf";
2594  log_msg(0,0,0, "Writing \"%s\" surface: %s", output_mesh->name.c_str(), output_file.c_str());
2595 
2596  if(param_globals::gridout_e & 2) {
2597  // the surface indexes into the points of the volume mesh written below
2598  sf_mesh surfmesh;
2599  compute_surface_mesh(*output_mesh, SF::NBR_SUBMESH, surfmesh);
2600  write_surface(surfmesh, output_file);
2601  }
2602  else {
2603  // no volume mesh is written, so the surface needs points of its own
2604  write_gridout_surface(*output_mesh, output_base + "_e");
2605  }
2606  }
2607  if(param_globals::gridout_e & 2) {
2608  bool write_binary = false;
2609  std::string output_file = output_base + "_e";
2610  log_msg(0,0,0, "Writing \"%s\" mesh: %s", output_mesh->name.c_str(), output_file.c_str());
2611  write_mesh_parallel(*output_mesh, write_binary, output_file.c_str());
2612  }
2613  }
2614 }
2615 
2616 [[noreturn]] void cleanup_and_exit()
2617 {
2618  destroy_physics();
2620  print_active_citation_suggestions();
2621 
2622  const paramschema::ResetResult reset = paramschema::reset_schema_state(paramschema::openCARP_schema());
2623  print_lines(stderr, "parameter cleanup warning: ", reset.warnings);
2624  print_lines(stderr, "parameter cleanup error: ", reset.errors);
2625  PetscFinalize();
2626 
2627  // close petsc error FD
2630 
2631  exit(EXIT_SUCCESS);
2632 }
2633 
2634 char* get_file_dir(const char* file)
2635 {
2636  char* filecopy = dupstr(file);
2637  char* dir = dupstr(dirname(filecopy));
2638 
2639  free(filecopy);
2640  return dir;
2641 }
2642 
2644 {
2645  int rank = get_rank();
2646  set_dir(OUTPUT);
2647 
2648  if(rank == 0) {
2649  // we open a error log file handle and set it as petsc stderr
2650  user_globals::petsc_error_fd = fopen("petsc_err_log.txt", "w");
2651  PETSC_STDERR = user_globals::petsc_error_fd;
2652  }
2653  else {
2654  PetscErrorPrintf = PetscErrorPrintfNone;
2655  }
2656 }
2657 
2659 {
2660  const sf_mesh & mesh = get_mesh(id);
2662  short mindim = 3;
2663 
2664  for(size_t eidx = 0; eidx < mesh.l_numelem; eidx++) {
2665  view.set_elem(eidx);
2666  short cdim = view.dimension();
2667  if(mindim < cdim) mindim = cdim;
2668  }
2669 
2670  mindim = get_global(mindim, MPI_MIN, mesh.comm);
2671 
2672  return mindim;
2673 }
2674 
2676  const mesh_t inp_meshid,
2677  const int dpn,
2678  const char* name,
2679  const char* units,
2680  const SF::vector<mesh_int_t>* idx,
2681  bool elem_data)
2682 {
2683  sync_io_item IO;
2684 
2685  IO.data = inp_data;
2686  IO.elem_flag = elem_data;
2687  IO.restr_idx = idx;
2688  if(idx != NULL) {
2689  const sf_mesh& mesh = get_mesh(inp_meshid);
2690  const SF::vector<mesh_int_t>& layout = elem_data ? mesh.epl.algebraic_layout() :
2691  mesh.pl.algebraic_layout();
2692  const mesh_int_t start = layout[get_rank()];
2693 
2694  IO.restr_global_idx.resize(idx->size());
2695  for(size_t i = 0; i < idx->size(); i++)
2696  IO.restr_global_idx[i] = start + (*idx)[i];
2697  }
2698 
2699  IGBheader regigb;
2701  const int num_io = tm.timers[iotm_spacedt]->numIOs;
2702  int err = 0;
2703 
2704  int gsize = inp_data->gsize();
2705 
2706  // if we are restricting, we have to compute the restricted global size
2707  if(idx != NULL)
2708  gsize = get_global(int(idx->size()), MPI_SUM) * dpn;
2709 
2710  regigb.x(gsize / dpn);
2711  regigb.dim_x(regigb.x()-1);
2712  regigb.inc_x(1);
2713 
2714  regigb.y(1); regigb.z(1);
2715  regigb.t(num_io);
2716  regigb.dim_t(tm.end-tm.start);
2717 
2718  switch(dpn) {
2719  default:
2720  case 1: regigb.type(IGB_FLOAT); break;
2721  case 3: regigb.type(IGB_VEC3_f); break;
2722  case 4: regigb.type(IGB_VEC4_f); break;
2723  case 9: regigb.type(IGB_VEC9_f); break;
2724  }
2725 
2726  regigb.unites_x("um"); regigb.unites_y("um"); regigb.unites_z("um");
2727  regigb.unites_t("ms");
2728  regigb.unites(units);
2729 
2730  regigb.inc_t(param_globals::spacedt);
2731 
2732  if(get_rank() == 0) {
2733  FILE_SPEC file = f_open(name, "w");
2734  if(file != NULL) {
2735  regigb.fileptr(file->fd);
2736  regigb.write();
2737  delete file;
2738  }
2739  else err++;
2740  }
2741 
2742  err = get_global(err, MPI_SUM);
2743  if(err) {
2744  log_msg(0,5,0, "%s error: Could not set up data output! Aborting!", __func__);
2745  EXIT(1);
2746  }
2747 
2748  IO.igb = regigb;
2749 
2750  SF::mixed_tuple<mesh_t, int> mesh_spec = {inp_meshid, dpn};
2751  IO.spec = mesh_spec;
2752 
2753  if(elem_data) {
2754  if(buffmap_elem.find(mesh_spec) == buffmap_elem.end()) {
2755  sf_vec *inp_copy; SF::init_vector(&inp_copy, inp_data);
2756  buffmap_elem[mesh_spec] = inp_copy;
2757  }
2758  } else {
2759  if(buffmap.find(mesh_spec) == buffmap.end()) {
2760  sf_vec *inp_copy; SF::init_vector(&inp_copy, inp_data);
2761  buffmap[mesh_spec] = inp_copy;
2762  }
2763  }
2764 
2765  this->sync_IOs.push_back(IO);
2766 }
2767 
2768 void igb_output_manager::register_output_async(sf_vec* inp_data,
2769  const mesh_t inp_meshid,
2770  const int dpn,
2771  const char* name,
2772  const char* units,
2773  const SF::vector<mesh_int_t>* idx,
2774  bool elem_data)
2775 {
2776  sf_mesh & mesh = get_mesh(inp_meshid);
2777  SF::vector<mesh_int_t> ioidx;
2778  int rank = get_rank();
2779 
2780  async_io_item IO;
2781  IO.data = inp_data;
2782  IO.restr_idx = idx;
2783 
2784  if(elem_data) {
2786  ioidx.resize(mesh.l_numelem);
2787  for(size_t i=0; i<mesh.l_numelem; i++)
2788  ioidx[i] = nbr[i];
2789  } else {
2790  const SF::vector<mesh_int_t> & alg_nod = mesh.pl.algebraic_nodes();
2792 
2793  if(idx == NULL) {
2794  ioidx.resize(alg_nod.size());
2795 
2796  for(size_t i=0; i<alg_nod.size(); i++)
2797  ioidx[i] = nbr[alg_nod[i]];
2798  } else {
2799  ioidx.resize(idx->size());
2800  IO.restr_petsc_idx.resize(idx->size());
2801 
2802  for(size_t i=0; i<idx->size(); i++) {
2803  mesh_int_t loc_nodal = (*idx)[i];
2804  ioidx[i] = nbr[loc_nodal];
2805  IO.restr_petsc_idx[i] = local_nodal_to_local_petsc(mesh, rank, loc_nodal);
2806  }
2807  }
2808  }
2809 
2810  int id = async::COMPUTE_register_output(ioidx, dpn, name, units);
2811  IO.IO_id = id;
2812 
2813  this->async_IOs.push_back(IO);
2814 }
2815 
2817  const mesh_t inp_meshid,
2818  const int dpn,
2819  const char* name,
2820  const char* units,
2821  const SF::vector<mesh_int_t>* idx,
2822  bool elem_data)
2823 {
2824  if(param_globals::num_io_nodes == 0)
2825  register_output_sync(inp_data, inp_meshid, dpn, name, units, idx, elem_data);
2826  else
2827  register_output_async(inp_data, inp_meshid, dpn, name, units, idx, elem_data);
2828 }
2829 
2830 sf_vec* igb_output_manager::fill_output_buffer(const sync_io_item & it)
2831 {
2832  const SF::mixed_tuple<mesh_t, int> & cspec = it.spec;
2833  sf_vec* data_vec = it.data;
2834 
2835  bool have_perm = it.elem_flag ? have_permutation(cspec.v1, ELEM_PETSC_TO_CANONICAL, cspec.v2):
2836  have_permutation(cspec.v1, PETSC_TO_CANONICAL, cspec.v2);
2837 
2838  if(have_perm) {
2839  sf_vec* perm_vec = it.elem_flag ? this->buffmap_elem[cspec] : this->buffmap[cspec];
2841  get_permutation(cspec.v1, PETSC_TO_CANONICAL, cspec.v2);
2842  sc->forward(*data_vec, *perm_vec);
2843  return perm_vec;
2844  } else {
2845  return data_vec;
2846  }
2847 }
2848 
2850 {
2851  SF::vector<float> restr_buff;
2852  int rank = get_rank();
2853  // loop over registered datasets and root-write one by one
2854  //
2855  for (sync_io_item & it : sync_IOs) {
2856  // write to associated file descriptor
2857  FILE* fd = static_cast<FILE*>(it.igb.fileptr());
2858 
2859  // fill the output buffer
2860  sf_vec* buff = fill_output_buffer(it);
2861 
2862  if(it.restr_idx == NULL) {
2863  buff->write_binary<float>(fd);
2864  } else {
2865  const SF::vector<mesh_int_t> & idx = *it.restr_idx;
2866  const int dpn = it.spec.v2;
2867  SF_real* p = buff->ptr();
2868 
2869  restr_buff.resize(idx.size()); restr_buff.resize(0);
2870 
2871  for(mesh_int_t ii : idx) {
2872  const mesh_int_t offset = ii * dpn;
2873  for(int j = 0; j < dpn; j++)
2874  restr_buff.push_back(p[offset + j]);
2875  }
2876 
2877  if(dpn == 1) {
2878  root_write_ordered(fd, it.restr_global_idx, restr_buff, PETSC_COMM_WORLD);
2879  } else {
2881  root_write_ordered(fd, it.restr_global_idx, cnt, restr_buff, PETSC_COMM_WORLD);
2882  }
2883  buff->release_ptr(p);
2884  }
2885  }
2886 
2887  // do all A-synchronous output:
2888  // loop over IDs received from the IO nodes and trigger async output
2889  //
2890  for (async_io_item & it : async_IOs) {
2891  SF_real* p = it.data->ptr();
2892  int ls = it.data->lsize();
2893  int id = it.IO_id;
2894 
2895  if(it.restr_idx == NULL)
2896  async::COMPUTE_do_output(p, ls, id);
2897  else {
2898  async::COMPUTE_do_output(p, it.restr_petsc_idx, id);
2899  }
2900 
2901  it.data->release_ptr(p);
2902  }
2903 }
2904 
2906 {
2907  if(get_rank() == 0) {
2908  // loop over registered datasets and close fd
2909  for(sync_io_item & it : sync_IOs) {
2910  FILE* fd = static_cast<FILE*>(it.igb.fileptr());
2911  fclose(fd);
2912  }
2913  }
2914 
2915  for(auto it = buffmap.begin(); it != buffmap.end(); ++it)
2916  delete it->second;
2917 
2918  for(auto it = buffmap_elem.begin(); it != buffmap_elem.end(); ++it)
2919  delete it->second;
2920 
2921  // we resize the arrays and clear the maps so that we are safe when calling
2922  // close_files_and_cleanup multiple times.
2923  sync_IOs.resize(0);
2924  async_IOs.resize(0);
2925  buffmap.clear();
2926  buffmap_elem.clear();
2927 }
2928 
2930 {
2931  for(sync_io_item & it : sync_IOs) {
2932  if(it.data == vec)
2933  return &it.igb;
2934  }
2935 
2936  return NULL;
2937 }
2938 
2939 void output_parameter_file(const char *fname, int argc, char **argv)
2940 {
2941  const int max_line_len = 128;
2942  const char* file_sep = "#=======================================================";
2943 
2944  // make sure only root executes this function
2945  if(get_rank() != 0)
2946  return;
2947 
2948  FILE_SPEC out = f_open(fname, "w");
2949  fprintf(out->fd, "# CARP GIT commit hash: %s\n", GIT_COMMIT_HASH);
2950  fprintf(out->fd, "# dependency hashes: %s\n", SUBREPO_COMMITS);
2951  fprintf(out->fd, "\n");
2952 
2953  // output the command line
2954  char line[8196] = "# ";
2955 
2956  for (int j=0; j<argc; j++) {
2957  strcat(line, argv[j]);
2958  if(strlen(line) > max_line_len) {
2959  fprintf(out->fd, "%s\n", line);
2960  strcpy(line, "# ");
2961  } else
2962  strcat(line, " ");
2963  }
2964 
2965  fprintf(out->fd, "%s\n\n", line);
2966  set_dir(INPUT);
2967 
2968  // convert command line to a par file
2969  for (int i=1; i<argc; i++) {
2970  std::string argument(argv[i]);
2971  if (argument == "+F" || argument.find("_options_file")!= std::string::npos) {
2972 
2973  std::string init = "";
2974  if (argument.find("_options_file")!= std::string::npos) {
2975  fprintf(out->fd, "%s = %s\n", argument.substr(1).c_str(), argv[i+1]);
2976  init = "#";
2977  }
2978  fprintf(out->fd, "%s>>\n", file_sep);
2979  // import par files
2980  i++;
2981  fprintf(out->fd, "## %s ##\n", argv[i]);
2982  FILE *in = fopen(argv[i], "r");
2983  while (fgets(line, 8196, in))
2984  fprintf(out->fd, "%s%s", init.c_str(), line);
2985  fclose(in);
2986  fprintf(out->fd, "\n##END of %s\n", argv[i]);
2987  fprintf(out->fd, "%s<<\n\n", file_sep);
2988  }
2989  else if(argv[i][0] == '-')
2990  {
2991  bool prelast = (i==argc-1);
2992  bool paramFollows = !prelast && ((argv[i+1][0] != '-') ||
2993  ((argv[i+1][1] >= '0') && (argv[i+1][1] <= '9')));
2994 
2995  // strip leading hyphens from command line opts
2996  // assume options do not start with numbers
2997  if(paramFollows) {
2998  // nonflag option
2999  char *optcpy = strdup(argv[i+1]);
3000  char *front = optcpy;
3001  // strip {} if present for arrays of values
3002  while(*front==' ' && *front) front++;
3003  if(*front=='{') {
3004  while(*++front == ' ');
3005  char *back = optcpy+strlen(optcpy)-1;
3006  while(*back==' ' && back>front) back--;
3007  if(*back == '}')
3008  *back = '\0';
3009  }
3010  if (strstr(front, "=") != nullptr) // if "=" is find then we need ""
3011  fprintf(out->fd, "%-40s= \"%s\"\n", argv[i]+1, front);
3012  else
3013  fprintf(out->fd, "%-40s= %s\n", argv[i]+1, front);
3014  free(optcpy);
3015  i++;
3016  }
3017  else // a flag was specified
3018  fprintf(out->fd, "%-40s= 1\n", argv[i]);
3019  }
3020  }
3021  f_close(out);
3022 }
3023 
3024 void savequit()
3025 {
3026  if(!get_physics(elec_phys)) return;
3027 
3028  set_dir(OUTPUT);
3029 
3030  double time = user_globals::tm_manager->time;
3031  char save_fn[512];
3032 
3033  snprintf(save_fn, sizeof save_fn, "exit.save.%.3f.roe", time);
3034  log_msg(NULL, 0, 0, "savequit called at time %g\n", time);
3035 
3037  elec->ion.miif->dump_state(save_fn, time, intra_elec_msh, false, GIT_COMMIT_COUNT);
3038 
3039  cleanup_and_exit();
3040 }
3041 
3042 } // namespace opencarp
opencarp::local_index_t mesh_int_t
Definition: SF_container.h:46
float mesh_real_t
Definition: SF_container.h:47
opencarp::real_t SF_real
Global scalar type.
Definition: SF_globals.h:33
Async IO functions.
Basic utility structs and functions, mostly IO related.
#define FLUSH
Definition: basics.h:319
#define ECHO
Definition: basics.h:316
#define NONL
Definition: basics.h:320
virtual S * ptr()=0
virtual void release_ptr(S *&p)=0
virtual T gsize() const =0
size_t write_binary(FILE *fd)
Write a vector to HD in binary. File descriptor is already set up.
virtual T lsize() const =0
Comfort class. Provides getter functions to access the mesh member variables more comfortably.
Definition: SF_fem_utils.h:704
void set_elem(size_t eidx)
Set the view to a new element.
Definition: SF_fem_utils.h:731
short dimension() const
Definition: SF_fem_utils.h:922
void clear_data()
Clear the mesh data from memory.
Definition: SF_container.h:552
overlapping_layout< T > pl
nodal parallel layout
Definition: SF_container.h:429
vector< T > dsp
connectivity starting index of each element
Definition: SF_container.h:416
vector< S > she
sheet direction
Definition: SF_container.h:421
vector< S > fib
fiber direction
Definition: SF_container.h:420
size_t l_numelem
local number of elements
Definition: SF_container.h:399
std::string name
the mesh name
Definition: SF_container.h:407
void generate_par_layout()
Set up the parallel layout.
Definition: SF_container.h:538
vector< T > con
Definition: SF_container.h:412
MPI_Comm comm
the parallel mesh is defined on a MPI world
Definition: SF_container.h:404
vector< T > & get_numbering(SF_nbr nbr_type)
Get the vector defining a certain numbering.
Definition: SF_container.h:464
vector< T > tag
element tag
Definition: SF_container.h:417
hashmap::unordered_set< int > extr_tag
the element tags based on which the mesh has been extracted
Definition: SF_container.h:424
non_overlapping_layout< T > epl
element parallel layout
Definition: SF_container.h:430
Functor class generating a numbering optimized for PETSc.
Definition: SF_numbering.h:231
void free_scatterings()
Free the registered scatterings.
Container for a PETSc VecScatter.
void forward(abstract_vector< T, S > &in, abstract_vector< T, S > &out, bool add=false)
Forward scattering.
Functor class applying a submesh renumbering.
Definition: SF_numbering.h:68
size_t size() const
The current size of the vector.
Definition: SF_vector.h:104
void resize(size_t n)
Resize a vector.
Definition: SF_vector.h:209
void assign(InputIterator s, InputIterator e)
Assign a memory range.
Definition: SF_vector.h:161
T * data()
Pointer to the vector's start.
Definition: SF_vector.h:91
T & push_back(T val)
Definition: SF_vector.h:283
iterator find(const K &key)
Search for key. Return iterator.
Definition: hashmap.hpp:641
void reserve(size_t n)
Definition: hashmap.hpp:734
size_t size() const
Definition: hashmap.hpp:1156
hm_int erase(const K &key)
Definition: hashmap.hpp:1068
hm_int count(const K &key) const
Definition: hashmap.hpp:1082
void insert(InputIterator first, InputIterator last)
Definition: hashmap.hpp:1052
void dump_state(char *, float, opencarp::mesh_t gid, bool, unsigned int)
The abstract physics interface we can use to trigger all physics.
Definition: physics_types.h:59
virtual void destroy()=0
int timer_idx
the timer index received from the timer manager
Definition: physics_types.h:66
virtual void output_timings()
Definition: physics_types.h:76
virtual void compute_step()=0
virtual void initialize()=0
const char * name
The name of the physic, each physic should have one.
Definition: physics_types.h:62
SF::vector< stimulus > stimuli
the electrical stimuli
Definition: electrics.h:265
std::string timer_unit(const int timer_id)
figure out units of a signal linked to a given timer
Definition: electrics.cc:843
double timer_val(const int timer_id)
figure out current value of a signal linked to a given timer
Definition: electrics.cc:827
void dim_t(float a)
Definition: IGBheader.h:329
void unites_x(const char *a)
Definition: IGBheader.h:341
void unites_z(const char *a)
Definition: IGBheader.h:347
void unites(const char *a)
Definition: IGBheader.h:353
void unites_y(const char *a)
Definition: IGBheader.h:344
void unites_t(const char *a)
Definition: IGBheader.h:350
void dim_x(float a)
Definition: IGBheader.h:320
void fileptr(FILE *f)
Definition: IGBheader.cc:327
void inc_t(float a)
Definition: IGBheader.h:317
void inc_x(float a)
Definition: IGBheader.h:308
limpet::MULTI_IF * miif
Definition: ionics.h:67
int run(Electrics &elec)
Full pipeline: construct or load Z, stream vm.igb, write ECG_*.dat.
Definition: leadfield.cc:269
class to store shape definitions
Definition: basics.h:389
std::map< SF::mixed_tuple< mesh_t, int >, sf_vec * > buffmap_elem
Definition: sim_utils.h:363
IGBheader * get_igb_header(const sf_vec *vec)
Get the pointer to the igb header for a vector that was registered for output.
Definition: sim_utils.cc:2929
void write_data()
write registered data to disk
Definition: sim_utils.cc:2849
SF::vector< async_io_item > async_IOs
Definition: sim_utils.h:360
void register_output_sync(sf_vec *inp_data, const mesh_t inp_meshid, const int dpn, const char *name, const char *units, const SF::vector< mesh_int_t > *idx=NULL, bool elem_data=false)
Definition: sim_utils.cc:2675
std::map< SF::mixed_tuple< mesh_t, int >, sf_vec * > buffmap
map data spec -> PETSc vector buffer
Definition: sim_utils.h:362
void close_files_and_cleanup()
close file descriptors
Definition: sim_utils.cc:2905
void register_output(sf_vec *inp_data, const mesh_t inp_meshid, const int dpn, const char *name, const char *units, const SF::vector< mesh_int_t > *idx=NULL, bool elem_data=false)
Register a data vector for output.
Definition: sim_utils.cc:2816
SF::vector< sync_io_item > sync_IOs
Definition: sim_utils.h:359
stim_t type
type of stimulus
Definition: stimulate.h:138
int timer_id
timer for stimulus
Definition: stimulate.h:123
double strength
strength of stimulus
Definition: stimulate.h:94
stim_protocol ptcl
applied stimulation protocol used
Definition: stimulate.h:169
stim_pulse pulse
stimulus wave form
Definition: stimulate.h:168
stim_physics phys
physics of stimulus
Definition: stimulate.h:170
centralize time managment and output triggering
Definition: timer_utils.h:73
void initialize_neq_timer(const std::vector< double > &itrig, double idur, int ID, const char *iname, const char *poolname=nullptr)
Definition: timer_utils.cc:63
double end
final time
Definition: timer_utils.h:81
long d_time
current time instance index
Definition: timer_utils.h:77
bool trigger(int ID) const
Definition: timer_utils.h:166
void initialize_eq_timer(double istart, double iend, int ntrig, double iintv, double idur, int ID, const char *iname, const char *poolname=nullptr)
Definition: timer_utils.cc:48
void reset_timers()
Reset time in timer_manager and then reset registered timers.
Definition: timer_utils.h:115
double start
initial time (nonzero when restarting)
Definition: timer_utils.h:79
void initialize_singlestep_timer(double tg, double idur, int ID, const char *iname, const char *poolname=nullptr)
Definition: timer_utils.h:156
std::vector< base_timer * > timers
vector containing individual timers
Definition: timer_utils.h:84
double time
current time
Definition: timer_utils.h:76
Base class for tracking progress.
Definition: progress.hpp:39
Top-level header of FEM module.
void extract_tagbased(const meshdata< T, S > &mesh, meshdata< T, S > &submesh)
Extract a submesh based on element tags.
void write_data_ascii(const MPI_Comm comm, const vector< T > &idx, const vector< S > &data, std::string file, short dpn=1)
void compute_surface_mesh(const meshdata< T, S > &mesh, const SF_nbr numbering, const hashmap::unordered_set< T > &tags, meshdata< T, S > &surfmesh)
Compute the surface of a given mesh.
void print_DD_info(const meshdata< T, S > &mesh)
Print some basic information on the domain decomposition of a mesh.
void read_points(const std::string basename, const MPI_Comm comm, vector< S > &pts, vector< T > &ptsidx)
Read the points and insert them into a list of meshes.
Definition: SF_mesh_io.h:953
void rebalance_mesh(meshdata< T, S > &mesh)
Rebalance the parallel distribution of a mesh, if a local size is 0.
void write_surface(const meshdata< T, S > &surfmesh, std::string surffile)
Definition: SF_mesh_io.h:841
void extract_mesh(const vector< bool > &keep, const meshdata< T, S > &mesh, meshdata< T, S > &submesh)
Extract a submesh from a given mesh.
void unique_resize(vector< T > &_P)
Definition: SF_sort.h:348
void count(const vector< T > &data, vector< S > &cnt)
Count number of occurrences of indices.
Definition: SF_vector.h:332
size_t root_write_ordered(FILE *fd, const vector< T > &idx, const vector< V > &vec, MPI_Comm comm)
Write index value pairs to disk in ordered permutation.
void insert_points(const vector< S > &pts, const vector< T > &ptsidx, std::list< meshdata< T, S > * > &meshlist)
Insert the points from the read-in buffers into a list of distributed meshes.
Definition: SF_mesh_io.h:1023
void read_element_tags(meshdata< T, S > &mesh, std::string filename)
Override element tags from an ASCII file (one int per element, global element order).
Definition: SF_mesh_io.h:556
void redistribute_elements(meshdata< T, S > &mesh, meshdata< T, S > &sendbuff, vector< T > &part)
Redistribute the element data of a parallel mesh among the ranks based on a partitioning.
void write_points_parallel(const meshdata< T, S > &mesh, bool binary, std::string basename)
Write a parallel mesh to harddisk without gathering it on one rank.
T local_nodal_to_local_petsc(const meshdata< T, S > &mesh, int rank, T local_nodal)
void init_vector(SF::abstract_vector< T, S > **vec)
Definition: SF_init.h:107
void binary_sort(vector< T > &_V)
Definition: SF_sort.h:284
void extract_myocardium(const meshdata< T, S > &mesh, meshdata< T, S > &submesh, bool require_fibers=true)
Extract the myocardium submesh.
void write_mesh_parallel(const meshdata< T, S > &mesh, bool binary, std::string basename)
void read_elements(meshdata< T, S > &mesh, std::string basename, bool require_fibers=true)
Read the element data (elements and fibers) of a CARP mesh.
Definition: SF_mesh_io.h:647
@ NBR_PETSC
PETSc numbering of nodes.
Definition: SF_container.h:203
@ NBR_ELEM_REF
The element numbering of the reference mesh (the one stored on HD).
Definition: SF_container.h:204
@ NBR_REF
The nodal numbering of the reference mesh (the one stored on HD).
Definition: SF_container.h:201
@ NBR_SUBMESH
Submesh nodal numbering: The globally ascending sorted reference indices are reindexed.
Definition: SF_container.h:202
@ NBR_ELEM_SUBMESH
Submesh element numbering: The globally ascending sorted reference indices are reindexed.
Definition: SF_container.h:205
int load_ionic_module(const char *)
void COMPUTE_do_output(SF_real *dat, const int lsize, const int IO_id)
Definition: async_io.cc:396
int COMPUTE_register_output(const SF::vector< mesh_int_t > &idx, const int dpn, const char *name, const char *units)
Definition: async_io.cc:104
std::map< int, std::string > units
Definition: stimulate.cc:41
MPI_Comm IO_Intercomm
Communicator between IO and compute worlds.
Definition: main.cc:63
FILE * petsc_error_fd
file descriptor for petsc error output
Definition: main.cc:59
timer_manager * tm_manager
a manager for the various physics timers
Definition: main.cc:55
std::map< datavec_t, sf_vec * > datavec_reg
important solution vectors from different physics
Definition: main.cc:57
bool using_legacy_stimuli
flag storing whether legacy stimuli are used
Definition: main.cc:61
SF::scatter_registry scatter_reg
Registry for the different scatter objects.
Definition: main.cc:47
std::map< mesh_t, sf_mesh > mesh_reg
Registry for the different meshes used in a multi-physics simulation.
Definition: main.cc:49
std::map< physic_t, Basic_physic * > physics_reg
the physics
Definition: main.cc:53
void time_to_string(float time, char *str, short str_size)
Definition: sim_utils.cc:1867
physic_t
Identifier for the different physics we want to set up.
Definition: physics_types.h:51
@ iotm_chkpt_list
Definition: timer_utils.h:44
@ iotm_console
Definition: timer_utils.h:44
@ iotm_spacedt
Definition: timer_utils.h:44
@ iotm_trace
Definition: timer_utils.h:44
@ iotm_chkpt_intv
Definition: timer_utils.h:44
@ iotm_state_var
Definition: timer_utils.h:44
sf_vec * get_data(datavec_t d)
Retrieve a petsc data vector from the data registry.
Definition: sim_utils.cc:2046
void output_parameter_file(const char *fname, int argc, char **argv)
Definition: sim_utils.cc:2939
void initialize_physics()
Initialize all physics in the registry.
Definition: sim_utils.cc:1203
bool setup_IO(int argc, char **argv)
Definition: sim_utils.cc:1524
void retag_elements(sf_mesh &mesh, TagRegion *tagRegs, int ntr)
Definition: sim_utils.cc:2128
void parse_params_cpy(int argc, char **argv)
Initialize input parameters on a copy of the real command line parameters.
Definition: sim_utils.cc:1134
sf_mesh & get_mesh(const mesh_t gt)
Get a mesh by specifying the gridID.
Definition: sf_interface.cc:33
int set_ignore_flags(int mode)
Definition: sim_utils.cc:1280
SF::scattering * get_permutation(const int mesh_id, const int perm_id, const int dpn)
Get the PETSC to canonical permutation scattering for a given mesh and number of dpn.
SF::meshdata< mesh_int_t, mesh_real_t > sf_mesh
Definition: sf_interface.h:48
datavec_t
Enum used to adress the different data vectors stored in the data registry.
Definition: physics_types.h:99
int set_dir(IO_t dest)
Definition: sim_utils.cc:1582
void cleanup_and_exit()
Definition: sim_utils.cc:2616
void register_physics()
Register physics to the physics registry.
Definition: sim_utils.cc:1181
void post_process()
do postprocessing
Definition: sim_utils.cc:1991
void check_and_convert_params()
Here we want to put all parameter checks, conversions and modifications that have been littered throu...
Definition: sim_utils.cc:1330
int get_rank(MPI_Comm comm=PETSC_COMM_WORLD)
Definition: basics.h:284
short get_mesh_dim(mesh_t id)
get (lowest) dimension of the mesh used in the experiment
Definition: sim_utils.cc:2658
T get_global(T in, MPI_Op OP, MPI_Comm comm=PETSC_COMM_WORLD)
Do a global reduction on a variable.
Definition: basics.h:233
@ GND_ex
Definition: stimulate.h:79
bool is_potential(stim_t type)
uses current for stimulation
Definition: stimulate.cc:67
bool phys_defined(int physreg)
function to check if certain physics are defined
void update_console_output(const timer_manager &tm, prog_stats &p)
Definition: sim_utils.cc:1876
void show_build_info()
show the build info, exit if -buildinfo was provided. This code runs before MPI_Init().
Definition: sim_utils.cc:1318
FILE_SPEC f_open(const char *fname, const char *mode)
Open a FILE_SPEC.
Definition: basics.cc:138
void savequit()
save state and quit simulator
Definition: sim_utils.cc:3024
bool have_permutation(const int mesh_id, const int perm_id, const int dpn)
void set_io_dirs(char *sim_ID, char *pp_ID, IO_t init)
Definition: sim_utils.cc:1453
void register_data(sf_vec *dat, datavec_t d)
Register a data vector in the global registry.
Definition: sim_utils.cc:2056
void basic_timer_setup()
Here we set up the timers that we always want to have, independent of physics.
Definition: sim_utils.cc:1594
char * get_file_dir(const char *file)
Definition: sim_utils.cc:2634
IO_t
The different output (directory) types.
Definition: sim_utils.h:54
@ POSTPROC
Definition: sim_utils.h:54
@ CURDIR
Definition: sim_utils.h:54
@ OUTPUT
Definition: sim_utils.h:54
void get_protocol_column_widths(std::vector< int > &col_width, std::vector< int > &used_timer_ids)
Definition: sim_utils.cc:1643
int get_phys_index(int physreg)
get index in param_globals::phys_region array for a certain phys region
void intersect_output_restriction(SF::vector< mesh_int_t > *&restr, const SF::vector< mesh_int_t > &selected_idx)
Intersect an existing output restriction with another local index set.
Definition: sim_utils.cc:2489
void check_nullspace_ok()
Definition: sim_utils.cc:1301
int postproc_recover_phie()
Definition: electrics.cc:2102
char * dupstr(const char *old_str)
Definition: basics.cc:44
int plot_protocols(const char *fname)
plot simulation protocols (I/O timers, stimuli, boundary conditions, etc)
Definition: sim_utils.cc:1686
void indices_from_geom_shape(SF::vector< mesh_int_t > &idx, const sf_mesh &mesh, const geom_shape shape, const bool nodal)
Populate vertex data with the vertices inside a defined box shape.
Definition: fem_utils.cc:184
void log_msg(FILE_SPEC out, int level, unsigned char flag, const char *fmt,...)
Definition: basics.cc:72
mesh_t
The enum identifying the different meshes we might want to load.
Definition: sf_interface.h:59
@ reference_msh
Definition: sf_interface.h:69
@ extra_elec_msh
Definition: sf_interface.h:61
@ intra_elec_msh
Definition: sf_interface.h:60
void setup_meshes(bool require_fibers=true)
Read in the reference mesh and use its data to populate all meshes registered in the mesh registry.
Definition: sim_utils.cc:2201
void get_time(double &tm)
Definition: basics.h:444
bool mesh_is_registered(const mesh_t gt)
check wheter a SF mesh is set
Definition: sf_interface.cc:63
bool parse_gridout_tags(hashmap::unordered_set< int > &output_tags)
Parse the gridout_tags idset into a unique set of region IDs.
Definition: sim_utils.cc:2400
void validate_gridout_tags(hashmap::unordered_set< int > &output_tags, const SF::vector< mesh_t > &mesh_ids)
Warn about selected gridout tags that are absent from the relevant meshes.
Definition: sim_utils.cc:2439
void output_meshes()
Definition: sim_utils.cc:2519
int get_size(MPI_Comm comm=PETSC_COMM_WORLD)
Definition: basics.h:298
Basic_physic * get_physics(physic_t p, bool error_if_missing)
Convinience function to get a physics.
Definition: sim_utils.cc:2030
size_t renormalise_fibres(SF::vector< mesh_real_t > &fib, size_t l_numelem)
Definition: sim_utils.cc:2177
void destroy_physics()
Destroy all physics in the registry.
Definition: sim_utils.cc:1227
void ignore_extracellular_stim(Stimulus *st, int ns, int ignore)
Definition: sim_utils.cc:1254
void init_console_output(const timer_manager &tm, prog_stats &p)
Definition: sim_utils.cc:1851
tagreg_t
tag regions types. must be in line with carp.prm
Definition: sim_utils.h:57
@ tagreg_list
Definition: sim_utils.h:57
V timing(V &t2, const V &t1)
Definition: basics.h:456
void read_indices(SF::vector< T > &idx, const std::string filename, const hashmap::unordered_map< mesh_int_t, mesh_int_t > &dd_map, MPI_Comm comm)
Read indices from a file.
Definition: fem_utils.h:120
std::string get_basename(const std::string &path)
Definition: basics.cc:61
void build_tagged_nodal_output_restriction(sf_mesh &mesh, const hashmap::unordered_set< int > &output_tags, SF::vector< mesh_int_t > &output_idx, bool async)
Build a local output-vector restriction from mesh element tags.
Definition: sim_utils.cc:2422
void update_cwd()
save the current working directory to curdir so that we can switch back to it if needed.
Definition: sim_utils.cc:1577
void f_close(FILE_SPEC &f)
Close a FILE_SPEC.
Definition: basics.cc:165
void setup_petsc_err_log()
set up error logs for PETSc, so that it doesnt print errors to stderr.
Definition: sim_utils.cc:2643
void simulate()
Main simulate loop.
Definition: sim_utils.cc:1904
void parse_mesh_types()
Parse the phys_type CLI parameters and set up (empty) SF::meshdata meshes.
Definition: sim_utils.cc:2066
#define IGB_VEC9_f
Definition: IGBheader.h:75
#define IGB_VEC3_f
Definition: IGBheader.h:70
#define IGB_FLOAT
Definition: IGBheader.h:59
#define IGB_VEC4_f
Definition: IGBheader.h:72
Top-level header of physics module.
#define PHYSREG_INTRA_ELEC
Definition: sf_interface.h:84
#define PHYSREG_LAPLACE
Definition: sf_interface.h:89
#define PETSC_TO_CANONICAL
Permute algebraic data from PETSC to canonical ordering.
Definition: sf_interface.h:79
#define PHYSREG_EIKONAL
Definition: sf_interface.h:86
#define PHYSREG_EMI
Definition: sf_interface.h:90
#define ELEM_PETSC_TO_CANONICAL
Permute algebraic element data from PETSC to canonical ordering.
Definition: sf_interface.h:81
#define PHYSREG_EXTRA_ELEC
Definition: sf_interface.h:85
std::vector< std::string > runtime_args
Definition: sim_utils.cc:80
bool warn_when_unavailable
Definition: sim_utils.cc:79
ParserFallbackMode fallback_mode
Definition: sim_utils.cc:74
bool exited
Definition: sim_utils.cc:85
int exit_status
Definition: sim_utils.cc:86
bool available
Definition: sim_utils.cc:78
int signal
Definition: sim_utils.cc:88
bool signaled
Definition: sim_utils.cc:87
std::string unavailable_reason
Definition: sim_utils.cc:81
Simulator-level utility execution control functions.
#define BIDOMAIN
Definition: sim_utils.h:189
#define RECOVER_PHIE
Definition: sim_utils.h:194
#define MONODOMAIN
Definition: sim_utils.h:188
#define EXP_POSTPROCESS
Definition: sim_utils.h:207
#define PSEUDO_BIDM
Definition: sim_utils.h:190
#define LEADFIELD
Definition: sim_utils.h:200
#define EXP_LAPLACE
Definition: sim_utils.h:205
#define Extracellular_I
Definition: stimulate.h:38
#define Extracellular_V
Definition: stimulate.h:39
#define STM_IGNORE_PSEUDO_BIDM
Definition: stimulate.h:63
#define NO_EXTRA_GND
Definition: stimulate.h:57
#define NO_EXTRA_I
Definition: stimulate.h:59
#define Intracellular_I
Definition: stimulate.h:41
#define Ignore_Stim
Definition: stimulate.h:49
#define STM_IGNORE_MONODOMAIN
Definition: stimulate.h:62
#define IsExtraV(A)
Definition: stimulate.h:52
#define IGNORE_NONE
Definition: stimulate.h:56
#define STM_IGNORE_BIDOMAIN
Definition: stimulate.h:61
#define Extracellular_Ground
Definition: stimulate.h:40
#define Extracellular_V_OL
Definition: stimulate.h:42
#define Transmembrane_I
Definition: stimulate.h:37
#define NO_EXTRA_V
Definition: stimulate.h:58
const SF::vector< mesh_int_t > * restr_idx
when using asyncIO, here we store the different IDs associated to the vectors we output
Definition: sim_utils.h:337
SF::vector< mesh_int_t > restr_petsc_idx
pointer to index vector with nodal indices we restrict to.
Definition: sim_utils.h:338
int IO_id
pointer to data registered for output
Definition: sim_utils.h:336
long d_trigger_dur
discrete duration
Definition: timer_utils.h:58
const char * name
timer name
Definition: timer_utils.h:53
bool triggered
flag indicating trigger at current time step
Definition: timer_utils.h:54
File descriptor struct.
Definition: basics.h:135
for display execution progress and statistical data of electrical solve
Definition: sim_utils.h:318
double curr
current output wallclock time
Definition: sim_utils.h:322
double start
output start wallclock time
Definition: sim_utils.h:320
double last
last output wallclock time
Definition: sim_utils.h:321
SF::vector< mesh_int_t > restr_global_idx
pointer to index vector used for restricting output.
Definition: sim_utils.h:328
bool elem_flag
igb header we use for output
Definition: sim_utils.h:330
IGBheader igb
global canonical indices matching restr_idx.
Definition: sim_utils.h:329
const SF::vector< mesh_int_t > * restr_idx
pointer to data registered for output
Definition: sim_utils.h:327
SF::mixed_tuple< mesh_t, int > spec
flag whether the data is elements-wise
Definition: sim_utils.h:331