Skip to content

Commit

Permalink
Unit conversion for netCDF output with udunits2
Browse files Browse the repository at this point in the history
- 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
  • Loading branch information
dschlaep committed Apr 16, 2024
1 parent da27da2 commit 6f112fd
Show file tree
Hide file tree
Showing 8 changed files with 437 additions and 133 deletions.
8 changes: 8 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 12 additions & 0 deletions include/SW_Defines.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 <udunits2.h>
#endif

#ifdef __cplusplus
extern "C" {
Expand Down Expand Up @@ -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
}
Expand Down
2 changes: 2 additions & 0 deletions include/SW_datastructs.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
6 changes: 4 additions & 2 deletions include/SW_netCDF.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
46 changes: 42 additions & 4 deletions makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand Down
8 changes: 8 additions & 0 deletions src/SW_Main.c
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
51 changes: 16 additions & 35 deletions src/SW_Output.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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] =
Expand All @@ -3296,15 +3265,27 @@ 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
}
}
}
}

} 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

Expand Down
Loading

0 comments on commit 6f112fd

Please sign in to comment.