diff --git a/extern/LASAM b/extern/LASAM index 889ebef625..7e5405866d 160000 --- a/extern/LASAM +++ b/extern/LASAM @@ -1 +1 @@ -Subproject commit 889ebef62577fcd8e03efb36b3c855e1ba0794fe +Subproject commit 7e5405866dd66747b0db7d0351f67ad3e5b22f12 diff --git a/extern/SoilFreezeThaw/SoilFreezeThaw b/extern/SoilFreezeThaw/SoilFreezeThaw index dd0a439c17..6ea238f346 160000 --- a/extern/SoilFreezeThaw/SoilFreezeThaw +++ b/extern/SoilFreezeThaw/SoilFreezeThaw @@ -1 +1 @@ -Subproject commit dd0a439c1765d7d6a2e743c491ce0b77dfb4aff2 +Subproject commit 6ea238f346c2f2bf2a7b3dba9bb0e65c75a63afa diff --git a/extern/SoilMoistureProfiles/SoilMoistureProfiles b/extern/SoilMoistureProfiles/SoilMoistureProfiles index 385189c0f8..aaa505dc39 160000 --- a/extern/SoilMoistureProfiles/SoilMoistureProfiles +++ b/extern/SoilMoistureProfiles/SoilMoistureProfiles @@ -1 +1 @@ -Subproject commit 385189c0f88ae5e71eabbc470130f449ed182d8b +Subproject commit aaa505dc39492cf755bb5792ba5762ae16d1b198 diff --git a/extern/bmi-cxx b/extern/bmi-cxx index 77aa8f0774..ca3bea02f9 160000 --- a/extern/bmi-cxx +++ b/extern/bmi-cxx @@ -1 +1 @@ -Subproject commit 77aa8f0774ea60ea96d0c834b14e82591a5b699c +Subproject commit ca3bea02f989e351ddd1700743105cd72983036b diff --git a/extern/cfe/cfe b/extern/cfe/cfe index 4717734939..8769f4885b 160000 --- a/extern/cfe/cfe +++ b/extern/cfe/cfe @@ -1 +1 @@ -Subproject commit 4717734939ec4dc547156448326a38f9ee7a3585 +Subproject commit 8769f4885ba1fdf56aea405b0adbd9de2ce18003 diff --git a/extern/evapotranspiration/evapotranspiration b/extern/evapotranspiration/evapotranspiration index 6b5f9d57d9..7ec90d137f 160000 --- a/extern/evapotranspiration/evapotranspiration +++ b/extern/evapotranspiration/evapotranspiration @@ -1 +1 @@ -Subproject commit 6b5f9d57d9eb0100b0afd5d4a722c0fc7a8376dc +Subproject commit 7ec90d137fc7d3ebaaf0dd16a8af34ca669d34f0 diff --git a/extern/lstm b/extern/lstm index 341be45ed8..ac56cbcd86 160000 --- a/extern/lstm +++ b/extern/lstm @@ -1 +1 @@ -Subproject commit 341be45ed854442459ad2f4ed05532b5eb5406fe +Subproject commit ac56cbcd86c8738c766f2b8288b968b2eb8977f6 diff --git a/extern/noah-owp-modular/noah-owp-modular b/extern/noah-owp-modular/noah-owp-modular index 3d5b1b0e79..6def57e360 160000 --- a/extern/noah-owp-modular/noah-owp-modular +++ b/extern/noah-owp-modular/noah-owp-modular @@ -1 +1 @@ -Subproject commit 3d5b1b0e794ba878bc59472df77f0c9ce4138a69 +Subproject commit 6def57e360f5c27c4453b4bfd61be275c93c2c73 diff --git a/extern/sac-sma/sac-sma b/extern/sac-sma/sac-sma index 857e364571..5522debfd2 160000 --- a/extern/sac-sma/sac-sma +++ b/extern/sac-sma/sac-sma @@ -1 +1 @@ -Subproject commit 857e36457141e100f5eef54218c6842877ef863b +Subproject commit 5522debfd216ff9aedd9e4b457e565c9f0266bd7 diff --git a/extern/sloth b/extern/sloth index 4e4d43d2b0..cf9030413d 160000 --- a/extern/sloth +++ b/extern/sloth @@ -1 +1 @@ -Subproject commit 4e4d43d2b0dc2d7753828397f446f82d3960a6a5 +Subproject commit cf9030413d36045a01d4b903cc61d425d2057289 diff --git a/extern/snow17 b/extern/snow17 index 0747f9a2ee..989758b4c4 160000 --- a/extern/snow17 +++ b/extern/snow17 @@ -1 +1 @@ -Subproject commit 0747f9a2eeaf2a7e191a5d064aca40cae72c375e +Subproject commit 989758b4c4157a06a10be04632cf2fc77a256383 diff --git a/extern/t-route b/extern/t-route index be2f1bbcca..61174a7757 160000 --- a/extern/t-route +++ b/extern/t-route @@ -1 +1 @@ -Subproject commit be2f1bbcca2f2832763aa64fdf81fac606f94f58 +Subproject commit 61174a7757da44e07ad1c1a007bbdf5b1e1a984d diff --git a/extern/test_bmi_c/src/bmi_test_bmi_c.c b/extern/test_bmi_c/src/bmi_test_bmi_c.c index 522fe4651d..187ca666cd 100644 --- a/extern/test_bmi_c/src/bmi_test_bmi_c.c +++ b/extern/test_bmi_c/src/bmi_test_bmi_c.c @@ -21,7 +21,7 @@ static const char *output_var_locations[OUTPUT_VAR_NAME_COUNT] = { "node", "node // Don't forget to update Get_value/Get_value_at_indices (and setter) implementation if these are adjusted static const char *input_var_names[INPUT_VAR_NAME_COUNT] = { "INPUT_VAR_1", "INPUT_VAR_2" }; static const char *input_var_types[INPUT_VAR_NAME_COUNT] = { "double", "double" }; -static const char *input_var_units[INPUT_VAR_NAME_COUNT] = { "m", "m/s" }; +static const char *input_var_units[INPUT_VAR_NAME_COUNT] = { "m", "Pa" }; static const int input_var_item_count[INPUT_VAR_NAME_COUNT] = { 1, 1 }; static const char *input_var_grids[INPUT_VAR_NAME_COUNT] = { 0, 0 }; static const char *input_var_locations[INPUT_VAR_NAME_COUNT] = { "node", "node" }; diff --git a/extern/test_bmi_cpp/include/test_bmi_cpp.hpp b/extern/test_bmi_cpp/include/test_bmi_cpp.hpp index c15896def2..4b2c31a429 100644 --- a/extern/test_bmi_cpp/include/test_bmi_cpp.hpp +++ b/extern/test_bmi_cpp/include/test_bmi_cpp.hpp @@ -172,8 +172,8 @@ class TestBmiCpp : public bmi::Bmi { std::vector input_var_types = { "double", "double" }; std::vector output_var_types = { "double", "double" }; std::vector model_var_types = {}; - std::vector input_var_units = { "m", "m" }; - std::vector output_var_units = { "m", "m/s", "m", "m" }; + std::vector input_var_units = { "mm/s", "Pa" }; + std::vector output_var_units = { "mm/s", "m/s", "m", "m" }; std::vector model_var_units = {}; std::vector input_var_locations = { "node", "node" }; std::vector output_var_locations = { "node", "node" }; diff --git a/extern/test_bmi_fortran/src/bmi_test_bmi_fortran.f90 b/extern/test_bmi_fortran/src/bmi_test_bmi_fortran.f90 index 08a08aa10e..e6c07a5631 100644 --- a/extern/test_bmi_fortran/src/bmi_test_bmi_fortran.f90 +++ b/extern/test_bmi_fortran/src/bmi_test_bmi_fortran.f90 @@ -123,10 +123,10 @@ module bmitestbmi integer :: input_grid(4) = [0, 0, 0, 1] character (len=BMI_MAX_UNITS_NAME) :: & - output_units(6) = [character(BMI_MAX_UNITS_NAME):: 'm', 'm', 's', 'm', 'm', 'm'] + output_units(6) = [character(BMI_MAX_UNITS_NAME):: 'mm s^-1', 'm', 's', 'm', 'm', 'm'] character (len=BMI_MAX_UNITS_NAME) :: & - input_units(4) = [character(BMI_MAX_UNITS_NAME):: 'm', 'm', 's', 'm'] + input_units(4) = [character(BMI_MAX_UNITS_NAME):: 'mm s^-1', 'Pa', 'K', 'mm s^-1'] character (len=BMI_MAX_LOCATION_NAME) :: & output_location(6) = [character(BMI_MAX_LOCATION_NAME):: 'node', 'node', 'node', 'node', 'node', 'node'] diff --git a/extern/test_bmi_py/bmi_model.py b/extern/test_bmi_py/bmi_model.py index 0fbb93e1b7..efbd791dbd 100755 --- a/extern/test_bmi_py/bmi_model.py +++ b/extern/test_bmi_py/bmi_model.py @@ -73,12 +73,12 @@ def __init__(self): # since the input variable names could come from any forcing... #------------------------------------------------------ #_var_name_map_long_first = { - _var_name_units_map = {'INPUT_VAR_1':['INPUT_VAR_1','-'], - 'INPUT_VAR_2':['INPUT_VAR_2','-'], - 'OUTPUT_VAR_1':['OUTPUT_VAR_1','-'], - 'OUTPUT_VAR_2':['OUTPUT_VAR_2','-'], + _var_name_units_map = {'INPUT_VAR_1':['INPUT_VAR_1','mm/s'], + 'INPUT_VAR_2':['INPUT_VAR_2','Pa'], + 'OUTPUT_VAR_1':['OUTPUT_VAR_1','m'], + 'OUTPUT_VAR_2':['OUTPUT_VAR_2','mm/s'], 'OUTPUT_VAR_3':['OUTPUT_VAR_3','-'], - 'GRID_VAR_1':['OUTPUT_VAR_1','-'], + 'GRID_VAR_1':['OUTPUT_VAR_1','mm/s'], 'GRID_VAR_2':['GRID_VAR_2','-'], } @@ -609,4 +609,4 @@ def _parse_config(self, cfg): pass # Add more config parsing if necessary - return cfg \ No newline at end of file + return cfg diff --git a/extern/topmodel/topmodel b/extern/topmodel/topmodel index 76c3140840..e7ace73a07 160000 --- a/extern/topmodel/topmodel +++ b/extern/topmodel/topmodel @@ -1 +1 @@ -Subproject commit 76c31408406d8fdafad2b1e99592619f45212a19 +Subproject commit e7ace73a0700bd2c174e3f1939b899f7a1b23bca diff --git a/extern/ueb-bmi b/extern/ueb-bmi index 00352d3b88..0e95f01580 160000 --- a/extern/ueb-bmi +++ b/extern/ueb-bmi @@ -1 +1 @@ -Subproject commit 00352d3b88a5b0198d647b0e8270e567df359b06 +Subproject commit 0e95f015805dfac3cfbfbaf61428179b54108651 diff --git a/include/forcing/CsvPerFeatureForcingProvider.hpp b/include/forcing/CsvPerFeatureForcingProvider.hpp index b0d2f85903..b8d05cb338 100644 --- a/include/forcing/CsvPerFeatureForcingProvider.hpp +++ b/include/forcing/CsvPerFeatureForcingProvider.hpp @@ -170,13 +170,13 @@ class CsvPerFeatureForcingProvider : public data_access::GenericDataProvider try { return UnitsHelper::get_converted_value(available_forcings_units[output_name], value, output_units); } - catch (const std::runtime_error& e){ - #ifndef UDUNITS_QUIET - std::stringstream ss; - ss <<"WARN: Unit conversion unsuccessful - Returning unconverted value! (\""<(wkf->second) : units; + auto wkf_name = std::get<0>(wkf->second); + LOG("CsvProvider has well-known name '" + wkf_name + "' for variable '" + var_name + "' with units '" + units + "'", LogLevel::DEBUG); available_forcings.push_back(var_name); // Allow lookup by non-canonical name available_forcings_units[var_name] = units; // Allow lookup of units by non-canonical name - var_name = std::get<0>(wkf->second); // Use the CSDMS name from here on + var_name = wkf_name; // Use the CSDMS name from here on } forcing_vectors[var_name] = {}; diff --git a/include/forcing/DataProvider.hpp b/include/forcing/DataProvider.hpp index 32cbb36029..2cf4573f08 100644 --- a/include/forcing/DataProvider.hpp +++ b/include/forcing/DataProvider.hpp @@ -3,6 +3,8 @@ #include #include +#include +#include #include namespace data_access @@ -101,6 +103,33 @@ namespace data_access private: }; + struct unit_error_log_key { + std::string requester_name; + std::string requester_variable; + std::string provider_name; + std::string provider_variable; + std::string failure_message; + + bool operator<(unit_error_log_key const& rhs) const { + return std::tie(requester_name, requester_variable, provider_name, provider_variable, failure_message) + < std::tie(rhs.requester_name, rhs.requester_variable, rhs.provider_name, rhs.provider_variable, rhs.failure_message); + } + + bool operator==(unit_error_log_key const& rhs) const { + return std::tie(requester_name, requester_variable, provider_name, provider_variable, failure_message) + == std::tie(rhs.requester_name, rhs.requester_variable, rhs.provider_name, rhs.provider_variable, rhs.failure_message); + } + }; + + extern std::set unit_errors_reported; + + struct unit_conversion_exception : public std::runtime_error { + unit_conversion_exception(std::string message) : std::runtime_error(message) {} + std::string provider_model_name; + std::string provider_bmi_var_name; + std::string provider_units; + std::vector unconverted_values; + }; } diff --git a/include/forcing/ForcingsEngineDataProvider.hpp b/include/forcing/ForcingsEngineDataProvider.hpp index ca11fc555c..89b49ec664 100644 --- a/include/forcing/ForcingsEngineDataProvider.hpp +++ b/include/forcing/ForcingsEngineDataProvider.hpp @@ -16,6 +16,7 @@ #include "DataProvider.hpp" #include "bmi/Bmi_Py_Adapter.hpp" +#include "Logger.hpp" namespace data_access { @@ -173,31 +174,38 @@ struct ForcingsEngineDataProvider : public DataProvider : time_begin_(std::chrono::system_clock::from_time_t(time_begin_seconds)) , time_end_(std::chrono::system_clock::from_time_t(time_end_seconds)) { + std::stringstream ss; + // Log the constructor arguments - std::cout << "[ngen debug] Entering ForcingsEngineDataProvider constructor" << std::endl; + ss.str(""); + ss << "Entering ForcingsEngineDataProvider constructor" << std::endl; + LOG(LogLevel::DEBUG, ss.str()); std::time_t start_t = static_cast(time_begin_seconds); std::time_t end_t = static_cast(time_end_seconds); - std::cout << " Start time: " << std::put_time(std::gmtime(&start_t), "%Y-%m-%d %H:%M:%S UTC") - << " (" << time_begin_seconds << ")" << std::endl; + ss.str(""); + ss << " Start time: " << std::put_time(std::gmtime(&start_t), "%Y-%m-%d %H:%M:%S UTC") + << " (" << time_begin_seconds << ")" << std::endl; + LOG(LogLevel::INFO, ss.str()); - std::cout << " End time: " << std::put_time(std::gmtime(&end_t), "%Y-%m-%d %H:%M:%S UTC") - << " (" << time_end_seconds << ")" << std::endl; + ss.str(""); + ss << " End time: " << std::put_time(std::gmtime(&end_t), "%Y-%m-%d %H:%M:%S UTC") + << " (" << time_end_seconds << ")" << std::endl; + LOG(LogLevel::INFO, ss.str()); // Attempt to retrieve a previously created BMI instance bmi_ = storage_type::instances.get(init_config); // If it doesn't exist, create it and assign it to the storage map if (bmi_ != nullptr) { - std::cout << "[ngen debug] Reusing existing BMI instance for init_config file: " << init_config << std::endl; + ss.str(""); + ss << "Reusing existing BMI instance for init_config file: " << init_config << std::endl; + LOG(LogLevel::DEBUG, ss.str()); } else { - // Ensure all prior output is flushed before invoking Python - std::cout.flush(); - std::cerr.flush(); - // Log the creation of a new BMI instance - std::cout << "[ngen debug] Creating new BMI instance for init_config file: " << init_config << std::endl; + ss.str(""); ss << "Creating new BMI instance for init_config file: " << init_config << std::endl; + LOG(LogLevel::DEBUG, ss.str()); // Create the BMI instance try { @@ -208,7 +216,8 @@ struct ForcingsEngineDataProvider : public DataProvider /*has_fixed_time_step=*/true ); } catch (const std::exception& ex) { - std::cerr << "[ngen error] Failed to create Bmi_Py_Adapter: " << ex.what() << std::endl; + ss.str("");ss << "Failed to create Bmi_Py_Adapter: " << ex.what() << std::endl; + LOG(LogLevel::FATAL, ss.str()); throw; } @@ -221,19 +230,28 @@ struct ForcingsEngineDataProvider : public DataProvider // NOTE: using std::lround instead of static_cast will prevent potential UB time_step_ = std::chrono::seconds{std::lround(bmi_->GetTimeStep())}; var_output_names_ = bmi_->GetOutputVarNames(); - std::cout << "[ngen debug] BMI instance initialized successfully" << std::endl; - std::cout << "[ngen debug] Time step: " << time_step_.count() << " seconds" << std::endl; - std::cout << "[ngen debug] Available output variable names:" << std::endl; - for (const auto& var_name : var_output_names_) { - std::cout << " - " << var_name << std::endl; + if (Logger::GetLogger()->GetLogLevel() == LogLevel::DEBUG) { + ss.str(""); ss << "BMI instance initialized successfully" << std::endl; + LOG(LogLevel::DEBUG, ss.str()); + ss.str(""); ss << "Time step: " << time_step_.count() << " seconds" << std::endl; + LOG(LogLevel::DEBUG, ss.str()); + ss.str(""); ss << "Available output variable names:" << std::endl; + LOG(LogLevel::DEBUG, ss.str()); + for (const auto& var_name : var_output_names_) { + ss.str(""); ss << " - " << var_name << std::endl; + LOG(LogLevel::DEBUG, ss.str()); + } } } catch (const std::exception& ex) { - std::cerr << "[ngen error] Error initializing BMI instance: " << ex.what() << std::endl; + ss.str(""); ss << "Error initializing BMI instance: " << ex.what() << std::endl; + LOG(LogLevel::FATAL, ss.str()); throw; } // Log successful constructor exit - std::cout << "[ngen debug] Exiting ForcingsEngineDataProvider constructor" << std::endl; + ss.str(""); ss << "Exiting ForcingsEngineDataProvider constructor" << std::endl; + LOG(LogLevel::DEBUG, ss.str()); + } //! Ensure a variable is available, appending the suffix if needed diff --git a/include/forcing/NetCDFPerFeatureDataProvider.hpp b/include/forcing/NetCDFPerFeatureDataProvider.hpp index 92ef771fff..b0b782f3f9 100644 --- a/include/forcing/NetCDFPerFeatureDataProvider.hpp +++ b/include/forcing/NetCDFPerFeatureDataProvider.hpp @@ -128,7 +128,7 @@ namespace data_access TimeUnit time_unit; // the unit that time was stored as in the file double time_stride; // the amount of time between stored time values utils::StreamHandler log_stream; - + std::string file_path; std::shared_ptr nc_file; diff --git a/include/realizations/catchment/Bmi_Module_Formulation.hpp b/include/realizations/catchment/Bmi_Module_Formulation.hpp index b8604adb20..d845523c0d 100644 --- a/include/realizations/catchment/Bmi_Module_Formulation.hpp +++ b/include/realizations/catchment/Bmi_Module_Formulation.hpp @@ -486,8 +486,6 @@ namespace realization { /** A configured mapping of BMI model variable names to standard names for use inside the framework. */ std::map bmi_var_names_map; bool model_initialized = false; - bool unitGetValueErrLogged = false; - bool unitGetValuesErrLogged = false; std::vector OPTIONAL_PARAMETERS = { BMI_REALIZATION_CFG_PARAM_OPT__USES_FORCINGS diff --git a/include/realizations/catchment/Bmi_Multi_Formulation.hpp b/include/realizations/catchment/Bmi_Multi_Formulation.hpp index 286c0eeb33..7b8090b57c 100644 --- a/include/realizations/catchment/Bmi_Multi_Formulation.hpp +++ b/include/realizations/catchment/Bmi_Multi_Formulation.hpp @@ -583,6 +583,15 @@ namespace realization { //TODO: After merge PR#405, try re-adding support for index return nested_module->get_value(selector); } + catch (data_access::unit_conversion_exception &uce) { + // We asked for it as a dimensionless quantity, "1", just above + static bool no_conversion_message_logged = false; + if (!no_conversion_message_logged) { + no_conversion_message_logged = true; + LOG("Emitting output variables from Bmi_Multi_Formulation without unit conversion - see NGWPC-7604", LogLevel::WARNING); + } + return uce.unconverted_values[0]; + } // If there was any problem with the cast and extraction of the value, throw runtime error catch (std::exception &e) { std::string throw_msg; throw_msg.assign("Multi BMI formulation can't use associated data provider as a nested module" diff --git a/include/realizations/catchment/Catchment_Formulation.hpp b/include/realizations/catchment/Catchment_Formulation.hpp index dc02a63f45..4892455b98 100644 --- a/include/realizations/catchment/Catchment_Formulation.hpp +++ b/include/realizations/catchment/Catchment_Formulation.hpp @@ -7,6 +7,8 @@ #include #include "GenericDataProvider.hpp" +#include "Logger.hpp" + #define DEFAULT_FORMULATION_OUTPUT_DELIMITER "," namespace realization { @@ -42,16 +44,23 @@ namespace realization { */ static void config_pattern_substitution(geojson::PropertyMap &properties, const std::string &key, const std::string &pattern, const std::string &replacement) { + std::stringstream ss; auto it = properties.find(key); // Do nothing and return if either the key isn't found or the associated property isn't a string if (it == properties.end() || it->second.get_type() != geojson::PropertyType::String) { - std::cout << "[DEBUG] Skipping pattern substitution for key: " << key << " (not found or not a string)" << std::endl; + ss.str(""); + ss << "Skipping pattern substitution for key: " << key << " (not found or not a string)" << std::endl; + LOG(ss.str(), LogLevel::DEBUG); return; } std::string value = it->second.as_string(); - std::cout << "[DEBUG] config_pattern_substitution Performing pattern substitution for key: " << key << ", pattern: " << pattern << ", replacement: " << replacement << std::endl; -// std::cout << "[DEBUG] Original value: " << value << std::endl; + ss.str(""); + ss << "config_pattern_substitution Performing pattern substitution for key: " << key << ", pattern: " << pattern << ", replacement: " << replacement << std::endl; + LOG(ss.str(), LogLevel::DEBUG); +// ss.str(""); +// ss << "Original value: " << value << std::endl; +// LOG(ss.str(), LogLevel::DEBUG); size_t id_index = value.find(pattern); while (id_index != std::string::npos) { @@ -63,7 +72,9 @@ namespace realization { properties.erase(key); properties.emplace(key, geojson::JSONProperty(key, value)); -// std::cout << "[DEBUG] Substitution result for key: " << key << " -> " << value << std::endl; +// ss.str(""); +// ss << "Substitution result for key: " << key << " -> " << value << std::endl; +// LOG(ss.str(), LogLevel::DEBUG); } /** diff --git a/include/realizations/catchment/Formulation_Constructors.hpp b/include/realizations/catchment/Formulation_Constructors.hpp index 9424320f56..8ad1923284 100644 --- a/include/realizations/catchment/Formulation_Constructors.hpp +++ b/include/realizations/catchment/Formulation_Constructors.hpp @@ -44,6 +44,8 @@ namespace realization { ) { constructor formulation_constructor = formulations.at(formulation_type); std::shared_ptr fp; + std::stringstream ss; + if (forcing_config.provider == "CsvPerFeature" || forcing_config.provider == ""){ fp = std::make_shared(forcing_config); } @@ -57,8 +59,11 @@ namespace realization { } #if NGEN_WITH_PYTHON else if (forcing_config.provider == "ForcingsEngineLumpedDataProvider") { - std::cout << "[ngen debug] Using ForcingsEngineLumpedDataProvider for '" << identifier - << "' with init_config = " << forcing_config.init_config << std::endl; + + ss.str(""); + ss << "Using ForcingsEngineLumpedDataProvider for '" << identifier + << "' with init_config = " << forcing_config.init_config << std::endl; + LOG(ss.str(), LogLevel::DEBUG); // Confirm requirements like Python module + WGRIB2 are present @@ -68,14 +73,18 @@ namespace realization { auto start = forcing_config.simulation_start_t; auto end = forcing_config.simulation_end_t; - std::cout << "[ngen debug] About to call ForcingsEngineLumpedDataProvider constructor" << std::endl; + ss.str(""); + ss << "About to call ForcingsEngineLumpedDataProvider constructor" << std::endl; + LOG(ss.str(), LogLevel::DEBUG); // Construct the ForcingsEngineLumpedDataProvider fp = std::make_shared( forcing_config.init_config, start, end, identifier ); - std::cout << "[ngen debug] Finished calling ForcingsEngineLumpedDataProvider constructor" << std::endl; + ss.str(""); + ss << "Finished calling ForcingsEngineLumpedDataProvider constructor" << std::endl; + LOG(ss.str(), LogLevel::DEBUG); } #endif else { // Some unknown string in the provider field? diff --git a/include/realizations/catchment/Formulation_Manager.hpp b/include/realizations/catchment/Formulation_Manager.hpp index e25fa5232f..4c97110798 100644 --- a/include/realizations/catchment/Formulation_Manager.hpp +++ b/include/realizations/catchment/Formulation_Manager.hpp @@ -32,7 +32,6 @@ namespace realization { class Formulation_Manager { public: - std::shared_ptr Simulation_Time_Object; Formulation_Manager(std::stringstream &data) { @@ -55,7 +54,8 @@ namespace realization { void read(geojson::GeoJSON fabric, utils::StreamHandler output_stream) { std::stringstream ss; - std::cout << "[DEBUG] Entering Formulation_Manager::read()" << std::endl; + ss.str(""); ss << "Entering Formulation_Manager::read()" << std::endl; + LOG(ss.str(), LogLevel::DEBUG); //TODO seperate the parsing of configuration options like time //and routing and other non feature specific tasks from this main function @@ -101,7 +101,8 @@ namespace realization { // add the layer to storage layer_storage.put_layer(layer_desc, layer_desc.id); - std::cout << "[DEBUG] Layer added: ID = " << layer_desc.id << ", Name = " << layer_desc.name << std::endl; + ss.str(""); ss << "Layer added: ID = " << layer_desc.id << ", Name = " << layer_desc.name << std::endl; + LOG(ss.str(), LogLevel::DEBUG); if (layer.has_formulation() && layer.get_domain() == "catchments") { double c_value = UnitsHelper::get_converted_value(layer_desc.time_step_units, layer_desc.time_step, "s"); @@ -136,9 +137,10 @@ namespace realization { using_routing = true; #else using_routing = false; - ss <<"WARNING: Formulation Manager found routing configuration" - << ", but routing support isn't enabled. No routing will occur." << std::endl; - LOG(ss.str(), LogLevel::SEVERE); ss.str(""); + ss.str(""); + ss <<"Formulation Manager found routing configuration" + << ", but routing support isn't enabled. No routing will occur." << std::endl; + LOG(ss.str(), LogLevel::WARNING); #endif // NGEN_WITH_ROUTING } @@ -168,15 +170,17 @@ namespace realization { if (possible_catchment_configs) { for (std::pair catchment_config : *possible_catchment_configs) { - std::cout << "[DEBUG] Processing catchment: " << catchment_config.first << std::endl; + ss.str(""); ss << "Processing catchment: " << catchment_config.first << std::endl; + LOG(ss.str(), LogLevel::DEBUG); int catchment_index = fabric->find(catchment_config.first); if (catchment_index == -1) { #ifndef NGEN_QUIET - ss <<"WARNING: Formulation_Manager::read: Cannot create formulation for catchment " - << catchment_config.first - << " that isn't identified in the hydrofabric or requested subset" << std::endl; - LOG(ss.str(), LogLevel::SEVERE); ss.str(""); + ss.str(""); + ss << "Formulation_Manager::read: Cannot create formulation for catchment " + << catchment_config.first + << " that isn't identified in the hydrofabric or requested subset" << std::endl; + LOG(ss.str(), LogLevel::WARNING); #endif continue; } @@ -191,7 +195,8 @@ namespace realization { // Parse catchment-specific model_params auto catchment_feature = fabric->get_feature(catchment_index); - std::cout << "[DEBUG] Linking external properties for catchment: " << catchment_config.first << std::endl; + ss.str(""); ss << "Linking external properties for catchment: " << catchment_config.first << std::endl; + LOG(ss.str(), LogLevel::DEBUG); catchment_formulation.formulation.link_external(catchment_feature); this->add_formulation( @@ -202,18 +207,21 @@ namespace realization { output_stream ) ); - std::cout << "[DEBUG] Formulation constructed for catchment: " << catchment_config.first << std::endl; + ss.str(""); ss << "Formulation constructed for catchment: " << catchment_config.first << std::endl; + LOG(ss.str(), LogLevel::DEBUG); } } // Process any catchments not explicitly defined in the realization file for (geojson::Feature location : *fabric) { if (not this->contains(location->get_id())) { - std::cout << "[DEBUG] Creating missing formulation for location: " << location->get_id() << std::endl; + ss.str(""); ss << "Creating missing formulation for location: " << location->get_id() << std::endl; + LOG(ss.str(), LogLevel::DEBUG); std::shared_ptr missing_formulation = this->construct_missing_formulation( location, output_stream, simulation_time_config); this->add_formulation(missing_formulation); -// std::cout << "[DEBUG] Missing formulation created for location: " << location->get_id() << std::endl; +// ss.str(""); ss << "Missing formulation created for location: " << location->get_id() << std::endl; +// LOG(ss.str(), LogLevel::DEBUG); } } } @@ -271,7 +279,9 @@ namespace realization { * @return routing t_route_config_file_with_path */ std::string get_t_route_config_file_with_path() { - std::cout << "[DEBUG] Retrieving t_route config file path" << std::endl; + std::stringstream ss; + ss.str(""); ss << "Retrieving t_route config file path" << std::endl; + LOG(ss.str(), LogLevel::DEBUG); if(this->routing_config != nullptr) return this->routing_config->t_route_config_file_with_path; else @@ -296,7 +306,9 @@ namespace realization { // constituent formulations points to any forcing // object other than the enclosing // Bmi_Multi_Formulation instance itself. - std::cout << "[DEBUG] Finalizing Formulation_Manager" << std::endl; + std::stringstream ss; + ss.str(""); ss << "Finalizing Formulation_Manager" << std::endl; + LOG(ss.str(), LogLevel::DEBUG); for (auto const& fmap: formulations) { fmap.second->finalize(); } @@ -310,7 +322,8 @@ namespace realization { #if NGEN_WITH_PYTHON data_access::detail::ForcingsEngineStorage::instances.clear(); #endif - std::cout << "[DEBUG] Formulation_Manager finalized" << std::endl; + ss.str(""); ss << "Formulation_Manager finalized" << std::endl; + LOG(ss.str(), LogLevel::DEBUG); } /** @@ -364,7 +377,9 @@ namespace realization { * @return a reference to the LayerStorageObject */ ngen::LayerDataStorage& get_layer_metadata() { - std::cout << "[DEBUG] Retrieving layer metadata" << std::endl; + std::stringstream ss; + ss.str(""); ss << "Retrieving layer metadata" << std::endl; + LOG(ss.str(), LogLevel::DEBUG); return layer_storage; } @@ -376,7 +391,9 @@ namespace realization { const realization::config::Config& catchment_formulation, utils::StreamHandler output_stream ) { - std::cout << "[DEBUG] Entering construct_formulation_from_config for identifier: " << identifier << std::endl; + std::stringstream ss; + ss.str(""); ss << "Entering construct_formulation_from_config for identifier: " << identifier << std::endl; + LOG(ss.str(), LogLevel::DEBUG); // Check if the formulation exists if (!formulation_exists(catchment_formulation.formulation.type)) { @@ -389,18 +406,20 @@ namespace realization { } // Check for missing forcing parameters - std::cout << "[DEBUG] Checking forcing parameters for identifier: " << identifier << std::endl; + ss.str(""); ss << "Checking forcing parameters for identifier: " << identifier << std::endl; + LOG(ss.str(), LogLevel::DEBUG); if (catchment_formulation.forcing.parameters.empty()) { std::string throw_msg; throw_msg.assign("No forcing definition was found for " + identifier); - LOG(throw_msg, LogLevel::WARNING); + LOG(throw_msg, LogLevel::FATAL); throw std::runtime_error(throw_msg); } // Check for missing path std::vector missing_parameters; if (!catchment_formulation.forcing.has_key("path")) { - std::cout << "[DEBUG] Missing path parameter for identifier: " << identifier << std::endl; + ss.str(""); ss << "Missing path parameter for identifier: " << identifier << std::endl; + LOG(ss.str(), LogLevel::DEBUG); missing_parameters.push_back("path"); } @@ -422,30 +441,38 @@ namespace realization { // Extract forcing parameters forcing_params forcing_config = this->get_forcing_params(catchment_formulation.forcing.parameters, identifier, simulation_time_config); - std::cout << "[ngen debug] Forcing parameters extracted for identifier: " << identifier << std::endl; - std::cout << " [ngen debug] Forcing path: " << forcing_config.path << std::endl; - std::cout << " [ngen debug] Forcing provider: " << forcing_config.provider << std::endl; - std::cout << " [ngen debug] Forcing init_config: " << forcing_config.init_config << std::endl; + ss.str(""); + ss << "Forcing parameters extracted for identifier: " << identifier << std::endl; + ss << " Forcing path: " << forcing_config.path << std::endl; + ss << " Forcing provider: " << forcing_config.provider << std::endl; + ss << " Forcing init_config: " << forcing_config.init_config << std::endl; + LOG(ss.str(), LogLevel::DEBUG); std::time_t start_t = static_cast(forcing_config.simulation_start_t); std::time_t end_t = static_cast(forcing_config.simulation_end_t); - std::cout << " [ngen debug] Simulation start time: " << std::put_time(std::gmtime(&start_t), "%Y-%m-%d %H:%M:%S UTC") + ss.str(""); + ss << " Simulation start time: " << std::put_time(std::gmtime(&start_t), "%Y-%m-%d %H:%M:%S UTC") << " (" << forcing_config.simulation_start_t << ")" << std::endl; - std::cout << " [ngen debug] Simulation end time: " << std::put_time(std::gmtime(&end_t), "%Y-%m-%d %H:%M:%S UTC") + ss << " Simulation end time: " << std::put_time(std::gmtime(&end_t), "%Y-%m-%d %H:%M:%S UTC") << " (" << forcing_config.simulation_end_t << ")" << std::endl; + LOG(ss.str(), LogLevel::DEBUG); // Construct formulation - std::cout << "[DEBUG] Constructing formulation for type: " << catchment_formulation.formulation.type << std::endl; + ss.str(""); ss << "Constructing formulation for type: " << catchment_formulation.formulation.type << std::endl; + LOG(ss.str(), LogLevel::DEBUG); std::shared_ptr constructed_formulation = construct_formulation( catchment_formulation.formulation.type, identifier, forcing_config, output_stream ); - std::cout << "[DEBUG] Formulation constructed successfully for identifier: " << identifier << std::endl; + ss.str(""); ss << "Formulation constructed successfully for identifier: " << identifier << std::endl; + LOG(ss.str(), LogLevel::DEBUG); // Create formulation instance - std::cout << "[DEBUG] Calling create_formulation for identifier: " << identifier << std::endl; + ss.str(""); ss << "Calling create_formulation for identifier: " << identifier << std::endl; + LOG(ss.str(), LogLevel::DEBUG); constructed_formulation->create_formulation(catchment_formulation.formulation.parameters); - std::cout << "[DEBUG] Formulation creation completed for identifier: " << identifier << std::endl; + ss.str(""); ss << "Formulation creation completed for identifier: " << identifier << std::endl; + LOG(ss.str(), LogLevel::DEBUG); return constructed_formulation; } @@ -455,39 +482,47 @@ namespace realization { utils::StreamHandler output_stream, simulation_time_params &simulation_time_config ) { + std::stringstream ss; const std::string identifier = feature->get_id(); - std::cout << "[DEBUG] Entering construct_missing_formulation for identifier: " << identifier << std::endl; + ss.str(""); ss << "Entering construct_missing_formulation for identifier: " << identifier << std::endl; + LOG(ss.str(), LogLevel::DEBUG); // Extract forcing parameters from the global config forcing_params forcing_config = this->get_forcing_params(global_config.forcing.parameters, identifier, simulation_time_config); - std::cout << "[ngen debug] Forcing parameters extracted for identifier: " << identifier << std::endl; - std::cout << " [ngen debug] Forcing path: " << forcing_config.path << std::endl; - std::cout << " [ngen debug] Forcing provider: " << forcing_config.provider << std::endl; - std::cout << " [ngen debug] Forcing init_config: " << forcing_config.init_config << std::endl; + ss.str(""); + ss << "Forcing parameters extracted for identifier: " << identifier << std::endl; + ss << " Forcing path: " << forcing_config.path << std::endl; + ss << " Forcing provider: " << forcing_config.provider << std::endl; + ss << " Forcing init_config: " << forcing_config.init_config << std::endl; + LOG(ss.str(), LogLevel::DEBUG); std::time_t start_t = static_cast(forcing_config.simulation_start_t); std::time_t end_t = static_cast(forcing_config.simulation_end_t); - std::cout << " [ngen debug] Simulation start time: " << std::put_time(std::gmtime(&start_t), "%Y-%m-%d %H:%M:%S UTC") + ss.str(""); + ss << " Simulation start time: " << std::put_time(std::gmtime(&start_t), "%Y-%m-%d %H:%M:%S UTC") << " (" << forcing_config.simulation_start_t << ")" << std::endl; - std::cout << " [ngen debug] Simulation end time: " << std::put_time(std::gmtime(&end_t), "%Y-%m-%d %H:%M:%S UTC") + ss << " Simulation end time: " << std::put_time(std::gmtime(&end_t), "%Y-%m-%d %H:%M:%S UTC") << " (" << forcing_config.simulation_end_t << ")" << std::endl; + LOG(ss.str(), LogLevel::DEBUG); // Construct the formulation object - std::cout << "[DEBUG] Entering construct_formulation for identifier: " << identifier << ", type: " << global_config.formulation.type << std::endl; + ss.str(""); ss << "Entering construct_formulation for identifier: " << identifier << ", type: " << global_config.formulation.type << std::endl; + LOG(ss.str(), LogLevel::DEBUG); std::shared_ptr missing_formulation = construct_formulation( global_config.formulation.type, identifier, forcing_config, output_stream ); - std::cout << "[DEBUG] Formulation object constructed for identifier: " << identifier << std::endl; + ss.str(""); ss << "Formulation object constructed for identifier: " << identifier << std::endl; + LOG(ss.str(), LogLevel::DEBUG); // Make a copy of the global configuration so parameters don't clash when linking to external data realization::config::Config global_copy = global_config; // // Log parameters before substitution -// std::cout << "[DEBUG] Global config parameters (before substitution) for identifier: " << identifier << std::endl; +// ss.str(""); ss << "Global config parameters (before substitution) for identifier: " << identifier << std::endl; // for (auto it = global_copy.formulation.parameters.begin(); it != global_copy.formulation.parameters.end(); ++it) { // const std::string& key = it->first; // const geojson::JSONProperty& value = it->second; @@ -500,7 +535,8 @@ namespace realization { // } // Substitute {{id}} in the global formulation - std::cout << "[DEBUG] Checking for init_config before substitution for identifier: " << identifier << std::endl; + ss.str(""); ss << "Checking for init_config before substitution for identifier: " << identifier << std::endl; + LOG(ss.str(), LogLevel::DEBUG); auto init_config_it = global_copy.formulation.parameters.find(BMI_REALIZATION_CFG_PARAM_REQ__INIT_CONFIG); if (init_config_it != global_copy.formulation.parameters.end()) { const geojson::JSONProperty& init_config_property = init_config_it->second; @@ -508,9 +544,12 @@ namespace realization { if (init_config_property.get_type() == geojson::PropertyType::String) { std::string original_value = init_config_property.as_string(); if (!original_value.empty()) { - std::cout << "[DEBUG] construct_missing_formulation Performing pattern substitution for key: " << BMI_REALIZATION_CFG_PARAM_REQ__INIT_CONFIG - << ", pattern: {{id}}, replacement: " << identifier << std::endl; -// std::cout << "[DEBUG] Original value: " << original_value << std::endl; + ss.str(""); ss << "construct_missing_formulation Performing pattern substitution for key: " << BMI_REALIZATION_CFG_PARAM_REQ__INIT_CONFIG + << ", pattern: {{id}}, replacement: " << identifier << std::endl; + LOG(ss.str(), LogLevel::DEBUG); + ss.str(""); +// ss.str(""); ss << "Original value: " << original_value << std::endl; +// LOG(ss.str(), LogLevel::DEBUG); Catchment_Formulation::config_pattern_substitution( global_copy.formulation.parameters, @@ -519,17 +558,21 @@ namespace realization { identifier ); } else { - std::cout << "[WARNING] init_config is present but empty for identifier: " << identifier << std::endl; + ss.str(""); ss << "init_config is present but empty for identifier: " << identifier << std::endl; + LOG(ss.str(), LogLevel::WARNING); } } else { - std::cout << "[WARNING] init_config is present but not a string for identifier: " << identifier << std::endl; + ss.str(""); ss << "init_config is present but not a string for identifier: " << identifier << std::endl; + LOG(ss.str(), LogLevel::WARNING); } } else { - std::cout << "[WARNING] init_config not present in global configuration for identifier: " << identifier << std::endl; + ss.str(""); ss << "[WARNING] init_config not present in global configuration for identifier: " << identifier << std::endl; + LOG(ss.str(), LogLevel::WARNING); } // // Log parameters after substitution -// std::cout << "[DEBUG] Global config parameters (after substitution) for identifier: " << identifier << std::endl; +// ss.str(""); ss << "Global config parameters (after substitution) for identifier: " << identifier << std::endl; +// LOG(ss.str(), LogLevel::DEBUG); // for (auto it = global_copy.formulation.parameters.begin(); it != global_copy.formulation.parameters.end(); ++it) { // const std::string& key = it->first; // const geojson::JSONProperty& value = it->second; @@ -542,33 +585,40 @@ namespace realization { // } // Link external properties - std::cout << "[DEBUG] Linking external properties for identifier: " << identifier << std::endl; + ss.str(""); ss << "Linking external properties for identifier: " << identifier << std::endl; + LOG(ss.str(), LogLevel::DEBUG); auto formulation = realization::config::Formulation(global_copy.formulation); formulation.link_external(feature); // Create the formulation - std::cout << "[DEBUG] Creating formulation for identifier: " << identifier << std::endl; + ss.str(""); ss << "Creating formulation for identifier: " << identifier << std::endl; + LOG(ss.str(), LogLevel::DEBUG); missing_formulation->create_formulation(formulation.parameters); -// std::cout << "[DEBUG] Formulation creation completed for identifier: " << identifier << std::endl; +// ss.str(""); ss << "Formulation creation completed for identifier: " << identifier << std::endl; +// LOG(ss.str(), LogLevel::DEBUG); return missing_formulation; } forcing_params get_forcing_params(const geojson::PropertyMap &forcing_prop_map, std::string identifier, simulation_time_params &simulation_time_config) { - std::cout << "[DEBUG] Entering get_forcing_params for identifier: " << identifier << std::endl; + std::stringstream ss; + ss.str(""); ss << "Entering get_forcing_params for identifier: " << identifier << std::endl; + LOG(ss.str(), LogLevel::DEBUG); // Extract the required 'path' parameter std::string path = ""; if (forcing_prop_map.count("path") != 0) { path = forcing_prop_map.at("path").as_string(); - std::cout << " [DEBUG] Forcing path: " << path << std::endl; + ss.str(""); ss << " Forcing path: " << path << std::endl; + LOG(ss.str(), LogLevel::DEBUG); } // Extract the required 'provider' parameter std::string provider; if (forcing_prop_map.count("provider") != 0) { provider = forcing_prop_map.at("provider").as_string(); - std::cout << " [DEBUG] Forcing provider: " << provider << std::endl; + ss.str(""); ss << " Forcing provider: " << provider << std::endl; + LOG(ss.str(), LogLevel::DEBUG); } // Extract the optional 'init_config' parameter from 'params' @@ -579,7 +629,8 @@ namespace realization { const geojson::PropertyMap& params_map = params_property.get_values(); if (params_map.count("init_config") != 0) { init_config = params_map.at("init_config").as_string(); - std::cout << " [DEBUG] Forcing init_config: " << init_config << std::endl; + ss.str(""); ss << " Forcing init_config: " << init_config << std::endl; + LOG(ss.str(), LogLevel::DEBUG); } } else { std::cout << "[WARNING] 'params' is not an object for identifier: " << identifier << std::endl; @@ -779,17 +830,19 @@ namespace realization { case geojson::PropertyType::List: case geojson::PropertyType::Object: default: - ss << "WARNING: property type " << static_cast(catchment_attribute.get_type()) << " not allowed as model parameter. " + ss.str(""); + ss << "property type " << static_cast(catchment_attribute.get_type()) << " not allowed as model parameter. " << "Must be one of: Natural (int), Real (double), Boolean, or String" << '\n'; - LOG(ss.str(), LogLevel::SEVERE); ss.str(""); + LOG(ss.str(), LogLevel::WARNING); ss.str(""); break; } } else { - ss << "WARNING Failed to parse external parameter: catchment `" - << catchment_feature->get_id() - << "` does not contain the property `" - << param_name << "`\n"; - LOG(ss.str(), LogLevel::SEVERE); ss.str(""); + ss.str(""); + ss << " Failed to parse external parameter: catchment `" + << catchment_feature->get_id() + << "` does not contain the property `" + << param_name << "`\n"; + LOG(ss.str(), LogLevel::WARNING); } } @@ -851,9 +904,9 @@ namespace realization { default: // TODO: Should list/object values be passed to model parameters? // Typically, feature properties *should* be scalars. - ss << "WARNING: property type " << static_cast(catchment_attribute.get_type()) << " not allowed as model parameter. " - << "Must be one of: Natural (int), Real (double), Boolean, or String" << '\n'; - LOG(ss.str(), LogLevel::SEVERE); ss.str(""); + ss.str(""); ss << "property type " << static_cast(catchment_attribute.get_type()) << " not allowed as model parameter. " + << "Must be one of: Natural (int), Real (double), Boolean, or String" << '\n'; + LOG(ss.str(), LogLevel::WARNING); break; } } diff --git a/include/utilities/Logger.hpp b/include/utilities/Logger.hpp index a1250cf88e..0441e3091f 100644 --- a/include/utilities/Logger.hpp +++ b/include/utilities/Logger.hpp @@ -40,6 +40,8 @@ class Logger { throw std::runtime_error(message); }; + static Logger* GetLogger(); + private: // Methods static std::string ConvertLogLevelToString(LogLevel level); @@ -74,8 +76,6 @@ class Logger { bool openedOnce = false; std::unordered_map moduleLogLevels; - - static Logger* GetLogger(); }; // Placed here to ensure the class is declared before setting this preprocessor symbol diff --git a/src/NGen.cpp b/src/NGen.cpp index 4aed4cda84..bfc4cebabe 100644 --- a/src/NGen.cpp +++ b/src/NGen.cpp @@ -690,7 +690,7 @@ int main(int argc, char* argv[]) { // next time if (layer_next_time <= next_time && layer_next_time <= prev_layer_time) { if (count % 100 == 0) { - ss << "Updating layer: " << layer->get_name() << "\n"; + ss << "Updating layer: " << layer->get_name() << " Count=" << count << "\n"; LOG(ss.str(), LogLevel::DEBUG); ss.str(""); } diff --git a/src/forcing/ForcingsEngineLumpedDataProvider.cpp b/src/forcing/ForcingsEngineLumpedDataProvider.cpp index b99cfafa43..68d746b4ce 100644 --- a/src/forcing/ForcingsEngineLumpedDataProvider.cpp +++ b/src/forcing/ForcingsEngineLumpedDataProvider.cpp @@ -19,9 +19,11 @@ std::size_t convert_divide_id_stoi(const std::string& divide_id) : ÷_id[separator + 1] ); - std::cout << "[ngen debug] Converting divide ID: " << divide_id - << " -> " << split << std::endl; - + std::stringstream ss; + ss.str(""); + ss << "Converting divide ID: " << divide_id + << " -> " << split << std::endl; + LOG(ss.str(), LogLevel::DEBUG); return std::atol(split); } @@ -34,19 +36,28 @@ Provider::ForcingsEngineLumpedDataProvider( : BaseProvider(init_config, time_begin_seconds, time_end_seconds) { // Add detailed logging of the constructor arguments - std::cout << "\n[ngen debug] Initializing ForcingsEngineLumpedDataProvider:" << std::endl; - std::cout << " init_config: " << init_config << std::endl; - + std::stringstream ss; + ss.str(""); + ss << "Initializing ForcingsEngineLumpedDataProvider:" << std::endl; + ss << " init_config: " << init_config << std::endl; + LOG(ss.str(), LogLevel::DEBUG); + std::time_t tb_t = static_cast(time_begin_seconds); std::time_t te_t = static_cast(time_end_seconds); - std::cout << " Time begin: " << std::put_time(std::gmtime(&tb_t), "%Y-%m-%d %H:%M:%S UTC") - << " (" << time_begin_seconds << ")" << std::endl; + ss.str(""); + ss << " Time begin: " << std::put_time(std::gmtime(&tb_t), "%Y-%m-%d %H:%M:%S UTC") + << " (" << time_begin_seconds << ")" << std::endl; + LOG(ss.str(), LogLevel::DEBUG); - std::cout << " Time end: " << std::put_time(std::gmtime(&te_t), "%Y-%m-%d %H:%M:%S UTC") - << " (" << time_end_seconds << ")" << std::endl; + ss.str(""); + ss << " Time end: " << std::put_time(std::gmtime(&te_t), "%Y-%m-%d %H:%M:%S UTC") + << " (" << time_end_seconds << ")" << std::endl; + LOG(ss.str(), LogLevel::DEBUG); - std::cout << " divide ID: " << divide_id << std::endl; + ss.str(""); + ss << " divide ID: " << divide_id << std::endl; + LOG(ss.str(), LogLevel::DEBUG); divide_id_ = convert_divide_id_stoi(divide_id); @@ -54,21 +65,26 @@ Provider::ForcingsEngineLumpedDataProvider( // running the correct configuration of the forcings engine for this class. const auto cat_id_pos = std::find(var_output_names_.begin(), var_output_names_.end(), "CAT-ID"); if (cat_id_pos == var_output_names_.end()) { - std::cerr << "[ngen error] Failed to initialize ForcingsEngineLumpedDataProvider: " + + ss << "Failed to initialize ForcingsEngineLumpedDataProvider: " << "`CAT-ID` is not an output variable of the forcings engine." << " Does " << init_config << " have `GRID_TYPE` set to 'hydrofabric'?" << std::endl; throw std::runtime_error{ "Failed to initialize ForcingsEngineLumpedDataProvider: `CAT-ID` is not an output variable of the forcings engine." }; } - std::cout << "[ngen debug] Found CAT-ID in output names" << std::endl; + ss.str(""); + ss << " Found CAT-ID in output names" << std::endl; + LOG(ss.str(), LogLevel::DEBUG); var_output_names_.erase(cat_id_pos); const auto size_id_dimension = static_cast( bmi_->GetVarNbytes("CAT-ID") / bmi_->GetVarItemsize("CAT-ID") ); - std::cout << "[ngen debug] CAT-ID size: " << size_id_dimension << std::endl; + ss.str(""); + ss << " CAT-ID size: " << size_id_dimension << std::endl; + LOG(ss.str(), LogLevel::DEBUG); // Copy CAT-ID values into instance vector const auto cat_id_span = boost::span( @@ -78,15 +94,21 @@ Provider::ForcingsEngineLumpedDataProvider( auto divide_id_pos = std::find(cat_id_span.begin(), cat_id_span.end(), divide_id_); if (divide_id_pos == cat_id_span.end()) { - std::cerr << "[ngen error] Unable to find divide ID `" << divide_id - << "` in the given Forcings Engine domain" << std::endl; + ss.str(""); + ss << "Unable to find divide ID `" << divide_id + << "` in the given Forcings Engine domain" << std::endl; + LOG(ss.str(), LogLevel::SEVERE); divide_idx_ = static_cast(-1); } else { divide_idx_ = std::distance(cat_id_span.begin(), divide_id_pos); - std::cout << "[ngen debug] Divide ID found at index: " << divide_idx_ << std::endl; + ss.str(""); + ss << " Divide ID found at index: " << divide_idx_ << std::endl; + LOG(ss.str(), LogLevel::INFO); } - std::cout << "[ngen debug] ForcingsEngineLumpedDataProvider initialization complete" << std::endl; + ss.str(""); + ss << " ForcingsEngineLumpedDataProvider initialization complete" << std::endl; + LOG(LogLevel::DEBUG, ss.str()); } std::size_t Provider::divide() const noexcept @@ -104,7 +126,15 @@ Provider::data_type Provider::get_value( data_access::ReSampleMethod m ) { - assert(divide_id_ == convert_divide_id_stoi(selector.get_id())); + std::stringstream ss; + if (!(divide_id_ == convert_divide_id_stoi(selector.get_id()))) { + ss.str(""); + ss << "get_value() divide_id_ " << divide_id_ << " != selector id " << convert_divide_id_stoi(selector.get_id()); + LOG(LogLevel::FATAL, ss.str()); + throw std::runtime_error{ + "Divide ID mismatch in the forcings engine." + }; + } auto variable = ensure_variable(selector.get_variable_name()); @@ -118,19 +148,39 @@ Provider::data_type Provider::get_value( auto s_t = std::chrono::system_clock::to_time_t(start); auto e_t = std::chrono::system_clock::to_time_t(end); - std::cout << "get_value() Time begin: " << std::put_time(std::gmtime(&tb_t), "%Y-%m-%d %H:%M:%S UTC") - << " (" << tb_t << ")" - << " | start: " << std::put_time(std::gmtime(&s_t), "%Y-%m-%d %H:%M:%S UTC") - << " (" << s_t << ")" << std::endl; + ss.str(""); + ss << "get_value() Time begin: " << std::put_time(std::gmtime(&tb_t), "%Y-%m-%d %H:%M:%S UTC") + << " (" << tb_t << ")" + << " | start: " << std::put_time(std::gmtime(&s_t), "%Y-%m-%d %H:%M:%S UTC") + << " (" << s_t << ")" << std::endl; + LOG(LogLevel::DEBUG, ss.str()); - std::cout << "get_value() Time end: " << std::put_time(std::gmtime(&te_t), "%Y-%m-%d %H:%M:%S UTC") - << " (" << te_t << ")" - << " | end: " << std::put_time(std::gmtime(&e_t), "%Y-%m-%d %H:%M:%S UTC") - << " (" << e_t << ")" << std::endl; + ss.str(""); + ss << "get_value() Time end: " << std::put_time(std::gmtime(&te_t), "%Y-%m-%d %H:%M:%S UTC") + << " (" << te_t << ")" + << " | end: " << std::put_time(std::gmtime(&e_t), "%Y-%m-%d %H:%M:%S UTC") + << " (" << e_t << ")" << std::endl; + LOG(LogLevel::DEBUG, ss.str()); using namespace std::literals::chrono_literals; - assert(start >= time_begin_); - assert(end < time_end_ + time_step_ + 1s); + if (!(start >= time_begin_)) { + LOG(LogLevel::FATAL, + "get_value() Begin time less than start: start=%lld vs time_begin_=%lld", + static_cast(start.time_since_epoch().count()), + static_cast(time_begin_.time_since_epoch().count())); + throw std::runtime_error{ + "Begin time range error." + }; + } + if (!(end < time_end_ + time_step_ + 1s)) { + LOG(LogLevel::FATAL, + "get_value() Next Timestep > end: end=%lld vs limit=%lld", + static_cast(end.time_since_epoch().count()), + static_cast((time_end_ + time_step_ + 1s).time_since_epoch().count())); + throw std::runtime_error{ + "End time range error." + }; + } auto current = start; while (current < end) { @@ -148,7 +198,10 @@ Provider::data_type Provider::get_value( return acc; } - throw std::runtime_error{"Given ReSampleMethod " + std::to_string(m) + " not implemented."}; + ss.str(""); + ss << "Given ReSampleMethod " + std::to_string(m) + " not implemented."; + LOG(LogLevel::FATAL, ss.str()); + throw std::runtime_error{ss.str()}; } std::vector Provider::get_values( @@ -156,8 +209,16 @@ std::vector Provider::get_values( data_access::ReSampleMethod /* unused */ ) { - assert(divide_id_ == convert_divide_id_stoi(selector.get_id())); - + std::stringstream ss; + if (!(divide_id_ == convert_divide_id_stoi(selector.get_id()))) { + ss.str(""); + ss << "get_values() divide id " << divide_id_ << "not equal to selector.get_id" << convert_divide_id_stoi(selector.get_id()); + LOG(LogLevel::FATAL, ss.str()); + throw std::runtime_error{ + "Divide ID mismatch in the forcings engine." + }; + } + auto variable = ensure_variable(selector.get_variable_name()); const auto start = clock_type::from_time_t(selector.get_init_time()); @@ -168,20 +229,41 @@ std::vector Provider::get_values( auto s_t = std::chrono::system_clock::to_time_t(start); auto e_t = std::chrono::system_clock::to_time_t(end); - std::cout << "get_values() Time begin: " << std::put_time(std::gmtime(&tb_t), "%Y-%m-%d %H:%M:%S UTC") + ss.str(""); + ss << "get_values() Time begin: " << std::put_time(std::gmtime(&tb_t), "%Y-%m-%d %H:%M:%S UTC") << " (" << tb_t << ")" << " | start: " << std::put_time(std::gmtime(&s_t), "%Y-%m-%d %H:%M:%S UTC") << " (" << s_t << ")" << std::endl; + LOG(LogLevel::DEBUG, ss.str()); - std::cout << "get_values() Time end: " << std::put_time(std::gmtime(&te_t), "%Y-%m-%d %H:%M:%S UTC") + ss.str(""); + ss << "get_values() Time end: " << std::put_time(std::gmtime(&te_t), "%Y-%m-%d %H:%M:%S UTC") << " (" << te_t << ")" << " | end: " << std::put_time(std::gmtime(&e_t), "%Y-%m-%d %H:%M:%S UTC") << " (" << e_t << ")" << std::endl; + LOG(LogLevel::DEBUG, ss.str()); using namespace std::literals::chrono_literals; - assert(start >= time_begin_); - assert(end < time_end_ + time_step_ + 1s); + if (!(start >= time_begin_)) { + LOG(LogLevel::FATAL, + "get_values() Begin time less than start: start=%lld vs time_begin_=%lld", + static_cast(start.time_since_epoch().count()), + static_cast(time_begin_.time_since_epoch().count())); + throw std::runtime_error{ + "Start time range error." + }; + } + if (!(end < time_end_ + time_step_ + 1s)) { + LOG(LogLevel::FATAL, + "get_values() Next Timestep > end: end=%lld vs limit=%lld", + static_cast(end.time_since_epoch().count()), + static_cast((time_end_ + time_step_ + 1s).time_since_epoch().count())); + throw std::runtime_error{ + "End time range error." + }; + } + std::vector values; auto current = start; while (current < end) { diff --git a/src/forcing/NetCDFPerFeatureDataProvider.cpp b/src/forcing/NetCDFPerFeatureDataProvider.cpp index 6c5efccf9e..f65784ff5c 100644 --- a/src/forcing/NetCDFPerFeatureDataProvider.cpp +++ b/src/forcing/NetCDFPerFeatureDataProvider.cpp @@ -38,9 +38,12 @@ void NetCDFPerFeatureDataProvider::cleanup_shared_providers() shared_providers.clear(); } -NetCDFPerFeatureDataProvider::NetCDFPerFeatureDataProvider(std::string input_path, time_t sim_start, time_t sim_end, utils::StreamHandler log_s) : log_stream(log_s), value_cache(20), - sim_start_date_time_epoch(sim_start), - sim_end_date_time_epoch(sim_end) +NetCDFPerFeatureDataProvider::NetCDFPerFeatureDataProvider(std::string input_path, time_t sim_start, time_t sim_end, utils::StreamHandler log_s) + : log_stream(log_s) + , file_path(input_path) + , value_cache(20) + , sim_start_date_time_epoch(sim_start) + , sim_end_date_time_epoch(sim_end) { //size_t sizep = 1073741824, nelemsp = 202481; //float preemptionp = 0.75; @@ -419,14 +422,12 @@ double NetCDFPerFeatureDataProvider::get_value(const CatchmentAggrDataSelector& } catch (const std::runtime_error& e) { - //minor change to aid debugging (log_stream is an output log stream for messages from the underlying library, therefore logging to both EWTS and library logger) - netcdf_ss << "NetCDFPerFeatureDataProvider: get_converted_value Unit conversion unsuccessful - Returning unconverted value! (" << e.what() << ")" << std::endl; - log_stream << netcdf_ss.str(); - LOG(netcdf_ss.str(), LogLevel::WARNING); netcdf_ss.str(""); - netcdf_ss << "=== Exiting get_value function ===" << std::endl; - log_stream << netcdf_ss.str(); - LOG(netcdf_ss.str(), LogLevel::WARNING); netcdf_ss.str(""); - return rvalue; + data_access::unit_conversion_exception uce(e.what()); + uce.provider_model_name = "NetCDFPerFeatureDataProvider(" + file_path + ")"; + uce.provider_bmi_var_name = selector.get_variable_name(); + uce.provider_units = native_units; + uce.unconverted_values.push_back(rvalue); + throw uce; } return rvalue; diff --git a/src/realizations/catchment/Bmi_Formulation.cpp b/src/realizations/catchment/Bmi_Formulation.cpp index 2048a47a79..ebb7bb32e2 100644 --- a/src/realizations/catchment/Bmi_Formulation.cpp +++ b/src/realizations/catchment/Bmi_Formulation.cpp @@ -18,3 +18,7 @@ namespace realization BMI_REALIZATION_CFG_PARAM_REQ__MODEL_TYPE, }; } + +namespace data_access { + std::set unit_errors_reported; +} diff --git a/src/realizations/catchment/Bmi_Module_Formulation.cpp b/src/realizations/catchment/Bmi_Module_Formulation.cpp index 335cafde19..883f82d679 100644 --- a/src/realizations/catchment/Bmi_Module_Formulation.cpp +++ b/src/realizations/catchment/Bmi_Module_Formulation.cpp @@ -25,8 +25,14 @@ namespace realization { if (timestep != (next_time_step_index - 1)) { throw std::invalid_argument("Only current time step valid when getting output for BMI C++ formulation"); } - std::string output_str; + static bool no_conversion_message_logged = false; + if (!no_conversion_message_logged) { + no_conversion_message_logged = true; + LOG("Emitting output variables from Bmi_Module_Formulation without unit conversion - see NGWPC-7604", LogLevel::WARNING); + } + + std::string output_str; for (const std::string& name : get_output_variable_names()) { output_str += (output_str.empty() ? "" : ",") + std::to_string(get_var_value_as_double(0, name)); } @@ -175,19 +181,12 @@ namespace realization { return values; } catch (const std::runtime_error& e) { - // Log at least one error - if (!unitGetValuesErrLogged) { - bmiform_ss << "BMI Module Formulation: get_values Unit conversion unsuccessful - Returning unconverted value! (" << e.what() << ")" << std::endl; - unitGetValuesErrLogged = true; - LOG(bmiform_ss.str(), LogLevel::WARNING); bmiform_ss.str(""); - } - else { - #ifndef UDUNITS_QUIET - bmiform_ss << "BMI Module Formulation: get_values Unit conversion unsuccessful - Returning unconverted value! (" << e.what() << ")" << std::endl; - LOG(bmiform_ss.str(), LogLevel::WARNING); bmiform_ss.str(""); - #endif - } - return values; + data_access::unit_conversion_exception uce(e.what()); + uce.provider_model_name = get_bmi_model()->get_model_name(); + uce.provider_bmi_var_name = bmi_var_name; + uce.provider_units = native_units; + uce.unconverted_values = std::move(values); + throw uce; } } //This is unlikely (impossible?) to throw since a pre-check on available names is done above. Assert instead? @@ -230,19 +229,12 @@ namespace realization { return UnitsHelper::get_converted_value(native_units, value, output_units); } catch (const std::runtime_error& e){ - // Log at least one error - if (!unitGetValueErrLogged) { - bmiform_ss << "BMI Module Formulation: get_value Unit conversion unsuccessful - Returning unconverted value! (" << e.what() << ")" << std::endl; - unitGetValueErrLogged = true; - LOG(bmiform_ss.str(), LogLevel::WARNING); bmiform_ss.str(""); - } - else { - #ifndef UDUNITS_QUIET - bmiform_ss << "BMI Module Formulation: get_value Unit conversion unsuccessful - Returning unconverted value! (" << e.what() << ")" << std::endl; - LOG(bmiform_ss.str(), LogLevel::WARNING); bmiform_ss.str(""); - #endif - } - return value; + data_access::unit_conversion_exception uce(e.what()); + uce.provider_model_name = get_bmi_model()->get_model_name(); + uce.provider_bmi_var_name = bmi_var_name; + uce.provider_units = native_units; + uce.unconverted_values.push_back(value); + throw uce; } } @@ -724,10 +716,28 @@ namespace realization { value_ptr = get_values_as_type( type, values.begin(), values.end() ); } else { - //scalar value - double value = provider->get_value(CatchmentAggrDataSelector(this->get_catchment_id(),var_map_alias, model_epoch_time, t_delta, - get_bmi_model()->GetVarUnits(var_name))); - value_ptr = get_value_as_type(type, value); + try { + //scalar value + double value = provider->get_value(CatchmentAggrDataSelector(this->get_catchment_id(),var_map_alias, model_epoch_time, t_delta, + get_bmi_model()->GetVarUnits(var_name))); + value_ptr = get_value_as_type(type, value); + } catch (data_access::unit_conversion_exception &uce) { + data_access::unit_error_log_key key{get_id(), var_map_alias, uce.provider_model_name, uce.provider_bmi_var_name, uce.what()}; + auto ret = data_access::unit_errors_reported.insert(key); + bool new_error = ret.second; + if (new_error) { + std::stringstream ss; + ss << "Unit conversion failure:" + << " requester {'" << get_bmi_model()->get_model_name() << "' catchment '" << get_catchment_id() + << "' variable '" << var_name << "'" << " (alias '" << var_map_alias << "')" + << " units '" << get_bmi_model()->GetVarUnits(var_name) << "'}" + << " 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); ss.str(""); + } + value_ptr = get_value_as_type(type, uce.unconverted_values[0]); + } } get_bmi_model()->SetValue(var_name, value_ptr.get()); } diff --git a/src/routing/Routing_Py_Adapter.cpp b/src/routing/Routing_Py_Adapter.cpp index 4dbb87520d..f003aa4893 100644 --- a/src/routing/Routing_Py_Adapter.cpp +++ b/src/routing/Routing_Py_Adapter.cpp @@ -16,22 +16,15 @@ Routing_Py_Adapter::Routing_Py_Adapter(std::string t_route_config_file_with_path t_route_config_path(t_route_config_file_with_path){ //hold a reference to the interpreter, ensures an interpreter exists as long as the reference is held interpreter = utils::ngenPy::InterpreterUtil::getInstance(); - //Import ngen_main. Will throw error if module isn't available - //in the embedded interpreters PYTHON_PATH + // Import the routing module. An error will be thrown if module isn't available + // in the embedded interpreters PYTHON_PATH try { - this->t_route_module = utils::ngenPy::InterpreterUtil::getPyModule("ngen_routing.ngen_main"); - LOG("Legacy t-route module detected; use of this version is deprecated!", LogLevel::WARNING); + this->t_route_module = utils::ngenPy::InterpreterUtil::getPyModule("nwm_routing.__main__"); + LOG("Routing module nwm_routing.__main__ detected and used.", LogLevel::INFO); } - catch (const pybind11::error_already_set& e){ - try { - // The legacy module has a `nwm_routing.__main__`, so we have to try this one second! - this->t_route_module = utils::ngenPy::InterpreterUtil::getPyModule("nwm_routing.__main__"); - LOG("Legacy t-route module nwm_routing.__main__ detected and used.", LogLevel::DEBUG); - } - catch (const pybind11::error_already_set& e){ - LOG("Unable to import a supported routing module.", LogLevel::FATAL); - throw e; - } + catch (const pybind11::error_already_set& e) { + LOG("Unable to import a supported routing module.", LogLevel::FATAL); + throw e; } } diff --git a/test/bmi/Bmi_Cpp_Adapter_Test.cpp b/test/bmi/Bmi_Cpp_Adapter_Test.cpp index 0151360852..d9e7ad6d2d 100644 --- a/test/bmi/Bmi_Cpp_Adapter_Test.cpp +++ b/test/bmi/Bmi_Cpp_Adapter_Test.cpp @@ -51,7 +51,7 @@ class Bmi_Cpp_Adapter_Test : public ::testing::Test { std::vector expected_output_var_names = { "OUTPUT_VAR_1", "OUTPUT_VAR_2" }; std::vector expected_output_var_locations = { "node", "node" }; std::vector expected_output_var_grids = { 0, 0 }; - std::vector expected_output_var_units = { "m", "m" }; + std::vector expected_output_var_units = { "mm/s", "m" }; std::vector expected_output_var_types = { "double", "double" }; int expected_grid_rank = 1; int expected_grid_size = 1; diff --git a/test/bmi/Bmi_Fortran_Adapter_Test.cpp b/test/bmi/Bmi_Fortran_Adapter_Test.cpp index 1e46792dc6..1e3982dfbe 100644 --- a/test/bmi/Bmi_Fortran_Adapter_Test.cpp +++ b/test/bmi/Bmi_Fortran_Adapter_Test.cpp @@ -52,7 +52,7 @@ class Bmi_Fortran_Adapter_Test : public ::testing::Test { std::vector expected_output_var_locations = { "node", "node", "node", "node", "node"}; std::vector expected_output_var_grids = { 0, 0, 0, 1, 2, 2 }; std::vector expected_input_var_grids = { 0, 0, 0, 1 }; - std::vector expected_output_var_units = { "m", "m", "s", "m", "m", "m" }; + std::vector expected_output_var_units = { "mm s^-1", "m", "s", "m", "m", "m" }; std::vector expected_output_var_types = { "double precision", "real", "integer", "double precision", "double precision", "double precision" }; std::vector expected_output_var_item_sizes = { 8, 4, 4, 8, 8, 8}; std::vector expected_input_var_types = { "double precision", "real", "integer", "double precision" }; diff --git a/test/forcing/CsvPerFeatureForcingProvider_Test.cpp b/test/forcing/CsvPerFeatureForcingProvider_Test.cpp index 9d292f1f08..774b3e7633 100644 --- a/test/forcing/CsvPerFeatureForcingProvider_Test.cpp +++ b/test/forcing/CsvPerFeatureForcingProvider_Test.cpp @@ -94,24 +94,24 @@ TEST_F(CsvPerFeatureForcingProviderTest, TestForcingDataRead) time_t t = begin+(i*3600); std::cerr << std::ctime(&t) << std::endl; - current_precipitation = Forcing_Object->get_value(CatchmentAggrDataSelector("", CSDMS_STD_NAME_LIQUID_EQ_PRECIP_RATE, begin+(i*3600), 3600, ""), data_access::SUM); + current_precipitation = Forcing_Object->get_value(CatchmentAggrDataSelector("", CSDMS_STD_NAME_LIQUID_EQ_PRECIP_RATE, begin+(i*3600), 3600, "mm/s"), data_access::SUM); EXPECT_NEAR(current_precipitation, 7.9999999999999996e-07, 0.00000005); - double temp_k = Forcing_Object->get_value(CatchmentAggrDataSelector("", CSDMS_STD_NAME_SURFACE_TEMP, begin+(i*3600), 3600, ""), data_access::MEAN); + double temp_k = Forcing_Object->get_value(CatchmentAggrDataSelector("", CSDMS_STD_NAME_SURFACE_TEMP, begin+(i*3600), 3600, "K"), data_access::MEAN); EXPECT_NEAR(temp_k, 286.9, 0.00001); int current_epoch; i = 387; - current_precipitation = Forcing_Object->get_value(CatchmentAggrDataSelector("", CSDMS_STD_NAME_LIQUID_EQ_PRECIP_RATE, begin+(i*3600), 3600, ""), data_access::SUM); + current_precipitation = Forcing_Object->get_value(CatchmentAggrDataSelector("", CSDMS_STD_NAME_LIQUID_EQ_PRECIP_RATE, begin+(i*3600), 3600, "mm/s"), data_access::SUM); EXPECT_NEAR(current_precipitation, 6.9999999999999996e-07, 0.00000005); //Check exceeding the forcing range to retrieve the last forcing precipation rate i = 388; - current_precipitation = Forcing_Object->get_value(CatchmentAggrDataSelector("", CSDMS_STD_NAME_LIQUID_EQ_PRECIP_RATE, begin+(i*3600), 3600, ""), data_access::SUM); + current_precipitation = Forcing_Object->get_value(CatchmentAggrDataSelector("", CSDMS_STD_NAME_LIQUID_EQ_PRECIP_RATE, begin+(i*3600), 3600, "mm/s"), data_access::SUM); EXPECT_NEAR(current_precipitation, 6.9999999999999996e-07, 0.00000005); } @@ -127,18 +127,18 @@ TEST_F(CsvPerFeatureForcingProviderTest, TestForcingDataReadAltFormat) time_t t = begin+(i*3600); std::cerr << std::ctime(&t) << std::endl; - current_precipitation = Forcing_Object_2->get_value(CatchmentAggrDataSelector("", CSDMS_STD_NAME_LIQUID_EQ_PRECIP_RATE, begin+(i*3600), 3600, ""), data_access::SUM); + current_precipitation = Forcing_Object_2->get_value(CatchmentAggrDataSelector("", CSDMS_STD_NAME_LIQUID_EQ_PRECIP_RATE, begin+(i*3600), 3600, "mm/s"), data_access::SUM); EXPECT_NEAR(current_precipitation, 0.00032685, 0.00000001); - double temp_k = Forcing_Object_2->get_value(CatchmentAggrDataSelector("", CSDMS_STD_NAME_SURFACE_TEMP, begin+(i*3600), 3600, ""), data_access::MEAN); + double temp_k = Forcing_Object_2->get_value(CatchmentAggrDataSelector("", CSDMS_STD_NAME_SURFACE_TEMP, begin+(i*3600), 3600, "K"), data_access::MEAN); EXPECT_NEAR(temp_k, 265.77, 0.00001); int current_epoch; i = 34; - current_precipitation = Forcing_Object_2->get_value(CatchmentAggrDataSelector("", CSDMS_STD_NAME_LIQUID_EQ_PRECIP_RATE, begin+(i*3600), 3600, ""), data_access::SUM); + current_precipitation = Forcing_Object_2->get_value(CatchmentAggrDataSelector("", CSDMS_STD_NAME_LIQUID_EQ_PRECIP_RATE, begin+(i*3600), 3600, "mm/s"), data_access::SUM); EXPECT_NEAR(current_precipitation, 0.00013539, 0.00000001); @@ -156,7 +156,7 @@ TEST_F(CsvPerFeatureForcingProviderTest, TestForcingDataUnitConversion) time_t t = begin+(i*3600); std::cerr << std::ctime(&t) << std::endl; - current_precipitation = Forcing_Object->get_value(CatchmentAggrDataSelector("", CSDMS_STD_NAME_LIQUID_EQ_PRECIP_RATE, begin+(i*3600), 3600, ""), data_access::SUM); + current_precipitation = Forcing_Object->get_value(CatchmentAggrDataSelector("", CSDMS_STD_NAME_LIQUID_EQ_PRECIP_RATE, begin+(i*3600), 3600, "mm/s"), data_access::SUM); EXPECT_NEAR(current_precipitation, 7.9999999999999996e-07, 0.00000005); @@ -188,9 +188,14 @@ TEST_F(CsvPerFeatureForcingProviderTest, TestForcingUnitHeaderParsing) {"U2D", "m s-1", "cm s-1"}, {"V2D", "m/s", "cm s-1"}, {"TEST", "kg", "g"}, + // Disable the cases where units won't be parsed and + // conversion was previously not expected. The non-conversion + // is now being treated as an error, per NGWPC-7604 +#if 0 {"PSFC[Pa)", "Pa", "bar"}, {"SWDOWN(W m-2]", "W m-2", "langley"}, {"LWDOWN [alt]", "W m-2", "langley"} +#endif }; for (auto ite = expected.begin(); ite != expected.end(); ite++) {