# 1 "../oda_tools/oda_core.F90"
!***********************************************************************
!* GNU Lesser General Public License
!*
!* This file is part of the GFDL Flexible Modeling System (FMS).
!*
!* FMS is free software: you can redistribute it and/or modify it under
!* the terms of the GNU Lesser General Public License as published by
!* the Free Software Foundation, either version 3 of the License, or (at
!* your option) any later version.
!*
!* FMS is distributed in the hope that it will be useful, but WITHOUT
!* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
!* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
!* for more details.
!*
!* You should have received a copy of the GNU Lesser General Public
!* License along with FMS. If not, see .
!***********************************************************************
module oda_core_mod
!
! Matthew Harrison
!
!
!
! core ocean data assimilation subroutines for ocean models. This module
! includes interfaces to :
!
! (i) initialize the ODA core with observations and model
! (ii) request ocean state increments using observed or idealized data
! (iii) calculate model guess differences and output to file
! (iv) terminate the DA core.
!
! NOTE: Profile files conform to v0.1a profile metadata standard.
! This metadata standard will evolve in time and will maintain
! backward compability.
!
! NOTE: This code is still under development. ODA data structures should be
! opaque in future releases.
!
!
!
!
!
! flag measurement if abs(omf) exceeds max_misfit
!
!
! minumum rms temperature observation error (degC) for
! variable obs error, else nominal temp error
!
!
! minimum rms salinity observation error (g/kg) for
! variable obs error,else nominal salt error
!
!
! Tidal internal displacement amplitude (meters) for observational error estimation.
!
!
! Minimum profile depth (meters)
!
!
! Data must be contiguous to this resolution (meters). Otherwise flag is activated.
!
!
! Half-width of profile time window (days)
!
!
! Add tidal aliasing to observation error. Use eta_tide_const * dT/dz to estimate
! observation.
!
!
! Allocation size of profile array
!
!
! Allocation size of surface data array
!
!
! nominal temperature error rms error
!
!
! nominal salinity error rms error
!
!
use fms_mod, only : file_exist,read_data
use mpp_mod, only : mpp_error, FATAL, NOTE, mpp_sum, stdout,&
mpp_sync_self, mpp_pe,mpp_npes,mpp_root_pe,&
mpp_broadcast, input_nml_file
use mpp_domains_mod, only : mpp_get_compute_domain, mpp_get_data_domain, &
domain2d, mpp_get_global_domain, mpp_update_domains
use time_manager_mod, only : time_type, operator( <= ), operator( - ), &
operator( > ), operator ( < ), set_time, set_date, &
get_date, get_time
use get_cal_time_mod, only : get_cal_time
use axis_utils_mod, only : frac_index
use constants_mod, only : radian, pi
use oda_types_mod
use write_ocean_data_mod, only : write_ocean_data_init
implicit none
private
real, private :: max_misfit = 5.0 ! reject fg diff gt max_misfit
real, parameter, private :: depth_min=0.0, depth_max=10000.
real, parameter, private :: temp_min=-3.0, temp_max=40.
real, parameter, private :: salt_min=0.0, salt_max=45.
integer :: max_profiles = 250000, max_sfc_obs = 1
integer, parameter, private :: max_files=100
integer, parameter, private :: PROFILE_FILE = 1,SFC_FILE= 2,&
IDEALIZED_PROFILES=3
! parameters for obs error inflation due to internal tidal displacements
real, private :: min_obs_err_t = 0.5, min_obs_err_s=0.1, eta_tide_const = 7.0
type(ocean_profile_type), target, save, private, allocatable :: profiles(:)
type(ocean_surface_type), target, save, private, allocatable :: sfc_obs(:)
integer, private, save :: num_profiles, num_sfc_obs ! total number of observations
integer, private, save :: isc, iec, jsc, jec, isd, ied, jsd, jed ! indices for local and global domain
integer, private, save :: isg, ieg, jsg, jeg
integer, private, save :: nk
! parameters used to restrict profiles
real, private, save :: min_prof_depth = 200.0 ! profile data ending
! above this level are
! discarded.
real, private, save :: max_prof_spacing = 1.e5 ! reject profile data
! if not contiguous
! at this resolution
! internal arrays for forward and backward observations
real, dimension(:,:,:), allocatable, private, save :: sum_wgt, nobs
type(time_type) , dimension(0:100), public :: time_window
! the DA module maintains a unique grid,domain association
type(grid_type), pointer :: Grd
! DA grid (1-d) coordinates. Generalized horizontal coordinates are not supporte.
real, allocatable, dimension(:) :: x_grid, y_grid
real :: temp_obs_rmse = 0.7071
real :: salt_obs_rmse = 0.1
logical :: add_tidal_aliasing=.false.
logical :: localize_data = .true.
logical :: debug=.false.
integer :: ndebug=10
integer, allocatable, dimension(:) :: nprof_in_comp_domain
namelist /oda_core_nml/ max_misfit,add_tidal_aliasing,min_obs_err_t,&
min_obs_err_s, eta_tide_const, debug, max_profiles,&
max_sfc_obs, temp_obs_rmse, salt_obs_rmse, ndebug
! NOTE: Surface observation type is not yet implemented.
! transform from Grd => Obs
interface forward_obs
module procedure forward_obs_profile
module procedure forward_obs_sfc
end interface
! transform from Obs => Grd
interface backward_obs
module procedure backward_obs_profile
module procedure backward_obs_sfc
end interface
! one-time association between profile and grid, used for storing
! interpolation weights
interface assign_forward_model
module procedure assign_forward_model_profile
module procedure assign_forward_model_sfc
end interface
! duplicate observation array
interface copy_obs
module procedure copy_obs_prof
module procedure copy_obs_sfc
end interface
! multiply observation data by inverse error variance
interface mult_obs_I_mse
module procedure mult_obs_I_mse_profile
module procedure mult_obs_I_mse_sfc
end interface
! difference between two observations (e.g. model first guess and observations)
interface diff_obs
module procedure diff_obs_profile
module procedure diff_obs_sfc
end interface
! inflate observational error based on first guess misfit
! and time window.
interface adjust_obs_error
module procedure adjust_obs_error_profile
module procedure adjust_obs_error_sfc
end interface
interface nullify_obs
module procedure nullify_obs_prof
end interface
public :: forward_obs, backward_obs, &
copy_obs, adjust_obs_error, &
oda_core_init, open_profile_dataset, get_obs, &
assign_forward_model, diff_obs, mult_obs_I_mse, &
purge_obs, nullify_obs
contains
subroutine open_profile_dataset(filename, localize)
!
! open dataset containing profile information in fms station format.
! store internally.
!
use mpp_io_mod, only : mpp_open, mpp_get_atts, mpp_get_info, &
mpp_get_fields, mpp_read, MPP_SINGLE, MPP_MULTI, MPP_NETCDF,&
axistype, atttype, fieldtype, MPP_RDONLY, mpp_get_axes, mpp_close,&
mpp_get_att_char
character(len=*), intent(in) :: filename
logical, intent(in), optional :: localize
logical :: found_neighbor,continue_looking
integer, parameter :: max_levels=1000
integer :: unit, ndim, nvar, natt, nstation
character(len=32) :: fldname, axisname
type(atttype), allocatable, dimension(:), target :: global_atts
type(atttype), pointer :: version => NULL()
type(axistype), pointer :: depth_axis => NULL(), station_axis => NULL()
type(axistype), allocatable, dimension(:), target :: axes
type(fieldtype), allocatable, dimension(:), target :: fields
type(fieldtype), pointer :: field_lon => NULL(), field_lat => NULL(), field_probe => NULL(),&
field_time => NULL(), field_depth => NULL()
type(fieldtype), pointer :: field_temp => NULL(), field_salt => NULL(), &
field_error => NULL(), field_link => NULL()
! NOTE: fields are restricted to be in separate files
real :: lon, lat, time, depth(max_levels), temp(max_levels), salt(max_levels),&
error(max_levels), rprobe, profile_error, rlink
integer :: nlev, probe, yr, mon, day, hr, minu, sec, kl, outunit
integer :: num_levs, num_levs_temp, num_levs_salt,&
k, kk, ll, i, i0, j0, k0, nlevs, a, nn, ii, nlinks
real :: ri0, rj0, rk0, dx1, dx2, dy1, dy2
character(len=128) :: time_units, attname, catt
type(time_type) :: profile_time
integer :: flag_t(max_levels), flag_s(max_levels), cpe
logical :: data_is_local, &
continue
logical :: found_temp=.false., found_salt=.false.
real, dimension(max_links,max_levels) :: temp_bfr, salt_bfr, depth_bfr
integer, dimension(max_links,max_levels) :: flag_t_bfr, flag_s_bfr
real :: temp_missing=missing_value,salt_missing=missing_value,&
depth_missing=missing_value
real :: max_prof_depth, zdist
! read timeseries of local observational data from NetCDF files
! and allocate ocean_obs arrays.
! File structure:
! dimensions:
! depth_index;station=UNLIMITED;
! variables:
! depth_index(depth_index);
! station(station);
! longitude(station);
! latitude(station);
! time(station);
! data(station,depth_index);
! depth(station,depth_index);
! probe(station);
! err(station, depth_index);
cpe = mpp_pe()
dx1 = (x_grid(isc)-x_grid(isc-1))/2.0
dx2 = (x_grid(iec+1)-x_grid(iec))/2.0
dy1 = (y_grid(jsc)-y_grid(jsc-1))/2.0
dy2 = (y_grid(jec+1)-y_grid(jec))/2.0
localize_data = .true.
if (PRESENT(localize)) localize_data = localize
call mpp_open(unit,filename,form=MPP_NETCDF,fileset=MPP_SINGLE,&
threading=MPP_MULTI,action=MPP_RDONLY)
call mpp_get_info(unit, ndim, nvar, natt, nstation)
outunit = stdout()
write(outunit,*) 'Opened profile dataset :',trim(filename)
! get version number of profiles
allocate(global_atts(natt))
call mpp_get_atts(unit,global_atts)
do i=1,natt
catt = mpp_get_att_char(global_atts(i))
select case (lowercase(trim(catt)))
case ('version')
version => global_atts(i)
end select
end do
if (.NOT.ASSOCIATED(version)) then
call mpp_error(NOTE,'no version number available for profile file, assuming v0.1a ')
else
write(outunit,*) 'Reading profile dataset version = ',trim(catt)
endif
! get axis information
allocate (axes(ndim))
call mpp_get_axes(unit,axes)
do i=1,ndim
call mpp_get_atts(axes(i),name=axisname)
select case (lowercase(trim(axisname)))
case ('depth_index')
depth_axis => axes(i)
case ('station_index')
station_axis => axes(i)
end select
end do
if (.NOT.ASSOCIATED(depth_axis) .or. .NOT.ASSOCIATED(station_axis)) then
call mpp_error(FATAL,'depth and/or station axes do not exist in input file')
endif
! get selected field information.
! NOTE: not checking for all variables here.
allocate(fields(nvar))
call mpp_get_fields(unit,fields)
do i=1,nvar
call mpp_get_atts(fields(i),name=fldname)
select case (lowercase(trim(fldname)))
case ('longitude')
field_lon => fields(i)
case ('latitude')
field_lat => fields(i)
case ('probe')
field_probe => fields(i)
case ('time')
field_time => fields(i)
case ('temp')
field_temp => fields(i)
case ('salt')
field_salt => fields(i)
case ('depth')
field_depth => fields(i)
case ('link')
field_link => fields(i)
case ('error')
field_error => fields(i)
end select
enddo
call mpp_get_atts(depth_axis,len=nlevs)
if (nlevs > max_levels) call mpp_error(FATAL,'increase parameter max_levels ')
if (nlevs < 1) call mpp_error(FATAL)
outunit = stdout()
write(outunit,*) 'There are ', nstation, ' records in this dataset'
write(outunit,*) 'Searching for profiles matching selection criteria ...'
if (ASSOCIATED(field_temp)) found_temp=.true.
if (ASSOCIATED(field_salt)) found_salt=.true.
if (.not. found_temp .and. .not. found_salt) then
write(outunit,*) 'temp or salt not found in profile file'
call mpp_error(FATAL)
endif
call mpp_get_atts(field_time,units=time_units)
if (found_temp) call mpp_get_atts(field_temp,missing=temp_missing)
if (found_salt) call mpp_get_atts(field_salt,missing=salt_missing)
call mpp_get_atts(field_depth,missing=depth_missing)
if (found_salt) then
write(outunit,*) 'temperature and salinity where available'
else
write(outunit,*) 'temperature only records'
endif
i=1
continue=.true.
do while (continue)
depth(:) = missing_value
temp(:) = missing_value
salt(:) = missing_value
! required fields
call mpp_read(unit,field_lon,lon,tindex=i)
call mpp_read(unit,field_lat,lat, tindex=i)
call mpp_read(unit,field_time,time,tindex=i)
call mpp_read(unit,field_depth,depth(1:nlevs),tindex=i)
if (found_temp) call mpp_read(unit,field_temp,temp(1:nlevs),tindex=i)
if (found_salt) call mpp_read(unit,field_salt,salt(1:nlevs),tindex=i)
! not required fields
if (ASSOCIATED(field_error)) then
call mpp_read(unit,field_error,profile_error,tindex=i)
endif
if (ASSOCIATED(field_probe)) then
call mpp_read(unit, field_probe, rprobe,tindex=i)
endif
if (ASSOCIATED(field_link)) then
call mpp_read(unit,field_link,rlink,tindex=i)
else
rlink = 0.0
endif
probe=rprobe
data_is_local = .false.
! NOTE: assuming grid is modulo 360 here. This needs to be generalized.
if (lon .lt. x_grid(isg-1) ) lon = lon + 360.0
if (lon .gt. x_grid(ieg+1) ) lon = lon - 360.0
! localized data is within region bounded by halo points
! (halo size = 1) adjacent to boundary points of computational domain
if (lon >= x_grid(isc-1) .and. &
lon < x_grid(iec+1) .and. &
lat >= y_grid(jsc-1) .and. &
lat < y_grid(jec+1)) data_is_local = .true.
profile_time = get_cal_time(time,time_units,'julian')
if ( data_is_local .OR. .NOT.localize_data) then
num_profiles=num_profiles+1
if (num_profiles > max_profiles) then
call mpp_error(FATAL,'maximum number of profiles exceeded.&
&Resize parameter max_profiles in ocean_obs_mod')
endif
call nullify_obs(Profiles(num_profiles))
num_levs_temp = 0
num_levs_salt = 0
do k = 1, nlevs
! flag=0 denotes a valid profile level, anything else
! is invalid. See NODC codes.
!================================================================
!0 - accepted station
!1 - failed annual standard deviation check
!2 - two or more density inversions (Levitus, 1982 criteria)
!3 - flagged cruise
!4 - failed seasonal standard deviation check
!5 - failed monthly standard deviation check
!6 - flag 1 and flag 4
!7 - bullseye from standard level data or failed annual and monthly
! standard deviation check
!8 - failed seasonal and monthly standard deviation check
!9 - failed annual, seasonal, and monthly standard deviation check
!================================================================
flag_t(k) = 0;flag_s(k) = 0
if (.not.found_salt) then
flag_s(k) = 1
endif
if (depth(k) .eq. depth_missing .or. depth(k) .lt.depth_min&
.or. depth(k) .gt. depth_max) then
depth(k) = missing_value
flag_t(k)=1
flag_s(k)=1
endif
if (found_temp .and. flag_t(k) .eq. 0) then
if (temp(k) .eq. temp_missing .or. temp(k) .lt. temp_min&
.or. temp(k) .gt. temp_max) then
temp(k) = missing_value
flag_t(k) = 1
flag_s(k) = 1 ! flag salt if no temperature data
else
num_levs_temp = num_levs_temp+1
endif
endif
if (found_salt .and. flag_s(k) .eq. 0) then
if (salt(k) .eq. salt_missing .or. salt(k) .lt. salt_min&
.or. salt(k) .gt. salt_max) then
salt(k) = missing_value
flag_s(k) = 1
else
num_levs_salt = num_levs_salt+1
endif
endif
enddo
! large profile are stored externally in separate records
! follow the links to get complete profile
ii=i+1
nlinks = 0
do while (rlink > 0.0 .and. nlinks .lt. max_links)
nlinks=nlinks+1
depth_bfr(nlinks,:) = missing_value
temp_bfr(nlinks,:) = missing_value
salt_bfr(nlinks,:) = missing_value
call mpp_read(unit,field_depth,depth_bfr(nlinks,1:nlevs),tindex=ii)
if (found_temp) call mpp_read(unit,field_temp,temp_bfr(nlinks,1:nlevs),tindex=ii)
if (found_salt) call mpp_read(unit,field_salt,salt_bfr(nlinks,1:nlevs),tindex=ii)
call mpp_read(unit,field_link,rlink,tindex=ii)
ii=ii+1
enddo
i=ii ! set record counter to start of next profile
if (nlinks > 0) then
do nn = 1, nlinks
do k=1, nlevs
flag_t_bfr(nn,k) = 0
flag_s_bfr(nn,k) = 0
if (depth_bfr(nn,k) .eq. depth_missing .or.&
depth_bfr(nn,k) .lt. depth_min .or. &
depth_bfr(nn,k) .gt. depth_max) then
depth_bfr(nn,k) = missing_value
flag_t_bfr(nn,k) = 1
flag_s_bfr(nn,k) = 1
endif
if (found_temp .and. flag_t_bfr(nn,k) .eq. 0) then
if (temp_bfr(nn,k) .eq. temp_missing .or.&
temp_bfr(nn,k) .lt. temp_min .or.&
temp_bfr(nn,k) .gt. temp_max) then
temp_bfr(nn,k) = missing_value
flag_t_bfr(nn,k) = 1
flag_s_bfr(nn,k) = 1
else
num_levs_temp = num_levs_temp+1
endif
endif
if (found_salt .and. flag_s_bfr(nn,k) .eq. 0) then
if (salt_bfr(nn,k) .eq. salt_missing .or.&
salt_bfr(nn,k) .lt. salt_min .or.&
salt_bfr(nn,k) .gt. salt_max) then
salt_bfr(nn,k) = missing_value
flag_t_bfr(nn,k) = 0
flag_s_bfr(nn,k) = 1
else
num_levs_salt = num_levs_salt+1
endif
endif
enddo
enddo
endif
num_levs = max(num_levs_temp,num_levs_salt)
if (num_levs == 0) then
if (i .gt. nstation) continue = .false.
cycle
endif
allocate(profiles(num_profiles)%depth(num_levs))
profiles(num_profiles)%depth=missing_value
if (num_levs_temp .gt. 0) then
allocate(profiles(num_profiles)%data_t(num_levs))
profiles(num_profiles)%data_t=missing_value
allocate(profiles(num_profiles)%flag_t(num_levs))
profiles(num_profiles)%flag_t= 1
profiles(num_profiles)%nvar=1
endif
if (num_levs_salt .gt. 0) then
allocate(profiles(num_profiles)%data_s(num_levs))
profiles(num_profiles)%data_s=missing_value
allocate(profiles(num_profiles)%flag_s(num_levs))
profiles(num_profiles)%flag_s= 1
profiles(num_profiles)%nvar=profiles(num_profiles)%nvar + 1
endif
if (probe < 1 ) probe = 0
profiles(num_profiles)%probe = probe
profiles(num_profiles)%levels = num_levs
profiles(num_profiles)%lat = lat
profiles(num_profiles)%lon = lon
allocate(profiles(num_profiles)%ms_t(num_levs))
profiles(num_profiles)%ms_t(:) = temp_obs_rmse**2.0 ! default error variance for temperature
if(num_levs_salt .gt. 0) then
allocate(profiles(num_profiles)%ms_s(num_levs))
profiles(num_profiles)%ms_s(:) = salt_obs_rmse**2.0 ! default error variance for salinity
endif
kk= 1
do k = 1, nlevs
if (flag_t(k) .eq. 0) then
if (kk > profiles(num_profiles)%levels) then
call mpp_error(FATAL)
endif
profiles(num_profiles)%depth(kk) = depth(k)
profiles(num_profiles)%data_t(kk) = temp(k)
profiles(num_profiles)%flag_t(kk) = 0
if (found_salt .and. flag_s(k) .eq. 0) then
profiles(num_profiles)%data_s(kk) = salt(k)
profiles(num_profiles)%flag_s(kk) = 0
endif
kk=kk+1
endif
enddo
do nn = 1, nlinks
do k = 1, nlevs
if (flag_t_bfr(nn,k) .eq. 0) then
if (kk > profiles(num_profiles)%levels) then
call mpp_error(FATAL)
endif
profiles(num_profiles)%depth(kk) = depth_bfr(nn,k)
profiles(num_profiles)%data_t(kk) = temp_bfr(nn,k)
profiles(num_profiles)%flag_t(kk) = 0
if (found_salt .and. flag_s_bfr(nn,k) .eq. 0) then
profiles(num_profiles)%data_s(kk) = salt_bfr(nn,k)
profiles(num_profiles)%flag_s(kk) = 0
endif
kk=kk+1
endif
enddo
enddo
profiles(num_profiles)%time = profile_time
! calculate interpolation coefficients (make sure to account for grid offsets here!)
! NOTE: this only works for lat/lon grids. Lower left indices.
!
ri0 = frac_index(lon, x_grid(isg-1:ieg+1)) - 1.
rj0 = frac_index(lat, y_grid(jsg-1:jeg+1)) - 1.
i0 = floor(ri0)
j0 = floor(rj0)
Profiles(num_profiles)%i_index = ri0
Profiles(num_profiles)%j_index = rj0
Profiles(num_profiles)%accepted = .true.
if (i0 < 0 .or. j0 < 0) then
Profiles(num_profiles)%accepted = .false.
endif
if (i0 > ieg .or. j0 > jeg) then
call mpp_error(FATAL,'grid lat/lon index is out of bounds ')
endif
if ((i0 < isc-1 .or. i0 > iec) .and. localize_data) then
call mpp_error(FATAL,'grid lat/lon index is out of bounds ')
endif
if ((j0 < jsc-1 .or. j0 > jec) .and. localize_data) then
call mpp_error(FATAL,'grid lat/lon index is out of bounds ')
endif
!
! flag the profile if it sits on a model land point
!
if (Profiles(num_profiles)%accepted ) then
if (Grd%mask(i0,j0,1) == 0.0 .or. &
Grd%mask(i0+1,j0,1) == 0.0 .or. &
Grd%mask(i0,j0+1,1) == 0.0 .or. &
Grd%mask(i0+1,j0+1,1) == 0.0) then
Profiles(num_profiles)%accepted = .false.
endif
endif
if (Profiles(num_profiles)%accepted) then
allocate(Profiles(num_profiles)%k_index(Profiles(num_profiles)%levels))
max_prof_depth=0.0
do k=1, Profiles(num_profiles)%levels
k0=0
if (Profiles(num_profiles)%flag_t(k).eq.0) then
rk0 = frac_index(Profiles(num_profiles)%depth(k), Grd%z(:))
k0 = floor(rk0)
if ( k0 == -1) then
if (Profiles(num_profiles)%depth(k) .lt. Grd%z(1)) then
k0 = 1
rk0 = 1.0
else if (Profiles(num_profiles)%depth(k) .gt. Grd%z(Grd%nk)) then
Profiles(num_profiles)%flag_t(k) = 1
endif
endif
else
cycle
endif
if (k0 .gt. size(Grd%z)-1 ) then
write(*,*) 'k0 out of bounds, rk0,k0= ',rk0,k0
write(*,*) 'Z_bound= ',Grd%z_bound
write(*,*) 'Profile%depth= ',Profiles(num_profiles)%depth
call mpp_error(FATAL)
endif
Profiles(num_profiles)%k_index(k) = rk0
! flag depth level if adjacent model grid points are land
if (Profiles(num_profiles)%flag_t(k) .eq. 0) then
if (i0 .lt. 0 .or. j0 .lt. 0 .or. k0 .lt. 0) then
write(*,*) 'profile index out of bounds'
write(*,*) 'i0,j0,k0=',i0,j0,k0
write(*,*) 'lon,lat,depth=',Profiles(num_profiles)%lon,&
Profiles(num_profiles)%lat,Profiles(num_profiles)%depth(k)
call mpp_error(FATAL)
endif
if (Grd%mask(i0,j0,k0) == 0.0 .or. &
Grd%mask(i0+1,j0,k0) == 0.0 .or. &
Grd%mask(i0,j0+1,k0) == 0.0 .or. &
Grd%mask(i0+1,j0+1,k0) == 0.0) then
Profiles(num_profiles)%flag_t(k) = 1
endif
if (Grd%mask(i0,j0,k0+1) == 0.0 .or. &
Grd%mask(i0+1,j0,k0+1) == 0.0 .or. &
Grd%mask(i0,j0+1,k0+1) == 0.0 .or. &
Grd%mask(i0+1,j0+1,k0+1) == 0.0) then
Profiles(num_profiles)%flag_t(k) = 1
endif
if (Profiles(num_profiles)%flag_t(k) .eq. 0) then
max_prof_depth = Profiles(num_profiles)%depth(k)
endif
endif
enddo ! Prof%levels loop
! Flag profile if it is too shallow.
if (max_prof_depth .lt. min_prof_depth) then
Profiles(num_profiles)%accepted = .false.
endif
found_neighbor=.false.
do k=2,Profiles(num_profiles)%levels - 1
if (Profiles(num_profiles)%flag_t(k) .eq. 0) then
kk = k-1
found_neighbor = .false.
continue_looking = .true.
do while (continue_looking .and. kk .ge. 1)
if (Profiles(num_profiles)%flag_t(kk) .eq. 0) then
zdist = Profiles(num_profiles)%depth(k) - Profiles(num_profiles)%depth(kk)
if (zdist .gt. max_prof_spacing) then
Profiles(num_profiles)%accepted = .false.
goto 199
else
continue_looking = .false.
found_neighbor = .true.
endif
else
kk = kk - 1
endif
end do
kk = k+1
continue_looking = .true.
do while (continue_looking .and. kk .le. Profiles(num_profiles)%levels)
if (Profiles(num_profiles)%flag_t(kk).eq. 0) then
zdist = Profiles(num_profiles)%depth(kk) - Profiles(num_profiles)%depth(k)
if (zdist .gt. max_prof_spacing) then
Profiles(num_profiles)%accepted = .false.
goto 199
else
continue_looking = .false.
found_neighbor = .true.
endif
else
kk = kk+1
endif
enddo
endif
enddo
if (.not. found_neighbor) Profiles(num_profiles)%accepted = .false.
199 continue
endif ! if Prof%accept
else ! data is not local
i = i+1
endif ! if data_is_local
if (i .gt. nstation) continue = .false.
enddo
! a = nprof_in_comp_domain(cpe)
! call mpp_broadcast(nprof_in_comp_domain(cpe),cpe)
! call mpp_sum(a)
! write(stdout(),*) 'A grand total of ',int(a),' profiles satisify acceptance criteria'
! do i=0,mpp_npes()
! write(stdout(),*) 'pe=',i,'profile count=',nprof_in_comp_domain(i)
! enddo
call mpp_sync_self()
call mpp_close(unit)
end subroutine open_profile_dataset
subroutine get_obs(model_time, Prof, Sfc, nprof, nsfc)
! get profiles and sfc
! obs relevant to current analysis interval
type(time_type), intent(in) :: model_time
type(ocean_profile_type), dimension(:) :: Prof
type(ocean_surface_type), dimension(:) :: Sfc
integer, intent(inout) :: nprof, nsfc
integer :: i,k,yr,mon,day,hr,minu,sec,a,mon_obs,yr_obs, outunit
type(time_type) :: tdiff
character(len=1) :: cchar
nprof=0
nsfc=0
outunit = stdout()
write(outunit,*) 'Gathering profiles for current analysis time'
call get_date(model_time,yr,mon,day,hr,minu,sec)
write(outunit,'(a,i4,a,i2,a,i2)') 'Current yyyy/mm/dd= ',yr,'/',mon,'/',day
write(outunit,*) 'num_profiles=',num_profiles
do i=1,num_profiles
if (debug .and. i.le.ndebug) then
call get_date(Profiles(i)%time,yr,mon,day,hr,minu,sec)
write(*,*) 'in get_obs prof time: yy/mm/dd= ',yr,mon,day
endif
if (Profiles(i)%time <= model_time) then
tdiff = model_time - Profiles(i)%time
else
tdiff = Profiles(i)%time - model_time
endif
if (debug .and. i .le. ndebug) then
write(*,*) 'Prof%accepted=',Profiles(i)%accepted
endif
if (tdiff <= time_window(0) .and. &
Profiles(i)%accepted) then
nprof=nprof+1
if (nprof > size(Prof,1)) &
call mpp_error(FATAL,'increase size of Prof array before call to get_obs')
call copy_obs(Profiles(i:i),Prof(nprof:nprof))
Prof(nprof)%tdiff = tdiff
if (debug .and. nprof .le. ndebug) then
call get_time(tdiff,sec,day)
write(*,'(a,i3,a,2f10.5)') 'Accepted profile #',i,' : lon,lat= ',Prof(nprof)%lon,Prof(nprof)%lat
do k=1,Prof(nprof)%levels
if (Prof(nprof)%nvar .eq. 2) then
write(*,'(a,i3,a,2f10.5,2i2,2f8.5)') 'Profile #',i,' : temp,salt,flag_t,flag_s,ms_t,ms_s= ',&
Prof(nprof)%data_t(k),Prof(nprof)%data_s(k),Prof(nprof)%flag_t(k),Prof(nprof)%flag_s(k),&
Prof(nprof)%ms_t(k),Prof(nprof)%ms_s(k)
else
write(*,'(a,i3,a,2f10.5)') 'Profile #',i,' : temp,flag_t= ',Prof(nprof)%data_t(k),Prof(nprof)%flag_t(k)
endif
enddo
endif
else
if (debug .and. i .le. ndebug) then
call get_time(tdiff,sec,day)
write(*,'(a,i3,a,2f10.5)') 'Rejected profile #',i,' : lon,lat= ',Prof(i)%lon,Prof(i)%lat
do k=1,Prof(i)%levels
if (Prof(i)%nvar .eq. 2) then
write(*,'(a,i3,a,2f10.5,2i2)') 'Profile #',i,' : temp,salt,flag_t,flag_s= ',Prof(i)%data_t(k),Prof(i)%data_s(k),Prof(i)%flag_t(k),Prof(i)%flag_s(k)
else
write(*,'(a,i3,a,2f10.5)') 'Profile #',i,' : temp,flag_t= ',Prof(i)%data_t(k),Prof(i)%flag_t(k)
endif
enddo
endif
endif
enddo
a=nprof
call mpp_sum(a)
write(outunit,*) 'A total of ',a,' profiles are being used for the current analysis step'
return
end subroutine get_obs
subroutine oda_core_init(Domain, Grid, localize)
use fms_mod, only : open_namelist_file, check_nml_error, close_file
type(domain2d), intent(in) :: Domain
type(grid_type), target, intent(in) :: Grid
logical, intent(in), optional :: localize
integer :: ioun, ierr, io_status
read (input_nml_file, oda_core_nml, iostat=io_status)
ierr = check_nml_error(io_status,'oda_core_nml')
# 940
! allocate(nprof_in_comp_domain(0:mpp_npes()-1))
! nprof_in_comp_domain = 0
Grd => Grid
call mpp_get_compute_domain(Domain,isc,iec,jsc,jec)
call mpp_get_data_domain(Domain,isd,ied,jsd,jed)
call mpp_get_global_domain(Domain,isg,ieg,jsg,jeg)
nk = size(Grid%z)
allocate(sum_wgt(isd:ied,jsd:jed,nk))
allocate(nobs(isd:ied,jsd:jed,nk))
if (PRESENT(localize)) localize_data = localize
call init_observations(localize_data)
call write_ocean_data_init()
end subroutine oda_core_init
subroutine purge_obs()
num_profiles=0
num_sfc_obs=0
end subroutine purge_obs
subroutine forward_obs_profile(Model_obs, fg_t, fg_s)
! map first guess to observation locations
! note that forward operator only becomes associated after
! this call
type(ocean_profile_type), dimension(:), intent(inout) ::Model_obs
real, dimension(isd:ied,jsd:jed,nk) , intent(in) :: fg_t ! first guess for temperature
real, dimension(isd:ied,jsd:jed,nk) , intent(in), optional :: fg_s ! first guess for salinity
integer :: n, i0, j0, k, num_prof, k0
real :: a,b,c
character(len=128) :: mesg
num_prof = size(Model_obs)
sum_wgt = 0.0
do n = 1, num_prof
i0 = floor(Model_obs(n)%i_index)
j0 = floor(Model_obs(n)%j_index)
a = Model_obs(n)%i_index - i0
b = Model_obs(n)%j_index - j0
if (a >= 1.0 .or. a < 0.0 ) call mpp_error(FATAL)
if (b >= 1.0 .or. b < 0.0 ) call mpp_error(FATAL)
if (i0 < isc-1 .or. i0 > iec) then
write(mesg,'(a,i4,2x,i4)') 'out of bounds in forward_obs: i0,j0= ',i0,j0
call mpp_error(FATAL,trim(mesg))
endif
if (j0 < jsc-1 .or. j0 > jec) then
write(mesg,*) 'out of bounds in forward_obs: i0,j0= ',i0,j0
call mpp_error(FATAL,trim(mesg))
endif
if (ASSOCIATED(Model_obs(n)%data_t) .and. Model_obs(n)%accepted) then
if (ASSOCIATED(Model_obs(n)%Forward_model%wgt))&
Model_obs(n)%Forward_model%wgt => NULL()
allocate(Model_obs(n)%Forward_model%wgt(2,2,2,Model_obs(n)%levels))
Model_obs(n)%Forward_model%wgt = 0.0
do k = 1, Model_obs(n)%levels
if (Model_obs(n)%flag_t(k) .eq. 0) then
k0 = floor(Model_obs(n)%k_index(k))
if (k0 < 1 .or. k0 > Grd%nk-1) then
write(mesg,*) 'out of bounds in forward_obs: k0= ',k0
call mpp_error(FATAL,trim(mesg))
endif
c = Model_obs(n)%k_index(k) - k0
if (c >= 1.0 .or. c < 0.0 ) call mpp_error(FATAL)
Model_obs(n)%Forward_model%wgt(1,1,1,k) = (1.0-a)*(1.0-b)*(1.0-c)
Model_obs(n)%Forward_model%wgt(2,1,1,k) = a*(1.0-b)*(1.0-c)
Model_obs(n)%Forward_model%wgt(1,2,1,k) = (1.0-a)*b*(1.0-c)
Model_obs(n)%Forward_model%wgt(2,2,1,k) = a*b*(1.0-c)
Model_obs(n)%Forward_model%wgt(1,1,2,k) = (1.0-a)*(1.0-b)*c
Model_obs(n)%Forward_model%wgt(2,1,2,k) = a*(1.0-b)*c
Model_obs(n)%Forward_model%wgt(1,2,2,k) = (1.0-a)*b*c
Model_obs(n)%Forward_model%wgt(2,2,2,k) = a*b*c
sum_wgt(i0,j0,k0) = sum_wgt(i0,j0,k0)+&
Model_obs(n)%Forward_model%wgt(1,1,1,k)
sum_wgt(i0+1,j0,k0) = sum_wgt(i0+1,j0,k0)+&
Model_obs(n)%Forward_model%wgt(2,1,1,k)
sum_wgt(i0,j0+1,k0) = sum_wgt(i0,j0+1,k0)+&
Model_obs(n)%Forward_model%wgt(1,2,1,k)
sum_wgt(i0+1,j0+1,k0) = sum_wgt(i0+1,j0+1,k0)+&
Model_obs(n)%Forward_model%wgt(2,2,1,k)
sum_wgt(i0,j0,k0+1) = sum_wgt(i0,j0,k0+1)+&
Model_obs(n)%Forward_model%wgt(1,1,2,k)
sum_wgt(i0+1,j0,k0+1) = sum_wgt(i0+1,j0,k0+1)+&
Model_obs(n)%Forward_model%wgt(2,1,2,k)
sum_wgt(i0,j0+1,k0+1) = sum_wgt(i0,j0+1,k0+1)+&
Model_obs(n)%Forward_model%wgt(1,2,2,k)
sum_wgt(i0+1,j0+1,k0+1) = sum_wgt(i0+1,j0+1,k0+1)+&
Model_obs(n)%Forward_model%wgt(2,2,2,k)
Model_obs(n)%data_t(k) = &
fg_t(i0,j0,k0)*Model_obs(n)%Forward_model%wgt(1,1,1,k) &
+ fg_t(i0+1,j0,k0)*Model_obs(n)%Forward_model%wgt(2,1,1,k) &
+ fg_t(i0,j0+1,k0)*Model_obs(n)%Forward_model%wgt(1,2,1,k) &
+ fg_t(i0+1,j0+1,k0)*Model_obs(n)%Forward_model%wgt(2,2,1,k) &
+ fg_t(i0,j0,k0+1)*Model_obs(n)%Forward_model%wgt(1,1,2,k) &
+ fg_t(i0+1,j0,k0+1)*Model_obs(n)%Forward_model%wgt(2,1,2,k) &
+ fg_t(i0,j0+1,k0+1)*Model_obs(n)%Forward_model%wgt(1,2,2,k) &
+ fg_t(i0+1,j0+1,k0+1)*Model_obs(n)%Forward_model%wgt(2,2,2,k)
if (ASSOCIATED(Model_obs(n)%data_s) .and. PRESENT(fg_s)) then
Model_obs(n)%data_s(k) = &
fg_s(i0,j0,k0)*Model_obs(n)%Forward_model%wgt(1,1,1,k) &
+ fg_s(i0+1,j0,k0)*Model_obs(n)%Forward_model%wgt(2,1,1,k) &
+ fg_s(i0,j0+1,k0)*Model_obs(n)%Forward_model%wgt(1,2,1,k) &
+ fg_s(i0+1,j0+1,k0)*Model_obs(n)%Forward_model%wgt(2,2,1,k) &
+ fg_s(i0,j0,k0+1)*Model_obs(n)%Forward_model%wgt(1,1,2,k) &
+ fg_s(i0+1,j0,k0+1)*Model_obs(n)%Forward_model%wgt(2,1,2,k) &
+ fg_s(i0,j0+1,k0+1)*Model_obs(n)%Forward_model%wgt(1,2,2,k) &
+ fg_s(i0+1,j0+1,k0+1)*Model_obs(n)%Forward_model%wgt(2,2,2,k)
endif
else
if (ASSOCIATED(Model_obs(n)%data_t)) then
Model_obs(n)%data_t(k) = missing_value
endif
if (ASSOCIATED(Model_obs(n)%data_s)) then
Model_obs(n)%data_s(k) = missing_value
endif
endif
enddo
endif
enddo
return
end subroutine forward_obs_profile
subroutine forward_obs_sfc(Sfc, Guess, Diff)
type(ocean_surface_type), intent(in) :: Sfc
type(ocean_dist_type), intent(in) :: Guess
type(ocean_surface_type), intent(inout) :: Diff
return
end subroutine forward_obs_sfc
subroutine backward_obs_profile(Obs,model_t,model_s)
!
! map observations back to model locations
!
type(ocean_profile_type), dimension(:), intent(in) :: Obs
real, dimension(isd:ied,jsd:jed,nk), intent(inout) :: model_t
real, dimension(isd:ied,jsd:jed,nk), intent(inout), optional :: model_s
real :: tmp
integer :: i0,j0,k0,k,n
model_t = 0.0
if (PRESENT(model_s)) model_s = 0.0
do n=1,size(Obs)
if (ASSOCIATED(Obs(n)%data_t) .and. Obs(n)%accepted) then
i0 = floor(Obs(n)%i_index)
j0 = floor(Obs(n)%j_index)
! profiles are assumed to lie
! in domain bounded by first halo points
if (i0 < isd .or. i0 > ied-1) cycle
if (j0 < jsd .or. j0 > jed-1) cycle
if (.not.ASSOCIATED(Obs(n)%Forward_model%wgt)) call mpp_error(FATAL,'forward operator not associated with obs')
do k=1, Obs(n)%levels
if (Obs(n)%flag_t(k) .eq. 0) then
k0 = floor(Obs(n)%k_index(k))
if (k0 < 1 .or. k0 > Grd%nk-1) call mpp_error(FATAL,'profile k indx out of bnds')
tmp = Obs(n)%data_t(k)
model_t(i0,j0,k0) = model_t(i0,j0,k0) + tmp*Obs(n)%Forward_model%wgt(1,1,1,k)
model_t(i0+1,j0,k0) = model_t(i0+1,j0,k0) + tmp*Obs(n)%Forward_model%wgt(2,1,1,k)
model_t(i0,j0+1,k0) = model_t(i0,j0+1,k0) + tmp*Obs(n)%Forward_model%wgt(1,2,1,k)
model_t(i0+1,j0+1,k0) = model_t(i0+1,j0+1,k0) + tmp*Obs(n)%Forward_model%wgt(2,2,1,k)
model_t(i0,j0,k0+1) = model_t(i0,j0,k0+1) + tmp*Obs(n)%Forward_model%wgt(1,1,2,k)
model_t(i0+1,j0,k0+1) = model_t(i0+1,j0,k0+1) + tmp*Obs(n)%Forward_model%wgt(2,1,2,k)
model_t(i0,j0+1,k0+1) = model_t(i0,j0+1,k0+1) + tmp*Obs(n)%Forward_model%wgt(1,2,2,k)
model_t(i0+1,j0+1,k0+1) = model_t(i0+1,j0+1,k0+1) + tmp*Obs(n)%Forward_model%wgt(2,2,2,k)
if (PRESENT(model_s)) then
tmp = Obs(n)%data_s(k)
model_s(i0,j0,k0) = model_s(i0,j0,k0) + tmp*Obs(n)%Forward_model%wgt(1,1,1,k)
model_s(i0+1,j0,k0) = model_s(i0+1,j0,k0) + tmp*Obs(n)%Forward_model%wgt(2,1,1,k)
model_s(i0,j0+1,k0) = model_s(i0,j0+1,k0) + tmp*Obs(n)%Forward_model%wgt(1,2,1,k)
model_s(i0+1,j0+1,k0) = model_s(i0+1,j0+1,k0) + tmp*Obs(n)%Forward_model%wgt(2,2,1,k)
model_s(i0,j0,k0+1) = model_s(i0,j0,k0+1) + tmp*Obs(n)%Forward_model%wgt(1,1,2,k)
model_s(i0+1,j0,k0+1) = model_s(i0+1,j0,k0+1) + tmp*Obs(n)%Forward_model%wgt(2,1,2,k)
model_s(i0,j0+1,k0+1) = model_s(i0,j0+1,k0+1) + tmp*Obs(n)%Forward_model%wgt(1,2,2,k)
model_s(i0+1,j0+1,k0+1) = model_s(i0+1,j0+1,k0+1) + tmp*Obs(n)%Forward_model%wgt(2,2,2,k)
endif
end if
end do
end if
end do
where(sum_wgt > 0.0)
model_t = model_t /sum_wgt
elsewhere
model_t = 0.0
end where
if (PRESENT(model_s)) then
where(sum_wgt > 0.0)
model_s = model_s /sum_wgt
elsewhere
model_s = 0.0
end where
endif
end subroutine backward_obs_profile
subroutine backward_obs_sfc(Obs,model)
type(ocean_surface_type), dimension(:), intent(in) :: Obs
real, dimension(isd:ied,jsd:jed,nk), intent(inout) :: model
end subroutine backward_obs_sfc
subroutine assign_forward_model_profile(Obs1,Obs2)
type(ocean_profile_type), dimension(:), target, intent(in) :: Obs1
type(ocean_profile_type), dimension(:), intent(inout) :: Obs2
integer :: n
if (size(Obs1) .ne. size(Obs2)) call mpp_error(FATAL)
do n=1,size(Obs1)
Obs2(n)%Forward_model%wgt => Obs1(n)%Forward_model%wgt
enddo
end subroutine assign_forward_model_profile
subroutine assign_forward_model_sfc(Obs1,Obs2)
type(ocean_surface_type), target, intent(in) :: Obs1
type(ocean_surface_type), intent(inout) :: Obs2
return
end subroutine assign_forward_model_sfc
subroutine diff_obs_profile(prof1, prof2, Diff)
type(ocean_profile_type), dimension(:), intent(in) :: prof1
type(ocean_profile_type), dimension(:), intent(in) :: prof2
type(ocean_profile_type), dimension(:), intent(inout) :: Diff
integer :: n,k
if (size(prof1) .ne. size(prof2) ) call mpp_error(FATAL)
if (size(prof1) .ne. size(Diff) ) call mpp_error(FATAL)
do n=1,size(prof1)
do k=1,prof1(n)%levels
if (prof1(n)%flag_t(k) .eq. 0) then
Diff(n)%data_t(k) = prof2(n)%data_t(k)-prof1(n)%data_t(k)
else
Diff(n)%data_t(k) = missing_value
endif
if (abs(Diff(n)%data_t(k)) .gt. max_misfit) then
Diff(n)%flag_t(k) = 1
endif
if (ASSOCIATED(prof1(n)%data_s)) then
if (prof1(n)%flag_s(k) .eq. 0) then
Diff(n)%data_s(k) = prof2(n)%data_s(k)-prof1(n)%data_s(k)
else
Diff(n)%data_s(k) = missing_value
endif
if (abs(Diff(n)%data_s(k)) .gt. max_misfit) then
Diff(n)%flag_s(k) = 1
endif
endif
enddo
enddo
end subroutine diff_obs_profile
subroutine diff_obs_sfc(prof1,prof2,Diff)
type(ocean_surface_type), dimension(:), intent(in) :: prof1, prof2
type(ocean_surface_type), dimension(:), intent(inout) :: Diff
end subroutine diff_obs_sfc
subroutine copy_obs_prof(obs_in, obs_out)
type(ocean_profile_type), dimension(:), intent(in) :: obs_in
type(ocean_profile_type), dimension(:), intent(inout) :: obs_out
integer :: n
if (size(obs_in) .ne. size(obs_out)) call mpp_error(FATAL,&
'size mismatch in call to copy_obs_prof')
do n=1,size(obs_in)
call nullify_obs(obs_out(n))
Obs_out(n)%nvar = Obs_in(n)%nvar
Obs_out(n)%project = Obs_in(n)%project
Obs_out(n)%probe = Obs_in(n)%probe
Obs_out(n)%ref_inst = Obs_in(n)%ref_inst
Obs_out(n)%wod_cast_num = Obs_in(n)%wod_cast_num
Obs_out(n)%fix_depth = Obs_in(n)%fix_depth
Obs_out(n)%ocn_vehicle = Obs_in(n)%ocn_vehicle
Obs_out(n)%database_id = Obs_in(n)%database_id
Obs_out(n)%levels = Obs_in(n)%levels
Obs_out(n)%profile_flag = Obs_in(n)%profile_flag
Obs_out(n)%profile_flag_s = Obs_in(n)%profile_flag_s
Obs_out(n)%lon = Obs_in(n)%lon
Obs_out(n)%lat = Obs_in(n)%lat
Obs_out(n)%accepted = Obs_in(n)%accepted
ALLOCATE(Obs_out(n)%depth(Obs_in(n)%levels))
Obs_out(n)%depth(:) = Obs_in(n)%depth(:)
ALLOCATE(Obs_out(n)%data_t(Obs_in(n)%levels))
Obs_out(n)%data_t(:) = Obs_in(n)%data_t(:)
ALLOCATE(Obs_out(n)%flag_t(Obs_in(n)%levels))
Obs_out(n)%flag_t(:) = Obs_in(n)%flag_t(:)
if (ASSOCIATED(Obs_in(n)%data_s)) then
ALLOCATE(Obs_out(n)%data_s(Obs_in(n)%levels))
Obs_out(n)%data_s(:) = Obs_in(n)%data_s(:)
ALLOCATE(Obs_out(n)%flag_s(Obs_in(n)%levels))
Obs_out(n)%flag_s(:) = Obs_in(n)%flag_s(:)
endif
Obs_out(n)%time = Obs_in(n)%time
Obs_out(n)%yyyy = Obs_in(n)%yyyy
Obs_out(n)%mmdd = Obs_in(n)%mmdd
Obs_out(n)%i_index = Obs_in(n)%i_index
Obs_out(n)%j_index = Obs_in(n)%j_index
ALLOCATE(Obs_out(n)%k_index(Obs_in(n)%levels))
Obs_out(n)%k_index = Obs_in(n)%k_index
ALLOCATE(Obs_out(n)%ms_t(Obs_in(n)%levels))
Obs_out(n)%ms_t = Obs_in(n)%ms_t
if (ASSOCIATED(Obs_in(n)%ms_s)) then
ALLOCATE(Obs_out(n)%ms_s(Obs_in(n)%levels))
Obs_out(n)%ms_s = Obs_in(n)%ms_s
endif
Obs_out(n)%tdiff = Obs_in(n)%tdiff
Obs_out(n)%nbr_index = Obs_in(n)%nbr_index
Obs_out(n)%nbr_dist = Obs_in(n)%nbr_dist
if (ASSOCIATED(Obs_in(n)%Model_grid)) &
Obs_out(n)%Model_grid => Obs_in(n)%Model_Grid
enddo
end subroutine copy_obs_prof
subroutine copy_obs_sfc(Obs_in, Obs_out)
type(ocean_surface_type), dimension(:), intent(in) :: Obs_in
type(ocean_surface_type), dimension(:), intent(inout) :: Obs_out
return
end subroutine copy_obs_sfc
subroutine adjust_obs_error_profile(Prof)
use time_manager_mod, only : get_time
type(ocean_profile_type), dimension(:), intent(inout) :: Prof
integer :: secs, days, n, k, secs_w, days_w, m
real :: tfac, Ims
do n=1,size(Prof)
call get_time(Prof(n)%tdiff,secs, days)
m=Prof(n)%probe
call get_time(time_window(m),secs_w,days_w)
tfac = (days + secs/86400.) / days_w
tfac = 1. - min(1.,tfac)
if (tfac > 1.0 ) call mpp_error(FATAL)
if (tfac < 0.0 ) call mpp_error(FATAL)
do k=1,Prof(n)%levels
Prof(n)%ms_t(k) = 1.0/ max(1.e-1,tfac) * Prof(n)%ms_t(k)
if (ASSOCIATED(Prof(n)%data_s)) then
Prof(n)%ms_s(k) = 1.0/ max(1.e-1,tfac) * Prof(n)%ms_s(k)
endif
end do
end do
end subroutine adjust_obs_error_profile
subroutine adjust_obs_error_sfc(Diff)
type(ocean_surface_type), intent(inout) :: Diff
return
end subroutine adjust_obs_error_sfc
subroutine mult_obs_I_mse_profile(Obs)
type(ocean_profile_type), dimension(:), intent(inout) :: Obs
integer :: n,k
real :: Ims
do n=1,size(Obs)
do k = 1, Obs(n)%levels
Ims = 1./Obs(n)%ms_t(k)
if (Obs(n)%flag_t(k) .eq. 0) Obs(n)%data_t(k) = Ims*Obs(n)%data_t(k)
if (ASSOCIATED(Obs(n)%data_s)) then
Ims = 1/Obs(n)%ms_s(k)
if (Obs(n)%flag_s(k) .eq. 0) Obs(n)%data_s(k) = Ims*Obs(n)%data_s(k)
endif
end do
end do
end subroutine mult_obs_I_mse_profile
subroutine mult_obs_I_mse_sfc(a, Obs)
real, dimension(:), intent(in) :: a
type(ocean_surface_type), intent(inout) :: Obs
end subroutine mult_obs_I_mse_sfc
!
!
!
! Turn a string from uppercase to lowercase, do nothing if the
! string is already in lowercase.
!
function lowercase (cs)
character(len=*), intent(in) :: cs
character(len=len(cs)) :: lowercase
character :: ca(len(cs))
integer, parameter :: co=iachar('a')-iachar('A') ! case offset
ca = transfer(cs,"x",len(cs))
where (ca >= "A" .and. ca <= "Z") ca = achar(iachar(ca)+co)
lowercase = transfer(ca,cs)
end function lowercase
subroutine init_observations(localize)
use fms_mod, only : open_namelist_file,close_file,check_nml_error
use mpp_io_mod, only : mpp_open, MPP_ASCII, MPP_RDONLY, MPP_MULTI, MPP_SINGLE
use mpp_domains_mod, only : mpp_global_field
logical, intent(in), optional :: localize
integer :: data_window = 15 ! default data half-window is 15 days
integer :: i,j
type obs_entry_type
character(len=128) :: filename
character(len=16) :: file_type
end type obs_entry_type
namelist /ocean_obs_nml/ data_window, max_prof_spacing, min_prof_depth
character(len=128) :: input_files(max_files) = ''
integer :: nfiles, filetype(max_files), ioun, io_status, ierr,&
unit, nrecs, n
character(len=256) :: record
type(obs_entry_type) :: tbl_entry
read (input_nml_file, ocean_obs_nml, iostat=io_status)
ierr = check_nml_error(io_status,'ocean_obs_nml')
# 1461
time_window(:) = set_time(0,data_window)
nfiles=0
if (file_exist('ocean_obs_table') ) then
call mpp_open(unit,'ocean_obs_table',action=MPP_RDONLY)
nfiles = 0;nrecs=0
do while (nfiles <= max_files)
read(unit,'(a)',end=99,err=98) record
nrecs=nrecs+1
if (record(1:1) == '#') cycle
read(record,*,err=98,end=98) tbl_entry
nfiles=nfiles+1
input_files(nfiles) = tbl_entry%filename
select case (trim(tbl_entry%file_type))
case ('profiles')
filetype(nfiles) = PROFILE_FILE
case ('sfc')
filetype(nfiles) = SFC_FILE
case ('idealized')
filetype(nfiles) = IDEALIZED_PROFILES
case default
call mpp_error(FATAL,'error in obs_table entry format')
end select
98 continue
enddo
call mpp_error(FATAL,' number of obs files exceeds max_files parameter')
99 continue
endif
num_profiles=0
num_sfc_obs=0
! get local indices for Model grid
! Since we currently only support regular grids, the
! input 2-d grid array is converted to 1-d
! halo points are added
! xhalo=isc-isd
! yhalo=jsc-jsd
! if (xhalo.ne.ied-iec) call mpp_error(FATAL)
! if (yhalo.ne.jed-jec) call mpp_error(FATAL)
allocate(x_grid(isg-1:ieg+1))
allocate(y_grid(jsg-1:jeg+1))
x_grid(isg:ieg) = Grd%x(isg:ieg,jsg)
y_grid(jsg:jeg) = Grd%y(ieg/4,jsg:jeg)
allocate(Profiles(max_profiles))
if (Grd%cyclic) then
x_grid(isg-1) = x_grid(ieg) - 360. ! assume grid is modulo 360 which is reasonable for data assimilation
x_grid(ieg+1) = x_grid(isg) + 360.
else
x_grid(isg-1) = x_grid(isg) - 1.e-10
x_grid(ieg+1) = x_grid(ieg) + 1.e-10
endif
y_grid(jsg-1) = y_grid(jsg) - 1.e-10
y_grid(jeg+1) = y_grid(jeg) + 1.e-10
do n=1, nfiles
select case (filetype(n))
case (PROFILE_FILE)
call open_profile_dataset(trim(input_files(n)), localize)
case (IDEALIZED_PROFILES)
call create_ideal_profiles(localize)
case default
call mpp_error(FATAL,'filetype not currently supported')
end select
enddo
return
end subroutine init_observations
subroutine add_tidal_error(Prof)
! NOT IMPLEMENTED YET !!!
type(ocean_profile_type), intent(inout) :: Prof
integer :: k
real :: dtdz, err, a1, a2
if (.not.ASSOCIATED(prof%ms_t)) then
allocate(prof%ms_t(prof%levels))
prof%ms_t(:) = min_obs_err_t
endif
do k=2,prof%levels - 1
if (prof%flag_t(k-1) .eq. 0 .and. prof%flag_t(k+1) .eq. 0) then
dtdz = (prof%data_t(k+1)-prof%data_t(k-1))/(prof%depth(k+1)-prof%depth(k-1))
a1 = abs(dtdz) * eta_tide_const
err = max(a1,min_obs_err_t)
prof%ms_t(k) = err*err
if (ASSOCIATED(prof%data_s)) then
dtdz = (prof%data_s(k+1)-prof%data_s(k-1))/(prof%depth(k+1)-prof%depth(k-1))
a1 = abs(dtdz) * eta_tide_const
err = max(a1,min_obs_err_s)
prof%ms_s(k) = err*err
endif
endif
enddo
end subroutine add_tidal_error
subroutine create_ideal_profiles(localize)
!
use field_manager_mod, only: MODEL_OCEAN, parse, find_field_index, get_field_methods, method_type, get_field_info
logical, intent(in), optional :: localize
logical :: localize_data = .true.
integer, parameter :: nlevels = 100 ! number of vertical levels for idealized profiles
real, parameter :: width_trans = 250.0 ! with over which to transition from sfc value to bottom value
real, parameter :: bot_depth = 2000.0 ! bottom depth for idealized profiles
real, allocatable, dimension(:) :: lon,lat, sst, sss, bot_temp, bot_salt, depth
real, allocatable, dimension(:,:) :: temp, salt, temp_error, salt_error
integer, allocatable, dimension(:) :: yr, mon, day, hr, mm, ss
integer :: nstation, unit, n, noobs, i, k
real :: ri0,rj0,rk0, mid_depth, dtdf, temp_cent, depthC_I_trans, dsdf, salt_cent
type(time_type) :: profile_time
logical :: data_is_local
integer :: model, parse_ok, cpe
integer :: i0,j0,k0
real :: dz, a, dx1, dx2, dy1, dy2
real :: temp_missing=missing_value,salt_missing=missing_value,depth_missing=missing_value
character(len=32) :: fld_type, fld_name
type(method_type), allocatable, dimension(:) :: ocean_obs_methods
if (PRESENT(localize)) localize_data = localize
cpe = mpp_pe()
dx1 = (x_grid(isc)-x_grid(isc-1))/2.0
dx2 = (x_grid(iec+1)-x_grid(iec))/2.0
dy1 = (y_grid(jsc)-y_grid(jsc-1))/2.0
dy2 = (y_grid(jec+1)-y_grid(jec))/2.0
model = model_ocean
n = find_field_index(model,'ideal_profiles')
call get_field_info(n,fld_type,fld_name,model,noobs)
allocate(ocean_obs_methods(noobs))
allocate(lon(noobs),lat(noobs), yr(noobs), mon(noobs), day(noobs), &
hr(noobs), mm(noobs), ss(noobs), &
sst(noobs), sss(noobs), bot_temp(noobs), bot_salt(noobs))
allocate(temp(noobs,nlevels), salt(noobs,nlevels), temp_error(noobs,nlevels), salt_error(noobs,nlevels))
allocate(depth(nlevels))
call get_field_methods(n,ocean_obs_methods)
do i=1,noobs
parse_ok = parse(ocean_obs_methods(i)%method_control,'lon',lon(i))
if (parse_ok == 0) call mpp_error(FATAL,'==>Error oda_core_mod: idealized_ocean_profiles table entry error')
if (lon(i) .lt. x_grid(isg) ) lon(i) = lon(i) + 360.0
if (lon(i) .gt. x_grid(ieg) ) lon(i) = lon(i) - 360.0
parse_ok = parse(ocean_obs_methods(i)%method_control,'lat',lat(i))
if (parse_ok == 0) call mpp_error(FATAL,'==>Error oda_core_mod: idealized_ocean_profiles table entry error')
parse_ok = parse(ocean_obs_methods(i)%method_control,'yr',yr(i))
if (parse_ok == 0) call mpp_error(FATAL,'==>Error oda_core_mod: idealized_ocean_profiles table entry error')
parse_ok = parse(ocean_obs_methods(i)%method_control,'mon',mon(i))
if (parse_ok == 0) call mpp_error(FATAL,'==>Error oda_core_mod: idealized_ocean_profiles table entry error')
parse_ok = parse(ocean_obs_methods(i)%method_control,'day',day(i))
if (parse_ok == 0) call mpp_error(FATAL,'==>Error oda_core_mod: idealized_ocean_profiles table entry error')
parse_ok = parse(ocean_obs_methods(i)%method_control,'hr',hr(i))
if (parse_ok == 0) call mpp_error(FATAL,'==>Error oda_core_mod: idealized_ocean_profiles table entry error')
parse_ok = parse(ocean_obs_methods(i)%method_control,'mm',mm(i))
if (parse_ok == 0) call mpp_error(FATAL,'==>Error oda_core_mod: idealized_ocean_profiles table entry error')
parse_ok = parse(ocean_obs_methods(i)%method_control,'ss',ss(i))
if (parse_ok == 0) call mpp_error(FATAL,'==>Error oda_core_mod: idealized_ocean_profiles table entry error')
parse_ok = parse(ocean_obs_methods(i)%method_control,'sst',sst(i))
if (parse_ok == 0) call mpp_error(FATAL,'==>Error oda_core_mod: idealized_ocean_profiles table entry error')
parse_ok = parse(ocean_obs_methods(i)%method_control,'sss',sss(i))
if (parse_ok == 0) call mpp_error(FATAL,'==>Error oda_core_mod: idealized_ocean_profiles table entry error')
parse_ok = parse(ocean_obs_methods(i)%method_control,'bot_temp',bot_temp(i))
if (parse_ok == 0) call mpp_error(FATAL,'==>Error oda_core_mod: idealized_ocean_profiles table entry error')
parse_ok = parse(ocean_obs_methods(i)%method_control,'bot_salt',bot_salt(i))
if (parse_ok == 0) call mpp_error(FATAL,'==>Error oda_core_mod: idealized_ocean_profiles table entry error')
enddo
if (noobs == 0 ) then
call mpp_error(FATAL,'==> NOTE from oda_core_mod: no idealized profiles given in field table')
return
endif
dz = bot_depth/(nlevels-1)
do k=1,nlevels
depth(k) = (k-1)*dz
enddo
mid_depth = bot_depth/2.0
depthC_I_trans = mid_depth / width_trans
do i=1,noobs
dtdf = (bot_temp(i) - sst(i)) / (2.0*atan(1.0) + atan(depthC_I_trans))
temp_cent = sst(i) + dtdf * atan(depthC_I_trans)
temp(i,1) = sst(i)
do k=2,nlevels-1
temp(i,k) = temp_cent + dtdf * atan((depth(k) - mid_depth)/width_trans)
enddo
temp(i,nlevels) = bot_temp(i)
dsdf = (bot_salt(i) - sss(i)) / (2.0*atan(1.0) + atan(depthC_I_trans))
salt_cent = sss(i) + dsdf * atan(depthC_I_trans)
salt(i,1) = sss(i)
do k=2,nlevels-1
salt(i,k) = salt_cent + dsdf * atan((depth(k) - mid_depth)/width_trans)
enddo
salt(i,nlevels) = bot_salt(i)
enddo
num_profiles=0
do i=1,noobs
data_is_local = .false.
! localized data is within region bounded by halo points
! (halo size = 1) adjacent to boundary points of computational domain
if (lon(i) >= x_grid(isc-1) .and. &
lon(i) < x_grid(iec+1) .and. &
lat(i) >= y_grid(jsc-1) .and. &
lat(i) < y_grid(jec+1)) data_is_local = .true.
profile_time = set_date(yr(i),mon(i),day(i),hr(i),mm(i),ss(i))
if ( data_is_local .OR. .NOT.localize_data) then
if (lon(i) >= x_grid(isc)-dx1 .and. &
lon(i) < x_grid(iec)+dx2 .and. &
lat(i) >= y_grid(jsc)-dy1 .and. &
lat(i) < y_grid(jec)+dy2) then
! nprof_in_comp_domain(cpe) = nprof_in_comp_domain(cpe)+1
endif
num_profiles=num_profiles+1
if (num_profiles > max_profiles) then
call mpp_error(FATAL,'maximum number of profiles exceeded. Resize parameter max_profiles in ocean_obs_mod')
endif
profiles(num_profiles)%Model_Grid => Grd
profiles(num_profiles)%nvar = 2
profiles(num_profiles)%profile_flag = 0
profiles(num_profiles)%profile_flag_s = 0
profiles(num_profiles)%accepted = .true.
allocate(profiles(num_profiles)%depth(nlevels))
profiles(num_profiles)%depth=depth(1:nlevels)
allocate(profiles(num_profiles)%data_t(nlevels))
profiles(num_profiles)%data_t=temp(i,:)
allocate(profiles(num_profiles)%flag_t(nlevels))
profiles(num_profiles)%flag_t= 0
allocate(profiles(num_profiles)%data_s(nlevels))
profiles(num_profiles)%data_s=salt(i,:)
allocate(profiles(num_profiles)%flag_s(nlevels))
profiles(num_profiles)%flag_s= 0
profiles(num_profiles)%probe = 0.
profiles(num_profiles)%levels = nlevels
profiles(num_profiles)%lat = lat(i)
profiles(num_profiles)%lon = lon(i)
allocate(profiles(num_profiles)%ms_t(nlevels))
profiles(num_profiles)%ms_t(:) = min_obs_err_t ! default error variance for temperature
allocate(profiles(num_profiles)%ms_s(nlevels))
profiles(num_profiles)%ms_s(:) = min_obs_err_s ! default error variance for salinity
profiles(num_profiles)%time = profile_time
! calculate interpolation coefficients (make sure to account for grid offsets here!)
! note that this only works for lat/lon grids
ri0 = frac_index(lon(i), x_grid(isg-1:ieg+1)) - 1.
rj0 = frac_index(lat(i), y_grid(jsg-1:jeg+1)) - 1.
i0 = floor(ri0)
j0 = floor(rj0)
Profiles(num_profiles)%i_index = ri0
Profiles(num_profiles)%j_index = rj0
Profiles(num_profiles)%accepted = .true.
if (i0 < 0 .or. j0 < 0) then
Profiles(num_profiles)%accepted = .false.
endif
if (i0 > ieg+1 .or. j0 > jeg+1) then
call mpp_error(FATAL,'grid lat/lon index is out of bounds ')
endif
if (i0 < isc-1 .or. i0 > iec) then
call mpp_error(FATAL,'grid lat/lon index is out of bounds ')
endif
if (j0 < jsc-1 .or. j0 > jec) then
call mpp_error(FATAL,'grid lat/lon index is out of bounds ')
endif
if (Profiles(num_profiles)%accepted ) then
if (Grd%mask(i0,j0,1) == 0.0 .or. &
Grd%mask(i0+1,j0,1) == 0.0 .or. &
Grd%mask(i0,j0+1,1) == 0.0 .or. &
Grd%mask(i0+1,j0+1,1) == 0.0) then
Profiles(num_profiles)%accepted = .false.
endif
endif
if (Profiles(num_profiles)%accepted) then
allocate(Profiles(num_profiles)%k_index(Profiles(num_profiles)%levels))
do k=1, Profiles(num_profiles)%levels
rk0 = frac_index(Profiles(num_profiles)%depth(k), Grd%z(:))
k0 = floor(rk0)
if ( k0 == -1) then
if (Profiles(num_profiles)%depth(k) .ne. missing_value .and. &
Profiles(num_profiles)%depth(k) .lt. Grd%z(1)) then
k0 = 1
rk0 = 1.0
endif
endif
if (k0 .gt. size(Grd%z)-1 ) then
write(*,*) 'k0 out of bounds, rk0,k0= ',rk0,k0
write(*,*) 'Z_bound= ',Grd%z_bound
write(*,*) 'Profile%depth= ',Profiles(num_profiles)%depth
call mpp_error(FATAL)
endif
Profiles(num_profiles)%k_index(k) = rk0
if (Profiles(num_profiles)%flag_t(k) .eq. 0) then
if (Grd%mask(i0,j0,k0) == 0.0 .or. &
Grd%mask(i0+1,j0,k0) == 0.0 .or. &
Grd%mask(i0,j0+1,k0) == 0.0 .or. &
Grd%mask(i0+1,j0+1,k0) == 0.0) then
Profiles(num_profiles)%flag_t(k) = 1
endif
if (Grd%mask(i0,j0,k0+1) == 0.0 .or. &
Grd%mask(i0+1,j0,k0+1) == 0.0 .or. &
Grd%mask(i0,j0+1,k0+1) == 0.0 .or. &
Grd%mask(i0+1,j0+1,k0+1) == 0.0) then
Profiles(num_profiles)%flag_t(k) = 1
endif
if (Profiles(num_profiles)%data_t(k) == missing_value &
.or. Profiles(num_profiles)%depth(k) == missing_value) then
Profiles(num_profiles)%flag_t(k) = 1
endif
endif
enddo
endif
endif
enddo
! a = nprof_in_comp_domain(cpe)
! call mpp_broadcast(nprof_in_comp_domain(cpe),cpe)
! call mpp_sum(a)
! write(stdout(),*) 'A grand total of ',a,' profiles satisify acceptance criteria'
! do i=0,mpp_npes()-1
! write(stdout(),*) 'pe=',i,'profile count=',nprof_in_comp_domain(i)
! enddo
end subroutine create_ideal_profiles
subroutine nullify_obs_prof(profile)
type(ocean_profile_type), intent(inout) :: profile
profile%nvar = 0
profile%project=0.
profile%probe=0.
profile%ref_inst=0.
profile%wod_cast_num=0
profile%fix_depth=0.
profile%ocn_vehicle=0.
profile%database_id=0.
profile%levels=0
profile%profile_flag=-1
profile%profile_flag_s=-1
profile%lon=-1.0e10
profile%lat=-1.0e10
profile%accepted=.false.
profile%nlinks=0
if (ASSOCIATED(profile%next)) profile%next=>NULL()
if (ASSOCIATED(profile%depth)) profile%depth=>NULL()
if (ASSOCIATED(profile%data_t)) profile%data_t=>NULL()
if (ASSOCIATED(profile%data_s)) profile%data_s=>NULL()
if (ASSOCIATED(profile%flag_t)) profile%flag_t=>NULL()
if (ASSOCIATED(profile%flag_s)) profile%flag_s=>NULL()
profile%temp_err=0.0
profile%salt_err=0.0
if (ASSOCIATED(profile%ms_t)) profile%ms_t=>NULL()
if (ASSOCIATED(profile%ms_s)) profile%ms_s=>NULL()
profile%time = set_time(0,0)
profile%yyyy = 0
profile%mmdd = 0
if (ASSOCIATED(profile%model_time)) profile%model_time=>NULL()
if (ASSOCIATED(profile%model_grid)) profile%model_grid=>NULL()
if (ASSOCIATED(profile%k_index)) profile%k_index=>NULL()
profile%i_index=-1.0
profile%j_index=-1.0
profile%tdiff = set_time(0,0)
end subroutine nullify_obs_prof
end module oda_core_mod