Skip to content

Conversation

@hga007
Copy link
Collaborator

@hga007 hga007 commented Jul 12, 2025

Description

This PR implements a new ROMS data assimilation observation operator. The basic principles of the new IODA-type file for ROMS are as follows:

  • It uses enhanced NetCDF-4 with first-level grouping and the extension nc4 for differentiation. A Group is like a subdirectory in a file. Although nested groups are possible, I don’t recommend them. Note that compression of NC4 files is possible.
  • They can be used for native ROMS 4D-Var or any data assimilation algorithms in ROMS-JEDI.
  • There is one file for each variable in the control vector. A file can include multiple variables representing vectors (u- and v-momentum) and spectral data with exact coordinates (lon, lat, depth, time). It may also contain multiple scalar variables if at the precise location, such as T and S profiles or biological profiles for various ecosystem state variables. I may also include secondary variables to compute a control variable with an elaborate operator. This setup provides us with a lot of flexibility for filtering and averaging.
! Input multiple observation filenames, [1:Ngrids].

       NOBSFILES == 5

         OBSname == /home/arango/ROMS/JediApps/usec3/obs/usec3_adt_20190827.nc4     \
                    /home/arango/ROMS/JediApps/usec3/obs/usec3_sst_20190827.nc4     \
                    /home/arango/ROMS/JediApps/usec3/obs/usec3_temp_20190827.nc4    \
                    /home/arango/ROMS/JediApps/usec3/obs/usec3_salt_20190827.nc4    \
                    /home/arango/ROMS/JediApps/usec3/obs/usec3_uv_codar_20190827.nc4
  • The data uses single precision, so there’s no need for double precision since the observations aren't measured with that accuracy. As a result, the files will be significantly smaller.
  • Time is measured in seconds from the reference date. It is an integer (int64, see dateTime and surveyTime below). There is no need to store the time data as double precision, and we avoid rounding errors by using integers. The timestep in ROMS data assimilation is not in microseconds anyway. As a result, the file will be smaller and easier to transfer.
  • ROMS allows multiple time references. I haven’t seen any benefit in using, for example, days since 2006-01-01 00:00:00, as we have done with our DOPPIO or ECCOFS applications. It is prone to many rounding errors. We could use seconds from the beginning of the simulation's year or, even better, from seconds since the date of the data assimilation cycle to create smaller files and improve compression. It doesn’t matter which reference date we choose for the observation files. ROMS will set it at the correct time!
  • The idea is to store observation data in a container with user-defined divisions. For example, we could have containers for annual observations. Then, we can create a script that extracts and generates the necessary IODA files for a specific data assimilation cycle. It is up to the user whether they want to save the IODA files. These protocols are to be determined by the user or the operational environment.
  • Repetitive observations should have their provenance multiplied by -1 so we can quickly identify them during processing. Usually, we use repetitive altimetry data that is added at different times but with different (larger) observation errors.

  • A new Fortran 2003 module, roms_obs.F, is introduced to store the input observation data, which is only read once. It has the following two CLASS objects: obs_data and obs_pool. And the obs_pool object is a vector, pool(:), set of TYPE (obs_data) observations separated by their type, source, or control vector variable. It can load native (old) or IODA (new) observation NetCDF files for data assimilation.
!
!-----------------------------------------------------------------------
!  Enhanced NetCDF-4 group object.
!-----------------------------------------------------------------------
!
      TYPE, PUBLIC :: nc_group

        integer :: gid                             ! group ID
        integer :: nvars                           ! number of variables
!
        integer, allocatable :: vid(:)             ! variables ID

        character (len=:), allocatable :: name     ! group name
        character (len=:), allocatable :: vname(:) ! variables name

      END TYPE nc_group
!
!-----------------------------------------------------------------------
!  Observation Data Type Structures/Objects: CLASS(obs_data).
!-----------------------------------------------------------------------
!
      TYPE, PUBLIC :: obs_data

        integer :: nlocs                       ! number of observations
        integer :: nsurvey                     ! number of times surveys
        integer :: nvars                       ! number of variables
        integer :: poolID                      ! observation pool ID
        integer :: timeWindow                  ! time-averaged windows
!
!  Switches indicating area-averaged and time-averaged observations.
!
        logical :: IsAreaAveraged
        logical :: IsTimeAveraged
!
!  Observation origin identifier. Repetitive observations have their
!  provenance multiplied by -1 so they can be quickly identified during
!  processing.
!
        integer, allocatable :: provenance(:)
!
!  Control vector state variable identifier.
!
        integer, allocatable :: stateID(:)
!
!  Observation survey time indices as they appear in the "time"
!  variable.
!
        integer, allocatable :: surveyIndex(:)
!
!  Starting and ending observation vector indices available for
!  requested model time interval, time-0.5*dt to time+0.5*dt. They
!  are zero, if no observations are available in the time window.
!
        integer :: NstrObs
        integer :: NendObs
!
!  Mapping indices for the packed observations vector. It is used
!  when computing H(x), the cost function, and minimization where
!  and the observations are clustered in a 1D array.
!
        integer, allocatable :: pack_mapping(:,:)
!
!  Half-length spatial scale (m) for area-averaged operator.
!
        real (r8) :: spatialAverage
!
!  Starting and ending times for time-averaged operator.
!
        real (r8), allocatable :: TimeStr(:)
        real (r8), allocatable :: TimeEnd(:)
!
!  Observation spatial and time coordinates. The fractional values are
!  used for efficient interpolation in the H(x) operator.
!
        real(r8), allocatable :: lon(:)         ! observation longitude
        real(r8), allocatable :: lat(:)         ! observation latitude
        real(r8), allocatable :: depth(:)       ! depth of observation
        real(r8), allocatable :: time(:)        ! time  of observation
        real(r8), allocatable :: surveyTime(:)  ! survey time
!
        real(r8), allocatable :: Xgrid(:)       ! fractional X-grid
        real(r8), allocatable :: Ygrid(:)       ! fractional Y-grid
        real(r8), allocatable :: Zgrid(:)       ! fractional Z-grid
!
!  If the observations are scalar, the second dimension is unity.
!  Otherwise, it is used for vectors or multiple observations at the
!  exact spatial and time coordinates.
!
        real(r8), allocatable :: error(:,:)     ! observation error(s)
        real(r8), allocatable :: value(:,:)     ! observation value(s)
!
        character (len=:), allocatable :: vars(:) ! NetCDF variable name
!
!  Observation statistics.
!
        integer (i8b), allocatable :: error_hash(:)
        integer (i8b), allocatable :: value_hash(:)
!
        real (r8),     allocatable :: error_avg(:)
        real (r8),     allocatable :: error_min(:)
        real (r8),     allocatable :: error_max(:)

        real (r8),     allocatable :: value_avg(:)
        real (r8),     allocatable :: value_min(:)
        real (r8),     allocatable :: value_max(:)
!
        real (r8) :: time_min
        real (r8) :: time_max
!
        character (len=22) :: date_min
        character (len=22) :: date_max
!
!  Internal observation data object name.
!
        character (len=:), allocatable :: name
!
!  Input observation filename. It supports native ROMS 4D-Var NetCDF
!  files and IODA enhanced NetCDF-4 files with Groups.
!
        character (len=:), allocatable :: ncname_inp
!
!  Output observation plus H(x) operator enhanced NetCDF-4 file
!  information.
!
        integer :: ncid                                ! NetCDF file ID
        integer :: ngroups                             ! groups size
!
        TYPE (nc_group), allocatable :: group(:)       ! group object
!
        character (len=:),  allocatable :: ncname_out  ! output filename
!
        CONTAINS
!
        PROCEDURE :: destroy          => obs_data_destroy
        PROCEDURE :: frac_coords      => obs_data_frac_coords
        PROCEDURE :: populate_ioda    => obs_data_populate_ioda
        PROCEDURE :: stats            => obs_data_stats
!
      END TYPE obs_data
!
!-----------------------------------------------------------------------
!  Collection Observation Data Types Object: CLASS(obs_pool).
!-----------------------------------------------------------------------
!
      TYPE, PUBLIC :: obs_pool
!
!  Total number of observations in pool depot.
!
        integer :: ndatum
!
!  Observation ID associated with control state variable, [1:ntype].
!  (zeta=1, ubar=2, vbar=3, u=4, v=5, temperature=6, salinity=7)
!
        integer, allocatable :: stateID(:)
!
!  Observation set object. Each element corresponds to an observation
!  associated with a state variable, [1:ntype].
!
        TYPE (obs_data), allocatable :: pool(:)
!
        CONTAINS
!
        PROCEDURE :: create           => obs_pool_create
        PROCEDURE :: destroy          => obs_pool_destroy
        PROCEDURE :: get              => obs_pool_get
        PROCEDURE :: has              => obs_pool_has
        PROCEDURE :: load_ioda        => obs_pool_load_ioda
        PROCEDURE :: load_native      => obs_pool_load_native
        PROCEDURE :: nc_create        => obs_pool_nc_create
        PROCEDURE :: nc_read          => obs_pool_nc_read
        PROCEDURE :: nc_write         => obs_pool_nc_write
        PROCEDURE :: populate_native  => obs_pool_populate_native
        PROCEDURE :: size             => obs_pool_size
        PROCEDURE :: tally            => obs_pool_tally
        PROCEDURE :: unpack_i         => obs_pool_unpack_i
        PROCEDURE :: unpack_r         => obs_pool_unpack_r
!
      END TYPE obs_pool
!
!:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
!
      PUBLIC  :: roms_obs_initialize
      PRIVATE
  • Also, I added a new module, unique.F to compute the unique values of a vector, such that,
      CALL unique (Ninp, Ain, Nout, Aout, Iout)
where
      Aout = Ainp(Iout)

Aout is a vector of the unique values, and Iout is an index vector of the first occurrence of each repeated value. It is similar to the MATLAB unique function.


The NetCDF4 schema for CODAR velocity data is as follows: momentum horizontal vector components at -2 m

% ncdump -h usec3_uv_codar_20190827.nc4

netcdf usec3_uv_codar_20190827 {
dimensions:
	Location = 10332 ;
	nvars = 2 ;
	survey = 72 ;
variables:
	int Location(Location) ;
		Location:suggested_chunck_dim = 512 ;
	int nvars(nvars) ;
		nvars:suggested_chunck_dim = 100 ;
	int survey(survey) ;
		survey:suggested_chunck_dim = 100 ;

// global attributes:
		:_ioda_layout = "ObsGroup" ;
		:_ioda_layout_version = 3 ;
		:odb_version = 1 ;
		:date_time = 2019082700. ;
		:datetimeReference = "2019-08-27T00:00:00Z" ;
		:description = "Native ROMS 4D-Var observations file converted to IODA" ;
		:sourceFiles = "usec3km_roms_obs_20190827.nc" ;
		:history = "Created from Matlab script: create_ioda_obs on Wednesday - July 9, 2025 - 9:27:38.0892 PM" ;

group: MetaData {
  variables:
  	int64 dateTime(Location) ;
  		dateTime:long_name = "elapsed observation time since reference" ;
  		dateTime:units = "seconds since 2019-08-27T00:00:00Z" ;
  	float depth(Location) ;
  		depth:long_name = "observation depth below sea level" ;
  		depth:units = "meter" ;
  		depth:negative = "downwards" ;
  	float latitude(Location) ;
  		latitude:long_name = "observation latitude" ;
  		latitude:units = "degrees_north" ;
  	float longitude(Location) ;
  		longitude:long_name = "observation longitude" ;
  		longitude:units = "degrees_east" ;
  	int provenance(Location) ;
  		provenance:long_name = "observation origin identifier" ;
  	int sequenceNumber(Location) ;
  		sequenceNumber:long_name = "observation sequence number" ;
  	int stateID(nvars) ;
  		stateID:long_name = "state variable index" ;
  		stateID:surface_level = 50 ;
  	int surveyIndex(survey) ;
  		surveyIndex:long_name = "observation survey time indices as they appear in dateTime" ;
  	int64 surveyTime(survey) ;
  		surveyTime:long_name = "observation survey time" ;
  		surveyTime:units = "seconds since 2019-08-27T00:00:00Z" ;
  	string variables_name(nvars) ;
  		variables_name:long_name = "observation UFO/IODA standard name" ;
  		string variables_name:_FillValue = "" ;
  	float x_grid(Location) ;
  		x_grid:long_name = "observation fractional x-grid location" ;
  	float y_grid(Location) ;
  		y_grid:long_name = "observation fractional y-grid location" ;
  	float z_grid(Location) ;
  		z_grid:long_name = "observation fractional z-grid location" ;
  } // group MetaData

group: ObsError {
  variables:
  	float waterZonalVelocity(Location) ;
  		waterZonalVelocity:long_name = "observation error standard deviation" ;
  		waterZonalVelocity:units = "m s-1" ;
  		waterSurfaceZonalVelocity:coordinates = "longitude, latitude, depth, dateTime" ;
  	float waterMeridionalVelocity(Location) ;
  		waterMeridionalVelocity:long_name = "observation error standard deviation" ;
  		waterMeridionalVelocity:units = "m s-1" ;
  		waterMeridionalVelocity:coordinates = "longitude, latitude, depth, dateTime" ;
  } // group ObsError

group: ObsValue {
  variables:
  	float waterZonalVelocity(Location) ;
  		waterZonalVelocity:long_name = "observation value" ;
  		waterZonalVelocity:units = "m s-1" ;
  		waterZonalVelocity:coordinates = "longitude, latitude, depth, dateTime" ;
  	float waterMeridionalVelocity(Location) ;
  		waterMeridionalVelocity:long_name = "observation value" ;
  		waterMeridionalVelocity:units = "m s-1" ;
  		waterMeridionalVelocity:coordinates = "longitude, latitude, depth, dateTime" ;
  } // group ObsValue

group: PreQC {
  variables:
  	int waterSurfaceZonalVelocity(Location) ;
  		waterZonalVelocity:long_name = "observation preset quality control filter identifier" ;
  		waterZonalVelocity:coordinates = "longitude, latitude, depth, dateTime" ;
  	int waterMeridionalVelocity(Location) ;
  		waterMeridionalVelocity:long_name = "observation preset quality control filter identifier" ;
  		waterMeridionalVelocity:coordinates = "longitude, latitude, depth, dateTime" ;
  } // group PreQC
}

A new Fortran 2003 module, roms_hofx.F, is added to compute and store $\boldsymbol{\rm{H(X^{s}_{j})}}$ where superscript s indicates background (s=b) or analysis (s=a) state vector. It also computes its transpose HT for adjoint kernel forcing. It has the following CLASS object obs_hofx:

!
!-----------------------------------------------------------------------
!  Observation Filter Operator Object: CLASS(obs_filter).
!-----------------------------------------------------------------------
!
      TYPE, PUBLIC :: obs_filter
!
!  Kernel variables lower and upper bound array dimensions, and parallel
!  tile partition indices.
!
        integer :: tile                             ! domain partition
        integer :: LBi,  UBi,  LBj,  UBj            ! array bounds
        integer :: Imin, Imax, Jmin, Jmax           ! global bounds
!
!  Application global fields required for spatial averaging operators.
!
        real (r8), pointer :: area(:,:) => NULL()   ! grid area
        real (r8), pointer :: dx(:,:)   => NULL()   ! X-grid spacing
        real (r8), pointer :: dy(:,:)   => NULL()   ! Y-grid spacing
# ifdef MASKING
        real (r8), pointer :: mask(:,:) => NULL()   ! land-sea mask
# endif
!
!  Generic global state variables to facilitate area averaging. The
!  area averaging is only supported for 2D fields (SSH, SSS, SST, Usur).
!
        real (r8), allocatable :: global2d(:,:)     ! 2D state variable
!
        CONTAINS
!
        PROCEDURE :: create   => obs_filter_create
        PROCEDURE :: destroy  => obs_filter_destroy

      END TYPE obs_filter
!
!-----------------------------------------------------------------------
!  Observation Operator HofX Object: CLASS(obs_hofx).
!-----------------------------------------------------------------------
!
      TYPE, PUBLIC :: obs_hofx
!
!  Total number of aggregated observations.
!
        integer :: ndatum
!
!  Kernel variables lower and upper bound array dimensions, and parallel
!  tile partition indices.
!
        integer :: tile                             ! domain partition
        integer :: LBi,   UBi,   LBj,   UBj         ! array bounds
        integer :: IstrR, IendR, JstrR, JendR       ! partition bounds
!
!  Contol vector to store state variables at the aggregated observation
!  locations.
!
        real(r8), allocatable :: state(:)           ! state vector
        integer,  allocatable :: ObsVetting(:)      ! reject/accept flag
!
!  Pointers for geometry variables.
!
        real(r8), pointer :: angle(:,:)   => NULL() ! curvilinear angle
        real(r8), pointer :: mask(:,:)    => NULL() ! land-sea mask
# ifdef SOLVE3D
        real(r8), pointer :: depth(:,:,:) => NULL() ! depths (m)
# endif
!
        CONTAINS
!
!  Constructors and destructors.
!
        PROCEDURE :: create              => obs_hofx_create
        PROCEDURE :: destroy             => obs_hofx_destroy
!
!  Operations.
!
        PROCEDURE :: add                 => obs_hofx_add
        PROCEDURE :: rms                 => obs_hofx_rms
        PROCEDURE :: zeros               => obs_hofx_zeros
!
!  Model at observation locations, no filters.
!
        PROCEDURE :: extract             => obs_hofx_extract
        PROCEDURE :: interp2d            => obs_hofx_interp2d
# ifdef ADJOINT
        PROCEDURE :: interp2d_ad         => obs_hofx_interp2d_ad
# endif
# ifdef SOLVE3D
        PROCEDURE :: interp3d            => obs_hofx_interp3d
#  ifdef ADJOINT
        PROCEDURE :: interp3d_ad         => obs_hofx_interp3d_ad
#  endif
# endif
!
!  Spatial and/or temporal H(X) filters: averaging.
!
        PROCEDURE :: area_avg2d          => obs_hofx_area_avg2d
# ifdef ADJOINT
        PROCEDURE :: area_avg2d_ad       => obs_hofx_area_avg2d_ad
# endif
        PROCEDURE :: area_time_avg2d     => obs_hofx_area_time_avg2d
# ifdef ADJOINT
        PROCEDURE :: area_time_avg2d_ad  => obs_hofx_area_time_avg2d_ad
# endif
!
        PROCEDURE :: time_avg2d          => obs_hofx_time_avg2d
# ifdef ADJOINT
        PROCEDURE :: time_avg2d_ad       => obs_hofx_time_avg2d_ad
# endif
# ifdef SOLVE3D
        PROCEDURE :: time_avg3d          => obs_hofx_time_avg3d
#  ifdef ADJOINT
        PROCEDURE :: time_avg3d_ad       => obs_hofx_time_avg3d_ad
#  endif
# endif
!
!  Parallel exchanges and writing.
!
        PROCEDURE :: nc_write            => obs_hofx_nc_write
        PROCEDURE :: update              => obs_hofx_update

      END TYPE obs_hofx
!
!-----------------------------------------------------------------------
!  Module variables.
!-----------------------------------------------------------------------
!
!  Background error (standard deviation) identifier.
!
      integer, parameter :: iSTD = 10
!
!  H(x) operators per nested grid, [1:Ngrids].
!
      TYPE (obs_hofx), allocatable :: nlm_hofx(:)     ! nonlinear
# ifdef ADJOINT
      TYPE (obs_hofx), allocatable :: adm_hofx(:)     ! adjoint
# endif
# ifndef VERIFICATION
      TYPE (obs_hofx), allocatable :: berr_hofx(:)    ! background error
# endif
# if defined TANGENT || defined TL_IOMS
      TYPE (obs_hofx), allocatable :: tlm_hofx(:)     ! tangent linear
# endif

The design is to have several objects of TYPE obs_hoxf to compute and store nonlinear, tangent linear, adjoint, background error, innovation, increment, and residual vectors. We can write IODA-type, showing several diagnostic Groups to analyze how the observations affected the data assimilation. Currently, we have BackgroundError, hofxInitial, hofxFinal, Innovation, Increment, and Residual.

% ncdump -h usec3_uv_codar_20190827_out.nc4 

netcdf usec3_uv_codar_20190827 {
dimensions:
	Location = 10332 ;
	nvars = 2 ;
	survey = 72 ;

// global attributes:
		:_ioda_layout = "ObsGroup" ;
		:_ioda_layout_version = 3 ;
		:odb_version = 1 ;
		:date_time = 2019082700. ;
		:datetimeReference = "2019-08-27T00:00:00Z" ;
		:description = "Native ROMS 4D-Var observations file converted to IODA" ;
		:sourceFiles = "usec3km_roms_obs_20190827.nc" ;
		:history = "Created from Matlab script: create_ioda_obs on Monday - July 14, 2025 - 9:55:31.40
83 AM" ;

group: MetaData {
  variables:
  	int64 dateTime(Location) ;
  		dateTime:long_name = "elapsed observation time since reference" ;
  		dateTime:units = "seconds since 2019-08-27T00:00:00Z" ;
  	float depth(Location) ;
  		depth:long_name = "observation depth below sea level" ;
  		depth:units = "meter" ;
  		depth:negative = "downwards" ;
  	float latitude(Location) ;
  		latitude:long_name = "observation latitude" ;
  		latitude:units = "degrees_north" ;
  	float longitude(Location) ;
  		longitude:long_name = "observation longitude" ;
  		longitude:units = "degrees_east" ;
  	int provenance(Location) ;
  		provenance:long_name = "observation origin identifier" ;
  	int sequenceNumber(Location) ;
  		sequenceNumber:long_name = "observation sequence number" ;
  	int stateID(nvars) ;
  		stateID:long_name = "state variable index" ;
  	int surveyIndex(survey) ;
  		surveyIndex:long_name = "observation survey time indices as they appear in dateTime" ;
  	int64 surveyTime(survey) ;
  		surveyTime:long_name = "observation survey time" ;
  		surveyTime:units = "seconds since 2019-08-27T00:00:00Z" ;
  	string variables_name(nvars) ;
  		variables_name:long_name = "observation UFO/IODA standard name" ;
  		string variables_name:_FillValue = "" ;
  	float x_grid(Location) ;
  		x_grid:long_name = "observation fractional x-grid location" ;
  	float y_grid(Location) ;
  		y_grid:long_name = "observation fractional y-grid location" ;
  	float z_grid(Location) ;
  		z_grid:long_name = "observation fractional z-grid location" ;
  } // group MetaData

group: ObsError {
  variables:
  	float waterZonalVelocity(Location) ;
  		waterZonalVelocity:long_name = "observation error standard deviation" ;
  		waterZonalVelocity:units = "m s-1" ;
  		waterZonalVelocity:coordinates = "longitude, latitude, depth, dateTime" ;
  	float waterMeridionalVelocity(Location) ;
  		waterMeridionalVelocity:long_name = "observation error standard deviation" ;
  		waterMeridionalVelocity:units = "m s-1" ;
  		waterMeridionalVelocity:coordinates = "longitude, latitude, depth, dateTime" ;
  } // group ObsError

group: ObsValue {
  variables:
  	float waterZonalVelocity(Location) ;
  		waterZonalVelocity:long_name = "observation value" ;
  		waterZonalVelocity:units = "m s-1" ;
  		waterZonalVelocity:coordinates = "longitude, latitude, depth, dateTime" ;
  	float waterMeridionalVelocity(Location) ;
  		waterMeridionalVelocity:long_name = "observation value" ;
  		waterMeridionalVelocity:units = "m s-1" ;
  		waterMeridionalVelocity:coordinates = "longitude, latitude, depth, dateTime" ;
  } // group ObsValue

group: PreQC {
  variables:
  	int waterZonalVelocity(Location) ;
  		waterZonalVelocity:long_name = "observation preset quality control filter identifier" ;
  		waterZonalVelocity:coordinates = "longitude, latitude, depth, dateTime" ;
  	int waterMeridionalVelocity(Location) ;
  		waterMeridionalVelocity:long_name = "observation preset quality control filter identifier" ;
  		waterMeridionalVelocity:coordinates = "longitude, latitude, depth, dateTime" ;
  } // group PreQC

group: ObsVetting {
  variables:
  	int waterZonalVelocity(Location) ;
  		waterZonalVelocity:long_name = "Observation screening flag, 0:reject and 1:accept" ;
  		waterZonalVelocity:coordinates = "longitude, latitude, depth, dateTime" ;
  	int waterMeridionalVelocity(Location) ;
  		waterMeridionalVelocity:long_name = "Observation screening flag, 0:reject and 1:accept" ;
  		waterMeridionalVelocity:coordinates = "longitude, latitude, depth, dateTime" ;
  } // group ObsVetting

group: BackgroundError {
  variables:
  	float waterZonalVelocity(Location) ;
  		waterZonalVelocity:long_name = "background error at observation locations" ;
  		waterZonalVelocity:units = "m s-1" ;
  		waterZonalVelocity:coordinates = "longitude, latitude, depth, dateTime" ;
  	float waterMeridionalVelocity(Location) ;
  		waterMeridionalVelocity:long_name = "background error at observation locations" ;
  		waterMeridionalVelocity:units = "m s-1" ;
  		waterMeridionalVelocity:coordinates = "longitude, latitude, depth, dateTime" ;
  } // group BackgroundError

group: hofxInitial {
  variables:
  	float waterZonalVelocity(Location) ;
  		waterZonalVelocity:long_name = "model background at observation locations" ;
  		waterZonalVelocity:units = "m s-1" ;
  		waterZonalVelocity:coordinates = "longitude, latitude, depth, dateTime" ;
  	float waterMeridionalVelocity(Location) ;
  		waterMeridionalVelocity:long_name = "model background at observation locations" ;
  		waterMeridionalVelocity:units = "m s-1" ;
  		waterMeridionalVelocity:coordinates = "longitude, latitude, depth, dateTime" ;
  } // group hofxInitial

group: hofxFinal {
  variables:
  	float waterZonalVelocity(Location) ;
  		waterZonalVelocity:long_name = "model analysis at observation locations" ;
  		waterZonalVelocity:units = "m s-1" ;
  		waterZonalVelocity:coordinates = "longitude, latitude, depth, dateTime" ;
  	float waterMeridionalVelocity(Location) ;
  		waterMeridionalVelocity:long_name = "model analysis at observation locations" ;
  		waterMeridionalVelocity:units = "m s-1" ;
  		waterMeridionalVelocity:coordinates = "longitude, latitude, depth, dateTime" ;
  } // group hofxFinal

group: Innovation {
  variables:
  	float waterZonalVelocity(Location) ;
  		waterZonalVelocity:long_name = "observation minus background" ;
  		waterZonalVelocity:units = "m s-1" ;
  		waterZonalVelocity:coordinates = "longitude, latitude, depth, dateTime" ;
  	float waterMeridionalVelocity(Location) ;
  		waterMeridionalVelocity:long_name = "observation minus background" ;
  		waterMeridionalVelocity:units = "m s-1" ;
  		waterMeridionalVelocity:coordinates = "longitude, latitude, depth, dateTime" ;
  } // group Innovation

group: Increment {
  variables:
  	float waterZonalVelocity(Location) ;
  		waterZonalVelocity:long_name = "analysis minus background" ;
  		waterZonalVelocity:units = "m s-1" ;
  		waterZonalVelocity:coordinates = "longitude, latitude, depth, dateTime" ;
  	float waterMeridionalVelocity(Location) ;
  		waterMeridionalVelocity:long_name = "analysis minus background" ;
  		waterMeridionalVelocity:units = "m s-1" ;
  		waterMeridionalVelocity:coordinates = "longitude, latitude, depth, dateTime" ;
  } // group Increment

group: Residual {
  variables:
  	float waterZonalVelocity(Location) ;
  		waterZonalVelocity:long_name = "observation minus analysis" ;
  		waterZonalVelocity:units = "m s-1" ;
  		waterZonalVelocity:coordinates = "longitude, latitude, depth, dateTime" ;
  	float waterMeridionalVelocity(Location) ;
  		waterMeridionalVelocity:long_name = "observation minus analysis" ;
  		waterMeridionalVelocity:units = "m s-1" ;
  		waterMeridionalVelocity:coordinates = "longitude, latitude, depth, dateTime" ;
  } // group Residual
}

Caution

The branch feature/obs_operator is still under development and is not ready for use.

Issue(s) addressed

None.

Impacts

It allows complex observation operators with averaging and other filtering algorithms that are currently under development.

Dependencies

None

Checklist

  • I have reviewed code updates or enhancements described in this PR
  • I have run and tested the changes before creating the PR

@hga007 hga007 changed the title Implementing a modern Data assimilation Observation Oper… Implementing a new Data assimilation Observation Operator Jul 12, 2025
@hga007 hga007 changed the title Implementing a new Data assimilation Observation Operator Implementing a new Data Assimilation Observation Operator Jul 12, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants