Skip to content

Commit

Permalink
Added example utility using OOPS and IODA (#553)
Browse files Browse the repository at this point in the history
* Move soca utils to subdirectory
  • Loading branch information
CoryMartin-NOAA committed Aug 9, 2023
1 parent 7b67146 commit f601d0c
Show file tree
Hide file tree
Showing 12 changed files with 159 additions and 11 deletions.
24 changes: 13 additions & 11 deletions utils/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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)
6 changes: 6 additions & 0 deletions utils/ioda_example/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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)

14 changes: 14 additions & 0 deletions utils/ioda_example/gdas_meanioda.cc
Original file line number Diff line number Diff line change
@@ -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);
}
86 changes: 86 additions & 0 deletions utils/ioda_example/gdas_meanioda.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
#include <iostream>
#include <numeric>
#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<float> 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
11 changes: 11 additions & 0 deletions utils/soca/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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)
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
13 changes: 13 additions & 0 deletions utils/test/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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)
16 changes: 16 additions & 0 deletions utils/test/testinput/gdas_meanioda.yaml
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit f601d0c

Please sign in to comment.