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