From 6f112fdd656a29dbe47b96edac57030fe7ae78a4 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Tue, 16 Apr 2024 12:06:21 -0400 Subject: [PATCH] Unit conversion for netCDF output with udunits2 - addressing issue #392 - unit conversion is activated if compiled with `CPPFLAGS='-DSWNETCDF -DSWUDUNITS' make all` (additional flags available to specify paths for udunits2 headers and libraries, see makefile) - users request output units via field "netCDF units" of input file "SW2_netCDF_output_variables.tsv"; internal units are used if user requested units cannot be converted from internal units - SOILWAT2 internal units (text string) are stored in the attribute "units_sw" of struct "SW_OUTPUT" for each (netCDF) output variable - udunits2 unit converters are stored in the attribute "uconv" of struct "SW_OUTPUT" for each (netCDF) output variable - new SW_NC_create_units_converters() creates udunits2 unit converters from internal units to user requested output units - SW_NC_write_output() converts units before writing to netCDF output files if a unit converter is available (if no unit converter is available, then output is written using internal units) - SW_NC_dealloc_outputkey_var_info() de-allocates unit converter memory --- NEWS.md | 8 + include/SW_Defines.h | 12 ++ include/SW_datastructs.h | 2 + include/SW_netCDF.h | 6 +- makefile | 46 ++++- src/SW_Main.c | 8 + src/SW_Output.c | 51 ++--- src/SW_netCDF.c | 437 ++++++++++++++++++++++++++++++--------- 8 files changed, 437 insertions(+), 133 deletions(-) diff --git a/NEWS.md b/NEWS.md index ce374dbdd..1e5724c39 100644 --- a/NEWS.md +++ b/NEWS.md @@ -34,6 +34,14 @@ progress tracking makes re-starts after partial completion of the simulation set by an earlier execution possible. +* SOILWAT2 can now be compiled with `"udunits2"` to convert output to user + requested units (#392; @dschlaep, @N1ckP3rsl3y). + * Unit conversion is available only in `"netCDF"`-mode and if compiled + with the new preprocessor definition `"SWUDUNITS"` + and if the `"udunits2"` library is available, + e.g., `CPPFLAGS='-DSWNETCDF -DSWUDUNITS' make all`. + * Users request output units via field `"netCDF units"` of the + input file `"SW2_netCDF_output_variables.tsv"`. * Tests now utilize the same template/deep-copy approach (@dschlaep), i.e., input files from `test/example/` populate a `"template"` and diff --git a/include/SW_Defines.h b/include/SW_Defines.h index 92e8ed451..90e1658c8 100644 --- a/include/SW_Defines.h +++ b/include/SW_Defines.h @@ -27,6 +27,9 @@ #include "external/pcg/pcg_basic.h" // see https://github.com/imneme/pcg-c-basic #endif +#if defined(SWNETCDF) && defined(SWUDUNITS) +#include +#endif #ifdef __cplusplus extern "C" { @@ -287,6 +290,15 @@ typedef int sw_random_t; /* not used by rSOILWAT2; it uses instead R's RNG */ typedef pcg32_random_t sw_random_t; #endif +/* =================================================== */ +/* unit conversion structs */ +/* --------------------------------------------------- */ +#if defined(SWNETCDF) && defined(SWUDUNITS) +typedef cv_converter sw_converter_t; /* udunits unit converter */ +#else +typedef int sw_converter_t; +#endif + #ifdef __cplusplus } diff --git a/include/SW_datastructs.h b/include/SW_datastructs.h index 69e0d5471..f62506759 100644 --- a/include/SW_datastructs.h +++ b/include/SW_datastructs.h @@ -986,6 +986,8 @@ typedef struct { #if defined(SWNETCDF) Bool* reqOutputVars; /* Output the respecitve variable */ char*** outputVarInfo; /* Information about the respective output variable */ + char **units_sw; /* Units internally utilized by SOILWAT2 */ + sw_converter_t **uconv; /* udunits2 unit converter from SOILWAT2 units to user-requested units */ #endif } SW_OUTPUT; diff --git a/include/SW_netCDF.h b/include/SW_netCDF.h index 3ef27a025..2ab9872ad 100644 --- a/include/SW_netCDF.h +++ b/include/SW_netCDF.h @@ -85,13 +85,15 @@ 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_read_out_vars(SW_OUTPUT* SW_Output, SW_GEN_OUT *GenOutput, char* InFiles[], SW_VEGESTAB_INFO** parms, LOG_INFO* LogInfo); +void SW_NC_create_units_converters(SW_OUTPUT* SW_Output, IntUS * nVars, LOG_INFO* LogInfo); void SW_NC_init_ptrs(SW_NETCDF* SW_netCDF); void SW_NC_deconstruct(SW_NETCDF* SW_netCDF); 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); -void SW_NC_alloc_outvars(char**** outkeyVars, int nVar, LOG_INFO* LogInfo); -void SW_NC_alloc_outReq(Bool** reqOutVar, int nVar, LOG_INFO* LogInfo); +void SW_NC_dealloc_outputkey_var_info(SW_OUTPUT* SW_Output, IntUS k, IntUS *nVars); +void SW_NC_alloc_output_var_info(SW_OUTPUT* SW_Output, IntUS *nVars, LOG_INFO* LogInfo); +void SW_NC_alloc_outputkey_var_info(SW_OUTPUT* currOut, IntUS nVar, LOG_INFO* LogInfo); void SW_NC_alloc_files(char*** ncOutFiles, int numFiles, LOG_INFO* LogInfo); #ifdef __cplusplus diff --git a/makefile b/makefile index b86b07d9e..5f788e54c 100644 --- a/makefile +++ b/makefile @@ -133,15 +133,24 @@ lib_gmock := $(dir_build_test)/lib$(gmock).a #------ netCDF SUPPORT # `CPPFLAGS=-DSWNETCDF make all` +# `CPPFLAGS='-DSWNETCDF -DSWUDUNITS' make all` # User-specified paths to netCDF header and library: # `CPPFLAGS=-DSWNETCDF NC_CFLAGS="-I/path/to/include" NC_LIBS="-L/path/to/lib" make all` +# User-specified paths to headers and libraries of netCDF, udunits2 and expat: +# `CPPFLAGS='-DSWNETCDF -DSWUDUNITS' NC_CFLAGS="-I/path/to/include" UD_CFLAGS="-I/path/to/include" EX_CFLAGS="-I/path/to/include" NC_LIBS="-L/path/to/lib" UD_LIBS="-L/path/to/lib" EX_LIBS="-L/path/to/lib" make all` + ifneq (,$(findstring -DSWNETCDF,$(CPPFLAGS))) # define makefile variable SWNETCDF if defined via CPPFLAGS SWNETCDF = 1 endif +ifneq (,$(findstring -DSWUDUNITS,$(CPPFLAGS))) + # define makefile variable SWUDUNITS if defined via CPPFLAGS + SWUDUNITS = 1 +endif + ifdef SWNETCDF ifndef NC_CFLAGS # use `nc-config` if user did not provide NC_CFLAGS @@ -156,13 +165,42 @@ ifdef SWNETCDF NC_LIBS += -lnetcdf endif endif + + ifdef SWUDUNITS + ifndef UD_CFLAGS + # assume headers are at same path as those for nc + UD_CFLAGS := $(NC_CFLAGS)/udunits2 + endif + + ifndef UD_LIBS + UD_LIBS := -ludunits2 + endif + + ifndef EX_LIBS + EX_LIBS := -lexpat + endif + else + # if SWUDUNITS is not defined, then unset + UD_CFLAGS := + EX_CFLAGS := + UD_LIBS := + EX_LIBS := + endif + else - # unset NC_CFLAGS and NC_LIBS if SWNETCDF is not defined + # if SWNETCDF is not defined, then unset + SWUDUNITS := NC_CFLAGS := + UD_CFLAGS := + EX_CFLAGS := NC_LIBS := + UD_LIBS := + EX_LIBS := endif + + #------ STANDARDS # googletest requires c++14 and POSIX API # see https://github.com/google/oss-policies-info/blob/main/foundational-cxx-support-matrix.md @@ -224,8 +262,8 @@ instr_flags_severe := \ sw_CPPFLAGS := $(CPPFLAGS) $(sw_info) -MMD -MP -I. sw_CPPFLAGS_bin := $(sw_CPPFLAGS) -I$(dir_build_sw2) sw_CPPFLAGS_test := $(sw_CPPFLAGS) -I$(dir_build_test) -sw_CFLAGS := $(CFLAGS) $(NC_CFLAGS) -sw_CXXFLAGS := $(CXXFLAGS) $(NC_CFLAGS) +sw_CFLAGS := $(CFLAGS) $(NC_CFLAGS) $(UD_CFLAGS) $(EX_CFLAGS) +sw_CXXFLAGS := $(CXXFLAGS) $(NC_CFLAGS) $(UD_CFLAGS) $(EX_CFLAGS) # `SW2_FLAGS` can be used to pass in additional flags bin_flags := -O2 -fno-stack-protector $(SW2_FLAGS) @@ -238,7 +276,7 @@ gtest_flags := -D_POSIX_C_SOURCE=200809L # googletest requires POSIX API # order of libraries is important for GNU gcc (libSOILWAT2 depends on libm) sw_LDFLAGS_bin := $(LDFLAGS) -L$(dir_bin) sw_LDFLAGS_test := $(LDFLAGS) -L$(dir_bin) -L$(dir_build_test) -sw_LDLIBS := $(LDLIBS) $(NC_LIBS) -lm +sw_LDLIBS := $(LDLIBS) $(NC_LIBS) $(UD_LIBS) $(EX_LIBS) -lm target_LDLIBS := -l$(target) $(sw_LDLIBS) test_LDLIBS := -l$(target_test) $(sw_LDLIBS) diff --git a/src/SW_Main.c b/src/SW_Main.c index c713d1438..d77339c05 100644 --- a/src/SW_Main.c +++ b/src/SW_Main.c @@ -168,6 +168,14 @@ int main(int argc, char **argv) { if(LogInfo.stopRun) { goto finishProgram; } + SW_NC_create_units_converters( + sw_template.Output, + sw_template.GenOutput.nvar_OUT, + &LogInfo + ); + if(LogInfo.stopRun) { + goto finishProgram; + } #endif // SWNETCDF SW_OUT_create_files( diff --git a/src/SW_Output.c b/src/SW_Output.c index d7f996a01..343841afe 100644 --- a/src/SW_Output.c +++ b/src/SW_Output.c @@ -1240,6 +1240,8 @@ void SW_OUT_construct(Bool make_soil[], Bool make_regular[], #if defined(SWNETCDF) SW_Output[k].outputVarInfo = NULL; SW_Output[k].reqOutputVars = NULL; + SW_Output[k].units_sw = NULL; + SW_Output[k].uconv = NULL; #endif // assign `get_XXX` functions @@ -1662,33 +1664,7 @@ void SW_OUT_deconstruct(Bool full_reset, SW_ALL *sw) #endif #if defined(SWNETCDF) - if(!isnull(sw->Output[k].outputVarInfo)) { - - for(int varNum = 0; varNum < sw->GenOutput.nvar_OUT[k]; varNum++) { - - if(!isnull(sw->Output[k].outputVarInfo[varNum])) { - - for(int attNum = 0; attNum < NUM_OUTPUT_INFO; attNum++) { - - if(!isnull(sw->Output[k].outputVarInfo[varNum][attNum])) { - free(sw->Output[k].outputVarInfo[varNum][attNum]); - sw->Output[k].outputVarInfo[varNum][attNum] = NULL; - } - } - - free(sw->Output[k].outputVarInfo[varNum]); - sw->Output[k].outputVarInfo[varNum] = NULL; - } - } - - free(sw->Output[k].outputVarInfo); - sw->Output[k].outputVarInfo = NULL; - } - - if(!isnull(sw->Output[k].reqOutputVars)) { - free(sw->Output[k].reqOutputVars); - sw->Output[k].reqOutputVars = NULL; - } + SW_NC_dealloc_outputkey_var_info(sw->Output, k, sw->GenOutput.nvar_OUT); #endif } @@ -3269,18 +3245,11 @@ void SW_OUT_deepCopy(SW_OUTPUT* dest_out, SW_OUTPUT* source_out, } } - SW_NC_alloc_outvars(&dest_out[key].outputVarInfo, - nvar_OUT[key], LogInfo); - if(LogInfo->stopRun) { - return; // Exit function prematurely due to error - } - - SW_NC_alloc_outReq(&dest_out[key].reqOutputVars, nvar_OUT[key], LogInfo); + SW_NC_alloc_outputkey_var_info(&dest_out[key], nvar_OUT[key], LogInfo); if(LogInfo->stopRun) { return; // Exit function prematurely due to error } - if (!isnull(source_out[key].reqOutputVars)) { for(varNum = 0; varNum < nvar_OUT[key]; varNum++) { dest_out[key].reqOutputVars[varNum] = @@ -3296,6 +3265,12 @@ void SW_OUT_deepCopy(SW_OUTPUT* dest_out, SW_OUTPUT* source_out, } } } + + dest_out[key].units_sw[varNum] = + Str_Dup(source_out[key].units_sw[varNum], LogInfo); + if(LogInfo->stopRun) { + return; // Exit function prematurely due to error + } } } } @@ -3303,8 +3278,14 @@ void SW_OUT_deepCopy(SW_OUTPUT* dest_out, SW_OUTPUT* source_out, } else { dest_out[key].reqOutputVars = NULL; dest_out[key].outputVarInfo = NULL; + dest_out[key].units_sw = NULL; + dest_out[key].uconv = NULL; } } + + #if defined(SWNETCDF) + SW_NC_create_units_converters(dest_out, nvar_OUT, LogInfo); + #endif } #endif diff --git a/src/SW_netCDF.c b/src/SW_netCDF.c index 130f6de91..6d1ba3fda 100644 --- a/src/SW_netCDF.c +++ b/src/SW_netCDF.c @@ -3,6 +3,10 @@ #include #include +#if defined(SWUDUNITS) +#include +#endif + #include "include/SW_netCDF.h" #include "include/generic.h" #include "include/filefuncs.h" @@ -950,28 +954,6 @@ static void dealloc_netCDF_domain_vars(double **valsY, double **valsX, } } -/** - * @brief Wrapper function to allocate output request variables - * and output variable information - * - * @param[out] SW_Output SW_OUTPUT array of size SW_OUTNKEYS which holds - * basic output information for all output keys - * @param[in] GenOutput Holds general variables that deal with output - * @param[out] LogInfo Holds information on warnings and errors -*/ -static void alloc_output_var_info(SW_OUTPUT* SW_Output, SW_GEN_OUT *GenOutput, LOG_INFO* LogInfo) { - int key; - - ForEachOutKey(key) { - SW_NC_alloc_outReq(&SW_Output[key].reqOutputVars, GenOutput->nvar_OUT[key], LogInfo); - if(LogInfo->stopRun) { - return; // Exit function prematurely due to error - } - - SW_Output[key].outputVarInfo = NULL; - } -} - /** * @brief Helper function to `fill_domain_netCDF_vars()` to allocate * memory for writing out values @@ -1030,6 +1012,141 @@ static void alloc_netCDF_domain_vars(Bool domTypeIsSite, LogInfo); } + + +/** + * @brief Allocate memory for information in regards to output variables + * respective to SW_OUTPUT instances + * + * @param[out] outkeyVars Holds all information about output variables + * in netCDFs (e.g., output variable name) + * @param[in] nVar Number of variables available for current output key + * @param[out] LogInfo Holds information on warnings and errors +*/ +static void alloc_outvars(char**** outkeyVars, int nVar, LOG_INFO* LogInfo) { + + *outkeyVars = NULL; + + if (nVar > 0) { + + int index, varNum, attNum; + + // Allocate all memory for the variable information in the current + // output key + *outkeyVars = + (char ***)Mem_Malloc(sizeof(char **) * nVar, + "alloc_outvars()", LogInfo); + if(LogInfo->stopRun) { + return; // Exit function prematurely due to error + } + + for(index = 0; index < nVar; index++) { + (*outkeyVars)[index] = NULL; + } + + for(index = 0; index < nVar; index++) { + (*outkeyVars)[index] = + (char **)Mem_Malloc(sizeof(char *) * NUM_OUTPUT_INFO, + "alloc_outvars()", LogInfo); + if(LogInfo->stopRun) { + for(varNum = 0; varNum < index; varNum++) { + free((*outkeyVars)[varNum]); + } + free(*outkeyVars); + return; // Exit function prematurely due to error + } + + for(attNum = 0; attNum < NUM_OUTPUT_INFO; attNum++) { + (*outkeyVars)[index][attNum] = NULL; + } + } + } +} + +/** + * @brief Allocate information about whether or not a variable should + * be output + * + * @param[out] reqOutVar Specifies the number of variables that can be output + * for a given output key + * @param[in] nVar Number of variables available for current output key + * @param[out] LogInfo Holds information on warnings and errors +*/ +static void alloc_outReq(Bool** reqOutVar, int nVar, LOG_INFO* LogInfo) { + + *reqOutVar = NULL; + + if (nVar > 0) { + + // Initialize the variable within SW_OUTPUT which specifies if a variable + // is to be written out or not + *reqOutVar = + (Bool *)Mem_Malloc(sizeof(Bool) * nVar, + "alloc_outReq()", LogInfo); + if(LogInfo->stopRun) { + return; // Exit function prematurely due to error + } + + for(int index = 0; index < nVar; index++) { + (*reqOutVar)[index] = swFALSE; + } + } +} + +/** + * @brief Allocate memory for internal SOILWAT2 units + * + * @param[out] units_sw Array of text representations of SOILWAT2 units + * @param[in] nVar Number of variables available for current output key + * @param[out] LogInfo Holds information on warnings and errors +*/ +static void alloc_unitssw(char*** units_sw, int nVar, LOG_INFO* LogInfo) { + + *units_sw = NULL; + + if (nVar > 0) { + + // Initialize the variable within SW_OUTPUT + *units_sw = (char **)Mem_Malloc(sizeof(char *) * nVar, + "alloc_unitssw()", LogInfo); + if(LogInfo->stopRun) { + return; // Exit function prematurely due to error + } + + for(int index = 0; index < nVar; index++) { + (*units_sw)[index] = NULL; + } + } +} + + +/** + * @brief Allocate memory for udunits2 unit converter + * + * @param[out] uconv Array of pointers to udunits2 unit converter + * @param[in] nVar Number of variables available for current output key + * @param[out] LogInfo Holds information on warnings and errors +*/ +static void alloc_uconv(sw_converter_t ***uconv, int nVar, LOG_INFO* LogInfo) { + + *uconv = NULL; + + if (nVar > 0) { + + *uconv = (sw_converter_t **)Mem_Malloc(sizeof(sw_converter_t *) * nVar, + "alloc_uconv()", LogInfo); + if(LogInfo->stopRun) { + return; // Exit function prematurely due to error + } + + for(int index = 0; index < nVar; index++) { + (*uconv)[index] = NULL; + } + } +} + + + /** * @brief Fill horizontal coordinate variables, "domain" * (or another user-specified name) variable and "sites" (if applicable) @@ -2366,13 +2483,15 @@ static void create_output_file(SW_OUTPUT* SW_Output, * @param[in] nsl Number of soil layers * @param[in] npft Number of plant functional types * @param[out] count Array storing the output dimensions + * @param[out] countTotal Total size (count) of output values */ static void get_vardim_write_counts( int ncFileID, size_t timeSize, IntUS nsl, IntUS npft, - size_t count[] + size_t count[], + size_t *countTotal ) { int dimIndex, ndimsp; int nSpaceDims = dimExists("site", ncFileID) ? 1 : 2; @@ -2383,21 +2502,26 @@ static void get_vardim_write_counts( count[dimIndex] = 1; } + *countTotal = 1; + /* Fill in time dimension */ if (timeSize > 0) { count[dimIndex] = timeSize; + *countTotal *= timeSize; dimIndex++; } /* Fill in vertical (if present) */ if (nsl > 0) { count[dimIndex] = nsl; + *countTotal *= nsl; dimIndex++; } /* Fill in pft (if present) */ if (npft > 0) { count[dimIndex] = npft; + *countTotal *= npft; dimIndex++; } @@ -2536,7 +2660,7 @@ void SW_NC_write_output(SW_OUTPUT* SW_Output, SW_GEN_OUT* GenOutput, char* fileName, *varName; size_t count[MAX_NUM_DIMS] = {0}; size_t start[MAX_NUM_DIMS] = {0}; - size_t pOUTIndex, startTime, timeSize = 0; + size_t pOUTIndex, startTime, timeSize = 0, countTotal = 0; int vertSize, pftSize; start[0] = ncSuid[0]; @@ -2594,7 +2718,8 @@ void SW_NC_write_output(SW_OUTPUT* SW_Output, SW_GEN_OUT* GenOutput, timeSize, GenOutput->nsl_OUT[key][varNum], GenOutput->npft_OUT[key][varNum], - count + count, + &countTotal ); #if defined(SWDEBUG) @@ -2628,6 +2753,18 @@ void SW_NC_write_output(SW_OUTPUT* SW_Output, SW_GEN_OUT* GenOutput, p_OUTValPtr = &GenOutput->p_OUT[key][pd][pOUTIndex]; + /* Convert units if udunits2 and if converter available */ + #if defined(SWUDUNITS) + if (!isnull(SW_Output[key].uconv[varNum])) { + cv_convert_doubles( + SW_Output[key].uconv[varNum], + p_OUTValPtr, + countTotal, + p_OUTValPtr + ); + } + #endif + /* For current variable x output period, write out all values across vegtypes and soil layers (if any) for current time-chunk @@ -3590,7 +3727,7 @@ void SW_NC_read_out_vars(SW_OUTPUT* SW_Output, SW_GEN_OUT *GenOutput, char* InFi return; // Exit prematurely due to error } - alloc_output_var_info(SW_Output, GenOutput, LogInfo); + SW_NC_alloc_output_var_info(SW_Output, GenOutput->nvar_OUT, LogInfo); if(LogInfo->stopRun) { return; // Exit prematurely due to error } @@ -3662,14 +3799,6 @@ void SW_NC_read_out_vars(SW_OUTPUT* SW_Output, SW_GEN_OUT *GenOutput, char* InFi } } - if(isnull(currOut->outputVarInfo)) { - SW_NC_alloc_outvars(&currOut->outputVarInfo, - GenOutput->nvar_OUT[currOutKey], LogInfo); - if(LogInfo->stopRun) { - return; // Exit function prematurely due to error - } - } - currOut->reqOutputVars[varNum] = swTRUE; // Read in the rest of the attributes @@ -3728,6 +3857,21 @@ void SW_NC_read_out_vars(SW_OUTPUT* SW_Output, SW_GEN_OUT *GenOutput, char* InFi } } + + // Copy SW units for later use + if(currOutKey == eSW_Estab) { + for(estVar = 0; estVar < GenOutput->nvar_OUT[currOutKey]; estVar++) { + currOut->units_sw[estVar] = Str_Dup(input[SWUnitsInd], LogInfo); + if(LogInfo->stopRun) { + return; // Exit function prematurely due to error + } + } + } else { + currOut->units_sw[varNum] = Str_Dup(input[SWUnitsInd], LogInfo); + if(LogInfo->stopRun) { + return; // Exit function prematurely due to error + } + } } } @@ -3740,6 +3884,94 @@ void SW_NC_read_out_vars(SW_OUTPUT* SW_Output, SW_GEN_OUT *GenOutput, char* InFi } } +/** Create unit converters for output variables + +This function requires previous calls to + - SW_NC_alloc_output_var_info() to initialize + SW_Output[key].uconv[varIndex] to NULL + - SW_NC_read_out_vars() to obtain user requested output units + - SW_OUT_setup_output() to set GenOutput.nvar_OUT for argument nVars + +@param[in,out] SW_Output SW_OUTPUT array of size SW_OUTNKEYS which holds + basic output information for all output keys +@param[in] nVars Array with number of output variables +@param[out] LogInfo Holds information on warnings and errors +*/ +void SW_NC_create_units_converters(SW_OUTPUT* SW_Output, IntUS * nVars, LOG_INFO* LogInfo) { + int varIndex, key; + + #if defined(SWUDUNITS) + ut_system *system; + ut_unit *unitFrom, *unitTo; + + ut_set_error_message_handler(ut_ignore); /* silence udunits2 error messages */ + system = ut_read_xml(NULL); /* Load unit system database */ + #endif + + ForEachOutKey(key) { + for (varIndex = 0; varIndex < nVars[key]; varIndex++) { + #if defined(SWUDUNITS) + if (!isnull(SW_Output[key].units_sw[varIndex])) { + unitFrom = ut_parse( + system, + SW_Output[key].units_sw[varIndex], + UT_UTF8 + ); + unitTo = ut_parse( + system, + SW_Output[key].outputVarInfo[varIndex][UNITS_INDEX], + UT_UTF8 + ); + + if (ut_are_convertible(unitFrom, unitTo)) { + // SW_Output[key].uconv[varIndex] was previously initialized to NULL + SW_Output[key].uconv[varIndex] = ut_get_converter(unitFrom, unitTo); + } + + if (isnull(SW_Output[key].uconv[varIndex])) { + // ut_are_convertible() is false or ut_get_converter() failed + LogError( + LogInfo, LOGWARN, + "Units of variable '%s' cannot get converted from " + "internal '%s' to requested '%s'. " + "Output will use internal units.", + SW_Output[key].outputVarInfo[varIndex][VARNAME_INDEX], + SW_Output[key].units_sw[varIndex], + SW_Output[key].outputVarInfo[varIndex][UNITS_INDEX] + ); + + /* converter is not available: output in internal units */ + free(SW_Output[key].outputVarInfo[varIndex][UNITS_INDEX]); + SW_Output[key].outputVarInfo[varIndex][UNITS_INDEX] = + Str_Dup(SW_Output[key].units_sw[varIndex], LogInfo); + } + + ut_free(unitFrom); + ut_free(unitTo); + } + + #else + /* udunits2 is not available: output in internal units */ + free(SW_Output[key].outputVarInfo[varIndex][UNITS_INDEX]); + if (!isnull(SW_Output[key].units_sw[varIndex])) { + SW_Output[key].outputVarInfo[varIndex][UNITS_INDEX] = + Str_Dup(SW_Output[key].units_sw[varIndex], LogInfo); + } + #endif + + if(LogInfo->stopRun) { + return; // Exit function prematurely due to error + } + } + } + + + #if defined(SWUDUNITS) + ut_free_system(system); + #endif +} + + /** * @brief Initializes pointers within the type SW_NETCDF and SW_CRS * @@ -3935,84 +4167,105 @@ void SW_NC_deepCopy(SW_NETCDF* source, SW_NETCDF* dest, LOG_INFO* LogInfo) { } } + /** - * @brief Allocate memory for information in regards to output variables - * respective to SW_OUTPUT instances + * @brief Wrapper function to allocate output request variables + * and output variable information * - * @param[out] outkeyVars Holds all information about output variables - * in netCDFs (e.g., output variable name) - * @param[in] nVar Number of variables available for current output key + * @param[out] SW_Output SW_OUTPUT array of size SW_OUTNKEYS which holds + * basic output information for all output keys + * @param[in] nVars Array with number of output variables * @param[out] LogInfo Holds information on warnings and errors */ -void SW_NC_alloc_outvars(char**** outkeyVars, int nVar, LOG_INFO* LogInfo) { - - *outkeyVars = NULL; +void SW_NC_alloc_output_var_info(SW_OUTPUT* SW_Output, IntUS *nVars, LOG_INFO* LogInfo) { + int key; - if (nVar > 0) { + ForEachOutKey(key) { + SW_NC_alloc_outputkey_var_info(&SW_Output[key], nVars[key], LogInfo); + if(LogInfo->stopRun) { + return; // Exit function prematurely due to error + } + } +} - int index, varNum, attNum; +void SW_NC_alloc_outputkey_var_info(SW_OUTPUT* currOut, IntUS nVar, LOG_INFO* LogInfo) { - // Allocate all memory for the variable information in the current - // output key - *outkeyVars = - (char ***)Mem_Malloc(sizeof(char **) * nVar, - "SW_NC_alloc_outvars()", LogInfo); - if(LogInfo->stopRun) { - return; // Exit function prematurely due to error - } + alloc_outReq(&currOut->reqOutputVars, nVar, LogInfo); + if(LogInfo->stopRun) { + return; // Exit function prematurely due to error + } - for(index = 0; index < nVar; index++) { - (*outkeyVars)[index] = NULL; - } + alloc_outvars(&currOut->outputVarInfo, nVar, LogInfo); + if(LogInfo->stopRun) { + return; // Exit function prematurely due to error + } - for(index = 0; index < nVar; index++) { - (*outkeyVars)[index] = - (char **)Mem_Malloc(sizeof(char *) * NUM_OUTPUT_INFO, - "SW_NC_alloc_outvars()", LogInfo); - if(LogInfo->stopRun) { - for(varNum = 0; varNum < index; varNum++) { - free((*outkeyVars)[varNum]); - } - free(*outkeyVars); - return; // Exit function prematurely due to error - } + alloc_unitssw(&currOut->units_sw, nVar, LogInfo); + if(LogInfo->stopRun) { + return; // Exit function prematurely due to error + } - for(attNum = 0; attNum < NUM_OUTPUT_INFO; attNum++) { - (*outkeyVars)[index][attNum] = NULL; - } - } + alloc_uconv(&currOut->uconv, nVar, LogInfo); + if(LogInfo->stopRun) { + return; // Exit function prematurely due to error } } -/** - * @brief Allocate information about whether or not a variable should - * be output - * - * @param[out] reqOutVar Specifies the number of variables that can be output - * for a given output key - * @param[in] nVar Number of variables available for current output key - * @param[out] LogInfo Holds information on warnings and errors -*/ -void SW_NC_alloc_outReq(Bool** reqOutVar, int nVar, LOG_INFO* LogInfo) { +void SW_NC_dealloc_outputkey_var_info(SW_OUTPUT* SW_Output, IntUS k, IntUS *nVars) { + if(!isnull(SW_Output[k].outputVarInfo)) { - *reqOutVar = NULL; + for(int varNum = 0; varNum < nVars[k]; varNum++) { - if (nVar > 0) { + if(!isnull(SW_Output[k].outputVarInfo[varNum])) { - int index; + for(int attNum = 0; attNum < NUM_OUTPUT_INFO; attNum++) { - // Initialize the variable within SW_OUTPUT which specifies if a variable - // is to be written out or not - *reqOutVar = - (Bool *)Mem_Malloc(sizeof(Bool) * nVar, - "SW_NC_alloc_outReq()", LogInfo); - if(LogInfo->stopRun) { - return; // Exit function prematurely due to error + if(!isnull(SW_Output[k].outputVarInfo[varNum][attNum])) { + free(SW_Output[k].outputVarInfo[varNum][attNum]); + SW_Output[k].outputVarInfo[varNum][attNum] = NULL; + } + } + + free(SW_Output[k].outputVarInfo[varNum]); + SW_Output[k].outputVarInfo[varNum] = NULL; + } } - for(index = 0; index < nVar; index++) { - (*reqOutVar)[index] = swFALSE; + free(SW_Output[k].outputVarInfo); + SW_Output[k].outputVarInfo = NULL; + } + + if (!isnull(SW_Output[k].units_sw)) { + for (int varNum = 0; varNum < nVars[k]; varNum++) { + if(!isnull(SW_Output[k].units_sw[varNum])) { + free(SW_Output[k].units_sw[varNum]); + SW_Output[k].units_sw[varNum] = NULL; + } } + + free(SW_Output[k].units_sw); + SW_Output[k].units_sw = NULL; + } + + if (!isnull(SW_Output[k].uconv)) { + for (int varNum = 0; varNum < nVars[k]; varNum++) { + if(!isnull(SW_Output[k].uconv[varNum])) { + #if defined(SWNETCDF) && defined(SWUDUNITS) + cv_free(SW_Output[k].uconv[varNum]); + #else + free(SW_Output[k].uconv[varNum]); + #endif + SW_Output[k].uconv[varNum] = NULL; + } + } + + free(SW_Output[k].uconv); + SW_Output[k].uconv = NULL; + } + + if(!isnull(SW_Output[k].reqOutputVars)) { + free(SW_Output[k].reqOutputVars); + SW_Output[k].reqOutputVars = NULL; } }