diff --git a/include/realizations/catchment/Bmi_Module_Formulation.hpp b/include/realizations/catchment/Bmi_Module_Formulation.hpp index 318fafe064..151e9958ab 100644 --- a/include/realizations/catchment/Bmi_Module_Formulation.hpp +++ b/include/realizations/catchment/Bmi_Module_Formulation.hpp @@ -21,6 +21,11 @@ class Bmi_C_Pet_IT; class Bmi_Cpp_Multi_Array_Test; namespace realization { + static bool is_ngen_realization_time_input(const std::string& var_name) { + return var_name == "ngen_realization_start_time" || + var_name == "ngen_realization_end_time" || + var_name == "ngen_realization_dt"; + } /** * Abstraction of a formulation with a single backing model object that implements the BMI. @@ -308,7 +313,6 @@ namespace realization { } protected: - /** * @brief Get correct BMI variable name, which may be the output or something mapped to this output. * @@ -393,6 +397,11 @@ namespace realization { */ void set_initial_bmi_parameters(geojson::PropertyMap properties); + /** + * If supported by the BMI module, pass realization timing metadata through legal BMI SetValue calls. + */ + void set_realization_time_inputs(); + /** * Test whether backing model has fixed time step size. * diff --git a/include/realizations/catchment/Bmi_Multi_Formulation.hpp b/include/realizations/catchment/Bmi_Multi_Formulation.hpp index 3387f9fa1e..6738def708 100644 --- a/include/realizations/catchment/Bmi_Multi_Formulation.hpp +++ b/include/realizations/catchment/Bmi_Multi_Formulation.hpp @@ -636,6 +636,9 @@ namespace realization { std::shared_ptr> var_aliases; var_aliases = std::make_shared>(std::map()); for (const std::string &var_name : mod->get_bmi_input_variables()) { + if (is_ngen_realization_time_input(var_name)) { + continue; + } std::string framework_alias = mod->get_config_mapped_variable_name(var_name); (*var_aliases)[framework_alias] = var_name; // If framework_name is not yet in collection from which we have available data sources ... diff --git a/src/realizations/catchment/Bmi_Module_Formulation.cpp b/src/realizations/catchment/Bmi_Module_Formulation.cpp index 359a257f88..ce92bb2499 100644 --- a/src/realizations/catchment/Bmi_Module_Formulation.cpp +++ b/src/realizations/catchment/Bmi_Module_Formulation.cpp @@ -6,6 +6,11 @@ #include "state_save_restore/State_Save_Utils.hpp" #include +#include +#include +#include +#include + std::stringstream bmiform_ss; namespace realization { @@ -426,6 +431,56 @@ namespace realization { return bmi_model_start_time_forcing_offset_s; } + void Bmi_Module_Formulation::set_realization_time_inputs() { + auto model = get_bmi_model(); + if (model == nullptr) { + return; + } + + std::vector input_vars = model->GetInputVarNames(); + + const bool has_start = + std::find(input_vars.begin(), input_vars.end(), "ngen_realization_start_time") != input_vars.end(); + const bool has_end = + std::find(input_vars.begin(), input_vars.end(), "ngen_realization_end_time") != input_vars.end(); + const bool has_dt = + std::find(input_vars.begin(), input_vars.end(), "ngen_realization_dt") != input_vars.end(); + + if (!has_start && !has_end && !has_dt) { + return; + } + + const double start_time_value = static_cast(forcing->get_data_start_time()); + const double end_time_value = static_cast(forcing->get_data_stop_time()); + const double dt_value = static_cast(forcing->record_duration()); + + if (dt_value <= 0.0) { + throw std::runtime_error( + "Invalid realization record duration while setting BMI realization time inputs for catchment '" + + this->get_id() + "'." + ); + } + + if (has_start) { + model->SetValue("ngen_realization_start_time", (void *)&start_time_value); + } + + if (has_end) { + model->SetValue("ngen_realization_end_time", (void *)&end_time_value); + } + + if (has_dt) { + model->SetValue("ngen_realization_dt", (void *)&dt_value); + } + + std::stringstream ss; + ss << "Applied realization time inputs via BMI SetValue for catchment '" << this->get_id() + << "': start_utime=" << static_cast(start_time_value) + << ", end_utime=" << static_cast(end_time_value) + << ", dt_seconds=" << static_cast(dt_value); + LOG(ss.str(), LogLevel::INFO); + } + void Bmi_Module_Formulation::inner_create_formulation(geojson::PropertyMap properties, bool needs_param_validation) { if (needs_param_validation) { validate_parameters(properties); @@ -435,6 +490,11 @@ namespace realization { set_bmi_main_output_var(properties.at(BMI_REALIZATION_CFG_PARAM_REQ__MAIN_OUT_VAR).as_string()); set_model_type_name(properties.at(BMI_REALIZATION_CFG_PARAM_REQ__MODEL_TYPE).as_string()); + const std::string model_type_name = + boost::algorithm::to_lower_copy( + properties.at(BMI_REALIZATION_CFG_PARAM_REQ__MODEL_TYPE).as_string() + ); + // Then optional ... auto uses_forcings_it = properties.find(BMI_REALIZATION_CFG_PARAM_OPT__USES_FORCINGS); @@ -473,6 +533,9 @@ namespace realization { //and set them before it is run set_initial_bmi_parameters(properties); + // Pass realization timing to BMI modules that explicitly advertise these inputs. + set_realization_time_inputs(); + // Make sure that this is able to interpret model time and convert to real time, since BMI model time is // usually starting at 0 and just counting up determine_model_time_offset(); @@ -885,6 +948,9 @@ namespace realization { time_t model_epoch_time = convert_model_time(model_init_time) + get_bmi_model_start_time_forcing_offset_s(); for (std::string & var_name : in_var_names) { + if (is_ngen_realization_time_input(var_name)) { + continue; + } data_access::GenericDataProvider *provider; std::string var_map_alias = get_config_mapped_variable_name(var_name); if (input_forcing_providers.find(var_map_alias) != input_forcing_providers.end()) { @@ -972,6 +1038,9 @@ namespace realization { inputs << "Input variables were as follows:"; for (std::string & var_name : in_var_names) { + if (is_ngen_realization_time_input(var_name)) { + continue; + } data_access::GenericDataProvider *provider; std::string var_map_alias = get_config_mapped_variable_name(var_name); if (input_forcing_providers.find(var_map_alias) != input_forcing_providers.end()) {