From 54766139f6b9c543faf348dc28c214c1807354ee Mon Sep 17 00:00:00 2001 From: mkarim-rtx <149624049+mkarim-rtx@users.noreply.github.com> Date: Wed, 11 Mar 2026 08:29:50 -0700 Subject: [PATCH 1/5] chnaged the atmosphere_water__liquid_equivalent_precipitation_rate_out value to match with atmosphere_water__liquid_equivalent_precipitation_rate (#6) --- src/bmi_topmodel.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bmi_topmodel.c b/src/bmi_topmodel.c index 54f3256..6e1aa22 100755 --- a/src/bmi_topmodel.c +++ b/src/bmi_topmodel.c @@ -810,8 +810,8 @@ static int Get_value_ptr(Bmi *self, const char *name, void **dest) { if (strcmp(name, "atmosphere_water__liquid_equivalent_precipitation_rate_out") == 0) { topmodel_model *topmodel; topmodel = (topmodel_model *)self->data; - *dest = (void *)&topmodel->p; - //*dest = (void*)&topmodel->rain[1]; Note: these are the same ==, either would work + //*dest = (void *)&topmodel->p;//Note: these are the same ==, either would work + *dest = (void*)&topmodel->rain[1]; return BMI_SUCCESS; // ep } From 6259302492941fbae086a685a80c5b4632cf822d Mon Sep 17 00:00:00 2001 From: Ian Todd <48330440+idtodd@users.noreply.github.com> Date: Fri, 13 Mar 2026 09:44:06 -0400 Subject: [PATCH 2/5] Reset Time Signal (NGWPC-9621) (#5) --- include/bmi_serialization.h | 2 +- include/topmodel.h | 20 +++++----- include/vecbuf.hpp | 12 +++++- src/bmi_serialization.cpp | 51 +++++++++++++++++++++---- src/bmi_topmodel.c | 33 ++++++++++------ src/topmodel.c | 76 ++++++++++++++++++------------------- 6 files changed, 124 insertions(+), 70 deletions(-) diff --git a/include/bmi_serialization.h b/include/bmi_serialization.h index 2ceb763..b1e776c 100644 --- a/include/bmi_serialization.h +++ b/include/bmi_serialization.h @@ -8,7 +8,7 @@ extern "C" { #include "bmi.h" const int serialize_topmodel(Bmi* bmi); -const int deserialize_topmodel(Bmi* bmi, const char* buffer); +const int deserialize_topmodel(Bmi* bmi, char* buffer); #ifdef __cplusplus } diff --git a/include/topmodel.h b/include/topmodel.h index b6333da..6fe0306 100755 --- a/include/topmodel.h +++ b/include/topmodel.h @@ -26,28 +26,28 @@ /*** Function/subroutine prototypes ***/ extern void convert_dist_to_histords(const double * const dist_from_outlet, const int num_channels, - const double * const chv, const double * const rv, const double dt, double* const tch); + const double chv, const double rv, const double dt, double* const tch); extern void calc_time_delay_histogram(int num_channels, double area, - double* tch, double *cum_dist_area_with_dist, + const double *tch, const double *cum_dist_area_with_dist, int *num_time_delay_histo_ords, int *num_delay, double **time_delay_histogram); extern void init_water_balance( - int num_topodex_values, double dt, double *sr0, - double *szm, double *Q0, double *t0, double tl, + int num_topodex_values, double dt, double sr0, + double szm, double Q0, double t0, double tl, double **stor_unsat_zone, double *szq, double **deficit_local, double **deficit_root_zone, double *sbar, double *bal); -extern void init_discharge_array(int stand_alone, int *num_delay, double *Q0, double area, - int *num_time_delay_histo_ords, double **time_delay_histogram, +extern void init_discharge_array(int stand_alone, int num_delay, double Q0, double area, + int num_time_delay_histo_ords, double **time_delay_histogram, double **Q); extern int init(FILE *in_param_fptr, FILE *output_fptr, char *subcat, int stand_alone, int num_channels, int num_topodex_values, int yes_print_output, double area, double **time_delay_histogram, double *cum_dist_area_with_dist, double dt, - double tl, double *dist_from_outlet, + double tl, const double *dist_from_outlet, int *num_time_delay_histo_ords,int *num_delay, double *szm, double *t0, double *chv, double *rv, double *td, double *srmax, double *Q0,double *sr0, int *infex, double *xk0, double *hf, double *dth, @@ -61,11 +61,11 @@ extern int inputs(FILE *input_fptr, int *nstep, double *dt, double **rain, extern void topmod(FILE *output_fptr, int nstep, int num_topodex_values, int yes_print_output,int infex, double dt, double szm, double *stor_unsat_zone, double *deficit_root_zone, - double *deficit_local, double *pe, double *rain,double xk0,double hf, - double *dist_area_lnaotb, double tl, double *lnaotb, double td, + double *deficit_local, const double *pe, const double *rain,double xk0,double hf, + const double *dist_area_lnaotb, double tl, const double *lnaotb, double td, double srmax, double *contrib_area, double szq, double *Qout, int num_time_delay_histo_ords,double *Q, - double *time_delay_histogram,char *subcat,double *bal, + const double *time_delay_histogram, double *sbar,int num_delay, int current_time_step, int stand_alone, double *sump, double *sumae, double *sumq, double *sumrz, double *sumuz, double *quz, double *qb, double *qof, double *p, double *ep); 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_serialization.cpp b/src/bmi_serialization.cpp index 03a3448..4daa17d 100644 --- a/src/bmi_serialization.cpp +++ b/src/bmi_serialization.cpp @@ -32,6 +32,12 @@ class TopmodelSerializer { template void TopmodelSerializer::serialize(Archive& ar, const unsigned int version) { topmodel_model* model = this->model; + if (model->stand_alone == TRUE) { + // the number of timesteps makes hindcasting nigh imposible when stand alone + auto error = "Topmodel serialization is not currently implemented when running stand alone."; + Log(SEVERE, error); + throw std::runtime_error(error); + } ar & model->current_time_step; // data summed between runs @@ -62,12 +68,37 @@ void TopmodelSerializer::serialize(Archive& ar, const unsigned int version) { ar & boost::serialization::make_array( model->deficit_local, num_topodex_values ); + + // nsteps will always be 1 for non-stand-alone models ar & boost::serialization::make_array( - model->contrib_area, num_topodex_values + model->contrib_area, model->nstep + 1 ); + + // copy the current sizes to detect changes, then archive the model value + int num_time_delay_histo_ords = model->num_time_delay_histo_ords; + ar & model->num_time_delay_histo_ords; + int num_delay = model->num_delay; + ar & model->num_delay; + size_t num_Q = model->num_delay + model->num_time_delay_histo_ords + 1; + if (Archive::is_loading::value) { + // if loading and array size has changed, reallocate + if (num_time_delay_histo_ords != model->num_time_delay_histo_ords) { + if (model->time_delay_histogram != NULL) + free(model->time_delay_histogram); + model->time_delay_histogram = (double *)malloc( + (model->num_time_delay_histo_ords + 1) * sizeof(double) + ); + } + if (num_delay != model->num_delay || num_time_delay_histo_ords != model->num_time_delay_histo_ords) { + if (model->Q != NULL) + free(model->Q); + model->Q = (double *)malloc(num_Q * sizeof(double)); + } + } ar & boost::serialization::make_array( - model->Q, model->num_time_delay_histo_ords + 1 + model->time_delay_histogram, model->num_time_delay_histo_ords + 1 ); + ar & boost::serialization::make_array(model->Q, num_Q); } @@ -99,15 +130,17 @@ const int serialize_topmodel(Bmi* bmi) { free(model->serialized); } // set size and allocate memory - model->serialized_length = stream.size(); - model->serialized = (char*)malloc(sizeof(char) * model->serialized_length); + uint64_t serialized_size = stream.size(); + model->serialized_length = serialized_size + sizeof(uint64_t); + model->serialized = (char*)malloc(model->serialized_length); // make sure memory could be allocated if (model->serialized == NULL) { model->serialized_length = 0; return BMI_FAILURE; } // copy stream data to new allocation - memcpy(model->serialized, stream.data(), model->serialized_length); + memcpy(model->serialized, &serialized_size, sizeof(uint64_t)); + memcpy(model->serialized + sizeof(uint64_t), stream.data(), serialized_size); return BMI_SUCCESS; } @@ -118,9 +151,13 @@ const int serialize_topmodel(Bmi* bmi) { * @param buffer Start of data that wil be read as previously serialized state * @return int signifiying whether the serialization process completed successfully. */ -const int deserialize_topmodel(Bmi* bmi, const char* buffer) { +const int deserialize_topmodel(Bmi* bmi, char* buffer) { TopmodelSerializer serializer(bmi); - std::istringstream stream(buffer); + // copy size of data out of header + uint64_t size; + memcpy(&size, buffer, sizeof(uint64_t)); + // create stream from data after header + membuf stream(buffer + sizeof(uint64_t), size); boost::archive::binary_iarchive archive(stream); try { archive >> serializer; diff --git a/src/bmi_topmodel.c b/src/bmi_topmodel.c index 6e1aa22..8c1e2da 100755 --- a/src/bmi_topmodel.c +++ b/src/bmi_topmodel.c @@ -481,8 +481,6 @@ static int Update(Bmi *self) { topmodel->num_time_delay_histo_ords, topmodel->Q, topmodel->time_delay_histogram, - topmodel->subcat, - &topmodel->bal, &topmodel->sbar, topmodel->num_delay, topmodel->current_time_step, @@ -652,6 +650,9 @@ static int Get_var_type(Bmi *self, const char *name, char *type) { } else if (strcmp(name, "serialization_free") == 0) { strncpy(type, "int", BMI_MAX_TYPE_NAME); return BMI_SUCCESS; + } else if (strcmp(name, "reset_time") == 0) { + strncpy(type, "double", BMI_MAX_TYPE_NAME); + return BMI_SUCCESS; } // If we get here, it means the variable name wasn't recognized type[0] = '\0'; @@ -776,7 +777,10 @@ static int Get_var_nbytes(Bmi *self, const char *name, int *nbytes) { } // special cases for save state if (item_count < 1) { - if (strcmp(name, "serialization_create") == 0 || strcmp(name, "serialization_size") == 0 || strcmp(name, "serialization_free") == 0) { + if (strcmp(name, "serialization_create") == 0 + || strcmp(name, "serialization_size") == 0 + || strcmp(name, "serialization_free") == 0 + || strcmp(name, "reset_time") == 0) { item_count = 1; } else if (strcmp(name, "serialization_state") == 0) { topmodel_model* model = (topmodel_model*)self->data; @@ -1063,6 +1067,11 @@ static int Set_value(Bmi *self, const char *name, void *array) { } else { return BMI_FAILURE; } + } else if (strcmp(name, "reset_time") == 0) { + topmodel_model* model = (topmodel_model *)self->data; + // current_time_step is mainly used for indexing into config data, so should be safe to reset and nothing else + model->current_time_step = 0; + return BMI_SUCCESS; } if (self->get_value_ptr(self, name, &dest) == BMI_FAILURE) @@ -1168,8 +1177,8 @@ static int Set_value(Bmi *self, const char *name, void *array) { convert_dist_to_histords( topmodel->dist_from_outlet, topmodel->num_channels, - &topmodel->chv, - &topmodel->rv, + topmodel->chv, + topmodel->rv, topmodel->dt, tch ); @@ -1188,10 +1197,10 @@ static int Set_value(Bmi *self, const char *name, void *array) { // Reinitialise discharge array init_discharge_array( topmodel->stand_alone, - &topmodel->num_delay, - &topmodel->Q0, + topmodel->num_delay, + topmodel->Q0, topmodel->area, - &topmodel->num_time_delay_histo_ords, + topmodel->num_time_delay_histo_ords, &topmodel->time_delay_histogram, &topmodel->Q ); @@ -1211,10 +1220,10 @@ static int Set_value(Bmi *self, const char *name, void *array) { init_water_balance( topmodel->num_topodex_values, topmodel->dt, - &topmodel->sr0, - &topmodel->szm, - &topmodel->Q0, - &topmodel->t0, + topmodel->sr0, + topmodel->szm, + topmodel->Q0, + topmodel->t0, topmodel->tl, &topmodel->stor_unsat_zone, &topmodel->szq, diff --git a/src/topmodel.c b/src/topmodel.c index 335931b..4eeea89 100755 --- a/src/topmodel.c +++ b/src/topmodel.c @@ -119,13 +119,13 @@ extern void topmod( double *stor_unsat_zone, double *deficit_root_zone, double *deficit_local, - double *pe, - double *rain, + const double *pe, + const double *rain, double xk0, double hf, - double *dist_area_lnaotb, + const double *dist_area_lnaotb, double tl, - double *lnaotb, + const double *lnaotb, double td, double srmax, double *contrib_area, @@ -133,9 +133,7 @@ extern void topmod( double *Qout, int num_time_delay_histo_ords, double *Q, - double *time_delay_histogram, - char *subcat, - double *bal, + const double *time_delay_histogram, double *sbar, int num_delay, int current_time_step, @@ -689,8 +687,8 @@ extern int tread( extern void convert_dist_to_histords( const double *const dist_from_outlet, const int num_channels, - const double *const chv, - const double *const rv, + const double chv, + const double rv, const double dt, double *const tch ) { @@ -705,8 +703,8 @@ extern void convert_dist_to_histords( double chvdt, rvdt; int j; - chvdt = *chv * dt; // distance water travels in one timestep within channel - rvdt = *rv * dt; // distance water travels as overland flow in one timestep + chvdt = chv * dt; // distance water travels in one timestep within channel + rvdt = rv * dt; // distance water travels as overland flow in one timestep tch[1] = dist_from_outlet[1] / chvdt; for (j = 2; j <= num_channels; j++) { @@ -741,8 +739,8 @@ extern void convert_dist_to_histords( extern void calc_time_delay_histogram( int num_channels, double area, - double *tch, - double *cum_dist_area_with_dist, + const double *tch, + const double *cum_dist_area_with_dist, int *num_time_delay_histo_ords, int *num_delay, double **time_delay_histogram @@ -861,10 +859,10 @@ extern void calc_time_delay_histogram( */ extern void init_discharge_array( int stand_alone, - int *num_delay, - double *Q0, + int num_delay, + double Q0, double area, - int *num_time_delay_histo_ords, + int num_time_delay_histo_ords, double **time_delay_histogram, double **Q ) { @@ -876,7 +874,7 @@ extern void init_discharge_array( *Q = NULL; } //*Q = calloc(*num_delay + *num_time_delay_histo_ords + 1, sizeof(double)); - d_alloc(Q, *num_delay + *num_time_delay_histo_ords); + d_alloc(Q, num_delay + num_time_delay_histo_ords); } // declare local variables @@ -885,14 +883,14 @@ extern void init_discharge_array( sum = 0.0; - for (i = 1; i <= (*num_delay); i++) { - (*Q)[i] += (*Q0) * area; + for (i = 1; i <= (num_delay); i++) { + (*Q)[i] += Q0 * area; } - for (i = 1; i <= (*num_time_delay_histo_ords); i++) { + for (i = 1; i <= num_time_delay_histo_ords; i++) { sum += (*time_delay_histogram)[i]; - in = (*num_delay) + i; - (*Q)[in] += (*Q0) * (area - sum); + in = num_delay + i; + (*Q)[in] += Q0 * (area - sum); }; return; @@ -931,10 +929,10 @@ extern void init_discharge_array( extern void init_water_balance( int num_topodex_values, double dt, - double *sr0, - double *szm, - double *Q0, - double *t0, + double sr0, + double szm, + double Q0, + double t0, double tl, double **stor_unsat_zone, double *szq, @@ -960,20 +958,20 @@ extern void init_water_balance( // document the assumption and the requirement for caller to size these arrays to // num_topodex_values - t0dt = (*t0) + log(dt); /* was ALOG - specific log function in fortran*/ + t0dt = t0 + log(dt); /* was ALOG - specific log function in fortran*/ /* Calculate SZQ parameter */ (*szq) = exp(t0dt - tl); for (ia = 1; ia <= num_topodex_values; ia++) { (*stor_unsat_zone)[ia] = 0.0; - (*deficit_root_zone)[ia] = (*sr0); + (*deficit_root_zone)[ia] = sr0; } - (*sbar) = -(*szm) * log((*Q0) / (*szq)); + (*sbar) = (-szm) * log(Q0 / (*szq)); // Initialise water balance. BAL is positive for storage - (*bal) = -(*sbar) - (*sr0); + (*bal) = -(*sbar) - sr0; return; } @@ -1072,7 +1070,7 @@ extern int init( double *cum_dist_area_with_dist, double dt, double tl, - double *dist_from_outlet, + const double *dist_from_outlet, int *num_time_delay_histo_ords, int *num_delay, double *szm, @@ -1149,7 +1147,7 @@ extern int init( // NJF num_channels is the value provided (SHOULD COME FROM TREAD) // Convert distance/area form to time delay histogram ordinates - convert_dist_to_histords(dist_from_outlet, num_channels, chv, rv, dt, tch); + convert_dist_to_histords(dist_from_outlet, num_channels, *chv, *rv, dt, tch); // calculate the time_delay_histogram calc_time_delay_histogram( @@ -1165,10 +1163,10 @@ extern int init( // Reinitialise discharge array init_discharge_array( stand_alone, - num_delay, - Q0, + *num_delay, + *Q0, area, - num_time_delay_histo_ords, + *num_time_delay_histo_ords, time_delay_histogram, Q ); @@ -1177,10 +1175,10 @@ extern int init( init_water_balance( num_topodex_values, dt, - sr0, - szm, - Q0, - t0, + *sr0, + *szm, + *Q0, + *t0, tl, stor_unsat_zone, szq, From 1f08e9822ba3eead7c70257949337f9fa3bda2d3 Mon Sep 17 00:00:00 2001 From: "Carolyn.Maynard" Date: Tue, 10 Mar 2026 10:24:10 -0700 Subject: [PATCH 3/5] Updates to use the nwm-ewts libraries --- include/bmi_topmodel.h | 1 - include/logger.h | 28 +--- src/bmi_topmodel.c | 7 + src/logger.c | 372 ----------------------------------------- 4 files changed, 14 insertions(+), 394 deletions(-) delete mode 100644 src/logger.c diff --git a/include/bmi_topmodel.h b/include/bmi_topmodel.h index 6bed3f5..264df07 100644 --- a/include/bmi_topmodel.h +++ b/include/bmi_topmodel.h @@ -16,7 +16,6 @@ int init_config(const char* config_file, topmodel_model* model); int read_init_config(const char* config_file, topmodel_model* model); - #if defined(__cplusplus) } #endif diff --git a/include/logger.h b/include/logger.h index 5d3c9a6..646a7e0 100644 --- a/include/logger.h +++ b/include/logger.h @@ -1,21 +1,7 @@ -#ifndef LOGGER_H -#define LOGGER_H - -#include // for variable args: va_list -#include - -typedef enum { - NONE = 0, - DEBUG = 1, - INFO = 2, - WARNING = 3, - SEVERE = 4, - FATAL = 5, -} LogLevel; - -// Public Methods -LogLevel GetLogLevel(void); -bool IsLoggingEnabled(void); -void Log(LogLevel messageLevel, const char* message, ...); - -#endif // LOGGER_H +#ifndef TOPMODEL_LOGGER_H +#define TOPMODEL_LOGGER_H +#include "ewts/module_constants.h" +#define EWTS_ID EWTS_ID_TOPMODEL +#include "ewts/logger.h" +#include "ewts/log_levels.h" +#endif /* TOPMODEL_LOGGER_H */ diff --git a/src/bmi_topmodel.c b/src/bmi_topmodel.c index 8c1e2da..ef39709 100755 --- a/src/bmi_topmodel.c +++ b/src/bmi_topmodel.c @@ -412,6 +412,13 @@ static int Initialize(Bmi *self, const char *cfg_file) { topmodel_model *topmodel; topmodel = (topmodel_model *)self->data; + // Initialize the Error, Warning and Trapping System +#ifdef EWTS_HAVE_NGEN_BRIDGE + EwtsInit(EWTS_ID_TOPMODEL, true); +#else + EwtsInit(EWTS_ID_TOPMODEL, false); +#endif + // Read and setup data from file int ret = init_config(cfg_file, topmodel); if (ret != BMI_SUCCESS) diff --git a/src/logger.c b/src/logger.c deleted file mode 100644 index 1cf25d4..0000000 --- a/src/logger.c +++ /dev/null @@ -1,372 +0,0 @@ -#include "logger.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// Define Environment Variable Names -#define MODULE_NAME "TopModel" -#define EV_MODULE_LOGLEVEL "TOPMODEL_LOGLEVEL" // This modules log level -#define EV_MODULE_LOGFILEPATH "TOPMODEL_LOGFILEPATH" // This modules log full log filename -#define EV_NGEN_LOGFILEPATH "NGEN_LOG_FILE_PATH" // ngen log file - -#define EV_EWTS_LOGGING "NGEN_EWTS_LOGGING" // Enable/disable of Error Warning and Trapping System - -#define DS "/" // Directory separator -#define LOG_DIR_NGENCERF "/ngencerf/data" // ngenCERF log directory -#define LOG_DIR_DEFAULT "run-logs" // Default subdir if ngen log file env not found -#define LOG_FILE_EXT "log" -#define LOG_MODULE_NAME_LEN 8 // Width of module name for log entries - -bool loggingEnabled = true; -bool openedAppendMode = true; -bool loggerInitialized = false; -FILE* logFile = NULL; -char logFilePath[1024] = ""; -LogLevel logLevel = INFO; -char moduleName[LOG_MODULE_NAME_LEN+1] = ""; - -void TrimString(const char *input, char *output, size_t outputSize) { - if (!input || !output || outputSize == 0) { - if (output && outputSize > 0) output[0] = '\0'; - return; - } - - // Skip leading whitespace - while (isspace((unsigned char)*input)) { - input++; - } - - size_t len = strlen(input); - if (len == 0) { - output[0] = '\0'; - return; - } - - // Find the end of the string and move backward past trailing whitespace - const char *end = input + len - 1; - while (end > input && isspace((unsigned char)*end)) { - end--; - } - - size_t trimmedLen = end - input + 1; - if (trimmedLen >= outputSize) { - trimmedLen = outputSize - 1; - } - - strncpy(output, input, trimmedLen); - output[trimmedLen] = '\0'; -} - -void SetLogModuleName(void) { - - // Copy MODULE_NAME to a string variable - char src[20]; - strncpy(src, MODULE_NAME, sizeof(src)); - src[sizeof(src) - 1] = '\0'; // ensure null termination - - // Convert to uppercase and copy, up to width or null terminator - size_t i = 0; - for (; i < LOG_MODULE_NAME_LEN && src[i] != '\0'; i++) { - moduleName[i] = toupper((unsigned char)src[i]); - } - - // Pad with spaces if needed - for (; i < LOG_MODULE_NAME_LEN; i++) { - moduleName[i] = ' '; - } - moduleName[LOG_MODULE_NAME_LEN] = '\0'; // null-terminate -} - -void CreateTimestamp(char *buffer, size_t size, bool appendMS, bool iso) { - struct timeval tv; - gettimeofday(&tv, NULL); - - struct tm utc_tm; - gmtime_r(&tv.tv_sec, &utc_tm); - - char base[32]; - if (iso) { - strftime(base, sizeof(base), "%Y-%m-%dT%H:%M:%S", &utc_tm); - } else { - strftime(base, sizeof(base), "%Y%m%dT%H%M%S", &utc_tm); - } - - if (appendMS) { - snprintf(buffer, size, "%s.%03ld", base, tv.tv_usec / 1000); - } else { - snprintf(buffer, size, "%s", base); - } -} - -bool DirectoryExists(const char *path) { - struct stat info; - return (stat(path, &info) == 0 && (info.st_mode & S_IFDIR)); -} - -bool CreateDirectory(const char *path) { - if (!DirectoryExists(path)) { - char cmd[512]; - snprintf(cmd, sizeof(cmd), "mkdir -p \"%s\"", path); - int status = system(cmd); - if (status == -1 || (WIFEXITED(status) && WEXITSTATUS(status) != 0)) { - fprintf(stderr, "Failed to create directory: %s\n", path); - return false; - } - } - return true; -} - -bool LogFileReady(bool appendMode) { - if (logFile && !ferror(logFile)) { - fseek(logFile, 0, SEEK_END); - return true; - } else if (strlen(logFilePath) > 0) { - logFile = fopen(logFilePath, appendMode ? "a" : "w"); - if (logFile != NULL) { - openedAppendMode = appendMode; - return true; - } - return false; - } - 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, othrewise - * - 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 SetupLogFile(void) { - logFilePath[0] = '\0'; - bool appendEntries = true; - bool moduleLogEnvExists = false; - - const char *envVar = getenv(EV_MODULE_LOGFILEPATH); - if (envVar && envVar[0] != '\0') { - strncpy(logFilePath, envVar, sizeof(logFilePath) - 1); - moduleLogEnvExists = true; - } else { - envVar = getenv(EV_NGEN_LOGFILEPATH); - if (envVar && envVar[0] != '\0') { - strncpy(logFilePath, envVar, sizeof(logFilePath) - 1); - } else { - appendEntries = false; - char logFileDir[512]; - - if (DirectoryExists(LOG_DIR_NGENCERF)) { - snprintf(logFileDir, sizeof(logFileDir), "%s%s%s", LOG_DIR_NGENCERF, DS, LOG_DIR_DEFAULT); - } else { - const char *home = getenv("HOME"); // Get users home directly pathname - if (home) { - snprintf(logFileDir, sizeof(logFileDir), "%s%s%s", home, DS, LOG_DIR_DEFAULT); - } - else { - snprintf(logFileDir, sizeof(logFileDir), "~%s%s", DS, LOG_DIR_DEFAULT); - } - } - - if (CreateDirectory(logFileDir)) { - const char *envUsername = getenv("USER"); - if (envUsername) { - strncat(logFileDir, DS, sizeof(logFileDir) - strlen(logFileDir) - 1); - strncat(logFileDir, envUsername, sizeof(logFileDir) - strlen(logFileDir) - 1); - } else { - char dateStr[32]; - CreateTimestamp(dateStr, sizeof(dateStr), 0, 0); - strncat(logFileDir, DS, sizeof(logFileDir) - strlen(logFileDir) - 1); - strncat(logFileDir, dateStr, sizeof(logFileDir) - strlen(logFileDir) - 1); - } - - if (CreateDirectory(logFileDir)) { - char timestamp[32]; - CreateTimestamp(timestamp, sizeof(timestamp), 0, 0); - snprintf(logFilePath, sizeof(logFilePath), "%s%s%s_%s.%s", - logFileDir, DS, MODULE_NAME, timestamp, LOG_FILE_EXT); - } - } - } - } - - if (LogFileReady(appendEntries)) { - if (!moduleLogEnvExists) setenv(EV_MODULE_LOGFILEPATH, logFilePath, 1); - printf("Module %s Log File: %s\n", MODULE_NAME, logFilePath); - fflush(stdout); // Force flushing of stdout - LogLevel saveLevel = logLevel; - logLevel = INFO; // Ensure this INFO message is always logged - Log(logLevel, "Opened log file %s in %s mode\n", logFilePath, (openedAppendMode ? "Append" : "Truncate")); - logLevel = saveLevel; - } else { - printf("Unable to open log file "); - if (strlen(logFilePath) > 0) { - printf("%s (Perhaps check permissions)\n", logFilePath); - } - printf("Log entries will be written to stdout\n"); - fflush(stdout); // Force flushing of stdout - } -} - -const char* ConvertLogLevelToString(LogLevel level) { - switch (level) { - case DEBUG: return "DEBUG "; - case INFO: return "INFO "; - case WARNING: return "WARNING"; - case SEVERE: return "SEVERE "; - case FATAL: return "FATAL "; - default: return "NONE "; - } -} - -LogLevel ConvertStringToLogLevel(const char* str) { - char trimmedStr[20] = {0}; - TrimString(str, trimmedStr, sizeof(trimmedStr)); - if (strcasecmp(trimmedStr, "DEBUG") == 0) return DEBUG; - if (strcasecmp(trimmedStr, "INFO") == 0) return INFO; - if (strcasecmp(trimmedStr, "WARNING") == 0) return WARNING; - if (strcasecmp(trimmedStr, "SEVERE") == 0) return SEVERE; - if (strcasecmp(trimmedStr, "FATAL") == 0) return FATAL; - return NONE; -} - -void SetLoggingFlag(void) { - const char* ewtsStr = getenv(EV_EWTS_LOGGING); - if (ewtsStr && ewtsStr[0] != '\0') { - char trimmedStr[20] = {0}; - TrimString(ewtsStr, trimmedStr, sizeof(trimmedStr)); - bool loggingEnabled = ((strcasecmp(trimmedStr, "ENABLED") == 0))? true:false; - } - printf("%s Logging %s\n", MODULE_NAME, ((loggingEnabled)?"ENABLED":"DISABLED")); - fflush(stdout); // Force flushing of stdout -} - -void SetLogLevel(void) { - const char* envLogLevel = getenv(EV_MODULE_LOGLEVEL); - if (envLogLevel && envLogLevel[0] != '\0') { - logLevel = ConvertStringToLogLevel(envLogLevel); - } - char llStr[10]; - char msg[100]; - TrimString(ConvertLogLevelToString(logLevel), llStr, sizeof(llStr)); - snprintf(msg, sizeof(msg), "Log level set to %s\n", llStr); - printf("%s %s", MODULE_NAME, msg); - fflush(stdout); // Force flushing of stdout - LogLevel saveLevel = logLevel; - logLevel = INFO; // Ensure this INFO message is always logged - Log(logLevel, msg); - logLevel = saveLevel; -} - -void SetLogPreferences(void) { - - if (!loggerInitialized) { - loggerInitialized = true; // Only call this once - - SetLoggingFlag(); // Based on the environment variable - if (loggingEnabled) { - SetLogModuleName(); // Set the module name used in log entries - SetupLogFile(); - SetLogLevel(); - // FOR TESTING ONLY! Uncomment next line to generate test log entries - // WriteLogTestMsgs(); - } - } -} - -void TrimToOneNewline(char *str, size_t max_len) { - size_t len = strlen(str); - - // Strip trailing whitespace including \n, \r, spaces, tabs - while (len > 0 && isspace((unsigned char)str[len - 1])) { - str[--len] = '\0'; - } - - // Ensure room to add a newline - if (len + 1 < max_len) { - str[len] = '\n'; - str[len + 1] = '\0'; - } -} - -LogLevel GetLogLevel(void) { - return logLevel; -} - -bool IsLoggingEnabled(void) { - return loggingEnabled; -} - -void Log(LogLevel messageLevel, const char* message, ...) { - if (!loggerInitialized) SetLogPreferences(); // Initialize logger on first call - - if (loggingEnabled && (messageLevel >= logLevel)) { - - va_list arglist; - va_start(arglist, message); - va_list arglist_copy; - va_copy(arglist_copy, arglist); - - int length = vsnprintf(NULL, 0, message, arglist_copy); - va_end(arglist_copy); - - char *buffer = malloc(length + 1); - if (!buffer) return; // Always good to check - - vsnprintf(buffer, length + 1, message, arglist); - va_end(arglist); - - char timestamp[32]; - CreateTimestamp(timestamp, sizeof(timestamp), 1, 1); - - char logPrefix[128]; - snprintf(logPrefix, sizeof(logPrefix), "%s %s %s", timestamp, moduleName, ConvertLogLevelToString(messageLevel)); - - TrimToOneNewline(buffer, sizeof(buffer)); - char *line = strtok(buffer, "\n"); - if (LogFileReady(true)) { - while (line != NULL) { - fprintf(logFile, "%s %s\n", logPrefix, line); - line = strtok(NULL, "\n"); - } - fflush(logFile); - } else { - while (line != NULL) { - printf("%s %s\n", logPrefix, line); - line = strtok(NULL, "\n"); - } - fflush(stdout); - } - - free(buffer); - } -} - -void WriteLogTestMsgs(void) { - - LogLevel savedLogLevel = logLevel; - - setenv(EV_MODULE_LOGLEVEL, "SEVERE", 1); Log(SEVERE, "Sample Log for LogLevel::SEVERE"); - setenv(EV_MODULE_LOGLEVEL, "FATAL", 1); Log(FATAL, "Sample Log for LogLevel::FATAL"); - setenv(EV_MODULE_LOGLEVEL, "WARNING", 1); Log(WARNING, "Sample Log for LogLevel::WARNING"); - setenv(EV_MODULE_LOGLEVEL, "INFO", 1); Log(INFO, "Sample Log for LogLevel::INFO"); - setenv(EV_MODULE_LOGLEVEL, "DEBUG", 1); Log(DEBUG, "Sample Log for LogLevel::DEBUG"); - - const char* multiline_log = - "First line of multiline log:\n" - "Second line of multiline log\n" - "Third line of multiline log\n" - "Fourth line of multiline log"; - Log(DEBUG, multiline_log); - - setenv(EV_MODULE_LOGLEVEL, ConvertLogLevelToString(savedLogLevel), 1); -} From 600cc720d70fde85e67eae09fe46ff62ed07cd91 Mon Sep 17 00:00:00 2001 From: mkarim-rtx <149624049+mkarim-rtx@users.noreply.github.com> Date: Tue, 21 Apr 2026 07:24:02 -0700 Subject: [PATCH 4/5] added nwm_ponded_depth (#8) * added nwm_ponded_depth * fixed typo * alignment * updated calculation of ponded_depth * review feedback updates --- include/topmodel.h | 4 +++- src/bmi_serialization.cpp | 1 + src/bmi_topmodel.c | 23 ++++++++++++++++++----- src/topmodel.c | 26 +++++++++++++++++++++++++- 4 files changed, 47 insertions(+), 7 deletions(-) diff --git a/include/topmodel.h b/include/topmodel.h index 6fe0306..e8dc18e 100755 --- a/include/topmodel.h +++ b/include/topmodel.h @@ -68,7 +68,7 @@ extern void topmod(FILE *output_fptr, int nstep, int num_topodex_values, const double *time_delay_histogram, double *sbar,int num_delay, int current_time_step, int stand_alone, double *sump, double *sumae, double *sumq, double *sumrz, double *sumuz, - double *quz, double *qb, double *qof, double *p, double *ep); + double *quz, double *qb, double *qof, double *p, double *ep, double *ponded_depth); extern int tread(FILE *subcat_fptr,FILE *output_fptr,char *subcat, int *num_topodex_values,int *num_channels,double *area, @@ -184,6 +184,7 @@ struct TopModel_Struct{ double qof; /* flow from saturated area and infiltration excess flow*/ double p; /* adjusted rain*/ double ep; /* adjusted potential evaporation*/ + double ponded_depth; /* queued delayed runoff depth from hydrograph ordinates */ /************** Framework vars **************/ int stand_alone; @@ -208,4 +209,5 @@ typedef struct TopModel_Struct topmodel_model; extern void alloc_topmodel(topmodel_model *model); extern void free_topmodel(topmodel_model *model); + #endif diff --git a/src/bmi_serialization.cpp b/src/bmi_serialization.cpp index 4daa17d..9647f60 100644 --- a/src/bmi_serialization.cpp +++ b/src/bmi_serialization.cpp @@ -54,6 +54,7 @@ void TopmodelSerializer::serialize(Archive& ar, const unsigned int version) { ar & model->qof; // ar & model->p; // reassigned each update; used in calc after assignment ar & model->ep; // reassigned each update; used in calc after assignment + ar & model->ponded_depth; // reassigned each update ar & model->sbar; // used then reassigned // array data that updates in update; counts set in config diff --git a/src/bmi_topmodel.c b/src/bmi_topmodel.c index ef39709..0131ef3 100755 --- a/src/bmi_topmodel.c +++ b/src/bmi_topmodel.c @@ -6,7 +6,7 @@ /* BMI Adaption: Max i/o file name length changed from 30 to 256 */ #define MAX_FILENAME_LENGTH 256 -#define OUTPUT_VAR_NAME_COUNT 14 +#define OUTPUT_VAR_NAME_COUNT 15 #define INPUT_VAR_NAME_COUNT 2 #define PARAM_VAR_NAME_COUNT 8 @@ -27,7 +27,8 @@ static const char *output_var_names[OUTPUT_VAR_NAME_COUNT] = { "land_surface_water__domain_time_integral_of_runoff_volume_flux", // sumq "soil_water__domain_root-zone_volume_deficit", // sumrz "soil_water__domain_unsaturated-zone_volume", // sumuz - "land_surface_water__water_balance_volume" // bal + "land_surface_water__water_balance_volume", // bal + "nwm_ponded_depth" // sum of Q[1..num_time_delay_histo_ords] }; static const char *output_var_types[OUTPUT_VAR_NAME_COUNT] = { @@ -44,11 +45,12 @@ static const char *output_var_types[OUTPUT_VAR_NAME_COUNT] = { "double", "double", "double", + "double", "double" }; static const int output_var_item_count[OUTPUT_VAR_NAME_COUNT] = - {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; + {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; static const char *output_var_units[OUTPUT_VAR_NAME_COUNT] = { "m h-1", @@ -64,11 +66,12 @@ static const char *output_var_units[OUTPUT_VAR_NAME_COUNT] = { "m", "m", "m", + "m", "m" }; static const int output_var_grids[OUTPUT_VAR_NAME_COUNT] = - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; static const char *output_var_locations[OUTPUT_VAR_NAME_COUNT] = { "node", @@ -84,6 +87,7 @@ static const char *output_var_locations[OUTPUT_VAR_NAME_COUNT] = { "node", "node", "node", + "node", "node" }; @@ -501,7 +505,8 @@ static int Update(Bmi *self) { &topmodel->qb, &topmodel->qof, &topmodel->p, - &topmodel->ep + &topmodel->ep, + &topmodel->ponded_depth ); return BMI_SUCCESS; @@ -911,6 +916,14 @@ static int Get_value_ptr(Bmi *self, const char *name, void **dest) { *dest = (void *)&topmodel->bal; return BMI_SUCCESS; } + + // ponded depth + if (strcmp(name, "nwm_ponded_depth") == 0) { + topmodel_model *topmodel; + topmodel = (topmodel_model *)self->data; + *dest = (void *)&topmodel->ponded_depth; + return BMI_SUCCESS; + } // szm (parameter) if (strcmp(name, "szm") == 0) { topmodel_model *topmodel; diff --git a/src/topmodel.c b/src/topmodel.c index 4eeea89..a72db38 100755 --- a/src/topmodel.c +++ b/src/topmodel.c @@ -147,7 +147,8 @@ extern void topmod( double *qb, double *qof, double *p, - double *ep + double *ep, + double *ponded_depth ) { /***************************************************************** @@ -357,6 +358,27 @@ extern void topmod( Q[in] += (*Qout) * time_delay_histogram[ir]; } + /* Ponded depth: sum delayed-flow components stored in the hydrograph- + * ordinates portion of the routing array, excluding the pure channel-delay + * slots. */ + *ponded_depth = 0.0; + + if (stand_alone == TRUE) { + int q_start = it + num_delay; + int q_end = q_start + num_time_delay_histo_ords - 1; + + for (ir = q_start; ir <= q_end; ir++) { + *ponded_depth += Q[ir]; + } + } else { + int q_start = num_delay + 1; + int q_end = num_delay + num_time_delay_histo_ords; + + for (ir = q_start; ir <= q_end; ir++) { + *ponded_depth += Q[ir]; + } + } + // Add current time flow to mass balance variable *sumq += Q[it]; /* BMI Adaption: replace nstep with current_time_step */ @@ -838,6 +860,8 @@ extern void calc_time_delay_histogram( } + + return; } From b0004e2895cf8833cbc07c7bd8d40254b3b23e45 Mon Sep 17 00:00:00 2001 From: Mohammed Karim Date: Wed, 6 May 2026 09:15:10 -0700 Subject: [PATCH 5/5] added and_surface_water__baseflow_volume_flux_m3_per_s --- include/topmodel.h | 1 + src/bmi_topmodel.c | 36 +++++++++++++++++++++++++++++++----- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/include/topmodel.h b/include/topmodel.h index e8dc18e..46a044b 100755 --- a/include/topmodel.h +++ b/include/topmodel.h @@ -185,6 +185,7 @@ struct TopModel_Struct{ double p; /* adjusted rain*/ double ep; /* adjusted potential evaporation*/ double ponded_depth; /* queued delayed runoff depth from hydrograph ordinates */ + double qb_m3_per_s; /************** Framework vars **************/ int stand_alone; diff --git a/src/bmi_topmodel.c b/src/bmi_topmodel.c index 0131ef3..1c71168 100755 --- a/src/bmi_topmodel.c +++ b/src/bmi_topmodel.c @@ -6,7 +6,7 @@ /* BMI Adaption: Max i/o file name length changed from 30 to 256 */ #define MAX_FILENAME_LENGTH 256 -#define OUTPUT_VAR_NAME_COUNT 15 +#define OUTPUT_VAR_NAME_COUNT 16 #define INPUT_VAR_NAME_COUNT 2 #define PARAM_VAR_NAME_COUNT 8 @@ -28,7 +28,8 @@ static const char *output_var_names[OUTPUT_VAR_NAME_COUNT] = { "soil_water__domain_root-zone_volume_deficit", // sumrz "soil_water__domain_unsaturated-zone_volume", // sumuz "land_surface_water__water_balance_volume", // bal - "nwm_ponded_depth" // sum of Q[1..num_time_delay_histo_ords] + "nwm_ponded_depth", // sum of Q[1..num_time_delay_histo_ords] + "land_surface_water__baseflow_volume_flux_m3_per_s" }; static const char *output_var_types[OUTPUT_VAR_NAME_COUNT] = { @@ -46,11 +47,12 @@ static const char *output_var_types[OUTPUT_VAR_NAME_COUNT] = { "double", "double", "double", + "double", "double" }; static const int output_var_item_count[OUTPUT_VAR_NAME_COUNT] = - {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; + {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; static const char *output_var_units[OUTPUT_VAR_NAME_COUNT] = { "m h-1", @@ -67,11 +69,12 @@ static const char *output_var_units[OUTPUT_VAR_NAME_COUNT] = { "m", "m", "m", - "m" + "m", + "m3 s-1" }; static const int output_var_grids[OUTPUT_VAR_NAME_COUNT] = - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; static const char *output_var_locations[OUTPUT_VAR_NAME_COUNT] = { "node", @@ -88,6 +91,7 @@ static const char *output_var_locations[OUTPUT_VAR_NAME_COUNT] = { "node", "node", "node", + "node", "node" }; @@ -433,6 +437,7 @@ static int Initialize(Bmi *self, const char *cfg_file) { topmodel->sump = 0.0; topmodel->sumae = 0.0; topmodel->sumq = 0.0; + topmodel->qb_m3_per_s = 0.0; topmodel->max_contrib_area = 0.0; @@ -859,6 +864,27 @@ static int Get_value_ptr(Bmi *self, const char *name, void **dest) { *dest = (void *)&topmodel->qb; return BMI_SUCCESS; } + // qb in m3/s + if (strcmp(name, "land_surface_water__baseflow_volume_flux_m3_per_s") == 0) { + topmodel_model *topmodel; + topmodel = (topmodel_model *)self->data; + + if (topmodel->area > 0.0) { + /* TOPMODEL native qb is baseflow depth rate [m h-1]. + * NWM expects volume flow rate [m3 s-1]. + * + * Conversion: + * m h-1 * m2 / 3600 s h-1 = m3 s-1 + */ + topmodel->qb_m3_per_s = topmodel->qb * topmodel->area / 3600.0; + } + else { + topmodel->qb_m3_per_s = 0.0; + } + + *dest = (void *)&topmodel->qb_m3_per_s; + return BMI_SUCCESS; + } // sbar if (strcmp(name, "soil_water__domain_volume_deficit") == 0) { topmodel_model *topmodel;