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 <string>
49 #include <sys/wait.h>
50 #include <unistd.h>
51 #include <vector>
52 
53 #include "openCARP_schema.hpp"
54 #include "runtime.hpp"
55 #include "snapshot_file_io.hpp"
56 
57 namespace opencarp {
58 
59 namespace {
60 
61 enum class ParserCompareMode {
62  Off,
63  Warn,
64  Strict,
65 };
66 
67 enum class ParserFallbackMode {
68  Off,
69  Legacy,
70 };
71 
72 struct RuntimeCompatOptions {
73  ParserFallbackMode fallback_mode = ParserFallbackMode::Off;
74 };
75 
76 struct LegacyCompareInput {
77  bool available = false;
78  std::vector<std::string> runtime_args;
79  std::string unavailable_reason;
80 };
81 
82 std::vector<paramschema::CitationSuggestion> active_citation_suggestions;
83 
84 std::string trim_copy(const std::string& value);
85 std::string to_lower_ascii(std::string value);
86 bool build_legacy_compare_input(int argc, char** argv, LegacyCompareInput* input, std::string* error);
87 
88 bool match_long_option(const std::string& token, const char* option, std::string* attached_value)
89 {
90  *attached_value = std::string();
91  if (token == option) {
92  return true;
93  }
94 
95  const std::string prefix = std::string(option) + "=";
96  if (token.size() > prefix.size() && token.compare(0, prefix.size(), prefix) == 0) {
97  *attached_value = token.substr(prefix.size());
98  return true;
99  }
100 
101  return false;
102 }
103 
104 bool is_help_topic_candidate(const char* token)
105 {
106  return token != NULL && token[0] != '\0' && token[0] != '-' && token[0] != '+';
107 }
108 
109 bool is_long_option_argument_error(const std::string& token,
110  const char* option,
111  const std::string& attached_value,
112  std::string* error)
113 {
114  if (attached_value.empty()) {
115  return false;
116  }
117 
118  *error = "Unexpected argument for " + std::string(option) + " in '" + token + "'";
119  return true;
120 }
121 
122 bool normalize_runtime_args(int argc,
123  char** argv,
124  std::vector<std::string>* normalized,
125  RuntimeCompatOptions* compat,
126  std::string* error)
127 {
128  normalized->clear();
129  compat->fallback_mode = ParserFallbackMode::Off;
130 
131  if (argc <= 0 || argv == NULL || argv[0] == NULL) {
132  *error = "Missing program name";
133  return false;
134  }
135 
136  normalized->push_back(argv[0]);
137 
138  for (int i = 1; i < argc; ++i) {
139  const std::string token = argv[i];
140  if (token == "+") {
141  break;
142  }
143 
144  std::string attached_value;
145 
146  if (token == "+Help" || match_long_option(token, "--help", &attached_value)) {
147  std::string topic = "PrM";
148  if (!attached_value.empty()) {
149  topic = attached_value;
150  } else if (i + 1 < argc && is_help_topic_candidate(argv[i + 1])) {
151  topic = argv[++i];
152  }
153  normalized->push_back("+Help");
154  normalized->push_back(topic);
155  break;
156  }
157 
158  if (token == "+Doc" || match_long_option(token, "--doc", &attached_value)) {
159  std::string topic = "ALL";
160  if (!attached_value.empty()) {
161  topic = attached_value;
162  } else if (i + 1 < argc && is_help_topic_candidate(argv[i + 1])) {
163  topic = argv[++i];
164  }
165  normalized->push_back("+Doc");
166  normalized->push_back(topic);
167  break;
168  }
169 
170  if (token == "+Default" || match_long_option(token, "--default", &attached_value)) {
171  if (token != "+Default" && is_long_option_argument_error(token, "--default", attached_value, error)) {
172  return false;
173  }
174  normalized->push_back("+Default");
175  break;
176  }
177 
178  if (token == "+Run" || match_long_option(token, "--run", &attached_value)) {
179  if (token != "+Run" && is_long_option_argument_error(token, "--run", attached_value, error)) {
180  return false;
181  }
182  normalized->push_back("+Run");
183  continue;
184  }
185 
186  if (token == "+I" || match_long_option(token, "--interactive", &attached_value)) {
187  if (!attached_value.empty()) {
188  *error = "Unexpected argument for --interactive in '" + token + "'";
189  } else {
190  *error = "Unsupported option " + token + " (interactive mode is not available)";
191  }
192  return false;
193  }
194 
195  if (match_long_option(token, "--param-fallback", &attached_value)) {
196  std::string mode = attached_value;
197  if (mode.empty()) {
198  if (i + 1 >= argc) {
199  *error = "Missing argument after --param-fallback";
200  return false;
201  }
202  mode = argv[++i];
203  }
204 
205  if (to_lower_ascii(trim_copy(mode)) != "legacy") {
206  *error = "Unsupported value '" + mode + "' for --param-fallback (expected legacy)";
207  return false;
208  }
209 
210  compat->fallback_mode = ParserFallbackMode::Legacy;
211  continue;
212  }
213 
214  if (token == "+F" || match_long_option(token, "--file", &attached_value)) {
215  std::string filename = attached_value;
216  if (filename.empty()) {
217  if (i + 1 >= argc) {
218  *error = "Missing filename after " + token;
219  return false;
220  }
221  filename = argv[++i];
222  }
223  normalized->push_back("+F");
224  normalized->push_back(filename);
225  continue;
226  }
227 
228  if (token == "+Save" || match_long_option(token, "--save", &attached_value)) {
229  std::string filename = attached_value;
230  if (filename.empty()) {
231  if (i + 1 >= argc) {
232  *error = "Missing argument after " + token;
233  return false;
234  }
235  filename = argv[++i];
236  }
237  normalized->push_back("+Save");
238  normalized->push_back(filename);
239  continue;
240  }
241 
242  normalized->push_back(token);
243  }
244 
245  return true;
246 }
247 
248 bool parse_option_argument(int* index,
249  int argc,
250  char** argv,
251  const std::string& token,
252  const std::string& attached_value,
253  const char* option_name,
254  std::string* value,
255  std::string* error)
256 {
257  if (!attached_value.empty()) {
258  *value = attached_value;
259  return true;
260  }
261  if (*index + 1 >= argc) {
262  *error = "Missing argument after " + std::string(option_name);
263  return false;
264  }
265  *value = argv[++(*index)];
266  return true;
267 }
268 
269 bool filename_has_suffix(const std::string& filename, const char* suffix)
270 {
271  const std::string normalized_filename = to_lower_ascii(trim_copy(filename));
272  const std::string normalized_suffix = to_lower_ascii(std::string(suffix == NULL ? "" : suffix));
273  return normalized_filename.size() >= normalized_suffix.size() &&
274  normalized_filename.compare(normalized_filename.size() - normalized_suffix.size(),
275  normalized_suffix.size(),
276  normalized_suffix) == 0;
277 }
278 
279 bool build_legacy_compare_input(int argc, char** argv, LegacyCompareInput* input, std::string* error)
280 {
281  input->available = false;
282  input->runtime_args.clear();
283  input->unavailable_reason.clear();
284 
285  if (argc <= 0 || argv == NULL || argv[0] == NULL) {
286  *error = "Missing program name";
287  return false;
288  }
289 
290  input->runtime_args.push_back(argv[0]);
291  bool saw_passthrough_token_before_file = false;
292 
293  for (int i = 1; i < argc; ++i) {
294  const std::string token = argv[i];
295  if (token == "+") {
296  break;
297  }
298 
299  std::string attached_value;
300 
301  if (match_long_option(token, "--param-fallback", &attached_value)) {
302  std::string ignored;
303  if (!parse_option_argument(&i, argc, argv, token, attached_value, "--param-fallback", &ignored, error)) {
304  return false;
305  }
306  continue;
307  }
308 
309  if (token == "+Save" || match_long_option(token, "--save", &attached_value)) {
310  std::string ignored;
311  if (!parse_option_argument(&i, argc, argv, token, attached_value, token.c_str(), &ignored, error)) {
312  return false;
313  }
314  continue;
315  }
316 
317  if (token == "+Help" || match_long_option(token, "--help", &attached_value)) {
318  std::string topic = "PrM";
319  if (!attached_value.empty()) {
320  topic = attached_value;
321  } else if (i + 1 < argc && is_help_topic_candidate(argv[i + 1])) {
322  topic = argv[++i];
323  }
324  input->runtime_args.push_back("+Help");
325  input->runtime_args.push_back(topic);
326  input->available = true;
327  return true;
328  }
329 
330  if (token == "+Doc" || match_long_option(token, "--doc", &attached_value)) {
331  std::string topic = "ALL";
332  if (!attached_value.empty()) {
333  topic = attached_value;
334  } else if (i + 1 < argc && is_help_topic_candidate(argv[i + 1])) {
335  topic = argv[++i];
336  }
337  input->runtime_args.push_back("+Doc");
338  input->runtime_args.push_back(topic);
339  input->available = true;
340  return true;
341  }
342 
343  if (token == "+Default" || match_long_option(token, "--default", &attached_value)) {
344  if (token != "+Default" && is_long_option_argument_error(token, "--default", attached_value, error)) {
345  return false;
346  }
347  input->runtime_args.push_back("+Default");
348  continue;
349  }
350 
351  if (token == "+Run" || match_long_option(token, "--run", &attached_value)) {
352  if (token != "+Run" && is_long_option_argument_error(token, "--run", attached_value, error)) {
353  return false;
354  }
355  input->runtime_args.push_back("+Run");
356  continue;
357  }
358 
359  if (token == "+F" || match_long_option(token, "--file", &attached_value)) {
360  std::string filename;
361  if (!parse_option_argument(&i, argc, argv, token, attached_value, token.c_str(), &filename, error)) {
362  return false;
363  }
364 
365  if (saw_passthrough_token_before_file) {
366  input->unavailable_reason =
367  "legacy compare is unavailable when direct parameter arguments precede a .par input file";
368  input->runtime_args.clear();
369  input->runtime_args.push_back(argv[0]);
370  return true;
371  }
372 
373  if (!filename_has_suffix(filename, ".par")) {
374  input->unavailable_reason =
375  "legacy compare is unavailable for non-.par input file '" + filename + "'";
376  input->runtime_args.clear();
377  input->runtime_args.push_back(argv[0]);
378  return true;
379  }
380 
381  input->runtime_args.push_back("+F");
382  input->runtime_args.push_back(filename);
383  continue;
384  }
385 
386  input->runtime_args.push_back(token);
387  saw_passthrough_token_before_file = true;
388  }
389 
390  input->available = input->runtime_args.size() > 1;
391  if (!input->available && input->unavailable_reason.empty()) {
392  input->unavailable_reason = "unable to reconstruct a legacy-compatible parameter input";
393  }
394  return true;
395 }
396 
397 std::string trim_copy(const std::string& value)
398 {
399  std::string::size_type first = 0;
400  while (first < value.size() && std::isspace(static_cast<unsigned char>(value[first]))) {
401  ++first;
402  }
403 
404  std::string::size_type last = value.size();
405  while (last > first && std::isspace(static_cast<unsigned char>(value[last - 1]))) {
406  --last;
407  }
408 
409  return value.substr(first, last - first);
410 }
411 
412 std::string to_lower_ascii(std::string value)
413 {
414  for (std::string::size_type i = 0; i < value.size(); ++i) {
415  value[i] = static_cast<char>(std::tolower(static_cast<unsigned char>(value[i])));
416  }
417  return value;
418 }
419 
420 ParserCompareMode parser_compare_mode()
421 {
422  const char* raw = std::getenv("OPENCARP_PARAM_COMPARE");
423  if (raw == NULL) {
424  return ParserCompareMode::Strict;
425  }
426 
427  const std::string normalized = to_lower_ascii(trim_copy(raw));
428  if (normalized.empty() || normalized == "1" || normalized == "on" || normalized == "true" ||
429  normalized == "yes" || normalized == "strict" || normalized == "fail" || normalized == "error") {
430  return ParserCompareMode::Strict;
431  }
432  if (normalized == "warn") {
433  return ParserCompareMode::Warn;
434  }
435  if (normalized == "0" || normalized == "off" || normalized == "false" || normalized == "no") {
436  return ParserCompareMode::Off;
437  }
438 
439  return ParserCompareMode::Strict;
440 }
441 
442 ParserFallbackMode parser_fallback_mode()
443 {
444  const char* raw = std::getenv("OPENCARP_PARAM_FALLBACK");
445  if (raw == NULL) {
446  return ParserFallbackMode::Off;
447  }
448 
449  const std::string normalized = to_lower_ascii(trim_copy(raw));
450  if (normalized.empty() || normalized == "0" || normalized == "off" || normalized == "false" ||
451  normalized == "no") {
452  return ParserFallbackMode::Off;
453  }
454  if (normalized == "legacy") {
455  return ParserFallbackMode::Legacy;
456  }
457 
458  return ParserFallbackMode::Off;
459 }
460 
461 void populate_arg_pointers(const std::vector<std::string>& values, std::vector<char*>* argv)
462 {
463  argv->assign(values.size(), NULL);
464  for (std::size_t i = 0; i < values.size(); ++i) {
465  (*argv)[i] = const_cast<char*>(values[i].c_str());
466  }
467 }
468 
469 void print_lines(FILE* stream, const char* label, const std::vector<std::string>& lines)
470 {
471  for (std::size_t i = 0; i < lines.size(); ++i) {
472  fprintf(stream, "%s%s\n", label, lines[i].c_str());
473  }
474 }
475 
476 void clear_active_citation_suggestions()
477 {
478  active_citation_suggestions.clear();
479 }
480 
481 void store_active_citation_suggestions(const std::vector<paramschema::CitationSuggestion>& suggestions)
482 {
483  active_citation_suggestions = suggestions;
484 }
485 
486 void print_active_citation_suggestions()
487 {
488  if (active_citation_suggestions.empty() || get_rank() != 0) {
489  return;
490  }
491 
492  const std::vector<std::string> lines =
493  paramschema::format_citation_suggestions(active_citation_suggestions);
494  if (lines.empty()) {
495  return;
496  }
497 
498  std::fprintf(stdout, "\nIf you publish studies based on this simulation, the following references are likely relevant:\n");
499  for (std::size_t i = 0; i < lines.size(); ++i) {
500  std::fprintf(stdout, " %s\n", lines[i].c_str());
501  }
502  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");
503  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");
504 
505  std::fflush(stdout);
506 }
507 
508 void print_parser_runtime_state(const std::vector<std::string>& runtime_args)
509 {
510  const auto& schema = paramschema::openCARP_schema();
511  const std::string program_name =
512  runtime_args.empty() || runtime_args[0].empty() ? "openCARP" : runtime_args[0];
513 
514  std::string rendered;
515  std::vector<std::string> errors;
516  if (!paramschema::render_save_text(schema, program_name, &rendered, &errors)) {
517  print_lines(stderr, "parameter parser warning: ", errors);
518  return;
519  }
520 
521  fputs(rendered.c_str(), stderr);
522 }
523 
524 paramschema::ExecutionResult execute_parser_runtime_args(const std::vector<std::string>& runtime_args,
525  const bool allow_save)
526 {
527  std::vector<char*> argv;
528  populate_arg_pointers(runtime_args, &argv);
529 
530  const auto& schema = paramschema::openCARP_schema();
531 
532  paramschema::ExecutionOptions options;
533  options.allow_save = allow_save;
534  return paramschema::execute_legacy_cli(schema, static_cast<int>(argv.size()), argv.data(), options);
535 }
536 
537 bool apply_parser_runtime_args(const std::vector<std::string>& runtime_args,
538  const bool allow_save,
539  paramschema::ExecutionResult* executed_out)
540 {
541  clear_active_citation_suggestions();
542  const paramschema::ExecutionResult executed = execute_parser_runtime_args(runtime_args, allow_save);
543  if (executed_out != NULL) {
544  *executed_out = executed;
545  }
546  print_lines(stderr, "parameter parser warning: ", executed.warnings);
547  if (!executed.rendered_output.empty()) {
548  fputs(executed.rendered_output.c_str(), stdout);
549  }
550  if (!executed.errors.empty() || executed.status == paramschema::ExecutionStatus::Fatal) {
551  print_lines(stderr, "parameter parser error: ", executed.errors);
552  return false;
553  }
554 
555  if (param_globals::output_setup) {
556  print_parser_runtime_state(runtime_args);
557  }
558 
559  if (executed.status == paramschema::ExecutionStatus::Help) {
560  exit(EXIT_SUCCESS);
561  }
562 
563  store_active_citation_suggestions(executed.citations);
564  return true;
565 }
566 
567 std::string parent_directory(const std::string& path)
568 {
569  const std::string::size_type slash = path.rfind('/');
570  if (slash == std::string::npos) {
571  return ".";
572  }
573  if (slash == 0) {
574  return "/";
575  }
576  return path.substr(0, slash);
577 }
578 
579 std::string join_path(const std::string& left, const std::string& right)
580 {
581  if (left.empty() || left == ".") {
582  return right;
583  }
584  if (!left.empty() && left[left.size() - 1] == '/') {
585  return left + right;
586  }
587  return left + "/" + right;
588 }
589 
590 bool find_executable_on_path(const std::string& name, std::string* resolved_path)
591 {
592  if (name.empty() || name.find('/') != std::string::npos) {
593  return false;
594  }
595 
596  const char* path_env = std::getenv("PATH");
597  if (path_env == NULL || path_env[0] == '\0') {
598  return false;
599  }
600 
601  const std::string path_list = path_env;
602  std::string::size_type start = 0;
603  while (start <= path_list.size()) {
604  std::string::size_type end = path_list.find(':', start);
605  if (end == std::string::npos) {
606  end = path_list.size();
607  }
608 
609  const std::string directory = path_list.substr(start, end - start);
610  const std::string candidate = join_path(directory.empty() ? "." : directory, name);
611  if (access(candidate.c_str(), X_OK) == 0) {
612  *resolved_path = candidate;
613  return true;
614  }
615 
616  if (end == path_list.size()) {
617  break;
618  }
619  start = end + 1;
620  }
621 
622  return false;
623 }
624 
625 bool resolve_legacy_snapshot_helper(const std::string& program_path, std::string* helper_path)
626 {
627  std::vector<std::string> resolved_program_paths;
628  if (!program_path.empty()) {
629  resolved_program_paths.push_back(program_path);
630  }
631 
632  if (program_path.find('/') == std::string::npos) {
633  std::string resolved_program_path;
634  if (find_executable_on_path(program_path, &resolved_program_path)) {
635  resolved_program_paths.push_back(resolved_program_path);
636  }
637  }
638 
639  for (std::size_t i = 0; i < resolved_program_paths.size(); ++i) {
640  const std::string program_dir = parent_directory(resolved_program_paths[i]);
641  const std::string parent_dir = parent_directory(program_dir);
642 
643  const std::vector<std::string> candidates = {
644  join_path(program_dir, "param-parser-legacy-snapshot"),
645  join_path(join_path(parent_dir, "simulator"), "param-parser-legacy-snapshot"),
646  };
647 
648  for (std::size_t j = 0; j < candidates.size(); ++j) {
649  if (access(candidates[j].c_str(), X_OK) == 0) {
650  *helper_path = candidates[j];
651  return true;
652  }
653  }
654  }
655 
656  return find_executable_on_path("param-parser-legacy-snapshot", helper_path);
657 }
658 
659 bool create_temp_output_path(const char* suffix, std::string* path)
660 {
661  char temp_path[128];
662  if (suffix != NULL && suffix[0] != '\0') {
663  std::snprintf(temp_path, sizeof temp_path, "/tmp/opencarp-parser-compare-XXXXXX%s", suffix);
664  } else {
665  std::snprintf(temp_path, sizeof temp_path, "/tmp/opencarp-parser-compare-XXXXXX");
666  }
667 
668  const int fd = suffix != NULL && suffix[0] != '\0' ? mkstemps(temp_path, static_cast<int>(std::strlen(suffix))) :
669  mkstemp(temp_path);
670  if (fd < 0) {
671  return false;
672  }
673  close(fd);
674  *path = temp_path;
675  return true;
676 }
677 
678 bool capture_current_parser_snapshot(paramschema::SnapshotResult* snapshot)
679 {
680  *snapshot = paramschema::snapshot_schema_state(paramschema::openCARP_schema());
681  print_lines(stderr, "parameter compare warning: ", snapshot->warnings);
682  if (!snapshot->errors.empty()) {
683  print_lines(stderr, "parameter compare error: ", snapshot->errors);
684  return false;
685  }
686  return true;
687 }
688 
689 void maybe_force_test_mismatch(paramschema::SnapshotResult* snapshot)
690 {
691  const char* raw = std::getenv("OPENCARP_PARAM_TEST_FORCE_MISMATCH");
692  if (raw == NULL) {
693  return;
694  }
695 
696  const std::string normalized = to_lower_ascii(trim_copy(raw));
697  if (normalized.empty() || normalized == "0" || normalized == "off" || normalized == "false" ||
698  normalized == "no") {
699  return;
700  }
701 
702  if (!snapshot->entries.empty()) {
703  snapshot->entries[0].value += "__forced_parser_compare_mismatch__";
704  return;
705  }
706 
707  paramschema::SnapshotEntry entry;
708  entry.path = "buildinfo";
709  entry.value = "__forced_parser_compare_mismatch__";
710  snapshot->entries.push_back(entry);
711 }
712 
713 bool run_legacy_snapshot_helper(const std::string& helper_path,
714  const std::vector<std::string>& runtime_args,
715  const std::string& output_path)
716 {
717  std::vector<std::string> child_values;
718  child_values.reserve(runtime_args.size() + 4);
719  child_values.push_back(helper_path);
720  child_values.push_back("--snapshot-out");
721  child_values.push_back(output_path);
722  child_values.push_back("--");
723  child_values.insert(child_values.end(), runtime_args.begin(), runtime_args.end());
724 
725  std::vector<char*> child_argv;
726  child_argv.reserve(child_values.size() + 1);
727  for (std::size_t i = 0; i < child_values.size(); ++i) {
728  child_argv.push_back(const_cast<char*>(child_values[i].c_str()));
729  }
730  child_argv.push_back(NULL);
731 
732  const pid_t pid = fork();
733  if (pid < 0) {
734  std::perror("fork");
735  return false;
736  }
737 
738  if (pid == 0) {
739  execv(helper_path.c_str(), child_argv.data());
740  std::perror("execv");
741  _exit(127);
742  }
743 
744  int status = 0;
745  if (waitpid(pid, &status, 0) < 0) {
746  std::perror("waitpid");
747  return false;
748  }
749 
750  if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
751  if (WIFSIGNALED(status)) {
752  std::fprintf(stderr, "parameter compare error: legacy snapshot helper terminated with signal %d\n",
753  WTERMSIG(status));
754  } else {
755  std::fprintf(stderr, "parameter compare error: legacy snapshot helper exited with status %d\n",
756  WEXITSTATUS(status));
757  }
758  return false;
759  }
760 
761  return true;
762 }
763 
764 bool legacy_fallback_requested(const RuntimeCompatOptions& compat)
765 {
766  return compat.fallback_mode == ParserFallbackMode::Legacy ||
767  parser_fallback_mode() == ParserFallbackMode::Legacy;
768 }
769 
770 void print_legacy_fallback_workaround()
771 {
772  std::fprintf(stderr,
773  "parameter compare error: rerun with OPENCARP_PARAM_FALLBACK=legacy to continue with the legacy parameter state\n");
774  std::fprintf(stderr,
775  "parameter compare error: or add --param-fallback=legacy to the openCARP command line\n");
776 }
777 
778 bool restore_legacy_snapshot_state(const paramschema::SnapshotResult& legacy_snapshot)
779 {
780  const paramschema::SnapshotRestoreResult restored =
781  paramschema::restore_snapshot_state(paramschema::openCARP_schema(), legacy_snapshot);
782  print_lines(stderr, "parameter compare warning: ", restored.warnings);
783  if (!restored.errors.empty()) {
784  print_lines(stderr, "parameter compare error: ", restored.errors);
785  return false;
786  }
787  return true;
788 }
789 
790 bool run_parser_legacy_compare(const std::vector<std::string>& runtime_args,
791  const LegacyCompareInput& legacy_input,
792  const RuntimeCompatOptions& compat)
793 {
794  const ParserCompareMode mode = parser_compare_mode();
795  if (mode == ParserCompareMode::Off) {
796  return true;
797  }
798  const bool fallback_to_legacy = legacy_fallback_requested(compat);
799 
800  if (runtime_args.empty()) {
801  std::fprintf(stderr, "parameter compare error: unable to reconstruct simulator argv\n");
802  if (fallback_to_legacy) {
803  print_legacy_fallback_workaround();
804  }
805  return mode != ParserCompareMode::Strict;
806  }
807 
808  paramschema::SnapshotResult parser_snapshot;
809  if (!capture_current_parser_snapshot(&parser_snapshot)) {
810  std::fprintf(stderr, "parameter compare error: unable to snapshot the parser runtime state\n");
811  if (fallback_to_legacy) {
812  print_legacy_fallback_workaround();
813  }
814  return mode != ParserCompareMode::Strict;
815  }
816  maybe_force_test_mismatch(&parser_snapshot);
817 
818  std::string helper_path;
819  if (!resolve_legacy_snapshot_helper(runtime_args[0], &helper_path)) {
820  std::fprintf(stderr, "parameter compare error: unable to locate param-parser-legacy-snapshot\n");
821  if (fallback_to_legacy) {
822  print_legacy_fallback_workaround();
823  } else {
824  std::fprintf(stderr,
825  "parameter compare error: rerun with OPENCARP_PARAM_COMPARE=0 to bypass this temporary compatibility gate\n");
826  }
827  return mode != ParserCompareMode::Strict;
828  }
829 
830  if (!legacy_input.available) {
831  std::fprintf(stderr, "parameter compare warning: %s\n", legacy_input.unavailable_reason.c_str());
832  if (fallback_to_legacy) {
833  std::fprintf(stderr,
834  "parameter compare warning: legacy fallback is unavailable because no legacy baseline exists for this input\n");
835  }
836  return true;
837  }
838 
839  std::string snapshot_path;
840  if (!create_temp_output_path("", &snapshot_path)) {
841  std::fprintf(stderr, "parameter compare error: unable to create temporary snapshot file\n");
842  if (fallback_to_legacy) {
843  print_legacy_fallback_workaround();
844  } else {
845  std::fprintf(stderr,
846  "parameter compare error: rerun with OPENCARP_PARAM_COMPARE=0 to bypass this temporary compatibility gate\n");
847  }
848  return mode != ParserCompareMode::Strict;
849  }
850 
851  const bool helper_ok = run_legacy_snapshot_helper(helper_path, legacy_input.runtime_args, snapshot_path);
852  paramschema::SnapshotResult legacy_snapshot;
853  std::string read_error;
854  const bool loaded = helper_ok &&
855  paramschema::snapshotio::read_snapshot_file(snapshot_path, &legacy_snapshot, &read_error);
856  unlink(snapshot_path.c_str());
857 
858  if (!loaded) {
859  if (!read_error.empty()) {
860  std::fprintf(stderr, "parameter compare error: %s\n", read_error.c_str());
861  } else {
862  std::fprintf(stderr, "parameter compare error: unable to capture the legacy parameter state\n");
863  }
864  if (fallback_to_legacy) {
865  print_legacy_fallback_workaround();
866  } else {
867  std::fprintf(stderr,
868  "parameter compare error: rerun with OPENCARP_PARAM_COMPARE=0 to bypass this temporary compatibility gate\n");
869  }
870  return mode != ParserCompareMode::Strict;
871  }
872 
873  const paramschema::SnapshotComparisonResult comparison =
874  paramschema::compare_snapshot_results(paramschema::openCARP_schema(), parser_snapshot, legacy_snapshot);
875  if (!comparison.errors.empty() || !comparison.mismatches.empty()) {
876  print_lines(stderr, "", paramschema::format_snapshot_comparison_report(comparison, "parser compare"));
877  std::fprintf(stderr,
878  "parameter compare error: the parser runtime and legacy param() produced different parameter states\n");
879  std::fprintf(stderr,
880  "parameter compare error: please open an issue and include the triggering command line and parameter files\n");
881  std::fprintf(stderr,
882  "https://git.opencarp.org/openCARP/openCARP/-/issues/new?type=ISSUE&initialCreationContext=list-route\n");
883 
884  if (fallback_to_legacy) {
885  if (!restore_legacy_snapshot_state(legacy_snapshot)) {
886  print_legacy_fallback_workaround();
887  return false;
888  }
889  clear_active_citation_suggestions();
890  std::fprintf(stderr, "parameter compare warning: continuing with the legacy parameter state\n");
891  return true;
892  }
893 
894  print_legacy_fallback_workaround();
895  return mode != ParserCompareMode::Strict;
896  }
897 
898  return true;
899 }
900 
901 } // namespace
902 
903 static char input_dir[1024], // directory from which to read input
904  output_dir[1024], // directory to which to write results
905  postproc_dir[1024], // postprocessing directory
906  current_dir[1024]; // current directory
907 
908 void parse_params_cpy(int argc, char** argv)
909 {
910  LegacyCompareInput legacy_input;
911  std::vector<std::string> normalized_args;
912  RuntimeCompatOptions compat;
913  std::string normalize_error;
914  if (!build_legacy_compare_input(argc, argv, &legacy_input, &normalize_error) ||
915  !normalize_runtime_args(argc, argv, &normalized_args, &compat, &normalize_error)) {
916  fprintf(stderr, "\n*** %s\n\n", normalize_error.c_str());
917  exit(EXIT_FAILURE);
918  }
919 
920  paramschema::ExecutionResult executed;
921  if (!apply_parser_runtime_args(normalized_args, true, &executed)) {
922  exit(EXIT_FAILURE);
923  }
924 
925  if (legacy_input.available) {
926  for (std::size_t i = 0; i < executed.validation.assignments.size(); ++i) {
927  if (!executed.validation.assignments[i].synthesized) {
928  continue;
929  }
930  legacy_input.available = false;
931  legacy_input.runtime_args.clear();
932  legacy_input.runtime_args.push_back(normalized_args[0]);
933  legacy_input.unavailable_reason =
934  "legacy compare is unavailable because the parser inferred optional controller counts from the original input";
935  break;
936  }
937  }
938 
939  if (!run_parser_legacy_compare(normalized_args, legacy_input, compat)) {
940  exit(EXIT_FAILURE);
941  }
942 }
943 
944 
946 {
947  // here all the physics can be registered to the physics registry
948  // then they should be processed automatically
952 #if WITH_EMI_MODEL
953  user_globals::physics_reg[emi_phys] = new EMI();
954 #else
955  log_msg(NULL, 5, ECHO, "The EMI model was not compiled for this binary.\n");
956  exit(EXIT_FAILURE);
957 #endif
958  }
959 
962 
965 }
966 
968 {
969  log_msg(0,0,0, "\n *** Initializing physics ***\n");
970 
971  //load in the external imp modules
972 #ifdef HAVE_DLOPEN
973  for (int ii = 0; ii < param_globals::num_external_imp; ii++) {
974  int loading_succeeded = limpet::load_ionic_module(param_globals::external_imp[ii]);
975  assert(loading_succeeded);
976  }
977 #else
978  if(param_globals::num_external_imp)
979  log_msg(NULL, 4, ECHO,"Loading of external LIMPET modules not enabled.\n"
980  "Recompile with DLOPEN set.\n" );
981 #endif
982 
983  // init physics via Basic_physic interface
984  for(auto it : user_globals::physics_reg) {
985  Basic_physic* p = it.second;
986  log_msg(NULL, 0, 0, "Initializing %s ..", p->name);
987  p->initialize();
988  }
989 }
990 
992 {
993  log_msg(0,0,0, "\n *** Destroying physics ***\n");
994 
995  for(auto it : user_globals::physics_reg) {
996  Basic_physic* p = it.second;
997  log_msg(NULL, 0, 0, "Destroying %s ..", p->name);
998  p->destroy();
999  }
1000 }
1001 
1002 // ignore_extracellular stim must be moved to stimulate.cc to be able
1003 // to use all defined set operations instead of defines
1004 
1018 void ignore_extracellular_stim(Stimulus *st, int ns, int ignore)
1019 {
1020  // needs to be switch to stim enum types defined in stimulate.h
1021  for ( int i=0; i<ns; i++ ) {
1022  int turn_off = 0;
1023  turn_off += (st[i].stimtype == Extracellular_Ground) && (ignore & NO_EXTRA_GND);
1024  turn_off += (IsExtraV(st[i])) && (ignore & NO_EXTRA_V);
1025  turn_off += (st[i].stimtype==Extracellular_I) && (ignore & NO_EXTRA_I);
1026 
1027  if (turn_off) {
1028  st[i].stimtype = Ignore_Stim;
1029  log_msg( NULL, 1, 0, "Extracellular stimulus %d ignored for monodomain", i );
1030  } else if ( st[i].stimtype==Intracellular_I ) {
1031  st[i].stimtype = Transmembrane_I;
1032  log_msg( NULL, 1, 0, "Intracellular stimulus %d converted to transmembrane", i );
1033  }
1034  }
1035 }
1036 
1044 int set_ignore_flags( int mode )
1045 {
1046  if(mode==MONODOMAIN)
1047  return STM_IGNORE_MONODOMAIN;
1048  if(mode==BIDOMAIN)
1049  return STM_IGNORE_BIDOMAIN;
1050  if(mode==PSEUDO_BIDM)
1051  return STM_IGNORE_PSEUDO_BIDM;
1052 
1053  return IGNORE_NONE;
1054 }
1055 
1056 
1066 {
1067  Stimulus* s = param_globals::stimulus;
1068 
1069  for(int i=0; i < param_globals::num_stim; i++) {
1070  if(s[i].stimtype == Extracellular_Ground ||
1071  s[i].stimtype == Extracellular_V ||
1072  s[i].stimtype == Extracellular_V_OL)
1073  return;
1074  }
1075 
1076  // for now we only warn, although we should actually stop the run
1077  log_msg( NULL, 4, 0,"Elliptic system is singular!\n"
1078  "Use an explicit ground:voltage (stimulus[X].stimtype=3)\n"
1079  "Do not trust the elliptic solution of this simulation run!\n");
1080 }
1081 
1083 {
1084  const char* real_precision = OPENCARP_REAL_BITS == 32 ? "single" : "double";
1085  printf("\n""*** GIT tag: %s\n", GIT_COMMIT_TAG);
1086  printf( "*** GIT hash: %s\n", GIT_COMMIT_HASH);
1087  printf( "*** GIT repo: %s\n", GIT_PATH);
1088  printf( "*** local index bits: %d\n", OPENCARP_LOCAL_INDEX_BITS);
1089  printf( "*** global index bits: %d\n", OPENCARP_GLOBAL_INDEX_BITS);
1090  printf( "*** real precision: %s (%d-bit)\n", real_precision, OPENCARP_REAL_BITS);
1091  printf( "*** dependency commits: %s\n\n", SUBREPO_COMMITS);
1092 }
1093 
1095 {
1096  // convert time steps to milliseconds
1097  param_globals::dt /= 1000.;
1098 
1099  // check parab-solve solution method
1100  if(param_globals::mass_lumping == 0 && param_globals::parab_solve==0) {
1101  log_msg(NULL, 2, ECHO,
1102  "Warning: explicit solve not possible without mass lumping. \n"
1103  "Switching to Crank-Nicolson!\n\n");
1104 
1105  param_globals::parab_solve = 1;
1106  }
1107 
1108  // check if we have to modify stimuli based on used bidomain setting
1109  if(!param_globals::extracell_monodomain_stim)
1110  ignore_extracellular_stim(param_globals::stimulus, param_globals::num_stim,
1111  set_ignore_flags(param_globals::bidomain));
1112 
1113  // check nullspace if necessary
1114  // if((param_globals::bidomain==BIDOMAIN) ||
1115  // (param_globals::bidomain==PSEUDO_BIDM))
1116  // check_nullspace_ok();
1117 
1118  if(param_globals::t_sentinel > 0 && param_globals::sentinel_ID < 0 ) {
1119  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");
1120  }
1121 
1122  if(param_globals::num_external_imp > 0 ) {
1123  for(int ext_imp_i = 0; ext_imp_i < param_globals::num_external_imp; ext_imp_i++) {
1124  if(param_globals::external_imp[ext_imp_i][0] != '/') {
1125  log_msg(0,5,0, "external_imp[%d] error: absolute paths must be used for .so file loading (\'%s\')",
1126  ext_imp_i, param_globals::external_imp[ext_imp_i]);
1127  EXIT(1);
1128  }
1129  }
1130  }
1131 
1132  if(param_globals::experiment == EXP_LAPLACE && param_globals::bidomain != 1) {
1133  log_msg(0,4,0, "Warning: Laplace experiment mode requires bidomain = 1. Setting bidomain = 1.");
1134  param_globals::bidomain = 1;
1135  }
1136 
1137  if(param_globals::num_phys_regions == 0) {
1138  log_msg(0,4,0, "Warning: No physics region defined! Please set phys_region parameters to correctly define physics.");
1139 
1140  if(param_globals::experiment != EXP_LAPLACE) {
1141  log_msg(0,4,0, "Intra-elec and Extra-elec domains will be derived from fibers.\n");
1142  param_globals::num_phys_regions = param_globals::bidomain ? 2 : 1;
1143  param_globals::phys_region = (p_region*) calloc(param_globals::num_phys_regions, sizeof(p_region));
1144  param_globals::phys_region[0].ptype = PHYSREG_INTRA_ELEC;
1145  param_globals::phys_region[0].name = strdup("Autogenerated intracellular Electrics");
1146  param_globals::phys_region[0].num_IDs = 0;
1147 
1148  if(param_globals::bidomain) {
1149  param_globals::phys_region[1].ptype = PHYSREG_EXTRA_ELEC;
1150  param_globals::phys_region[1].name = strdup("Autogenerated extracellular Electrics");
1151  param_globals::phys_region[1].num_IDs = 0;
1152  }
1153  } else {
1154  log_msg(0,4,0, "Laplace domain will be derived from fibers.\n");
1155  param_globals::num_phys_regions = 1;
1156  param_globals::phys_region = (p_region*) calloc(param_globals::num_phys_regions, sizeof(p_region));
1157  param_globals::phys_region[0].ptype = PHYSREG_LAPLACE;
1158  param_globals::phys_region[0].name = strdup("Autogenerated Laplace");
1159  param_globals::phys_region[0].num_IDs = 0;
1160  }
1161  }
1162 
1163  if(param_globals::experiment == EXP_LAPLACE && !phys_defined(PHYSREG_LAPLACE)) {
1164  log_msg(0,4,0, "Warning: Laplace experiment mode requires a laplace physics regions defined.");
1165 
1166  int idx = -1;
1167  if((idx = get_phys_index(PHYSREG_EXTRA_ELEC)) > -1) {
1168  log_msg(0,4,0, "Converting the defined extracellular-electrics-region to laplace-region.");
1169  param_globals::phys_region[idx].ptype = PHYSREG_LAPLACE;
1170  } else if ((idx = get_phys_index(PHYSREG_INTRA_ELEC)) > -1) {
1171  log_msg(0,4,0, "Converting the defined intracellular-electrics-region to laplace-region.");
1172  param_globals::phys_region[idx].ptype = PHYSREG_LAPLACE;
1173  } else {
1174  param_globals::num_phys_regions += 1;
1175  param_globals::phys_region = (p_region*) realloc(param_globals::phys_region, param_globals::num_phys_regions * sizeof(p_region));
1176 
1177  param_globals::phys_region[param_globals::num_phys_regions - 1].ptype = PHYSREG_LAPLACE;
1178  param_globals::phys_region[param_globals::num_phys_regions - 1].name = strdup("Autogenerated Laplace");
1179  param_globals::phys_region[param_globals::num_phys_regions - 1].num_IDs = 0;
1180  }
1181  }
1182 
1183 #ifndef WITH_PARMETIS
1184  if(param_globals::pstrat == 1) {
1185  log_msg(0,3,0, "openCARP was built without Parmetis support. Swithing to KDtree.");
1186  param_globals::pstrat = 2;
1187  }
1188 #endif
1189 
1190  // check if we have the legacy stimuli or the new stimuli defined by the user
1191  bool legacy_stim_set = false, new_stim_set = false;
1192 
1193  for(int i=0; i<param_globals::num_stim; i++) {
1194  Stimulus & legacy_stim = param_globals::stimulus[i];
1195  Stim & new_stim = param_globals::stim[i];
1196 
1197  if(legacy_stim.stimtype || legacy_stim.strength)
1198  legacy_stim_set = true;
1199 
1200  if(new_stim.crct.type || new_stim.pulse.strength)
1201  new_stim_set = true;
1202  }
1203 
1204  if(legacy_stim_set || new_stim_set) {
1205  if(legacy_stim_set && new_stim_set) {
1206  log_msg(0,4,0, "Warning: Legacy stimuli and default stimuli are defined. Only default stimuli will be used!");
1207  }
1208  else if (legacy_stim_set) {
1209  log_msg(0,1,0, "Warning: Legacy stimuli defined. Please consider switching to stimulus definition \"stim[]\"!");
1211  }
1212  }
1213  else {
1214  log_msg(0,4,0, "Warning: No potential or current stimuli found!");
1215  }
1216 }
1217 
1218 void set_io_dirs(char *sim_ID, char *pp_ID, IO_t init)
1219 {
1220  int flg = 0, err = 0, rank = get_rank();
1221 
1222  char *ptr = getcwd(current_dir, 1024);
1223  if (ptr == NULL) err++;
1224  ptr = getcwd(input_dir, 1024);
1225  if (ptr == NULL) err++;
1226  //if (param_globals::experiment == 4 && post_processing_opts == MECHANIC_POSTPROCESS)
1227  // { sim_ID = param_globals::ppID; param_globals::ppID = "POSTPROC_DIR"; }
1228 
1229  // output directory
1230  if (rank == 0) {
1231  if (strcmp(sim_ID, "OUTPUT_DIR")) {
1232  if (mkdir(sim_ID, 0775)) { // rwxrwxr-x
1233  if (errno == EEXIST ) {
1234  log_msg(NULL, 2, 0, "Output directory exists: %s\n", sim_ID);
1235  } else {
1236  log_msg(NULL, 5, 0, "Unable to make output directory\n");
1237  flg = 1;
1238  }
1239  }
1240  } else if (mkdir(sim_ID, 0775) && errno != EEXIST) {
1241  log_msg(NULL, 5, 0, "Unable to make output directory\n");
1242  flg = 1;
1243  }
1244  }
1245 
1246  // terminate?
1247  if(get_global(flg, MPI_SUM)) { EXIT(-1); }
1248 
1249  err += chdir(sim_ID);
1250  ptr = getcwd(output_dir, 1024);
1251  if (ptr == NULL) err++;
1252 
1253  // terminate?
1254  if(get_global(err, MPI_SUM)) { EXIT(-1); }
1255 
1256  err += chdir(output_dir);
1257 
1258  // postprocessing directory
1259  if (rank == 0 && ((param_globals::experiment==EXP_POSTPROCESS) || (param_globals::post_processing_opts & LEADFIELD))) {
1260 
1261  if (strcmp(param_globals::ppID, "POSTPROC_DIR")) {
1262  if (mkdir(param_globals::ppID, 0775)) { // rwxrwxr-x
1263  if (errno == EEXIST ) {
1264  log_msg(NULL, 2, ECHO, "Postprocessing directory exists: %s\n\n", param_globals::ppID);
1265  } else {
1266  log_msg(NULL, 5, ECHO, "Unable to make postprocessing directory\n\n");
1267  flg = 1;
1268  }
1269  }
1270  } else if (mkdir(param_globals::ppID, 0775) && errno != EEXIST) {
1271  log_msg(NULL, 5, ECHO, "Unable to make postprocessing directory\n\n");
1272  flg = 1;
1273  }
1274 
1275  }
1276 
1277  if(get_global(flg, MPI_SUM)) { EXIT(-1); }
1278 
1279  err += chdir(param_globals::ppID);
1280  ptr = getcwd(postproc_dir, 1024);
1281  if (ptr == NULL) err++;
1282  err = chdir(output_dir);
1283  if(get_global(err, MPI_SUM)) { EXIT(-1); }
1284 
1285  err = set_dir(init);
1286  if(get_global(err, MPI_SUM)) { EXIT(-1); }
1287 }
1288 
1289 bool setup_IO(int argc, char **argv)
1290 {
1291  bool io_node = false;
1292  int psize = get_size(), prank = get_rank();
1293 
1294  if (param_globals::num_io_nodes > 0) {
1295  // Can't do async IO with only one core
1296  if (get_size() == 1) {
1297  log_msg(NULL, 5, 0, "You cannot run with async IO on only one core.\n");
1298  EXIT(EXIT_FAILURE);
1299  }
1300  // Can't do async IO with more IO cores than compute cores
1301  if (2 * param_globals::num_io_nodes >= psize) {
1302  log_msg(NULL, 5, 0, "The number of IO cores be less " "than the number of compute cores.");
1303  EXIT(EXIT_FAILURE);
1304  }
1305 #if 0
1306  if (param_globals::num_PS_nodes && param_globals::num_io_nodes > param_globals::num_PS_nodes) {
1307  LOG_MSG(NULL, 5, 0,
1308  "The number of IO cores (%d) should not "
1309  "exceed the number of PS compute cores (%d).\n",
1310  param_globals::num_io_nodes, param_globals::num_PS_nodes);
1311  EXIT(-1);
1312  }
1313 #endif
1314  // root IO node is global node 0
1315  io_node = prank < param_globals::num_io_nodes;
1316 
1317  MPI_Comm comm;
1318  MPI_Comm_split(PETSC_COMM_WORLD, io_node, get_rank(), &comm);
1319  MPI_Comm_set_name(comm, io_node ? "IO" : "compute");
1320 
1321  PETSC_COMM_WORLD = comm; // either the compute world or IO world
1322 
1323  prank = get_rank();
1324 
1325  MPI_Intercomm_create(comm, 0, MPI_COMM_WORLD, io_node ? param_globals::num_io_nodes : 0,
1327 
1328  if(prank != get_rank(user_globals::IO_Intercomm))
1329  log_msg(NULL, 4, 0, "Global node %d, Comm rank %d != Intercomm rank %d\n",
1330  get_rank(MPI_COMM_WORLD), get_rank(PETSC_COMM_WORLD),
1332  } else
1333  MPI_Comm_set_name(PETSC_COMM_WORLD, "compute");
1334 
1335  set_io_dirs(param_globals::simID, param_globals::ppID, OUTPUT);
1336 
1337  if((io_node || !param_globals::num_io_nodes) && !prank)
1338  output_parameter_file("parameters.par", argc, argv);
1339 
1340  return io_node;
1341 }
1343 {
1344  getcwd(current_dir, 1024);
1345 }
1346 
1347 int set_dir(IO_t dest)
1348 {
1349  int err;
1350 
1351  if (dest==OUTPUT) err = chdir(output_dir);
1352  else if (dest==POSTPROC) err = chdir(postproc_dir);
1353  else if (dest==CURDIR) err = chdir(current_dir);
1354  else err = chdir(input_dir);
1355 
1356  return err;
1357 }
1358 
1360 {
1361  // if we restart from a checkpoint, the timer_manager will be notified at a later stage
1362  double start_time = 0.0;
1363  user_globals::tm_manager = new timer_manager(param_globals::dt, start_time, param_globals::tend);
1364 
1365  double end_time = param_globals::tend;
1367 
1368  if(param_globals::experiment == EXP_LAPLACE) {
1369  tm.initialize_singlestep_timer(tm.time, 0, iotm_console, "IO (console)", nullptr);
1370  tm.initialize_singlestep_timer(tm.time, 0, iotm_state_var, "IO (state vars)", nullptr);
1371  tm.initialize_singlestep_timer(tm.time, 0, iotm_spacedt, "IO (spacedt)", nullptr);
1372  }
1373  else {
1374  tm.initialize_eq_timer(tm.time, end_time, 0, param_globals::timedt, 0, iotm_console, "IO (console)");
1375  tm.initialize_eq_timer(tm.time, end_time, 0, param_globals::spacedt, 0, iotm_state_var, "IO (state vars)");
1376  tm.initialize_eq_timer(tm.time, end_time, 0, param_globals::spacedt, 0, iotm_spacedt, "IO (spacedt)");
1377  }
1378 
1379  if(param_globals::num_tsav) {
1380  std::vector<double> trig(param_globals::num_tsav);
1381  for(size_t i=0; i<trig.size(); i++) trig[i] = param_globals::tsav[i];
1382 
1383  tm.initialize_neq_timer(trig, 0, iotm_chkpt_list, "instance checkpointing");
1384  }
1385 
1386  if(param_globals::chkpt_intv)
1387  tm.initialize_eq_timer(param_globals::chkpt_start, param_globals::chkpt_stop, 0,
1388  param_globals::chkpt_intv, 0, iotm_chkpt_intv, "interval checkpointing");
1389 
1390  if(param_globals::num_trace)
1391  tm.initialize_eq_timer(tm.time, end_time, 0, param_globals::tracedt, 0, iotm_trace, "IO (node trace)");
1392 }
1393 
1394 #ifdef WITH_POWERCAPPING
1395 void basic_powercapping_setup()
1396 {
1397  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);
1398 }
1399 #endif
1400 
1401 void get_protocol_column_widths(std::vector<int> & col_width, std::vector<int> & used_timer_ids)
1402 {
1403  char buff[256];
1404  const short padding = 4;
1405  Electrics* elec = (Electrics*) get_physics(elec_phys, false);
1406 
1407  do {
1408  snprintf(buff, sizeof buff, "%.3lf", user_globals::tm_manager->time);
1409  if(col_width[0] < int(strlen(buff)+padding))
1410  col_width[0] = strlen(buff)+padding;
1411 
1412  snprintf(buff, sizeof buff, "%.3ld", user_globals::tm_manager->d_time);
1413  if(col_width[1] < int(strlen(buff)+padding))
1414  col_width[1] = strlen(buff)+padding;
1415 
1416  int col = 2;
1417  for (size_t tid = 0; tid < used_timer_ids.size(); tid++)
1418  {
1419  int timer_id = used_timer_ids[tid];
1420  base_timer* t = user_globals::tm_manager->timers[timer_id];
1421 
1422  if(t->d_trigger_dur && elec) {
1423  // figure out value of signal linked to this timer
1424  double val = 0.;
1425 
1426  // determine timer linked to which physics, for now we deal with electrics only
1427  val = elec->timer_val(timer_id);
1428 
1429  snprintf(buff, sizeof buff, "%.3lf", val);
1430  if(col_width[col] < int(strlen(buff)+padding))
1431  col_width[col] = strlen(buff)+padding;
1432  }
1433  col++;
1434  }
1435 
1436  // advance time
1438  } while (!user_globals::tm_manager->elapsed());
1439 
1441 }
1444 int plot_protocols(const char *fname)
1445 {
1446  int err = {0};
1447  std::ofstream fh;
1448  const char* smpl_endl = "\n";
1449 
1450  if(!get_rank()) {
1451  fh.open(fname);
1452 
1453  // If we couldn't open the output file stream for writing
1454  if (!fh) {
1455  // Print an error and exit
1456  log_msg(0,5,0,"Protocol file %s could not be opened for writing!\n", fname);
1457  err = -1;
1458  }
1459  }
1460 
1461  // broadcast and return if err
1462  if(get_global(err, MPI_SUM))
1463  return err;
1464 
1465  // only rank 0 writes
1466  if(!get_rank()) {
1467 
1468  // collect timer information, label, short label, unit
1469  std::vector<std::string> col_labels = {"time", "tick"};
1470  std::vector<std::string> col_short_labels = {"A", "B"};
1471  std::vector<std::string> col_unit_labels = {"ms", "--" };
1472  std::vector<int> col_width = {4, 4};
1473 
1474  char c_label = {'C'};
1475  std::string label = {""};
1476  std::string unit = {""};
1477 
1478  // here we store the IDs of the timers that we care about. currently this are the IO and TS timers
1479  // and the electricts timers
1480  std::vector<int> used_timer_ids;
1481  std::vector<int> used_stim_ids;
1482 
1483  Electrics* elec = (Electrics*) get_physics(elec_phys, false);
1484  if(elec) {
1485  int sidx = 0;
1486  for(const stimulus & s : elec->stimuli) {
1487  if(s.ptcl.timer_id > -1) {
1489  if(t) {
1490  used_timer_ids.push_back(s.ptcl.timer_id);
1491  used_stim_ids.push_back(sidx);
1492  }
1493  }
1494 
1495  sidx++;
1496  }
1497  }
1498 
1499  // determine longest timer label
1500  int mx_llen = 0;
1501  for (size_t tid = 0; tid < used_timer_ids.size(); tid++)
1502  {
1503  int timer_id = used_timer_ids[tid];
1504  base_timer* t = user_globals::tm_manager->timers[timer_id];
1505 
1506  int llen = strlen(t->name);
1507  mx_llen = llen > mx_llen ? llen : mx_llen;
1508  }
1509 
1510  for (size_t tid = 0; tid < used_timer_ids.size(); tid++)
1511  {
1512  int timer_id = used_timer_ids[tid];
1513  base_timer* t = user_globals::tm_manager->timers[timer_id];
1514 
1515  col_labels.push_back(t->name);
1516  label = c_label;
1517  col_short_labels.push_back(label);
1518 
1519  if(elec) {
1520  // search physics for signals linked to timer
1521  unit = elec->timer_unit(timer_id);
1522  if(unit.empty()) unit = "--";
1523  col_unit_labels.push_back(unit);
1524  col_width.push_back(4);
1525  }
1526  c_label++;
1527  }
1528 
1529  get_protocol_column_widths(col_width, used_timer_ids);
1530 
1531  // print header + legend first
1532  fh << "# Protocol header\n#\n" << "# Legend:\n";
1533  for(size_t i = 0; i<col_short_labels.size(); i++)
1534  {
1535  fh << "#" << std::setw(2) << col_short_labels[i] << " = " << std::setw(mx_llen) << col_labels[i];
1536  fh << " [" << std::setw(10) << col_unit_labels[i] << "]";
1537 
1538  if(i >= 2 && used_stim_ids[i-2] > -1) {
1539  stimulus & s = elec->stimuli[used_stim_ids[i-2]];
1540 
1541  if (is_potential(s.phys.type)) {
1542  if(s.phys.type == GND_ex)
1543  fh << " ground stim" << smpl_endl;
1544  else
1545  fh << " applied: " << std::to_string(s.pulse.strength) << smpl_endl;
1546  } else {
1547  fh << smpl_endl;
1548  }
1549  } else {
1550  fh << smpl_endl;
1551  }
1552  }
1553 
1554  // plot column short labels
1555  fh << "#";
1556  for(size_t i = 0; i<col_short_labels.size(); i++)
1557  fh << std::setw(col_width[i] - 3) << col_short_labels[i].c_str() << std::setw(3) << " ";
1558 
1559  // plot column units
1560  fh << smpl_endl << "#";
1561  for(size_t i = 0; i<col_unit_labels.size(); i++)
1562  fh << "[" << std::setw(col_width[i]-2) << col_unit_labels[i].c_str() << "]";
1563 
1564  // step through simulated time period
1565  fh << smpl_endl << std::fixed;
1566  do {
1567  // time and discrete time
1568  fh << std::setw(col_width[0]) << std::setprecision(3) << user_globals::tm_manager->time;
1569  fh << std::setw(col_width[1]) << user_globals::tm_manager->d_time;
1570 
1571  // iterate over all timers
1572  int col = 2;
1573  for (size_t tid = 0; tid < used_timer_ids.size(); tid++)
1574  {
1575  int timer_id = used_timer_ids[tid];
1576  base_timer* t = user_globals::tm_manager->timers[timer_id];
1577 
1578  // type of timer: plain trigger or trigger linked to signal
1579  if(!t->d_trigger_dur) {
1580  int On = t->triggered ? 1 : 0;
1581  fh << std::setw(col_width[col]) << On;
1582  } else if(elec) {
1583  // figure out value of signal linked to this timer
1584  double val = 0.;
1585 
1586  // determine timer linked to which physics, for now we deal with electrics only
1587  val = elec->timer_val(timer_id);
1588 
1589  fh << std::setw(col_width[col]) << std::setprecision(3) << val;
1590  }
1591  col++;
1592  }
1593 
1594  fh << smpl_endl;
1595 
1596  // advance time
1598  } while (!user_globals::tm_manager->elapsed());
1599 
1600  fh.close();
1601 
1602  // reset timer to start before actual simulation
1604  }
1605 
1606  return err;
1607 }
1608 
1610 {
1611  const char* h1_prog = "PROG\t----- \t----\t-------\t-------|";
1612  const char* h2_prog = "time\t%%comp\ttime\t ctime \t ETA |";
1613  const char* h1_wc = "\tELAPS |";
1614  const char* h2_wc = "\twc |";
1615 
1616  p.start = get_time();
1617  p.last = p.start;
1618 
1619  log_msg(NULL, 0, 0, "%s", h1_prog );
1620  log_msg(NULL, 0, NONL, "%s", h2_prog );
1621  log_msg(NULL, 0, 0, "" );
1622 }
1623 
1624 
1625 void time_to_string(float time, char* str, short str_size)
1626 {
1627  int req_hours = ((int)(time)) / 3600;
1628  int req_min = (((int)(time)) % 3600) / 60;
1629  int req_sec = (((int)(time)) % 3600) % 60;
1630 
1631  snprintf(str, str_size, "%d:%02d:%02d", req_hours, req_min, req_sec);
1632 }
1633 
1635 {
1636 
1637  float progress = 100.*(tm.time - tm.start) / (tm.end - tm.start);
1638  float elapsed_time = timing(p.curr, p.start);
1639  float req_time = (elapsed_time / progress) * (100.0f - progress);
1640 
1641  if(progress == 0.0f)
1642  req_time = 0.0f;
1643 
1644  char elapsed_time_str[256];
1645  char req_time_str[256];
1646  time_to_string(elapsed_time, elapsed_time_str, 255);
1647  time_to_string(req_time, req_time_str, 255);
1648 
1649  log_msg( NULL, 0, NONL, "%.2f\t%.1f\t%.1f\t%s\t%s",
1650  tm.time,
1651  progress,
1652  (float)(p.curr - p.last),
1653  elapsed_time_str,
1654  req_time_str);
1655 
1656  p.last = p.curr;
1657 
1658  // we add an empty string for newline and flush
1659  log_msg( NULL, 0, ECHO | FLUSH, "");
1660 }
1661 
1662 void simulate()
1663 {
1664  // here we want to include all time dependent physics, to check if we have any of those
1665  bool have_timedependent_phys = (phys_defined(PHYSREG_INTRA_ELEC) || phys_defined(PHYSREG_EIKONAL) || phys_defined(PHYSREG_EMI));
1666 
1667  if(!have_timedependent_phys) {
1668  log_msg(0,0,0, "\n no time-dependent physics region registered, skipping simulate loop..\n");
1669  return;
1670  }
1671 
1672  log_msg(0,0,0, "\n *** Launching simulation ***\n");
1673 
1674  set_dir(OUTPUT);
1675 
1676  if(param_globals::dump_protocol)
1677  plot_protocols("protocol.trc");
1678 
1679  prog_stats prog;
1681  init_console_output(tm, prog);
1682 
1683 #ifdef WITH_POWERCAPPING
1684  basic_powercapping_setup();
1685  powercapping_manager &pc = *user_globals::pc_manager;
1686 #endif
1687 
1688  // main loop
1689  do {
1690  // console output
1691  if(tm.trigger(iotm_console)) {
1692  // print console
1693  update_console_output(tm, prog);
1694  }
1695 
1696 #ifdef WITH_POWERCAPPING
1697  std::vector<int> flops_per_rank;
1698 
1699  // TODO: fill vector with expected flops per MPI rank for upcoming iteration
1700 #endif
1701 
1702 #ifdef WITH_POWERCAPPING
1703  pc.iteration_begin(flops_per_rank);
1704 #endif
1705 
1706  // in order to be closer to carpentry we first do output and then compute the solution
1707  // for the next time slice ..
1708  if (tm.trigger(iotm_spacedt)) {
1709  for(const auto & it : user_globals::physics_reg) {
1710  it.second->output_step();
1711  }
1712  }
1713 #ifdef WITH_POWERCAPPING
1714  pc.sample("output step");
1715 #endif
1716 
1717  // compute step
1718  for(const auto & it : user_globals::physics_reg) {
1719  Basic_physic* p = it.second;
1720  if (tm.trigger(p->timer_idx))
1721  p->compute_step();
1722 #ifdef WITH_POWERCAPPING
1723  pc.sample(p->name);
1724 #endif
1725  }
1726 
1727 #ifdef WITH_POWERCAPPING
1728  pc.iteration_end(flops_per_rank);
1729 #endif
1730 
1731  // advance time
1732  tm.update_timers();
1733  } while (!tm.elapsed());
1734 
1735  log_msg(0,0,0, "\n\nTimings of individual physics:");
1736  log_msg(0,0,0, "------------------------------\n");
1737 
1738  for(const auto & it : user_globals::physics_reg) {
1739  Basic_physic* p = it.second;
1740  p->output_timings();
1741  }
1742 
1743 }
1744 
1746 {
1747  if(param_globals::post_processing_opts & RECOVER_PHIE) {
1748  log_msg(NULL,0,ECHO,"\nPOSTPROCESSOR: Recovering Phie ...");
1749  log_msg(NULL,0,ECHO, "----------------------------------\n");
1750 
1751  // do postprocessing
1752  int err = postproc_recover_phie();
1753 
1754  if(!err) {
1755  log_msg(NULL,0,ECHO,"\n-----------------------------------------");
1756  log_msg(NULL,0,ECHO, "POSTPROCESSOR: Successfully recoverd Phie.\n");
1757  }
1758  }
1759 
1760  if(param_globals::post_processing_opts & LEADFIELD) {
1761 #ifdef WITH_LEADFIELD
1762  log_msg(NULL,0,ECHO,"\nPOSTPROCESSOR: Computing leadfields ...");
1763  log_msg(NULL,0,ECHO, "-------------------------------------\n");
1764 
1765  Electrics* elec = static_cast<Electrics*>(get_physics(elec_phys));
1766  if(!elec) {
1767  log_msg(NULL, 5, 0, "Error: Leadfield requires active EP physics. Aborting.");
1768  EXIT(1);
1769  }
1770  Leadfield leadfield;
1771  int err = leadfield.run(*elec);
1772 
1773  if(!err) {
1774  log_msg(NULL,0,ECHO,"\n------------------------------------------");
1775  log_msg(NULL,0,ECHO, "POSTPROCESSOR: Successfully computed leadfields.\n");
1776  }
1777 #else
1778  log_msg(NULL, 5, 0, "Error: leadfield support not compiled in. Rebuild with -DENABLE_LEADFIELD=ON.");
1779  EXIT(1);
1780 #endif
1781  }
1782 }
1783 
1784 Basic_physic* get_physics(physic_t p, bool error_if_missing)
1785 {
1786  auto it = user_globals::physics_reg.find(p);
1787 
1788  if(it != user_globals::physics_reg.end()) {
1789  return it->second;
1790  } else {
1791  if(error_if_missing) {
1792  log_msg(0,5,0, "%s error: required physic is not active! Usually this is due to an inconsistent experiment configuration. Aborting!", __func__);
1793  EXIT(EXIT_FAILURE);
1794  }
1795 
1796  return NULL;
1797  }
1798 }
1799 
1801 {
1802  sf_vec* ret = NULL;
1803 
1805  ret = user_globals::datavec_reg[d];
1806 
1807  return ret;
1808 }
1809 
1811 {
1812  if(user_globals::datavec_reg.count(d) == 0) {
1813  user_globals::datavec_reg[d] = dat;
1814  }
1815  else {
1816  log_msg(0,5,0, "%s warning: trying to register already registered data vector.", __func__);
1817  }
1818 }
1819 
1821 {
1822  std::map<mesh_t, sf_mesh> & mesh_registry = user_globals::mesh_reg;
1823 
1824  // This is the initial grid we read the hard-disk data into
1825  mesh_registry[reference_msh] = sf_mesh();
1826  // we specify the MPI communicator for the reference mesh,
1827  // all derived meshes will get this comminicator automatically
1828  mesh_registry[reference_msh].comm = PETSC_COMM_WORLD;
1829 
1830  auto register_new_mesh = [&] (mesh_t mt, int pidx) {
1831  if(!mesh_registry.count(mt)) {
1832  mesh_registry[mt] = sf_mesh();
1833  mesh_registry[mt].name = param_globals::phys_region[pidx].name;
1834  }
1835  return &mesh_registry[mt];
1836  };
1837 
1838  // based on cli parameters we determine which grids need to be defined
1839  for(int i=0; i<param_globals::num_phys_regions; i++)
1840  {
1841  sf_mesh* curmesh = NULL;
1842  // register mesh type
1843  switch(param_globals::phys_region[i].ptype) {
1844  case PHYSREG_EIKONAL:
1845  case PHYSREG_INTRA_ELEC:
1846  curmesh = register_new_mesh(intra_elec_msh, i);
1847  break;
1848 
1849  case PHYSREG_LAPLACE:
1850  case PHYSREG_EXTRA_ELEC:
1851  curmesh = register_new_mesh(extra_elec_msh, i);
1852  break;
1853 #if WITH_EMI_MODEL
1854  case PHYSREG_EMI:
1855  curmesh = register_new_mesh(emi_msh, i);
1856  break;
1857 #endif
1858 
1859  default:
1860  log_msg(0,5,0, "Unsupported mesh type %d! Aborting!", param_globals::phys_region[i].ptype);
1861  EXIT(EXIT_FAILURE);
1862  }
1863 
1864  if(curmesh) {
1865  // set mesh unique tags
1866  for(int j=0; j<param_globals::phys_region[i].num_IDs; j++)
1867  curmesh->extr_tag.insert(param_globals::phys_region[i].ID[j]);
1868  }
1869  }
1870 }
1871 
1882 void retag_elements(sf_mesh & mesh, TagRegion *tagRegs, int ntr)
1883 {
1884  if(ntr == 0) return;
1885  // checkTagRegDefs(ntr, tagRegs);
1886 
1888 
1889  for (int i=0; i<ntr; i++) {
1890  tagreg_t type = tagreg_t(tagRegs[i].type);
1891  SF::vector<mesh_int_t> elem_indices;
1892 
1893  if (type == tagreg_list)
1894  read_indices(elem_indices, tagRegs[i].elemfile, ref_eidx, mesh.comm);
1895  else {
1896  geom_shape shape;
1897  shape.type = geom_shape::shape_t(tagRegs[i].type);
1898  shape.p0.x = tagRegs[i].p0[0];
1899  shape.p0.y = tagRegs[i].p0[1];
1900  shape.p0.z = tagRegs[i].p0[2];
1901  shape.p1.x = tagRegs[i].p1[0];
1902  shape.p1.y = tagRegs[i].p1[1];
1903  shape.p1.z = tagRegs[i].p1[2];
1904  shape.radius = tagRegs[i].radius;
1905 
1906  bool nodal = false;
1907  indices_from_geom_shape(elem_indices, mesh, shape, nodal);
1908  }
1909 
1910  if(get_global((long int)elem_indices.size(), MPI_SUM, mesh.comm) == 0)
1911  log_msg(0,3,0,"Tag region %d is empty", i);
1912 
1913  for(size_t j=0; j<elem_indices.size(); j++)
1914  mesh.tag[elem_indices[j]] = tagRegs[i].tag;
1915  }
1916 
1917  // output the vector?
1918  if(strlen(param_globals::retagfile))
1919  {
1920  update_cwd();
1921  set_dir(OUTPUT);
1922 
1923  int dpn = 1;
1924  SF::write_data_ascii(mesh.comm, ref_eidx, mesh.tag, param_globals::retagfile, dpn);
1925 
1926  // Set dir back to what is was prior to retagfile output
1927  set_dir(CURDIR);
1928  }
1929 }
1930 
1931 size_t renormalise_fibres(SF::vector<mesh_real_t> &fib, size_t l_numelem)
1932 {
1933  size_t renormalised_count = 0;
1934 
1935  // using pragma omp without global OMP control can lead to massive compute stalls,
1936  // as all cores may be already occupied by MPI, thus they become oversubscribed. Once
1937  // there is a global OMP control in place, we can activate this parallel for again. -Aurel, 20.01.2022
1938  // #pragma omp parallel for schedule(static) reduction(+ : renormalised_count)
1939  for (size_t i = 0; i < l_numelem; i++)
1940  {
1941  const mesh_real_t f0 = fib[3*i+0], f1 = fib[3*i+1], f2 = fib[3*i+2];
1942  mesh_real_t fibre_len = sqrt(f0*f0 + f1*f1 + f2*f2);
1943 
1944  if (fibre_len && fabs(fibre_len - 1) > 1e-3) {
1945  fib[3 * i + 0] /= fibre_len;
1946  fib[3 * i + 1] /= fibre_len;
1947  fib[3 * i + 2] /= fibre_len;
1948  renormalised_count++;
1949  }
1950  }
1951 
1952  return renormalised_count;
1953 }
1954 
1955 void setup_meshes(bool require_fibers=true)
1956 {
1957  log_msg(0,0,0, "\n *** Processing meshes ***\n");
1958 
1959  const std::string basename = param_globals::meshname;
1960  const int verb = param_globals::output_level;
1961  std::map<mesh_t, sf_mesh> & mesh_registry = user_globals::mesh_reg;
1962  assert(mesh_registry.count(reference_msh) == 1); // There must be a reference mesh
1963 
1964  set_dir(INPUT);
1965 
1966  // we always read into the reference mesh
1967  sf_mesh & ref_mesh = mesh_registry[reference_msh];
1968  MPI_Comm comm = ref_mesh.comm;
1969 
1970  int size, rank;
1971  double t1, t2, s1, s2;
1972  MPI_Comm_size(comm, &size); MPI_Comm_rank(comm, &rank);
1973 
1975  SF::vector<mesh_int_t> ptsidx;
1976 
1977  // we add pointers to the meshes that need vertex cooridnates to this list
1978  std::list< sf_mesh* > ptsread_list;
1979 
1980  // read element mesh data
1981  t1 = MPI_Wtime(); s1 = t1;
1982  if(verb) log_msg(NULL, 0, 0,"Reading reference mesh: %s.*", basename.c_str());
1983 
1984  SF::read_elements(ref_mesh, basename, require_fibers);
1985  SF::read_points(basename, comm, pts, ptsidx);
1986 
1987  if (strlen(param_globals::tagfile)) {
1988  if(verb) log_msg(NULL, 0, 0, "Overriding element tags from: %s", param_globals::tagfile);
1989  SF::read_element_tags(ref_mesh, param_globals::tagfile);
1990  }
1991 
1992  t2 = MPI_Wtime();
1993  if(verb) log_msg(NULL, 0, 0, "Done in %f sec.", float(t2 - t1));
1994 
1995  bool check_fibre_normality = true;
1996  if (check_fibre_normality and ref_mesh.fib.size()>0) {
1997  t1 = MPI_Wtime();
1998 
1999  // make sure that all fibre vectors have unit length
2000  size_t l_num_fixed_fib = renormalise_fibres(ref_mesh.fib, ref_mesh.l_numelem);
2001 
2002  size_t l_num_fixed_she = 0;
2003  if (ref_mesh.she.size() > 0)
2004  l_num_fixed_she = renormalise_fibres(ref_mesh.she, ref_mesh.l_numelem);
2005 
2006  unsigned long fixed[2] = {(unsigned long) l_num_fixed_fib, (unsigned long) l_num_fixed_she};
2007  MPI_Allreduce(MPI_IN_PLACE, fixed, 2, MPI_UNSIGNED_LONG, MPI_SUM, comm);
2008 
2009  if (fixed[0] + fixed[1] > 0)
2010  log_msg(NULL, 0, 0, "Renormalised %ld longitudinal and %ld sheet-transverse fibre vectors.", fixed[0], fixed[1]);
2011 
2012  t2 = MPI_Wtime();
2013  if(verb) log_msg(NULL, 0, 0, "Done in %f sec.", float(t2 - t1));
2014  }
2015 
2016  if(param_globals::numtagreg > 0) {
2017  log_msg(0, 0, 0, "Re-tagging reference mesh");
2018 
2019  // the retagging requires vertex coordinates, as such we need to read them into
2020  // the reference mesh
2021  ptsread_list.push_back(&ref_mesh);
2022  SF::insert_points(pts, ptsidx, ptsread_list);
2023 
2024  retag_elements(ref_mesh, param_globals::tagreg, param_globals::numtagreg);
2025 
2026  // we clear the list of meshet to receive vertices
2027  ptsread_list.clear();
2028  }
2029 
2030  if(verb) log_msg(NULL, 0, 0, "Processing submeshes");
2031 
2032  bool have_emi_mesh = false;
2033  bool have_non_emi_mesh = false;
2034 
2035  for(auto it = mesh_registry.begin(); it != mesh_registry.end(); ++it) {
2036  mesh_t grid_type = it->first;
2037  sf_mesh & submesh = it->second;
2038 
2039  if(grid_type != reference_msh) {
2040  if(grid_type == emi_msh)
2041  have_emi_mesh = true;
2042  else
2043  have_non_emi_mesh = true;
2044 
2045  if(verb > 1) log_msg(NULL, 0, 0, "\nSubmesh name: %s", submesh.name.c_str());
2046  t1 = MPI_Wtime();
2047 
2048  if(submesh.extr_tag.size() && grid_type != emi_msh)
2049  extract_tagbased(ref_mesh, submesh);
2050  else {
2051  // all submeshes should be defined on sets of tags, for backwards compatibility
2052  // we do a fiber based intra_elec_msh extraction if no tags are provided. Also, we
2053  // could do special treatments of any other physics type here. It would defeat
2054  // the purpose of the tag-based design, though. -Aurel
2055  switch(grid_type) {
2056  case emi_msh:
2057  case intra_elec_msh: extract_myocardium(ref_mesh, submesh, require_fibers); break;
2058  default: extract_tagbased(ref_mesh, submesh); break;
2059  }
2060  }
2061  t2 = MPI_Wtime();
2062  if(verb > 1) log_msg(NULL, 0, 0, "Extraction done in %f sec.", float(t2 - t1));
2063 
2064  ptsread_list.push_back(&submesh);
2065  }
2066  }
2067 
2068  if(have_emi_mesh && have_non_emi_mesh) {
2069  log_msg(NULL, 5, ECHO, "EMI and non-EMI submeshes cannot be mixed during mesh setup.");
2070  EXIT(EXIT_FAILURE);
2071  }
2072 
2073  // KDtree partitioning requires the coordinates to be present in the mesh data
2074  if(param_globals::pstrat == 2 && have_non_emi_mesh)
2075  SF::insert_points(pts, ptsidx, ptsread_list);
2076 
2077  for(auto it = mesh_registry.begin(); it != mesh_registry.end(); ++it)
2078  {
2079  mesh_t grid_type = it->first;
2080  sf_mesh & submesh = it->second;
2081  if(grid_type != reference_msh && grid_type!=emi_msh) {
2082  if(verb > 2) log_msg(NULL, 0, 0, "\nSubmesh name: %s", submesh.name.c_str());
2084 
2085  // generate partitioning vector
2086  t1 = MPI_Wtime();
2087  switch(param_globals::pstrat) {
2088  case 0:
2089  if(verb > 2) log_msg(NULL, 0, 0, "Using linear partitioning ..");
2090  break;
2091 
2092 #ifdef WITH_PARMETIS
2093  case 1:
2094  {
2095  if(verb > 2) log_msg(NULL, 0, 0, "Using Parmetis partitioner ..");
2096  SF::parmetis_partitioner<mesh_int_t, mesh_real_t> partitioner(param_globals::pstrat_imbalance, 2);
2097  partitioner(submesh, part);
2098  break;
2099  }
2100 #endif
2101  default:
2102  case 2: {
2103  if(verb > 2) log_msg(NULL, 0, 0, "Using KDtree partitioner ..");
2105  partitioner(submesh, part);
2106  break;
2107  }
2108  }
2109  t2 = MPI_Wtime();
2110  if(verb > 2) log_msg(NULL, 0, 0, "Partitioning done in %f sec.", float(t2 - t1));
2111 
2112  if(param_globals::pstrat > 0) {
2113  if(param_globals::gridout_p) {
2114  std::string out_name = get_basename(param_globals::meshname);
2115  if(grid_type == intra_elec_msh) out_name += "_i.part.dat";
2116  else if(grid_type == extra_elec_msh) out_name += "_e.part.dat";
2117 
2118  set_dir(OUTPUT);
2119  log_msg(0,0,0, "Writing \"%s\" partitioning to: %s", submesh.name.c_str(), out_name.c_str());
2120  write_data_ascii(submesh.comm, submesh.get_numbering(SF::NBR_ELEM_REF), part, out_name);
2121  }
2122 
2123  t1 = MPI_Wtime();
2124  SF::redistribute_elements(submesh, part);
2125  t2 = MPI_Wtime();
2126  if(verb > 2) log_msg(NULL, 0, 0, "Redistributing done in %f sec.", float(t2 - t1));
2127  }
2128 
2129  t1 = MPI_Wtime();
2131  sm_numbering(submesh);
2132  t2 = MPI_Wtime();
2133  if(verb > 2) log_msg(NULL, 0, 0, "Canonical numbering done in %f sec.", float(t2 - t1));
2134 
2135  t1 = MPI_Wtime();
2136  submesh.generate_par_layout();
2137  SF::petsc_numbering<mesh_int_t, mesh_real_t> p_numbering(submesh.pl);
2138  p_numbering(submesh);
2139  t2 = MPI_Wtime();
2140  if(verb > 2) log_msg(NULL, 0, 0, "PETSc numbering done in %f sec.", float(t2 - t1));
2141  if(verb > 2) print_DD_info(submesh);
2142  }
2143  }
2144 
2145  if(have_non_emi_mesh)
2146  SF::insert_points(pts, ptsidx, ptsread_list);
2147 
2148  ref_mesh.clear_data();
2149 
2150  s2 = MPI_Wtime();
2151  if(verb) log_msg(NULL, 0, 0, "All done in %f sec.", float(s2 - s1));
2152 }
2153 
2154 
2156 {
2157  bool write_intra_elec = mesh_is_registered(intra_elec_msh) && param_globals::gridout_i;
2158  bool write_extra_elec = mesh_is_registered(extra_elec_msh) && param_globals::gridout_e;
2159 
2160  set_dir(OUTPUT);
2161  std::string output_base = get_basename(param_globals::meshname);
2162 
2163  if(write_intra_elec) {
2164  sf_mesh & mesh = get_mesh(intra_elec_msh);
2165 
2166  if(param_globals::gridout_i & 1) {
2167  sf_mesh surfmesh;
2168  if(param_globals::output_level > 1)
2169  log_msg(0,0,0, "Computing \"%s\" surface ..", mesh.name.c_str());
2170  compute_surface_mesh(mesh, SF::NBR_SUBMESH, surfmesh);
2171 
2172  std::string output_file = output_base + "_i.surf";
2173  log_msg(0,0,0, "Writing \"%s\" surface: %s", mesh.name.c_str(), output_file.c_str());
2174  write_surface(surfmesh, output_file);
2175  }
2176  if(param_globals::gridout_i & 2) {
2177  bool write_binary = false;
2178 
2179  std::string output_file = output_base + "_i";
2180  log_msg(0,0,0, "Writing \"%s\" mesh: %s", mesh.name.c_str(), output_file.c_str());
2181  write_mesh_parallel(mesh, write_binary, output_file.c_str());
2182  }
2183  }
2184 
2185  if(write_extra_elec) {
2186  sf_mesh & mesh = get_mesh(extra_elec_msh);
2187 
2188  if(param_globals::gridout_e & 1) {
2189  sf_mesh surfmesh;
2190  if(param_globals::output_level > 1)
2191  log_msg(0,0,0, "Computing \"%s\" surface ..", mesh.name.c_str());
2192 
2193  compute_surface_mesh(mesh, SF::NBR_SUBMESH, surfmesh);
2194  std::string output_file = output_base + "_e.surf";
2195  log_msg(0,0,0, "Writing \"%s\" surface: %s", mesh.name.c_str(), output_file.c_str());
2196  write_surface(surfmesh, output_file);
2197  }
2198  if(param_globals::gridout_e & 2) {
2199  bool write_binary = false;
2200  std::string output_file = output_base + "_e";
2201  log_msg(0,0,0, "Writing \"%s\" mesh: %s", mesh.name.c_str(), output_file.c_str());
2202  write_mesh_parallel(mesh, write_binary, output_file.c_str());
2203  }
2204  }
2205 }
2206 
2207 [[noreturn]] void cleanup_and_exit()
2208 {
2209  destroy_physics();
2211  print_active_citation_suggestions();
2212 
2213  const paramschema::ResetResult reset = paramschema::reset_schema_state(paramschema::openCARP_schema());
2214  print_lines(stderr, "parameter cleanup warning: ", reset.warnings);
2215  print_lines(stderr, "parameter cleanup error: ", reset.errors);
2216  PetscFinalize();
2217 
2218  // close petsc error FD
2221 
2222  exit(EXIT_SUCCESS);
2223 }
2224 
2225 char* get_file_dir(const char* file)
2226 {
2227  char* filecopy = dupstr(file);
2228  char* dir = dupstr(dirname(filecopy));
2229 
2230  free(filecopy);
2231  return dir;
2232 }
2233 
2235 {
2236  int rank = get_rank();
2237  set_dir(OUTPUT);
2238 
2239  if(rank == 0) {
2240  // we open a error log file handle and set it as petsc stderr
2241  user_globals::petsc_error_fd = fopen("petsc_err_log.txt", "w");
2242  PETSC_STDERR = user_globals::petsc_error_fd;
2243  }
2244  else {
2245  PetscErrorPrintf = PetscErrorPrintfNone;
2246  }
2247 }
2248 
2250 {
2251  const sf_mesh & mesh = get_mesh(id);
2253  short mindim = 3;
2254 
2255  for(size_t eidx = 0; eidx < mesh.l_numelem; eidx++) {
2256  view.set_elem(eidx);
2257  short cdim = view.dimension();
2258  if(mindim < cdim) mindim = cdim;
2259  }
2260 
2261  mindim = get_global(mindim, MPI_MIN, mesh.comm);
2262 
2263  return mindim;
2264 }
2265 
2267  const mesh_t inp_meshid,
2268  const int dpn,
2269  const char* name,
2270  const char* units,
2271  const SF::vector<mesh_int_t>* idx,
2272  bool elem_data)
2273 {
2274  sync_io_item IO;
2275 
2276  IO.data = inp_data;
2277  IO.elem_flag = elem_data;
2278  IO.restr_idx = idx;
2279 
2280  IGBheader regigb;
2282  const int num_io = tm.timers[iotm_spacedt]->numIOs;
2283  int err = 0;
2284 
2285  int gsize = inp_data->gsize();
2286 
2287  // if we are restricting, we have to compute the restricted global size
2288  if(idx != NULL)
2289  gsize = get_global(int(idx->size()), MPI_SUM);
2290 
2291  regigb.x(gsize / dpn);
2292  regigb.dim_x(regigb.x()-1);
2293  regigb.inc_x(1);
2294 
2295  regigb.y(1); regigb.z(1);
2296  regigb.t(num_io);
2297  regigb.dim_t(tm.end-tm.start);
2298 
2299  switch(dpn) {
2300  default:
2301  case 1: regigb.type(IGB_FLOAT); break;
2302  case 3: regigb.type(IGB_VEC3_f); break;
2303  case 4: regigb.type(IGB_VEC4_f); break;
2304  case 9: regigb.type(IGB_VEC9_f); break;
2305  }
2306 
2307  regigb.unites_x("um"); regigb.unites_y("um"); regigb.unites_z("um");
2308  regigb.unites_t("ms");
2309  regigb.unites(units);
2310 
2311  regigb.inc_t(param_globals::spacedt);
2312 
2313  if(get_rank() == 0) {
2314  FILE_SPEC file = f_open(name, "w");
2315  if(file != NULL) {
2316  regigb.fileptr(file->fd);
2317  regigb.write();
2318  delete file;
2319  }
2320  else err++;
2321  }
2322 
2323  err = get_global(err, MPI_SUM);
2324  if(err) {
2325  log_msg(0,5,0, "%s error: Could not set up data output! Aborting!", __func__);
2326  EXIT(1);
2327  }
2328 
2329  IO.igb = regigb;
2330 
2331  SF::mixed_tuple<mesh_t, int> mesh_spec = {inp_meshid, dpn};
2332  IO.spec = mesh_spec;
2333 
2334  if(elem_data) {
2335  if(buffmap_elem.find(mesh_spec) == buffmap_elem.end()) {
2336  sf_vec *inp_copy; SF::init_vector(&inp_copy, inp_data);
2337  buffmap_elem[mesh_spec] = inp_copy;
2338  }
2339  } else {
2340  if(buffmap.find(mesh_spec) == buffmap.end()) {
2341  sf_vec *inp_copy; SF::init_vector(&inp_copy, inp_data);
2342  buffmap[mesh_spec] = inp_copy;
2343  }
2344  }
2345 
2346  this->sync_IOs.push_back(IO);
2347 }
2348 
2349 void igb_output_manager::register_output_async(sf_vec* inp_data,
2350  const mesh_t inp_meshid,
2351  const int dpn,
2352  const char* name,
2353  const char* units,
2354  const SF::vector<mesh_int_t>* idx,
2355  bool elem_data)
2356 {
2357  sf_mesh & mesh = get_mesh(inp_meshid);
2358  SF::vector<mesh_int_t> ioidx;
2359  int rank = get_rank();
2360 
2361  async_io_item IO;
2362  IO.data = inp_data;
2363  IO.restr_idx = idx;
2364 
2365  if(elem_data) {
2367  ioidx.resize(mesh.l_numelem);
2368  for(size_t i=0; i<mesh.l_numelem; i++)
2369  ioidx[i] = nbr[i];
2370  } else {
2371  const SF::vector<mesh_int_t> & alg_nod = mesh.pl.algebraic_nodes();
2373 
2374  if(idx == NULL) {
2375  ioidx.resize(alg_nod.size());
2376 
2377  for(size_t i=0; i<alg_nod.size(); i++)
2378  ioidx[i] = nbr[alg_nod[i]];
2379  } else {
2380  ioidx.resize(idx->size());
2381  IO.restr_petsc_idx.resize(idx->size());
2382 
2383  for(size_t i=0; i<idx->size(); i++) {
2384  mesh_int_t loc_nodal = (*idx)[i];
2385  ioidx[i] = nbr[loc_nodal];
2386  IO.restr_petsc_idx[i] = local_nodal_to_local_petsc(mesh, rank, loc_nodal);
2387  }
2388  }
2389  }
2390 
2391  int id = async::COMPUTE_register_output(ioidx, dpn, name, units);
2392  IO.IO_id = id;
2393 
2394  this->async_IOs.push_back(IO);
2395 }
2396 
2398  const mesh_t inp_meshid,
2399  const int dpn,
2400  const char* name,
2401  const char* units,
2402  const SF::vector<mesh_int_t>* idx,
2403  bool elem_data)
2404 {
2405  if(param_globals::num_io_nodes == 0)
2406  register_output_sync(inp_data, inp_meshid, dpn, name, units, idx, elem_data);
2407  else
2408  register_output_async(inp_data, inp_meshid, dpn, name, units, idx, elem_data);
2409 }
2410 
2411 sf_vec* igb_output_manager::fill_output_buffer(const sync_io_item & it)
2412 {
2413  const SF::mixed_tuple<mesh_t, int> & cspec = it.spec;
2414  sf_vec* data_vec = it.data;
2415 
2416  bool have_perm = it.elem_flag ? have_permutation(cspec.v1, ELEM_PETSC_TO_CANONICAL, cspec.v2):
2417  have_permutation(cspec.v1, PETSC_TO_CANONICAL, cspec.v2);
2418 
2419  if(have_perm) {
2420  sf_vec* perm_vec = it.elem_flag ? this->buffmap_elem[cspec] : this->buffmap[cspec];
2422  get_permutation(cspec.v1, PETSC_TO_CANONICAL, cspec.v2);
2423  sc->forward(*data_vec, *perm_vec);
2424  return perm_vec;
2425  } else {
2426  return data_vec;
2427  }
2428 }
2429 
2431 {
2432  SF::vector<float> restr_buff;
2433  int rank = get_rank();
2434  // loop over registered datasets and root-write one by one
2435  //
2436  for (sync_io_item & it : sync_IOs) {
2437  // write to associated file descriptor
2438  FILE* fd = static_cast<FILE*>(it.igb.fileptr());
2439 
2440  // fill the output buffer
2441  sf_vec* buff = fill_output_buffer(it);
2442 
2443  if(it.restr_idx == NULL) {
2444  buff->write_binary<float>(fd);
2445  } else {
2446  const SF::vector<mesh_int_t> & idx = *it.restr_idx;
2447  SF_real* p = buff->ptr();
2448 
2449  restr_buff.resize(idx.size()); restr_buff.resize(0);
2450 
2451  for(mesh_int_t ii : idx)
2452  restr_buff.push_back(p[ii]);
2453 
2454  root_write(fd, restr_buff, PETSC_COMM_WORLD);
2455  buff->release_ptr(p);
2456  }
2457  }
2458 
2459  // do all A-synchronous output:
2460  // loop over IDs received from the IO nodes and trigger async output
2461  //
2462  for (async_io_item & it : async_IOs) {
2463  SF_real* p = it.data->ptr();
2464  int ls = it.data->lsize();
2465  int id = it.IO_id;
2466 
2467  if(it.restr_idx == NULL)
2468  async::COMPUTE_do_output(p, ls, id);
2469  else {
2470  async::COMPUTE_do_output(p, it.restr_petsc_idx, id);
2471  }
2472 
2473  it.data->release_ptr(p);
2474  }
2475 }
2476 
2478 {
2479  if(get_rank() == 0) {
2480  // loop over registered datasets and close fd
2481  for(sync_io_item & it : sync_IOs) {
2482  FILE* fd = static_cast<FILE*>(it.igb.fileptr());
2483  fclose(fd);
2484  }
2485  }
2486 
2487  for(auto it = buffmap.begin(); it != buffmap.end(); ++it)
2488  delete it->second;
2489 
2490  for(auto it = buffmap_elem.begin(); it != buffmap_elem.end(); ++it)
2491  delete it->second;
2492 
2493  // we resize the arrays and clear the maps so that we are safe when calling
2494  // close_files_and_cleanup multiple times.
2495  sync_IOs.resize(0);
2496  async_IOs.resize(0);
2497  buffmap.clear();
2498  buffmap_elem.clear();
2499 }
2500 
2502 {
2503  for(sync_io_item & it : sync_IOs) {
2504  if(it.data == vec)
2505  return &it.igb;
2506  }
2507 
2508  return NULL;
2509 }
2510 
2511 void output_parameter_file(const char *fname, int argc, char **argv)
2512 {
2513  const int max_line_len = 128;
2514  const char* file_sep = "#=======================================================";
2515 
2516  // make sure only root executes this function
2517  if(get_rank() != 0)
2518  return;
2519 
2520  FILE_SPEC out = f_open(fname, "w");
2521  fprintf(out->fd, "# CARP GIT commit hash: %s\n", GIT_COMMIT_HASH);
2522  fprintf(out->fd, "# dependency hashes: %s\n", SUBREPO_COMMITS);
2523  fprintf(out->fd, "\n");
2524 
2525  // output the command line
2526  char line[8196] = "# ";
2527 
2528  for (int j=0; j<argc; j++) {
2529  strcat(line, argv[j]);
2530  if(strlen(line) > max_line_len) {
2531  fprintf(out->fd, "%s\n", line);
2532  strcpy(line, "# ");
2533  } else
2534  strcat(line, " ");
2535  }
2536 
2537  fprintf(out->fd, "%s\n\n", line);
2538  set_dir(INPUT);
2539 
2540  // convert command line to a par file
2541  for (int i=1; i<argc; i++) {
2542  std::string argument(argv[i]);
2543  if (argument == "+F" || argument.find("_options_file")!= std::string::npos) {
2544 
2545  std::string init = "";
2546  if (argument.find("_options_file")!= std::string::npos) {
2547  fprintf(out->fd, "%s = %s\n", argument.substr(1).c_str(), argv[i+1]);
2548  init = "#";
2549  }
2550  fprintf(out->fd, "%s>>\n", file_sep);
2551  // import par files
2552  i++;
2553  fprintf(out->fd, "## %s ##\n", argv[i]);
2554  FILE *in = fopen(argv[i], "r");
2555  while (fgets(line, 8196, in))
2556  fprintf(out->fd, "%s%s", init.c_str(), line);
2557  fclose(in);
2558  fprintf(out->fd, "\n##END of %s\n", argv[i]);
2559  fprintf(out->fd, "%s<<\n\n", file_sep);
2560  }
2561  else if(argv[i][0] == '-')
2562  {
2563  bool prelast = (i==argc-1);
2564  bool paramFollows = !prelast && ((argv[i+1][0] != '-') ||
2565  ((argv[i+1][1] >= '0') && (argv[i+1][1] <= '9')));
2566 
2567  // strip leading hyphens from command line opts
2568  // assume options do not start with numbers
2569  if(paramFollows) {
2570  // nonflag option
2571  char *optcpy = strdup(argv[i+1]);
2572  char *front = optcpy;
2573  // strip {} if present for arrays of values
2574  while(*front==' ' && *front) front++;
2575  if(*front=='{') {
2576  while(*++front == ' ');
2577  char *back = optcpy+strlen(optcpy)-1;
2578  while(*back==' ' && back>front) back--;
2579  if(*back == '}')
2580  *back = '\0';
2581  }
2582  if (strstr(front, "=") != nullptr) // if "=" is find then we need ""
2583  fprintf(out->fd, "%-40s= \"%s\"\n", argv[i]+1, front);
2584  else
2585  fprintf(out->fd, "%-40s= %s\n", argv[i]+1, front);
2586  free(optcpy);
2587  i++;
2588  }
2589  else // a flag was specified
2590  fprintf(out->fd, "%-40s= 1\n", argv[i]);
2591  }
2592  }
2593  f_close(out);
2594 }
2595 
2596 void savequit()
2597 {
2598  if(!get_physics(elec_phys)) return;
2599 
2600  set_dir(OUTPUT);
2601 
2602  double time = user_globals::tm_manager->time;
2603  char save_fn[512];
2604 
2605  snprintf(save_fn, sizeof save_fn, "exit.save.%.3f.roe", time);
2606  log_msg(NULL, 0, 0, "savequit called at time %g\n", time);
2607 
2609  elec->ion.miif->dump_state(save_fn, time, intra_elec_msh, false, GIT_COMMIT_COUNT);
2610 
2611  cleanup_and_exit();
2612 }
2613 
2614 } // 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< 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
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
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
T & push_back(T val)
Definition: SF_vector.h:283
size_t size() const
Definition: hashmap.hpp:1156
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:264
std::string timer_unit(const int timer_id)
figure out units of a signal linked to a given timer
Definition: electrics.cc:768
double timer_val(const int timer_id)
figure out current value of a signal linked to a given timer
Definition: electrics.cc:752
void dim_t(float a)
Definition: IGBheader.h:333
void unites_x(const char *a)
Definition: IGBheader.h:345
void unites_z(const char *a)
Definition: IGBheader.h:351
void unites(const char *a)
Definition: IGBheader.h:357
void unites_y(const char *a)
Definition: IGBheader.h:348
void unites_t(const char *a)
Definition: IGBheader.h:354
void fileptr(gzFile f)
Definition: IGBheader.cc:336
void dim_x(float a)
Definition: IGBheader.h:324
void inc_t(float a)
Definition: IGBheader.h:321
void inc_x(float a)
Definition: IGBheader.h:312
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:327
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:2501
void write_data()
write registered data to disk
Definition: sim_utils.cc:2430
SF::vector< async_io_item > async_IOs
Definition: sim_utils.h:324
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:2266
std::map< SF::mixed_tuple< mesh_t, int >, sf_vec * > buffmap
map data spec -> PETSc vector buffer
Definition: sim_utils.h:326
void close_files_and_cleanup()
close file descriptors
Definition: sim_utils.cc:2477
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:2397
SF::vector< sync_io_item > sync_IOs
Definition: sim_utils.h:323
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 write_surface(const meshdata< T, S > &surfmesh, std::string surffile)
Definition: SF_mesh_io.h:841
void count(const vector< T > &data, vector< S > &cnt)
Count number of occurrences of indices.
Definition: SF_vector.h:332
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.
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 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)
Write a parallel mesh to harddisk without gathering it on one rank.
size_t root_write(FILE *fd, const vector< V > &vec, MPI_Comm comm)
Write vector data binary to disk.
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_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:1625
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:1800
void output_parameter_file(const char *fname, int argc, char **argv)
Definition: sim_utils.cc:2511
void initialize_physics()
Initialize all physics in the registry.
Definition: sim_utils.cc:967
bool setup_IO(int argc, char **argv)
Definition: sim_utils.cc:1289
void retag_elements(sf_mesh &mesh, TagRegion *tagRegs, int ntr)
Definition: sim_utils.cc:1882
void parse_params_cpy(int argc, char **argv)
Initialize input parameters on a copy of the real command line parameters.
Definition: sim_utils.cc:908
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:1044
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:1347
void cleanup_and_exit()
Definition: sim_utils.cc:2207
void register_physics()
Register physics to the physics registry.
Definition: sim_utils.cc:945
void post_process()
do postprocessing
Definition: sim_utils.cc:1745
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:1094
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:2249
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:1634
void show_build_info()
show the build info, exit if -buildinfo was provided. This code runs before MPI_Init().
Definition: sim_utils.cc:1082
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:2596
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:1218
void register_data(sf_vec *dat, datavec_t d)
Register a data vector in the global registry.
Definition: sim_utils.cc:1810
void basic_timer_setup()
Here we set up the timers that we always want to have, independent of physics.
Definition: sim_utils.cc:1359
char * get_file_dir(const char *file)
Definition: sim_utils.cc:2225
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:1401
int get_phys_index(int physreg)
get index in param_globals::phys_region array for a certain phys region
void check_nullspace_ok()
Definition: sim_utils.cc:1065
int postproc_recover_phie()
Definition: electrics.cc:2006
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:1444
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:1955
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
void output_meshes()
Definition: sim_utils.cc:2155
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:1784
size_t renormalise_fibres(SF::vector< mesh_real_t > &fib, size_t l_numelem)
Definition: sim_utils.cc:1931
void destroy_physics()
Destroy all physics in the registry.
Definition: sim_utils.cc:991
void ignore_extracellular_stim(Stimulus *st, int ns, int ignore)
Definition: sim_utils.cc:1018
void init_console_output(const timer_manager &tm, prog_stats &p)
Definition: sim_utils.cc:1609
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 update_cwd()
save the current working directory to curdir so that we can switch back to it if needed.
Definition: sim_utils.cc:1342
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:2234
void simulate()
Main simulate loop.
Definition: sim_utils.cc:1662
void parse_mesh_types()
Parse the phys_type CLI parameters and set up (empty) SF::meshdata meshes.
Definition: sim_utils.cc:1820
#define IGB_VEC9_f
Definition: IGBheader.h:76
#define IGB_VEC3_f
Definition: IGBheader.h:71
#define IGB_FLOAT
Definition: IGBheader.h:60
#define IGB_VEC4_f
Definition: IGBheader.h:73
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:78
ParserFallbackMode fallback_mode
Definition: sim_utils.cc:73
bool available
Definition: sim_utils.cc:77
std::string unavailable_reason
Definition: sim_utils.cc:79
Simulator-level utility execution control functions.
#define BIDOMAIN
Definition: sim_utils.h:154
#define RECOVER_PHIE
Definition: sim_utils.h:159
#define MONODOMAIN
Definition: sim_utils.h:153
#define EXP_POSTPROCESS
Definition: sim_utils.h:172
#define PSEUDO_BIDM
Definition: sim_utils.h:155
#define LEADFIELD
Definition: sim_utils.h:165
#define EXP_LAPLACE
Definition: sim_utils.h:170
#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:301
SF::vector< mesh_int_t > restr_petsc_idx
pointer to index vector with nodal indices we restrict to.
Definition: sim_utils.h:302
int IO_id
pointer to data registered for output
Definition: sim_utils.h:300
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:283
double curr
current output wallclock time
Definition: sim_utils.h:287
double start
output start wallclock time
Definition: sim_utils.h:285
double last
last output wallclock time
Definition: sim_utils.h:286
bool elem_flag
igb header we use for output
Definition: sim_utils.h:294
IGBheader igb
pointer to index vector used for restricting output.
Definition: sim_utils.h:293
const SF::vector< mesh_int_t > * restr_idx
pointer to data registered for output
Definition: sim_utils.h:292
SF::mixed_tuple< mesh_t, int > spec
flag whether the data is elements-wise
Definition: sim_utils.h:295