Creating new Observation Operator in UFO

Existing Observation Operators

Before implementing a new observation operator, check if one of the observation operators already implemented in UFO is suitable.

Creating files for a new Observation Operator

If your observation operator is different from the above, you may need to create a new observation operator. Typically, all the files for a new observation operator are in a new directory under ufo/src/ufo.

The new observation operator has to have a C++ interface, because all observation operators have to be accessed by a generic data assimilation layer written in C++ in oops. Most of the observation operators, however, are written in Fortran. The directory for the observation operator consists of the following files (example from atmvertinterp):

  1. ObsAtmVertInterp.cc, ObsAtmVertInterp.h: C++ files defining the ObsOperator class. The methods (functions) there call Fortran subroutines.

  2. ObsAtmVertInterp.interface.F90, ObsAtmVertInterp.interface.h: C++ and Fortan files defining interfaces between Fortran and C++.

  3. ufo_atmvertinterp_mod.F90 - Fortran module containing the code to run observation operator.

Most of the time you’d only need to modify the Fortran module (3), and the files from (1-2) can be generated automatically.

To generate the ObsOperator files, you can run the following script: ufo/tools/new_obsop/create_obsop_fromexample.sh <ObsOperatorName> <directory>

<ObsOperatorName> is an UpperCamelCase name you’d like your obs operator to go by. <directory> is a directory name in ufo/src/ufo. Examples for existing obsoperators: atmvertinterp, crtm, identity.

Example of calling create_obsop_fromexample.sh:

$> ./create_obsop_fromexample.sh MyOperator myoperator

After the directory with the new obsoperator is created, add it to ufo/src/ufo/CMakeLists.txt:

add_subdirectory( identity )
add_subdirectory( myoperator )
list( APPEND ufo_src_files
     ${identity_src_files}
     ${myoperator_src_files}

and try to compile/build the code.

Adding an Observation Operator test

After this skeleton code is generated, create a test for your new observation operator. Even if the test fails because of missing data or a mismatch between computed and provided values, the test will still call your operator and any print statements or other calls you perform within the Fortran subroutines will execute.

For observation operator test one needs a sample observation file and a corresponding geovals file.

Observation file should be added to ioda repository in ioda/test/testinput/atmosphere/. Corresponding geovals file should be added to ufo repository in ufo/test/testinput/atmosphere/.

All observation operator tests in UFO use the OOPS ObsOperator test. To create a new one, add an entry to ufo/test/CMakeLists.txt similar to:

ecbuild_add_test( TARGET  test_ufo_myoperator_opr          # test name
                  SOURCES mains/TestObsOperator.cc         # source file
                  ARGS    "testinput/myoperator.yaml"      # config file
                  LIBS    ufo )

Other changes required in ufo/test/CMakeLists.txt:

Link the config file you will be using for the test:

list( APPEND ufo_test_input
        testinput/myoperator.yaml

Link the observations and geovals files you will be using for the test:

list( APPEND ufo_test_data
        atmosphere/geoval_file_name.nc4
list (APPEND ioda_obs_test_data
        atmosphere/obs_file_name.nc4

To configure the test, create config file ufo/test/testinput/myoperator.yaml and fill appropriately. For examples see ufo/test/testinput/amsua_crtm.yaml, ufo/test/testinput/radiosonde.yaml.

Adding substance to the new Observation Operator

To implement the Observation Operator, one needs to:

  • Specify input variable names (requested from the model) in ufo_obsoperator_mod.F90, subroutine ufo_obsoperator_setup. The input variable names need to be saved in self%varin (set self%nvars_in and allocate accordingly). The variables that need to be simulated by the observation operator are already set in self%varout(self%nvars_out) (these are the variables from ObsSpace.simulate section of configuration file. See examples in ufo/src/ufo/atmvertinterp/ufo_atmvertinterp_mod.F90 and ufo/src/ufo/crtm/ufo_radiancecrtm_mod.F90. The variables can be hard-coded or controlled from the config file depending on your observation operator.

  • Fill in ufo_obsoperator_simobs routine. This subroutine is for calculating H(x). Inputs: geovals (horizontally interpolated to obs locations model fields for the variables specified in self%varin above), obss (observation space, can be used to request observation metadata). Output: hofx(nvars, nlocs) (obs vector to hold H(x), nvars are equal to self%nvars_out). Note that the hofx vector was allocated before the call to ufo_obsoperator_simobs, and only needs to be filled in.

Observation Operator test

All observation operator tests in UFO use the OOPS ObsOperator test from oops/src/test/interface/ObsOperator.h.

There are two parts of this test:

  1. testConstructor: tests that ObsOperator objects can be created and destroyed

  2. testSimulateObs: tests observation operator calculation in the following way:

  • Creates observation operator, calls ufo_obsoperator_setup

  • Reads “GeoVaLs” (vertical profiles of relevant model variables, interpolated to observation lat-lon location) from the geovals file

  • Computes H(x) by calling ufo_obsoperator_simobs

  • Reads benchmark H(x) from the obs file (netcdf variable name defined by vecequiv entry in the config) and compares it to H(x) computed above

  • Test passes if the norm(benchmark H(x) - H(x)) < tolerance, with tolerance defined in the config by tolerance.