From f601d0cea2a20ad257266f3f37d93c3114cdd731 Mon Sep 17 00:00:00 2001 From: Cory Martin Date: Wed, 9 Aug 2023 15:37:38 -0400 Subject: [PATCH] Added example utility using OOPS and IODA (#553) * Move soca utils to subdirectory --- utils/CMakeLists.txt | 24 +++--- utils/ioda_example/CMakeLists.txt | 6 ++ utils/ioda_example/gdas_meanioda.cc | 14 ++++ utils/ioda_example/gdas_meanioda.h | 86 ++++++++++++++++++++++ utils/soca/CMakeLists.txt | 11 +++ utils/{ => soca}/gdas_incr_handler.cc | 0 utils/{ => soca}/gdas_incr_handler.h | 0 utils/{ => soca}/gdas_postprocincr.h | 0 utils/{ => soca}/gdas_socahybridweights.cc | 0 utils/{ => soca}/gdas_socahybridweights.h | 0 utils/test/CMakeLists.txt | 13 ++++ utils/test/testinput/gdas_meanioda.yaml | 16 ++++ 12 files changed, 159 insertions(+), 11 deletions(-) create mode 100644 utils/ioda_example/CMakeLists.txt create mode 100644 utils/ioda_example/gdas_meanioda.cc create mode 100644 utils/ioda_example/gdas_meanioda.h create mode 100644 utils/soca/CMakeLists.txt rename utils/{ => soca}/gdas_incr_handler.cc (100%) rename utils/{ => soca}/gdas_incr_handler.h (100%) rename utils/{ => soca}/gdas_postprocincr.h (100%) rename utils/{ => soca}/gdas_socahybridweights.cc (100%) rename utils/{ => soca}/gdas_socahybridweights.h (100%) create mode 100644 utils/test/CMakeLists.txt create mode 100644 utils/test/testinput/gdas_meanioda.yaml diff --git a/utils/CMakeLists.txt b/utils/CMakeLists.txt index 86be649b2..beda28531 100644 --- a/utils/CMakeLists.txt +++ b/utils/CMakeLists.txt @@ -1,18 +1,20 @@ project(gdas-utils LANGUAGES C CXX ) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS OFF) +set(CMAKE_FORTRAN_STANDARD 08) +set(CMAKE_FORTRAN_STANDARD_REQUIRED ON) +set(CMAKE_FORTRAN_EXTENSIONS OFF) + find_package(NetCDF REQUIRED COMPONENTS CXX) find_package(oops REQUIRED) find_package(atlas REQUIRED) find_package(soca REQUIRED) -# Increment post processing -ecbuild_add_executable( TARGET gdas_incr_handler.x - SOURCES gdas_incr_handler.cc gdas_postprocincr.h) -target_compile_features( gdas_incr_handler.x PUBLIC cxx_std_17) -target_link_libraries( gdas_incr_handler.x PUBLIC NetCDF::NetCDF_CXX oops atlas soca) - -# Hybrid-Weight -ecbuild_add_executable( TARGET gdas_socahybridweights.x - SOURCES gdas_socahybridweights.cc ) -target_compile_features( gdas_socahybridweights.x PUBLIC cxx_std_17) -target_link_libraries( gdas_socahybridweights.x PUBLIC NetCDF::NetCDF_CXX oops atlas soca) +add_subdirectory(soca) +add_subdirectory(ioda_example) +add_subdirectory(test) diff --git a/utils/ioda_example/CMakeLists.txt b/utils/ioda_example/CMakeLists.txt new file mode 100644 index 000000000..4ae18115f --- /dev/null +++ b/utils/ioda_example/CMakeLists.txt @@ -0,0 +1,6 @@ +ecbuild_add_executable( TARGET gdas_meanioda.x + SOURCES gdas_meanioda.cc ) + +target_compile_features( gdas_meanioda.x PUBLIC cxx_std_17) +target_link_libraries( gdas_meanioda.x PUBLIC oops ioda) + diff --git a/utils/ioda_example/gdas_meanioda.cc b/utils/ioda_example/gdas_meanioda.cc new file mode 100644 index 000000000..6e18ff5fa --- /dev/null +++ b/utils/ioda_example/gdas_meanioda.cc @@ -0,0 +1,14 @@ +#include "gdas_meanioda.h" +#include "oops/runs/Run.h" + +// this is an example application that +// will use IODA to read a file and print something +// it is intended to be very bare bones +// you will note the .cc file is very empty +// the .h file is where the action is! + +int main(int argc, char ** argv) { + oops::Run run(argc, argv); + gdasapp::IodaExample iodaexample; + return run.execute(iodaexample); +} diff --git a/utils/ioda_example/gdas_meanioda.h b/utils/ioda_example/gdas_meanioda.h new file mode 100644 index 000000000..95742c196 --- /dev/null +++ b/utils/ioda_example/gdas_meanioda.h @@ -0,0 +1,86 @@ +#include +#include +#include "eckit/config/LocalConfiguration.h" +#include "ioda/Group.h" +#include "ioda/ObsSpace.h" +#include "ioda/ObsVector.h" +#include "oops/base/PostProcessor.h" +#include "oops/mpi/mpi.h" +#include "oops/runs/Application.h" +#include "oops/util/DateTime.h" +#include "oops/util/Duration.h" +#include "oops/util/Logger.h" + +namespace gdasapp { + // this is an example of how one can use OOPS and IODA to do something + // in this code, we will read in configuration from YAML + // and then use that configuration to read in a IODA formatted file, + // read one group/variable (with optional channel) + // and compute and print out the mean of the variable. + // Nothing fancy, but you can see how this could be expanded! + + class IodaExample : public oops::Application { + public: + explicit IodaExample(const eckit::mpi::Comm & comm = oops::mpi::world()) + : Application(comm) {} + static const std::string classname() {return "gdasapp::IodaExample";} + + int execute(const eckit::Configuration & fullConfig, bool /*validate*/) const { + + // get the obs space configuration + const eckit::LocalConfiguration obsConfig(fullConfig, "obs space"); + ioda::ObsTopLevelParameters obsparams; + obsparams.validateAndDeserialize(obsConfig); // TODO CRM, can I remove this and then the simulated vars junk?? + oops::Log::info() << "obs space: " << std::endl << obsConfig << std::endl; + + // time window stuff + std::string winbegin; + std::string winend; + fullConfig.get("window begin", winbegin); + fullConfig.get("window end", winend); + + // what variable to get the mean of + std::string group; + std::string variable; + fullConfig.get("group", group); + fullConfig.get("variable", variable); + int chan = 0; + if (fullConfig.has("channel")) { + fullConfig.get("channel", chan); + } + + // read the obs space + // Note, the below line does a lot of heavy lifting + // we can probably go to a lower level function (and more of them) to accomplish the same thing + ioda::ObsSpace ospace(obsparams, oops::mpi::world(), util::DateTime(winbegin), util::DateTime(winend), oops::mpi::myself()); + const size_t nlocs = ospace.nlocs(); + oops::Log::info() << "nlocs =" << nlocs << std::endl; + std::vector buffer(nlocs); + + // below is grabbing from the IODA obs space the specified group/variable and putting it into the buffer + if (chan == 0) { + // no channel is selected + ospace.get_db(group, variable, buffer); + } else { + // give it the channel as a single item list + ospace.get_db(group, variable, buffer, {chan}); + } + + // the below line computes the mean, aka sum divided by count + const float mean = std::accumulate(buffer.begin(), buffer.end(), 0) / float(nlocs); + + // write the mean out to the stdout + oops::Log::info() << "mean value for " << group << "/" << variable << "=" << mean << std::endl; + + // a better program should return a real exit code depending on result, but this is just an example! + return 0; + } + // ----------------------------------------------------------------------------- + private: + std::string appname() const { + return "gdasapp::IodaExample"; + } + // ----------------------------------------------------------------------------- + }; + +} // namespace gdasapp diff --git a/utils/soca/CMakeLists.txt b/utils/soca/CMakeLists.txt new file mode 100644 index 000000000..041e905b7 --- /dev/null +++ b/utils/soca/CMakeLists.txt @@ -0,0 +1,11 @@ +# Increment post processing +ecbuild_add_executable( TARGET gdas_incr_handler.x + SOURCES gdas_incr_handler.cc gdas_postprocincr.h) +target_compile_features( gdas_incr_handler.x PUBLIC cxx_std_17) +target_link_libraries( gdas_incr_handler.x PUBLIC NetCDF::NetCDF_CXX oops atlas soca) + +# Hybrid-Weight +ecbuild_add_executable( TARGET gdas_socahybridweights.x + SOURCES gdas_socahybridweights.cc ) +target_compile_features( gdas_socahybridweights.x PUBLIC cxx_std_17) +target_link_libraries( gdas_socahybridweights.x PUBLIC NetCDF::NetCDF_CXX oops atlas soca) diff --git a/utils/gdas_incr_handler.cc b/utils/soca/gdas_incr_handler.cc similarity index 100% rename from utils/gdas_incr_handler.cc rename to utils/soca/gdas_incr_handler.cc diff --git a/utils/gdas_incr_handler.h b/utils/soca/gdas_incr_handler.h similarity index 100% rename from utils/gdas_incr_handler.h rename to utils/soca/gdas_incr_handler.h diff --git a/utils/gdas_postprocincr.h b/utils/soca/gdas_postprocincr.h similarity index 100% rename from utils/gdas_postprocincr.h rename to utils/soca/gdas_postprocincr.h diff --git a/utils/gdas_socahybridweights.cc b/utils/soca/gdas_socahybridweights.cc similarity index 100% rename from utils/gdas_socahybridweights.cc rename to utils/soca/gdas_socahybridweights.cc diff --git a/utils/gdas_socahybridweights.h b/utils/soca/gdas_socahybridweights.h similarity index 100% rename from utils/gdas_socahybridweights.h rename to utils/soca/gdas_socahybridweights.h diff --git a/utils/test/CMakeLists.txt b/utils/test/CMakeLists.txt new file mode 100644 index 000000000..9eb9d310e --- /dev/null +++ b/utils/test/CMakeLists.txt @@ -0,0 +1,13 @@ +# Create Data directory for test input config and symlink all files +list( APPEND utils_test_input + testinput/gdas_meanioda.yaml +) + +file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/testinput) +CREATE_SYMLINK( ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ${utils_test_input} ) + +# Test example IODA utility that computes the mean of a variable +ecbuild_add_test( TARGET test_gdasapp_util_ioda_example + COMMAND ${CMAKE_BINARY_DIR}/bin/gdas_meanioda.x + ARGS "testinput/gdas_meanioda.yaml" + LIBS gdas-utils) \ No newline at end of file diff --git a/utils/test/testinput/gdas_meanioda.yaml b/utils/test/testinput/gdas_meanioda.yaml new file mode 100644 index 000000000..ff71b1870 --- /dev/null +++ b/utils/test/testinput/gdas_meanioda.yaml @@ -0,0 +1,16 @@ +# the window is 30 years long to capture anything we can throw at it in this input file +window begin: 2000-11-01T09:00:00Z +window end: 2030-11-01T15:00:00Z +obs space: + name: gmi_gpm_test_mean + obsdatain: + engine: + type: H5File + obsfile: ../../../soca/test/Data/obs/gmi_gpm_obs.nc + # the below 2 lines are not used but needed by the IODA obsspace it seems... + simulated variables: [brightnessTemperature] + observed variables: [brightnessTemperature] +group: ObsValue +variable: brightnessTemperature +# channel is optional, depends on what variable you are reading +channel: 6