!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !! !! !! GNU General Public License !! !! !! !! This file is part of the Flexible Modeling System (FMS). !! !! !! !! FMS is free software; you can redistribute it and/or modify !! !! it and are expected to follow the terms of the GNU General Public !! !! License as published by the Free Software Foundation. !! !! !! !! 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 General Public License !! !! along with FMS; if not, write to: !! !! Free Software Foundation, Inc. !! !! 59 Temple Place, Suite 330 !! !! Boston, MA 02111-1307 USA !! !! or see: !! !! http://www.gnu.org/licenses/gpl.txt !! !! !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 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 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 ioun = open_namelist_file() read(ioun,nml=oda_core_nml,iostat = io_status) ierr = check_nml_error(io_status,'oda_core_nml') call close_file(ioun) ! 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) model_t = model_t /sum_wgt elsewhere model_t = 0.0 end where if (PRESENT(model_s)) then where(sum_wgt > 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 ioun = open_namelist_file() read(ioun,nml=ocean_obs_nml,iostat = io_status) ierr = check_nml_error(io_status,'ocean_obs_nml') call close_file(ioun) 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