Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Code.v05-00/include/Core/Input_Mod.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
5 changes: 3 additions & 2 deletions Code.v05-00/include/Util/MC_Rand.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@
#include <cstdlib>
#include "Core/Input_Mod.hpp"

/* Set seed for pseudo-random generator */
void setSeed(const 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 <typename T>
Expand Down
6 changes: 6 additions & 0 deletions Code.v05-00/include/YamlInputReader/YamlInputReader.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ 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.
// 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.
void writeYaml(const YAML::Node& node, const std::filesystem::path& outputDir, const string& filename);

Expand Down
9 changes: 6 additions & 3 deletions Code.v05-00/src/Main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 ) {
Expand Down Expand Up @@ -140,7 +142,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, 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
Expand Down
26 changes: 14 additions & 12 deletions Code.v05-00/src/Util/MC_Rand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,30 +10,32 @@
/* File : MC_Rand.cpp */
/* */
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
#include <ctime>
#include <iostream>
#include "APCEMM.h"
#include "Util/MC_Rand.hpp"
#include "Core/Input_Mod.hpp"

void setSeed(const OptInput& input) {
unsigned int setSeed(OptInput& input) {

// Sets seed for pseudo-random generator.
// 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
std::cout << "Compiled in DEBUG mode: random seed is set to 0 for all simulations" << std::endl;
srand(0);
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){
srand(input.SIMULATION_SEED_VALUE);
std::cout << "Random seed is set to " << input.SIMULATION_SEED_VALUE << " for all simulations" << std::endl;
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.
input.SIMULATION_SEED_VALUE = static_cast<unsigned int>(std::time(nullptr));
}
else{
// If the seed is not being forced to a value use the current unix timestamp instead.
srand(time(NULL));
}

std::cout << "Random seed is set to " << input.SIMULATION_SEED_VALUE << " for all simulations" << std::endl;
#endif

srand(input.SIMULATION_SEED_VALUE);
return input.SIMULATION_SEED_VALUE;
} /* End of setSeed */

template <typename T>
Expand Down
31 changes: 27 additions & 4 deletions Code.v05-00/src/YamlInputReader/YamlInputReader.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "YamlInputReader/YamlInputReader.hpp"
#include "Core/Input_Mod.hpp"
#include "YamlInputReader/YamlPathUtils.hpp"
#include "APCEMM.h"
#include "Util/ForwardDecl.hpp"
Expand Down Expand Up @@ -253,6 +254,31 @@ 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){
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.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.");
}

// Update seed values here
YAML::Node seedSubmenu = node["SIMULATION MENU"][submenuKey];
seedSubmenu[forceKey] = "T";
seedSubmenu[seedKey] = 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;
Expand Down Expand Up @@ -318,10 +344,7 @@ namespace YamlInputReader{

YAML::Node seedSubmenu = simNode["RANDOM NUMBER GENERATION SUBMENU"];
input.SIMULATION_FORCE_SEED = parseBoolString(seedSubmenu["Force seed value (T/F)"].as<string>(), "Force seed value (T/F)");
input.SIMULATION_SEED_VALUE = parseIntString(seedSubmenu["Seed value (positive int)"].as<string>(), "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<string>(), "Seed value (positive int)");

string epm =
simNode["EPM type (original/external/new)"].as<string>();
Expand Down
24 changes: 24 additions & 0 deletions Code.v05-00/tests/test_yamlreader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<string>() == "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<string>() == "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<string>() == "T");
REQUIRE(seedSubmenu["Seed value (positive int)"].as<string>() == "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});
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.

Expand Down