From c46334afdbd2043c8e4af7859a1b5116c17c8dea Mon Sep 17 00:00:00 2001 From: Ian Todd Date: Fri, 23 Jan 2026 09:14:29 -0500 Subject: [PATCH 1/6] Implement reset_time message --- src/bmi_lgar.cxx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/bmi_lgar.cxx b/src/bmi_lgar.cxx index a580b2f..796a8f8 100644 --- a/src/bmi_lgar.cxx +++ b/src/bmi_lgar.cxx @@ -773,6 +773,7 @@ GetVarGrid(std::string name) || name.compare("groundwater_to_stream_recharge") == 0 || name.compare("mass_balance") == 0 || name.compare(NWM_PONDED_DEPTH_OUT_VAR) == 0 + || name.compare("reset_time") == 0 ) // double return 1; else if ( @@ -1086,6 +1087,11 @@ SetValue (std::string name, void *src) } else if (name.compare("serialization_create") == 0) { this->new_serialized(); return; + } else if (name.compare("reset_time") == 0) { + // time_s and timesteps seems to be used exclusively for reporting current time + this->state->lgar_bmi_params.time_s = this->GetStartTime(); + this->state->lgar_bmi_params.timesteps = 0; + return; } void * dest = NULL; dest = this->GetValuePtr(name); From f31e9fbb54307bc918f13831e1810f533ea942cd Mon Sep 17 00:00:00 2001 From: Ian Todd Date: Thu, 5 Feb 2026 14:46:14 -0500 Subject: [PATCH 2/6] Add size header to serialized data --- include/bmi_lgar.hxx | 2 +- include/vecbuf.hpp | 12 +++++++++++- src/bmi_lgar.cxx | 14 +++++++++++--- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/include/bmi_lgar.hxx b/include/bmi_lgar.hxx index 91a1cbb..d4a131d 100644 --- a/include/bmi_lgar.hxx +++ b/include/bmi_lgar.hxx @@ -184,7 +184,7 @@ private: void serialize_wetting_front_list(Archive &ar, wetting_front **head, int &count); void new_serialized(); - void load_serialized(const char* data); + void load_serialized(char* data); void free_serialized(); }; diff --git a/include/vecbuf.hpp b/include/vecbuf.hpp index 6db982c..a20bc70 100644 --- a/include/vecbuf.hpp +++ b/include/vecbuf.hpp @@ -28,6 +28,9 @@ class vecbuf : public std::basic_streambuf { // Forwarder for std::vector::clear() constexpr void clear() { vector_.clear(); } + // Forwarder for std::vector::resize(size) + constexpr void resize(size_type size) { vector_.resize(size); } + // Forwarder for std::vector::reserve constexpr void reserve(size_type capacity) { vector_.reserve(capacity); setp_from_vector(); } @@ -35,7 +38,7 @@ class vecbuf : public std::basic_streambuf { constexpr void reserve_additional(size_type additional_capacity) { reserve(size() + additional_capacity); } // Forwarder for std::vector::data - constexpr const value_type* data() const { return vector_.data(); } + constexpr value_type* data() { return vector_.data(); } // Forwarder for std::vector::size constexpr size_type size() const { return vector_.size(); } @@ -112,4 +115,11 @@ class vecbuf : public std::basic_streambuf { }; +class membuf : public std::streambuf { +public: + membuf(char *begin, size_t size) { + this->setg(begin, begin, begin + size); + } +}; + #endif diff --git a/src/bmi_lgar.cxx b/src/bmi_lgar.cxx index 796a8f8..4b252bd 100644 --- a/src/bmi_lgar.cxx +++ b/src/bmi_lgar.cxx @@ -1436,11 +1436,15 @@ void BmiLGAR::serialize_wetting_front_list(Archive &ar, wetting_front **head, in } void BmiLGAR::new_serialized() { - this->m_serialized.clear(); + // resize with reserved space for storing size + this->m_serialized.resize(sizeof(uint64_t)); boost::archive::binary_oarchive archive(this->m_serialized); try { archive << (*this); this->m_serialized_length = this->m_serialized.size(); + // get serialized size without header and copy size to the beginning of the buffer + uint64_t serialized_size = this->m_serialized_length - sizeof(uint64_t); + memcpy(this->m_serialized.data(), &serialized_size, sizeof(uint64_t)); } catch (const std::exception &e) { Logger::Log(LogLevel::SEVERE, "Serializing LASAM encountered an error: %s", e.what()); this->free_serialized(); @@ -1448,8 +1452,12 @@ void BmiLGAR::new_serialized() { } } -void BmiLGAR::load_serialized(const char* data) { - std::stringstream stream(data); +void BmiLGAR::load_serialized(char* data) { + // copy size from the start of data + uint64_t size; + memcpy(&size, data, sizeof(uint64_t)); + // create stream from everything past the size header + membuf stream(data + sizeof(uint64_t), size); boost::archive::binary_iarchive archive(stream); try { archive >> (*this); From d8fdbacfecbcc7c4bdf17b80b6176a55961c7186 Mon Sep 17 00:00:00 2001 From: mkarim-rtx <149624049+mkarim-rtx@users.noreply.github.com> Date: Wed, 11 Mar 2026 08:36:54 -0700 Subject: [PATCH 3/6] Adding Precipitation Rate Output (#11) * added precipitation_rate_out * corrected syntax error --- include/bmi_lgar.hxx | 3 ++- realizations/realization_config_lasam.json | 1 + realizations/realization_config_lasam_sft.json | 1 + realizations/realization_config_lasam_smp.json | 1 + src/bmi_lgar.cxx | 14 ++++++++++---- src/bmi_main_lgar.cxx | 4 ++-- tests/main_unit_test_bmi.cxx | 6 +++--- 7 files changed, 20 insertions(+), 10 deletions(-) diff --git a/include/bmi_lgar.hxx b/include/bmi_lgar.hxx index 91a1cbb..498d687 100644 --- a/include/bmi_lgar.hxx +++ b/include/bmi_lgar.hxx @@ -61,6 +61,7 @@ public: this->output_var_names[13] = "groundwater_to_stream_recharge"; this->output_var_names[14] = "mass_balance"; this->output_var_names[15] = NWM_PONDED_DEPTH_OUT_VAR; + this->output_var_names[16] = "precipitation_rate_out"; /* this->output_var_names[13] = "cum_precipitation"; @@ -145,7 +146,7 @@ private: void realloc_soil(); struct model_state* state; static const int input_var_name_count = 3; - static const int output_var_name_count = 16; + static const int output_var_name_count = 17; static const int calib_var_name_count = 7; std::string input_var_names[input_var_name_count]; diff --git a/realizations/realization_config_lasam.json b/realizations/realization_config_lasam.json index 7066b47..6bf3db0 100644 --- a/realizations/realization_config_lasam.json +++ b/realizations/realization_config_lasam.json @@ -17,6 +17,7 @@ "main_output_variable": "total_discharge", "output_variables" : [ "precipitation", + "precipitation_rate_out", "potential_evapotranspiration", "actual_evapotranspiration", "surface_runoff", diff --git a/realizations/realization_config_lasam_sft.json b/realizations/realization_config_lasam_sft.json index 2248399..58a4d64 100644 --- a/realizations/realization_config_lasam_sft.json +++ b/realizations/realization_config_lasam_sft.json @@ -17,6 +17,7 @@ "main_output_variable": "total_discharge", "output_variables" : [ "precipitation", + "precipitation_rate_out", "potential_evapotranspiration", "actual_evapotranspiration", "surface_runoff", diff --git a/realizations/realization_config_lasam_smp.json b/realizations/realization_config_lasam_smp.json index 3203240..e671382 100644 --- a/realizations/realization_config_lasam_smp.json +++ b/realizations/realization_config_lasam_smp.json @@ -17,6 +17,7 @@ "main_output_variable": "total_discharge", "output_variables" : [ "precipitation", + "precipitation_rate_out", "potential_evapotranspiration", "actual_evapotranspiration", "surface_runoff", diff --git a/src/bmi_lgar.cxx b/src/bmi_lgar.cxx index a580b2f..34e6b88 100644 --- a/src/bmi_lgar.cxx +++ b/src/bmi_lgar.cxx @@ -758,6 +758,7 @@ GetVarGrid(std::string name) return 0; else if ( name.compare("precipitation_rate") == 0 + || name.compare("precipitation_rate_out") == 0 || name.compare("precipitation") == 0 || name.compare("potential_evapotranspiration_rate") == 0 || name.compare("potential_evapotranspiration") == 0 @@ -847,7 +848,8 @@ GetVarItemsize(std::string name) std::string BmiLGAR:: GetVarUnits(std::string name) { - if (name.compare("precipitation_rate") == 0 || name.compare("potential_evapotranspiration_rate") == 0) + if (name.compare("precipitation_rate") == 0 || name.compare("precipitation_rate_out") == 0 + || name.compare("potential_evapotranspiration_rate") == 0) return "mm h^-1"; else if (name.compare("precipitation") == 0 || name.compare("potential_evapotranspiration") == 0 || name.compare("actual_evapotranspiration") == 0) // double @@ -887,9 +889,11 @@ GetVarNbytes(std::string name) std::string BmiLGAR:: GetVarLocation(std::string name) { - if (name.compare("precipitation_rate") == 0 || name.compare("precipitation") == 0 || - name.compare("potential_evapotranspiration") == 0 || name.compare("potential_evapotranspiration_rate") == 0 - || name.compare("actual_evapotranspiration") == 0) // double + if (name.compare("precipitation_rate") == 0 || name.compare("precipitation_rate_out") == 0 || + name.compare("precipitation") == 0 || + name.compare("potential_evapotranspiration") == 0 || + name.compare("potential_evapotranspiration_rate") == 0 || + name.compare("actual_evapotranspiration") == 0) return "node"; else if (name.compare("surface_runoff") == 0 || name.compare("giuh_runoff") == 0 || name.compare("soil_storage") == 0 || name.compare(NWM_PONDED_DEPTH_OUT_VAR)) // double @@ -985,6 +989,8 @@ GetValuePtr (std::string name) { if (name.compare("precipitation_rate") == 0) return (void*)(&this->state->lgar_bmi_input_params->precipitation_mm_per_h); + else if (name.compare("precipitation_rate_out") == 0) + return (void*)(&this->state->lgar_bmi_input_params->precipitation_mm_per_h); else if (name.compare("precipitation") == 0) return (void*)(&bmi_unit_conv.volprecip_timestep_m); else if (name.compare("potential_evapotranspiration_rate") == 0) diff --git a/src/bmi_main_lgar.cxx b/src/bmi_main_lgar.cxx index 1dd07a5..7a03373 100644 --- a/src/bmi_main_lgar.cxx +++ b/src/bmi_main_lgar.cxx @@ -57,7 +57,7 @@ int main(int argc, char *argv[]) std::string var_name_wf = "soil_moisture_wetting_fronts"; std::string var_name_thickness_wf = "soil_depth_wetting_fronts"; - int num_output_var = 11; + int num_output_var = 12; std::vector output_var_names(num_output_var); std::vector output_var_data(num_output_var); @@ -72,7 +72,7 @@ int main(int argc, char *argv[]) output_var_names[8] = "percolation"; output_var_names[9] = "groundwater_to_stream_recharge"; output_var_names[10] = "mass_balance"; - + output_var_names[11] = "precipitation_rate_out"; // total number of timesteps diff --git a/tests/main_unit_test_bmi.cxx b/tests/main_unit_test_bmi.cxx index 84a4c56..c3d29d1 100644 --- a/tests/main_unit_test_bmi.cxx +++ b/tests/main_unit_test_bmi.cxx @@ -46,7 +46,7 @@ int main(int argc, char *argv[]) int num_wetting_fronts = 3; // total number of wetting fronts bool test_status = true; // unit test status flag, if test fail the flag turns false int num_input_vars = 3; // total number of bmi input variables - int num_output_vars = 15; // total number of bmi output variables + int num_output_vars = 16; // total number of bmi output variables // ************************************************************************************* // names of the bmi input/output variables and the corresponding sizes, with units of input variables @@ -59,13 +59,13 @@ int main(int argc, char *argv[]) "actual_evapotranspiration", "surface_runoff", "giuh_runoff", "soil_storage", "total_discharge", "infiltration", "percolation", "groundwater_to_stream_recharge", - "mass_balance"}; + "mass_balance", "precipitation_rate_out"}; int nbytes_input[] = {sizeof(double), sizeof(double), sizeof(double)}; int nbytes_output[] = {int(num_wetting_fronts * sizeof(double)), int(num_layers * sizeof(double)), int(num_wetting_fronts * sizeof(double)), sizeof(int), sizeof(double), sizeof(double), sizeof(double), sizeof(double), sizeof(double), sizeof(double), - sizeof(double), sizeof(double), sizeof(double), sizeof(double), sizeof(double)}; + sizeof(double), sizeof(double), sizeof(double), sizeof(double), sizeof(double), sizeof(double)}; std::vector bmi_units = {"mm h^-1", "mm h^-1", "K"}; // ************************************************************************************* From 1a659e98b0eee7630afa592a8bbc6714864ee971 Mon Sep 17 00:00:00 2001 From: "Carolyn.Maynard" Date: Mon, 9 Mar 2026 13:25:22 -0700 Subject: [PATCH 4/6] Updates to use the nwm-ewts library --- CMakeLists.txt | 25 ++- include/Logger.hpp | 71 +------- src/Logger.cpp | 424 --------------------------------------------- src/bmi_lgar.cxx | 11 +- 4 files changed, 38 insertions(+), 493 deletions(-) delete mode 100644 src/Logger.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 634ee80..3ec2496 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,10 @@ +# +# LASAM (aka lgar-c, CASAM) CMakeLists.txt +# + cmake_minimum_required(VERSION 3.10) -set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_C_COMPILER $ENV{CC}) @@ -69,13 +73,13 @@ ENDIF((NOT ${NGEN}) AND (NOT ${STANDALONE})) if(STANDALONE) add_executable(${exe_name} ./src/bmi_main_lgar.cxx ./src/bmi_lgar.cxx ./src/lgar.cxx ./src/soil_funcs.cxx - ./src/linked_list.cxx ./src/mem_funcs.cxx ./src/util_funcs.cxx ./src/aet.cxx ./src/Logger.cpp ./include/Logger.hpp + ./src/linked_list.cxx ./src/mem_funcs.cxx ./src/util_funcs.cxx ./src/aet.cxx ./giuh/giuh.h ./giuh/giuh.c) target_link_libraries(${exe_name} PRIVATE m) target_link_libraries(${exe_name} PRIVATE Boost::serialization) elseif(UNITTEST) add_executable(${exe_name} ./tests/main_unit_test_bmi.cxx ./src/bmi_lgar.cxx ./src/lgar.cxx ./src/soil_funcs.cxx - ./src/linked_list.cxx ./src/mem_funcs.cxx ./src/util_funcs.cxx ./src/aet.cxx ./src/Logger.cpp ./include/Logger.hpp ./giuh/giuh.h + ./src/linked_list.cxx ./src/mem_funcs.cxx ./src/util_funcs.cxx ./src/aet.cxx ./giuh/giuh.h ./giuh/giuh.c) target_link_libraries(${exe_name} PRIVATE m) target_link_libraries(${exe_name} PRIVATE Boost::serialization) @@ -91,10 +95,10 @@ add_compile_definitions(BMI_ACTIVE) if(WIN32) add_library(lasambmi SHARED src/bmi_lgar.cxx src/lgar.cxx ./src/soil_funcs.cxx ./src/linked_list.cxx ./src/mem_funcs.cxx - ./src/util_funcs.cxx ./src/aet.cxx ./src/Logger.cpp ./include/Logger.hpp ./giuh/giuh.c include/all.hxx ./giuh/giuh.h) + ./src/util_funcs.cxx ./src/aet.cxx ./giuh/giuh.c include/all.hxx ./giuh/giuh.h) else() add_library(lasambmi SHARED src/bmi_lgar.cxx src/lgar.cxx ./src/soil_funcs.cxx ./src/linked_list.cxx ./src/mem_funcs.cxx - ./src/util_funcs.cxx ./src/aet.cxx ./src/Logger.cpp ./include/Logger.hpp ./giuh/giuh.c include/all.hxx ./giuh/giuh.h) + ./src/util_funcs.cxx ./src/aet.cxx ./giuh/giuh.c include/all.hxx ./giuh/giuh.h) endif() @@ -106,6 +110,17 @@ set_target_properties(lasambmi PROPERTIES PUBLIC_HEADER ./include/bmi_lgar.hxx) target_link_libraries(lasambmi PRIVATE Boost::serialization) +# --- EWTS (installed from nwm-ewts) --- +find_package(ewts CONFIG REQUIRED) + +# Always use EWTS runtime logger for CPP +target_link_libraries(lasambmi PRIVATE ewts::ewts_cpp) + +# Built with ngen bridge +target_link_libraries(lasambmi PRIVATE + "-Wl,--no-as-needed" ewts::ewts_ngen_bridge "-Wl,--as-needed") +target_compile_definitions(lasambmi PRIVATE EWTS_HAVE_NGEN_BRIDGE) + include(GNUInstallDirs) install(TARGETS lasambmi diff --git a/include/Logger.hpp b/include/Logger.hpp index c884c3e..93c9b87 100644 --- a/include/Logger.hpp +++ b/include/Logger.hpp @@ -1,66 +1,13 @@ -#ifndef LOGGER_HPP -#define LOGGER_HPP +#ifndef LASAM_LOGGER_HPP +#define LASAM_LOGGER_HPP -#include -#include -#include -#include -#include -#include -#include +#include "ewts/logger.hpp" +#include "ewts/module_constants.hpp" -#define LOG Logger::Log +using ewts::EwtsInit; +using ewts::LogLevel; -enum class LogLevel { - NONE = 0, - DEBUG = 1, - INFO = 2, - WARNING = 3, - SEVERE = 4, - FATAL = 5, -}; +// Provide the constant in the global namespace: +inline constexpr const char* EWTS_ID_LASAM = ewts::modules::EWTS_ID_LASAM; -/** - * Logger Class Used to Output Details of Current Application Flow - All methods and variables are static so instantiating an object is unnecessary. - */ -class Logger { - public: - // Methods - static void Log(LogLevel messageLevel, const char* message, ...); - static void Log(LogLevel messageLevel, std::string message); - static void Log(std::string message, LogLevel messageLevel = LogLevel::INFO); - static bool IsLoggingEnabled(void); - static LogLevel GetLogLevel(void); - - private: - // Methods - static std::string CreateDateString(void); - static std::string CreateTimestamp(bool appendMS = true, bool iso = true); - static bool CreateDirectory(const std::string& path); - static std::string ConvertLogLevelToString(LogLevel level); - static LogLevel ConvertStringToLogLevel(const std::string& logLevel); - static bool DirectoryExists(const std::string& path); - static std::string GetLogFilePath(void); - static bool LogFileReady(bool appendMode=true); - static void SetLogFilePath(void); - static void SetLoggingFlag(void); - static void SetLogLevel(void); - static void SetLogModuleName(void); - static void SetLogPreferences(void); - static std::string ToUpper(const std::string& input); - static std::string TrimString(const std::string& str); - - // Variables - // Declaring these static so they exist in the class without needing to instantiate it. - static bool loggerInitialized; - static std::string logFilePath; - static bool loggingEnabled; - static std::fstream logFile; - static LogLevel logLevel; - static std::string moduleName; - - static std::shared_ptr loggerInstance; -}; - -#endif +#endif /* LASAM_LOGGER_HPP */ diff --git a/src/Logger.cpp b/src/Logger.cpp deleted file mode 100644 index ee4a281..0000000 --- a/src/Logger.cpp +++ /dev/null @@ -1,424 +0,0 @@ -#include "../include/Logger.hpp" -#include -#include -#include -#include // For getenv() -#include -#include // For file handling -#include -#include -#include -#include // For std::string -#include -#include -#include -#include -#include -#include -#include - -const std::string MODULE_NAME = "LASAM"; -const std::string LOG_DIR_NGENCERF = "/ngencerf/data"; // ngenCERF log directory string if environement var empty. -const std::string LOG_DIR_DEFAULT = "run-logs"; // Default parent log directory string if env var empty & ngencerf dosn't exist -const std::string LOG_FILE_EXT = "log"; // Log file name extension -const std::string DS = "/"; // Directory separator -const unsigned int LOG_MODULE_NAME_LEN = 8; // Width of module name for log entries - -const std::string EV_EWTS_LOGGING = "NGEN_EWTS_LOGGING"; // Enable/disable of Error Warning and Trapping System -const std::string EV_NGEN_LOGFILEPATH = "NGEN_LOG_FILE_PATH"; // ngen log file -const std::string EV_MODULE_LOGLEVEL = "LASAM_LOGLEVEL"; // This modules log level -const std::string EV_MODULE_LOGFILEPATH = "LASAM_LOGFILEPATH"; // This modules log full log filename - -bool Logger::loggerInitialized = false; -std::string Logger::logFilePath = ""; -bool Logger::loggingEnabled = true; -std::fstream Logger::logFile; -LogLevel Logger::logLevel = LogLevel::INFO; // Default Log Level -std::string Logger::moduleName = ""; - -std::shared_ptr Logger::loggerInstance; - -using namespace std; - -// String to LogLevel map -static const std::unordered_map logLevelMap = { - {"NONE", LogLevel::NONE }, - {"0", LogLevel::NONE }, - {"DEBUG", LogLevel::DEBUG}, - {"1", LogLevel::DEBUG}, - {"INFO", LogLevel::INFO }, - {"2", LogLevel::INFO }, - {"WARNING", LogLevel::WARNING}, - {"3", LogLevel::WARNING}, - {"SEVERE", LogLevel::SEVERE }, - {"4", LogLevel::SEVERE }, - {"FATAL", LogLevel::FATAL}, - {"5", LogLevel::FATAL}, -}; - -// Reverse map: LogLevel to String -static const std::unordered_map logLevelToStringMap = { - {LogLevel::NONE, "NONE "}, - {LogLevel::DEBUG, "DEBUG "}, - {LogLevel::INFO, "INFO "}, - {LogLevel::WARNING, "WARNING"}, - {LogLevel::SEVERE, "SEVERE "}, - {LogLevel::FATAL, "FATAL "}, -}; - -std::string Logger::ToUpper(const std::string& input) { - std::string result = input; - std::transform(result.begin(), result.end(), result.begin(), - [](unsigned char c){ return std::toupper(c); }); - return result; -} - -// Function to trim leading and trailing spaces -std::string Logger::TrimString(const std::string& str) { - // Trim leading spaces - size_t first = str.find_first_not_of(" \t\n\r\f\v"); - if (first == std::string::npos) { - return ""; // No non-whitespace characters - } - - // Trim trailing spaces - size_t last = str.find_last_not_of(" \t\n\r\f\v"); - - // Return the trimmed string - return str.substr(first, last - first + 1); -} - -LogLevel Logger::ConvertStringToLogLevel(const std::string& levelStr) { - std::string level = TrimString(levelStr); - if (!level.empty()) { - // Convert string to LogLevel (supports both names and numbers) - auto it = logLevelMap.find(level); - if (it != logLevelMap.end()) { - return it->second; // Found valid named or numeric log level - } - - // Try parsing as an integer (for cases where an invalid numeric value is given) - try { - int levelNum = std::stoi(level); - if (levelNum >= 0 && levelNum <= 5) { - return static_cast(levelNum); - } - } catch (...) { - // Ignore errors (e.g., if std::stoi fails for non-numeric input) - } - } - return LogLevel::NONE; -} - -/** - * Convert LogLevel to String Representation of Log Level - * @param logLevel : LogLevel - * @return String log level - */ -std::string Logger::ConvertLogLevelToString(LogLevel level) { - auto it = logLevelToStringMap.find(level); - if (it != logLevelToStringMap.end()) { - return it->second; // Found valid named or numeric log level - } - return "NONE"; -} - -bool Logger::DirectoryExists(const std::string& path) { - struct stat info; - if (stat(path.c_str(), &info) != 0) { - return false; // Cannot access path - } - return (info.st_mode & S_IFDIR) != 0; -} - -/** - * Create the directory checking both the call - * to execute the command and the result of the command - */ -bool Logger::CreateDirectory(const std::string& path) { - - if (!DirectoryExists(path)) { - std::string mkdir_cmd = "mkdir -p " + path; - int status = system(mkdir_cmd.c_str()); - - if (status == -1) { - std::cerr << "system() failed to run mkdir.\n"; - return false; - } else if (WIFEXITED(status)) { - int exitCode = WEXITSTATUS(status); - if (exitCode != 0) { - std::cerr << "mkdir command failed with exit code: " << exitCode << "\n"; - return false; - } - } else { - std::cerr << "mkdir terminated abnormally.\n"; - return false; - } - } - return true; -} - -/** - * Open log file and return open status. If already open, - * ensure the write pointer is at the end of the file. - * - * return bool true if open and good, false otherwise - */ -bool Logger::LogFileReady(bool appendMode) { - - if (logFile.is_open() && logFile.good()) { - logFile.seekp(0, std::ios::end); // Ensure write pointer is at the actual file end - return true; - } else { - // Attempt to open - if (!logFilePath.empty()) { - logFile.open( - logFilePath, - ios::out | ((appendMode) ? ios::app : ios::trunc) - ); // This will silently fail if already open. - if (logFile.good()) { - return true; - } - } - } - return false; -} - -/** - * Set the log file path name using the following pattern - * - Use the module log file if available (unset when first run by ngen), otherwise - * - Use ngen log file if available, otherwise - * - Use /ngencerf/data/run-logs//_ if available, otherwise - * - Use ~/run-logs//_ - * - Onced opened, save the full log path to the modules log environment variable so - * it is only opened once for each ngen run (vs for each catchment) - */ -void Logger::SetLogFilePath(void) { - // Use module logfile path environment variable if it exists - logFilePath = ""; - bool appendEntries = true; - bool mdduleLogEnvExists = false; - const char* envVar = std::getenv(EV_MODULE_LOGFILEPATH.c_str()); // Set once module has successfully opened a log file - if (envVar != nullptr && envVar[0] != '\0') { - logFilePath = envVar; - mdduleLogEnvExists = true; - } else { - envVar = std::getenv(EV_NGEN_LOGFILEPATH.c_str()); // Currently set by ngen-cal but envision set for WCOSS at some point - if (envVar != nullptr && envVar[0] != '\0') { - logFilePath = envVar; - } else { - appendEntries = false; - // Get parent log directory - std::string logFileDir; - if (DirectoryExists(LOG_DIR_NGENCERF)) { - logFileDir = LOG_DIR_NGENCERF + DS + LOG_DIR_DEFAULT; - } else { - logFileDir = "~" + DS + LOG_DIR_DEFAULT; - } - - // Ensure parent log direcotry exists - if (CreateDirectory(logFileDir)) { - // Get full log directory path - const char* envUsername = std::getenv("USER"); - if (envUsername) { - std::string username = envUsername; - logFileDir = logFileDir + DS + username; - } else { - logFileDir = logFileDir + DS + CreateDateString(); - } - // Set the full path if log directory exists/created - if (CreateDirectory(logFileDir)) - logFilePath = logFileDir + DS + MODULE_NAME + "_" + CreateTimestamp(false, false) + - "." + LOG_FILE_EXT; - } - } - } - - // Open log file. If path not found, it will try an alternate file. If neither - // works false returned and logs will be written to stdout. - if (LogFileReady(appendEntries)) { - if (!mdduleLogEnvExists) { - setenv(EV_MODULE_LOGFILEPATH.c_str(), logFilePath.c_str(), 1); - std::cout << "Module " << MODULE_NAME << " Log File: " << logFilePath << std::endl; - LogLevel saveLevel = logLevel; - logLevel = LogLevel::INFO; // Ensure this INFO message is always logged - Log(logLevel, "Logging started. Log File Path: %s\n", logFilePath.c_str()); - logLevel = saveLevel; - } - } else { - std::cout << "Unable to open log file "; - if (!logFilePath.empty()) { - std::cout << logFilePath; - std::cout << " (Perhaps check permissions)" << std::endl; - } - std::cout << "Log entries will be writen to stdout" << std::endl; - } -} - -void Logger::SetLogLevel(void) { - // Set the logger log level if environment var not found - const char* envLogLevel = std::getenv(EV_MODULE_LOGLEVEL.c_str()); - if (envLogLevel != nullptr && envLogLevel[0] != '\0') { - logLevel = ConvertStringToLogLevel(envLogLevel); - } - std::string llMsg = "Log level set to " + ConvertLogLevelToString(logLevel) + "\n"; - cout << MODULE_NAME << " " << llMsg; - LogLevel saveLevel = logLevel; - logLevel = LogLevel::INFO; // Ensure this INFO message is always logged - Log(logLevel, llMsg); - logLevel = saveLevel; - -} - -void Logger::SetLoggingFlag(void) { - const char* envVar = std::getenv(EV_EWTS_LOGGING.c_str()); // Set once module has successfully opened a log file - if (envVar != nullptr && envVar[0] != '\0') { - std::string logState = ToUpper(TrimString(envVar)); - loggingEnabled = (logState == "ENABLED")?true:false; - } - std::cout << MODULE_NAME << " Logging " << ((loggingEnabled)?"ENABLED":"DISABLED") << std::endl; -} - -void Logger::SetLogModuleName(void) { - // Make sure the module name used for logging entries is all uppercase and 8 characters wide. - moduleName = MODULE_NAME; - std::string upperName = moduleName.substr(0, LOG_MODULE_NAME_LEN); // Truncate to LOG_MODULE_NAME_LEN chars max - std::transform(upperName.begin(), upperName.end(), upperName.begin(), ::toupper); - - std::ostringstream oss; - oss << std::left << std::setw(8) << std::setfill(' ') << upperName; - moduleName = oss.str(); -} - -/** - * Configure Logger Preferences - * @param logFile - * @param level: LogLevel::INFO by Default - * @return void - */ -void Logger::SetLogPreferences(void) { - - if (!loggerInitialized) { - loggerInitialized = true; // Only call this once - - SetLoggingFlag(); - if (loggingEnabled) { - SetLogModuleName(); - SetLogFilePath(); - SetLogLevel(); - } - } -} - -void Logger::Log(LogLevel messageLevel, const char* message, ...) { - va_list args; - va_start(args, message); - - // Make a copy to calculate required size - va_list args_copy; - va_copy(args_copy, args); - int requiredLen = vsnprintf(nullptr, 0, message, args_copy); - va_end(args_copy); - - if (requiredLen > 0) { - std::vector buffer(requiredLen + 1); // +1 for null terminator - vsnprintf(buffer.data(), buffer.size(), message, args); - - va_end(args); - - Log(std::string(buffer.data()), messageLevel); - } else { - va_end(args); // still need to clean up - } -} - -/** - * Log given message with defined parameters and generate message to pass on Console or File - * @param message: Log Message - * @param messageLevel: Log Level, LogLevel::INFO by default - */ -void Logger::Log(LogLevel messageLevel, std::string message) { - Log(message, messageLevel); -} -void Logger::Log(std::string message, LogLevel messageLevel) { - - if (!loggerInitialized) SetLogPreferences(); // Cover case where Log is called before setup done - - // don't log if messageLevel < logLevel - if (loggingEnabled && (messageLevel >= logLevel)) { - std::string logType = ConvertLogLevelToString(messageLevel); - std::string logPrefix = CreateTimestamp() + " " + moduleName + " " + logType; - - // Log message, creating individual entries for a multi-line message - std::istringstream logMsg(message); - std::string line; - if (LogFileReady()) { - while (std::getline(logMsg, line)) { - logFile << logPrefix + " " + line << std::endl; - } - logFile.flush(); - } else { - // Log file not found. Write to stdout. - while (std::getline(logMsg, line)) { - cout << logPrefix + " " + line << std::endl; - } - cout << std::flush; - } - } -} - -std::string Logger::CreateDateString(void) { - std::time_t tt = std::time(0); - std::tm* timeinfo = std::gmtime(&tt); // Use std::localtime(&tt) if you want local time - - char buffer[11]; // Enough for "YYYY-MM-DD" + null terminator - std::strftime(buffer, sizeof(buffer), "%F", timeinfo); // %F == %Y-%m-%d - - std::stringstream ss; - ss << buffer; - - return ss.str(); -} - -std::string Logger::CreateTimestamp(bool appendMS, bool iso) { - using namespace std::chrono; - - // Get current time point - auto now = system_clock::now(); - auto now_time_t = system_clock::to_time_t(now); - - // Get milliseconds - auto ms = duration_cast(now.time_since_epoch()) % 1000; - - // Convert to UTC time - std::tm utc_tm; - gmtime_r(&now_time_t, &utc_tm); - - // Format date/time with strftime - char buffer[32]; - if (iso) { - std::strftime(buffer, sizeof(buffer), "%Y-%m-%dT%H:%M:%S", &utc_tm); - } else { - std::strftime(buffer, sizeof(buffer), "%Y%m%dT%H%M%S", &utc_tm); - } - - if (appendMS) { - // Combine with milliseconds - std::ostringstream oss; - oss << buffer << '.' << std::setw(3) << std::setfill('0') << ms.count(); - return oss.str(); - } - return std::string(buffer); -} - -std::string Logger::GetLogFilePath() { - return logFilePath; -} - -bool Logger::IsLoggingEnabled(void) { - return loggingEnabled; -} - -LogLevel Logger::GetLogLevel(void) { - return logLevel; -} diff --git a/src/bmi_lgar.cxx b/src/bmi_lgar.cxx index 97219d3..1410b9c 100644 --- a/src/bmi_lgar.cxx +++ b/src/bmi_lgar.cxx @@ -37,6 +37,13 @@ BmiLGAR::~BmiLGAR(){ void BmiLGAR:: Initialize (std::string config_file) { + // Initialize the Error, Warning and Trapping System +#ifdef EWTS_HAVE_NGEN_BRIDGE + EwtsInit(EWTS_ID_LASAM, true); +#else + EwtsInit(EWTS_ID_LASAM, false); +#endif + LOG("Inside BmiLGAR::Initialize \n", LogLevel::INFO); if (config_file.compare("") != 0 ) { this->state = new model_state; @@ -1452,7 +1459,7 @@ void BmiLGAR::new_serialized() { uint64_t serialized_size = this->m_serialized_length - sizeof(uint64_t); memcpy(this->m_serialized.data(), &serialized_size, sizeof(uint64_t)); } catch (const std::exception &e) { - Logger::Log(LogLevel::SEVERE, "Serializing LASAM encountered an error: %s", e.what()); + LOG(LogLevel::SEVERE, "Serializing LASAM encountered an error: %s", e.what()); this->free_serialized(); throw; } @@ -1468,7 +1475,7 @@ void BmiLGAR::load_serialized(char* data) { try { archive >> (*this); } catch (const std::exception &e) { - Logger::Log(LogLevel::SEVERE, "Deserializing LASAM encountered an error: %s", e.what()); + LOG(LogLevel::SEVERE, "Deserializing LASAM encountered an error: %s", e.what()); throw; } this->free_serialized(); From 3b632107d3b8a3cd3c9c94d78e3247942aed7a76 Mon Sep 17 00:00:00 2001 From: mkarim-rtx <149624049+mkarim-rtx@users.noreply.github.com> Date: Tue, 21 Apr 2026 09:23:01 -0700 Subject: [PATCH 5/6] check for soil_type value (#14) * check for soil_type value * added logging before throwing runtime error * loglevel SEVERE -> FATAL --- src/lgar.cxx | 41 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/src/lgar.cxx b/src/lgar.cxx index bb681b6..9af223d 100755 --- a/src/lgar.cxx +++ b/src/lgar.cxx @@ -789,10 +789,40 @@ extern void InitializeWettingFronts(int num_layers, double initial_psi_cm, int * double Ksat_cm_per_h; struct wetting_front *current; - for(int layer=1;layer<=num_layers;layer++) { + if (layer_soil_type == NULL) { + LOG(LogLevel::FATAL, "InitializeWettingFronts: layer_soil_type is NULL"); + throw std::runtime_error("InitializeWettingFronts: layer_soil_type is NULL"); + } + + if (cum_layer_thickness_cm == NULL) { + LOG(LogLevel::FATAL, "InitializeWettingFronts: cum_layer_thickness_cm is NULL"); + throw std::runtime_error("InitializeWettingFronts: cum_layer_thickness_cm is NULL"); + } + + if (frozen_factor == NULL) { + LOG(LogLevel::FATAL, "InitializeWettingFronts: frozen_factor is NULL"); + throw std::runtime_error("InitializeWettingFronts: frozen_factor is NULL"); + } + + if (soil_properties == NULL) { + LOG(LogLevel::FATAL, "InitializeWettingFronts: soil_properties is NULL"); + throw std::runtime_error("InitializeWettingFronts: soil_properties is NULL"); + } + + for(int layer=1; layer<=num_layers; layer++) { front++; soil = layer_soil_type[layer]; + + if (soil <= 0 || soil > MAX_NUM_SOIL_TYPES) { + std::stringstream error_message; + error_message << "InitializeWettingFronts found invalid soil type index " << soil + << " at layer " << layer + << ". Valid range is [1," << MAX_NUM_SOIL_TYPES << "]."; + LOG(LogLevel::FATAL, error_message.str()); + throw std::runtime_error(error_message.str()); + } + theta_init = calc_theta_from_h(initial_psi_cm,soil_properties[soil].vg_alpha_per_cm, soil_properties[soil].vg_m,soil_properties[soil].vg_n, soil_properties[soil].theta_e,soil_properties[soil].theta_r); @@ -809,14 +839,19 @@ extern void InitializeWettingFronts(int num_layers, double initial_psi_cm, int * current = listInsertFront(cum_layer_thickness_cm[layer],theta_init,front,layer,bottom_flag, head); + if (current == NULL) { + std::stringstream error_message; + error_message << "InitializeWettingFronts: listInsertFront returned NULL at layer " << layer; + LOG(LogLevel::FATAL, error_message.str()); + throw std::runtime_error(error_message.str()); + } + current->psi_cm = initial_psi_cm; Se = calc_Se_from_theta(current->theta,soil_properties[soil].theta_e,soil_properties[soil].theta_r); Ksat_cm_per_h = frozen_factor[layer] * soil_properties[soil].Ksat_cm_per_h; current->K_cm_per_h = calc_K_from_Se(Se, Ksat_cm_per_h , soil_properties[soil].vg_m); // cm/s - } - } // ################################################################################## From 23d717655ac4efbb772cb7e71bbeedfde63b3919 Mon Sep 17 00:00:00 2001 From: mkarim-rtx <149624049+mkarim-rtx@users.noreply.github.com> Date: Tue, 5 May 2026 16:37:08 -0700 Subject: [PATCH 6/6] updated groundwater_to_stream_recharge unit (#15) * updated groundwater_to_stream_recharge unit * added new output variable groundwater_to_stream_recharge_m3_per_s * fixed missed updates * fixed missed updates * Initialize bmi_unit_conv.catchment_area_m2 = 0.0 --- include/bmi_lgar.hxx | 5 ++++- src/bmi_lgar.cxx | 23 ++++++++++++++++++++++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/include/bmi_lgar.hxx b/include/bmi_lgar.hxx index cf40d6c..be549ee 100644 --- a/include/bmi_lgar.hxx +++ b/include/bmi_lgar.hxx @@ -62,6 +62,7 @@ public: this->output_var_names[14] = "mass_balance"; this->output_var_names[15] = NWM_PONDED_DEPTH_OUT_VAR; this->output_var_names[16] = "precipitation_rate_out"; + this->output_var_names[17] = "groundwater_to_stream_recharge_m3_per_s"; /* this->output_var_names[13] = "cum_precipitation"; @@ -146,7 +147,7 @@ private: void realloc_soil(); struct model_state* state; static const int input_var_name_count = 3; - static const int output_var_name_count = 17; + static const int output_var_name_count = 18; static const int calib_var_name_count = 7; std::string input_var_names[input_var_name_count]; @@ -175,6 +176,8 @@ private: double volQ_gw_timestep_m; double volPET_timestep_m; double mass_balance_m; + double volQ_gw_timestep_m3_per_s; + double catchment_area_m2; }; struct bmi_unit_conversion bmi_unit_conv; diff --git a/src/bmi_lgar.cxx b/src/bmi_lgar.cxx index 1410b9c..96ec9a6 100644 --- a/src/bmi_lgar.cxx +++ b/src/bmi_lgar.cxx @@ -68,6 +68,9 @@ Initialize (std::string config_file) giuh_runoff_queue[i] = 0.0; } + bmi_unit_conv.volQ_gw_timestep_m3_per_s = 0.0; + bmi_unit_conv.catchment_area_m2 = 0.0; + } /** @@ -126,6 +129,7 @@ Update() bmi_unit_conv.volrunoff_timestep_m = state->lgar_bmi_input_params->precipitation_mm_per_h * mm_to_m; bmi_unit_conv.volQ_timestep_m = state->lgar_bmi_input_params->precipitation_mm_per_h * mm_to_m; bmi_unit_conv.volQ_gw_timestep_m = 0.0; + bmi_unit_conv.volQ_gw_timestep_m3_per_s = 0.0; bmi_unit_conv.volPET_timestep_m = 0.0; bmi_unit_conv.volrunoff_giuh_timestep_m = 0.0; bmi_unit_conv.volrunoff_giuh_ponded_m = 0.0; @@ -603,6 +607,15 @@ Update() bmi_unit_conv.volrunoff_timestep_m = volrunoff_timestep_cm * state->units.cm_to_m; bmi_unit_conv.volQ_timestep_m = volQ_timestep_cm * state->units.cm_to_m; bmi_unit_conv.volQ_gw_timestep_m = volQ_gw_timestep_cm * state->units.cm_to_m; + if (bmi_unit_conv.catchment_area_m2 > 0.0 && this->GetTimeStep() > 0.0) { + bmi_unit_conv.volQ_gw_timestep_m3_per_s = + (bmi_unit_conv.volQ_gw_timestep_m * bmi_unit_conv.catchment_area_m2) / + this->GetTimeStep(); + } + else { + bmi_unit_conv.volQ_gw_timestep_m3_per_s = 0.0; + } + bmi_unit_conv.volPET_timestep_m = PET_timestep_cm * state->units.cm_to_m; bmi_unit_conv.volrunoff_giuh_timestep_m = volrunoff_giuh_timestep_cm * state->units.cm_to_m; bmi_unit_conv.volrunoff_giuh_ponded_m = volrunoff_giuh_ponded_cm * state->units.cm_to_m; @@ -779,6 +792,7 @@ GetVarGrid(std::string name) || name.compare("infiltration") == 0 || name.compare("percolation") == 0 || name.compare("groundwater_to_stream_recharge") == 0 + || name.compare("groundwater_to_stream_recharge_m3_per_s") == 0 || name.compare("mass_balance") == 0 || name.compare(NWM_PONDED_DEPTH_OUT_VAR) == 0 || name.compare("reset_time") == 0 @@ -870,6 +884,10 @@ GetVarUnits(std::string name) return "m"; else if (name.compare("mass_balance") == 0 || name.compare("groundwater_to_stream_recharge") == 0) return "m"; + else if (name.compare("groundwater_to_stream_recharge_m3_per_s") == 0) + return "m3 s-1"; + else if (name.compare("mass_balance") == 0) + return "m"; else if (name.compare("soil_moisture_wetting_fronts") == 0) // array of doubles return "none"; else if (name.compare("soil_depth_layers") == 0 || name.compare("soil_depth_wetting_fronts") == 0) // array of doubles @@ -907,7 +925,8 @@ GetVarLocation(std::string name) || name.compare("soil_storage") == 0 || name.compare(NWM_PONDED_DEPTH_OUT_VAR)) // double return "node"; else if (name.compare("total_discharge") == 0 || name.compare("infiltration") == 0 - || name.compare("percolation") == 0 || name.compare("groundwater_to_stream_recharge") == 0) // double + || name.compare("percolation") == 0 || name.compare("groundwater_to_stream_recharge") == 0 + || name.compare("groundwater_to_stream_recharge_m3_per_s") == 0) //double return "node"; else if (name.compare("soil_moisture_wetting_fronts") == 0) // array of doubles return "node"; @@ -1021,6 +1040,8 @@ GetValuePtr (std::string name) return (void*)(&bmi_unit_conv.volrech_timestep_m); else if (name.compare("groundwater_to_stream_recharge") == 0) return (void*)(&bmi_unit_conv.volQ_gw_timestep_m); + else if (name.compare("groundwater_to_stream_recharge_m3_per_s") == 0) + return (void*)(&bmi_unit_conv.volQ_gw_timestep_m3_per_s); else if (name.compare("mass_balance") == 0) return (void*)(&bmi_unit_conv.mass_balance_m); else if (name.compare(NWM_PONDED_DEPTH_OUT_VAR) == 0)