diff --git a/include/bmi/Bmi_Py_Adapter.hpp b/include/bmi/Bmi_Py_Adapter.hpp index d66124d534..3e956a41d8 100644 --- a/include/bmi/Bmi_Py_Adapter.hpp +++ b/include/bmi/Bmi_Py_Adapter.hpp @@ -597,6 +597,24 @@ namespace models { } } + /** + * Set the value of a variable. This version of setting a variable will send an array with the `size` specified instead of checking the BMI for its current size of the variable. + * Ownership of the pointer will remain in C++, so the consuming BMI should not maintain a reference to the values beyond the scope of its `set_value` method. + * + * @param name The name of the BMI variable. + * @param src Pointer to the data that will be sent to the BMI. + * @param size The number of items represented by the pointer. + */ + template + void set_value_unchecked(const std::string &name, T *src, size_t size) { + // declare readonly array info with the pointer and size + py::buffer_info info(src, static_cast(size), true); + // create the array with the info and NULL handler so python doesn't take ownership + py::array_t src_array(info, nullptr); + // pass the array to python to read; the BMI should not attempt to maintain a reference beyond the scope of this function to prevent trying to use freed memory + bmi_model->attr("set_value")(name, src_array); + } + /** * Set values for a model's BMI variable at specified indices. * diff --git a/include/core/Layer.hpp b/include/core/Layer.hpp index 5c3c4481fa..88817c15d0 100644 --- a/include/core/Layer.hpp +++ b/include/core/Layer.hpp @@ -104,7 +104,7 @@ namespace ngen /*** * @brief Run one simulation timestep for each model in this layer */ - virtual void update_models(boost::span catchment_outflows, + virtual void update_models(boost::span catchment_evapotranspiration, std::unordered_map &catchment_indexes, boost::span nexus_downstream_flows, std::unordered_map &nexus_indexes, diff --git a/include/core/NgenSimulation.hpp b/include/core/NgenSimulation.hpp index 00e5ef49eb..46af075e85 100644 --- a/include/core/NgenSimulation.hpp +++ b/include/core/NgenSimulation.hpp @@ -42,7 +42,7 @@ class NgenSimulation /** * Run the catchment formulations for the full configured duration of the simulation * - * Captures calculated runoff values in `catchment_outflows_` and + * Captures calculated runoff values in `catchment_evapotranspiration_` and * `nexus_downstream_flows_` for subsequent output and consumption * by `run_routing()` */ @@ -71,7 +71,7 @@ class NgenSimulation // Routing data structured for t-route std::unordered_map catchment_indexes_; - std::vector catchment_outflows_; + std::vector catchment_evapotranspiration_; std::unordered_map nexus_indexes_; std::vector nexus_downstream_flows_; diff --git a/include/realizations/catchment/Bmi_Formulation.hpp b/include/realizations/catchment/Bmi_Formulation.hpp index f2a13074e8..6adda840b3 100644 --- a/include/realizations/catchment/Bmi_Formulation.hpp +++ b/include/realizations/catchment/Bmi_Formulation.hpp @@ -16,6 +16,7 @@ #define BMI_REALIZATION_CFG_PARAM_REQ__MODEL_TYPE "model_type_name" // Then the optional +#define BMI_REALIZATION_CFG_PARAM_OPT__EVAPOTRANSPIRATION "evapotranspiration_variable" #define BMI_REALIZATION_CFG_PARAM_OPT__USES_FORCINGS "uses_forcing_file" #define BMI_REALIZATION_CFG_PARAM_OPT__FORCING_FILE "forcing_file" #define BMI_REALIZATION_CFG_PARAM_OPT__VAR_STD_NAMES "variables_names_map" @@ -176,6 +177,10 @@ namespace realization { return output_variable_names; } + std::string get_bmi_evapotranspiration_var() const { + return this->bmi_evapotranspiration_var; + } + const std::vector &get_required_parameters() const override { return REQUIRED_PARAMETERS; } @@ -225,6 +230,10 @@ namespace realization { bmi_main_output_var = main_output_var; } + void set_bmi_evapotranspiration_var(const std::string &evapotranspiration_var) { + this->bmi_evapotranspiration_var = evapotranspiration_var; + } + /** * Set the name of the specific type of the backing model object. * @@ -253,6 +262,7 @@ namespace realization { private: std::string bmi_main_output_var; + std::string bmi_evapotranspiration_var; std::string model_type_name; /** * Output header field strings corresponding to the variables output by the realization, as defined in diff --git a/include/realizations/catchment/Bmi_Module_Formulation.hpp b/include/realizations/catchment/Bmi_Module_Formulation.hpp index cd6dda4969..d99eab6114 100644 --- a/include/realizations/catchment/Bmi_Module_Formulation.hpp +++ b/include/realizations/catchment/Bmi_Module_Formulation.hpp @@ -41,7 +41,9 @@ namespace realization { * @param output_stream */ Bmi_Module_Formulation(std::string id, std::shared_ptr forcing_provider, utils::StreamHandler output_stream) - : Bmi_Formulation(std::move(id), forcing_provider, output_stream) { } + : Bmi_Formulation(std::move(id), forcing_provider, output_stream) + , evapotranspiration_index{-1} + { } ~Bmi_Module_Formulation() override = default; @@ -513,6 +515,8 @@ namespace realization { bool is_realization_legacy_format() const; + double get_current_evapotranspiration() override; + private: models::bmi::protocols::NgenBmiProtocols bmi_protocols; /** @@ -543,6 +547,8 @@ namespace realization { std::vector output_var_indices; + int evapotranspiration_index; + std::vector OPTIONAL_PARAMETERS = { BMI_REALIZATION_CFG_PARAM_OPT__USES_FORCINGS BMI_REALIZATION_CFG_PARAM_OPT__FORCING_FILE, @@ -552,7 +558,8 @@ namespace realization { BMI_REALIZATION_CFG_PARAM_OPT__OUTPUT_PRECISION, BMI_REALIZATION_CFG_PARAM_OPT__ALLOW_EXCEED_END, BMI_REALIZATION_CFG_PARAM_OPT__FIXED_TIME_STEP, - BMI_REALIZATION_CFG_PARAM_OPT__LIB_FILE + BMI_REALIZATION_CFG_PARAM_OPT__LIB_FILE, + BMI_REALIZATION_CFG_PARAM_OPT__EVAPOTRANSPIRATION }; std::vector REQUIRED_PARAMETERS = { BMI_REALIZATION_CFG_PARAM_REQ__INIT_CONFIG, diff --git a/include/realizations/catchment/Bmi_Multi_Formulation.hpp b/include/realizations/catchment/Bmi_Multi_Formulation.hpp index 64ee733db0..7eccc65515 100644 --- a/include/realizations/catchment/Bmi_Multi_Formulation.hpp +++ b/include/realizations/catchment/Bmi_Multi_Formulation.hpp @@ -43,7 +43,9 @@ namespace realization { * @param output_stream */ Bmi_Multi_Formulation(std::string id, std::shared_ptr forcing_provider, utils::StreamHandler output_stream) - : Bmi_Formulation(std::move(id), forcing_provider, output_stream) { }; + : Bmi_Formulation(std::move(id), forcing_provider, output_stream) + , evapotranspiration_module_index{-1} + { }; virtual ~Bmi_Multi_Formulation() {}; @@ -544,6 +546,8 @@ namespace realization { void update(time_step_t t_index, time_step_t t_delta) override; + double get_current_evapotranspiration() override; + protected: /** @@ -770,6 +774,8 @@ namespace realization { /** Whether the realization file follows legacy format or the new format. */ bool legacy_json_format = false; + int evapotranspiration_module_index; + friend Bmi_Multi_Formulation_Test; friend class ::Bmi_Cpp_Multi_Array_Test; diff --git a/include/realizations/catchment/Catchment_Formulation.hpp b/include/realizations/catchment/Catchment_Formulation.hpp index 5a777c6857..db35e34296 100644 --- a/include/realizations/catchment/Catchment_Formulation.hpp +++ b/include/realizations/catchment/Catchment_Formulation.hpp @@ -102,6 +102,8 @@ namespace realization { */ void finalize(); + virtual double get_current_evapotranspiration() = 0; + protected: std::string get_catchment_id() const override { return this->cat_id; diff --git a/src/core/Layer.cpp b/src/core/Layer.cpp index aac9ced099..4d0e12dedf 100644 --- a/src/core/Layer.cpp +++ b/src/core/Layer.cpp @@ -7,7 +7,7 @@ #include "HY_Features.hpp" #endif -void ngen::Layer::update_models(boost::span catchment_outflows, +void ngen::Layer::update_models(boost::span catchment_evapotranspiration, std::unordered_map &catchment_indexes, boost::span nexus_downstream_flows, std::unordered_map &nexus_indexes, @@ -46,8 +46,8 @@ void ngen::Layer::update_models(boost::span catchment_outflows, throw std::runtime_error(msg); } #if NGEN_WITH_ROUTING - int results_index = catchment_indexes[id]; - catchment_outflows[results_index] += response; + int et_index = catchment_indexes[id]; + catchment_evapotranspiration[et_index] += r_c->get_current_evapotranspiration(); #endif // NGEN_WITH_ROUTING if (r_c->get_output_header_count() > 0) { // only write output if config specifies output values diff --git a/src/core/NgenSimulation.cpp b/src/core/NgenSimulation.cpp index c5cce69103..9516b202fe 100644 --- a/src/core/NgenSimulation.cpp +++ b/src/core/NgenSimulation.cpp @@ -14,6 +14,94 @@ #include "parallel_utils.h" +// local functions to decrease repeating code +namespace { + #if NGEN_WITH_MPI + // collect and merge output values from other MPI processes + void gather_indexes_and_values( + const NgenSimulation::hy_features_t &features, + const std::unordered_map &indexes, + const std::vector &values, + std::unordered_map &gathered_indexes, + std::vector &gathered_values, + size_t number_of_timesteps, + int mpi_rank, + int mpi_num_procs + ) { + std::vector local_ids; + for (const auto& ids : indexes) { + local_ids.push_back(ids.first); + } + // MPI_Gather all IDs into a single vector + std::vector all_ids = parallel::gather_strings(local_ids, mpi_rank, mpi_num_procs); + if (mpi_rank == 0) { + // filter to only the unique IDs + std::sort(all_ids.begin(), all_ids.end()); + all_ids.erase( + std::unique(all_ids.begin(), all_ids.end()), + all_ids.end() + ); + } + // MPI_Broadcast so all processes share the IDs + all_ids = std::move(parallel::broadcast_strings(all_ids, mpi_rank, mpi_num_procs)); + + // MPI_Reduce to collect the results from processes + if (mpi_rank == 0) { + gathered_values.resize(number_of_timesteps * all_ids.size(), 0.0); + } + std::vector local_buffer(number_of_timesteps); + std::vector receive_buffer(number_of_timesteps, 0.0); + for (int i = 0; i < all_ids.size(); ++i) { + std::string current_id = all_ids[i]; + if (indexes.find(current_id) != indexes.end() && !features.is_remote_sender_nexus(current_id)) { + // if this process has the id and receives/records data, copy the values to the buffer + int value_index = indexes.at(current_id); + for (int step = 0; step < number_of_timesteps; ++step) { + int offset = step * indexes.size() + value_index; + local_buffer[step] = values[offset]; + } + } else { + // if this process does not have the id, fill with 0 to make sure it doesn't affect reduce sum + std::fill(local_buffer.begin(), local_buffer.end(), 0.0); + } + MPI_Reduce(local_buffer.data(), receive_buffer.data(), number_of_timesteps, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD); + if (mpi_rank == 0) { + // copy reduce values to a combined downflows vector + gathered_indexes[current_id] = i; + for (int step = 0; step < number_of_timesteps; ++step) { + int offset = step * all_ids.size() + i; + gathered_values[offset] = receive_buffer[step]; + receive_buffer[step] = 0.0; + } + } + } + } + #endif // NGEN_WITH_MPI + + // convert IDs in the indexes lookup into a vector of int representations with the same order as the map indexes + std::vector ids_stoi(std::unordered_map *indexes_map) { + std::vector int_ids(indexes_map->size()); + for (const auto& key_value : *indexes_map) { + int id_index = key_value.second; + + // Convert string ID into numbers for T-route index + int id_as_int = -1; + size_t sep_index = key_value.first.find(hy_features::identifiers::separator); + if (sep_index != std::string::npos) { + std::string numbers = key_value.first.substr(sep_index + hy_features::identifiers::separator.length()); + id_as_int = std::stoi(numbers); + } + if (id_as_int == -1) { + std::string error_msg = "Cannot convert the ID to an integer: " + key_value.first; + LOG(LogLevel::FATAL, error_msg); + throw std::runtime_error(error_msg); + } + int_ids[id_index] = id_as_int; + } + return int_ids; + } +} + NgenSimulation::NgenSimulation( Simulation_Time const& sim_time, std::vector> layers, @@ -30,7 +118,7 @@ NgenSimulation::NgenSimulation( , mpi_rank_(mpi_rank) , mpi_num_procs_(mpi_num_procs) { - catchment_outflows_.reserve(catchment_indexes_.size() * get_num_output_times()); + catchment_evapotranspiration_.reserve(catchment_indexes_.size() * get_num_output_times()); nexus_downstream_flows_.reserve(nexus_indexes_.size() * get_num_output_times()); } @@ -43,7 +131,7 @@ void NgenSimulation::run_catchments() for (; simulation_step_ < num_times; simulation_step_++) { // Make room for this output step's results - catchment_outflows_.resize(catchment_outflows_.size() + catchment_indexes_.size(), 0.0); + catchment_evapotranspiration_.resize(catchment_evapotranspiration_.size() + catchment_indexes_.size(), 0.0); nexus_downstream_flows_.resize(nexus_downstream_flows_.size() + nexus_indexes_.size(), 0.0); advance_models_one_output_step(); @@ -84,7 +172,7 @@ void NgenSimulation::advance_models_one_output_step() LOG(("Updating layer: '" + layer->get_name() + "' at output step " + std::to_string(simulation_step_)), LogLevel::DEBUG); } - boost::span catchment_span(catchment_outflows_.data() + (simulation_step_ * catchment_indexes_.size()), + boost::span catchment_span(catchment_evapotranspiration_.data() + (simulation_step_ * catchment_indexes_.size()), catchment_indexes_.size()); boost::span nexus_span(nexus_downstream_flows_.data() + (simulation_step_ * nexus_indexes_.size()), nexus_indexes_.size()); @@ -122,6 +210,8 @@ double NgenSimulation::get_nexus_outflow(int nexus_index, int timestep_index) co void NgenSimulation::run_routing(NgenSimulation::hy_features_t &features, std::string const& t_route_config_file_with_path) { #if NGEN_WITH_ROUTING + std::vector *routing_evapotranspiration = &this->catchment_evapotranspiration_; + std::unordered_map *routing_catchment_indexes = &this->catchment_indexes_; std::vector *routing_nexus_downflows = &nexus_downstream_flows_; std::unordered_map *routing_nexus_indexes = &nexus_indexes_; @@ -135,58 +225,34 @@ void NgenSimulation::run_routing(NgenSimulation::hy_features_t &features, std::s #if NGEN_WITH_MPI std::vector all_nexus_downflows; std::unordered_map all_nexus_indexes; + std::vector all_evapotranspiration; + std::unordered_map all_catchment_indexes; if (mpi_num_procs_ > 1) { - std::vector local_nexus_ids; - for (const auto& nexus : nexus_indexes_) { - local_nexus_ids.push_back(nexus.first); - } - // MPI_Gather all nexus IDs into a single vector - std::vector all_nexus_ids = parallel::gather_strings(local_nexus_ids, mpi_rank_, mpi_num_procs_); - if (mpi_rank_ == 0) { - // filter to only the unique IDs - std::sort(all_nexus_ids.begin(), all_nexus_ids.end()); - all_nexus_ids.erase( - std::unique(all_nexus_ids.begin(), all_nexus_ids.end()), - all_nexus_ids.end() - ); - } - // MPI_Broadcast so all processes share the nexus IDs - all_nexus_ids = std::move(parallel::broadcast_strings(all_nexus_ids, mpi_rank_, mpi_num_procs_)); - - // MPI_Reduce to collect the results from processes - if (mpi_rank_ == 0) { - all_nexus_downflows.resize(number_of_timesteps * all_nexus_ids.size(), 0.0); - } - std::vector local_buffer(number_of_timesteps); - std::vector receive_buffer(number_of_timesteps, 0.0); - for (int i = 0; i < all_nexus_ids.size(); ++i) { - std::string nexus_id = all_nexus_ids[i]; - if (nexus_indexes_.find(nexus_id) != nexus_indexes_.end() && !features.is_remote_sender_nexus(nexus_id)) { - // if this process has the id and receives/records data, copy the values to the buffer - int nexus_index = nexus_indexes_[nexus_id]; - for (int step = 0; step < number_of_timesteps; ++step) { - int offset = step * nexus_indexes_.size() + nexus_index; - local_buffer[step] = nexus_downstream_flows_[offset]; - } - } else { - // if this process does not have the id, fill with 0 to make sure it doesn't affect reduce sum - std::fill(local_buffer.begin(), local_buffer.end(), 0.0); - } - MPI_Reduce(local_buffer.data(), receive_buffer.data(), number_of_timesteps, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD); - if (mpi_rank_ == 0) { - // copy reduce values to a combined downflows vector - all_nexus_indexes[nexus_id] = i; - for (int step = 0; step < number_of_timesteps; ++step) { - int offset = step * all_nexus_ids.size() + i; - all_nexus_downflows[offset] = receive_buffer[step]; - receive_buffer[step] = 0.0; - } - } - } - + gather_indexes_and_values( + features, + this->catchment_indexes_, + this->catchment_evapotranspiration_, + all_catchment_indexes, + all_evapotranspiration, + number_of_timesteps, + this->mpi_rank_, + this->mpi_num_procs_ + ); + gather_indexes_and_values( + features, + this->nexus_indexes_, + this->nexus_downstream_flows_, + all_nexus_indexes, + all_nexus_downflows, + number_of_timesteps, + this->mpi_rank_, + this->mpi_num_procs_ + ); if (mpi_rank_ == 0) { // update root's local data for running t-route below + routing_catchment_indexes = &all_catchment_indexes; + routing_evapotranspiration = &all_evapotranspiration; routing_nexus_indexes = &all_nexus_indexes; routing_nexus_downflows = &all_nexus_downflows; } @@ -205,35 +271,25 @@ void NgenSimulation::run_routing(NgenSimulation::hy_features_t &features, std::s // model for routing models::bmi::Bmi_Py_Adapter py_troute("T-Route", t_route_config_file_with_path, "troute_nwm_bmi.troute_bmi.BmiTroute", true); + py_troute.SetValue("delta_time", &delta_time); + // tell BMI to resize nexus containers int64_t nexus_count = routing_nexus_indexes->size(); py_troute.SetValue("land_surface_water_source__volume_flow_rate__count", &nexus_count); py_troute.SetValue("land_surface_water_source__id__count", &nexus_count); - // set up nexus id indexes - std::vector nexus_df_index(nexus_count); - for (const auto& key_value : *routing_nexus_indexes) { - int id_index = key_value.second; + // set up id indexes + std::vector cat_df_index = std::move(ids_stoi(routing_catchment_indexes)); + std::vector nexus_df_index = std::move(ids_stoi(routing_nexus_indexes)); - // Convert string ID into numbers for T-route index - int id_as_int = -1; - size_t sep_index = key_value.first.find(hy_features::identifiers::separator); - if (sep_index != std::string::npos) { - std::string numbers = key_value.first.substr(sep_index + hy_features::identifiers::separator.length()); - id_as_int = std::stoi(numbers); - } - if (id_as_int == -1) { - std::string error_msg = "Cannot convert the nexus ID to an integer: " + key_value.first; - LOG(LogLevel::FATAL, error_msg); - throw std::runtime_error(error_msg); - } - nexus_df_index[id_index] = id_as_int; - } py_troute.SetValue("land_surface_water_source__id", nexus_df_index.data()); for (int i = 0; i < number_of_timesteps; ++i) { py_troute.SetValue("land_surface_water_source__volume_flow_rate", routing_nexus_downflows->data() + (i * nexus_count)); py_troute.Update(); } + // send ET forcing data to t-route BMI + py_troute.set_value_unchecked("et_forcing_id", cat_df_index.data(), cat_df_index.size()); + py_troute.set_value_unchecked("et_forcing_data", routing_evapotranspiration->data(), routing_evapotranspiration->size()); // Finalize will write the output file py_troute.Finalize(); } @@ -265,7 +321,7 @@ void NgenSimulation::serialize(Archive& ar) { // Nexus and catchment indexes could be re-generated, but only if // the set of catchments remains consistent ar & catchment_indexes_; - ar & catchment_outflows_; + ar & catchment_evapotranspiration_; ar & nexus_indexes_; ar & nexus_downstream_flows_; } diff --git a/src/realizations/catchment/Bmi_Formulation.cpp b/src/realizations/catchment/Bmi_Formulation.cpp index ebb7bb32e2..2c667d1c51 100644 --- a/src/realizations/catchment/Bmi_Formulation.cpp +++ b/src/realizations/catchment/Bmi_Formulation.cpp @@ -10,7 +10,8 @@ namespace realization BMI_REALIZATION_CFG_PARAM_OPT__OUT_HEADER_FIELDS, BMI_REALIZATION_CFG_PARAM_OPT__ALLOW_EXCEED_END, BMI_REALIZATION_CFG_PARAM_OPT__FIXED_TIME_STEP, - BMI_REALIZATION_CFG_PARAM_OPT__LIB_FILE + BMI_REALIZATION_CFG_PARAM_OPT__LIB_FILE, + BMI_REALIZATION_CFG_PARAM_OPT__EVAPOTRANSPIRATION }; const std::vector Bmi_Formulation::REQUIRED_PARAMETERS = { BMI_REALIZATION_CFG_PARAM_REQ__INIT_CONFIG, diff --git a/src/realizations/catchment/Bmi_Module_Formulation.cpp b/src/realizations/catchment/Bmi_Module_Formulation.cpp index da478a294e..778fa55b14 100644 --- a/src/realizations/catchment/Bmi_Module_Formulation.cpp +++ b/src/realizations/catchment/Bmi_Module_Formulation.cpp @@ -608,6 +608,22 @@ namespace realization { if(output_var_indices.size() == 0){ output_var_indices.resize(names.size(), 0); } + + // look for optional evapotransporation variable + auto evapotranspiration_it = properties.find(BMI_REALIZATION_CFG_PARAM_OPT__EVAPOTRANSPIRATION); + if (evapotranspiration_it != properties.end()) { + std::string et_var = evapotranspiration_it->second.as_string(); + if (!et_var.empty()) { + const auto& out_fields = this->get_output_header_fields(); + auto it = std::find(out_fields.begin(), out_fields.end(), et_var); + if (it != out_fields.end()) { + this->evapotranspiration_index = std::distance(out_fields.begin(), it); + this->set_bmi_evapotranspiration_var(et_var); + } else { + LOG(LogLevel::WARNING, "An evapotranspiration source was set (%s) but could not be found in the available output fields.", et_var.c_str()); + } + } + } } /** * @brief Template function for copying iterator range into contiguous array. @@ -1106,4 +1122,44 @@ namespace realization { void* _; // this pointer will be unused by SetValue bmi->SetValue("serialization_free", _); } + + double Bmi_Module_Formulation::get_current_evapotranspiration() { + if (this->evapotranspiration_index < 0) { + return 0.0; + } + std::string out_units_norm = (output_var_units[this->evapotranspiration_index].empty() || output_var_units[this->evapotranspiration_index] == "none") ? "1" : output_var_units[this->evapotranspiration_index]; + double value; + try { + value = this->get_value( + CatchmentAggrDataSelector( + this->get_catchment_id(), + this->get_bmi_evapotranspiration_var(), + 0, + 0, + out_units_norm, + output_var_indices[this->evapotranspiration_index] + ), + MEAN + ); + } catch (data_access::unit_conversion_exception &uce) { + data_access::unit_error_log_key key{"Evapotranspiration", this->get_bmi_evapotranspiration_var(), uce.provider_model_name, uce.provider_bmi_var_name, uce.what()}; + auto ret = data_access::unit_errors_reported.insert(key); + if (ret.second) { // if new error + std::stringstream ss; + ss << "Unit conversion failure:" + << " requester {'Get Output Line for Timestep (Module Formulation)" + << "' catchment '" << get_catchment_id() + << "' variable '" << this->get_bmi_evapotranspiration_var() + << "' units '" << output_var_units[this->evapotranspiration_index] << "'}" + << " provider {'" << uce.provider_model_name + << "' source variable '" << uce.provider_bmi_var_name << "'" + << " raw value " << uce.unconverted_values[0] << "}" + << " message \"" << uce.what() << "\""; + LOG(ss.str(), LogLevel::WARNING); + } + value = uce.unconverted_values[0]; + } + return value; + } + } diff --git a/src/realizations/catchment/Bmi_Multi_Formulation.cpp b/src/realizations/catchment/Bmi_Multi_Formulation.cpp index 145b7dfb73..e1b721a46b 100644 --- a/src/realizations/catchment/Bmi_Multi_Formulation.cpp +++ b/src/realizations/catchment/Bmi_Multi_Formulation.cpp @@ -256,6 +256,14 @@ void Bmi_Multi_Formulation::create_multi_formulation(geojson::PropertyMap proper available_forcing_units[var_name] = module->get_provider_units_for_variable(var_name); } } + + // find evapotranspiration source if applicable + for (int i = 0; i < this->modules.size(); ++i) { + if (!this->modules[i]->get_bmi_evapotranspiration_var().empty()) { + this->evapotranspiration_module_index = i; + break; + } + } } /** @@ -609,6 +617,12 @@ void Bmi_Multi_Formulation::set_realization_file_format(bool is_legacy_format){ legacy_json_format = is_legacy_format; } +double Bmi_Multi_Formulation::get_current_evapotranspiration() { + return (this->evapotranspiration_module_index < 0) + ? 0.0 + : this->modules[this->evapotranspiration_module_index]->get_current_evapotranspiration(); +} + //Function to find whether any item in the string vector is empty or blank int find_empty_string_index(const std::vector& str_vector) { for (int i = 0; i < str_vector.size(); ++i) {