diff --git a/NEWS.md b/NEWS.md index 8369186bc..707b9b307 100644 --- a/NEWS.md +++ b/NEWS.md @@ -29,6 +29,11 @@ * For `"netCDF"`-based SOILWAT2, information about the `"domain"` (i.e., simulation units and their geographic locations) is obtained from `"domain.nc"` (#361; @N1ckP3rsl3y, @dschlaep). + * For `"netCDF"`-based SOILWAT2, simulation progress (success/failure) is + tracked by `"progress.nc"` (#387; @N1ckP3rsl3y, @dschlaep); + progress tracking makes re-starts after partial completion of the + simulation set by an earlier execution possible. + * Tests now utilize the same template/deep-copy approach (@dschlaep), i.e., input files from `test/example/` populate a `"template"` and @@ -79,7 +84,7 @@ * New input directory `"Input_nc/"` that describes `"netCDF"`-based inputs (paths provided by new entries in `"files.in"`). * New text input file `"files_nc.in"` that lists for each input purpose - (currently, only `"domain"` is implemented) + (currently, `"domain"` and `"progress"` are implemented) the path to the `"netCDF"` input file and associated variable name. * New text input file `"attribues_nc.in"` to provide global attributes and a geographic (and optionally a projected) `"CRS"` (coordinate reference system) @@ -87,6 +92,9 @@ * A user provided `"domain.nc"` that describes the simulation `"domain"`. Specifications must be consistent with `"domain.in"`. If absent, a template is automatically generated based on `"domain.in"`. +* A user provided `"progress.nc"` that describes the simulation `"progress"`. + Specifications must be consistent with `"domain.nc"`. + If absent, it is automatically generated based on `"domain.nc"`. # SOILWAT2 v7.2.0 diff --git a/doc/additional_pages/A_SOILWAT2_user_guide.md b/doc/additional_pages/A_SOILWAT2_user_guide.md index 287632ea7..8d1fadcc5 100644 --- a/doc/additional_pages/A_SOILWAT2_user_guide.md +++ b/doc/additional_pages/A_SOILWAT2_user_guide.md @@ -122,6 +122,11 @@ on your side. CPPFLAGS=-DSWNETCDF NC_CFLAGS="-I/path/to/include" NC_LIBS="-L/path/to/lib" make ``` + * User-specified username and hostname, e.g., +```{.sh} + USERNAME=nobody HOSTNAME=nowhere make +``` +
diff --git a/include/SW_Domain.h b/include/SW_Domain.h index 7a951a2b8..33b2a6c3b 100644 --- a/include/SW_Domain.h +++ b/include/SW_Domain.h @@ -13,10 +13,13 @@ extern "C" { void SW_DOM_calc_ncSuid(SW_DOMAIN* SW_Domain, unsigned long suid, unsigned long ncSuid[]); void SW_DOM_calc_nSUIDs(SW_DOMAIN* SW_Domain); -Bool SW_DOM_CheckProgress(char* domainType, unsigned long ncSuid[]); -void SW_DOM_CreateProgress(SW_DOMAIN* SW_Domain); +Bool SW_DOM_CheckProgress(int progFileID, int progVarID, + unsigned long ncSuid[], LOG_INFO* LogInfo); +void SW_DOM_CreateProgress(SW_DOMAIN* SW_Domain, LOG_INFO* LogInfo); void SW_DOM_read(SW_DOMAIN* SW_Domain, LOG_INFO* LogInfo); -void SW_DOM_SetProgress(char* domainType, unsigned long ncSuid[]); +void SW_DOM_SetProgress(Bool isFailure, const char* domType, int progFileID, + int progVarID, unsigned long ncSuid[], + LOG_INFO* LogInfo); void SW_DOM_SimSet(SW_DOMAIN* SW_Domain, unsigned long userSUID, LOG_INFO* LogInfo); void SW_DOM_deepCopy(SW_DOMAIN* source, SW_DOMAIN* dest, LOG_INFO* LogInfo); diff --git a/include/SW_datastructs.h b/include/SW_datastructs.h index cae09cca0..561fe6a93 100644 --- a/include/SW_datastructs.h +++ b/include/SW_datastructs.h @@ -26,7 +26,7 @@ #endif #define SW_NFILES 26 // For `InFiles` -#define SW_NVARNC 1 // For `InFilesNC` +#define SW_NVARNC 2 // For `InFilesNC` /* =================================================== */ @@ -769,7 +769,8 @@ typedef struct { Bool stopRun; // Specifies if an error has occurred and // the program needs to stop early (backtrack) - Bool QuietMode; /**< Don't print version, error message, or notify user about logfile (only used by SOILWAT2) */ + Bool QuietMode, /**< Don't print version, error message, or notify user about logfile (only used by SOILWAT2) */ + printProgressMsg; /**< Do/don't print progress messages to the console */ } LOG_INFO; typedef struct { @@ -1066,6 +1067,7 @@ typedef struct { char *InFilesNC[SW_NVARNC]; int ncFileIDs[SW_NVARNC]; + int ncVarIDs[SW_NVARNC]; } SW_NETCDF; /* =================================================== */ diff --git a/include/SW_netCDF.h b/include/SW_netCDF.h index 6ec1cb0f3..7253c2298 100644 --- a/include/SW_netCDF.h +++ b/include/SW_netCDF.h @@ -12,6 +12,7 @@ extern "C" { /* --------------------------------------------------- */ #define vNCdom 0 // Domain netCDF index within `InFilesNC` and `varNC` (SW_NETCDF) +#define vNCprog 1 // Progress netCDF index within `InFilesNC` and `varNC` (SW_NETCDF) #define DOMAIN_TEMP "Input_nc/domain_template.nc" @@ -21,17 +22,26 @@ extern "C" { void SW_NC_check(SW_DOMAIN* SW_Domain, int ncFileID, const char* fileName, LOG_INFO* LogInfo); void SW_NC_create_domain_template(SW_DOMAIN* SW_Domain, LOG_INFO* LogInfo); -void SW_NC_create_template(const char* fileName, unsigned long timeSize, - unsigned long vertSize, const char* varName, - char* varAttributes[], LOG_INFO* LogInfo); +void SW_NC_create_template(const char* domFile, int domFileID, + const char* fileName, int* newFileID, int newVarType, + unsigned long timeSize, unsigned long vertSize, const char* varName, + const char* attNames[], const char* attVals[], int numAtts, Bool isInput, + const char* freq, LOG_INFO* LogInfo); +void SW_NC_create_progress(SW_DOMAIN* SW_Domain, LOG_INFO* LogInfo); +void SW_NC_set_progress(Bool isFailure, const char* domType, int progFileID, + int progVarID, unsigned long ncSUID[], + LOG_INFO* LogInfo); +Bool SW_NC_check_progress(int progFileID, int progVarID, + unsigned long ncSUID[], LOG_INFO* LogInfo); void SW_NC_read_inputs(SW_ALL* sw, SW_DOMAIN* SW_Domain, unsigned long ncSUID[], LOG_INFO* LogInfo); void SW_NC_check_input_files(SW_DOMAIN* SW_Domain, LOG_INFO* LogInfo); void SW_NC_read(SW_NETCDF* SW_netCDF, PATH_INFO* PathInfo, LOG_INFO* LogInfo); void SW_NC_init_ptrs(SW_NETCDF* SW_netCDF); void SW_NC_deconstruct(SW_NETCDF* SW_netCDF); -void SW_NC_open_files(SW_NETCDF* SW_netCDF, LOG_INFO* LogInfo); +void SW_NC_open_dom_prog_files(SW_NETCDF* SW_netCDF, LOG_INFO* LogInfo); void SW_NC_close_files(SW_NETCDF* SW_netCDF); +void SW_NC_deepCopy(SW_NETCDF* source, SW_NETCDF* dest, LOG_INFO* LogInfo); #ifdef __cplusplus } diff --git a/include/Times.h b/include/Times.h index 4b32cb4d3..07cde97b4 100644 --- a/include/Times.h +++ b/include/Times.h @@ -99,6 +99,7 @@ double diff_walltime(WallTimeSpec start, Bool ok_start); void SW_WT_StartTime(SW_WALLTIME *wt); void SW_WT_TimeRun(WallTimeSpec ts, Bool ok_ts, SW_WALLTIME *wt); void SW_WT_ReportTime(SW_WALLTIME wt, LOG_INFO* LogInfo); +void timeStringISO8601(char *timeString, int stringLength); #ifdef __cplusplus } diff --git a/include/filefuncs.h b/include/filefuncs.h index 29bf03663..c05c12a50 100644 --- a/include/filefuncs.h +++ b/include/filefuncs.h @@ -32,6 +32,7 @@ Bool MkDir(const char *dname, LOG_INFO* LogInfo); Bool RemoveFiles(const char *fspec, LOG_INFO* LogInfo); Bool CopyFile(const char *from, const char *to, LOG_INFO* LogInfo); void LogError(LOG_INFO* LogInfo, const int mode, const char *fmt, ...); +void sw_message(const char *msg); int key_to_id(const char* key, const char **possibleKeys, int numPossKeys); diff --git a/makefile b/makefile index c81d96c6c..6f0a4ae16 100644 --- a/makefile +++ b/makefile @@ -80,10 +80,11 @@ SHELL = /bin/sh #------ Identification SW2_VERSION := "$(shell git describe --abbrev=7 --dirty --always --tags)" -HOSTNAME := "$(shell uname -sn)" +HOSTNAME ?= "$(shell uname -sn)" +USERNAME ?= "$(USER)" sw_info := -DSW2_VERSION=\"$(SW2_VERSION)\" \ - -DUSERNAME=\"$(USER)\" \ + -DUSERNAME=\"$(USERNAME)\" \ -DHOSTNAME=\"$(HOSTNAME)\" diff --git a/src/SW_Control.c b/src/SW_Control.c index dc470c500..ceca8b569 100644 --- a/src/SW_Control.c +++ b/src/SW_Control.c @@ -210,10 +210,25 @@ void SW_CTL_RunSimSet(SW_ALL *sw_template, SW_OUTPUT_POINTERS SW_OutputPtrs[], char tag_suid[32]; /* 32 = 11 character for "(suid = ) " + 20 character for ULONG_MAX + '\0' */ tag_suid[0] = '\0'; WallTimeSpec tss, tsr; - Bool ok_tss = swFALSE, ok_tsr = swFALSE; + Bool ok_tss = swFALSE, ok_tsr = swFALSE, ok_suid; + + int progFileID = 0; // Value does not matter if SWNETCDF is not defined + int progVarID = 0; // Value does not matter if SWNETCDF is not defined + + #if defined(SWNETCDF) + progFileID = SW_Domain->netCDFInfo.ncFileIDs[vNCprog]; + progVarID = SW_Domain->netCDFInfo.ncVarIDs[vNCprog]; + #endif set_walltime(&tss, &ok_tss); + #if defined(SOILWAT) + if(main_LogInfo->printProgressMsg) { + sw_message("is running simulations across the domain ..."); + } + #endif + + /* Loop over suids in simulation set of domain */ for(suid = SW_Domain->startSimSet; suid < SW_Domain->endSimSet; suid++) { /* Check wall time against limit */ @@ -229,41 +244,53 @@ void SW_CTL_RunSimSet(SW_ALL *sw_template, SW_OUTPUT_POINTERS SW_OutputPtrs[], LOG_INFO local_LogInfo; sw_init_logs(main_LogInfo->logfp, &local_LogInfo); + local_LogInfo.printProgressMsg = main_LogInfo->printProgressMsg; + + /* Check if suid needs to be simulated */ SW_DOM_calc_ncSuid(SW_Domain, suid, ncSuid); - if(SW_DOM_CheckProgress(SW_Domain->DomainType, ncSuid)) { - nSims++; // Counter of simulation runs + ok_suid = SW_DOM_CheckProgress(progFileID, progVarID, ncSuid, + &local_LogInfo); + if(ok_suid && !local_LogInfo.stopRun) { + /* Count simulation run */ + nSims++; + + /* Simulate suid */ set_walltime(&tsr, &ok_tsr); SW_CTL_run_sw(sw_template, SW_Domain, ncSuid, SW_OutputPtrs, NULL, &local_LogInfo); SW_WT_TimeRun(tsr, ok_tsr, SW_WallTime); - if(local_LogInfo.stopRun) { - main_LogInfo->numDomainErrors++; // Counter of simulation units with error + /* Report progress for suid */ + SW_DOM_SetProgress(local_LogInfo.stopRun, + SW_Domain->DomainType, progFileID, + progVarID, ncSuid, &local_LogInfo); + } - } else { - // Set simulation run progress - SW_DOM_SetProgress(SW_Domain->DomainType, ncSuid); - } + /* Report errors and warnings for suid */ + if(local_LogInfo.stopRun) { + main_LogInfo->numDomainErrors++; // Counter of simulation units with error + } - if (local_LogInfo.numWarnings > 0) { - main_LogInfo->numDomainWarnings++; // Counter of simulation units with warnings - } + if (local_LogInfo.numWarnings > 0) { + main_LogInfo->numDomainWarnings++; // Counter of simulation units with warnings + } - if (local_LogInfo.stopRun || local_LogInfo.numWarnings > 0) { - snprintf(tag_suid, 32, "(suid = %lu) ", suid + 1); - sw_write_warnings(tag_suid, &local_LogInfo); - } + if (local_LogInfo.stopRun || local_LogInfo.numWarnings > 0) { + snprintf(tag_suid, 32, "(suid = %lu) ", suid + 1); + sw_write_warnings(tag_suid, &local_LogInfo); } } - if (nSims == main_LogInfo->numDomainErrors) { + /* Produce global error if all suids failed */ + if (nSims > 0 && nSims == main_LogInfo->numDomainErrors) { LogError( main_LogInfo, LOGERROR, - "All simulated units produced errors." + "All simulated units (n = %zu) produced errors.", + nSims ); } @@ -364,7 +391,7 @@ void SW_CTL_setup_domain(unsigned long userSUID, } // Open necessary netCDF input files and check for consistency with domain - SW_NC_open_files(&SW_Domain->netCDFInfo, LogInfo); + SW_NC_open_dom_prog_files(&SW_Domain->netCDFInfo, LogInfo); if(LogInfo->stopRun) { return; // Exit function prematurely due to error } @@ -376,6 +403,11 @@ void SW_CTL_setup_domain(unsigned long userSUID, } #endif + SW_DOM_CreateProgress(SW_Domain, LogInfo); + if(LogInfo->stopRun) { + return; // Exit function prematurely due to error + } + SW_DOM_SimSet(SW_Domain, userSUID, LogInfo); } diff --git a/src/SW_Domain.c b/src/SW_Domain.c index 71dafab1f..c7fd1cdb3 100644 --- a/src/SW_Domain.c +++ b/src/SW_Domain.c @@ -63,18 +63,26 @@ void SW_DOM_calc_nSUIDs(SW_DOMAIN* SW_Domain) { /** * @brief Check progress in domain * - * @param[in] domainType Type of domain in which simulations are running - * (gridcell/sites) - * @param[in] ncSuid Current simulation unit identifier for which progress - * should be checked. + * @param[in] progFileID Identifier of the progress netCDF file + * @param[in] progVarID Identifier of the progress variable + * @param[in] ncSuid Current simulation unit identifier for which is used + * to get data from netCDF + * @param[in,out] LogInfo Holds information dealing with logfile output * * @return * TRUE if simulation for \p ncSuid has not been completed yet; * FALSE if simulation for \p ncSuid has been completed (i.e., skip). */ -Bool SW_DOM_CheckProgress(char* domainType, unsigned long ncSuid[]) { - (void) domainType; +Bool SW_DOM_CheckProgress(int progFileID, int progVarID, + unsigned long ncSuid[], LOG_INFO* LogInfo) { + #if defined(SWNETCDF) + return SW_NC_check_progress(progFileID, progVarID, ncSuid, LogInfo); + #else + (void) progFileID; + (void) progVarID; (void) ncSuid; + (void) LogInfo; + #endif // return TRUE (due to lack of capability to track progress) return swTRUE; @@ -85,9 +93,15 @@ Bool SW_DOM_CheckProgress(char* domainType, unsigned long ncSuid[]) { * * @param[in] SW_Domain Struct of type SW_DOMAIN holding constant * temporal/spatial information for a set of simulation runs + * @param[in] LogInfo Holds information dealing with logfile output */ -void SW_DOM_CreateProgress(SW_DOMAIN* SW_Domain) { +void SW_DOM_CreateProgress(SW_DOMAIN* SW_Domain, LOG_INFO* LogInfo) { + #if defined(SWNETCDF) + SW_NC_create_progress(SW_Domain, LogInfo); + #else (void) SW_Domain; + (void) LogInfo; + #endif } /** @@ -241,16 +255,31 @@ void SW_DOM_read(SW_DOMAIN* SW_Domain, LOG_INFO* LogInfo) { } /** - * @brief Mark a completed suid in progress netCDF + * @brief Mark completion status of simulation run * - * @param[in] domainType Type of domain in which simulations are running + * @param[in] isFailure Did simulation run fail or succeed? + * @param[in] domType Type of domain in which simulations are running * (gridcell/sites) + * @param[in] progFileID Identifier of the progress netCDF file + * @param[in] progVarID Identifier of the progress variable * @param[in] ncSuid Unique indentifier of the first suid to run * in relation to netCDFs + * @param[in,out] LogInfo */ -void SW_DOM_SetProgress(char* domainType, unsigned long ncSuid[]) { - (void) domainType; +void SW_DOM_SetProgress(Bool isFailure, const char* domType, int progFileID, + int progVarID, unsigned long ncSuid[], + LOG_INFO* LogInfo) { + + #if defined(SWNETCDF) + SW_NC_set_progress(isFailure, domType, progFileID, progVarID, ncSuid, LogInfo); + #else + (void) isFailure; + (void) progFileID; + (void) progVarID; (void) ncSuid; + (void) LogInfo; + (void) domType; + #endif } /** @@ -265,10 +294,18 @@ void SW_DOM_SetProgress(char* domainType, unsigned long ncSuid[]) { void SW_DOM_SimSet(SW_DOMAIN* SW_Domain, unsigned long userSUID, LOG_INFO* LogInfo) { + Bool progFound; unsigned long *startSimSet = &SW_Domain->startSimSet, *endSimSet = &SW_Domain->endSimSet, startSuid[2]; // 2 -> [y, x] or [0, s] + int progFileID = 0; // Value does not matter if SWNETCDF is not defined + int progVarID = 0; // Value does not matter if SWNETCDF is not defined + + #if defined(SWNETCDF) + progFileID = SW_Domain->netCDFInfo.ncFileIDs[vNCprog]; + progVarID = SW_Domain->netCDFInfo.ncVarIDs[vNCprog]; + #endif if(userSUID > 0) { if(userSUID > SW_Domain->nSUIDs) { @@ -283,12 +320,21 @@ void SW_DOM_SimSet(SW_DOMAIN* SW_Domain, unsigned long userSUID, *startSimSet = userSUID - 1; *endSimSet = userSUID; } else { + #if defined(SOILWAT) + if(LogInfo->printProgressMsg) { + sw_message("is identifying the simulation set ..."); + } + #endif + *endSimSet = SW_Domain->nSUIDs; for(*startSimSet = 0; *startSimSet < *endSimSet; (*startSimSet)++) { SW_DOM_calc_ncSuid(SW_Domain, *startSimSet, startSuid); - if(SW_DOM_CheckProgress(SW_Domain->DomainType, startSuid)) { - return; // Found start suid + progFound = SW_DOM_CheckProgress(progFileID, progVarID, + startSuid, LogInfo); + + if(progFound || LogInfo->stopRun) { + return; // Found start suid or error occurred } } } @@ -298,6 +344,10 @@ void SW_DOM_deepCopy(SW_DOMAIN* source, SW_DOMAIN* dest, LOG_INFO* LogInfo) { memcpy(dest, source, sizeof (*dest)); SW_F_deepCopy(&dest->PathInfo, &source->PathInfo, LogInfo); + + #if defined(SWNETCDF) + SW_NC_deepCopy(&dest->netCDFInfo, &source->netCDFInfo, LogInfo); + #endif } void SW_DOM_init_ptrs(SW_DOMAIN* SW_Domain) { diff --git a/src/SW_Main.c b/src/SW_Main.c index 078cca918..35fd2158a 100644 --- a/src/SW_Main.c +++ b/src/SW_Main.c @@ -80,10 +80,13 @@ int main(int argc, char **argv) { goto finishProgram; } - // Print version if not in quiet mode - if (!LogInfo.QuietMode) { - sw_print_version(); - } + // SOILWAT2: do print progress to console unless user requests quiet + LogInfo.printProgressMsg = (Bool)(!LogInfo.QuietMode); + + if (LogInfo.printProgressMsg) { + sw_message("started."); + sw_print_version(); + } // setup and construct domain SW_CTL_setup_domain(userSUID, &SW_Domain, &LogInfo); @@ -162,6 +165,9 @@ int main(int argc, char **argv) { SW_WT_ReportTime(SW_WallTime, &LogInfo); sw_wrapup_logs(&LogInfo); sw_fail_on_error(&LogInfo); + if (LogInfo.printProgressMsg) { + sw_message("ended."); + } } return 0; diff --git a/src/SW_Main_lib.c b/src/SW_Main_lib.c index cb0c3a70d..2522e2825 100644 --- a/src/SW_Main_lib.c +++ b/src/SW_Main_lib.c @@ -261,6 +261,9 @@ void sw_fail_on_error(LOG_INFO* LogInfo) { if(!LogInfo->QuietMode) { fprintf(stderr, "%s", LogInfo->errorMsg); } + if(LogInfo->printProgressMsg) { + sw_message("ended."); + } exit(EXIT_FAILURE); } #endif @@ -280,6 +283,7 @@ void sw_init_logs(FILE* logInitPtr, LOG_INFO* LogInfo) { LogInfo->stopRun = swFALSE; LogInfo->QuietMode = swFALSE; + LogInfo->printProgressMsg = swFALSE; LogInfo->numWarnings = 0; LogInfo->numDomainWarnings = 0; LogInfo->numDomainErrors = 0; diff --git a/src/SW_Output_outtext.c b/src/SW_Output_outtext.c index ea73b2dec..cdabf9d27 100644 --- a/src/SW_Output_outtext.c +++ b/src/SW_Output_outtext.c @@ -335,6 +335,12 @@ void SW_OUT_create_files(SW_FILE_STATUS* SW_FileStatus, SW_OUTPUT* SW_Output, OutPeriod pd; + #if defined(SOILWAT) + if(LogInfo->printProgressMsg) { + sw_message("is creating output files ..."); + } + #endif + ForEachOutPeriod(pd) { if (GenOutput->use_OutPeriod[pd]) { _create_csv_files(SW_FileStatus, pd, InFiles, LogInfo); diff --git a/src/SW_netCDF.c b/src/SW_netCDF.c index 3404b145b..e6b3a9e4c 100644 --- a/src/SW_netCDF.c +++ b/src/SW_netCDF.c @@ -9,14 +9,21 @@ #include "include/SW_Defines.h" #include "include/SW_Files.h" #include "include/myMemory.h" +#include "include/Times.h" +#include "include/SW_Domain.h" /* =================================================== */ /* Local Defines */ /* --------------------------------------------------- */ -#define NUM_NC_IN_KEYS 1 // Number of possible keys within `files_nc.in` +#define NUM_NC_IN_KEYS 2 // Number of possible keys within `files_nc.in` #define NUM_ATT_IN_KEYS 25 // Number of possible keys within `attributes_nc.in` +#define PRGRSS_READY ((signed char)0) // SUID is ready for simulation +#define PRGRSS_DONE ((signed char)1) // SUID has successfully been simulated +#define PRGRSS_FAIL ((signed char)-1) // SUID failed to simulate + + /* =================================================== */ /* Local Function Definitions */ /* --------------------------------------------------- */ @@ -245,7 +252,7 @@ static void nc_read_atts(SW_NETCDF* SW_netCDF, PATH_INFO* PathInfo, * @param[out] dimID Identifier of the dimension * @param[out] LogInfo Holds information on warnings and errors */ -static void get_dim_identifier(int ncFileID, char* dimName, int* dimID, +static void get_dim_identifier(int ncFileID, const char* dimName, int* dimID, LOG_INFO* LogInfo) { if(nc_inq_dimid(ncFileID, dimName, dimID) != NC_NOERR) { @@ -375,6 +382,44 @@ static void get_single_double_val(int ncFileID, const char* varName, } } +/** + * @brief Get an unsigned integer value from a variable + * + * @param[in] ncFileID Identifier of the open netCDF file to access + * @param[in] varID Identifier of the variable + * @param[in] index Location of the value within the variable + * @param[out] value String buffer to hold the resulting value + * @param[in,out] LogInfo Holds information dealing with logfile output +*/ +static void get_single_uint_val(int ncFileID, int varID, size_t index[], + unsigned int* value, LOG_INFO* LogInfo) { + + if(nc_get_var1_uint(ncFileID, varID, index, value) != NC_NOERR) { + LogError(LogInfo, LOGERROR, "An error occurred when trying to " + "get a value from a variable of type " + "unsigned integer."); + } +} + +/** + * @brief Get a byte value from a variable + * + * @param[in] ncFileID Identifier of the open netCDF file to access + * @param[in] varID Identifier of the variable + * @param[in] varName Name of the variable to access + * @param[in] index Location of the value within the variable + * @param[out] value String buffer to hold the resulting value + * @param[in,out] LogInfo Holds information dealing with logfile output +*/ +static void get_single_byte_val(int ncFileID, int varID, size_t index[], + signed char* value, LOG_INFO* LogInfo) { + + if(nc_get_var1_schar(ncFileID, varID, index, value) != NC_NOERR) { + LogError(LogInfo, LOGERROR, "An error occurred when trying to " + "get a value from a variable of type byte"); + } +} + /** * @brief Get a dimension value from a given netCDF file * @@ -383,7 +428,7 @@ static void get_single_double_val(int ncFileID, const char* varName, * @param[out] dimVal String buffer to hold the resulting value * @param[out] LogInfo Holds information on warnings and errors */ -static void get_dim_val(int ncFileID, char* dimName, size_t* dimVal, +static void get_dim_val(int ncFileID, const char* dimName, size_t* dimVal, LOG_INFO* LogInfo) { int dimID = 0; @@ -407,8 +452,10 @@ static void get_dim_val(int ncFileID, char* dimName, size_t* dimVal, */ static Bool is_wgs84(char* crs_name) { const int numPosSyns = 5; - static char* wgs84_synonyms[] = {"WGS84", "WGS 84", "EPSG:4326", - "WGS_1984", "World Geodetic System 1984"}; + static char* wgs84_synonyms[] = { + (char *)"WGS84", (char *)"WGS 84", (char *)"EPSG:4326", + (char *)"WGS_1984", (char *)"World Geodetic System 1984" + }; for (int index = 0; index < numPosSyns; index++) { if (Str_CompareI(crs_name, wgs84_synonyms[index]) == 0) { @@ -477,6 +524,27 @@ static void fill_netCDF_var_uint(int ncFileID, int varID, unsigned int values[], } } +/** + * @brief Fills a variable with value(s) of type byte + * + * @param[in] ncFileID Identifier of the open netCDF file to write the value(s) to + * @param[in] varID Identifier to the variable within the given netCDF file + * @param[in] values Individual or list of input variables + * @param[in] startIndices Specification of where the C-provided netCDF + * should start writing values within the specified variable + * @param[in] count How many values to write into the given variable + * @param[out] LogInfo Holds information on warnings and errors +*/ +static void fill_netCDF_var_byte(int ncFileID, int varID, const signed char values[], + size_t startIndices[], size_t count[], + LOG_INFO* LogInfo) { + + if(nc_put_vara_schar(ncFileID, varID, startIndices, count, &values[0]) != NC_NOERR) { + LogError(LogInfo, LOGERROR, "Could not fill variable (byte) " + "with the given value(s)."); + } +} + /** * @brief Fills a variable with value(s) of type double * @@ -499,7 +567,26 @@ static void fill_netCDF_var_double(int ncFileID, int varID, double values[], } /** - * @brief Write an attribute of type unsigned integer to a variable + * @brief Write a local attribute of type byte + * + * @param[in] attName Name of the attribute to create + * @param[in] attVal Attribute value(s) to write out + * @param[in] varID Identifier of the variable to add the attribute to + * @param[in] ncFileID Identifier of the open netCDF file to write the attribute to + * @param[in] numVals Number of values to write to the single attribute + * @param[out] LogInfo Holds information on warnings and errors +*/ +static void write_byte_att(const char* attName, const signed char* attVal, + int varID, int ncFileID, int numVals, LOG_INFO* LogInfo) { + + if(nc_put_att_schar(ncFileID, varID, attName, NC_BYTE, numVals, attVal) != NC_NOERR) { + LogError(LogInfo, LOGERROR, "Could not create new attribute %s", + attName); + } +} + +/** + * @brief Write a global attribute (text) to a netCDF file * * @param[in] attName Name of the attribute to create * @param[in] attVal Attribute string to write out @@ -540,7 +627,7 @@ static void write_str_att(const char* attName, const char* attStr, * @brief Write an attribute of type double to a variable * * @param[in] attName Name of the attribute to create - * @param[in] attVal Attribute value to write out + * @param[in] attVal Attribute value(s) to write out * @param[in] varID Identifier of the variable to add the attribute to * (Note: NC_GLOBAL is acceptable and is a global attribute of the netCDF file) * @param[in] ncFileID Identifier of the open netCDF file to write the attribute to @@ -556,6 +643,54 @@ static void write_double_att(const char* attName, const double* attVal, int varI } } +/** + * @brief Fill the progress variable in the progress netCDF with values + * + * @param[in] SW_Domain Struct of type SW_DOMAIN holding constant + * temporal/spatial information for a set of simulation runs + * @param[in,out] LogInfo Holds information on warnings and errors +*/ +static void fill_prog_netCDF_vals(SW_DOMAIN* SW_Domain, LOG_INFO* LogInfo) { + + int domVarID = SW_Domain->netCDFInfo.ncVarIDs[vNCdom]; + int progVarID = SW_Domain->netCDFInfo.ncVarIDs[vNCprog]; + unsigned int domStatus; + unsigned long suid, ncSuid[2], nSUIDs = SW_Domain->nSUIDs; + unsigned long nDimY = SW_Domain->nDimY, nDimX = SW_Domain->nDimX; + int progFileID = SW_Domain->netCDFInfo.ncFileIDs[vNCprog]; + int domFileID = SW_Domain->netCDFInfo.ncFileIDs[vNCdom]; + size_t start1D[] = {0}, start2D[] = {0, 0}; + size_t count1D[] = {nSUIDs}, count2D[] = {nDimY, nDimX}; + size_t* start = (strcmp(SW_Domain->DomainType, "s") == 0) ? start1D : start2D; + size_t* count = (strcmp(SW_Domain->DomainType, "s") == 0) ? count1D : count2D; + + signed char* vals = (signed char*)Mem_Malloc(nSUIDs * sizeof(signed char), + "fill_prog_netCDF_vals()", + LogInfo); + if(LogInfo->stopRun) { + return; // Exit function prematurely due to error + } + + for(suid = 0; suid < nSUIDs; suid++) { + SW_DOM_calc_ncSuid(SW_Domain, suid, ncSuid); + + get_single_uint_val(domFileID, domVarID, ncSuid, &domStatus, LogInfo); + if(LogInfo->stopRun) { + goto freeMem; // Exit function prematurely due to error + } + + vals[suid] = (domStatus == NC_FILL_UINT) ? NC_FILL_BYTE : PRGRSS_READY; + } + + fill_netCDF_var_byte(progFileID, progVarID, vals, start, count, LogInfo); + nc_sync(progFileID); + + // Free allocated memory + freeMem: { + free(vals); + } +} + /** * @brief Create a dimension within a netCDF file * @@ -1241,7 +1376,6 @@ static void fill_netCDF_with_global_atts(SW_NETCDF* SW_netCDF, int* ncFileID, char sourceStr[40]; // 40 - valid size of the SOILWAT2 global `SW2_VERSION` + "SOILWAT2" char creationDateStr[21]; // 21 - valid size to hold a string of format YYYY-MM-DDTHH:MM:SSZ - time_t t = time(NULL); int attNum; const int numGlobAtts = (strcmp(domType, "s") == 0) ? 14 : 13; // Do or do not include "featureType" @@ -1268,7 +1402,55 @@ static void fill_netCDF_with_global_atts(SW_NETCDF* SW_netCDF, int* ncFileID, // Fill `sourceStr` and `creationDateStr` snprintf(sourceStr, 40, "SOILWAT2%s", SW2_VERSION); - strftime(creationDateStr, sizeof creationDateStr, "%FT%TZ", gmtime(&t)); + timeStringISO8601(creationDateStr, sizeof creationDateStr); + + // Write out the necessary global attributes that are listed above + for(attNum = 0; attNum < numGlobAtts; attNum++) { + write_str_att(attNames[attNum], attVals[attNum], NC_GLOBAL, *ncFileID, LogInfo); + if(LogInfo->stopRun) { + return; // Exit function prematurely due to error + } + } +} + +/** + * @brief Overwrite specific global attributes into a new file + * + * @param[in] ncFileID Identifier of the open netCDF file to write all information to + * @param[in] domType Type of domain in which simulations are running + * (gridcell/sites) + * @param[in] freqAtt Value of a global attribute "frequency" (may be "fx", + * "day", "week", "month", or "year") + * @param[in] isInputFile Specifies if the file being written to is input + * @param[in,out] LogInfo Holds information dealing with logfile output +*/ +static void update_netCDF_global_atts(int* ncFileID, const char* domType, + const char* freqAtt, Bool isInputFile, LOG_INFO* LogInfo) { + + char sourceStr[40]; // 40 - valid size of the SOILWAT2 global `SW2_VERSION` + "SOILWAT2" + char creationDateStr[21]; // 21 - valid size to hold a string of format YYYY-MM-DDTHH:MM:SSZ + + int attNum; + const int numGlobAtts = (strcmp(domType, "s") == 0) ? 5 : 4; // Do or do not include "featureType" + const char* attNames[] = { + "source", "creation_date", "product", "frequency", "featureType" + }; + + const char* productStr = (isInputFile) ? "model-input" : "model-output"; + const char* featureTypeStr; + if(strcmp(domType, "s") == 0) { + featureTypeStr = (dimExists("time", *ncFileID)) ? "timeSeries" : "point"; + } else { + featureTypeStr = ""; + } + + const char* attVals[] = { + sourceStr, creationDateStr, productStr, freqAtt, featureTypeStr + }; + + // Fill `sourceStr` and `creationDateStr` + snprintf(sourceStr, 40, "SOILWAT2%s", SW2_VERSION); + timeStringISO8601(creationDateStr, sizeof creationDateStr); // Write out the necessary global attributes that are listed above for(attNum = 0; attNum < numGlobAtts; attNum++) { @@ -1331,6 +1513,74 @@ static void fill_netCDF_with_invariants(SW_NETCDF* SW_netCDF, char* domType, isInputFile, LogInfo); } +/** + * @brief Create a new variable by calculating the dimensions + * and writing attributes + * + * @param[in] ncFileID Identifier of the netCDF file + * @param[in] newVarType Type of the variable to create + * @param[in] timeSize Size of "time" dimension + * @param[in] vertSize Size of "vertical" dimension + * @param[in] varName Name of variable to write + * @param[in] attNames Attribute names that the new variable will contain + * @param[in] attVals Attribute values that the new variable will contain + * @param[in] numAtts Number of attributes being sent in + * @param[in,out] LogInfo Holds information dealing with logfile output +*/ +static void create_full_var(int* ncFileID, int newVarType, + unsigned long timeSize, unsigned long vertSize, const char* varName, + const char* attNames[], const char* attVals[], int numAtts, + LOG_INFO* LogInfo) { + + int dimArrSize = 0, index, varID = 0; + int dimIDs[4]; // Maximum expected number of dimensions + Bool siteDimExists = dimExists("site", *ncFileID); + const char* latName = (dimExists("lat", *ncFileID)) ? "lat" : "y"; + const char* lonName = (dimExists("lon", *ncFileID)) ? "lon" : "x"; + int numConstDims = (siteDimExists) ? 1 : 2; + const char* thirdDim = (siteDimExists) ? "site" : latName; + const char* constDimNames[] = {thirdDim, lonName}; + const char* timeVertNames[] = {"time", "vertical"}; + unsigned long timeVertVals[] = {timeSize, vertSize}; + int numTimeVertVals = 2; + + for(index = 0; index < numTimeVertVals; index++) { + if(timeVertVals[index] > 0) { + create_netCDF_dim(timeVertNames[index], timeSize, ncFileID, + &dimIDs[dimArrSize], LogInfo); + if(LogInfo->stopRun) { + return; // Exit function prematurely due to error + } + + dimArrSize++; + } + } + + for(index = 0; index < numConstDims; index++) { + get_dim_identifier(*ncFileID, constDimNames[index], + &dimIDs[dimArrSize], LogInfo); + if(LogInfo->stopRun) { + return; // Exit function prematurely due to error + } + + dimArrSize++; + } + + for(index = 0; index < numAtts; index++) { + write_str_att(attNames[index], attVals[index], + varID, *ncFileID, LogInfo); + if(LogInfo->stopRun) { + return; // Exit function prematurely due to error + } + } + + create_netCDF_var(&varID, varName, dimIDs, ncFileID, newVarType, + dimArrSize, LogInfo); + if(LogInfo->stopRun) { + return; // Exit function prematurely due to error + } +} + /* =================================================== */ /* Global Function Definitions */ /* --------------------------------------------------- */ @@ -1356,7 +1606,7 @@ void SW_NC_check(SW_DOMAIN* SW_Domain, int ncFileID, const char* fileName, const char *geoCRS = "crs_geogsc", *projCRS = "crs_projsc"; Bool geoCRSExists = varExists(ncFileID, geoCRS); Bool projCRSExists = varExists(ncFileID, projCRS); - char* impliedDomType = (dimExists("site", ncFileID)) ? "s" : "xy"; + const char* impliedDomType = (dimExists("site", ncFileID)) ? "s" : "xy"; Bool dimMismatch = swFALSE; size_t latDimVal = 0, lonDimVal = 0, SDimVal = 0; @@ -1402,10 +1652,9 @@ void SW_NC_check(SW_DOMAIN* SW_Domain, int ncFileID, const char* fileName, double projStdParallel[2]; // Compare to standard_parallel is projected CRS int attNum; - char* attFailMsg = "The attribute '%s' of the variable '%s' " - "within the file %s does not match the one " - "in the domain input file. Please make sure " - "these match."; + const char* attFailMsg = "The attribute '%s' of the variable '%s' " + "within the file %s does not match the one in the domain input " + "file. Please make sure these match."; /* Make sure the domain types are consistent @@ -1427,7 +1676,7 @@ void SW_NC_check(SW_DOMAIN* SW_Domain, int ncFileID, const char* fileName, return; // Exit function prematurely due to error } - dimMismatch = SDimVal != SW_Domain->nDimS; + dimMismatch = (Bool) (SDimVal != SW_Domain->nDimS); } else if(strcmp(impliedDomType, "xy") == 0) { if(geoIsPrimCRS && geoCRSExists) { get_dim_val(ncFileID, "lat", &latDimVal, LogInfo); @@ -1453,8 +1702,8 @@ void SW_NC_check(SW_DOMAIN* SW_Domain, int ncFileID, const char* fileName, return; // Exit function prematurely due to error } - dimMismatch = latDimVal != SW_Domain->nDimY || - lonDimVal != SW_Domain->nDimX; + dimMismatch = (Bool) (latDimVal != SW_Domain->nDimY || + lonDimVal != SW_Domain->nDimX); } if(dimMismatch) { @@ -1604,6 +1853,12 @@ void SW_NC_create_domain_template(SW_DOMAIN* SW_Domain, LOG_INFO* LogInfo) { return; // Exit prematurely due to error } + #if defined(SOILWAT) + if(LogInfo->printProgressMsg) { + sw_message("is creating a domain template ..."); + } + #endif + if(nc_create(DOMAIN_TEMP, NC_NETCDF4, domFileID) != NC_NOERR) { LogError(LogInfo, LOGERROR, "Could not create new domain template due " "to something internal."); @@ -1667,24 +1922,206 @@ void SW_NC_create_domain_template(SW_DOMAIN* SW_Domain, LOG_INFO* LogInfo) { } /** - * @brief Copy domain netCDF to a new file and make it ready for a new variable + * @brief Copy domain netCDF to a new file and add a new variable * + * @param[in] domFile Name of the domain netCDF + * @param[in] domFileID Identifier of the domain netCDF file * @param[in] fileName Name of the netCDF file to create + * @param[in] newFileID Identifier of the netCDF file to create + * @param[in] newVarType Type of the variable to create * @param[in] timeSize Size of "time" dimension * @param[in] vertSize Size of "vertical" dimension * @param[in] varName Name of variable to write - * @param[in] varAttributes Attributes that the new variable will contain - * @param[in,out] LogInfo Holds information on warnings and errors + * @param[in] attNames Attribute names that the new variable will contain + * @param[in] attVals Attribute values that the new variable will contain + * @param[in] numAtts Number of attributes being sent in + * @param[in] isInput Specifies if the created file will be input or output + * @param[in] freq Value of the global attribute "frequency" + * @param[in,out] LogInfo Holds information dealing with logfile output */ -void SW_NC_create_template(const char* fileName, unsigned long timeSize, - unsigned long vertSize, const char* varName, - char* varAttributes[], LOG_INFO* LogInfo) { - (void) fileName; - (void) timeSize; - (void) vertSize; - (void) varName; - (void) varAttributes; - (void) LogInfo; +void SW_NC_create_template(const char* domFile, int domFileID, + const char* fileName, int* newFileID, int newVarType, + unsigned long timeSize, unsigned long vertSize, const char* varName, + const char* attNames[], const char* attVals[], int numAtts, Bool isInput, + const char* freq, LOG_INFO* LogInfo) { + + Bool siteDimExists = dimExists("site", domFileID); + const char* domType = (siteDimExists) ? "s" : "xy"; + + + CopyFile(domFile, fileName, LogInfo); + if(LogInfo->stopRun) { + return; // Exit function prematurely due to error + } + + if(nc_open(fileName, NC_WRITE, newFileID) != NC_NOERR) { + LogError(LogInfo, LOGERROR, "An error occurred when attempting " + "to access the new file %s.", fileName); + return; // Exit function prematurely due to error + } + + if(!varExists(*newFileID, varName)) { + create_full_var(newFileID, newVarType, timeSize, vertSize, varName, + attNames, attVals, numAtts, LogInfo); + if(LogInfo->stopRun) { + return; // Exit function prematurely due to error + } + } + + update_netCDF_global_atts(newFileID, domType, freq, isInput, LogInfo); +} + +/** + * @brief Create a progress netCDF file + * + * @param[in,out] SW_Domain Struct of type SW_DOMAIN holding constant + * temporal/spatial information for a set of simulation runs + * @param[in,out] LogInfo Holds information dealing with logfile output +*/ +void SW_NC_create_progress(SW_DOMAIN* SW_Domain, LOG_INFO* LogInfo) { + + SW_NETCDF* SW_netCDF = &SW_Domain->netCDFInfo; + Bool primCRSIsGeo = SW_Domain->netCDFInfo.primary_crs_is_geographic; + Bool domTypeIsS = (Bool) (strcmp(SW_Domain->DomainType, "s") == 0); + const char* projGridMap = "crs_projsc: x y crs_geogsc: lat lon"; + const char* geoGridMap = "crs_geogsc"; + const char* sCoord = "lat lon site"; + const char* xyCoord = "lat lon"; + const char* coord = domTypeIsS ? sCoord : xyCoord; + const char* grid_map = primCRSIsGeo ? geoGridMap : projGridMap; + const char* attNames[] = {"long_name", "units", "grid_mapping", + "coordinates"}; + const char* attVals[] = {"simulation progress", "1", grid_map, coord}; + const int numAtts = 4; + int numValsToWrite; + const signed char fillVal = NC_FILL_BYTE; + const signed char flagVals[] = {PRGRSS_FAIL, PRGRSS_READY, PRGRSS_DONE}; + const char* flagMeanings = "simulation_error ready_to_simulate simulation_complete"; + const char* progVarName = SW_netCDF->varNC[vNCprog]; + const char* freq = "fx"; + + int domFileID = SW_netCDF->ncFileIDs[vNCdom]; + int* progFileID = &SW_netCDF->ncFileIDs[vNCprog]; + const char* domFileName = SW_netCDF->InFilesNC[vNCdom]; + const char* progFileName = SW_netCDF->InFilesNC[vNCprog]; + int* progVarID = &SW_netCDF->ncVarIDs[vNCprog]; + + Bool progFileIsDom = (Bool) (strcmp(progFileName, domFileName) == 0); + Bool progFileExists = FileExists(progFileName); + Bool progVarExists = varExists(*progFileID, progVarName); + Bool createOrModFile = (Bool) (!progFileExists || (progFileIsDom && !progVarExists)); + + /* + If the progress file is not to be created or modified, check it + + See if the progress variable exists within it's file, also handling + the case where the progress variable is in the domain netCDF + + In addition to making sure the file exists, make sure the progress + variable is present + */ + if(!createOrModFile) { + SW_NC_check(SW_Domain, *progFileID, progFileName, LogInfo); + } else { + + #if defined(SOILWAT) + if(LogInfo->printProgressMsg) { + sw_message("is creating a progress tracker ..."); + } + #endif + + if(progFileExists) { + nc_redef(*progFileID); + + create_full_var(progFileID, NC_BYTE, 0, 0, progVarName, + attNames, attVals, numAtts, LogInfo); + } else { + SW_NC_create_template(domFileName, domFileID, progFileName, + progFileID, NC_BYTE, 0, 0, progVarName, attNames, attVals, + numAtts, swFALSE, freq, LogInfo); + } + + if(LogInfo->stopRun) { + return; // Exit function prematurely due to error + } + + get_var_identifier(*progFileID, progVarName, progVarID, LogInfo); + if(LogInfo->stopRun) { + return; // Exit function prematurely due to error + } + + // If the progress existed before this function was called, + // do not set the new attributes + if(!progVarExists) { + // Add attribute "_FillValue" to the progress variable + numValsToWrite = 1; + write_byte_att("_FillValue", &fillVal, *progVarID, *progFileID, numValsToWrite, LogInfo); + if(LogInfo->stopRun) { + return; // Exit function prematurely due to error + } + + // Add attributes "flag_values" and "flag_meanings" + numValsToWrite = 3; + write_byte_att("flag_values", flagVals, *progVarID, *progFileID, numValsToWrite, LogInfo); + if(LogInfo->stopRun) { + return; // Exit function prematurely due to error + } + + write_str_att("flag_meanings", flagMeanings, *progVarID, *progFileID, LogInfo); + if(LogInfo->stopRun) { + return; // Exit function prematurely due to error + } + } + + + nc_enddef(*progFileID); + + fill_prog_netCDF_vals(SW_Domain, LogInfo); + } +} + +/** + * @brief Mark a site/gridcell as completed (success/fail) in the progress file + * + * @param[in] isFailure Did simulation run fail or succeed? + * @param[in] domType Type of domain in which simulations are running + * (gridcell/sites) + * @param[in] progFileID Identifier of the progress netCDF file + * @param[in] progVarID Identifier of the progress variable within the progress netCDF + * @param[in] ncSUID Current simulation unit identifier for which is used + * to get data from netCDF + * @param[in,out] LogInfo Holds information dealing with logfile output +*/ +void SW_NC_set_progress(Bool isFailure, const char* domType, int progFileID, + int progVarID, unsigned long ncSUID[], + LOG_INFO* LogInfo) { + + const signed char mark = (isFailure) ? PRGRSS_FAIL : PRGRSS_DONE; + size_t count1D[] = {1}, count2D[] = {1, 1}; + size_t *count = (strcmp(domType, "s") == 0) ? count1D : count2D; + + fill_netCDF_var_byte(progFileID, progVarID, &mark, ncSUID, count, LogInfo); + nc_sync(progFileID); +} + +/** + * @brief Check if a site/grid cell is marked to be run in the progress + * netCDF + * + * @param[in] progFileID Identifier of the progress netCDF file + * @param[in] progVarID Identifier of the progress variable + * @param[in] ncSUID Current simulation unit identifier for which is used + * to get data from netCDF + * @param[in,out] LogInfo Holds information dealing with logfile output +*/ +Bool SW_NC_check_progress(int progFileID, int progVarID, + unsigned long ncSUID[], LOG_INFO* LogInfo) { + + signed char progVal = 0; + + get_single_byte_val(progFileID, progVarID, ncSUID, &progVal, LogInfo); + + return (Bool) (!LogInfo->stopRun && progVal == PRGRSS_READY); } /** @@ -1702,7 +2139,8 @@ void SW_NC_read_inputs(SW_ALL* sw, SW_DOMAIN* SW_Domain, size_t ncSUID[], LOG_INFO* LogInfo) { int file, varNum; - Bool domTypeS = Str_CompareI(SW_Domain->DomainType, "s") == 0; + Bool domTypeS = (Bool) (Str_CompareI(SW_Domain->DomainType, (char *)"s") == 0); + const int numInFilesNC = 1; const int numDomVals = 2; const int numVals[] = {numDomVals}; const int ncFileIDs[] = {SW_Domain->netCDFInfo.ncFileIDs[vNCdom]}; @@ -1719,7 +2157,7 @@ void SW_NC_read_inputs(SW_ALL* sw, SW_DOMAIN* SW_Domain, size_t ncSUID[], For the domain type "xy", the index of the variable "y" is the first in "ncSUID" and the index of the variable "x" is the second in "ncSUID" */ - for(file = 0; file < SW_NVARNC; file++) { + for(file = 0; file < numInFilesNC; file++) { for(varNum = 0; varNum < numVals[file]; varNum++) { ncIndex = (domTypeS) ? 0 : varNum % 2; @@ -1761,10 +2199,10 @@ void SW_NC_check_input_files(SW_DOMAIN* SW_Domain, LOG_INFO* LogInfo) { * @param[out] LogInfo Holds information on warnings and errors */ void SW_NC_read(SW_NETCDF* SW_netCDF, PATH_INFO* PathInfo, LOG_INFO* LogInfo) { - static const char* possibleKeys[NUM_NC_IN_KEYS] = {"domain"}; + static const char* possibleKeys[NUM_NC_IN_KEYS] = {"domain", "progress"}; static const Bool requiredKeys[NUM_NC_IN_KEYS] = - {swTRUE}; - Bool hasKeys[NUM_NC_IN_KEYS] = {swFALSE}; + {swTRUE, swTRUE}; + Bool hasKeys[NUM_NC_IN_KEYS] = {swFALSE, swFALSE}; FILE *f; char inbuf[MAX_FILENAMESIZE], *MyFileName; @@ -1787,6 +2225,10 @@ void SW_NC_read(SW_NETCDF* SW_netCDF, PATH_INFO* PathInfo, LOG_INFO* LogInfo) { SW_netCDF->varNC[vNCdom] = Str_Dup(varName, LogInfo); SW_netCDF->InFilesNC[vNCdom] = Str_Dup(path, LogInfo); break; + case vNCprog: + SW_netCDF->varNC[vNCprog] = Str_Dup(varName, LogInfo); + SW_netCDF->InFilesNC[vNCprog] = Str_Dup(path, LogInfo); + break; default: LogError(LogInfo, LOGWARN, "Ignoring unknown key in %s, %s", MyFileName, key); @@ -1888,28 +2330,57 @@ void SW_NC_deconstruct(SW_NETCDF* SW_netCDF) { } /** - * @brief Open all netCDF files that should be open throughout the program + * @brief Open netCDF file(s) that contain(s) domain and progress variables + * + * These files are kept open during simulations + * * to read geographic coordinates from the domain + * * to identify and update progress * * @param[in,out] SW_netCDF Struct of type SW_NETCDF holding constant * netCDF file information - * @param[out] LogInfo Struct of type SW_NETCDF holding constant - * netCDF file information + * @param[out] LogInfo Holds information on warnings and errors */ -void SW_NC_open_files(SW_NETCDF* SW_netCDF, LOG_INFO* LogInfo) { - int fileNum; - - for(fileNum = 0; fileNum < SW_NVARNC; fileNum++) { - if(FileExists(SW_netCDF->InFilesNC[fileNum])) { - if(nc_open(SW_netCDF->InFilesNC[fileNum], NC_NOWRITE, - &SW_netCDF->ncFileIDs[fileNum]) != NC_NOERR) { - +void SW_NC_open_dom_prog_files(SW_NETCDF* SW_netCDF, LOG_INFO* LogInfo) { + int fileNum, openType = NC_WRITE, *fileID; + char* fileName, *domFile = SW_netCDF->InFilesNC[vNCdom]; + char* progFile = SW_netCDF->InFilesNC[vNCprog]; + Bool progFileDomain = (Bool) (strcmp(domFile, progFile) == 0); + + // Open the domain/progress netCDF + for(fileNum = vNCdom; fileNum <= vNCprog; fileNum++) { + fileName = SW_netCDF->InFilesNC[fileNum]; + fileID = &SW_netCDF->ncFileIDs[fileNum]; + + if(FileExists(fileName)) { + if(nc_open(fileName, openType, fileID) != NC_NOERR) { LogError(LogInfo, LOGERROR, "An error occurred when opening %s.", - SW_netCDF->InFilesNC[fileNum]); - + fileName); return; // Exit function prematurely due to error } + + /* + Get the ID for the domain variable and the progress variable if + it is not in the domain netCDF or it exists in the domain netCDF + */ + if(fileNum == vNCdom || !progFileDomain || + varExists(*fileID, SW_netCDF->varNC[fileNum])) { + + get_var_identifier(*fileID, SW_netCDF->varNC[fileNum], + &SW_netCDF->ncVarIDs[fileNum], LogInfo); + if(LogInfo->stopRun) { + return; // Exit function prematurely due to error + } + } } } + + // If the progress variable is contained in the domain netCDF, then + // close the (redundant) progress file identifier + // and use instead the (equivalent) domain file identifier + if(progFileDomain) { + nc_close(SW_netCDF->ncFileIDs[vNCprog]); + SW_netCDF->ncFileIDs[vNCprog] = SW_netCDF->ncFileIDs[vNCdom]; + } } /** @@ -1925,3 +2396,47 @@ void SW_NC_close_files(SW_NETCDF* SW_netCDF) { nc_close(SW_netCDF->ncFileIDs[fileNum]); } } + +/** + * @brief Deep copy a source instance of SW_NETCDF into a destination instance + * + * @param[in] source Source struct of type SW_NETCDF to copy + * @param[out] dest Destination struct of type SW_NETCDF to be copied into + * @param[out] LogInfo Holds information on warnings and errors +*/ +void SW_NC_deepCopy(SW_NETCDF* source, SW_NETCDF* dest, LOG_INFO* LogInfo) { + int index, numIndivCopy = 5; + + char* srcStrs[] = { + source->title, source->author, source->institution, source->comment, + source->coordinate_system + }; + + char** destStrs[] = { + &dest->title, &dest->author, &dest->institution, &dest->comment, + &dest->coordinate_system + }; + + memcpy(dest, source, sizeof(*dest)); + + SW_NC_init_ptrs(dest); + + for(index = 0; index < numIndivCopy; index++) { + *destStrs[index] = Str_Dup(srcStrs[index], LogInfo); + if(LogInfo->stopRun) { + return; // Exit function prematurely due to + } + } + + for(index = 0; index < SW_NVARNC; index++) { + dest->varNC[index] = Str_Dup(source->varNC[index], LogInfo); + if(LogInfo->stopRun) { + return; // Exit function prematurely due to error + } + + dest->InFilesNC[index] = Str_Dup(source->InFilesNC[index], LogInfo); + if(LogInfo->stopRun) { + return; // Exit function prematurely due to error + } + } +} diff --git a/src/Times.c b/src/Times.c index a3001b830..52c936963 100644 --- a/src/Times.c +++ b/src/Times.c @@ -364,7 +364,7 @@ void SW_WT_ReportTime(SW_WALLTIME wt, LOG_INFO* LogInfo) { total_time = diff_walltime(wt.timeStart, wt.has_walltime); // negative if failed - if (GT(total_time, 0.)) { + if (GE(total_time, 0.)) { fprintf(logfp, " * Total wall time: %.2f [seconds]\n", total_time); } else { fprintf(logfp, " * Wall time failed.\n"); @@ -404,3 +404,15 @@ void SW_WT_ReportTime(SW_WALLTIME wt, LOG_INFO* LogInfo) { ); } } + + +/** + @brief Current date and time in UTC formatted according to ISO 8601 + + @param[out] timeString Character array that returns the formatted time. + @param[in] stringLength Length of timeString (should be at least 21). +*/ +void timeStringISO8601(char *timeString, int stringLength) { + time_t t = time(NULL); + strftime(timeString, stringLength, "%FT%TZ", gmtime(&t)); +} diff --git a/src/filefuncs.c b/src/filefuncs.c index 884f0a92b..ff77ba65f 100644 --- a/src/filefuncs.c +++ b/src/filefuncs.c @@ -15,6 +15,7 @@ #include "include/filefuncs.h" #include "include/myMemory.h" +#include "include/Times.h" /* 01/05/2011 (drs) removed unused variable *p from MkDir() @@ -207,6 +208,23 @@ void LogError(LOG_INFO* LogInfo, const int mode, const char *fmt, ...) { } + + +/** + @brief Print a SOILWAT2 status message + + @param[in] msg Message string. +*/ +void sw_message(const char *msg) { + char timeString[21]; + timeStringISO8601(timeString, sizeof timeString); + + swprintf("SOILWAT2 (%s) %s\n", timeString, msg); +} + + + + /**************************************************************/ Bool GetALine(FILE *f, char buf[], int numChars) { /* Read a line of possibly commented input from the file *f. diff --git a/tests/example/Input_nc/files_nc.in b/tests/example/Input_nc/files_nc.in index 91157be10..940593a56 100644 --- a/tests/example/Input_nc/files_nc.in +++ b/tests/example/Input_nc/files_nc.in @@ -5,4 +5,5 @@ # The second value is the path of the netCDF file. # This convention eliminates the possibility of incorrect reads due to line dependencies. -domain domain Input_nc/domain.nc \ No newline at end of file +domain domain Input_nc/domain.nc +progress progress Input_nc/progress.nc # Note: progress can be a separate file or the same as domain diff --git a/tests/gtests/sw_testhelpers.cc b/tests/gtests/sw_testhelpers.cc index 97548dc84..856a2b31f 100644 --- a/tests/gtests/sw_testhelpers.cc +++ b/tests/gtests/sw_testhelpers.cc @@ -144,7 +144,7 @@ void setup_testGlobalSoilwatTemplate() { // Initialize SOILWAT2 variables and read values from example input file sw_init_logs(NULL, &LogInfo); - SW_F_init_ptrs(template_SW_Domain.PathInfo.InFiles); + SW_DOM_init_ptrs(&template_SW_Domain); SW_CTL_init_ptrs(&template_SW_All); template_SW_Domain.PathInfo.InFiles[eFirst] = Str_Dup(DFLT_FIRSTFILE, &LogInfo); @@ -184,6 +184,6 @@ void setup_testGlobalSoilwatTemplate() { /* Free allocated memory of global test variables */ void teardown_testGlobalSoilwatTemplate() { - SW_F_deconstruct(template_SW_Domain.PathInfo.InFiles); + SW_DOM_deconstruct(&template_SW_Domain); SW_CTL_clear_model(swTRUE, &template_SW_All); }