33 #include "opencarp_types.h"
37 #ifdef WITH_POWERCAPPING
39 #include "powercapping.h"
53 #include "openCARP_schema.hpp"
54 #include "runtime.hpp"
55 #include "snapshot_file_io.hpp"
61 enum class ParserCompareMode {
67 enum class ParserFallbackMode {
72 struct RuntimeCompatOptions {
76 struct LegacyCompareInput {
82 std::vector<paramschema::CitationSuggestion> active_citation_suggestions;
84 std::string trim_copy(
const std::string& value);
85 std::string to_lower_ascii(std::string value);
86 bool build_legacy_compare_input(
int argc,
char** argv, LegacyCompareInput* input, std::string* error);
88 bool match_long_option(
const std::string& token,
const char* option, std::string* attached_value)
90 *attached_value = std::string();
91 if (token == option) {
95 const std::string prefix = std::string(option) +
"=";
96 if (token.size() > prefix.size() && token.compare(0, prefix.size(), prefix) == 0) {
97 *attached_value = token.substr(prefix.size());
104 bool is_help_topic_candidate(
const char* token)
106 return token != NULL && token[0] !=
'\0' && token[0] !=
'-' && token[0] !=
'+';
109 bool is_long_option_argument_error(
const std::string& token,
111 const std::string& attached_value,
114 if (attached_value.empty()) {
118 *error =
"Unexpected argument for " + std::string(option) +
" in '" + token +
"'";
122 bool normalize_runtime_args(
int argc,
124 std::vector<std::string>* normalized,
125 RuntimeCompatOptions* compat,
129 compat->fallback_mode = ParserFallbackMode::Off;
131 if (argc <= 0 || argv == NULL || argv[0] == NULL) {
132 *error =
"Missing program name";
136 normalized->push_back(argv[0]);
138 for (
int i = 1; i < argc; ++i) {
139 const std::string token = argv[i];
144 std::string attached_value;
146 if (token ==
"+Help" || match_long_option(token,
"--help", &attached_value)) {
147 std::string topic =
"PrM";
148 if (!attached_value.empty()) {
149 topic = attached_value;
150 }
else if (i + 1 < argc && is_help_topic_candidate(argv[i + 1])) {
153 normalized->push_back(
"+Help");
154 normalized->push_back(topic);
158 if (token ==
"+Doc" || match_long_option(token,
"--doc", &attached_value)) {
159 std::string topic =
"ALL";
160 if (!attached_value.empty()) {
161 topic = attached_value;
162 }
else if (i + 1 < argc && is_help_topic_candidate(argv[i + 1])) {
165 normalized->push_back(
"+Doc");
166 normalized->push_back(topic);
170 if (token ==
"+Default" || match_long_option(token,
"--default", &attached_value)) {
171 if (token !=
"+Default" && is_long_option_argument_error(token,
"--default", attached_value, error)) {
174 normalized->push_back(
"+Default");
178 if (token ==
"+Run" || match_long_option(token,
"--run", &attached_value)) {
179 if (token !=
"+Run" && is_long_option_argument_error(token,
"--run", attached_value, error)) {
182 normalized->push_back(
"+Run");
186 if (token ==
"+I" || match_long_option(token,
"--interactive", &attached_value)) {
187 if (!attached_value.empty()) {
188 *error =
"Unexpected argument for --interactive in '" + token +
"'";
190 *error =
"Unsupported option " + token +
" (interactive mode is not available)";
195 if (match_long_option(token,
"--param-fallback", &attached_value)) {
196 std::string mode = attached_value;
199 *error =
"Missing argument after --param-fallback";
205 if (to_lower_ascii(trim_copy(mode)) !=
"legacy") {
206 *error =
"Unsupported value '" + mode +
"' for --param-fallback (expected legacy)";
210 compat->fallback_mode = ParserFallbackMode::Legacy;
214 if (token ==
"+F" || match_long_option(token,
"--file", &attached_value)) {
215 std::string
filename = attached_value;
218 *error =
"Missing filename after " + token;
223 normalized->push_back(
"+F");
228 if (token ==
"+Save" || match_long_option(token,
"--save", &attached_value)) {
229 std::string
filename = attached_value;
232 *error =
"Missing argument after " + token;
237 normalized->push_back(
"+Save");
242 normalized->push_back(token);
248 bool parse_option_argument(
int* index,
251 const std::string& token,
252 const std::string& attached_value,
253 const char* option_name,
257 if (!attached_value.empty()) {
258 *value = attached_value;
261 if (*index + 1 >= argc) {
262 *error =
"Missing argument after " + std::string(option_name);
265 *value = argv[++(*index)];
269 bool filename_has_suffix(
const std::string&
filename,
const char* suffix)
271 const std::string normalized_filename = to_lower_ascii(trim_copy(
filename));
272 const std::string normalized_suffix = to_lower_ascii(std::string(suffix == NULL ?
"" : suffix));
273 return normalized_filename.size() >= normalized_suffix.size() &&
274 normalized_filename.compare(normalized_filename.size() - normalized_suffix.size(),
275 normalized_suffix.size(),
276 normalized_suffix) == 0;
279 bool build_legacy_compare_input(
int argc,
char** argv, LegacyCompareInput* input, std::string* error)
281 input->available =
false;
282 input->runtime_args.clear();
283 input->unavailable_reason.clear();
285 if (argc <= 0 || argv == NULL || argv[0] == NULL) {
286 *error =
"Missing program name";
290 input->runtime_args.push_back(argv[0]);
291 bool saw_passthrough_token_before_file =
false;
293 for (
int i = 1; i < argc; ++i) {
294 const std::string token = argv[i];
299 std::string attached_value;
301 if (match_long_option(token,
"--param-fallback", &attached_value)) {
303 if (!parse_option_argument(&i, argc, argv, token, attached_value,
"--param-fallback", &ignored, error)) {
309 if (token ==
"+Save" || match_long_option(token,
"--save", &attached_value)) {
311 if (!parse_option_argument(&i, argc, argv, token, attached_value, token.c_str(), &ignored, error)) {
317 if (token ==
"+Help" || match_long_option(token,
"--help", &attached_value)) {
318 std::string topic =
"PrM";
319 if (!attached_value.empty()) {
320 topic = attached_value;
321 }
else if (i + 1 < argc && is_help_topic_candidate(argv[i + 1])) {
324 input->runtime_args.push_back(
"+Help");
325 input->runtime_args.push_back(topic);
326 input->available =
true;
330 if (token ==
"+Doc" || match_long_option(token,
"--doc", &attached_value)) {
331 std::string topic =
"ALL";
332 if (!attached_value.empty()) {
333 topic = attached_value;
334 }
else if (i + 1 < argc && is_help_topic_candidate(argv[i + 1])) {
337 input->runtime_args.push_back(
"+Doc");
338 input->runtime_args.push_back(topic);
339 input->available =
true;
343 if (token ==
"+Default" || match_long_option(token,
"--default", &attached_value)) {
344 if (token !=
"+Default" && is_long_option_argument_error(token,
"--default", attached_value, error)) {
347 input->runtime_args.push_back(
"+Default");
351 if (token ==
"+Run" || match_long_option(token,
"--run", &attached_value)) {
352 if (token !=
"+Run" && is_long_option_argument_error(token,
"--run", attached_value, error)) {
355 input->runtime_args.push_back(
"+Run");
359 if (token ==
"+F" || match_long_option(token,
"--file", &attached_value)) {
361 if (!parse_option_argument(&i, argc, argv, token, attached_value, token.c_str(), &
filename, error)) {
365 if (saw_passthrough_token_before_file) {
366 input->unavailable_reason =
367 "legacy compare is unavailable when direct parameter arguments precede a .par input file";
368 input->runtime_args.clear();
369 input->runtime_args.push_back(argv[0]);
373 if (!filename_has_suffix(
filename,
".par")) {
374 input->unavailable_reason =
375 "legacy compare is unavailable for non-.par input file '" +
filename +
"'";
376 input->runtime_args.clear();
377 input->runtime_args.push_back(argv[0]);
381 input->runtime_args.push_back(
"+F");
382 input->runtime_args.push_back(
filename);
386 input->runtime_args.push_back(token);
387 saw_passthrough_token_before_file =
true;
390 input->available = input->runtime_args.size() > 1;
391 if (!input->available && input->unavailable_reason.empty()) {
392 input->unavailable_reason =
"unable to reconstruct a legacy-compatible parameter input";
397 std::string trim_copy(
const std::string& value)
399 std::string::size_type first = 0;
400 while (first < value.size() && std::isspace(
static_cast<unsigned char>(value[first]))) {
404 std::string::size_type last = value.size();
405 while (last > first && std::isspace(
static_cast<unsigned char>(value[last - 1]))) {
409 return value.substr(first, last - first);
412 std::string to_lower_ascii(std::string value)
414 for (std::string::size_type i = 0; i < value.size(); ++i) {
415 value[i] =
static_cast<char>(std::tolower(
static_cast<unsigned char>(value[i])));
420 ParserCompareMode parser_compare_mode()
422 const char* raw = std::getenv(
"OPENCARP_PARAM_COMPARE");
424 return ParserCompareMode::Strict;
427 const std::string normalized = to_lower_ascii(trim_copy(raw));
428 if (normalized.empty() || normalized ==
"1" || normalized ==
"on" || normalized ==
"true" ||
429 normalized ==
"yes" || normalized ==
"strict" || normalized ==
"fail" || normalized ==
"error") {
430 return ParserCompareMode::Strict;
432 if (normalized ==
"warn") {
433 return ParserCompareMode::Warn;
435 if (normalized ==
"0" || normalized ==
"off" || normalized ==
"false" || normalized ==
"no") {
436 return ParserCompareMode::Off;
439 return ParserCompareMode::Strict;
442 ParserFallbackMode parser_fallback_mode()
444 const char* raw = std::getenv(
"OPENCARP_PARAM_FALLBACK");
446 return ParserFallbackMode::Off;
449 const std::string normalized = to_lower_ascii(trim_copy(raw));
450 if (normalized.empty() || normalized ==
"0" || normalized ==
"off" || normalized ==
"false" ||
451 normalized ==
"no") {
452 return ParserFallbackMode::Off;
454 if (normalized ==
"legacy") {
455 return ParserFallbackMode::Legacy;
458 return ParserFallbackMode::Off;
461 void populate_arg_pointers(
const std::vector<std::string>& values, std::vector<char*>* argv)
463 argv->assign(values.size(), NULL);
464 for (std::size_t i = 0; i < values.size(); ++i) {
465 (*argv)[i] =
const_cast<char*
>(values[i].c_str());
469 void print_lines(FILE* stream,
const char* label,
const std::vector<std::string>& lines)
471 for (std::size_t i = 0; i < lines.size(); ++i) {
472 fprintf(stream,
"%s%s\n", label, lines[i].c_str());
476 void clear_active_citation_suggestions()
478 active_citation_suggestions.clear();
481 void store_active_citation_suggestions(
const std::vector<paramschema::CitationSuggestion>& suggestions)
483 active_citation_suggestions = suggestions;
486 void print_active_citation_suggestions()
488 if (active_citation_suggestions.empty() ||
get_rank() != 0) {
492 const std::vector<std::string> lines =
493 paramschema::format_citation_suggestions(active_citation_suggestions);
498 std::fprintf(stdout,
"\nIf you publish studies based on this simulation, the following references are likely relevant:\n");
499 for (std::size_t i = 0; i < lines.size(); ++i) {
500 std::fprintf(stdout,
" %s\n", lines[i].c_str());
502 std::fprintf(stdout,
" You can use https://citation.doi.org to format references in your preferred format or https://www.doi2bib.org to turn the DOIs into BibTeX format.\n");
503 std::fprintf(stdout,
"\nWe'd be happy to also see your experiment published to boost its impact and foster reproducibility.\nSee https://opencarp.org/community/upload-experiment for details.\n\n");
508 void print_parser_runtime_state(
const std::vector<std::string>&
runtime_args)
510 const auto& schema = paramschema::openCARP_schema();
511 const std::string program_name =
514 std::string rendered;
515 std::vector<std::string> errors;
516 if (!paramschema::render_save_text(schema, program_name, &rendered, &errors)) {
517 print_lines(stderr,
"parameter parser warning: ", errors);
521 fputs(rendered.c_str(), stderr);
524 paramschema::ExecutionResult execute_parser_runtime_args(
const std::vector<std::string>&
runtime_args,
525 const bool allow_save)
527 std::vector<char*> argv;
530 const auto& schema = paramschema::openCARP_schema();
532 paramschema::ExecutionOptions options;
533 options.allow_save = allow_save;
534 return paramschema::execute_legacy_cli(schema,
static_cast<int>(argv.size()), argv.data(), options);
537 bool apply_parser_runtime_args(
const std::vector<std::string>&
runtime_args,
538 const bool allow_save,
539 paramschema::ExecutionResult* executed_out)
541 clear_active_citation_suggestions();
542 const paramschema::ExecutionResult executed = execute_parser_runtime_args(
runtime_args, allow_save);
543 if (executed_out != NULL) {
544 *executed_out = executed;
546 print_lines(stderr,
"parameter parser warning: ", executed.warnings);
547 if (!executed.rendered_output.empty()) {
548 fputs(executed.rendered_output.c_str(), stdout);
550 if (!executed.errors.empty() || executed.status == paramschema::ExecutionStatus::Fatal) {
551 print_lines(stderr,
"parameter parser error: ", executed.errors);
555 if (param_globals::output_setup) {
559 if (executed.status == paramschema::ExecutionStatus::Help) {
563 store_active_citation_suggestions(executed.citations);
567 std::string parent_directory(
const std::string& path)
569 const std::string::size_type slash = path.rfind(
'/');
570 if (slash == std::string::npos) {
576 return path.substr(0, slash);
579 std::string join_path(
const std::string& left,
const std::string& right)
581 if (left.empty() || left ==
".") {
584 if (!left.empty() && left[left.size() - 1] ==
'/') {
587 return left +
"/" + right;
590 bool find_executable_on_path(
const std::string& name, std::string* resolved_path)
592 if (name.empty() || name.find(
'/') != std::string::npos) {
596 const char* path_env = std::getenv(
"PATH");
597 if (path_env == NULL || path_env[0] ==
'\0') {
601 const std::string path_list = path_env;
602 std::string::size_type start = 0;
603 while (start <= path_list.size()) {
604 std::string::size_type end = path_list.find(
':', start);
605 if (end == std::string::npos) {
606 end = path_list.size();
609 const std::string directory = path_list.substr(start, end - start);
610 const std::string candidate = join_path(directory.empty() ?
"." : directory, name);
611 if (access(candidate.c_str(), X_OK) == 0) {
612 *resolved_path = candidate;
616 if (end == path_list.size()) {
625 bool resolve_legacy_snapshot_helper(
const std::string& program_path, std::string* helper_path)
627 std::vector<std::string> resolved_program_paths;
628 if (!program_path.empty()) {
629 resolved_program_paths.push_back(program_path);
632 if (program_path.find(
'/') == std::string::npos) {
633 std::string resolved_program_path;
634 if (find_executable_on_path(program_path, &resolved_program_path)) {
635 resolved_program_paths.push_back(resolved_program_path);
639 for (std::size_t i = 0; i < resolved_program_paths.size(); ++i) {
640 const std::string program_dir = parent_directory(resolved_program_paths[i]);
641 const std::string parent_dir = parent_directory(program_dir);
643 const std::vector<std::string> candidates = {
644 join_path(program_dir,
"param-parser-legacy-snapshot"),
645 join_path(join_path(parent_dir,
"simulator"),
"param-parser-legacy-snapshot"),
648 for (std::size_t j = 0; j < candidates.size(); ++j) {
649 if (access(candidates[j].c_str(), X_OK) == 0) {
650 *helper_path = candidates[j];
656 return find_executable_on_path(
"param-parser-legacy-snapshot", helper_path);
659 bool create_temp_output_path(
const char* suffix, std::string* path)
662 if (suffix != NULL && suffix[0] !=
'\0') {
663 std::snprintf(temp_path,
sizeof temp_path,
"/tmp/opencarp-parser-compare-XXXXXX%s", suffix);
665 std::snprintf(temp_path,
sizeof temp_path,
"/tmp/opencarp-parser-compare-XXXXXX");
668 const int fd = suffix != NULL && suffix[0] !=
'\0' ? mkstemps(temp_path,
static_cast<int>(std::strlen(suffix))) :
678 bool capture_current_parser_snapshot(paramschema::SnapshotResult* snapshot)
680 *snapshot = paramschema::snapshot_schema_state(paramschema::openCARP_schema());
681 print_lines(stderr,
"parameter compare warning: ", snapshot->warnings);
682 if (!snapshot->errors.empty()) {
683 print_lines(stderr,
"parameter compare error: ", snapshot->errors);
689 void maybe_force_test_mismatch(paramschema::SnapshotResult* snapshot)
691 const char* raw = std::getenv(
"OPENCARP_PARAM_TEST_FORCE_MISMATCH");
696 const std::string normalized = to_lower_ascii(trim_copy(raw));
697 if (normalized.empty() || normalized ==
"0" || normalized ==
"off" || normalized ==
"false" ||
698 normalized ==
"no") {
702 if (!snapshot->entries.empty()) {
703 snapshot->entries[0].value +=
"__forced_parser_compare_mismatch__";
707 paramschema::SnapshotEntry entry;
708 entry.path =
"buildinfo";
709 entry.value =
"__forced_parser_compare_mismatch__";
710 snapshot->entries.push_back(entry);
713 bool run_legacy_snapshot_helper(
const std::string& helper_path,
715 const std::string& output_path)
717 std::vector<std::string> child_values;
719 child_values.push_back(helper_path);
720 child_values.push_back(
"--snapshot-out");
721 child_values.push_back(output_path);
722 child_values.push_back(
"--");
725 std::vector<char*> child_argv;
726 child_argv.reserve(child_values.size() + 1);
727 for (std::size_t i = 0; i < child_values.size(); ++i) {
728 child_argv.push_back(
const_cast<char*
>(child_values[i].c_str()));
730 child_argv.push_back(NULL);
732 const pid_t pid = fork();
739 execv(helper_path.c_str(), child_argv.data());
740 std::perror(
"execv");
745 if (waitpid(pid, &status, 0) < 0) {
746 std::perror(
"waitpid");
750 if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
751 if (WIFSIGNALED(status)) {
752 std::fprintf(stderr,
"parameter compare error: legacy snapshot helper terminated with signal %d\n",
755 std::fprintf(stderr,
"parameter compare error: legacy snapshot helper exited with status %d\n",
756 WEXITSTATUS(status));
764 bool legacy_fallback_requested(
const RuntimeCompatOptions& compat)
766 return compat.fallback_mode == ParserFallbackMode::Legacy ||
767 parser_fallback_mode() == ParserFallbackMode::Legacy;
770 void print_legacy_fallback_workaround()
773 "parameter compare error: rerun with OPENCARP_PARAM_FALLBACK=legacy to continue with the legacy parameter state\n");
775 "parameter compare error: or add --param-fallback=legacy to the openCARP command line\n");
778 bool restore_legacy_snapshot_state(
const paramschema::SnapshotResult& legacy_snapshot)
780 const paramschema::SnapshotRestoreResult restored =
781 paramschema::restore_snapshot_state(paramschema::openCARP_schema(), legacy_snapshot);
782 print_lines(stderr,
"parameter compare warning: ", restored.warnings);
783 if (!restored.errors.empty()) {
784 print_lines(stderr,
"parameter compare error: ", restored.errors);
790 bool run_parser_legacy_compare(
const std::vector<std::string>&
runtime_args,
791 const LegacyCompareInput& legacy_input,
792 const RuntimeCompatOptions& compat)
794 const ParserCompareMode mode = parser_compare_mode();
795 if (mode == ParserCompareMode::Off) {
798 const bool fallback_to_legacy = legacy_fallback_requested(compat);
801 std::fprintf(stderr,
"parameter compare error: unable to reconstruct simulator argv\n");
802 if (fallback_to_legacy) {
803 print_legacy_fallback_workaround();
805 return mode != ParserCompareMode::Strict;
808 paramschema::SnapshotResult parser_snapshot;
809 if (!capture_current_parser_snapshot(&parser_snapshot)) {
810 std::fprintf(stderr,
"parameter compare error: unable to snapshot the parser runtime state\n");
811 if (fallback_to_legacy) {
812 print_legacy_fallback_workaround();
814 return mode != ParserCompareMode::Strict;
816 maybe_force_test_mismatch(&parser_snapshot);
818 std::string helper_path;
819 if (!resolve_legacy_snapshot_helper(
runtime_args[0], &helper_path)) {
820 std::fprintf(stderr,
"parameter compare error: unable to locate param-parser-legacy-snapshot\n");
821 if (fallback_to_legacy) {
822 print_legacy_fallback_workaround();
825 "parameter compare error: rerun with OPENCARP_PARAM_COMPARE=0 to bypass this temporary compatibility gate\n");
827 return mode != ParserCompareMode::Strict;
830 if (!legacy_input.available) {
831 std::fprintf(stderr,
"parameter compare warning: %s\n", legacy_input.unavailable_reason.c_str());
832 if (fallback_to_legacy) {
834 "parameter compare warning: legacy fallback is unavailable because no legacy baseline exists for this input\n");
839 std::string snapshot_path;
840 if (!create_temp_output_path(
"", &snapshot_path)) {
841 std::fprintf(stderr,
"parameter compare error: unable to create temporary snapshot file\n");
842 if (fallback_to_legacy) {
843 print_legacy_fallback_workaround();
846 "parameter compare error: rerun with OPENCARP_PARAM_COMPARE=0 to bypass this temporary compatibility gate\n");
848 return mode != ParserCompareMode::Strict;
851 const bool helper_ok = run_legacy_snapshot_helper(helper_path, legacy_input.runtime_args, snapshot_path);
852 paramschema::SnapshotResult legacy_snapshot;
853 std::string read_error;
854 const bool loaded = helper_ok &&
855 paramschema::snapshotio::read_snapshot_file(snapshot_path, &legacy_snapshot, &read_error);
856 unlink(snapshot_path.c_str());
859 if (!read_error.empty()) {
860 std::fprintf(stderr,
"parameter compare error: %s\n", read_error.c_str());
862 std::fprintf(stderr,
"parameter compare error: unable to capture the legacy parameter state\n");
864 if (fallback_to_legacy) {
865 print_legacy_fallback_workaround();
868 "parameter compare error: rerun with OPENCARP_PARAM_COMPARE=0 to bypass this temporary compatibility gate\n");
870 return mode != ParserCompareMode::Strict;
873 const paramschema::SnapshotComparisonResult comparison =
874 paramschema::compare_snapshot_results(paramschema::openCARP_schema(), parser_snapshot, legacy_snapshot);
875 if (!comparison.errors.empty() || !comparison.mismatches.empty()) {
876 print_lines(stderr,
"", paramschema::format_snapshot_comparison_report(comparison,
"parser compare"));
878 "parameter compare error: the parser runtime and legacy param() produced different parameter states\n");
880 "parameter compare error: please open an issue and include the triggering command line and parameter files\n");
882 "https://git.opencarp.org/openCARP/openCARP/-/issues/new?type=ISSUE&initialCreationContext=list-route\n");
884 if (fallback_to_legacy) {
885 if (!restore_legacy_snapshot_state(legacy_snapshot)) {
886 print_legacy_fallback_workaround();
889 clear_active_citation_suggestions();
890 std::fprintf(stderr,
"parameter compare warning: continuing with the legacy parameter state\n");
894 print_legacy_fallback_workaround();
895 return mode != ParserCompareMode::Strict;
903 static char input_dir[1024],
910 LegacyCompareInput legacy_input;
911 std::vector<std::string> normalized_args;
912 RuntimeCompatOptions compat;
913 std::string normalize_error;
914 if (!build_legacy_compare_input(argc, argv, &legacy_input, &normalize_error) ||
915 !normalize_runtime_args(argc, argv, &normalized_args, &compat, &normalize_error)) {
916 fprintf(stderr,
"\n*** %s\n\n", normalize_error.c_str());
920 paramschema::ExecutionResult executed;
921 if (!apply_parser_runtime_args(normalized_args,
true, &executed)) {
925 if (legacy_input.available) {
926 for (std::size_t i = 0; i < executed.validation.assignments.size(); ++i) {
927 if (!executed.validation.assignments[i].synthesized) {
930 legacy_input.available =
false;
931 legacy_input.runtime_args.clear();
932 legacy_input.runtime_args.push_back(normalized_args[0]);
933 legacy_input.unavailable_reason =
934 "legacy compare is unavailable because the parser inferred optional controller counts from the original input";
939 if (!run_parser_legacy_compare(normalized_args, legacy_input, compat)) {
955 log_msg(NULL, 5,
ECHO,
"The EMI model was not compiled for this binary.\n");
969 log_msg(0,0,0,
"\n *** Initializing physics ***\n");
973 for (
int ii = 0; ii < param_globals::num_external_imp; ii++) {
975 assert(loading_succeeded);
978 if(param_globals::num_external_imp)
979 log_msg(NULL, 4,
ECHO,
"Loading of external LIMPET modules not enabled.\n"
980 "Recompile with DLOPEN set.\n" );
986 log_msg(NULL, 0, 0,
"Initializing %s ..", p->
name);
993 log_msg(0,0,0,
"\n *** Destroying physics ***\n");
1021 for (
int i=0; i<ns; i++ ) {
1029 log_msg( NULL, 1, 0,
"Extracellular stimulus %d ignored for monodomain", i );
1032 log_msg( NULL, 1, 0,
"Intracellular stimulus %d converted to transmembrane", i );
1067 Stimulus* s = param_globals::stimulus;
1069 for(
int i=0; i < param_globals::num_stim; i++) {
1077 log_msg( NULL, 4, 0,
"Elliptic system is singular!\n"
1078 "Use an explicit ground:voltage (stimulus[X].stimtype=3)\n"
1079 "Do not trust the elliptic solution of this simulation run!\n");
1084 const char* real_precision = OPENCARP_REAL_BITS == 32 ?
"single" :
"double";
1085 printf(
"\n""*** GIT tag: %s\n", GIT_COMMIT_TAG);
1086 printf(
"*** GIT hash: %s\n", GIT_COMMIT_HASH);
1087 printf(
"*** GIT repo: %s\n", GIT_PATH);
1088 printf(
"*** local index bits: %d\n", OPENCARP_LOCAL_INDEX_BITS);
1089 printf(
"*** global index bits: %d\n", OPENCARP_GLOBAL_INDEX_BITS);
1090 printf(
"*** real precision: %s (%d-bit)\n", real_precision, OPENCARP_REAL_BITS);
1091 printf(
"*** dependency commits: %s\n\n", SUBREPO_COMMITS);
1097 param_globals::dt /= 1000.;
1100 if(param_globals::mass_lumping == 0 && param_globals::parab_solve==0) {
1102 "Warning: explicit solve not possible without mass lumping. \n"
1103 "Switching to Crank-Nicolson!\n\n");
1105 param_globals::parab_solve = 1;
1109 if(!param_globals::extracell_monodomain_stim)
1118 if(param_globals::t_sentinel > 0 && param_globals::sentinel_ID < 0 ) {
1119 log_msg(0,4,0,
"Warning: t_sentinel is set but no sentinel_ID has been specified; check_quiescence() behavior may not be as expected");
1122 if(param_globals::num_external_imp > 0 ) {
1123 for(
int ext_imp_i = 0; ext_imp_i < param_globals::num_external_imp; ext_imp_i++) {
1124 if(param_globals::external_imp[ext_imp_i][0] !=
'/') {
1125 log_msg(0,5,0,
"external_imp[%d] error: absolute paths must be used for .so file loading (\'%s\')",
1126 ext_imp_i, param_globals::external_imp[ext_imp_i]);
1132 if(param_globals::experiment ==
EXP_LAPLACE && param_globals::bidomain != 1) {
1133 log_msg(0,4,0,
"Warning: Laplace experiment mode requires bidomain = 1. Setting bidomain = 1.");
1134 param_globals::bidomain = 1;
1137 if(param_globals::num_phys_regions == 0) {
1138 log_msg(0,4,0,
"Warning: No physics region defined! Please set phys_region parameters to correctly define physics.");
1141 log_msg(0,4,0,
"Intra-elec and Extra-elec domains will be derived from fibers.\n");
1142 param_globals::num_phys_regions = param_globals::bidomain ? 2 : 1;
1143 param_globals::phys_region = (p_region*) calloc(param_globals::num_phys_regions,
sizeof(p_region));
1145 param_globals::phys_region[0].name = strdup(
"Autogenerated intracellular Electrics");
1146 param_globals::phys_region[0].num_IDs = 0;
1148 if(param_globals::bidomain) {
1150 param_globals::phys_region[1].name = strdup(
"Autogenerated extracellular Electrics");
1151 param_globals::phys_region[1].num_IDs = 0;
1154 log_msg(0,4,0,
"Laplace domain will be derived from fibers.\n");
1155 param_globals::num_phys_regions = 1;
1156 param_globals::phys_region = (p_region*) calloc(param_globals::num_phys_regions,
sizeof(p_region));
1158 param_globals::phys_region[0].name = strdup(
"Autogenerated Laplace");
1159 param_globals::phys_region[0].num_IDs = 0;
1164 log_msg(0,4,0,
"Warning: Laplace experiment mode requires a laplace physics regions defined.");
1168 log_msg(0,4,0,
"Converting the defined extracellular-electrics-region to laplace-region.");
1171 log_msg(0,4,0,
"Converting the defined intracellular-electrics-region to laplace-region.");
1174 param_globals::num_phys_regions += 1;
1175 param_globals::phys_region = (p_region*) realloc(param_globals::phys_region, param_globals::num_phys_regions *
sizeof(p_region));
1177 param_globals::phys_region[param_globals::num_phys_regions - 1].ptype =
PHYSREG_LAPLACE;
1178 param_globals::phys_region[param_globals::num_phys_regions - 1].name = strdup(
"Autogenerated Laplace");
1179 param_globals::phys_region[param_globals::num_phys_regions - 1].num_IDs = 0;
1183 #ifndef WITH_PARMETIS
1184 if(param_globals::pstrat == 1) {
1185 log_msg(0,3,0,
"openCARP was built without Parmetis support. Swithing to KDtree.");
1186 param_globals::pstrat = 2;
1191 bool legacy_stim_set =
false, new_stim_set =
false;
1193 for(
int i=0; i<param_globals::num_stim; i++) {
1194 Stimulus & legacy_stim = param_globals::stimulus[i];
1195 Stim & new_stim = param_globals::stim[i];
1197 if(legacy_stim.stimtype || legacy_stim.strength)
1198 legacy_stim_set =
true;
1200 if(new_stim.crct.type || new_stim.pulse.strength)
1201 new_stim_set =
true;
1204 if(legacy_stim_set || new_stim_set) {
1205 if(legacy_stim_set && new_stim_set) {
1206 log_msg(0,4,0,
"Warning: Legacy stimuli and default stimuli are defined. Only default stimuli will be used!");
1208 else if (legacy_stim_set) {
1209 log_msg(0,1,0,
"Warning: Legacy stimuli defined. Please consider switching to stimulus definition \"stim[]\"!");
1214 log_msg(0,4,0,
"Warning: No potential or current stimuli found!");
1220 int flg = 0, err = 0, rank =
get_rank();
1222 char *ptr = getcwd(current_dir, 1024);
1223 if (ptr == NULL) err++;
1224 ptr = getcwd(input_dir, 1024);
1225 if (ptr == NULL) err++;
1231 if (strcmp(sim_ID,
"OUTPUT_DIR")) {
1232 if (mkdir(sim_ID, 0775)) {
1233 if (errno == EEXIST ) {
1234 log_msg(NULL, 2, 0,
"Output directory exists: %s\n", sim_ID);
1236 log_msg(NULL, 5, 0,
"Unable to make output directory\n");
1240 }
else if (mkdir(sim_ID, 0775) && errno != EEXIST) {
1241 log_msg(NULL, 5, 0,
"Unable to make output directory\n");
1249 err += chdir(sim_ID);
1250 ptr = getcwd(output_dir, 1024);
1251 if (ptr == NULL) err++;
1256 err += chdir(output_dir);
1259 if (rank == 0 && ((param_globals::experiment==
EXP_POSTPROCESS) || (param_globals::post_processing_opts &
LEADFIELD))) {
1261 if (strcmp(param_globals::ppID,
"POSTPROC_DIR")) {
1262 if (mkdir(param_globals::ppID, 0775)) {
1263 if (errno == EEXIST ) {
1264 log_msg(NULL, 2,
ECHO,
"Postprocessing directory exists: %s\n\n", param_globals::ppID);
1266 log_msg(NULL, 5,
ECHO,
"Unable to make postprocessing directory\n\n");
1270 }
else if (mkdir(param_globals::ppID, 0775) && errno != EEXIST) {
1271 log_msg(NULL, 5,
ECHO,
"Unable to make postprocessing directory\n\n");
1279 err += chdir(param_globals::ppID);
1280 ptr = getcwd(postproc_dir, 1024);
1281 if (ptr == NULL) err++;
1282 err = chdir(output_dir);
1291 bool io_node =
false;
1294 if (param_globals::num_io_nodes > 0) {
1297 log_msg(NULL, 5, 0,
"You cannot run with async IO on only one core.\n");
1301 if (2 * param_globals::num_io_nodes >= psize) {
1302 log_msg(NULL, 5, 0,
"The number of IO cores be less " "than the number of compute cores.");
1306 if (param_globals::num_PS_nodes && param_globals::num_io_nodes > param_globals::num_PS_nodes) {
1308 "The number of IO cores (%d) should not "
1309 "exceed the number of PS compute cores (%d).\n",
1310 param_globals::num_io_nodes, param_globals::num_PS_nodes);
1315 io_node = prank < param_globals::num_io_nodes;
1318 MPI_Comm_split(PETSC_COMM_WORLD, io_node,
get_rank(), &comm);
1319 MPI_Comm_set_name(comm, io_node ?
"IO" :
"compute");
1321 PETSC_COMM_WORLD = comm;
1325 MPI_Intercomm_create(comm, 0, MPI_COMM_WORLD, io_node ? param_globals::num_io_nodes : 0,
1329 log_msg(NULL, 4, 0,
"Global node %d, Comm rank %d != Intercomm rank %d\n",
1333 MPI_Comm_set_name(PETSC_COMM_WORLD,
"compute");
1337 if((io_node || !param_globals::num_io_nodes) && !prank)
1344 getcwd(current_dir, 1024);
1351 if (dest==
OUTPUT) err = chdir(output_dir);
1352 else if (dest==
POSTPROC) err = chdir(postproc_dir);
1353 else if (dest==
CURDIR) err = chdir(current_dir);
1354 else err = chdir(input_dir);
1362 double start_time = 0.0;
1365 double end_time = param_globals::tend;
1379 if(param_globals::num_tsav) {
1380 std::vector<double> trig(param_globals::num_tsav);
1381 for(
size_t i=0; i<trig.size(); i++) trig[i] = param_globals::tsav[i];
1386 if(param_globals::chkpt_intv)
1388 param_globals::chkpt_intv, 0,
iotm_chkpt_intv,
"interval checkpointing");
1390 if(param_globals::num_trace)
1394 #ifdef WITH_POWERCAPPING
1395 void basic_powercapping_setup()
1397 user_globals::pc_manager =
new powercapping_manager(param_globals::powcap_backend, param_globals::powcap_backend_policy, param_globals::powcap_metrics_backend, param_globals::powcap_policy);
1404 const short padding = 4;
1409 if(col_width[0] <
int(strlen(buff)+padding))
1410 col_width[0] = strlen(buff)+padding;
1413 if(col_width[1] <
int(strlen(buff)+padding))
1414 col_width[1] = strlen(buff)+padding;
1417 for (
size_t tid = 0; tid < used_timer_ids.size(); tid++)
1419 int timer_id = used_timer_ids[tid];
1429 snprintf(buff,
sizeof buff,
"%.3lf", val);
1430 if(col_width[col] <
int(strlen(buff)+padding))
1431 col_width[col] = strlen(buff)+padding;
1448 const char* smpl_endl =
"\n";
1456 log_msg(0,5,0,
"Protocol file %s could not be opened for writing!\n", fname);
1469 std::vector<std::string> col_labels = {
"time",
"tick"};
1470 std::vector<std::string> col_short_labels = {
"A",
"B"};
1471 std::vector<std::string> col_unit_labels = {
"ms",
"--" };
1472 std::vector<int> col_width = {4, 4};
1474 char c_label = {
'C'};
1475 std::string label = {
""};
1476 std::string unit = {
""};
1480 std::vector<int> used_timer_ids;
1481 std::vector<int> used_stim_ids;
1491 used_stim_ids.push_back(sidx);
1501 for (
size_t tid = 0; tid < used_timer_ids.size(); tid++)
1503 int timer_id = used_timer_ids[tid];
1506 int llen = strlen(t->
name);
1507 mx_llen = llen > mx_llen ? llen : mx_llen;
1510 for (
size_t tid = 0; tid < used_timer_ids.size(); tid++)
1512 int timer_id = used_timer_ids[tid];
1515 col_labels.push_back(t->
name);
1517 col_short_labels.push_back(label);
1522 if(unit.empty()) unit =
"--";
1523 col_unit_labels.push_back(unit);
1524 col_width.push_back(4);
1532 fh <<
"# Protocol header\n#\n" <<
"# Legend:\n";
1533 for(
size_t i = 0; i<col_short_labels.size(); i++)
1535 fh <<
"#" << std::setw(2) << col_short_labels[i] <<
" = " << std::setw(mx_llen) << col_labels[i];
1536 fh <<
" [" << std::setw(10) << col_unit_labels[i] <<
"]";
1538 if(i >= 2 && used_stim_ids[i-2] > -1) {
1543 fh <<
" ground stim" << smpl_endl;
1545 fh <<
" applied: " << std::to_string(s.
pulse.
strength) << smpl_endl;
1556 for(
size_t i = 0; i<col_short_labels.size(); i++)
1557 fh << std::setw(col_width[i] - 3) << col_short_labels[i].c_str() << std::setw(3) <<
" ";
1560 fh << smpl_endl <<
"#";
1561 for(
size_t i = 0; i<col_unit_labels.size(); i++)
1562 fh <<
"[" << std::setw(col_width[i]-2) << col_unit_labels[i].c_str() <<
"]";
1565 fh << smpl_endl << std::fixed;
1573 for (
size_t tid = 0; tid < used_timer_ids.size(); tid++)
1575 int timer_id = used_timer_ids[tid];
1581 fh << std::setw(col_width[col]) << On;
1589 fh << std::setw(col_width[col]) << std::setprecision(3) << val;
1611 const char* h1_prog =
"PROG\t----- \t----\t-------\t-------|";
1612 const char* h2_prog =
"time\t%%comp\ttime\t ctime \t ETA |";
1613 const char* h1_wc =
"\tELAPS |";
1614 const char* h2_wc =
"\twc |";
1619 log_msg(NULL, 0, 0,
"%s", h1_prog );
1627 int req_hours = ((int)(time)) / 3600;
1628 int req_min = (((int)(time)) % 3600) / 60;
1629 int req_sec = (((int)(time)) % 3600) % 60;
1631 snprintf(str, str_size,
"%d:%02d:%02d", req_hours, req_min, req_sec);
1644 char elapsed_time_str[256];
1645 char req_time_str[256];
1649 log_msg( NULL, 0,
NONL,
"%.2f\t%.1f\t%.1f\t%s\t%s",
1667 if(!have_timedependent_phys) {
1668 log_msg(0,0,0,
"\n no time-dependent physics region registered, skipping simulate loop..\n");
1672 log_msg(0,0,0,
"\n *** Launching simulation ***\n");
1676 if(param_globals::dump_protocol)
1683 #ifdef WITH_POWERCAPPING
1684 basic_powercapping_setup();
1685 powercapping_manager &pc = *user_globals::pc_manager;
1696 #ifdef WITH_POWERCAPPING
1697 std::vector<int> flops_per_rank;
1702 #ifdef WITH_POWERCAPPING
1703 pc.iteration_begin(flops_per_rank);
1710 it.second->output_step();
1713 #ifdef WITH_POWERCAPPING
1714 pc.sample(
"output step");
1722 #ifdef WITH_POWERCAPPING
1727 #ifdef WITH_POWERCAPPING
1728 pc.iteration_end(flops_per_rank);
1735 log_msg(0,0,0,
"\n\nTimings of individual physics:");
1736 log_msg(0,0,0,
"------------------------------\n");
1747 if(param_globals::post_processing_opts &
RECOVER_PHIE) {
1748 log_msg(NULL,0,
ECHO,
"\nPOSTPROCESSOR: Recovering Phie ...");
1749 log_msg(NULL,0,
ECHO,
"----------------------------------\n");
1755 log_msg(NULL,0,
ECHO,
"\n-----------------------------------------");
1756 log_msg(NULL,0,
ECHO,
"POSTPROCESSOR: Successfully recoverd Phie.\n");
1760 if(param_globals::post_processing_opts &
LEADFIELD) {
1761 #ifdef WITH_LEADFIELD
1762 log_msg(NULL,0,
ECHO,
"\nPOSTPROCESSOR: Computing leadfields ...");
1763 log_msg(NULL,0,
ECHO,
"-------------------------------------\n");
1767 log_msg(NULL, 5, 0,
"Error: Leadfield requires active EP physics. Aborting.");
1771 int err = leadfield.
run(*elec);
1774 log_msg(NULL,0,
ECHO,
"\n------------------------------------------");
1775 log_msg(NULL,0,
ECHO,
"POSTPROCESSOR: Successfully computed leadfields.\n");
1778 log_msg(NULL, 5, 0,
"Error: leadfield support not compiled in. Rebuild with -DENABLE_LEADFIELD=ON.");
1791 if(error_if_missing) {
1792 log_msg(0,5,0,
"%s error: required physic is not active! Usually this is due to an inconsistent experiment configuration. Aborting!", __func__);
1816 log_msg(0,5,0,
"%s warning: trying to register already registered data vector.", __func__);
1830 auto register_new_mesh = [&] (
mesh_t mt,
int pidx) {
1831 if(!mesh_registry.count(mt)) {
1832 mesh_registry[mt] =
sf_mesh();
1833 mesh_registry[mt].name = param_globals::phys_region[pidx].name;
1835 return &mesh_registry[mt];
1839 for(
int i=0; i<param_globals::num_phys_regions; i++)
1843 switch(param_globals::phys_region[i].ptype) {
1855 curmesh = register_new_mesh(
emi_msh, i);
1860 log_msg(0,5,0,
"Unsupported mesh type %d! Aborting!", param_globals::phys_region[i].ptype);
1866 for(
int j=0; j<param_globals::phys_region[i].num_IDs; j++)
1884 if(ntr == 0)
return;
1889 for (
int i=0; i<ntr; i++) {
1898 shape.
p0.
x = tagRegs[i].p0[0];
1899 shape.
p0.
y = tagRegs[i].p0[1];
1900 shape.
p0.
z = tagRegs[i].p0[2];
1901 shape.
p1.
x = tagRegs[i].p1[0];
1902 shape.
p1.
y = tagRegs[i].p1[1];
1903 shape.
p1.
z = tagRegs[i].p1[2];
1904 shape.
radius = tagRegs[i].radius;
1911 log_msg(0,3,0,
"Tag region %d is empty", i);
1913 for(
size_t j=0; j<elem_indices.
size(); j++)
1914 mesh.
tag[elem_indices[j]] = tagRegs[i].tag;
1918 if(strlen(param_globals::retagfile))
1933 size_t renormalised_count = 0;
1939 for (
size_t i = 0; i < l_numelem; i++)
1941 const mesh_real_t f0 = fib[3*i+0], f1 = fib[3*i+1], f2 = fib[3*i+2];
1942 mesh_real_t fibre_len = sqrt(f0*f0 + f1*f1 + f2*f2);
1944 if (fibre_len && fabs(fibre_len - 1) > 1e-3) {
1945 fib[3 * i + 0] /= fibre_len;
1946 fib[3 * i + 1] /= fibre_len;
1947 fib[3 * i + 2] /= fibre_len;
1948 renormalised_count++;
1952 return renormalised_count;
1957 log_msg(0,0,0,
"\n *** Processing meshes ***\n");
1959 const std::string basename = param_globals::meshname;
1960 const int verb = param_globals::output_level;
1968 MPI_Comm comm = ref_mesh.
comm;
1971 double t1, t2, s1, s2;
1972 MPI_Comm_size(comm, &size); MPI_Comm_rank(comm, &rank);
1978 std::list< sf_mesh* > ptsread_list;
1981 t1 = MPI_Wtime(); s1 = t1;
1982 if(verb)
log_msg(NULL, 0, 0,
"Reading reference mesh: %s.*", basename.c_str());
1987 if (strlen(param_globals::tagfile)) {
1988 if(verb)
log_msg(NULL, 0, 0,
"Overriding element tags from: %s", param_globals::tagfile);
1993 if(verb)
log_msg(NULL, 0, 0,
"Done in %f sec.",
float(t2 - t1));
1995 bool check_fibre_normality =
true;
1996 if (check_fibre_normality and ref_mesh.
fib.
size()>0) {
2002 size_t l_num_fixed_she = 0;
2006 unsigned long fixed[2] = {(
unsigned long) l_num_fixed_fib, (
unsigned long) l_num_fixed_she};
2007 MPI_Allreduce(MPI_IN_PLACE, fixed, 2, MPI_UNSIGNED_LONG, MPI_SUM, comm);
2009 if (fixed[0] + fixed[1] > 0)
2010 log_msg(NULL, 0, 0,
"Renormalised %ld longitudinal and %ld sheet-transverse fibre vectors.", fixed[0], fixed[1]);
2013 if(verb)
log_msg(NULL, 0, 0,
"Done in %f sec.",
float(t2 - t1));
2016 if(param_globals::numtagreg > 0) {
2017 log_msg(0, 0, 0,
"Re-tagging reference mesh");
2021 ptsread_list.push_back(&ref_mesh);
2024 retag_elements(ref_mesh, param_globals::tagreg, param_globals::numtagreg);
2027 ptsread_list.clear();
2030 if(verb)
log_msg(NULL, 0, 0,
"Processing submeshes");
2032 bool have_emi_mesh =
false;
2033 bool have_non_emi_mesh =
false;
2035 for(
auto it = mesh_registry.begin(); it != mesh_registry.end(); ++it) {
2036 mesh_t grid_type = it->first;
2037 sf_mesh & submesh = it->second;
2041 have_emi_mesh =
true;
2043 have_non_emi_mesh =
true;
2045 if(verb > 1)
log_msg(NULL, 0, 0,
"\nSubmesh name: %s", submesh.
name.c_str());
2062 if(verb > 1)
log_msg(NULL, 0, 0,
"Extraction done in %f sec.",
float(t2 - t1));
2064 ptsread_list.push_back(&submesh);
2068 if(have_emi_mesh && have_non_emi_mesh) {
2069 log_msg(NULL, 5,
ECHO,
"EMI and non-EMI submeshes cannot be mixed during mesh setup.");
2074 if(param_globals::pstrat == 2 && have_non_emi_mesh)
2077 for(
auto it = mesh_registry.begin(); it != mesh_registry.end(); ++it)
2079 mesh_t grid_type = it->first;
2080 sf_mesh & submesh = it->second;
2082 if(verb > 2)
log_msg(NULL, 0, 0,
"\nSubmesh name: %s", submesh.
name.c_str());
2087 switch(param_globals::pstrat) {
2089 if(verb > 2)
log_msg(NULL, 0, 0,
"Using linear partitioning ..");
2092 #ifdef WITH_PARMETIS
2095 if(verb > 2)
log_msg(NULL, 0, 0,
"Using Parmetis partitioner ..");
2096 SF::parmetis_partitioner<mesh_int_t, mesh_real_t> partitioner(param_globals::pstrat_imbalance, 2);
2097 partitioner(submesh, part);
2103 if(verb > 2)
log_msg(NULL, 0, 0,
"Using KDtree partitioner ..");
2105 partitioner(submesh, part);
2110 if(verb > 2)
log_msg(NULL, 0, 0,
"Partitioning done in %f sec.",
float(t2 - t1));
2112 if(param_globals::pstrat > 0) {
2113 if(param_globals::gridout_p) {
2114 std::string out_name =
get_basename(param_globals::meshname);
2119 log_msg(0,0,0,
"Writing \"%s\" partitioning to: %s", submesh.
name.c_str(), out_name.c_str());
2126 if(verb > 2)
log_msg(NULL, 0, 0,
"Redistributing done in %f sec.",
float(t2 - t1));
2131 sm_numbering(submesh);
2133 if(verb > 2)
log_msg(NULL, 0, 0,
"Canonical numbering done in %f sec.",
float(t2 - t1));
2138 p_numbering(submesh);
2140 if(verb > 2)
log_msg(NULL, 0, 0,
"PETSc numbering done in %f sec.",
float(t2 - t1));
2145 if(have_non_emi_mesh)
2151 if(verb)
log_msg(NULL, 0, 0,
"All done in %f sec.",
float(s2 - s1));
2161 std::string output_base =
get_basename(param_globals::meshname);
2163 if(write_intra_elec) {
2166 if(param_globals::gridout_i & 1) {
2168 if(param_globals::output_level > 1)
2169 log_msg(0,0,0,
"Computing \"%s\" surface ..", mesh.
name.c_str());
2172 std::string output_file = output_base +
"_i.surf";
2173 log_msg(0,0,0,
"Writing \"%s\" surface: %s", mesh.
name.c_str(), output_file.c_str());
2176 if(param_globals::gridout_i & 2) {
2177 bool write_binary =
false;
2179 std::string output_file = output_base +
"_i";
2180 log_msg(0,0,0,
"Writing \"%s\" mesh: %s", mesh.
name.c_str(), output_file.c_str());
2185 if(write_extra_elec) {
2188 if(param_globals::gridout_e & 1) {
2190 if(param_globals::output_level > 1)
2191 log_msg(0,0,0,
"Computing \"%s\" surface ..", mesh.
name.c_str());
2194 std::string output_file = output_base +
"_e.surf";
2195 log_msg(0,0,0,
"Writing \"%s\" surface: %s", mesh.
name.c_str(), output_file.c_str());
2198 if(param_globals::gridout_e & 2) {
2199 bool write_binary =
false;
2200 std::string output_file = output_base +
"_e";
2201 log_msg(0,0,0,
"Writing \"%s\" mesh: %s", mesh.
name.c_str(), output_file.c_str());
2211 print_active_citation_suggestions();
2213 const paramschema::ResetResult reset = paramschema::reset_schema_state(paramschema::openCARP_schema());
2214 print_lines(stderr,
"parameter cleanup warning: ", reset.warnings);
2215 print_lines(stderr,
"parameter cleanup error: ", reset.errors);
2227 char* filecopy =
dupstr(file);
2228 char* dir =
dupstr(dirname(filecopy));
2245 PetscErrorPrintf = PetscErrorPrintfNone;
2255 for(
size_t eidx = 0; eidx < mesh.
l_numelem; eidx++) {
2258 if(mindim < cdim) mindim = cdim;
2285 int gsize = inp_data->
gsize();
2291 regigb.
x(gsize / dpn);
2292 regigb.
dim_x(regigb.
x()-1);
2295 regigb.
y(1); regigb.
z(1);
2311 regigb.
inc_t(param_globals::spacedt);
2325 log_msg(0,5,0,
"%s error: Could not set up data output! Aborting!", __func__);
2332 IO.
spec = mesh_spec;
2342 buffmap[mesh_spec] = inp_copy;
2349 void igb_output_manager::register_output_async(
sf_vec* inp_data,
2377 for(
size_t i=0; i<alg_nod.
size(); i++)
2378 ioidx[i] = nbr[alg_nod[i]];
2383 for(
size_t i=0; i<idx->
size(); i++) {
2385 ioidx[i] = nbr[loc_nodal];
2405 if(param_globals::num_io_nodes == 0)
2408 register_output_async(inp_data, inp_meshid, dpn, name,
units, idx, elem_data);
2423 sc->
forward(*data_vec, *perm_vec);
2438 FILE* fd =
static_cast<FILE*
>(it.
igb.
fileptr());
2441 sf_vec* buff = fill_output_buffer(it);
2454 root_write(fd, restr_buff, PETSC_COMM_WORLD);
2482 FILE* fd =
static_cast<FILE*
>(it.
igb.
fileptr());
2513 const int max_line_len = 128;
2514 const char* file_sep =
"#=======================================================";
2521 fprintf(out->
fd,
"# CARP GIT commit hash: %s\n", GIT_COMMIT_HASH);
2522 fprintf(out->
fd,
"# dependency hashes: %s\n", SUBREPO_COMMITS);
2523 fprintf(out->
fd,
"\n");
2526 char line[8196] =
"# ";
2528 for (
int j=0; j<argc; j++) {
2529 strcat(line, argv[j]);
2530 if(strlen(line) > max_line_len) {
2531 fprintf(out->
fd,
"%s\n", line);
2537 fprintf(out->
fd,
"%s\n\n", line);
2541 for (
int i=1; i<argc; i++) {
2542 std::string argument(argv[i]);
2543 if (argument ==
"+F" || argument.find(
"_options_file")!= std::string::npos) {
2545 std::string init =
"";
2546 if (argument.find(
"_options_file")!= std::string::npos) {
2547 fprintf(out->
fd,
"%s = %s\n", argument.substr(1).c_str(), argv[i+1]);
2550 fprintf(out->
fd,
"%s>>\n", file_sep);
2553 fprintf(out->
fd,
"## %s ##\n", argv[i]);
2554 FILE *in = fopen(argv[i],
"r");
2555 while (fgets(line, 8196, in))
2556 fprintf(out->
fd,
"%s%s", init.c_str(), line);
2558 fprintf(out->
fd,
"\n##END of %s\n", argv[i]);
2559 fprintf(out->
fd,
"%s<<\n\n", file_sep);
2561 else if(argv[i][0] ==
'-')
2563 bool prelast = (i==argc-1);
2564 bool paramFollows = !prelast && ((argv[i+1][0] !=
'-') ||
2565 ((argv[i+1][1] >=
'0') && (argv[i+1][1] <=
'9')));
2571 char *optcpy = strdup(argv[i+1]);
2572 char *front = optcpy;
2574 while(*front==
' ' && *front) front++;
2576 while(*++front ==
' ');
2577 char *back = optcpy+strlen(optcpy)-1;
2578 while(*back==
' ' && back>front) back--;
2582 if (strstr(front,
"=") !=
nullptr)
2583 fprintf(out->
fd,
"%-40s= \"%s\"\n", argv[i]+1, front);
2585 fprintf(out->
fd,
"%-40s= %s\n", argv[i]+1, front);
2590 fprintf(out->
fd,
"%-40s= 1\n", argv[i]);
2605 snprintf(save_fn,
sizeof save_fn,
"exit.save.%.3f.roe", time);
2606 log_msg(NULL, 0, 0,
"savequit called at time %g\n", time);
opencarp::local_index_t mesh_int_t
opencarp::real_t SF_real
Global scalar type.
Basic utility structs and functions, mostly IO related.
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.
void set_elem(size_t eidx)
Set the view to a new element.
void clear_data()
Clear the mesh data from memory.
overlapping_layout< T > pl
nodal parallel layout
vector< S > she
sheet direction
vector< S > fib
fiber direction
size_t l_numelem
local number of elements
std::string name
the mesh name
void generate_par_layout()
Set up the parallel layout.
MPI_Comm comm
the parallel mesh is defined on a MPI world
vector< T > & get_numbering(SF_nbr nbr_type)
Get the vector defining a certain numbering.
vector< T > tag
element tag
hashmap::unordered_set< int > extr_tag
the element tags based on which the mesh has been extracted
Functor class generating a numbering optimized for PETSc.
void free_scatterings()
Free the registered scatterings.
Container for a PETSc VecScatter.
void forward(abstract_vector< T, S > &in, abstract_vector< T, S > &out, bool add=false)
Forward scattering.
Functor class applying a submesh renumbering.
size_t size() const
The current size of the vector.
void resize(size_t n)
Resize a vector.
void insert(InputIterator first, InputIterator last)
void dump_state(char *, float, opencarp::mesh_t gid, bool, unsigned int)
The abstract physics interface we can use to trigger all physics.
int timer_idx
the timer index received from the timer manager
virtual void output_timings()
virtual void compute_step()=0
virtual void initialize()=0
const char * name
The name of the physic, each physic should have one.
SF::vector< stimulus > stimuli
the electrical stimuli
std::string timer_unit(const int timer_id)
figure out units of a signal linked to a given timer
double timer_val(const int timer_id)
figure out current value of a signal linked to a given timer
int run(Electrics &elec)
Full pipeline: construct or load Z, stream vm.igb, write ECG_*.dat.
class to store shape definitions
std::map< SF::mixed_tuple< mesh_t, int >, sf_vec * > buffmap_elem
IGBheader * get_igb_header(const sf_vec *vec)
Get the pointer to the igb header for a vector that was registered for output.
void write_data()
write registered data to disk
SF::vector< async_io_item > async_IOs
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)
std::map< SF::mixed_tuple< mesh_t, int >, sf_vec * > buffmap
map data spec -> PETSc vector buffer
void close_files_and_cleanup()
close file descriptors
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.
SF::vector< sync_io_item > sync_IOs
stim_t type
type of stimulus
int timer_id
timer for stimulus
double strength
strength of stimulus
stim_protocol ptcl
applied stimulation protocol used
stim_pulse pulse
stimulus wave form
stim_physics phys
physics of stimulus
centralize time managment and output triggering
void initialize_neq_timer(const std::vector< double > &itrig, double idur, int ID, const char *iname, const char *poolname=nullptr)
long d_time
current time instance index
bool trigger(int ID) const
void initialize_eq_timer(double istart, double iend, int ntrig, double iintv, double idur, int ID, const char *iname, const char *poolname=nullptr)
void reset_timers()
Reset time in timer_manager and then reset registered timers.
double start
initial time (nonzero when restarting)
void initialize_singlestep_timer(double tg, double idur, int ID, const char *iname, const char *poolname=nullptr)
std::vector< base_timer * > timers
vector containing individual timers
Base class for tracking progress.
Top-level header of FEM module.
void extract_tagbased(const meshdata< T, S > &mesh, meshdata< T, S > &submesh)
Extract a submesh based on element tags.
void write_data_ascii(const MPI_Comm comm, const vector< T > &idx, const vector< S > &data, std::string file, short dpn=1)
void compute_surface_mesh(const meshdata< T, S > &mesh, const SF_nbr numbering, const hashmap::unordered_set< T > &tags, meshdata< T, S > &surfmesh)
Compute the surface of a given mesh.
void print_DD_info(const meshdata< T, S > &mesh)
Print some basic information on the domain decomposition of a mesh.
void read_points(const std::string basename, const MPI_Comm comm, vector< S > &pts, vector< T > &ptsidx)
Read the points and insert them into a list of meshes.
void write_surface(const meshdata< T, S > &surfmesh, std::string surffile)
void count(const vector< T > &data, vector< S > &cnt)
Count number of occurrences of indices.
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.
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).
void redistribute_elements(meshdata< T, S > &mesh, meshdata< T, S > &sendbuff, vector< T > &part)
Redistribute the element data of a parallel mesh among the ranks based on a partitioning.
T local_nodal_to_local_petsc(const meshdata< T, S > &mesh, int rank, T local_nodal)
void init_vector(SF::abstract_vector< T, S > **vec)
void extract_myocardium(const meshdata< T, S > &mesh, meshdata< T, S > &submesh, bool require_fibers=true)
Extract the myocardium submesh.
void write_mesh_parallel(const meshdata< T, S > &mesh, bool binary, std::string basename)
Write a parallel mesh to harddisk without gathering it on one rank.
size_t root_write(FILE *fd, const vector< V > &vec, MPI_Comm comm)
Write vector data binary to disk.
void read_elements(meshdata< T, S > &mesh, std::string basename, bool require_fibers=true)
Read the element data (elements and fibers) of a CARP mesh.
@ NBR_PETSC
PETSc numbering of nodes.
@ NBR_ELEM_REF
The element numbering of the reference mesh (the one stored on HD).
@ NBR_SUBMESH
Submesh nodal numbering: The globally ascending sorted reference indices are reindexed.
@ NBR_ELEM_SUBMESH
Submesh element numbering: The globally ascending sorted reference indices are reindexed.
int load_ionic_module(const char *)
void COMPUTE_do_output(SF_real *dat, const int lsize, const int IO_id)
int COMPUTE_register_output(const SF::vector< mesh_int_t > &idx, const int dpn, const char *name, const char *units)
std::map< int, std::string > units
MPI_Comm IO_Intercomm
Communicator between IO and compute worlds.
FILE * petsc_error_fd
file descriptor for petsc error output
timer_manager * tm_manager
a manager for the various physics timers
std::map< datavec_t, sf_vec * > datavec_reg
important solution vectors from different physics
bool using_legacy_stimuli
flag storing whether legacy stimuli are used
SF::scatter_registry scatter_reg
Registry for the different scatter objects.
std::map< mesh_t, sf_mesh > mesh_reg
Registry for the different meshes used in a multi-physics simulation.
std::map< physic_t, Basic_physic * > physics_reg
the physics
void time_to_string(float time, char *str, short str_size)
physic_t
Identifier for the different physics we want to set up.
sf_vec * get_data(datavec_t d)
Retrieve a petsc data vector from the data registry.
void output_parameter_file(const char *fname, int argc, char **argv)
void initialize_physics()
Initialize all physics in the registry.
bool setup_IO(int argc, char **argv)
void retag_elements(sf_mesh &mesh, TagRegion *tagRegs, int ntr)
void parse_params_cpy(int argc, char **argv)
Initialize input parameters on a copy of the real command line parameters.
sf_mesh & get_mesh(const mesh_t gt)
Get a mesh by specifying the gridID.
int set_ignore_flags(int mode)
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
datavec_t
Enum used to adress the different data vectors stored in the data registry.
void register_physics()
Register physics to the physics registry.
void post_process()
do postprocessing
void check_and_convert_params()
Here we want to put all parameter checks, conversions and modifications that have been littered throu...
int get_rank(MPI_Comm comm=PETSC_COMM_WORLD)
short get_mesh_dim(mesh_t id)
get (lowest) dimension of the mesh used in the experiment
T get_global(T in, MPI_Op OP, MPI_Comm comm=PETSC_COMM_WORLD)
Do a global reduction on a variable.
bool is_potential(stim_t type)
uses current for stimulation
bool phys_defined(int physreg)
function to check if certain physics are defined
void update_console_output(const timer_manager &tm, prog_stats &p)
void show_build_info()
show the build info, exit if -buildinfo was provided. This code runs before MPI_Init().
FILE_SPEC f_open(const char *fname, const char *mode)
Open a FILE_SPEC.
void savequit()
save state and quit simulator
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)
void register_data(sf_vec *dat, datavec_t d)
Register a data vector in the global registry.
void basic_timer_setup()
Here we set up the timers that we always want to have, independent of physics.
char * get_file_dir(const char *file)
IO_t
The different output (directory) types.
void get_protocol_column_widths(std::vector< int > &col_width, std::vector< int > &used_timer_ids)
int get_phys_index(int physreg)
get index in param_globals::phys_region array for a certain phys region
void check_nullspace_ok()
int postproc_recover_phie()
char * dupstr(const char *old_str)
int plot_protocols(const char *fname)
plot simulation protocols (I/O timers, stimuli, boundary conditions, etc)
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.
void log_msg(FILE_SPEC out, int level, unsigned char flag, const char *fmt,...)
mesh_t
The enum identifying the different meshes we might want to load.
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.
void get_time(double &tm)
bool mesh_is_registered(const mesh_t gt)
check wheter a SF mesh is set
int get_size(MPI_Comm comm=PETSC_COMM_WORLD)
Basic_physic * get_physics(physic_t p, bool error_if_missing)
Convinience function to get a physics.
size_t renormalise_fibres(SF::vector< mesh_real_t > &fib, size_t l_numelem)
void destroy_physics()
Destroy all physics in the registry.
void ignore_extracellular_stim(Stimulus *st, int ns, int ignore)
void init_console_output(const timer_manager &tm, prog_stats &p)
tagreg_t
tag regions types. must be in line with carp.prm
V timing(V &t2, const V &t1)
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.
std::string get_basename(const std::string &path)
void update_cwd()
save the current working directory to curdir so that we can switch back to it if needed.
void f_close(FILE_SPEC &f)
Close a FILE_SPEC.
void setup_petsc_err_log()
set up error logs for PETSc, so that it doesnt print errors to stderr.
void simulate()
Main simulate loop.
void parse_mesh_types()
Parse the phys_type CLI parameters and set up (empty) SF::meshdata meshes.
Top-level header of physics module.
#define PHYSREG_INTRA_ELEC
#define PETSC_TO_CANONICAL
Permute algebraic data from PETSC to canonical ordering.
#define ELEM_PETSC_TO_CANONICAL
Permute algebraic element data from PETSC to canonical ordering.
#define PHYSREG_EXTRA_ELEC
std::vector< std::string > runtime_args
ParserFallbackMode fallback_mode
std::string unavailable_reason
Simulator-level utility execution control functions.
#define STM_IGNORE_PSEUDO_BIDM
#define STM_IGNORE_MONODOMAIN
#define STM_IGNORE_BIDOMAIN
#define Extracellular_Ground
#define Extracellular_V_OL
const SF::vector< mesh_int_t > * restr_idx
when using asyncIO, here we store the different IDs associated to the vectors we output
SF::vector< mesh_int_t > restr_petsc_idx
pointer to index vector with nodal indices we restrict to.
int IO_id
pointer to data registered for output
long d_trigger_dur
discrete duration
const char * name
timer name
bool triggered
flag indicating trigger at current time step
for display execution progress and statistical data of electrical solve
double curr
current output wallclock time
double start
output start wallclock time
double last
last output wallclock time
bool elem_flag
igb header we use for output
IGBheader igb
pointer to index vector used for restricting output.
const SF::vector< mesh_int_t > * restr_idx
pointer to data registered for output
SF::mixed_tuple< mesh_t, int > spec
flag whether the data is elements-wise