From ebebc3cddb344912490fde90e914c32ec07f76f8 Mon Sep 17 00:00:00 2001 From: Louis Robion Date: Fri, 21 Aug 2026 14:21:43 -0400 Subject: [PATCH 1/5] merged input yaml records seed used --- Code.v05-00/include/Core/Input_Mod.hpp | 2 +- Code.v05-00/include/Util/MC_Rand.hpp | 4 ++-- .../YamlInputReader/YamlInputReader.hpp | 5 ++++ Code.v05-00/src/Main.cpp | 8 ++++--- Code.v05-00/src/Util/MC_Rand.cpp | 24 ++++++++++++------- .../src/YamlInputReader/YamlInputReader.cpp | 13 ++++++---- Code.v05-00/tests/test_yamlreader.cpp | 24 +++++++++++++++++++ README.md | 2 +- 8 files changed, 62 insertions(+), 20 deletions(-) diff --git a/Code.v05-00/include/Core/Input_Mod.hpp b/Code.v05-00/include/Core/Input_Mod.hpp index 470ab310e..2b08881dc 100644 --- a/Code.v05-00/include/Core/Input_Mod.hpp +++ b/Code.v05-00/include/Core/Input_Mod.hpp @@ -43,7 +43,7 @@ struct OptInput bool SIMULATION_BOXMODEL; std::string SIMULATION_BOX_FILENAME; bool SIMULATION_FORCE_SEED; - int SIMULATION_SEED_VALUE; + unsigned int SIMULATION_SEED_VALUE; epm_type SIMULATION_EPM_TYPE; std::string SIMULATION_EXTERNAL_EPM_NETCDF_FILENAME; diff --git a/Code.v05-00/include/Util/MC_Rand.hpp b/Code.v05-00/include/Util/MC_Rand.hpp index c5e776a5a..fef276119 100644 --- a/Code.v05-00/include/Util/MC_Rand.hpp +++ b/Code.v05-00/include/Util/MC_Rand.hpp @@ -17,8 +17,8 @@ #include #include "Core/Input_Mod.hpp" -/* Set seed for pseudo-random generator */ -void setSeed(const OptInput& input); +// Set seed for pseudo-random generator and return the seed it was given/set. +unsigned int setSeed(const OptInput& input); /* Generates a random number of type T between fMin and fMax */ template diff --git a/Code.v05-00/include/YamlInputReader/YamlInputReader.hpp b/Code.v05-00/include/YamlInputReader/YamlInputReader.hpp index 81ea22bff..0997c9fa7 100644 --- a/Code.v05-00/include/YamlInputReader/YamlInputReader.hpp +++ b/Code.v05-00/include/YamlInputReader/YamlInputReader.hpp @@ -29,6 +29,11 @@ namespace YamlInputReader{ // Fill OptInput and Input from an already merged node. void populateInput(OptInput& input, Input& scenario, const YAML::Node& merged); + // Rewrite the seed of a merged node to the seed the run actually used, so that + // re-running APCEMM on the written node reproduces the run. Sets + // 'Force seed value (T/F)' to T and 'Seed value (positive int)' to seed. + void recordEffectiveSeed(YAML::Node& node, unsigned int seed); + // Write a node to outputDir/filename. The directory must already exist. void writeYaml(const YAML::Node& node, const std::filesystem::path& outputDir, const string& filename); diff --git a/Code.v05-00/src/Main.cpp b/Code.v05-00/src/Main.cpp index dab2e9746..7d089bf46 100644 --- a/Code.v05-00/src/Main.cpp +++ b/Code.v05-00/src/Main.cpp @@ -104,8 +104,9 @@ int main( int argc, char* argv[]) Diag::set_storePSD(true); } - // Set the seed once at the top-level - setSeed( Input_Opt ); + // Set the seed once at the top-level and keep the seed that was used, to save + // it in the merged input file output + const unsigned int effectiveSeed = setSeed( Input_Opt ); if ( folderIsOccupied( Input_Opt.SIMULATION_OUTPUT_FOLDER ) \ && !Input_Opt.SIMULATION_OVERWRITE ) { @@ -140,7 +141,8 @@ int main( int argc, char* argv[]) } - // Write merged YAML input files to output directory + // Write merged YAML input files to output directory, with the seed this run used + YamlInputReader::recordEffectiveSeed( mergedInput, effectiveSeed ); YamlInputReader::writeYaml( mergedInput, Input_Opt.SIMULATION_OUTPUT_FOLDER, OutputFiles::MERGED_YAML ); /* The switch is a placeholder for future models. Only the plume model is diff --git a/Code.v05-00/src/Util/MC_Rand.cpp b/Code.v05-00/src/Util/MC_Rand.cpp index 9d877bd42..b9070c824 100644 --- a/Code.v05-00/src/Util/MC_Rand.cpp +++ b/Code.v05-00/src/Util/MC_Rand.cpp @@ -10,30 +10,36 @@ /* File : MC_Rand.cpp */ /* */ /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ +#include #include #include "APCEMM.h" #include "Util/MC_Rand.hpp" #include "Core/Input_Mod.hpp" -void setSeed(const OptInput& input) { +unsigned int setSeed(const OptInput& input) { - // Sets seed for pseudo-random generator. + unsigned int seed; + // Sets seed for pseudo-random generator and return its value to save it later. #ifdef DEBUG // With DEBUG compile flag set a constant seed for reproducibility - std::cout << "Compiled in DEBUG mode: random seed is set to 0 for all simulations" << std::endl; - srand(0); + seed = 0; + std::cout << "Compiled in DEBUG mode: random seed is set to " << seed << " for all simulations" << std::endl; #else if(input.SIMULATION_FORCE_SEED){ - srand(input.SIMULATION_SEED_VALUE); - std::cout << "Random seed is set to " << input.SIMULATION_SEED_VALUE << " for all simulations" << std::endl; + seed = input.SIMULATION_SEED_VALUE; } else{ - // If the seed is not being forced to a value use the current unix timestamp instead. - srand(time(NULL)); + // If the seed is not being forced to a value use the current unix timestamp + // instead. The conversion from time_t to unsigned int wraps (time_t is larger) + // but that's fine as any unsigned int is a usable seed. + seed = static_cast(std::time(nullptr)); } - + std::cout << "Random seed is set to " << seed << " for all simulations" << std::endl; #endif + srand(seed); + return seed; + } /* End of setSeed */ template diff --git a/Code.v05-00/src/YamlInputReader/YamlInputReader.cpp b/Code.v05-00/src/YamlInputReader/YamlInputReader.cpp index a7d5692e1..f4340c48d 100644 --- a/Code.v05-00/src/YamlInputReader/YamlInputReader.cpp +++ b/Code.v05-00/src/YamlInputReader/YamlInputReader.cpp @@ -253,6 +253,14 @@ namespace YamlInputReader{ } } + // Overwrite both seed fields with the actual seed used so the saved merged input file + // can exactly reproduce the run. + void recordEffectiveSeed(YAML::Node& node, unsigned int seed){ + YAML::Node seedSubmenu = node["SIMULATION MENU"]["RANDOM NUMBER GENERATION SUBMENU"]; + seedSubmenu["Force seed value (T/F)"] = "T"; + seedSubmenu["Seed value (positive int)"] = seed; + } + // Output dir must exist before calling this void writeYaml(const YAML::Node& node, const std::filesystem::path& outputDir, const string& filename){ const std::filesystem::path fullPath = outputDir / filename; @@ -318,10 +326,7 @@ namespace YamlInputReader{ YAML::Node seedSubmenu = simNode["RANDOM NUMBER GENERATION SUBMENU"]; input.SIMULATION_FORCE_SEED = parseBoolString(seedSubmenu["Force seed value (T/F)"].as(), "Force seed value (T/F)"); - input.SIMULATION_SEED_VALUE = parseIntString(seedSubmenu["Seed value (positive int)"].as(), "Seed value (positive int)"); - if(input.SIMULATION_SEED_VALUE < 0){ - throw std::invalid_argument("Seed value (under SIMULATION MENU) cannot be less than 0!"); - } + input.SIMULATION_SEED_VALUE = parseScalarUIntParam(seedSubmenu["Seed value (positive int)"].as(), "Seed value (positive int)"); string epm = simNode["EPM type (original/external/new)"].as(); diff --git a/Code.v05-00/tests/test_yamlreader.cpp b/Code.v05-00/tests/test_yamlreader.cpp index 0926f0eaf..7c4c65daf 100644 --- a/Code.v05-00/tests/test_yamlreader.cpp +++ b/Code.v05-00/tests/test_yamlreader.cpp @@ -482,6 +482,30 @@ TEST_CASE("mergeYamlInputFiles resolves defaults and overrides into one node"){ REQUIRE(merged["SIMULATION MENU"]["RANDOM NUMBER GENERATION SUBMENU"]["Force seed value (T/F)"].as() == "F"); } +TEST_CASE("recordEffectiveSeed updates seed submenu"){ + string filename = string(APCEMM_TESTS_DIR) + YAML_DIR + "/test1.yaml"; + YAML::Node merged = YamlInputReader::mergeYamlInputFiles({filename}); + // Verify that the default is an unforced seed otherwise the rest of the test is useless + REQUIRE(merged["SIMULATION MENU"]["RANDOM NUMBER GENERATION SUBMENU"]["Force seed value (T/F)"].as() == "F"); + + SECTION("The seed submenu is rewritten in place"){ + YamlInputReader::recordEffectiveSeed(merged, 1755792000); + + // Verify that the node is correctly updated + YAML::Node seedSubmenu = merged["SIMULATION MENU"]["RANDOM NUMBER GENERATION SUBMENU"]; + REQUIRE(seedSubmenu["Force seed value (T/F)"].as() == "T"); + REQUIRE(seedSubmenu["Seed value (positive int)"].as() == "1755792000"); + + // Sanity check that if we recreate an input from the new node + // the seed menu is correct + OptInput input; + Input scenario; + YamlInputReader::populateInput(input, scenario, merged); + REQUIRE(input.SIMULATION_FORCE_SEED == true); + REQUIRE(input.SIMULATION_SEED_VALUE == 1755792000); + } +} + TEST_CASE("writeYaml round trip"){ string filename = string(APCEMM_TESTS_DIR) + YAML_DIR + "/test1.yaml"; YAML::Node merged = YamlInputReader::mergeYamlInputFiles({filename}); diff --git a/README.md b/README.md index 6a8eae712..80b48164c 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,7 @@ Examples and their accompanying jupyter notebooks for postprocessing tutorials a The input file options are explained via comments in the file `Code.v05-00/defaults/input.yaml` -Each run writes `merged-input.yaml` into its output folder to record the result of the input merging step. It holds the defaults overwritten by every parameter in user input files passed to APCEMM, in the order they were passed. +Each run writes `merged-input.yaml` into its output folder which is an input file that can be used to reproduce exactly the run. It records the result of the input merging step and records the seed used for that run (even if it was not manually forced). It holds the defaults overwritten by every parameter in user input files passed to APCEMM, in the order they were passed. Advanced simulation parameters hidden in the input files (e.g. Aerosol bin size ratios, minimum/max bin aerosol sizes, etc) can be modified in `Code.v05-00/src/include/Parameters.hpp`. From afe6defcfc5760cd139d773233fad0f6da7f4c1b Mon Sep 17 00:00:00 2001 From: Louis Robion Date: Fri, 21 Aug 2026 15:21:47 -0400 Subject: [PATCH 2/5] Update OptInput object with seed --- Code.v05-00/include/Util/MC_Rand.hpp | 4 ++-- Code.v05-00/src/Main.cpp | 7 +++---- Code.v05-00/src/Util/MC_Rand.cpp | 20 +++++++------------ .../src/YamlInputReader/YamlInputReader.cpp | 1 + 4 files changed, 13 insertions(+), 19 deletions(-) diff --git a/Code.v05-00/include/Util/MC_Rand.hpp b/Code.v05-00/include/Util/MC_Rand.hpp index fef276119..0f674fa4b 100644 --- a/Code.v05-00/include/Util/MC_Rand.hpp +++ b/Code.v05-00/include/Util/MC_Rand.hpp @@ -17,8 +17,8 @@ #include #include "Core/Input_Mod.hpp" -// Set seed for pseudo-random generator and return the seed it was given/set. -unsigned int setSeed(const OptInput& input); +// Set seed for pseudo-random generator +void setSeed(OptInput& input); /* Generates a random number of type T between fMin and fMax */ template diff --git a/Code.v05-00/src/Main.cpp b/Code.v05-00/src/Main.cpp index 7d089bf46..639330d7c 100644 --- a/Code.v05-00/src/Main.cpp +++ b/Code.v05-00/src/Main.cpp @@ -104,9 +104,8 @@ int main( int argc, char* argv[]) Diag::set_storePSD(true); } - // Set the seed once at the top-level and keep the seed that was used, to save - // it in the merged input file output - const unsigned int effectiveSeed = setSeed( Input_Opt ); + // Set the seed once at the top-level + setSeed( Input_Opt ); if ( folderIsOccupied( Input_Opt.SIMULATION_OUTPUT_FOLDER ) \ && !Input_Opt.SIMULATION_OVERWRITE ) { @@ -142,7 +141,7 @@ int main( int argc, char* argv[]) } // Write merged YAML input files to output directory, with the seed this run used - YamlInputReader::recordEffectiveSeed( mergedInput, effectiveSeed ); + YamlInputReader::recordEffectiveSeed( mergedInput, Input_Opt.SIMULATION_SEED_VALUE ); YamlInputReader::writeYaml( mergedInput, Input_Opt.SIMULATION_OUTPUT_FOLDER, OutputFiles::MERGED_YAML ); /* The switch is a placeholder for future models. Only the plume model is diff --git a/Code.v05-00/src/Util/MC_Rand.cpp b/Code.v05-00/src/Util/MC_Rand.cpp index b9070c824..763790aa2 100644 --- a/Code.v05-00/src/Util/MC_Rand.cpp +++ b/Code.v05-00/src/Util/MC_Rand.cpp @@ -16,30 +16,24 @@ #include "Util/MC_Rand.hpp" #include "Core/Input_Mod.hpp" -unsigned int setSeed(const OptInput& input) { +void setSeed(OptInput& input) { - unsigned int seed; // Sets seed for pseudo-random generator and return its value to save it later. #ifdef DEBUG // With DEBUG compile flag set a constant seed for reproducibility - seed = 0; - std::cout << "Compiled in DEBUG mode: random seed is set to " << seed << " for all simulations" << std::endl; + input.SIMULATION_SEED_VALUE = 0; + std::cout << "Compiled in DEBUG mode: random seed is set to " << input.SIMULATION_SEED_VALUE << " for all simulations" << std::endl; #else - if(input.SIMULATION_FORCE_SEED){ - seed = input.SIMULATION_SEED_VALUE; - } - else{ + if(!input.SIMULATION_FORCE_SEED){ // If the seed is not being forced to a value use the current unix timestamp // instead. The conversion from time_t to unsigned int wraps (time_t is larger) // but that's fine as any unsigned int is a usable seed. - seed = static_cast(std::time(nullptr)); + input.SIMULATION_SEED_VALUE = static_cast(std::time(nullptr)); } - std::cout << "Random seed is set to " << seed << " for all simulations" << std::endl; + std::cout << "Random seed is set to " << input.SIMULATION_SEED_VALUE << " for all simulations" << std::endl; #endif - srand(seed); - return seed; - + srand(input.SIMULATION_SEED_VALUE); } /* End of setSeed */ template diff --git a/Code.v05-00/src/YamlInputReader/YamlInputReader.cpp b/Code.v05-00/src/YamlInputReader/YamlInputReader.cpp index f4340c48d..7142da6ea 100644 --- a/Code.v05-00/src/YamlInputReader/YamlInputReader.cpp +++ b/Code.v05-00/src/YamlInputReader/YamlInputReader.cpp @@ -1,4 +1,5 @@ #include "YamlInputReader/YamlInputReader.hpp" +#include "Core/Input_Mod.hpp" #include "YamlInputReader/YamlPathUtils.hpp" #include "APCEMM.h" #include "Util/ForwardDecl.hpp" From e4d4c94a5258093fbb5b75f5bdf304c19219b243 Mon Sep 17 00:00:00 2001 From: Louis Robion Date: Fri, 21 Aug 2026 15:32:10 -0400 Subject: [PATCH 3/5] Check that RNG submenu is up to date --- .../YamlInputReader/YamlInputReader.hpp | 1 + .../src/YamlInputReader/YamlInputReader.cpp | 23 ++++++++++++++++--- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/Code.v05-00/include/YamlInputReader/YamlInputReader.hpp b/Code.v05-00/include/YamlInputReader/YamlInputReader.hpp index 0997c9fa7..65715b34b 100644 --- a/Code.v05-00/include/YamlInputReader/YamlInputReader.hpp +++ b/Code.v05-00/include/YamlInputReader/YamlInputReader.hpp @@ -32,6 +32,7 @@ namespace YamlInputReader{ // Rewrite the seed of a merged node to the seed the run actually used, so that // re-running APCEMM on the written node reproduces the run. Sets // 'Force seed value (T/F)' to T and 'Seed value (positive int)' to seed. + // Throws if the node does not already carry that submenu and both keys. void recordEffectiveSeed(YAML::Node& node, unsigned int seed); // Write a node to outputDir/filename. The directory must already exist. diff --git a/Code.v05-00/src/YamlInputReader/YamlInputReader.cpp b/Code.v05-00/src/YamlInputReader/YamlInputReader.cpp index 7142da6ea..26b2d8141 100644 --- a/Code.v05-00/src/YamlInputReader/YamlInputReader.cpp +++ b/Code.v05-00/src/YamlInputReader/YamlInputReader.cpp @@ -257,9 +257,26 @@ namespace YamlInputReader{ // Overwrite both seed fields with the actual seed used so the saved merged input file // can exactly reproduce the run. void recordEffectiveSeed(YAML::Node& node, unsigned int seed){ - YAML::Node seedSubmenu = node["SIMULATION MENU"]["RANDOM NUMBER GENERATION SUBMENU"]; - seedSubmenu["Force seed value (T/F)"] = "T"; - seedSubmenu["Seed value (positive int)"] = seed; + const string submenuKey = "RANDOM NUMBER GENERATION SUBMENU"; + const string forceKey = "Force seed value (T/F)"; + const string seedKey = "Seed value (positive int)"; + + // Double check that these keys exist otherwise we'd be creating + // new nodes instead of updating in place. const[] look-up + // does not create a new node if it does not exist + // Only happens if the RNG submenu is changed and keys are not updated here + const YAML::Node& readOnly = node; + const YAML::Node existing = readOnly["SIMULATION MENU"][submenuKey]; + if (!existing.IsMap() || !existing[forceKey].IsDefined() || !existing[seedKey].IsDefined()){ + throw std::runtime_error("Cannot record the effective seed: 'SIMULATION MENU -> " + + submenuKey + "' with keys '" + forceKey + "' and '" + + seedKey + "' is missing from the merged input."); + } + + // Update seed values here + YAML::Node seedSubmenu = node["SIMULATION MENU"][submenuKey]; + seedSubmenu[forceKey] = "T"; + seedSubmenu[seedKey] = seed; } // Output dir must exist before calling this From 6cdbda317c619cd8052dbb1973f5d674848dad9b Mon Sep 17 00:00:00 2001 From: Louis Robion Date: Fri, 21 Aug 2026 15:44:19 -0400 Subject: [PATCH 4/5] Fix stale comment --- Code.v05-00/src/Util/MC_Rand.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Code.v05-00/src/Util/MC_Rand.cpp b/Code.v05-00/src/Util/MC_Rand.cpp index 763790aa2..49cd96dab 100644 --- a/Code.v05-00/src/Util/MC_Rand.cpp +++ b/Code.v05-00/src/Util/MC_Rand.cpp @@ -18,7 +18,7 @@ void setSeed(OptInput& input) { - // Sets seed for pseudo-random generator and return its value to save it later. + // Sets seed for pseudo-random generator and updates OptInput #ifdef DEBUG // With DEBUG compile flag set a constant seed for reproducibility input.SIMULATION_SEED_VALUE = 0; From 71c0e7bad6c6e1b89e782514f59bd3f151723958 Mon Sep 17 00:00:00 2001 From: Louis Robion Date: Fri, 21 Aug 2026 15:57:05 -0400 Subject: [PATCH 5/5] Cleanup --- Code.v05-00/include/Util/MC_Rand.hpp | 5 +++-- Code.v05-00/src/Main.cpp | 8 +++++--- Code.v05-00/src/Util/MC_Rand.cpp | 6 ++++-- Code.v05-00/src/YamlInputReader/YamlInputReader.cpp | 2 +- 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/Code.v05-00/include/Util/MC_Rand.hpp b/Code.v05-00/include/Util/MC_Rand.hpp index 0f674fa4b..4099642b6 100644 --- a/Code.v05-00/include/Util/MC_Rand.hpp +++ b/Code.v05-00/include/Util/MC_Rand.hpp @@ -17,8 +17,9 @@ #include #include "Core/Input_Mod.hpp" -// Set seed for pseudo-random generator -void setSeed(OptInput& input); +// Set the seed of the pseudo-random generator and return it. +// Also writes the resolved seed back into input.SIMULATION_SEED_VALUE. +unsigned int setSeed(OptInput& input); /* Generates a random number of type T between fMin and fMax */ template diff --git a/Code.v05-00/src/Main.cpp b/Code.v05-00/src/Main.cpp index 639330d7c..f969ef47f 100644 --- a/Code.v05-00/src/Main.cpp +++ b/Code.v05-00/src/Main.cpp @@ -104,8 +104,10 @@ int main( int argc, char* argv[]) Diag::set_storePSD(true); } - // Set the seed once at the top-level - setSeed( Input_Opt ); + // Set the seed once at the top-level, update in place in + // OptInput and return it as well to ensure setSeed cannot be called + // after writeYaml + const unsigned int seed = setSeed( Input_Opt ); if ( folderIsOccupied( Input_Opt.SIMULATION_OUTPUT_FOLDER ) \ && !Input_Opt.SIMULATION_OVERWRITE ) { @@ -141,7 +143,7 @@ int main( int argc, char* argv[]) } // Write merged YAML input files to output directory, with the seed this run used - YamlInputReader::recordEffectiveSeed( mergedInput, Input_Opt.SIMULATION_SEED_VALUE ); + YamlInputReader::recordEffectiveSeed( mergedInput, seed ); YamlInputReader::writeYaml( mergedInput, Input_Opt.SIMULATION_OUTPUT_FOLDER, OutputFiles::MERGED_YAML ); /* The switch is a placeholder for future models. Only the plume model is diff --git a/Code.v05-00/src/Util/MC_Rand.cpp b/Code.v05-00/src/Util/MC_Rand.cpp index 49cd96dab..8d1fb123d 100644 --- a/Code.v05-00/src/Util/MC_Rand.cpp +++ b/Code.v05-00/src/Util/MC_Rand.cpp @@ -16,9 +16,10 @@ #include "Util/MC_Rand.hpp" #include "Core/Input_Mod.hpp" -void setSeed(OptInput& input) { +unsigned int setSeed(OptInput& input) { - // Sets seed for pseudo-random generator and updates OptInput + // Sets the seed of the pseudo-random generator, stores it in OptInput and returns + // it, so the caller can record the seed the run actually used. #ifdef DEBUG // With DEBUG compile flag set a constant seed for reproducibility input.SIMULATION_SEED_VALUE = 0; @@ -34,6 +35,7 @@ void setSeed(OptInput& input) { #endif srand(input.SIMULATION_SEED_VALUE); + return input.SIMULATION_SEED_VALUE; } /* End of setSeed */ template diff --git a/Code.v05-00/src/YamlInputReader/YamlInputReader.cpp b/Code.v05-00/src/YamlInputReader/YamlInputReader.cpp index 26b2d8141..12e5391e6 100644 --- a/Code.v05-00/src/YamlInputReader/YamlInputReader.cpp +++ b/Code.v05-00/src/YamlInputReader/YamlInputReader.cpp @@ -267,7 +267,7 @@ namespace YamlInputReader{ // Only happens if the RNG submenu is changed and keys are not updated here const YAML::Node& readOnly = node; const YAML::Node existing = readOnly["SIMULATION MENU"][submenuKey]; - if (!existing.IsMap() || !existing[forceKey].IsDefined() || !existing[seedKey].IsDefined()){ + if (!existing.IsDefined() || !existing.IsMap() || !existing[forceKey].IsDefined() || !existing[seedKey].IsDefined()){ throw std::runtime_error("Cannot record the effective seed: 'SIMULATION MENU -> " + submenuKey + "' with keys '" + forceKey + "' and '" + seedKey + "' is missing from the merged input.");