Changes between Version 17 and Version 18 of OMI_observation_modules_PDAF3


Ignore:
Timestamp:
May 9, 2026, 2:24:47 PM (7 days ago)
Author:
lnerger
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • OMI_observation_modules_PDAF3

    v17 v18  
    3232Each obs-module contains three routines (where 'TYPE' will be replaced by the name of the observation):
    3333
    34  - `init_dim_obs_OBSTYPE` initializes all variables holding the information about one observation type. The information about the observation type is stored in a data structure (Fortran derived type).
    35  - `obs_op_OBSTYPE` applies the observation operator to a state vector. One can call an observation operator routine provided by PDAF, or one can to implement a new operator.
    36  - `init_dim_obs_l_OBSTYPE` calls a PDAF-OMI routine to initialize the observation information corresponding to a local analysis domain. One can set localization parameters, like the localization radius, for each observation type.
    37 
    38 The template file `obs_OBSTYPE_pdafomi_TEMPLATE.F90` shows the different steps needed when implementing these routines. The main work is to implement `init_dim_obs`, while the other routines mainly call a subroutine provided by PDAF-OMI.
    39 
    40 In the obs-module the subroutines are named according to the observation type. The template file uses generic names which can be replaced by the user. Having distinct names for each observation type is relevant to include the subroutine from the module in the call-back routine with ‘use’. In the header of each obs-module, the user can declare further variables, e.g. assim_OBSTYPE as a flag to control whether the observation type should be assimilated.
    41 
    42 '''Note:''' In contrast to the 'classical' implementation of observation routines for PDAF, the global and local filters use the same routines `init_dim_obs` and `obs_op`. PDAF-OMI recognizes whether a global or local filter is used and does the necessary operations by itself.
     34 - **init_dim_obs_OBSTYPE** initializes all variables holding the information about one observation type. The information about the observation type is stored in a data structure (Fortran 'type' variale).
     35 - **obs_op_OBSTYPE** applies the observation operator to a state vector. One can call an observation operator routine provided by PDAF, or one can to implement a new operator.
     36 - **init_dim_obs_l_OBSTYPE** calls a PDAF-OMI routine to obtain the local observation information corresponding to a local analysis domain. i.e. observations within the localization radius. One can set localization parameters, like the localization radius, for each observation type.
     37
     38The template file `obs_OBSTYPE_pdafomi_TEMPLATE.F90` shows the different steps needed when implementing these routines. The main work is to implement `init_dim_obs_OBSTYPE`, while the other routines mainly call a subroutine provided by PDAF-OMI.
     39
     40In the obs-module, the subroutines are named according to the observation type. The template file uses generic names which should be replaced by the user. Having distinct names for each observation type is relevant to include the subroutine from the module in the call-back routine with ‘use’. In the header of each obs-module, the user can declare further variables, e.g. `rms_obs_OBSTYPE` as a uniform observation error value.
     41
    4342
    4443== Data type `obs_f` ==
    4544
    46 To ensure the functionality within each obs-module, we rely on a derived data type called `obs_f` that contains all information about the observation. One instance of this data type is allocated in each obs-module with the generic variable name `thisobs`. A few of the elements of `obs_f` are initialized by the user when the observation information is initialized on `init_dim_obs_f`. Further variables is set in a call to the routine `PDAFomi_gather_obs`. This information is then used by all other routines in the obs-module. The template file `obs_OBSTYPE_pdafomi_TEMPLATE.F90` shows the different steps needed to initialize `thisobs`.
     45To ensure the functionality within each obs-module, we rely on a Fortran data type called `obs_f` that contains all information about the observation. The data type is declared by PDAF-OMI and one instance of this data type is allocated in each obs-module with the generic variable name `thisobs`. A few of the elements of `obs_f` are initialized by the user when the observation information is initialized on `init_dim_obs_OBSTYPE`. Further variables is set in a call to the routine `PDAFomi_gather_obs`. This information is then used by all other routines in the obs-module. The template file `obs_OBSTYPE_pdafomi_TEMPLATE.F90` shows the different steps needed to initialize `thisobs`.
    4746
    4847The '''mandatory variables''' in `obs_f` that need to be set by the user are:
     
    5049  TYPE obs_f
    5150     ! ---- Mandatory variables to be set in INIT_DIM_OBS ----
    52      INTEGER :: doassim=0                 !< Whether to assimilate this observation type
    53      INTEGER :: disttype                  !< Type of distance computation to use for localization
     51     INTEGER :: doassim=0                 ! Whether to assimilate this observation type
     52     INTEGER :: disttype                  ! Type of distance computation to use for localization
    5453                                          !   (0) Cartesian, (1) Cartesian periodic
    5554                                          !   (2) simplified geographic, (3) geographic haversine function
     
    5756                                          !   (12) simplified geographic 2+1D factorized
    5857                                          ! (13) geographic haversine function  2+1D factorized
    59      INTEGER :: ncoord                    !< Number of coordinates use for distnce computation
    60      INTEGER, ALLOCATABLE :: id_obs_p(:,:) !< Indices of process-local observed field in state vector
     58     INTEGER :: ncoord                    ! Number of coordinates use for distnce computation
     59     INTEGER, ALLOCATABLE :: id_obs_p(:,:) ! Indices of process-local observed field in state vector
    6160     ...
    6261  END TYPE obs_f
     
    6867     ...
    6968     ! ---- Optional variables - they can be set in INIT_DIM_OBS ----
    70      REAL, ALLOCATABLE :: icoeff_p(:,:)   !< Interpolation coefficients for obs. operator (optional)
    71      REAL, ALLOCATABLE :: domainsize(:)   !< Size of domain for periodicity (<=0 for no periodicity) (optional)
     69     REAL, ALLOCATABLE :: icoeff_p(:,:)   ! Interpolation coefficients for obs. operator (optional)
     70     REAL, ALLOCATABLE :: domainsize(:)   ! Size of domain for periodicity (<=0 for no periodicity) (optional)
    7271
    7372     ! ---- Variables with predefined values - they can be changed in INIT_DIM_OBS  ----
    74      INTEGER :: obs_err_type=0            !< Type of observation error: (0) Gauss, (1) Laplace
    75      INTEGER :: use_global_obs=1          !< Whether to use (1) global full obs.
    76                                           !< or (0) obs. restricted to those relevant for a process domain
    77      CHARACTER(len=20) :: name=""         !< Name of observation type
     73     INTEGER :: obs_err_type=0            ! Type of observation error: (0) Gauss, (1) Laplace
     74     INTEGER :: use_global_obs=1          ! Whether to use (1) global full obs.
     75                                          ! or (0) obs. restricted to those relevant for a process domain
     76     CHARACTER(len=20) :: name=""         ! Name of observation type
    7877     ...
    7978  END TYPE obs_f
    8079}}}
    8180
    82 Apart from these variables, there is a number of variables that are set internally when the routine `PDAFomi_gather_obs` is called. The full data type can be seen on the [wiki:OMI_debugging_PDAF3 page on OMI debugging].
    83 
    84 
    85 Next to the derived data type `obs_f`, there is a derived type `obs_l` for localization. This is only used internally. It will be filled in the routine `init_dim_obs_l` when calling `PDAFomi_init_dim_obs_l`.
     81Additional variables are set internally when the routine `PDAFomi_gather_obs` is called. The full data type can be seen on the [wiki:OMI_debugging_PDAF3 page on OMI debugging].
     82
     83
     84Next to the data type `obs_f`, there is a data type `obs_l` for localization. This is only used internally. It will be filled in the routine `PDAFomi_init_dim_obs_l` which is called by `init_dim_obs_l_OBSTYPE`.
    8685
    8786== Required routines ==
     
    103102**1. Initialize variables **
    104103
    105 The main variables that the filled in this routine are
     104The main variables of **thisobs** that are directly filled in this routine are
    106105 * [wiki:#thisobsdoassim thisobs%doassim]: Specify whether this observation type is assimilated
    107106 * [wiki:#thisobsdisttype thisobs%disttype]: Specify the type of distance computation
    108107 * [wiki:#thisobsncoord thisobs%ncoord]: Specify the number of dimensions used to compute distances
     108 * [wiki:#thisobsid_obs_p thisobs%id_obs_p]: store the indices of state vector elements that correspond to an observation (A single value for observation at grid points, or multiple values for derived quantities or interpolation; this is only used in the OMI-provided observation operators)
     109
     110The next variables are filled and provided to `PDAFomi_gather_obs`. The variable names shown here are those used in the template and tutorial codes.
    109111 * [wiki:#dim_obs_p dim_obs_p]: Count the number of available observations
    110112 * [wiki:#obs_p obs_p]: Fill the vector of observations
    111113 * [wiki:#ocoord_p ocoord_p]: store the coordinates of the observations (only actually used in case of localization)
    112114 * [wiki:#ivar_obs_p ivar_obs_p]: store the inverse error variance of each observation for the default mode of OMI, which assumes a diagonal observation error covariance matrix
    113  * [wiki:#thisobsid_obs_p thisobs%id_obs_p]: store the indices of state vector elements that correspond to an observation (A single value for observation at grid points, or multiple values for derived quantities or interpolation; this is only used in the OMI-provided observation operators)
    114 
    115 Further possible initializations:
    116  * When the observation operator performs interpolation, one further needs to initialize an array of interpolation coefficients ([wiki:OMI_observation_modules_PDAF3#thisobsicoeff_p thisobs%icoeff_p]).
     115
     116Further possible initializations are:
     117 * When one uses PDAF-OMI's observation operator with interpolation, one further needs to initialize an array of interpolation coefficients ([wiki:OMI_observation_modules_PDAF3#thisobsicoeff_p thisobs%icoeff_p]).
    117118 * For Cartesian distance computation with periodicity one also needs to set [wiki:OMI_observation_modules#thisobsdomainsize thisobs%domainsize].
    118  * One can store the name of the observation type  (thisobs%name; a string of lenggth up to 20 characters)
     119 * When using local ensemble filters with parallelization, and can also use `thisobs%use_global_obs=0` to improve the performance of the search for local observations. This feature requires that the sub-domain limits have been provided to PDAF-OMI with `PDAFomi_set_domain_limits` (which is usually don ein `init_pdaf`).
    119120 * One can also activate the [wiki:OMI_additional_functionality_PDAF3#Omittingobservationsthatarepotentialoutliers omission of observations that are too different from the ensemble mean]. This is activated by setting `thisobs%inno_omit>0.0`
     121 * One can store the name of the observation type  (thisobs%name; a string of length up to 20 characters)
    120122
    121123When parallel model with domain decomposition is used, the variables with suffix `_p` need to describe the observation information for a particular process sub-domain.
     
    153155Here, `state_p` is the state vector and `ostate` is the observed state vector.
    154156
    155 For ensemble methods, the observation operator is called in a loop over all ensemble members for one ensmble state at a time. The index of the ensemble member for which the observation operator is called can be determine using the routine [wiki:PDAF_get_obsmemberid].
     157Note:
     158
     159 * For ensemble methods, the observation operator is called in a loop over all ensemble members for one ensemble state at a time. One can obtain the ''index of the ensemble member for which the observation operator is called'' using the routine [wiki:PDAF_get_obsmemberid].
    156160
    157161For more information on the available observation operators and on how to implement your own observation operator see the [wiki:OMI_observation_operators_PDAF3 documentation of observation operators for OMI].
     
    160164=== `init_dim_obs_l_OBSTYPE` ===
    161165
    162 This routine initializes local observation information. The routine is only used by the domain-localized filters (LESTKF, LETKF, LSEIK, LNETF, LKNETF).
     166This routine initializes local observation information. The routine is only used by the domain-localized filters (LESTKF, LETKF, LSEIK, LNETF, LKNETF) and by ensemble 3D-Var using the LESTKF.
    163167
    164168For the initialization the following routine is called:
     
    171175
    172176''Implementation steps:''
    173  - Ensure that `coords_l` is filled in `init_dim_l_pdaf` and that the unit of `coords_l` is the same as that used fo rthe observation coordinates.
     177 - Ensure that `coords_l` is filled and that the unit of `coords_l` is the same as that used for the observation coordinates. Usually `coords_l` is filled in `init_dim_l_pdaf` as shown in the tempaltes and tutorial.
    174178 - Specify the localization variables (These variables are usually set in `init_pdaf` and included with `use mod_assimilation`)
    175    - `locweight`: Type of localization (see table below)
    176    - `cradius`: The localization radius or directional radii (cut-off radius for the observations, weight is always =0 for distances > cradius)
    177    - `sradius`: The support radius (or directional redii) of the localization weight function
    178 Note, that starting with PDAF V2.2.1 these three variables can be either scalar values - for isotropic localization-, or arrays - for non-isotropic localization and for additionally choose separate weights functions for the horizontal and vertical directions (see the notes below for more information)
     179   - ''locweight'': Type of localization (see table below)
     180   - ''cradius'': The localization cut-off radius or directional radii (cut-off radius for the observations, weight is always =0 for distances > cradius)
     181   - ''sradius'': The support radius (or directional radii) of the localization weight function. Usually, we set `sradius=cradius`. Using different radii is an option to tune the localization.
     182
     183Note, that for PDAF V2.2.1 and later these three variables can be either scalar values (for isotropic localization), or arrays (for non-isotropic localization). In the latter case one can additionally choose separate weight functions for the horizontal and vertical directions (see the notes below for more information)
    179184
    180185The setting of `locweight` influences the weight function for the localization. The choices are standardized as follows
     
    186191||= '''sradius''' =||  no impact  ||  weight = exp(-d / sradius)  ||||||||  weight = 0 if d >= sradius[[BR]] else[[BR]] weight = f(sradius, distance)  ||
    187192
    188 Here, 'regulation' refers to the regulated localization introduced in Nerger, L., Janjić, T., Schröter, J., Hiller, W. (2012). A regulated localization scheme for ensemble-based Kalman filters. Quarterly Journal of the Royal Meteorological Society, 138, 802-812. ​[https://doi.org/10.1002/qj.945 doi:10.1002/qj.945].
     193Here, 'regulation' refers to the regulated localization introduced in Nerger, L., et al. (2012). A regulated localization scheme for ensemble-based Kalman filters. Quarterly Journal of the Royal Meteorological Society, 138, 802-812. ​[https://doi.org/10.1002/qj.945 doi:10.1002/qj.945].
    189194
    190195**Notes:**
     
    193198- **2D+1D factorized non-isotropic localization**: With PDAF V2.2.1 a factorized 2D+1D localization can be specified (see [wiki:OMI_observation_modules#thisobsdisttype explanation of disttype]. If the non-isotropic localization is used one can specify different weight functions for the vertical and horizontal directions. This is achieved by declaring `loweight` as a vector of size 2. Now the first element specifies the weight function (according to the table above) for the horizontal direction and the second element specified the wieght function for the vertical direction. When 'locweight' is used as a scalar variable, it specified the weight function in the horizontal direction while the weight function in the vertical dircetion is a constant value of one.
    194199- A common choice is to use `locweight=2` or `locweight=4` in combination with `cradius=sradius`. Choosing `sradius>cradius` is possible, but `sradius<cradius` should be avoided (one would set the weights of distant observation to zero, but would still assimilate them).
    195 - **optimizing the search operation**: With PDAF V3.1 and later one can optimize the performance of the local search operation in `PDAFomi_init_dim_obs_l` by using the routine [wiki:PDAFomi_set_searchtype]. The speed of the search operation depends on different factors and this routine allows to switch the type of the search operation which can improve the speed.
     200- **optimizing the search operation**: With PDAF V3.1 and later one can optimize the performance of the search for local observations in `PDAFomi_init_dim_obs_l` by using the routine [wiki:PDAFomi_set_searchtype]. The speed of the search operation depends on different factors and this routine allows to switch the type of the search operation which can improve the speed.
    196201
    197202== Routine for backward compatibility ==
     
    199204=== `localize_covar_OBSTYPE` ===
    200205
    201 || This routine exists for backward compatibility. In PDAF V3.0, we added the routine [wiki:OMI_observation_modules_PDAF3#Initializingcovariancelocalization PDAFomi_set_localize_covar], which is described below. When `PDAFomi_set_localize_covar` is used, the user-provided routine `localize_covar_OBSTYPE` is no longer required. ||
     206|| This routine exists for backward compatibility for implementations started before PDAF V3.0. In PDAF V3.0, we added the routine [wiki:OMI_observation_modules_PDAF3#Initializingcovariancelocalization PDAFomi_set_localize_covar], which is described below. When `PDAFomi_set_localize_covar` is used, the user-provided routine `localize_covar_OBSTYPE` is no longer required. ||
    202207
    203208This routine initializes local observation information. The routine is only used by the local EnKF (LEnKF).
     
    209214}}}
    210215
    211 Here, `thisobs` is the data-type variable `obs_f`. `HP_p` and `HPH` are the covariance matrices projected onto the observations. The localization will be applied to these variables.
     216Here, `thisobs` is the variable with data type `obs_f` of PDAF-OMI. `HP_p` and `HPH` are the covariance matrices projected onto the observation space. The localization will be applied to these variables.
    212217
    213218''Implementation steps:''
    214  - Ensure that `coords_p` is filled in `localize_covar_pdafomi`
     219 - Ensure that `coords_p` is filled. This can be don ein `init_pdaf` or in `localize_covar_pdafomi`.
    215220 - Specify the localization variables (These variables are usually set in `init_pdaf` and included with `use mod_assimilation`)
    216221   - `locweight`: Type of localization (see table above)
    217222   - `cradius`: The localization radius (cut-off radius for the observations, weight is always =0 for distances > cradius)
    218223   - `sradius`: The support radius of the localization weight function
    219 Note, that starting with PDAF V2.2.1 these three variables can be either scalar values - for isotropic localization-, or arrays - for non-isotropic localization and for additionally choose separate weights functions for the horizontal and vertical directions (see the notes below for more information)
     224
     225Note, that with PDAF V2.2.1 and later these three variables can be either scalar values - for isotropic localization-, or arrays - for non-isotropic localization and for additionally choose separate weights functions for the horizontal and vertical directions (see the notes on `init_dim_obs_l_OBSTYPE` above for more information)
    220226
    221227**Notes:**
    222 - **isotropic localization**: If `cradius` and `sradius` are scalar values, the localization is isotropic. Thus, it uses the same `cradius` in all directions. If different localization scales should be applied e.g. in the vertical compared to the horizonal one needs to scale the vertical coordinates.
    223 - **non-isotropic localization**: Nonisotropic localization was introduced with PDAF V2.2: `cradius` and `sradius` can declared as vectors of length `thisobs%ncoords` and each element can get a different value. In this case, the values defined a non-isotropic localization according to the values specified in `cradius` and `sradius`.
    224 - **non-isotropic localization**: Nonisotropic localization was introduced with PDAF V2.2: `cradius` and `sradius` can be declared as vectors of length `thisobs%ncoords` and each element can get a different value. In this case, the values define a non-isotropic localization according to the values specified in `cradius` and `sradius`. PDAF-OMI will use these values to compute a directional localization radius.
    225 - **2D+1D factorized non-isotropic localization**: With PDAF V2.2.1 a factorized 2D+1D localization can be specified (see [wiki:OMI_observation_modules#thisobsdisttype explanation of disttype]. If the non-isotropic localization is used one can specify different weight functions for the vertical and horizontal directions. This is achieved by declaring `loweight` as a vector of size 2. Now the first element specifies the weight function (according to the table above) for the horizontal direction and the second element specified the wieght function for the vertical direction. When 'locweight' is used as a scalar variable, it specified the weight function in the horizontal direction while the weight function in the vertical dircetion is a constant value of one.
    226 - A common choice for the localization is to use `locweight=2` or `locweight=4` in combination with `cradius=sradius`. Choosing `sradius>cradius` is possible, but `sradius<cradius` should be avoided (one would set the weights of distant observation to zero, but would still assimilate them).
     228- A common choice for the localization is to use `locweight=2` in combination with `cradius=sradius`. Choosing `sradius>cradius` is possible, but `sradius<cradius` should be avoided (one would set the weights of distant observation to zero, but would still assimilate them).
    227229- Particular for the LEnKF: When choosing `locweight=1` (exponential decrease) with a finite value of `cradius` if might be that the localized covariance matrices are no longer positive semidefinite. Mathematically consistent for `locweight=1` would be to set `cradius` so that the full model domain is covered. The width of the localization weight function is then defined by `sradius`. For `locweight>1` one should set `cradius=sradius`.
    228230
     
    323325
    324326This variable specifies the type of distance computation. Possible choices are
    325  - 0: Cartesian distance in ncoord dimension
    326  - 1: Cartesian distance in ncoord dimensions with periodicity (Needs specification of thisobs%domainsize(ncoord))
    327  - 2: Approximate geographic distance in meters with horizontal coordinates in radians (latitude: -pi/2 to +pi/2; longitude -pi/+pi or 0 to 2pi)
    328  - 3: Geographic distance computation in meters using haversine formula with horizontal coordinates in radians (latitude: -pi/2 to +pi/2; longitude -pi/+pi or 0 to 2pi)
     327
     328||= disttype =||= Description =||
     329|| 0 || Cartesian distance in ncoord dimension ||
     330|| 1 || Cartesian distance in ncoord dimensions with periodicity (Needs specification of thisobs%domainsize(ncoord)) ||
     331|| 2 || Approximate geographic distance in meters with horizontal coordinates in radians (latitude: -pi/2 to +pi/2; longitude -pi/+pi or 0 to 2pi) ||
     332|| 3 || Geographic distance computation in meters using haversine formula with horizontal coordinates in radians (latitude: -pi/2 to +pi/2; longitude -pi/+pi or 0 to 2pi) ||
     333
     334
    329335With PDAF V2.2.1, a **2D+1D factorized localization** was introduced for 3-dimensional applications. With the factorized localization, the horizontal distance (components 1 and 2) is treated separately from the vertical direction (3rd component). This is available for both isoptropic and non-isotropic localization and activated using the choices
    330  - 10: Cartesian distance 2D+1D factorized in 3 dimensions
    331  - 11: Cartesian distance 2D+1D factorized in 3 dimensions with periodicity (Needs specification of thisobs%domainsize(ncoord))
    332  - 12: Approximate geographic distance 2D+1D factorized in meters with horizontal coordinates in radians (latitude: -pi/2 to +pi/2; longitude -pi/+pi or 0 to 2pi) and vertical in unit chosen by the user.
    333  - 13: Geographic distance computation 2D+1D factorized in meters using haversine formula with horizontal coordinates in radians (latitude: -pi/2 to +pi/2; longitude -pi/+pi or 0 to 2pi) and vertical in unit as chosen by the user.
     336||= disttype =||= Description =||
     337|| 10 || Cartesian distance 2D+1D factorized in 3 dimensions ||
     338|| 11 || Cartesian distance 2D+1D factorized in 3 dimensions with periodicity (Needs specification of `thisobs%domainsize(ncoord)`) ||
     339|| 12 || Approximate geographic distance 2D+1D factorized in meters with horizontal coordinates in radians (latitude: -pi/2 to +pi/2; longitude -pi/+pi or 0 to 2pi) and vertical in unit chosen by the user. ||
     340|| 13 || Geographic distance computation 2D+1D factorized in meters using haversine formula with horizontal coordinates in radians (latitude: -pi/2 to +pi/2; longitude -pi/+pi or 0 to 2pi) and vertical in unit as chosen by the user. ||
    334341
    335342**Notes:**
    336 - When disttype>=10 is specified with isotropic localization the weight function for the vertical direction is constant with a valu eof one. For non-isotropic localization, the weight functions can be separately specified for the vertical and horizontal directions. (see the [wiki:OMI_observation_modules_PDAF3#init_dim_obs_l_OBSTYPE description of init_dim_obs_l_OBSTYPE] for information on how to specify the ono-isotropic localization.
     343- When disttype>=10 is specified with isotropic localization, the weight function for the vertical direction is constant with a value =1. For non-isotropic localization, the weight functions can be separately specified for the vertical and horizontal directions. (see the [wiki:OMI_observation_modules_PDAF3#init_dim_obs_l_OBSTYPE description of init_dim_obs_l_OBSTYPE] for information on how to specify the ono-isotropic localization.
    337344- For 0 and 1 (likewise 10, 11) any distance unit can be used. The computed distance will be in the same unit. For 2 and 3 the horizontal input coordinates are in radians and the distance is computed in meters. Essential is that the grid point coordinates and observation coordinates use the same unit.
    338345- For 3-dimensional localization, the unit of the vertical direction can be chosen by the user. However, for geographic ditances, the unit should be chosen to be 'compatible' with the unit in the horizontal (meter). When isotropic localization is used, the unit for the vertical direction can be scaled do that the length scales in the vertical and horizontal directions are the same  (this, e.g., allows to use pressure as the distance measure in the vertical in atmospheric models). For non-isotropic localization, the units can differ without scaling. In ccase of the factorized 2D+1D localization (disttype>=10), the units in the horizontal and vertical directions are independent.