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