From 64bcd4e6013f11b09ce6f41d3e86bdf5af1e88c6 Mon Sep 17 00:00:00 2001 From: Helen Kershaw Date: Thu, 26 Sep 2024 13:30:28 -0400 Subject: [PATCH] feat: unified wrf/wrf-chem model_mod New model_mod directory so development can happen concurrently on wrf and wrf_unified. Original pull request #683 --- models/wrf_unified/.gitignore | 13 + models/wrf_unified/WRF_BC/README | 45 + .../wrf_unified/WRF_BC/module_couple_uv.f90 | 150 + .../WRF_BC/module_netcdf_interface.f90 | 588 +++ models/wrf_unified/WRF_BC/module_timediff.f90 | 117 + models/wrf_unified/WRF_BC/pert_wrf_bc.f90 | 607 +++ models/wrf_unified/WRF_BC/update_wrf_bc.f90 | 637 +++ .../add_pert_where_high_refl.f90 | 710 +++ .../WRF_DART_utilities/advance_cymdh.f90 | 128 + .../WRF_DART_utilities/convertdate.f90 | 108 + .../WRF_DART_utilities/ensemble_init.f90 | 579 +++ .../WRF_DART_utilities/extract.f90 | 127 + .../WRF_DART_utilities/grid_refl_obs.f90 | 409 ++ .../WRF_DART_utilities/replace_wrf_fields.f90 | 348 ++ .../WRF_DART_utilities/replace_wrf_fields.nml | 7 + .../WRF_DART_utilities/replace_wrf_fields.rst | 102 + .../wrf_dart_obs_preprocess.f90 | 2199 ++++++++++ .../wrf_dart_obs_preprocess.nml | 43 + .../wrf_dart_obs_preprocess.rst | 172 + .../WRF_DART_utilities/wrf_data_module.f90 | 1713 ++++++++ models/wrf_unified/model_mod.f90 | 3803 +++++++++++++++++ models/wrf_unified/model_mod.nml | 78 + models/wrf_unified/module_map_utils.f90 | 2348 ++++++++++ models/wrf_unified/readme.rst | 369 ++ models/wrf_unified/select.f90 | 167 + models/wrf_unified/work/input.nml | 560 +++ models/wrf_unified/work/quickbuild.sh | 72 + models/wrf_unified/work/runme_filter | 317 ++ 28 files changed, 16516 insertions(+) create mode 100644 models/wrf_unified/.gitignore create mode 100644 models/wrf_unified/WRF_BC/README create mode 100644 models/wrf_unified/WRF_BC/module_couple_uv.f90 create mode 100644 models/wrf_unified/WRF_BC/module_netcdf_interface.f90 create mode 100644 models/wrf_unified/WRF_BC/module_timediff.f90 create mode 100644 models/wrf_unified/WRF_BC/pert_wrf_bc.f90 create mode 100644 models/wrf_unified/WRF_BC/update_wrf_bc.f90 create mode 100644 models/wrf_unified/WRF_DART_utilities/add_pert_where_high_refl.f90 create mode 100644 models/wrf_unified/WRF_DART_utilities/advance_cymdh.f90 create mode 100644 models/wrf_unified/WRF_DART_utilities/convertdate.f90 create mode 100644 models/wrf_unified/WRF_DART_utilities/ensemble_init.f90 create mode 100644 models/wrf_unified/WRF_DART_utilities/extract.f90 create mode 100644 models/wrf_unified/WRF_DART_utilities/grid_refl_obs.f90 create mode 100644 models/wrf_unified/WRF_DART_utilities/replace_wrf_fields.f90 create mode 100644 models/wrf_unified/WRF_DART_utilities/replace_wrf_fields.nml create mode 100644 models/wrf_unified/WRF_DART_utilities/replace_wrf_fields.rst create mode 100644 models/wrf_unified/WRF_DART_utilities/wrf_dart_obs_preprocess.f90 create mode 100644 models/wrf_unified/WRF_DART_utilities/wrf_dart_obs_preprocess.nml create mode 100644 models/wrf_unified/WRF_DART_utilities/wrf_dart_obs_preprocess.rst create mode 100644 models/wrf_unified/WRF_DART_utilities/wrf_data_module.f90 create mode 100644 models/wrf_unified/model_mod.f90 create mode 100644 models/wrf_unified/model_mod.nml create mode 100644 models/wrf_unified/module_map_utils.f90 create mode 100644 models/wrf_unified/readme.rst create mode 100644 models/wrf_unified/select.f90 create mode 100644 models/wrf_unified/work/input.nml create mode 100755 models/wrf_unified/work/quickbuild.sh create mode 100755 models/wrf_unified/work/runme_filter diff --git a/models/wrf_unified/.gitignore b/models/wrf_unified/.gitignore new file mode 100644 index 0000000000..c9d584577b --- /dev/null +++ b/models/wrf_unified/.gitignore @@ -0,0 +1,13 @@ +# WRF executables +add_pert_where_high_refl +convertdate +ensemble_init +extract +grid_refl_obs +pert_wrf_bc +replace_wrf_fields +select +update_wrf_bc +wrf_dart_obs_preprocess +wrfinput_d01 +wrfinput_d02 diff --git a/models/wrf_unified/WRF_BC/README b/models/wrf_unified/WRF_BC/README new file mode 100644 index 0000000000..24ac008247 --- /dev/null +++ b/models/wrf_unified/WRF_BC/README @@ -0,0 +1,45 @@ +# DART software - Copyright UCAR. This open source software is provided +# by UCAR, "as is", without charge, subject to all terms of use at +# http://www.image.ucar.edu/DAReS/DART/DART_download +# +# DART $Id$ + +update_wrf_bc: + +File list: + +update_wrf_bc.f90 +module_couple_uv.f90 +module_netcdf_interface.f90 +module_timediff.f90 + +Files needed: + +wrfinput_d01 Updated WRF IC (3DVAR analysis) +wrfbdy_d01 Original WRF BC +input.nml namelist + +------------------------------------------------------------------ +NOTE: + +wrfinput_d01 will be READ only. +wrfbdy_d01 will be OVERWRITTEN. + +------------------------------------------------------------------ + +Compile: + + ./quickbuild.sh + +Run: + + update_wrf_bc + + (make sure you have all three files mentioned above) + +------------------------------------------------------------------ + +# +# $URL$ +# $Revision$ +# $Date$ diff --git a/models/wrf_unified/WRF_BC/module_couple_uv.f90 b/models/wrf_unified/WRF_BC/module_couple_uv.f90 new file mode 100644 index 0000000000..ad7c8fc4cc --- /dev/null +++ b/models/wrf_unified/WRF_BC/module_couple_uv.f90 @@ -0,0 +1,150 @@ +! DART software - Copyright UCAR. This open source software is provided +! by UCAR, "as is", without charge, subject to all terms of use at +! http://www.image.ucar.edu/DAReS/DART/DART_download +! +! $Id$ + +MODULE module_couple_uv + + use types_mod, only : r8 + +CONTAINS + +!------------------------------------------------------------------------ + + SUBROUTINE couple_uvw ( u, v, w, mu, mub, msfu, msfv, msfm, & + ids, ide, jds, jde, kds, kde ) + + IMPLICIT NONE + +!--Input data. + + INTEGER, INTENT(IN) :: ids, ide, jds, jde, kds, kde + + REAL(r8), DIMENSION(ids:ide+1, jds:jde , kds:kde), INTENT(INOUT) :: u + REAL(r8), DIMENSION(ids:ide , jds:jde+1, kds:kde), INTENT(INOUT) :: v + REAL(r8), DIMENSION(ids:ide , jds:jde, kds:kde+1), INTENT(INOUT) :: w + + REAL(r8), DIMENSION(ids:ide+1, jds:jde ), INTENT(IN) :: msfu + REAL(r8), DIMENSION(ids:ide , jds:jde+1), INTENT(IN) :: msfv + REAL(r8), DIMENSION(ids:ide , jds:jde ), INTENT(IN) :: msfm + + REAL(r8), DIMENSION(ids:ide , jds:jde ), INTENT(IN) :: mu, mub + + REAL(r8), ALLOCATABLE, DIMENSION(:, :) :: muu, muv, muw + + allocate(muu(ids:ide+1, jds:jde )) + allocate(muv(ids:ide , jds:jde+1)) + allocate(muw(ids:ide , jds:jde )) + +!--couple variables u, v + + CALL calc_mu_uvw ( mu, mub, muu, muv, muw, & + ids, ide, jds, jde ) + + CALL couple ( muu, u, msfu, & + ids, ide+1, jds, jde, kds, kde ) + + CALL couple ( muv, v, msfv, & + ids, ide, jds, jde+1, kds, kde ) + + CALL couple ( muw, w, msfm, & + ids, ide, jds, jde, kds, kde+1 ) + + deallocate(muu) + deallocate(muv) + deallocate(muw) + + END SUBROUTINE couple_uvw + +!------------------------------------------------------------------------------- + + SUBROUTINE calc_mu_uvw ( mu, mub, muu, muv, muw, & + ids, ide, jds, jde ) + + IMPLICIT NONE + +!--Input data + + INTEGER, INTENT(IN) :: ids, ide, jds, jde + + REAL(r8), DIMENSION(ids:ide, jds:jde), INTENT(IN) :: mu, mub + + REAL(r8), DIMENSION(ids:ide+1, jds:jde ), INTENT(OUT) :: muu + REAL(r8), DIMENSION(ids:ide , jds:jde+1), INTENT(OUT) :: muv + REAL(r8), DIMENSION(ids:ide , jds:jde ), INTENT(OUT) :: muw + + REAL(r8), DIMENSION(ids-1:ide+1, jds-1:jde+1) :: mut + + INTEGER :: i, j + + DO j=jds,jde + DO i=ids,ide + mut(i,j) = mu(i,j)+mub(i,j) + ENDDO + + mut(ids-1,j) = mut(ids,j) + mut(ide+1,j) = mut(ide,j) + ENDDO + + DO i=ids-1,ide+1 + mut(i,jds-1)=mut(i,jds) + mut(i,jde+1)=mut(i,jde) + ENDDO + + DO j=jds,jde + DO i=ids,ide+1 + muu(i,j) = 0.5_r8*(mut(i,j)+mut(i-1,j)) + ENDDO + ENDDO + + DO j=jds,jde+1 + DO i=ids,ide + muv(i,j) = 0.5_r8*(mut(i,j)+mut(i,j-1)) + ENDDO + ENDDO + + DO j=jds,jde + DO i=ids,ide + muw(i,j) = mut(i,j) + ENDDO + ENDDO + + END SUBROUTINE calc_mu_uvw + +!------------------------------------------------------------------------------- + + SUBROUTINE couple ( mut, field, msf, & + ids, ide, jds, jde, kds, kde ) + + IMPLICIT NONE + +!--Input data + + INTEGER, INTENT(IN) :: ids, ide, jds, jde, kds, kde + + REAL(r8), DIMENSION(ids:ide, jds:jde), INTENT(IN ) :: mut, msf + + REAL(r8), DIMENSION(ids:ide, jds:jde, kds:kde), INTENT(INOUT) :: field + +!--Local data + + INTEGER :: i, j, k + + DO j=jds,jde + DO k=kds,kde + DO i=ids,ide + field(i,j,k)=field(i,j,k)*mut(i,j)/msf(i,j) + ENDDO + ENDDO + ENDDO + + END SUBROUTINE couple + +END MODULE module_couple_uv + +! +! $URL$ +! $Id$ +! $Revision$ +! $Date$ diff --git a/models/wrf_unified/WRF_BC/module_netcdf_interface.f90 b/models/wrf_unified/WRF_BC/module_netcdf_interface.f90 new file mode 100644 index 0000000000..930f577f7c --- /dev/null +++ b/models/wrf_unified/WRF_BC/module_netcdf_interface.f90 @@ -0,0 +1,588 @@ +! DART software - Copyright UCAR. This open source software is provided +! by UCAR, "as is", without charge, subject to all terms of use at +! http://www.image.ucar.edu/DAReS/DART/DART_download +! +! $Id$ + +MODULE module_netcdf_interface + + use types_mod, only : r8 + use utilities_mod, only : error_handler, E_ERR + + use netcdf + + implicit none + private + +public :: get_dims_cdf, & + get_gl_att_real_cdf, & + put_gl_att_real_cdf, & + get_var_3d_real_cdf, & + get_var_2d_real_cdf, & + put_var_3d_real_cdf, & + put_var_2d_real_cdf, & + get_times_cdf, & + put_time_cdf, & + variable_exist + +! version controlled file description for error handling, do not edit +character(len=256), parameter :: source = & + "$URL$" +character(len=32 ), parameter :: revision = "$Revision$" +character(len=128), parameter :: revdate = "$Date$" + +character(len=128) :: errstring + +CONTAINS + +!-------------------------------------------------------------------- + +subroutine get_dims_cdf( fname, var, idims, ndims, debug ) + + implicit none + + character (len=80), intent(in) :: fname + character (len=*), intent(in) :: var + logical, intent(in) :: debug + integer, intent(out), dimension(4) :: idims + integer, intent(out) :: ndims + + integer :: cdfid, id_data, i + integer :: dimids(4) + + if(debug) write(6,*) ' open netcdf file ', trim(fname) + + call check( nf90_open(fname, NF90_NOWRITE, cdfid) ) + + call check( nf90_inq_varid(cdfid, var, id_data) ) + + call check( nf90_Inquire_Variable(cdfid, id_data, ndims=ndims, dimids=dimids) ) + + do i=1,ndims + call check( nf90_inquire_dimension(cdfid, dimids(i), len=idims(i)) ) + enddo + + call check( nf90_close(cdfid) ) + +contains + + ! Internal subroutine - checks error status after each netcdf, prints + ! text message each time an error code is returned. + subroutine check(istatus) + integer, intent (in) :: istatus + + if(istatus /= nf90_noerr) call error_handler(E_ERR, 'get_dims_cdf', & + trim(nf90_strerror(istatus)), source, revision, revdate) + + end subroutine check + +end subroutine get_dims_cdf + +!------------------------------------------------------------------------------- + +subroutine get_gl_att_real_cdf( fname, att_name, value, debug ) + + implicit none + + character (len=80), intent(in) :: fname + character (len=*), intent(in) :: att_name + logical, intent(in) :: debug + real(r8), intent(out) :: value + + integer :: cdfid + + if(debug) write(6,*) ' open netcdf file ', trim(fname) + + call check( nf90_open(fname, NF90_NOWRITE, cdfid) ) + + call check( nf90_get_att(cdfid, nf90_global, att_name, value) ) + + if(debug) write(6,*) ' global attribute ',att_name,' is ',value + + call check( nf90_close(cdfid) ) + +contains + + ! Internal subroutine - checks error status after each netcdf, prints + ! text message each time an error code is returned. + subroutine check(istatus) + integer, intent (in) :: istatus + + if(istatus /= nf90_noerr) call error_handler(E_ERR, 'get_gl_att_real_cdf', & + trim(nf90_strerror(istatus)), source, revision, revdate) + + end subroutine check + +end subroutine get_gl_att_real_cdf + +!------------------------------------------------------------------------------- + +subroutine put_gl_att_real_cdf( fname, att_name, value, debug ) + + implicit none + + character (len=80), intent(in) :: fname + character (len=*), intent(in) :: att_name + logical, intent(in) :: debug + real(r8), intent(in) :: value + + integer :: cdfid + + if(debug) write(6,*) ' open netcdf file ', trim(fname) + + call check( nf90_open(fname, NF90_WRITE, cdfid) ) + + call check( nf90_redef(cdfid) ) + call check( nf90_put_att(cdfid, nf90_global, att_name, value) ) + + if(debug) write(6,*) ' global attribute ',att_name,' is ',value + + call check( nf90_close(cdfid) ) + +contains + + ! Internal subroutine - checks error status after each netcdf, prints + ! text message each time an error code is returned. + subroutine check(istatus) + integer, intent (in) :: istatus + + if(istatus /= nf90_noerr) call error_handler(E_ERR, 'put_gl_att_real_cdf', & + trim(nf90_strerror(istatus)), source, revision, revdate) + + end subroutine check + + +end subroutine put_gl_att_real_cdf + + +!-------------------------------------------------------------------- + +subroutine get_var_3d_real_cdf( fname, var, data, & + i1, i2, i3, time, debug ) + + implicit none + + integer, intent(in) :: i1, i2, i3, time + character (len=80), intent(in) :: fname + logical, intent(in) :: debug + character (len=*), intent(in) :: var + real(r8), dimension(i1,i2,i3), intent(out) :: data + + integer :: cdfid, id_data + character (len=80) :: varnam + integer :: ndims, idims(4), dimids(4) + integer :: i, ivtype + + if(debug) write(6,*) ' open netcdf file ', trim(fname) + + call check( nf90_open(fname, NF90_NOWRITE, cdfid) ) + + call check( nf90_inq_varid(cdfid, var, id_data) ) + + call check( nf90_Inquire_Variable(cdfid, id_data, name=varnam, xtype=ivtype, ndims=ndims, dimids=dimids) ) + + if(debug) then + write(6,*) ' number of dims for ',var,' ',ndims + write(unit=*, fmt='(a,i6)') ' ivtype=', ivtype + write(unit=*, fmt='(a, a)') ' varnam=', trim(varnam) + write(unit=*, fmt='(a,i6)') ' kind(data)=', kind(data) + endif + + do i=1,ndims + call check( nf90_inquire_dimension(cdfid, dimids(i), len=idims(i)) ) + if(debug) write(6,*) ' dimension ',i,idims(i) + enddo + +! check the dimensions + + if( (i1 /= idims(1)) .or. & + (i2 /= idims(2)) .or. & + (i3 /= idims(3)) .or. & + (time > idims(4)) ) then + + write(6,*) ' error in get_var_3d_real read, dimension problem ' + write(6,*) i1, idims(1) + write(6,*) i2, idims(2) + write(6,*) i3, idims(3) + write(6,*) time, idims(4) + stop + + end if + +! get the data + + call check( nf90_get_var(cdfid, id_data, data, start = (/ 1, 1, 1, time /)) ) + + if(debug) write(unit=*, fmt='(a,e24.12)') ' Sample data=', data(1,1,1) + + call check( nf90_close(cdfid) ) + +contains + + ! Internal subroutine - checks error status after each netcdf, prints + ! text message each time an error code is returned. + subroutine check(istatus) + integer, intent (in) :: istatus + + if(istatus /= nf90_noerr) call error_handler(E_ERR, 'get_var_3d_real_cdf', & + trim(nf90_strerror(istatus)), source, revision, revdate) + + end subroutine check + +end subroutine get_var_3d_real_cdf + +!-------------------------------------------------------------------- + +subroutine get_var_2d_real_cdf( fname, var, data, & + i1, i2, time, debug ) + + implicit none + + integer, intent(in) :: i1, i2, time + character (len=80), intent(in) :: fname + logical, intent(in) :: debug + character (len=*), intent(in) :: var + real(r8), dimension(i1,i2), intent(out) :: data + + integer :: cdfid, id_data + character (len=80) :: varnam + integer :: ndims, idims(4), dimids(4) + integer :: i, ivtype + + if(debug) write(6,*) ' open netcdf file ', trim(fname) + + call check( nf90_open(fname, NF90_NOWRITE, cdfid) ) + + call check( nf90_inq_varid(cdfid, var, id_data) ) + + call check( nf90_Inquire_Variable(cdfid, id_data, name=varnam, xtype=ivtype, ndims=ndims, dimids=dimids) ) + + if(debug) then + write(6,*) ' number of dims for ',var,' ',ndims + write(unit=*, fmt='(a,i6)') ' ivtype=', ivtype + write(unit=*, fmt='(a, a)') ' varnam=', trim(varnam) + endif + + do i=1,ndims + call check( nf90_inquire_dimension(cdfid, dimids(i), len=idims(i)) ) + if(debug) write(6,*) ' dimension ',i,idims(i) + enddo + +! check the dimensions + + if( (i1 /= idims(1)) .or. & + (i2 /= idims(2)) .or. & + (time > idims(3)) ) then + + write(6,*) ' error in get_var_2d_real read, dimension problem ' + write(6,*) i1, idims(1) + write(6,*) i2, idims(2) + write(6,*) time, idims(4) + stop + + end if + +! get the data + + call check( nf90_get_var(cdfid, id_data, data, start = (/ 1, 1, time /)) ) + + if(debug) write(unit=*, fmt='(a,e24.12)') ' Sample data=', data(1,1) + + call check( nf90_close(cdfid) ) + +contains + + ! Internal subroutine - checks error status after each netcdf, prints + ! text message each time an error code is returned. + subroutine check(istatus) + integer, intent (in) :: istatus + + if(istatus /= nf90_noerr) call error_handler(E_ERR, 'get_var_2d_real_cdf', & + trim(nf90_strerror(istatus)), source, revision, revdate) + + end subroutine check + +end subroutine get_var_2d_real_cdf + +!-------------------------------------------------------------------- + +subroutine put_var_3d_real_cdf( fname, var, data, & + i1, i2, i3, time, debug ) + + implicit none + + integer, intent(in) :: i1, i2, i3, time + character (len=80), intent(in) :: fname + logical, intent(in) :: debug + character (len=*), intent(in) :: var + real(r8), dimension(i1,i2,i3), intent(in) :: data + + integer :: cdfid, id_data + character (len=80) :: varnam + integer :: ndims, idims(4), dimids(4) + integer :: i, ivtype + + if(debug) write(6,*) ' open netcdf file ', trim(fname) + + call check( nf90_open(fname, NF90_WRITE, cdfid) ) + + call check( nf90_inq_varid(cdfid, var, id_data) ) + + call check( nf90_Inquire_Variable(cdfid, id_data, name=varnam, xtype=ivtype, ndims=ndims, dimids=dimids) ) + + if(debug) write(6,*) ' number of dims for ',var,' ',ndims + + do i=1,ndims + call check( nf90_inquire_dimension(cdfid, dimids(i), len=idims(i)) ) + if(debug) write(6,*) ' dimension ',i,idims(i) + enddo + +! check the dimensions + + if( (i1 /= idims(1)) .or. & + (i2 /= idims(2)) .or. & + (i3 /= idims(3)) .or. & + (time > idims(4)) ) then + + write(6,*) ' error in put_var_3d_real read, dimension problem ' + write(6,*) i1, idims(1) + write(6,*) i2, idims(2) + write(6,*) i3, idims(3) + write(6,*) time, idims(4) + stop + + end if + +! write the data + + call check( nf90_put_var(cdfid, id_data, data, start = (/ 1, 1, 1, time /)) ) + + call check( nf90_close(cdfid) ) + +contains + + ! Internal subroutine - checks error status after each netcdf, prints + ! text message each time an error code is returned. + subroutine check(istatus) + integer, intent (in) :: istatus + + if(istatus /= nf90_noerr) call error_handler(E_ERR, 'put_var_3d_real_cdf', & + trim(nf90_strerror(istatus)), source, revision, revdate) + + end subroutine check + +end subroutine put_var_3d_real_cdf + +!-------------------------------------------------------------------- + +subroutine put_var_2d_real_cdf( fname, var, data, & + i1, i2, time, debug ) + + implicit none + + integer, intent(in) :: i1, i2, time + character (len=80), intent(in) :: fname + logical, intent(in) :: debug + character (len=*), intent(in) :: var + real(r8), dimension(i1,i2), intent(in) :: data + + integer :: cdfid, id_data + character (len=80) :: varnam + integer :: ndims, idims(3), dimids(3) + integer :: i, ivtype + + if(debug) write(6,*) ' open netcdf file ', trim(fname) + + call check( nf90_open(fname, NF90_WRITE, cdfid) ) + + call check( nf90_inq_varid(cdfid, var, id_data) ) + + call check( nf90_Inquire_Variable(cdfid, id_data, name=varnam, xtype=ivtype, ndims=ndims, dimids=dimids) ) + + if(debug) write(6,*) ' number of dims for ',var,' ',ndims + + do i=1,ndims + call check( nf90_inquire_dimension(cdfid, dimids(i), len=idims(i)) ) + if(debug) write(6,*) ' dimension ',i,idims(i) + enddo + +! check the dimensions + + if( (i1 /= idims(1)) .or. & + (i2 /= idims(2)) .or. & + (time > idims(3)) ) then + + write(6,*) ' error in put_var_2d_real read, dimension problem ' + write(6,*) i1, idims(1) + write(6,*) i2, idims(2) + write(6,*) time, idims(3) + stop + + end if + +! write the data + + call check( nf90_put_var(cdfid, id_data, data, start = (/ 1, 1, time /)) ) + + call check( nf90_close(cdfid) ) + +contains + + ! Internal subroutine - checks error status after each netcdf, prints + ! text message each time an error code is returned. + subroutine check(istatus) + integer, intent (in) :: istatus + + if(istatus /= nf90_noerr) call error_handler(E_ERR, 'put_var_2d_real_cdf', & + trim(nf90_strerror(istatus)), source, revision, revdate) + + end subroutine check + +end subroutine put_var_2d_real_cdf + +!-------------------------------------------------------------------- + +subroutine get_times_cdf( fname, time_name, times, n_times, max_times, debug ) + + implicit none + + integer, intent(in) :: max_times + integer, intent(out) :: n_times + character (len=80), intent(in) :: fname, time_name + character (len=19), intent(out) :: times(max_times) + logical, intent(in) :: debug + + integer :: cdfid, id_time + character (len=80) :: varnam, time1 + integer :: ndims, idims(max_times) + integer :: istart(max_times),iend(max_times), dimids(max_times) + integer :: i, ivtype + + if(debug) write(6,*) ' open netcdf file ', trim(fname) + + call check( nf90_open(fname, NF90_NOWRITE, cdfid) ) + + call check( nf90_inq_varid(cdfid, time_name, id_time) ) + + call check( nf90_Inquire_Variable(cdfid, id_time, name=varnam, xtype=ivtype, ndims=ndims, dimids=dimids) ) + + do i=1,ndims + call check( nf90_inquire_dimension(cdfid, dimids(i), len=idims(i)) ) + if(debug) write(6,*) ' dimension ',i,idims(i) + enddo + +! get the times + + n_times = idims(2) + if (n_times > max_times) then + write(errstring, '(2(A,I6))') 'number of times in file ', n_times, & + ' is larger than allocated space ', max_times + call error_handler(E_ERR, 'get_times_cdf', errstring, source, revision, revdate, & + text2='increase max_times in [pert,update]_wrf_bc.f90 and recompile') + endif + + do i=1,idims(2) + istart(1) = 1 + iend(1) = idims(1) + istart(2) = i + iend(2) = 1 + + call check( nf90_get_var(cdfid, id_time, times(i), start = (/ 1, i /), & + count = (/idims(1), 1/) ) ) +! rcode = NF_GET_VARA_TEXT ( cdfid, id_time, & +! istart, iend, & +! times(i) ) + time1 = times(i) + + if(debug) write(6,*) trim(fname), time1(1:19) + enddo + + call check( nf90_close(cdfid) ) +contains + + ! Internal subroutine - checks error status after each netcdf, prints + ! text message each time an error code is returned. + subroutine check(istatus) + integer, intent (in) :: istatus + + if(istatus /= nf90_noerr) call error_handler(E_ERR, 'get_times_cdf', & + trim(nf90_strerror(istatus)), source, revision, revdate) + + end subroutine check + +end subroutine get_times_cdf + +!-------------------------------------------------------------------- + +subroutine put_time_cdf( fname, time_name, char, itime, debug ) + + implicit none + + integer, intent(in) :: itime + character (len=80), intent(in) :: fname, time_name + character (len=19), intent(in) :: char + logical, intent(in) :: debug + + integer :: cdfid, id_time + + if(debug) write(6,*) ' open netcdf file ', trim(fname) + + call check( nf90_open(fname, NF90_WRITE, cdfid) ) + + call check( nf90_inq_varid(cdfid, time_name, id_time) ) + + call check( nf90_put_var(cdfid, id_time, char, start = (/ 1, itime /)) ) + + call check( nf90_close(cdfid) ) + +contains + + ! Internal subroutine - checks error status after each netcdf, prints + ! text message each time an error code is returned. + subroutine check(istatus) + integer, intent (in) :: istatus + + if(istatus /= nf90_noerr) call error_handler(E_ERR, 'put_time_cdf', & + trim(nf90_strerror(istatus)), source, revision, revdate) + + end subroutine check + +end subroutine put_time_cdf + +function variable_exist( fname, var_name ) + + implicit none + + character(len=*), intent(in) :: fname, var_name + + integer :: cdfid, var_id, rcode + logical :: variable_exist + + variable_exist = .false. + call check( nf90_open(fname, nf90_nowrite, cdfid) ) + rcode = nf90_inq_varid(cdfid, var_name, var_id) + call check( nf90_close(cdfid) ) + if ( rcode == nf90_noerr ) variable_exist = .true. + return + +contains + + ! Internal subroutine - checks error status after each netcdf, prints + ! text message each time an error code is returned. + subroutine check(istatus) + integer, intent (in) :: istatus + + if(istatus /= nf90_noerr) call error_handler(E_ERR, 'variable_exist', & + trim(nf90_strerror(istatus)), source, revision, revdate) + + end subroutine check + +end function variable_exist + +END MODULE module_netcdf_interface + +! +! $URL$ +! $Id$ +! $Revision$ +! $Date$ diff --git a/models/wrf_unified/WRF_BC/module_timediff.f90 b/models/wrf_unified/WRF_BC/module_timediff.f90 new file mode 100644 index 0000000000..613d587489 --- /dev/null +++ b/models/wrf_unified/WRF_BC/module_timediff.f90 @@ -0,0 +1,117 @@ +! DART software - Copyright UCAR. This open source software is provided +! by UCAR, "as is", without charge, subject to all terms of use at +! http://www.image.ucar.edu/DAReS/DART/DART_download +! +! $Id$ + +MODULE module_timediff + + use types_mod, only : r8, i8 + use time_manager_mod, only : time_type, set_date_gregorian, get_time + + implicit none + private + +public :: time_diff, find_time_index + +CONTAINS + +SUBROUTINE time_diff(stime,etime,diff) +!####################################### +! computes the difference in seconds between stime and etime, where stime +! and etime are character*19 as: YYYY-MM-DD_hh:mm:ss + + implicit none + + character(len=19), intent(in) :: stime, etime + real(r8), intent(out) :: diff + + type(time_type) :: sdate, edate + integer :: syy,smm,sdd,shr,smi,sss + integer :: eyy,emm,edd,ehr,emi,ess + integer(i8) :: s_secs, e_secs + + read(stime(1:4),*) syy + read(stime(6:7),*) smm + read(stime(9:10),*) sdd + read(stime(12:13),*) shr + read(stime(15:16),*) smi + read(stime(18:19),*) sss + read(etime(1:4),*) eyy + read(etime(6:7),*) emm + read(etime(9:10),*) edd + read(etime(12:13),*) ehr + read(etime(15:16),*) emi + read(etime(18:19),*) ess + + sdate = set_date_gregorian(syy,smm,sdd,shr,smi,sss) + edate = set_date_gregorian(eyy,emm,edd,ehr,emi,ess) + +!!$ s_secs = shr*3600 + smi*60 +sss +!!$ e_secs = ehr*3600 + emi*60 +ess + call get_time(sdate, sss, sdd) + call get_time(edate, ess, edd) + s_secs = sss + sdd*86400_i8 + e_secs = ess + edd*86400_i8 + + diff = e_secs - s_secs + + if ( diff < 0 ) then + print*,sss,ess,sdd,edd,e_secs,s_secs,diff + print*, "your time difference is negative - aborting:" + stop 'time_diff' + endif + +END SUBROUTINE time_diff + +SUBROUTINE find_time_index(timelist, time_to_find, ntimes, itime) +!################################################ +! finds first index in timelist such that time_to_find is later than +! timelist(itime) +!################################################## + implicit none + + character(len=19), dimension(:), intent(in) :: timelist + character(len=19), intent(in) :: time_to_find + integer, intent(in) :: ntimes + integer, intent(out) :: itime + + integer :: it + integer(kind=i8) :: tfyr, tfmo, tfdy, tfhr, tfmm, tfss, tf + integer(kind=i8) :: lyr, lmo, ldy, lhr, lmm, lss, l + + read(time_to_find(1:4),*) tfyr + read(time_to_find(6:7),*) tfmo + read(time_to_find(9:10),*) tfdy + read(time_to_find(12:13),*) tfhr + read(time_to_find(15:16),*) tfmm + read(time_to_find(18:19),*) tfss + + tf = (tfyr*10000000000_i8)+(tfmo*100000000_i8) + (tfdy*1000000_i8) + & + (tfhr*10000_i8) + (tfmm*100_i8) + tfss + + itime = ntimes + do it = ntimes,1,-1 + read(timelist(it)(1:4),*) lyr + read(timelist(it)(6:7),*) lmo + read(timelist(it)(9:10),*) ldy + read(timelist(it)(12:13),*) lhr + read(timelist(it)(15:16),*) lmm + read(timelist(it)(18:19),*) lss + l = (lyr*10000000000_i8)+(lmo*100000000_i8) + (ldy*1000000_i8) + & + (lhr*10000_i8) + (lmm*100_i8) + lss + if ( l <= tf ) then + itime = it + exit + endif + enddo + +END SUBROUTINE find_time_index + +END MODULE module_timediff + +! +! $URL$ +! $Id$ +! $Revision$ +! $Date$ diff --git a/models/wrf_unified/WRF_BC/pert_wrf_bc.f90 b/models/wrf_unified/WRF_BC/pert_wrf_bc.f90 new file mode 100644 index 0000000000..2cf94facd3 --- /dev/null +++ b/models/wrf_unified/WRF_BC/pert_wrf_bc.f90 @@ -0,0 +1,607 @@ +! DART software - Copyright UCAR. This open source software is provided +! by UCAR, "as is", without charge, subject to all terms of use at +! http://www.image.ucar.edu/DAReS/DART/DART_download +! +! $Id$ + +program pert_wrf_bc + +! program to update BC file from 3dvar or filter output. +! current version reads only wrf-netcdf file format. + +! Input files: wrfinput_this, wrfbdy_this, and wrfinput_next. +! wrfbdy_this contains (at least) values at THIS time, and +! tendencies valid from THIS to NEXT. + +use types_mod, only : r8 +use utilities_mod, only : file_exist, open_file, close_file, & + initialize_utilities, finalize_utilities, & + register_module, error_handler, E_ERR, E_MSG, & + logfileunit, timestamp +use module_netcdf_interface, only : get_dims_cdf, get_gl_att_real_cdf, put_gl_att_real_cdf, & + get_var_3d_real_cdf, get_var_2d_real_cdf, & + put_var_3d_real_cdf, put_var_2d_real_cdf, & + get_times_cdf, put_time_cdf, variable_exist +use module_couple_uv +use module_timediff, only : time_diff, find_time_index + +implicit none + +! version controlled file description for error handling, do not edit +character(len=256), parameter :: source = & + "$URL$" +character(len=32 ), parameter :: revision = "$Revision$" +character(len=128), parameter :: revdate = "$Date$" + +!----------------------------------------------------------------------- + +integer, parameter :: max_3d_variables = 20, & + max_2d_variables = 20, & + max_times = 100 + +character(len=80) :: wrf_input_current_file, wrf_input_next_file, & + wrf_bdy_file, time_name + +character(len=20) :: var_name + +character(len=20) :: var3d(max_3d_variables), & + var2d(max_2d_variables) + +character(len=10), dimension(4) :: bdyname, tenname + +character(len=19), dimension(max_times) :: udtime, bdytime, thisbdytime, & + nextbdytime, nexttime + +character(len=129) :: msgstring + +integer :: ids, ide, jds, jde, kds, kde +integer :: num3d, num2d, ndims, nmoist +integer :: i,j,k,l,m,n +integer :: ntimes_bdy, ntimes_ud, ntimes_next, itime + +integer, dimension(4) :: dims + +real(r8), allocatable, dimension(:,:) :: tend2d, scnd2d, frst2d + +real(r8), allocatable, dimension(:,:,:) :: tend3d, scnd3d, frst3d, full3d, full3d_next + +real(r8), allocatable, dimension(:,:,:) :: u, v, w +real(r8), allocatable, dimension(:,:,:) :: u_next, v_next, w_next + +real(r8), allocatable, dimension(:, :) :: mu, mu_next, mub, msfu, msfv, msfm + +integer :: east_end, north_end + +logical, parameter :: debug = .false. + +real(r8) :: bdyfrq_old, bdyfrq, dtnext + +!---------------------------------------------------------------------- + +call initialize_utilities('pert_wrf_bc') +call register_module(source, revision, revdate) + +wrf_input_current_file='wrfinput_this' +wrf_input_next_file='wrfinput_next' +wrf_bdy_file ='wrfbdy_this' + +if((.not. file_exist(wrf_input_current_file)) .or. & + (.not. file_exist(wrf_input_next_file)) .or. & + (.not. file_exist(wrf_bdy_file))) then + write(msgstring, *)'wrfinput_this or wrfinput_next or wrfbdy_this is absent.' + call error_handler(E_ERR,'pert_wrf_bc',msgstring,source,revision,revdate) +endif + +!--boundary variables +bdyname(1)='_BXS' +bdyname(2)='_BXE' +bdyname(3)='_BYS' +bdyname(4)='_BYE' + +!--boundary tendency variables +tenname(1)='_BTXS' +tenname(2)='_BTXE' +tenname(3)='_BTYS' +tenname(4)='_BTYE' + +!--3D need update +nmoist = 7 +num3d = 5 + nmoist +var3d(1)='U' +var3d(2)='V' +var3d(3)='W' +var3d(4)='PH' +var3d(5)='T' +var3d(6)='QVAPOR' +var3d(7)='QCLOUD' +var3d(8)='QRAIN' +var3d(9)='QICE' +var3d(10)='QSNOW' +var3d(11)='QGRAUP' +var3d(12)='QNICE' + +!--2D need update +num2d=5 +var2d(1)='MUB' +var2d(2)='MU' +var2d(3)='MAPFAC_U' +var2d(4)='MAPFAC_V' +var2d(5)='MAPFAC_M' + +!--------------------------------------------------------------------- + +east_end=0 +north_end=0 + +!--------------------------------------------------------------------- +!-- Current time in the first input file +time_name = 'Times' +call get_times_cdf( wrf_input_current_file, time_name, udtime, ntimes_ud, max_times, debug ) +!-- Next time in the next input file +call get_times_cdf( wrf_input_next_file, time_name, nexttime, ntimes_next, max_times, debug ) + +if(debug) print*, 'udtime = ',udtime(1) +if(debug) print*, 'nexttime = ',nexttime(1) + +!-- list of boundary times +call get_times_cdf( wrf_bdy_file, time_name, bdytime, ntimes_bdy, max_times, debug ) + +!-- Time index of current tendency - to grab from wrfbdy +call find_time_index(bdytime, udtime(1), ntimes_bdy, itime) + +if(debug) print*, 'itime = ',itime +if(debug) print*, 'bdytime = ',bdytime(itime) + +!--------------------------------------------------------------------- +!--First, the boundary frequency. +time_name = 'md___thisbdytimee_x_t_d_o_m_a_i_n_m_e_t_a_data_' +call get_times_cdf( wrf_bdy_file, time_name, & + thisbdytime, ntimes_bdy, max_times, debug ) +time_name = 'md___nextbdytimee_x_t_d_o_m_a_i_n_m_e_t_a_data_' +call get_times_cdf( wrf_bdy_file, time_name, & + nextbdytime, ntimes_bdy, max_times, debug ) +call time_diff( thisbdytime(itime), nextbdytime(itime), bdyfrq_old ) + +if(debug) then + write(unit=*, fmt='(a, f12.2)') & + 'BDYFRQ=', bdyfrq_old +endif +!-- time diff between this time and most recent (to shorten bdyfrq) +call time_diff( udtime(1), nextbdytime(itime), bdyfrq ) + +if(debug) print*, 'New bdyfrq = ',bdyfrq + +!-- time diff between nexttime and most recent (this should be zero) +call time_diff( nexttime(1), nextbdytime(itime), dtnext ) +if(debug) print*, 'dtnext = ',dtnext +if(abs(dtnext)>0.0) then + write(msgstring, *)'Times in wrfinput_next and wrfbdy_this are not same.' + call error_handler(E_ERR,'pert_wrf_bc',msgstring,source,revision,revdate) +endif + + +!-- put in the new thisbdytime +thisbdytime(itime) = udtime(1) +time_name = 'md___thisbdytimee_x_t_d_o_m_a_i_n_m_e_t_a_data_' +call put_time_cdf( wrf_bdy_file, time_name, & + thisbdytime(itime), itime, debug ) + +!--For 2D variables +!--Get mu, mub, msfu, msfv, and msfm + +do n=1,num2d + call get_dims_cdf( wrf_input_current_file, trim(var2d(n)), dims, ndims, debug ) + + select case(trim(var2d(n))) + case ('MU') ; + allocate(mu(dims(1), dims(2))) + + call get_var_2d_real_cdf( wrf_input_current_file, trim(var2d(n)), mu, & + dims(1), dims(2), 1, debug ) + + allocate(mu_next(dims(1), dims(2))) + + call get_var_2d_real_cdf( wrf_input_next_file, trim(var2d(n)), mu_next, & + dims(1), dims(2), 1, debug ) + + east_end=dims(1)+1 + north_end=dims(2)+1 + case ('MUB') ; + allocate(mub(dims(1), dims(2))) + + call get_var_2d_real_cdf( wrf_input_current_file, trim(var2d(n)), mub, & + dims(1), dims(2), 1, debug ) + case ('MAPFAC_U') ; + allocate(msfu(dims(1), dims(2))) + + call get_var_2d_real_cdf( wrf_input_current_file, trim(var2d(n)), msfu, & + dims(1), dims(2), 1, debug ) + + if(debug) then + do j=1,dims(2) + write(unit=*, fmt='(2(a,i5), a, f12.8)') & + 'msfu(', dims(1), ',', j, ')=', msfu(dims(1),j) + enddo + endif + + case ('MAPFAC_V') ; + allocate(msfv(dims(1), dims(2))) + + call get_var_2d_real_cdf( wrf_input_current_file, trim(var2d(n)), msfv, & + dims(1), dims(2), 1, debug ) + + if(debug) then + do i=1,dims(1) + write(unit=*, fmt='(2(a,i5), a, f12.8)') & + 'msfv(', i, ',', dims(2), ')=', msfv(i,dims(2)) + enddo + endif + + case ('MAPFAC_M') ; + allocate(msfm(dims(1), dims(2))) + + call get_var_2d_real_cdf( wrf_input_current_file, trim(var2d(n)), msfm, & + dims(1), dims(2), 1, debug ) + + if(debug) then + do i=1,dims(1) + write(unit=*, fmt='(2(a,i5), a, f12.8)') & + 'msfm(', i, ',', dims(2), ')=', msfm(i,dims(2)) + enddo + endif + + case default ; + print *, 'It is impossible here. var2d(n)=', trim(var2d(n)) + end select +enddo + +if(debug) then + write(unit=*, fmt='(2(2x,a,e20.12)/2x,a,i2,2x,a,4i6)') & + 'Sample mu =', mu(dims(1)/2,dims(2)/2), & + 'Sample mub=', mub(dims(1)/2,dims(2)/2), & + 'ndims=', ndims, 'dims=', (dims(i), i=1,ndims) +endif + +if(east_end < 1 .or. north_end < 1) then + write(msgstring, *)'Wrong data for Boundary (east_end or north_end < 1).' + call error_handler(E_ERR,'pert_wrf_bc',msgstring,source,revision,revdate) +endif + +!--------------------------------------------------------------------- + + do m=1,4 + var_name='MU' // trim(bdyname(m)) + + call get_dims_cdf( wrf_bdy_file, trim(var_name), dims, ndims, debug ) + + allocate(frst2d(dims(1), dims(2))) + allocate(scnd2d(dims(1), dims(2))) + allocate(tend2d(dims(1), dims(2))) + +!-----Assign new BC values + select case(m) + case (1) ; ! West boundary + do l=1,dims(2) + do j=1,dims(1) + frst2d(j,l) = mu(l,j) + scnd2d(j,l) = mu_next(l,j) + enddo + enddo + case (2) ; ! East boundary + do l=1,dims(2) + do j=1,dims(1) + frst2d(j,l) = mu(east_end-l,j) + scnd2d(j,l) = mu_next(east_end-l,j) + enddo + enddo + case (3) ; ! South boundary + do l=1,dims(2) + do i=1,dims(1) + frst2d(i,l) = mu(i,l) + scnd2d(i,l) = mu_next(i,l) + enddo + enddo + case (4) ; ! North boundary + do l=1,dims(2) + do i=1,dims(1) + frst2d(i,l) = mu(i,north_end-l) + scnd2d(i,l) = mu_next(i,north_end-l) + enddo + enddo + case default ; + print *, 'It is impossible here. mu, m=', m + end select + +!-----calculate new tendency + + tend2d = (scnd2d - frst2d)/bdyfrq + + if(debug) then + write(unit=*, fmt='(a,i2,2x,2a/3(a,e20.12,4x)/a,i2,2x,a,4i6)') & + 'No.', n, 'Variable: ', trim(var_name), & + 'Sampe frst2d=', frst2d(dims(1)/2,dims(2)/2), & + 'scnd2d=', scnd2d(dims(1)/2,dims(2)/2), & + 'tend2d=', tend2d(dims(1)/2,dims(2)/2), & + 'ndims=', ndims, 'dims=', (dims(i), i=1,ndims) + endif + +!-----output new variable at first time level + var_name='MU' // trim(bdyname(m)) + call put_var_2d_real_cdf( wrf_bdy_file, trim(var_name), frst2d, & + dims(1), dims(2), itime, debug ) +!-----output new tendency + var_name='MU' // trim(tenname(m)) + call put_var_2d_real_cdf( wrf_bdy_file, trim(var_name), tend2d, & + dims(1), dims(2), itime, debug ) + + deallocate(frst2d) + deallocate(scnd2d) + deallocate(tend2d) + enddo + +!--------------------------------------------------------------------- +!--For 3D variables + +!--Get U + call get_dims_cdf( wrf_input_current_file, 'U', dims, ndims, debug ) + + allocate(u(dims(1), dims(2), dims(3))) + + ids=1 + ide=dims(1)-1 + jds=1 + jde=dims(2) + kds=1 + kde=dims(3) + + call get_var_3d_real_cdf( wrf_input_current_file, 'U', u, & + dims(1), dims(2), dims(3), 1, debug ) + + allocate(u_next(dims(1), dims(2), dims(3))) + + call get_var_3d_real_cdf( wrf_input_next_file, 'U', u_next, & + dims(1), dims(2), dims(3), 1, debug ) + + if(debug) then + do j=1,dims(2) + write(unit=*, fmt='(2(a,i5), a, f12.8)') & + 'u(', dims(1), ',', j, ',1)=', u(dims(1),j,1) + enddo + endif + +!--Get V + call get_dims_cdf( wrf_input_current_file, 'V', dims, ndims, debug ) + + allocate(v(dims(1), dims(2), dims(3))) + + call get_var_3d_real_cdf( wrf_input_current_file, 'V', v, & + dims(1), dims(2), dims(3), 1, debug ) + + allocate(v_next(dims(1), dims(2), dims(3))) + + call get_var_3d_real_cdf( wrf_input_next_file, 'V', v_next, & + dims(1), dims(2), dims(3), 1, debug ) + + if(debug) then + do i=1,dims(1) + write(unit=*, fmt='(2(a,i5), a, f12.8)') & + 'v(', i, ',', dims(2), ',1)=', v(i,dims(2),1) + enddo + endif + +!--Get W + call get_dims_cdf( wrf_input_current_file, 'W', dims, ndims, debug ) + + allocate(w(dims(1), dims(2), dims(3))) + + call get_var_3d_real_cdf( wrf_input_current_file, 'W', w, & + dims(1), dims(2), dims(3), 1, debug ) + + allocate(w_next(dims(1), dims(2), dims(3))) + + call get_var_3d_real_cdf( wrf_input_next_file, 'W', w_next, & + dims(1), dims(2), dims(3), 1, debug ) + + if(debug) then + do i=1,dims(1) + write(unit=*, fmt='(2(a,i5), a, f12.8)') & + 'w(', i, ',', dims(2), ',1)=', w(i,dims(2),1) + enddo + endif + + if(debug) then + write(unit=*, fmt='(a,e20.12,4x)') & + 'Before couple Sampe u=', u(dims(1)/2,dims(2)/2,dims(3)/2), & + 'Before couple Sampe v=', v(dims(1)/2,dims(2)/2,dims(3)/2), & + 'Before couple Sampe w=', w(dims(1)/2,dims(2)/2,dims(3)/2) + endif + +!--------------------------------------------------------------------- +!--Couple u, v, w. + call couple_uvw ( u, v, w, mu, mub, msfu, msfv, msfm, ids, ide, jds, jde, kds, kde ) + + call couple_uvw ( u_next, v_next, w_next, mu_next, mub, msfu, msfv, msfm, ids, ide, jds, jde, kds, kde ) + + if(debug) then + write(unit=*, fmt='(a,e20.12,4x)') & + 'After couple Sampe u=', u(dims(1)/2,dims(2)/2,dims(3)/2), & + 'After couple Sampe v=', v(dims(1)/2,dims(2)/2,dims(3)/2) + endif + +!--------------------------------------------------------------------- +!--For 3D variables + + loop3d : do n = 1, num3d + + if ( .not. variable_exist(wrf_input_current_file, trim(var3d(n))) ) cycle loop3d + write(6,*) 'Processing',trim(var3d(n)) + + call get_dims_cdf( wrf_input_current_file, trim(var3d(n)), dims, ndims, debug ) + + allocate(full3d(dims(1), dims(2), dims(3))) + + allocate(full3d_next(dims(1), dims(2), dims(3))) + + east_end=dims(1)+1 + north_end=dims(2)+1 + + select case(trim(var3d(n))) + case ('U') ; ! U + full3d(:,:,:)=u(:,:,:) + full3d_next(:,:,:)=u_next(:,:,:) + case ('V') ; ! V + full3d(:,:,:)=v(:,:,:) + full3d_next(:,:,:)=v_next(:,:,:) + case ('W') ; ! W + full3d(:,:,:)=w(:,:,:) + full3d_next(:,:,:)=w_next(:,:,:) + case ('T', 'PH', 'QVAPOR', 'QCLOUD', 'QRAIN', 'QICE', 'QSNOW', 'QGRAUP', 'QNICE') ; + + call get_var_3d_real_cdf( wrf_input_current_file, trim(var3d(n)), full3d, & + dims(1), dims(2), dims(3), 1, debug ) + + call get_var_3d_real_cdf( wrf_input_next_file, trim(var3d(n)), full3d_next, & + dims(1), dims(2), dims(3), 1, debug ) + + if(debug) then + write(unit=*, fmt='(3a,e20.12,4x)') & + 'Before couple Sampe ', trim(var3d(n)), & + '=', full3d(dims(1)/2,dims(2)/2,dims(3)/2) + endif + + do k=1,dims(3) + do j=1,dims(2) + do i=1,dims(1) + full3d(i,j,k)=full3d(i,j,k)*(mu(i,j)+mub(i,j)) + full3d_next(i,j,k)=full3d_next(i,j,k)*(mu_next(i,j)+mub(i,j)) + enddo + enddo + enddo + + if(debug) then + write(unit=*, fmt='(3a,e20.12,4x)') & + 'After couple Sampe ', trim(var3d(n)), & + '=', full3d(dims(1)/2,dims(2)/2,dims(3)/2) + endif + case default ; + print *, 'It is impossible here. var3d(', n, ')=', trim(var3d(n)) + end select + + do m=1,4 + var_name=trim(var3d(n)) // trim(bdyname(m)) + call get_dims_cdf( wrf_bdy_file, trim(var_name), dims, ndims, debug ) + + allocate(frst3d(dims(1), dims(2), dims(3))) + allocate(scnd3d(dims(1), dims(2), dims(3))) + allocate(tend3d(dims(1), dims(2), dims(3))) + +!-----Assign new BC values + select case(trim(bdyname(m))) + case ('_BXS') ; ! West boundary + do l=1,dims(3) + do k=1,dims(2) + do j=1,dims(1) + frst3d(j,k,l) = full3d(l,j,k) + scnd3d(j,k,l) = full3d_next(l,j,k) + enddo + enddo + enddo + case ('_BXE') ; ! East boundary + do l=1,dims(3) + do k=1,dims(2) + do j=1,dims(1) + frst3d(j,k,l) = full3d(east_end-l,j,k) + scnd3d(j,k,l) = full3d_next(east_end-l,j,k) + enddo + enddo + enddo + case ('_BYS') ; ! South boundary + do l=1,dims(3) + do k=1,dims(2) + do i=1,dims(1) + frst3d(i,k,l) = full3d(i,l,k) + scnd3d(i,k,l) = full3d_next(i,l,k) + enddo + enddo + enddo + case ('_BYE') ; ! North boundary + do l=1,dims(3) + do k=1,dims(2) + do i=1,dims(1) + frst3d(i,k,l) = full3d(i,north_end-l,k) + scnd3d(i,k,l) = full3d_next(i,north_end-l,k) + enddo + enddo + enddo + case default ; + write(msgstring, *)'impossible bdyname(', m, ')=', trim(bdyname(m)) + call error_handler(E_ERR,'pert_wrf_bc',msgstring,source,revision,revdate) + end select + +!--------Make sure that microphysics variables are not negatives. + + if(n >= 6) then + frst3d(:,:,:) = max(0.0_r8,frst3d(:,:,:)) + scnd3d(:,:,:) = max(0.0_r8,scnd3d(:,:,:)) + endif + +!--------calculate new tendency + + tend3d = (scnd3d - frst3d)/bdyfrq + + if(debug) then + write(unit=*, fmt='(a,i2,2x,2a/3(a,e20.12,4x)/a,i2,2x,a,4i6)') & + 'No.', n, 'Variable: ', trim(var_name), & + 'Sampe o frst3d=', frst3d(dims(1)/2,dims(2)/2,dims(3)/2), & + 'scnd3d=', scnd3d(dims(1)/2,dims(2)/2,dims(3)/2), & + 'tend3d=', tend3d(dims(1)/2,dims(2)/2,dims(3)/2), & + 'ndims=', ndims, 'dims=', (dims(i), i=1,ndims) + endif + +!-----output new variable at first time level + var_name=trim(var3d(n)) // trim(bdyname(m)) + call put_var_3d_real_cdf( wrf_bdy_file, trim(var_name), frst3d, & + dims(1), dims(2), dims(3), itime, debug ) + +!-----output new tendency + var_name=trim(var3d(n)) // trim(tenname(m)) + call put_var_3d_real_cdf( wrf_bdy_file, trim(var_name), tend3d, & + dims(1), dims(2), dims(3), itime, debug ) + + deallocate(frst3d) + deallocate(scnd3d) + deallocate(tend3d) + enddo + + deallocate(full3d) + deallocate(full3d_next) + + enddo loop3d + + deallocate(mu) + deallocate(mu_next) + deallocate(mub) + deallocate(msfu) + deallocate(msfv) + deallocate(msfm) + deallocate(u) + deallocate(v) + deallocate(w) + deallocate(u_next) + deallocate(v_next) + deallocate(w_next) + + + call error_handler(E_MSG,'pert_wrf_bc','pert_wrf_bc terminated normally.') + call error_handler(E_MSG,'pert_wrf_bc','FINISHED pert_wrf_bc.') + call error_handler(E_MSG,'pert_wrf_bc','Finished successfully.',source,revision,revdate) + call finalize_utilities() + + +end program pert_wrf_bc + +! +! $URL$ +! $Id$ +! $Revision$ +! $Date$ diff --git a/models/wrf_unified/WRF_BC/update_wrf_bc.f90 b/models/wrf_unified/WRF_BC/update_wrf_bc.f90 new file mode 100644 index 0000000000..cd78e5a84f --- /dev/null +++ b/models/wrf_unified/WRF_BC/update_wrf_bc.f90 @@ -0,0 +1,637 @@ +! DART software - Copyright UCAR. This open source software is provided +! by UCAR, "as is", without charge, subject to all terms of use at +! http://www.image.ucar.edu/DAReS/DART/DART_download +! +! $Id$ + +program update_wrf_bc + +! program to update BC file from 3dvar or filter output. +! current version reads only wrf-netcdf file format. + +! Input files: wrfinput_d01, wrfbdy_d01, and wrfinput_mean (optional). + +use types_mod, only : r8 +use utilities_mod, only : file_exist, open_file, close_file, & + initialize_utilities, finalize_utilities, register_module, & + error_handler, E_ERR, E_MSG, timestamp +use module_netcdf_interface, only : get_dims_cdf, get_gl_att_real_cdf, put_gl_att_real_cdf, & + get_var_3d_real_cdf, get_var_2d_real_cdf, put_var_3d_real_cdf, & + put_var_2d_real_cdf, get_times_cdf, put_time_cdf, variable_exist +use module_couple_uv +use module_timediff, only : time_diff, find_time_index + +implicit none + +! version controlled file description for error handling, do not edit +character(len=256), parameter :: source = & + "$URL$" +character(len=32 ), parameter :: revision = "$Revision$" +character(len=128), parameter :: revdate = "$Date$" + +!----------------------------------------------------------------------- +! Model namelist parameters with default values. +!----------------------------------------------------------------------- + +!----------------------------------------------------------------------- + +integer, parameter :: max_3d_variables = 20, & + max_2d_variables = 20, & + max_times = 100 + +character(len=80) :: wrf_output_file, wrf_mean_output_file, & + wrf_bdy_file, time_name + +character(len=20) :: var_name + +character(len=20) :: var3d(max_3d_variables), & + var2d(max_2d_variables) + +character(len=10), dimension(4) :: bdyname, tenname + +character(len=19), dimension(max_times) :: udtime, bdytime, thisbdytime, nextbdytime + +character(len=129) :: msgstring + +integer :: ids, ide, jds, jde, kds, kde +integer :: num3d, num2d, ndims, nmoist +integer :: i,j,k,l,m,n +integer :: ntimes_bdy, ntimes_ud, itime + +integer, dimension(4) :: dims + +real(r8), allocatable, dimension(:,:) :: tend2d, scnd2d, frst2d + +real(r8), allocatable, dimension(:,:,:) :: tend3d, scnd3d, frst3d, full3d, full3d_mean + +real(r8), allocatable, dimension(:,:,:) :: u, v, w +real(r8), allocatable, dimension(:,:,:) :: u_mean, v_mean, w_mean + +real(r8), allocatable, dimension(:, :) :: mu, mu_mean, mub, msfu, msfv, msfm + +integer :: east_end, north_end + +logical, parameter :: debug = .false. + +real(r8) :: bdyfrq_old, bdyfrq, infl + +!---------------------------------------------------------------------- + +call initialize_utilities('update_wrf_bc') +call register_module(source, revision, revdate) + +wrf_output_file='wrfinput_d01' +wrf_bdy_file ='wrfbdy_d01' + +if((.not. file_exist(wrf_output_file)) .or. (.not. file_exist(wrf_bdy_file))) then + write(msgstring, *)'wrfinput_d01 or wrfbdy_d01 is absent.' + call error_handler(E_ERR,'update_wrf_bc',msgstring,source,revision,revdate) +endif + +! If the file 'wrfinput_mean' exists, +! input the ensemble mean to calculate deviations from the mean. +! These perturbations are then added to the fields at the end of the interval. + +if(file_exist('wrfinput_mean')) then + wrf_mean_output_file='wrfinput_mean' + write(*,*) 'Input real coefficient multiplying (wrfinput_d01 - wrfinput_mean):' + write(*,*) 'Result will be added to the fields at the end of the interval.' + read(*,*) infl +else + wrf_mean_output_file='wrfinput_d01' + infl = 0.0_r8 +endif + +!--boundary variables +bdyname(1)='_BXS' +bdyname(2)='_BXE' +bdyname(3)='_BYS' +bdyname(4)='_BYE' + +!--boundary tendency variables +tenname(1)='_BTXS' +tenname(2)='_BTXE' +tenname(3)='_BTYS' +tenname(4)='_BTYE' + +!--3D need update +nmoist = 7 +num3d = 5 + nmoist +var3d(1)='U' +var3d(2)='V' +var3d(3)='W' +var3d(4)='PH' +var3d(5)='T' +var3d(6)='QVAPOR' +var3d(7)='QCLOUD' +var3d(8)='QRAIN' +var3d(9)='QICE' +var3d(10)='QSNOW' +var3d(11)='QGRAUP' +var3d(12)='QNICE' + +!--2D need update +num2d=5 +var2d(1)='MUB' +var2d(2)='MU' +var2d(3)='MAPFAC_U' +var2d(4)='MAPFAC_V' +var2d(5)='MAPFAC_M' + +!--------------------------------------------------------------------- + +east_end=0 +north_end=0 + +!--------------------------------------------------------------------- +!-- Current time in file +time_name = 'Times' +call get_times_cdf( wrf_output_file, time_name, udtime, ntimes_ud, max_times, debug ) + +if(debug) print*, 'udtime = ',udtime(1) + +!-- list of boundary times +call get_times_cdf( wrf_bdy_file, time_name, bdytime, ntimes_bdy, max_times, debug ) + +!-- Time index of current tendency - to grab from wrfbdy +call find_time_index(bdytime, udtime(1), ntimes_bdy, itime) + +if(debug) print*, 'itime = ',itime +if(debug) print*, 'bdytime = ',bdytime(itime) + +!--------------------------------------------------------------------- +!--First, the boundary frequency. +time_name = 'md___thisbdytimee_x_t_d_o_m_a_i_n_m_e_t_a_data_' +call get_times_cdf( wrf_bdy_file, time_name, & + thisbdytime, ntimes_bdy, max_times, debug ) +time_name = 'md___nextbdytimee_x_t_d_o_m_a_i_n_m_e_t_a_data_' +call get_times_cdf( wrf_bdy_file, time_name, & + nextbdytime, ntimes_bdy, max_times, debug ) +call time_diff( thisbdytime(itime), nextbdytime(itime), bdyfrq_old ) + +if(debug) then + write(unit=*, fmt='(a, f12.2)') & + 'BDYFRQ=', bdyfrq_old +endif +!-- time diff between this time and most recent (to shorten bdyfrq) +call time_diff( udtime(1), nextbdytime(itime), bdyfrq ) + +if(debug) print*, 'New bdyfrq = ',bdyfrq + +!-- put in the new thisbdytime +thisbdytime(itime) = udtime(1) +time_name = 'md___thisbdytimee_x_t_d_o_m_a_i_n_m_e_t_a_data_' +call put_time_cdf( wrf_bdy_file, time_name, & + thisbdytime(itime), itime, debug ) + +!--For 2D variables +!--Get mu, mub, msfu, msfv, and msfm + +do n=1,num2d + call get_dims_cdf( wrf_output_file, trim(var2d(n)), dims, ndims, debug ) + + select case(trim(var2d(n))) + case ('MU') ; + allocate(mu(dims(1), dims(2))) + + call get_var_2d_real_cdf( wrf_output_file, trim(var2d(n)), mu, & + dims(1), dims(2), 1, debug ) + + allocate(mu_mean(dims(1), dims(2))) + + call get_var_2d_real_cdf( wrf_mean_output_file, trim(var2d(n)), mu_mean, & + dims(1), dims(2), 1, debug ) + + east_end=dims(1)+1 + north_end=dims(2)+1 + case ('MUB') ; + allocate(mub(dims(1), dims(2))) + + call get_var_2d_real_cdf( wrf_output_file, trim(var2d(n)), mub, & + dims(1), dims(2), 1, debug ) + case ('MAPFAC_U') ; + allocate(msfu(dims(1), dims(2))) + + call get_var_2d_real_cdf( wrf_output_file, trim(var2d(n)), msfu, & + dims(1), dims(2), 1, debug ) + + if(debug) then + do j=1,dims(2) + write(unit=*, fmt='(2(a,i5), a, f12.8)') & + 'msfu(', dims(1), ',', j, ')=', msfu(dims(1),j) + enddo + endif + + case ('MAPFAC_V') ; + allocate(msfv(dims(1), dims(2))) + + call get_var_2d_real_cdf( wrf_output_file, trim(var2d(n)), msfv, & + dims(1), dims(2), 1, debug ) + + if(debug) then + do i=1,dims(1) + write(unit=*, fmt='(2(a,i5), a, f12.8)') & + 'msfv(', i, ',', dims(2), ')=', msfv(i,dims(2)) + enddo + endif + + case ('MAPFAC_M') ; + allocate(msfm(dims(1), dims(2))) + + call get_var_2d_real_cdf( wrf_output_file, trim(var2d(n)), msfm, & + dims(1), dims(2), 1, debug ) + + if(debug) then + do i=1,dims(1) + write(unit=*, fmt='(2(a,i5), a, f12.8)') & + 'msfm(', i, ',', dims(2), ')=', msfm(i,dims(2)) + enddo + endif + + case default ; + print *, 'It is impossible here. var2d(n)=', trim(var2d(n)) + end select +enddo + +if(debug) then + write(unit=*, fmt='(2(2x,a,e20.12)/2x,a,i2,2x,a,4i6)') & + 'Sample mu =', mu(dims(1)/2,dims(2)/2), & + 'Sample mub=', mub(dims(1)/2,dims(2)/2), & + 'ndims=', ndims, 'dims=', (dims(i), i=1,ndims) +endif + +if(east_end < 1 .or. north_end < 1) then + write(unit=*, fmt='(a)') 'Wrong data for Boundary.' + stop +endif + +!--------------------------------------------------------------------- + + do m=1,4 + var_name='MU' // trim(bdyname(m)) + + call get_dims_cdf( wrf_bdy_file, trim(var_name), dims, ndims, debug ) + + allocate(frst2d(dims(1), dims(2))) + allocate(scnd2d(dims(1), dims(2))) + allocate(tend2d(dims(1), dims(2))) + +!-----Calculate variable at second time level +!--------Get variable tendency + var_name='MU' // trim(tenname(m)) + call get_var_2d_real_cdf( wrf_bdy_file, trim(var_name), tend2d, & + dims(1), dims(2), itime, debug ) + +!--------Get variable at first time level + var_name='MU' // trim(bdyname(m)) + call get_var_2d_real_cdf( wrf_bdy_file, trim(var_name), frst2d, & + dims(1), dims(2), itime, debug ) + + scnd2d = tend2d*bdyfrq_old + frst2d + +!--------Get variable at second time level +! call get_var_2d_real_cdf( wrf_bdy_file, trim(var_name), scnd2d, & +! dims(1), dims(2), 2, debug ) + + + +!-----Add BC perturbation at second time level + select case(m) + case (1) ; ! West boundary + do l=1,dims(2) + do j=1,dims(1) + frst2d(j,l)=mu(l,j) + scnd2d(j,l) = scnd2d(j,l) + infl*(mu(l,j)-mu_mean(l,j)) + enddo + enddo + case (2) ; ! East boundary + do l=1,dims(2) + do j=1,dims(1) + frst2d(j,l)=mu(east_end-l,j) + scnd2d(j,l) = scnd2d(j,l) + infl*(mu(east_end-l,j)-mu_mean(east_end-l,j)) + enddo + enddo + case (3) ; ! South boundary + do l=1,dims(2) + do i=1,dims(1) + frst2d(i,l)=mu(i,l) + scnd2d(i,l) = scnd2d(i,l) + infl*(mu(i,l)-mu_mean(i,l)) + enddo + enddo + case (4) ; ! North boundary + do l=1,dims(2) + do i=1,dims(1) + frst2d(i,l)=mu(i,north_end-l) + scnd2d(i,l) = scnd2d(i,l) + infl*(mu(i,north_end-l)-mu_mean(i,north_end-l)) + enddo + enddo + case default ; + print *, 'It is impossible here. mu, m=', m + end select + +!-----calculate new tendency + + tend2d = (scnd2d - frst2d)/bdyfrq + + if(debug) then + write(unit=*, fmt='(a,i2,2x,2a/3(a,e20.12,4x)/a,i2,2x,a,4i6)') & + 'No.', n, 'Variable: ', trim(var_name), & + 'Sampe frst2d=', frst2d(dims(1)/2,dims(2)/2), & + 'scnd2d=', scnd2d(dims(1)/2,dims(2)/2), & + 'tend2d=', tend2d(dims(1)/2,dims(2)/2), & + 'ndims=', ndims, 'dims=', (dims(i), i=1,ndims) + endif + +!-----output new variable at first time level + var_name='MU' // trim(bdyname(m)) + call put_var_2d_real_cdf( wrf_bdy_file, trim(var_name), frst2d, & + dims(1), dims(2), itime, debug ) +!-----output new tendency + var_name='MU' // trim(tenname(m)) + call put_var_2d_real_cdf( wrf_bdy_file, trim(var_name), tend2d, & + dims(1), dims(2), itime, debug ) + + deallocate(frst2d) + deallocate(scnd2d) + deallocate(tend2d) + enddo + +!--------------------------------------------------------------------- +!--For 3D variables + +!--Get U + call get_dims_cdf( wrf_output_file, 'U', dims, ndims, debug ) + + allocate(u(dims(1), dims(2), dims(3))) + + ids=1 + ide=dims(1)-1 + jds=1 + jde=dims(2) + kds=1 + kde=dims(3) + + call get_var_3d_real_cdf( wrf_output_file, 'U', u, & + dims(1), dims(2), dims(3), 1, debug ) + + allocate(u_mean(dims(1), dims(2), dims(3))) + + call get_var_3d_real_cdf( wrf_mean_output_file, 'U', u_mean, & + dims(1), dims(2), dims(3), 1, debug ) + + if(debug) then + do j=1,dims(2) + write(unit=*, fmt='(2(a,i5), a, f12.8)') & + 'u(', dims(1), ',', j, ',1)=', u(dims(1),j,1) + enddo + endif + +!--Get V + call get_dims_cdf( wrf_output_file, 'V', dims, ndims, debug ) + + allocate(v(dims(1), dims(2), dims(3))) + + call get_var_3d_real_cdf( wrf_output_file, 'V', v, & + dims(1), dims(2), dims(3), 1, debug ) + + allocate(v_mean(dims(1), dims(2), dims(3))) + + call get_var_3d_real_cdf( wrf_mean_output_file, 'V', v_mean, & + dims(1), dims(2), dims(3), 1, debug ) + + if(debug) then + do i=1,dims(1) + write(unit=*, fmt='(2(a,i5), a, f12.8)') & + 'v(', i, ',', dims(2), ',1)=', v(i,dims(2),1) + enddo + endif + +!--Get W + call get_dims_cdf( wrf_output_file, 'W', dims, ndims, debug ) + + allocate(w(dims(1), dims(2), dims(3))) + + call get_var_3d_real_cdf( wrf_output_file, 'W', w, & + dims(1), dims(2), dims(3), 1, debug ) + + allocate(w_mean(dims(1), dims(2), dims(3))) + + call get_var_3d_real_cdf( wrf_mean_output_file, 'W', w_mean, & + dims(1), dims(2), dims(3), 1, debug ) + + if(debug) then + do i=1,dims(1) + write(unit=*, fmt='(2(a,i5), a, f12.8)') & + 'w(', i, ',', dims(2), ',1)=', w(i,dims(2),1) + enddo + endif + + if(debug) then + write(unit=*, fmt='(a,e20.12,4x)') & + 'Before couple Sampe u=', u(dims(1)/2,dims(2)/2,dims(3)/2), & + 'Before couple Sampe v=', v(dims(1)/2,dims(2)/2,dims(3)/2), & + 'Before couple Sampe w=', w(dims(1)/2,dims(2)/2,dims(3)/2) + endif + +!--------------------------------------------------------------------- +!--Couple u, v, w. + call couple_uvw ( u, v, w, mu, mub, msfu, msfv, msfm, ids, ide, jds, jde, kds, kde ) + + call couple_uvw ( u_mean, v_mean, w_mean, mu_mean, mub, msfu, msfv, msfm, ids, ide, jds, jde, kds, kde ) + + if(debug) then + write(unit=*, fmt='(a,e20.12,4x)') & + 'After couple Sampe u=', u(dims(1)/2,dims(2)/2,dims(3)/2), & + 'After couple Sampe v=', v(dims(1)/2,dims(2)/2,dims(3)/2) + endif + +!--------------------------------------------------------------------- +!--For 3D variables + + loop3d : do n=1,num3d + + if ( .not. variable_exist(wrf_output_file, trim(var3d(n))) ) cycle loop3d + + call get_dims_cdf( wrf_output_file, trim(var3d(n)), dims, ndims, debug ) + + allocate(full3d(dims(1), dims(2), dims(3))) + + allocate(full3d_mean(dims(1), dims(2), dims(3))) + + east_end=dims(1)+1 + north_end=dims(2)+1 + + select case(trim(var3d(n))) + case ('U') ; ! U + full3d(:,:,:)=u(:,:,:) + full3d_mean(:,:,:)=u_mean(:,:,:) + case ('V') ; ! V + full3d(:,:,:)=v(:,:,:) + full3d_mean(:,:,:)=v_mean(:,:,:) + case ('W') ; ! W + full3d(:,:,:)=w(:,:,:) + full3d_mean(:,:,:)=w_mean(:,:,:) + case ('T', 'PH', 'QVAPOR', 'QCLOUD', 'QRAIN', 'QICE', 'QSNOW', 'QGRAUP', 'QNICE') ; + + call get_var_3d_real_cdf( wrf_output_file, trim(var3d(n)), full3d, & + dims(1), dims(2), dims(3), 1, debug ) + + call get_var_3d_real_cdf( wrf_mean_output_file, trim(var3d(n)), full3d_mean, & + dims(1), dims(2), dims(3), 1, debug ) + + if(debug) then + write(unit=*, fmt='(3a,e20.12,4x)') & + 'Before couple Sampe ', trim(var3d(n)), & + '=', full3d(dims(1)/2,dims(2)/2,dims(3)/2) + endif + + do k=1,dims(3) + do j=1,dims(2) + do i=1,dims(1) + full3d(i,j,k)=full3d(i,j,k)*(mu(i,j)+mub(i,j)) + full3d_mean(i,j,k)=full3d_mean(i,j,k)*(mu_mean(i,j)+mub(i,j)) + enddo + enddo + enddo + + if(debug) then + write(unit=*, fmt='(3a,e20.12,4x)') & + 'After couple Sampe ', trim(var3d(n)), & + '=', full3d(dims(1)/2,dims(2)/2,dims(3)/2) + endif + case default ; + print *, 'It is impossible here. var3d(', n, ')=', trim(var3d(n)) + end select + + do m=1,4 + var_name=trim(var3d(n)) // trim(bdyname(m)) + call get_dims_cdf( wrf_bdy_file, trim(var_name), dims, ndims, debug ) + + allocate(frst3d(dims(1), dims(2), dims(3))) + allocate(scnd3d(dims(1), dims(2), dims(3))) + allocate(tend3d(dims(1), dims(2), dims(3))) + +!-----Calculate variable at second time level +!--------Get variable tendency + var_name=trim(var3d(n)) // trim(tenname(m)) + call get_var_3d_real_cdf( wrf_bdy_file, trim(var_name), tend3d, & + dims(1), dims(2), dims(3), itime, debug ) + +!--------Get variable at first time level + var_name=trim(var3d(n)) // trim(bdyname(m)) + call get_var_3d_real_cdf( wrf_bdy_file, trim(var_name), frst3d, & + dims(1), dims(2), dims(3), itime, debug ) + + scnd3d = tend3d*bdyfrq_old + frst3d + +!--------Get variable at second time level +! call get_var_3d_real_cdf( wrf_bdy_file, trim(var_name), scnd3d, & +! dims(1), dims(2), dims(3), 2, debug ) + +!-----Add BC perturbation at second time level + select case(trim(bdyname(m))) + case ('_BXS') ; ! West boundary + do l=1,dims(3) + do k=1,dims(2) + do j=1,dims(1) + frst3d(j,k,l)=full3d(l,j,k) + scnd3d(j,k,l) = scnd3d(j,k,l) + infl*(full3d(l,j,k)-full3d_mean(l,j,k)) + enddo + enddo + enddo + case ('_BXE') ; ! East boundary + do l=1,dims(3) + do k=1,dims(2) + do j=1,dims(1) + frst3d(j,k,l)=full3d(east_end-l,j,k) + scnd3d(j,k,l) = scnd3d(j,k,l) + infl*(full3d(east_end-l,j,k)-full3d_mean(east_end-l,j,k)) + enddo + enddo + enddo + case ('_BYS') ; ! South boundary + do l=1,dims(3) + do k=1,dims(2) + do i=1,dims(1) + frst3d(i,k,l)=full3d(i,l,k) + scnd3d(i,k,l) = scnd3d(i,k,l) + infl*(full3d(i,l,k) - full3d_mean(i,l,k)) + enddo + enddo + enddo + case ('_BYE') ; ! North boundary + do l=1,dims(3) + do k=1,dims(2) + do i=1,dims(1) + frst3d(i,k,l)=full3d(i,north_end-l,k) + scnd3d(i,k,l) = scnd3d(i,k,l) + infl*(full3d(i,north_end-l,k)-full3d_mean(i,north_end-l,k)) + enddo + enddo + enddo + case default ; + print *, 'It is impossible here.' + print *, 'bdyname(', m, ')=', trim(bdyname(m)) + stop + end select + +!--------Make sure that microphysics variables at the end of the interval are not negatives. + + if(n >= 6) then + scnd3d(:,:,:) = max(0.0_r8,scnd3d(:,:,:)) + endif + +!--------calculate new tendency + + tend3d = (scnd3d - frst3d)/bdyfrq + + if(debug) then + write(unit=*, fmt='(a,i2,2x,2a/3(a,e20.12,4x)/a,i2,2x,a,4i6)') & + 'No.', n, 'Variable: ', trim(var_name), & + 'Sampe o frst3d=', frst3d(dims(1)/2,dims(2)/2,dims(3)/2), & + 'scnd3d=', scnd3d(dims(1)/2,dims(2)/2,dims(3)/2), & + 'tend3d=', tend3d(dims(1)/2,dims(2)/2,dims(3)/2), & + 'ndims=', ndims, 'dims=', (dims(i), i=1,ndims) + endif + +!-----output new variable at first time level + var_name=trim(var3d(n)) // trim(bdyname(m)) + call put_var_3d_real_cdf( wrf_bdy_file, trim(var_name), frst3d, & + dims(1), dims(2), dims(3), itime, debug ) + +!-----output new tendency + var_name=trim(var3d(n)) // trim(tenname(m)) + call put_var_3d_real_cdf( wrf_bdy_file, trim(var_name), tend3d, & + dims(1), dims(2), dims(3), itime, debug ) + + deallocate(frst3d) + deallocate(scnd3d) + deallocate(tend3d) + enddo + + deallocate(full3d) + deallocate(full3d_mean) + enddo loop3d + + deallocate(mu) + deallocate(mu_mean) + deallocate(mub) + deallocate(msfu) + deallocate(msfv) + deallocate(msfm) + deallocate(u) + deallocate(v) + deallocate(w) + deallocate(u_mean) + deallocate(v_mean) + deallocate(w_mean) + + call error_handler(E_MSG, 'update_wrf_bc', 'update_wrf_bc terminated normally.') + call error_handler(E_MSG, 'update_wrf_bc', 'FINISHED update_wrf_bc.') + call error_handler(E_MSG, 'update_wrf_bc', 'Finished successfully.',source,revision,revdate) + call finalize_utilities() + +end program update_wrf_bc + +! +! $URL$ +! $Id$ +! $Revision$ +! $Date$ diff --git a/models/wrf_unified/WRF_DART_utilities/add_pert_where_high_refl.f90 b/models/wrf_unified/WRF_DART_utilities/add_pert_where_high_refl.f90 new file mode 100644 index 0000000000..ae4a0d228f --- /dev/null +++ b/models/wrf_unified/WRF_DART_utilities/add_pert_where_high_refl.f90 @@ -0,0 +1,710 @@ +! DART software - Copyright UCAR. This open source software is provided +! by UCAR, "as is", without charge, subject to all terms of use at +! http://www.image.ucar.edu/DAReS/DART/DART_download +! +! $Id$ + +PROGRAM add_pert_where_high_refl + +! Add 3D random but smooth perturbations to WRF fields in/near where the observations +! indicate high reflectivity values. The technique is somewhat like that of +! Caya et al. 2005, Monthly Weather Review, 3081-3094. +! +! David Dowell 27 June 2007 +! +! input parameters from command line: +! (1) refl_ob_file -- name of text file containing WRF grid indices where observed reflectivity is high +! (2) wrf_file -- path name of WRF netcdf file +! (3) lh -- horizontal length scale (m) for perturbations +! (4) lv -- vertical length scale (m) for perturbations +! (5) u_sd -- std. dev. of u noise (m/s), before smoothing +! (6) v_sd -- std. dev. of v noise (m/s), before smoothing +! (7) w_sd -- std. dev. of w noise (m/s), before smoothing +! (8) t_sd -- std. dev. of potential temperature noise (K), before smoothing +! (9) td_sd -- std. dev. of dewpoint noise (K), before smoothing +! (10) qv_sd -- std. dev. of water vapor mixing ratio noise, before smoothing +! (input value is in g/kg, value after conversion is in kg/kg) +! (11) ens_num -- ensemble number for consistently seeding the random number generator +! (12) gdays -- gregorian day number of wrf analysis time +! (13) gsecs -- gregorian seconds of day of wrf analysis time +! +! output: + +use types_mod, only : r8, gravity, t_kelvin, ps0, gas_constant, gas_constant_v +use utilities_mod, only : error_handler, E_ERR, initialize_utilities, finalize_utilities +use random_seq_mod, only : random_gaussian, random_seq_type, init_random_seq +use netcdf + +implicit none + +! version controlled file description for error handling, do not edit +character(len=256), parameter :: source = & + "$URL$" +character(len=32 ), parameter :: revision = "$Revision$" +character(len=128), parameter :: revdate = "$Date$" + +! command-line parameters +character(len=129) :: refl_ob_file +character(len=129) :: wrf_file +real(r8) :: lh +real(r8) :: lv +real(r8) :: u_sd +real(r8) :: v_sd +real(r8) :: w_sd +real(r8) :: t_sd +real(r8) :: td_sd +real(r8) :: qv_sd +integer :: ens_num +integer :: gdays +integer :: gsecs + +! local variables +integer :: n_obs ! number of gridded reflectivity observations +integer, allocatable :: i_ob(:) ! grid indices of observations +integer, allocatable :: j_ob(:) ! " " +integer, allocatable :: k_ob(:) ! " " +real(r8), allocatable :: refl_ob(:) ! observed reflectivity (dBZ) + +real(r8), allocatable :: phb(:,:,:) ! base-state geopotential (m^2 s^-2) +real(r8), allocatable :: ht(:,:,:) ! height MSL of mass grid points (m) +real(r8), allocatable :: ht_u(:,:,:) ! height MSL of u grid points (m) +real(r8), allocatable :: ht_v(:,:,:) ! height MSL of v grid points (m) +real(r8), allocatable :: ht_w(:,:,:) ! height MSL of w grid points (m) +real(r8), allocatable :: f(:,:,:) ! WRF field +real(r8), allocatable :: f2(:,:,:) ! Extra WRF field +real(r8), allocatable :: sd(:,:,:) ! standard deviations of grid-point noise + +real(r8), allocatable :: dnw(:) ! d(eta) values between full (w) levels +real(r8), allocatable :: ph(:,:,:) ! perturbation geopotential (m^2 s^-2) +real(r8), allocatable :: qv(:,:,:) ! water vapor (kg/kg) +real(r8), allocatable :: t(:,:,:) ! perturbation potential temperature (K) +real(r8) :: rho ! density (kg m^-3) +real(r8), allocatable :: mu(:,:) ! perturbation dry air mass in column (Pa) +real(r8), allocatable :: mub(:,:) ! base state dry air mass in column (Pa) +real(r8) :: ph_e +real(r8) :: qvf1 +real(r8), PARAMETER :: ts0 = 300.0_r8 ! Base potential temperature for all levels. +real(r8), PARAMETER :: kappa = 2.0_r8/7.0_r8 ! gas_constant / cp +real(r8), PARAMETER :: rd_over_rv = gas_constant / gas_constant_v +real(r8), PARAMETER :: cpovcv = 1.4_r8 ! cp / (cp - gas_constant) +real(r8), allocatable :: p(:,:,:) ! pressure (mb) + + +real(r8) :: dx, dy ! horizontal grid spacings (m) +integer :: bt, sn, we ! WRF grid dimensions +integer :: i, j, k, o + +! netcdf stuff +integer :: var_id, ncid, ierr +character(len=80) :: varname + +! f2kcli stuff +integer :: status, length +character(len=120) :: string + +! random number generator stuff +type (random_seq_type) :: rs + + +call initialize_utilities('add_pert_where_high_refl') + + ! Get command-line parameters, using the F2KCLI interface. See f2kcli.f90 for details. + +if( COMMAND_ARGUMENT_COUNT() .ne. 13 ) then + print*, 'INCORRECT # OF ARGUMENTS ON COMMAND LINE: ', COMMAND_ARGUMENT_COUNT() + call exit(1) +else + + call GET_COMMAND_ARGUMENT(1,refl_ob_file,length,status) + if( status .ne. 0 ) then + print*, 'refl_ob_file NOT RETRIEVED FROM COMMAND LINE: ', status + call exit(1) + endif + + call GET_COMMAND_ARGUMENT(2,wrf_file,length,status) + if( status .ne. 0 ) then + print*, 'wrf_file NOT RETRIEVED FROM COMMAND LINE: ', status + call exit(1) + endif + + call GET_COMMAND_ARGUMENT(3,string,length,status) + if( status .ne. 0 ) then + print*, 'lh NOT RETRIEVED FROM COMMAND LINE: ', status + call exit(1) + else + read(string,*) lh + endif + + call GET_COMMAND_ARGUMENT(4,string,length,status) + if( status .ne. 0 ) then + print*, 'lv NOT RETRIEVED FROM COMMAND LINE: ', status + call exit(1) + else + read(string,*) lv + endif + + call GET_COMMAND_ARGUMENT(5,string,length,status) + if( status .ne. 0 ) then + print*, 'u_sd NOT RETRIEVED FROM COMMAND LINE: ', status + call exit(1) + else + read(string,*) u_sd + endif + + call GET_COMMAND_ARGUMENT(6,string,length,status) + if( status .ne. 0 ) then + print*, 'v_sd NOT RETRIEVED FROM COMMAND LINE: ', status + call exit(1) + else + read(string,*) v_sd + endif + + call GET_COMMAND_ARGUMENT(7,string,length,status) + if( status .ne. 0 ) then + print*, 'w_sd NOT RETRIEVED FROM COMMAND LINE: ', status + call exit(1) + else + read(string,*) w_sd + endif + + call GET_COMMAND_ARGUMENT(8,string,length,status) + if( status .ne. 0 ) then + print*, 't_sd NOT RETRIEVED FROM COMMAND LINE: ', status + call exit(1) + else + read(string,*) t_sd + endif + + call GET_COMMAND_ARGUMENT(9,string,length,status) + if( status .ne. 0 ) then + print*, 'td_sd NOT RETRIEVED FROM COMMAND LINE: ', status + call exit(1) + else + read(string,*) td_sd + endif + + call GET_COMMAND_ARGUMENT(10,string,length,status) + if( status .ne. 0 ) then + print*, 'qv_sd NOT RETRIEVED FROM COMMAND LINE: ', status + call exit(1) + else + read(string,*) qv_sd + endif + + call GET_COMMAND_ARGUMENT(11,string,length,status) + if( status .ne. 0 ) then + print*, 'ens_num NOT RETRIEVED FROM COMMAND LINE: ', status + call exit(1) + else + read(string,*) ens_num + endif + + call GET_COMMAND_ARGUMENT(12,string,length,status) + if( status .ne. 0 ) then + print*, 'gdays NOT RETRIEVED FROM COMMAND LINE: ', status + call exit(1) + else + read(string,*) gdays + endif + + call GET_COMMAND_ARGUMENT(13,string,length,status) + if( status .ne. 0 ) then + print*, 'gsecs NOT RETRIEVED FROM COMMAND LINE: ', status + call exit(1) + else + read(string,*) gsecs + endif + +endif + +qv_sd = 0.001_r8 * qv_sd ! convert g/kg to kg/kg + + +! Read locations where high reflectivity was observed. + +! first, count observations + +open(unit=11, file=refl_ob_file, status='old') +n_obs = 0 +998 read(11,*,end=999) + n_obs = n_obs + 1 +go to 998 +999 close(11) + +! now allocate storage and read the observations + +allocate(i_ob(n_obs)) +allocate(j_ob(n_obs)) +allocate(k_ob(n_obs)) +allocate(refl_ob(n_obs)) + +open(unit=11, file=refl_ob_file, status='old') +do o=1, n_obs + read(11,*) i_ob(o), j_ob(o), k_ob(o), refl_ob(o) +enddo +close(11) + + +! Open WRF file and obtain miscellaneous values. + +call check ( nf90_open(wrf_file, NF90_WRITE, ncid) ) + +call check ( nf90_inq_dimid(ncid, 'bottom_top', var_id) ) +call check ( nf90_inquire_dimension(ncid, var_id, varname, bt) ) + +call check ( nf90_inq_dimid(ncid, 'south_north', var_id) ) +call check ( nf90_inquire_dimension(ncid, var_id, varname, sn) ) + +call check ( nf90_inq_dimid(ncid, 'west_east', var_id) ) +call check ( nf90_inquire_dimension(ncid, var_id, varname, we) ) + +call check( nf90_get_att(ncid, nf90_global, 'DX', dx) ) +call check( nf90_get_att(ncid, nf90_global, 'DY', dy) ) + + +! Read WRF base-state geopotential height field and compute height (m MSL) +! of each grid point. + +allocate(phb(we,sn,bt+1)) +allocate(ht(we,sn,bt)) +allocate(ht_u(we+1,sn,bt)) +allocate(ht_v(we,sn+1,bt)) +allocate(ht_w(we,sn,bt+1)) + +call check ( nf90_inq_varid(ncid, 'PHB', var_id)) +call check ( nf90_get_var(ncid, var_id, phb, start = (/ 1, 1, 1, 1/))) + +do k=1, bt + do j=1, sn + do i=1, we + ht(i,j,k) = ( phb(i,j,k) + phb(i,j,k+1) ) / (2.0_r8*gravity) + enddo + enddo +enddo +do k=1, bt + do j=1, sn + do i=2, we + ht_u(i,j,k) = ( phb(i-1,j,k) + phb(i-1,j,k+1) + phb(i,j,k) + phb(i,j,k+1) ) / (4.0_r8*gravity) + enddo + ht_u(1,j,k) = ht_u(2,j,k) + ht_u(we+1,j,k) = ht_u(we,j,k) + enddo +enddo +do k=1, bt + do i=1, we + do j=2, sn + ht_v(i,j,k) = ( phb(i,j-1,k) + phb(i,j-1,k+1) + phb(i,j,k) + phb(i,j,k+1) ) / (4.0_r8*gravity) + enddo + ht_v(i,1,k) = ht_v(i,2,k) + ht_v(i,sn+1,k) = ht_v(i,sn,k) + enddo +enddo +do k=1, bt+1 + do j=1, sn + do i=1, we + ht_w(i,j,k) = phb(i,j,k) / gravity + enddo + enddo +enddo + + +! Initialize random number generator based on the analysis time and +! the ensemble number so repeated runs have reproducible values. +call init_random_seq(rs, (gdays + gsecs)*1000 + ens_num) + + +! Add perturbations. + +if (u_sd .gt. 0.0_r8) then + + allocate(f(we+1,sn,bt)) + call check ( nf90_inq_varid(ncid, 'U', var_id)) + call check ( nf90_get_var(ncid, var_id, f, start = (/ 1, 1, 1, 1/))) + allocate(sd(we+1,sn,bt)) + + sd(:,:,:) = 0.0_r8 + do o=1, n_obs + sd(i_ob(o), j_ob(o), k_ob(o)) = u_sd + sd(i_ob(o)+1, j_ob(o), k_ob(o)) = u_sd + enddo + + call add_smooth_perturbations(f, sd, we+1, sn, bt, lh, lv, dx, dy, ht_u) + + call check ( nf90_put_var(ncid, var_id, f, start = (/ 1, 1, 1, 1/))) + deallocate(f) + deallocate(sd) + +end if + + +if (v_sd .gt. 0.0_r8) then + + allocate(f(we,sn+1,bt)) + call check ( nf90_inq_varid(ncid, 'V', var_id)) + call check ( nf90_get_var(ncid, var_id, f, start = (/ 1, 1, 1, 1/))) + allocate(sd(we,sn+1,bt)) + + sd(:,:,:) = 0.0_r8 + do o=1, n_obs + sd(i_ob(o), j_ob(o), k_ob(o)) = v_sd + sd(i_ob(o), j_ob(o)+1, k_ob(o)) = v_sd + enddo + + call add_smooth_perturbations(f, sd, we, sn+1, bt, lh, lv, dx, dy, ht_v) + + call check ( nf90_put_var(ncid, var_id, f, start = (/ 1, 1, 1, 1/))) + deallocate(f) + deallocate(sd) + +end if + + +! note: Perturbing w is not advised currently because there is no enforcement +! that w perturbations should be small near the lower and upper boundaries. +if (w_sd .gt. 0.0_r8) then + + allocate(f(we,sn,bt+1)) + call check ( nf90_inq_varid(ncid, 'W', var_id)) + call check ( nf90_get_var(ncid, var_id, f, start = (/ 1, 1, 1, 1/))) + allocate(sd(we,sn,bt+1)) + + sd(:,:,:) = 0.0_r8 + do o=1, n_obs + sd(i_ob(o), j_ob(o), k_ob(o)) = w_sd + sd(i_ob(o), j_ob(o), k_ob(o)+1) = w_sd + enddo + + call add_smooth_perturbations(f, sd, we, sn, bt+1, lh, lv, dx, dy, ht_w) + + call check ( nf90_put_var(ncid, var_id, f, start = (/ 1, 1, 1, 1/))) + deallocate(f) + deallocate(sd) + +end if + + +if (t_sd .gt. 0.0_r8) then + + allocate(f(we,sn,bt)) + call check ( nf90_inq_varid(ncid, 'T', var_id)) + call check ( nf90_get_var(ncid, var_id, f, start = (/ 1, 1, 1, 1/))) + allocate(sd(we,sn,bt)) + + sd(:,:,:) = 0.0_r8 + do o=1, n_obs + sd(i_ob(o), j_ob(o), k_ob(o)) = t_sd + enddo + + call add_smooth_perturbations(f, sd, we, sn, bt, lh, lv, dx, dy, ht) + + call check ( nf90_put_var(ncid, var_id, f, start = (/ 1, 1, 1, 1/))) + deallocate(f) + deallocate(sd) + +end if + + +! note: Perturbing dewpoint can produce supersaturation. +if (td_sd .gt. 0.0_r8) then + + allocate(dnw(bt)) + allocate(ph(we,sn,bt+1)) + allocate(qv(we,sn,bt)) + allocate(t(we,sn,bt)) + allocate(mu(we,sn)) + allocate(mub(we,sn)) + allocate(p(we,sn,bt)) + call check ( nf90_inq_varid(ncid, 'DNW', var_id)) + call check ( nf90_get_var(ncid, var_id, dnw, start = (/ 1, 1/))) + call check ( nf90_inq_varid(ncid, 'PH', var_id)) + call check ( nf90_get_var(ncid, var_id, ph, start = (/ 1, 1, 1, 1/))) + call check ( nf90_inq_varid(ncid, 'QVAPOR', var_id)) + call check ( nf90_get_var(ncid, var_id, qv, start = (/ 1, 1, 1, 1/))) + call check ( nf90_inq_varid(ncid, 'T', var_id)) + call check ( nf90_get_var(ncid, var_id, t, start = (/ 1, 1, 1, 1/))) + call check ( nf90_inq_varid(ncid, 'MU', var_id)) + call check ( nf90_get_var(ncid, var_id, mu, start = (/ 1, 1, 1/))) + call check ( nf90_inq_varid(ncid, 'MUB', var_id)) + call check ( nf90_get_var(ncid, var_id, mub, start = (/ 1, 1, 1/))) + allocate(f(we,sn,bt)) + allocate(f2(we,sn,bt)) +! f2 is the sensible temperature array + allocate(sd(we,sn,bt)) + + ! compute pressure + ! see model_rho_t and model_pressure_t functions in model_mod.f90 + do k=1, bt + do j=1, sn + do i=1, we + ph_e = ( (ph(i,j,k+1) + phb(i,j,k+1)) - (ph(i,j,k) + phb(i,j,k)) ) / dnw(k) + rho = - ( mub(i,j)+mu(i,j) ) / ph_e + qvf1 = 1.0_r8 + qv(i,j,k) / rd_over_rv + p(i,j,k) = 0.01_r8 * ps0 * ( (gas_constant*(ts0+t(i,j,k))*qvf1) / (ps0/rho) )**cpovcv + f2(i,j,k) = (ts0 + t(i,j,k))*(100.0*p(i,j,k)/ps0)**kappa + enddo + enddo + enddo + + ! compute dewpoint + do k=1, bt + do j=1, sn + do i=1, we + call compute_td(f(i,j,k), p(i,j,k), qv(i,j,k)) + enddo + enddo + enddo + + ! perturb dewpoint + sd(:,:,:) = 0.0_r8 + do o=1, n_obs + sd(i_ob(o), j_ob(o), k_ob(o)) = td_sd + enddo + + + call add_smooth_perturbations(f, sd, we, sn, bt, lh, lv, dx, dy, ht) + +! check to make sure the perturbed dewpoint is <= temperature + 4 K + do k=1, bt + do j=1, sn + do i=1, we + if ( f(i,j,k) .gt. f2(i,j,k)+4.0 ) then +! write(*,*) 'supersaturation violation i,j,k ', i, j, k, f(i,j,k), f2(i,j,k) + f(i,j,k) = f2(i,j,k)+4.0 + end if + enddo + enddo + enddo + + ! compute qv + do k=1, bt + do j=1, sn + do i=1, we + call compute_qv(qv(i,j,k), p(i,j,k), f(i,j,k)) + enddo + enddo + enddo + + call check ( nf90_inq_varid(ncid, 'QVAPOR', var_id)) + call check ( nf90_put_var(ncid, var_id, qv, start = (/ 1, 1, 1, 1/))) + deallocate(dnw) + deallocate(ph) + deallocate(qv) + deallocate(t) + deallocate(mu) + deallocate(mub) + deallocate(p) + deallocate(f) + deallocate(f2) + deallocate(sd) + +end if + + +! note: Since negative qv values are set to 0 after the perturbations are added, +! the following procedure produces a net increase in qv in the domain. +if (qv_sd .gt. 0.0_r8) then + + allocate(f(we,sn,bt)) + call check ( nf90_inq_varid(ncid, 'QVAPOR', var_id)) + call check ( nf90_get_var(ncid, var_id, f, start = (/ 1, 1, 1, 1/))) + allocate(sd(we,sn,bt)) + + sd(:,:,:) = 0.0_r8 + do o=1, n_obs + sd(i_ob(o), j_ob(o), k_ob(o)) = qv_sd + enddo + + call add_smooth_perturbations(f, sd, we, sn, bt, lh, lv, dx, dy, ht) + + f(:,:,:) = max(0.0_r8, f(:,:,:)) ! require qv to be nonnegative + + call check ( nf90_put_var(ncid, var_id, f, start = (/ 1, 1, 1, 1/))) + deallocate(f) + deallocate(sd) + +end if + + +! Close file and deallocate arrays. + +ierr = NF90_close(ncid) + +deallocate(i_ob) +deallocate(j_ob) +deallocate(k_ob) +deallocate(refl_ob) +deallocate(phb) +deallocate(ht) +deallocate(ht_u) +deallocate(ht_v) +deallocate(ht_w) + + +call finalize_utilities('add_pert_where_high_refl') + +contains + + + ! Internal subroutine - checks error status after each netcdf, prints + ! text message each time an error code is returned. + subroutine check(istatus) + integer, intent ( in) :: istatus + if(istatus /= nf90_noerr) call error_handler(E_ERR,'add_pert_where_high_refl', & + trim(nf90_strerror(istatus)), source, revision, revdate) + end subroutine check + +!------------------------------------------------------------------------------------ + + ! Compute dewpoint (in Kelvin) from the specified values of pressure and water vapor mixing ratio. + ! Author: David Dowell + ! Date: July 9, 2007 + + subroutine compute_td(td, p, qv) + implicit none + +!-- returned parameter + real(r8), intent(out) :: td ! dewpoint (K) + +!-- passed parameters + real(r8), intent(in) :: p ! pressure (mb) + real(r8), intent(in) :: qv ! water vapor mixing ratio (kg/kg) + +!-- local variables + real(r8) :: e ! water vapor pressure (mb) + real(r8) :: qv_ckd ! checked mixing ratio + real(r8), PARAMETER :: e_min = 0.001_r8 ! threshold for minimum vapor pressure (mb), + ! to avoid problems near zero in Bolton's equation + real(r8), PARAMETER :: qv_min = 1.0e-12 ! threshold for minimum water vapor mixing ratio + real(r8), PARAMETER :: qv_max = 0.050 ! threshold for maximum water vapor mixing ratio + +! ensure qv is a reasonable number +! qv_ckd = max (qv, qv_min) +! qv_ckd = min (qv_ckd, qv_max) + qv_ckd = qv + e = qv_ckd * p / (0.622_r8 + qv_ckd) ! vapor pressure + e = max(e, e_min) ! avoid problems near zero + td = t_kelvin + (243.5_r8 / ((17.67_r8 / log(e/6.112_r8)) - 1.0_r8) ) ! Bolton's approximation + + end subroutine compute_td + +!------------------------------------------------------------------------------------ + + ! Compute water vapor mixing ratio (in kg/kg) from the specified values of pressure and dewpoint. + ! Author: David Dowell + ! Date: July 9, 2007 + + subroutine compute_qv(qv, p, td) + implicit none + +!-- returned parameter + real(r8), intent(out) :: qv ! water vapor mixing ratio (kg/kg) + +!-- passed parameters + real(r8), intent(in) :: p ! pressure (mb) + real(r8), intent(in) :: td ! dewpoint (K) + +!-- local variables + real(r8) :: tdc ! dewpoint (Celsius) + real(r8) :: e ! water vapor pressure (mb) + + tdc = td - t_kelvin + e = 6.112_r8 * exp(17.67_r8 * tdc / (tdc+243.5_r8) ) ! Bolton's approximation + qv = 0.622_r8 * e / (p-e) + + return + + end subroutine compute_qv + +!------------------------------------------------------------------------------------ + + ! Add smooth perturbations to an array. The technique is based on + ! Caya et al. 2005, Monthly Weather Review, 3081-3094. + ! Author: David Dowell + ! Date: July 9, 2007 + + subroutine add_smooth_perturbations(f, sd, nx, ny, nz, lh, lv, dx, dy, ht) + implicit none + +!-- passed parameters + integer, intent(in) :: nx, ny, nz ! grid dimensions + real(r8), intent(in) :: lh, lv ! horizontal and vertical length scales (m) + real(r8), intent(in) :: dx, dy ! horizontal grid spacings (m) + real(r8), intent(in) :: ht(nx,ny,nz) ! heights MSL of f and sd grid points (m) + real(r8), intent(in) :: sd(nx,ny,nz) ! standard deviation of grid-point noise + +!-- passed and returned variable + real(r8), intent(inout) :: f(nx,ny,nz) ! field to be perturbed + +!-- local variables + + real(r8) :: r(nx,ny,nz) ! realization of random, normally distributed noise + integer :: i, i0, j, j0, k, k0 ! grid indices + integer :: i1, i2, j1, j2, k1, k2 ! more grid indices + real(r8) :: rlh, rlv ! reciprocals of lh and lv + integer, parameter :: nl = 5 ! number of length scales for computing exponential weight + + + rlh = 1.0_r8 / lh + rlv = 1.0_r8 / lv + +! generate random, normally-distributed gridpoint noise + + r(:,:,:) = 0.0_r8 + do k0=1, nz + do j0=1, ny + do i0=1, nx + if (sd(i0,j0,k0) .ne. 0.0_r8) then + r(i0,j0,k0) = random_gaussian(rs, 0.0_r8, sd(i0,j0,k0)) + endif + enddo + enddo + enddo + +! smooth the perturbations with an inverse exponential function + + do k0=1, nz + do j0=1, ny + do i0=1, nx + + if (r(i0,j0,k0).ne.0.0_r8) then + + i1 = max(1, nint(i0-nl*lh/dx)) + i2 = min(nx, nint(i0+nl*lh/dx)) + j1 = max(1, nint(j0-nl*lh/dy)) + j2 = min(ny, nint(j0+nl*lh/dy)) + k1 = k0 + do while ( (k1.gt.1) .and. ( ht(i0,j0,k1) .gt. (ht(i0,j0,k0)-nl*lv) ) ) + k1 = k1 - 1 + enddo + k2 = k0 + do while ( (k2.lt.nz) .and. ( ht(i0,j0,k2) .lt. (ht(i0,j0,k0)+nl*lv) ) ) + k2 = k2 + 1 + enddo + + do k=k1, k2 + do j=j1, j2 + do i=i1, i2 + f(i,j,k) = f(i,j,k) & + + r(i0,j0,k0)*exp( -dx*abs(i0-i)*rlh & + -dy*abs(j0-j)*rlh & + -abs(ht(i0,j0,k0)-ht(i,j,k))*rlv ) + enddo + enddo + enddo + + endif + + enddo + enddo + enddo + + end subroutine add_smooth_perturbations + + +END PROGRAM add_pert_where_high_refl + +! +! $URL$ +! $Id$ +! $Revision$ +! $Date$ diff --git a/models/wrf_unified/WRF_DART_utilities/advance_cymdh.f90 b/models/wrf_unified/WRF_DART_utilities/advance_cymdh.f90 new file mode 100644 index 0000000000..9cc291c316 --- /dev/null +++ b/models/wrf_unified/WRF_DART_utilities/advance_cymdh.f90 @@ -0,0 +1,128 @@ +! DART software - Copyright UCAR. This open source software is provided +! by UCAR, "as is", without charge, subject to all terms of use at +! http://www.image.ucar.edu/DAReS/DART/DART_download +! +! $Id$ + +program advance_cymdh + + implicit none + +! version controlled file description for error handling, do not edit +character(len=256), parameter :: source = & + "$URL$" +character(len=32 ), parameter :: revision = "$Revision$" +character(len=128), parameter :: revdate = "$Date$" + + integer :: ccyy, mm, dd, hh, dh + + integer :: nargum, i, n, sign + + character(len=80), dimension(2) :: argum + + character(len=10) :: ccyymmddhh + + nargum=COMMAND_ARGUMENT_COUNT() + + if(nargum /= 2) then + write(unit=*, fmt='(a)') & + 'Usage: advance_cymdh ccyymmddhh dh' + stop 'try again.' + endif + + do i=1,nargum + do n=1,80 + argum(i)(n:n)=' ' + enddo + call GET_COMMAND_ARGUMENT(i,argum(i)) + enddo + + ccyymmddhh = trim(argum(1)) + + read(ccyymmddhh(1:10), fmt='(i4, 3i2)') ccyy, mm, dd, hh + + sign = 1 + + dh = 0 + + do n=1,len_trim(argum(2)) + if(argum(2)(n:n) == '-') then + sign = -1 + cycle + else + read(argum(2)(n:n), fmt='(i1)') i + dh=10*dh + i + end if + enddo + + dh = sign * dh + + hh = hh + dh + + do while (hh < 0) + hh = hh + 24 + call change_date ( ccyy, mm, dd, -1 ) + end do + + do while (hh > 23) + hh = hh - 24 + call change_date ( ccyy, mm, dd, 1 ) + end do + + write(ccyymmddhh(1:10), fmt='(i4, 3i2.2)') ccyy, mm, dd, hh + write(unit=*, fmt='(a)') ccyymmddhh + +contains + + subroutine change_date( ccyy, mm, dd, delta ) + + implicit none + + integer, intent(inout) :: ccyy, mm, dd + integer, intent(in) :: delta + + integer, dimension(12) :: mmday + + mmday = (/31,28,31,30,31,30,31,31,30,31,30,31/) + + if (mod(ccyy,4) == 0) then + mmday(2) = 29 + + if ( mod(ccyy,100) == 0) then + mmday(2) = 28 + endif + + if(mod(ccyy,400) == 0) then + mmday(2) = 29 + end if + endif + + dd = dd + delta + + if(dd == 0) then + mm = mm - 1 + + if(mm == 0) then + mm = 12 + ccyy = ccyy - 1 + endif + + dd = mmday(mm) + elseif ( dd .gt. mmday(mm) ) then + dd = 1 + mm = mm + 1 + if(mm > 12 ) then + mm = 1 + ccyy = ccyy + 1 + end if + end if + + end subroutine change_date + +end program advance_cymdh + +! +! $URL$ +! $Id$ +! $Revision$ +! $Date$ diff --git a/models/wrf_unified/WRF_DART_utilities/convertdate.f90 b/models/wrf_unified/WRF_DART_utilities/convertdate.f90 new file mode 100644 index 0000000000..65b2716463 --- /dev/null +++ b/models/wrf_unified/WRF_DART_utilities/convertdate.f90 @@ -0,0 +1,108 @@ +! DART software - Copyright UCAR. This open source software is provided +! by UCAR, "as is", without charge, subject to all terms of use at +! http://www.image.ucar.edu/DAReS/DART/DART_download +! +! $Id$ + +PROGRAM convertdate + +use time_manager_mod + +implicit none + +! version controlled file description for error handling, do not edit +character(len=256), parameter :: source = & + "$URL$" +character(len=32 ), parameter :: revision = "$Revision$" +character(len=128), parameter :: revdate = "$Date$" + +integer :: direction +type(time_type) :: atime +integer :: year, month, day, hour, minute, second +integer :: jday +integer :: days, seconds + +! days prior to beginning of each month (non&) leap year + +integer, parameter, dimension( 13) :: & + bgn_day = (/ 0, 31, 59, 90, 120, 151, & + 181, 212, 243, 273, 304, 334, 365 /), & + bgn_day_ly = (/ 0, 31, 60, 91, 121, 152, & + 182, 213, 244, 274, 305, 335, 366 /) + + +! begin +write(6,*) 'Which direction? ' +write(6,*) 'YYYY MM DD HH MM SS ===> Gregorian day and second (1)' +write(6,*) 'YYYY MM DD HH MM SS <=== Gregorian day and second (2)' +write(6,*) 'YYYY MM DD ===> Julian day of year YYYY (3)' +write(6,*) 'YYYY MM DD <=== Julian day of year YYYY (4)' + +read(5,*) direction + +if ( direction == 1 ) then + write(*,*) 'Input YYYY MM DD HH MM SS: ' + read(*,*) year, month, day, hour, minute, second + atime=set_date_gregorian(year, month, day, hour, minute, second) + call get_time (atime, seconds, days) + write(*,*) 'Gregorian days and second: ', days, seconds + +else if ( direction == 2 ) then + write(*,*) 'Input Gregorian days and second: ' + read(*,*) days, seconds + atime = set_time(seconds, days) + call get_date_gregorian(atime, year, month, day, hour, minute, second) + write (*,FMT='(I4,5I3.2)') year, month, day, hour, minute, second + +else if ( direction == 3 ) then + write(*,*) 'Input YYYY MM DD: ' + read(*,*) year, month, day + if(isleapyear(year)) then + jday=bgn_day_ly(month)+day + else + jday=bgn_day(month)+day + endif + write(*,*) 'Julian day: ', year, jday + +else if ( direction == 4 ) then + write(*,*) 'Input Julian YYYY and JDAY: ' + read(*,*) year, jday + do month=1,12 + if(isleapyear(year)) then + if( (jday>bgn_day_ly(month)) .and. (jday<=bgn_day_ly(month+1)) ) then + day = jday - bgn_day_ly(month) + exit + endif + else + if( (jday>bgn_day(month)) .and. (jday<=bgn_day(month+1)) ) then + day = jday - bgn_day(month) + exit + endif + endif + enddo + write(*,*) year, month, day +endif + +contains + +function isleapyear(year) +! check if year is leapyear +implicit none +integer,intent(in) :: year +logical :: isleapyear + +if( mod(year,4) .ne. 0 ) then + isleapyear=.FALSE. +else + isleapyear=.TRUE. + if ( mod(year,100) == 0 .and. mod(year,400) .ne. 0 ) isleapyear=.FALSE. +endif +end function isleapyear + +end program convertdate + +! +! $URL$ +! $Id$ +! $Revision$ +! $Date$ diff --git a/models/wrf_unified/WRF_DART_utilities/ensemble_init.f90 b/models/wrf_unified/WRF_DART_utilities/ensemble_init.f90 new file mode 100644 index 0000000000..e2c8d82df8 --- /dev/null +++ b/models/wrf_unified/WRF_DART_utilities/ensemble_init.f90 @@ -0,0 +1,579 @@ +! DART software - Copyright UCAR. This open source software is provided +! by UCAR, "as is", without charge, subject to all terms of use at +! http://www.image.ucar.edu/DAReS/DART/DART_download +! +! $Id$ + +program ensemble_init + +! This program is out of date compared to the other utilities in this directory. +! I believe it could call the standard model_mod initialization routine and +! be updated to work with the existing files, but I'm unsure about what exactly +! it does. So for now, I'm leaving it alone - but it's not going to work +! correctly if used. nsc 16 feb 2010 + +! Prototype to initialize ensemble for WRF EnKF +! +! Reads: Ensemble size, Ne from stdin +! Ensemble mean from wrfinput_mean, wrfbdy_mean +! ith day from climo from wrfinput_i, wrfbdy_i +! (e.g. wrfinput_245) [i < 10^6] +! +! Computes: mean of i days from climo, ith deviation from mean +! IC's, LBC's for ith member = +! ensemble mean + scale * ith deviation +! +! Writes: ICs for ith member to wrfinput_i +! LBCs for ith member to wrfbdy_i +! [Note that contents of wrfinput_i, wrfbdy_i are OVERWRITTEN.] +! +! AT PRESENT, ith member inherits "auxilliary variables" (i.e. those +! not read by wrf_io below) from ith day from climo. To make sure +! that all mems have identical auxilliary variables, could copy +! wrfinput_mean to wrfinput_mem_i (i=1:Ne) before executing this +! program, and have this program write state variables for ith member +! to wrfinput_mem_i rather than overwriting wrfinput_i. +! + +use types_mod, only : r8 +use time_manager_mod, only : time_type, get_date, read_time, set_calendar_type, & + GREGORIAN, julian_day +use wrf_data_module, only : wrf_data, wrf_bdy_data, & + wrf_open_and_alloc, wrfbdy_open_and_alloc, & + wrf_dealloc, wrfbdy_dealloc, & + wrf_io, wrfbdy_io, & + set_wrf_date +use location_mod, only : location_type, get_location, set_location, & + VERTISHEIGHT +use utilities_mod, only : get_unit, file_exist, open_file, & + close_file, error_handler, E_ERR, E_MSG, initialize_utilities, & + register_module, logfileunit, nmlfileunit, & + find_namelist_in_file, check_namelist_read, finalize_utilities + +use netcdf + +implicit none + +! version controlled file description for error handling, do not edit +character(len=256), parameter :: source = & + "$URL$" +character(len=32 ), parameter :: revision = "$Revision$" +character(len=128), parameter :: revdate = "$Date$" + +! miscellaneous +integer, parameter :: max_state_variables = 100 +integer, parameter :: num_state_table_columns = 5 +integer, parameter :: num_bounds_table_columns = 4 + +!----------------------------------------------------------------------- +! Model namelist parameters with default values. +!----------------------------------------------------------------------- + +logical :: output_state_vector = .false. ! output prognostic variables +logical :: default_state_variables = .true. ! use default state list? +character(len=129) :: wrf_state_variables(num_state_table_columns,max_state_variables) = 'NULL' +character(len=129) :: wrf_state_bounds(num_bounds_table_columns,max_state_variables) = 'NULL' +integer :: num_moist_vars = 3 +integer :: num_domains = 1 +integer :: calendar_type = GREGORIAN +integer :: assimilation_period_seconds = 21600 +logical :: surf_obs = .true. +logical :: soil_data = .true. +logical :: h_diab = .false. +! Max height a surface obs can be away from the actual model surface +! and still be accepted (in meters) +real (kind=r8) :: sfc_elev_max_diff = -1.0_r8 ! could be something like 200.0_r8 +character(len = 72) :: adv_mod_command = './wrf.exe' +real (kind=r8) :: center_search_half_length = 500000.0_r8 +real(r8) :: circulation_pres_level = 80000.0_r8 +real(r8) :: circulation_radius = 108000.0_r8 +integer :: center_spline_grid_scale = 10 +integer :: vert_localization_coord = VERTISHEIGHT +! Allow (or not) observations above the surface but below the lowest sigma level. +logical :: allow_obs_below_vol = .false. +!nc -- we are adding these to the model.nml until they appear in the NetCDF files +logical :: polar = .false. ! wrap over the poles +logical :: periodic_x = .false. ! wrap in longitude or x +logical :: periodic_y = .false. ! used for single column model, wrap in y +!JPH -- single column model flag +logical :: scm = .false. ! using the single column model + +! JPH note that soil_data and h_diab are never used and can be removed. +namelist /model_nml/ output_state_vector, num_moist_vars, & + num_domains, calendar_type, surf_obs, soil_data, h_diab, & + default_state_variables, wrf_state_variables, & + wrf_state_bounds, sfc_elev_max_diff, & + adv_mod_command, assimilation_period_seconds, & + allow_obs_below_vol, vert_localization_coord, & + center_search_half_length, center_spline_grid_scale, & + circulation_pres_level, circulation_radius, polar, & + periodic_x, periodic_y, scm + +!----------------------------------------------------------------------- + +integer :: iunit, io, var_id, itime + +type(wrf_data) :: wrf, wrf_mean, wrf_tmp +type(wrf_bdy_data) :: wrf_bdy, wrf_bdy_mean, wrf_bdy_tmp + +real(r8) :: scale ! each deviation scaled by this amt + +character (len=6) :: imem +character (len=1) :: idom +logical :: debug = .false. +integer :: Ne, & ! Ensemble size + i, id + +type(time_type) :: dart_time(2) +integer :: year, month, day, hour, minute, second + +character(len=19) :: timestring + +read(*,*) Ne ! Read ensemble size from stdin. +read(*,*) scale ! Read scaling from stdin. + +call initialize_utilities('ensemble_init') +call register_module(source, revision, revdate) + +! FIXME: i believe this program should call the wrf static_model_init code +! to initialize the wrf static data, and then it doesn't have to read the +! wrf model_mod namelist. the num_moist_vars is no longer used, and there's +! an array of fields which are going to be used in the state vector that +! i believe should be used here instead. see wrf_to_dart and dart_to_wrf +! for now this program should be restructured. + +! Begin by reading the namelist input +call find_namelist_in_file("input.nml", "model_nml", iunit) +read(iunit, nml = model_nml, iostat = io) +call check_namelist_read(iunit, io, "model_nml") + +write(nmlfileunit, nml=model_nml) +write( * , nml=model_nml) + +call set_calendar_type(calendar_type) + +iunit = get_unit() +open(unit = iunit, file = 'wrf.info') +dart_time(1) = read_time(iunit) +dart_time(2) = read_time(iunit) +close(iunit) + +wrf%n_moist = num_moist_vars +wrf_mean%n_moist = num_moist_vars +wrf_tmp%n_moist = num_moist_vars +wrf_bdy%n_moist = num_moist_vars +wrf_bdy_mean%n_moist = num_moist_vars +wrf_bdy_tmp%n_moist = num_moist_vars + +wrf%surf_obs = surf_obs +wrf_mean%surf_obs = surf_obs +wrf_tmp%surf_obs = surf_obs + +wrf%h_diab = h_diab +wrf_mean%h_diab = h_diab +wrf_tmp%h_diab = h_diab + +!-- First, do BC's. + +call wrfbdy_open_and_alloc(wrf_bdy, 'wrfbdy_1', NF90_NOWRITE, debug ) +call check ( nf90_close(wrf_bdy%ncid) ) +call wrfbdy_open_and_alloc(wrf_bdy_mean, 'wrfbdy_mean', NF90_NOWRITE, debug ) +call wrfbdy_io( wrf_bdy_mean, "INPUT ", debug ) +call check ( nf90_close(wrf_bdy_mean%ncid) ) + +call wrfbdy_open_and_alloc(wrf_bdy_tmp , 'wrfbdy_mean', NF90_NOWRITE, debug ) +call check ( nf90_close(wrf_bdy_tmp%ncid) ) + +call wrfbdy_add( wrf_bdy_tmp, 0.0_r8, wrf_bdy_mean, 0.0_r8) + +!-- Compute mean over Ne input and bdy files +do i=1,Ne + + !- Open appropriate files + write( imem , '(I6)') i + + if(debug) write(*,*) ' OPENING wrfbdy_'//adjustl(trim(imem)) + call check ( nf90_open('wrfbdy_'//adjustl(trim(imem)), NF90_NOWRITE, wrf_bdy%ncid) ) + + !- Read data + call wrfbdy_io( wrf_bdy, "INPUT ", debug ) + + !- Close files + call check ( nf90_close(wrf_bdy%ncid) ) + + !- accumulate sum + call wrfbdy_add( wrf_bdy_tmp, 1.0_r8, wrf_bdy, 1.0_r8) + +enddo + +call wrfbdy_add( wrf_bdy_tmp, 1.0_r8/Ne, wrf_bdy, 0.0_r8) + +if (debug) write(*,*) ' --------------------' + +itime = 1 + +!-- Compute deviation from mean for each input file; overwrite input file with deviation +do i=1,Ne + + !- Open appropriate files + write( imem , '(I6)') i + if(debug) write(*,*) ' OPENING wrfbdy_'//adjustl(trim(imem)) + call check ( nf90_open('wrfbdy_'//adjustl(trim(imem)), NF90_NOWRITE, wrf_bdy%ncid) ) + + !- Read data, again + call wrfbdy_io( wrf_bdy, "INPUT ", debug ) + + !- Close files + call check ( nf90_close(wrf_bdy%ncid) ) + + !- deviation from mean over input files + call wrfbdy_add( wrf_bdy, 1.0_r8 , wrf_bdy_tmp, -1.0_r8 ) + + !- New IC: scaled deviation + chosen ensemble mean + call wrfbdy_add( wrf_bdy, scale , wrf_bdy_mean, 1.0_r8 ) + + !- Open same files for writing + if(debug) write(*,*) ' OPENING wrfbdy_'//adjustl(trim(imem))//' for WRITE' + call check ( nf90_open('wrfbdy_'//adjustl(trim(imem)), NF90_WRITE, wrf_bdy%ncid) ) + + !- Write bdy for ith member + if(debug) write(*,*) 'Write boundary conditions' + call wrfbdy_io( wrf_bdy, "OUTPUT", debug ) + + call check( nf90_inq_varid(wrf_bdy%ncid, 'md___thisbdytimee_x_t_d_o_m_a_i_n_m_e_t_a_data_', var_id) ) + call check( nf90_get_var(wrf_bdy%ncid, var_id, timestring, start = (/ 1, itime /)) ) + if(debug) write(*,*) 'Original_thisbdytime = ',timestring + call check( nf90_Redef(wrf_bdy%ncid) ) + call check( nf90_put_att(wrf_bdy%ncid, NF90_GLOBAL, "Original_thisbdytime", timestring) ) + call check( nf90_enddef(wrf_bdy%ncid) ) + call get_date(dart_time(1), year, month, day, hour, minute, second) + call set_wrf_date(timestring, year, month, day, hour, minute, second) + if(debug) write(*,*) 'New thisbdytime = ',timestring + call check( nf90_put_var(wrf_bdy%ncid, var_id, timestring, start = (/ 1, itime /)) ) + call check( nf90_inq_varid(wrf_bdy%ncid, "Times", var_id) ) + if(debug) write(*,*) 'writing Times = ',timestring + call check( nf90_put_var(wrf_bdy%ncid, var_id, timestring) ) + + if(debug) write(*,*) 'writing START_DATE = ',timestring + call check( nf90_put_att(wrf_bdy%ncid, nf90_global, "START_DATE", timestring) ) + + call check( nf90_inq_varid(wrf_bdy%ncid, 'md___nextbdytimee_x_t_d_o_m_a_i_n_m_e_t_a_data_', var_id) ) + call check( nf90_get_var(wrf_bdy%ncid, var_id, timestring, start = (/ 1, itime /)) ) + if(debug) write(*,*) 'Original_nextbdytime = ',timestring + call check( nf90_Redef(wrf_bdy%ncid) ) + call check( nf90_put_att(wrf_bdy%ncid, NF90_GLOBAL, "Original_nextbdytime", timestring) ) + call check( nf90_enddef(wrf_bdy%ncid) ) + call get_date(dart_time(2), year, month, day, hour, minute, second) + call set_wrf_date(timestring, year, month, day, hour, minute, second) + if(debug) write(*,*) 'New nextbdytime = ',timestring + call check( nf90_put_var(wrf_bdy%ncid, var_id, timestring, start = (/ 1, itime /)) ) + + !- Close files + call check ( nf90_close(wrf_bdy%ncid) ) + +enddo + +call wrfbdy_dealloc(wrf_bdy) +call wrfbdy_dealloc(wrf_bdy_mean) +call wrfbdy_dealloc(wrf_bdy_tmp) + +!-- Now do IC's for all domains. + +do id=1,num_domains + + write( idom , '(I1)') id + +!-- Allocate arrays for input data + + call wrf_open_and_alloc( wrf, 'wrfinput_d0'//idom//'_1', NF90_WRITE, debug ) + call check ( nf90_close(wrf%ncid) ) + +!-- Read data to be used as ensemble mean (plus open,close netcdf file) + call wrf_open_and_alloc( wrf_mean, 'wrfinput_d0'//idom//'_mean', NF90_WRITE, debug ) + call wrf_io( wrf_mean, "INPUT ", debug ) + call check ( nf90_close(wrf_mean%ncid) ) + + call wrf_open_and_alloc( wrf_tmp, 'wrfinput_d0'//idom//'_mean', NF90_WRITE, debug ) + call check ( nf90_close(wrf_tmp%ncid) ) + + call wrf_add ( wrf_tmp , 0.0_r8, wrf_mean , 0.0_r8) + +!-- Compute mean over Ne input and bdy files + do i=1,Ne + + !- Open appropriate files + write( imem , '(I6)') i + call check ( nf90_open('wrfinput_d0'//idom//'_'//adjustl(trim(imem)), NF90_NOWRITE, wrf%ncid) ) + + !- Read data + call wrf_io ( wrf , "INPUT ", debug ) + + !- Close files + call check ( nf90_close(wrf%ncid) ) + + !- accumulate sum + call wrf_add ( wrf_tmp , 1.0_r8, wrf , 1.0_r8) + + enddo + + call wrf_add( wrf_tmp , 1.0_r8/Ne, wrf , 0.0_r8) + +!-- Compute deviation from mean for each input file; overwrite input file with deviation + do i=1,Ne + + !- Open appropriate files + write( imem , '(I6)') i + call check ( nf90_open('wrfinput_d0'//idom//'_'//adjustl(trim(imem)), NF90_NOWRITE, wrf%ncid) ) + + !- Read data, again + call wrf_io ( wrf , "INPUT ", debug ) + + !- Close files + call check ( nf90_close(wrf%ncid) ) + + !- deviation from mean over input files + call wrf_add( wrf , 1.0_r8 , wrf_tmp , -1.0_r8 ) + + !- New IC: scaled deviation + chosen ensemble mean + call wrf_add( wrf , scale , wrf_mean , 1.0_r8 ) + + !- Open same files for writing + call check ( nf90_open('wrfinput_d0'//idom//'_'//adjustl(trim(imem)), NF90_WRITE, wrf%ncid) ) + + !- Write IC for ith member + call wrf_io( wrf , "OUTPUT", debug ) + + call get_date(dart_time(1), year, month, day, hour, minute, second) + call set_wrf_date(timestring, year, month, day, hour, minute, second) + + call check( nf90_inq_varid(wrf%ncid, "Times", var_id) ) + call check( nf90_put_var(wrf%ncid, var_id, timestring) ) + + call check( nf90_put_att(wrf%ncid, nf90_global, "START_DATE", timestring) ) + + !- Close files + call check ( nf90_close(wrf%ncid) ) + + enddo + + call wrf_dealloc(wrf) + call wrf_dealloc(wrf_mean) + call wrf_dealloc(wrf_tmp) + +enddo + +write(logfileunit,*)'FINISHED ensemble_init.' +write(logfileunit,*) + +call finalize_utilities('ensemble_init') ! closes log file. + +contains + + ! Internal subroutine - checks error status after each netcdf, prints + ! text message each time an error code is returned. + subroutine check(istatus) + integer, intent (in) :: istatus + + if(istatus /= nf90_noerr) call error_handler(E_ERR, 'ensemble_init', & + trim(nf90_strerror(istatus)), source, revision, revdate) + + end subroutine check + +!--------------------------------------------------------------- + + subroutine wrf_add( wrf_a, a, wrf_b, b ) +! +! Does wrf_a = a*wrf_a + b*wrf_b, for each real array in type wrf. +! Other components of wrf_a (integers, logical) are unchanged. +! + + implicit none + + type(wrf_data), intent(inout) :: wrf_a + type(wrf_data), intent(in ) :: wrf_b + real(r8), intent(in) :: a,b + + !-- Add: wrf_a = a*wrf_a + b*wrf_b, for components + ! u,v,w,ph,phb,t, qv,qc,qr, qi,qs,qg,qnice, mu,mub, tslb,tsk, of a and b + wrf_a%u = a * wrf_a%u + b * wrf_b%u + wrf_a%v = a * wrf_a%v + b * wrf_b%v + wrf_a%w = a * wrf_a%w + b * wrf_b%w + wrf_a%ph = a * wrf_a%ph + b * wrf_b%ph + wrf_a%phb = a * wrf_a%phb + b * wrf_b%phb + wrf_a%t = a * wrf_a%t + b * wrf_b%t + wrf_a%mu = a * wrf_a%mu + b * wrf_b%mu + wrf_a%mub = a * wrf_a%mub + b * wrf_b%mub + wrf_a%tslb = a * wrf_a%tslb + b * wrf_b%tslb + wrf_a%tsk = a * wrf_a%tsk + b * wrf_b%tsk + if(wrf%n_moist > 0) then + wrf_a%qv = a * wrf_a%qv + b * wrf_b%qv + endif + if(wrf%n_moist > 1) then + wrf_a%qc = a * wrf_a%qc + b * wrf_b%qc + endif + if(wrf%n_moist > 2) then + wrf_a%qr = a * wrf_a%qr + b * wrf_b%qr + endif + if(wrf%n_moist > 3) then + wrf_a%qi = a * wrf_a%qi + b * wrf_b%qi + endif + if(wrf%n_moist > 4) then + wrf_a%qs = a * wrf_a%qs + b * wrf_b%qs + endif + if(wrf%n_moist > 5) then + wrf_a%qg = a * wrf_a%qg + b * wrf_b%qg + endif + if(wrf%n_moist > 6) then + wrf_a%qnice = a * wrf_a%qnice + b * wrf_b%qnice + endif + +end subroutine wrf_add +!--------------------------------------------------------------- + +subroutine wrfbdy_add( wrfbdy_a, a, wrfbdy_b, b ) +! +! Does wrfbdy_a = a*wrfbdy_a + b*wrfbdy_b, for each real array in type wrfbdy. +! Other components of wrf_a (integers, logical) are unchanged. +! + + implicit none + + type(wrf_bdy_data), intent(inout) :: wrfbdy_a + type(wrf_bdy_data), intent(in ) :: wrfbdy_b + real(r8), intent(in) :: a,b + + !-- Add: wrfbdy_a = a*wrfbdy_a + b*wrfbdy_b, for components + ! u,v,w,ph,t,qv on bndries + their tendencies + + wrfbdy_a%uxs = a * wrfbdy_a%uxs + b * wrfbdy_b%uxs + wrfbdy_a%uxe = a * wrfbdy_a%uxe + b * wrfbdy_b%uxe + wrfbdy_a%uys = a * wrfbdy_a%uys + b * wrfbdy_b%uys + wrfbdy_a%uye = a * wrfbdy_a%uye + b * wrfbdy_b%uye + wrfbdy_a%utxs = a * wrfbdy_a%utxs + b * wrfbdy_b%utxs + wrfbdy_a%utxe = a * wrfbdy_a%utxe + b * wrfbdy_b%utxe + wrfbdy_a%utys = a * wrfbdy_a%utys + b * wrfbdy_b%utys + wrfbdy_a%utye = a * wrfbdy_a%utye + b * wrfbdy_b%utye + + wrfbdy_a%vxs = a * wrfbdy_a%vxs + b * wrfbdy_b%vxs + wrfbdy_a%vxe = a * wrfbdy_a%vxe + b * wrfbdy_b%vxe + wrfbdy_a%vys = a * wrfbdy_a%vys + b * wrfbdy_b%vys + wrfbdy_a%vye = a * wrfbdy_a%vye + b * wrfbdy_b%vye + wrfbdy_a%vtxs = a * wrfbdy_a%vtxs + b * wrfbdy_b%vtxs + wrfbdy_a%vtxe = a * wrfbdy_a%vtxe + b * wrfbdy_b%vtxe + wrfbdy_a%vtys = a * wrfbdy_a%vtys + b * wrfbdy_b%vtys + wrfbdy_a%vtye = a * wrfbdy_a%vtye + b * wrfbdy_b%vtye + + wrfbdy_a%phxs = a * wrfbdy_a%phxs + b * wrfbdy_b%phxs + wrfbdy_a%phxe = a * wrfbdy_a%phxe + b * wrfbdy_b%phxe + wrfbdy_a%phys = a * wrfbdy_a%phys + b * wrfbdy_b%phys + wrfbdy_a%phye = a * wrfbdy_a%phye + b * wrfbdy_b%phye + wrfbdy_a%phtxs = a * wrfbdy_a%phtxs + b * wrfbdy_b%phtxs + wrfbdy_a%phtxe = a * wrfbdy_a%phtxe + b * wrfbdy_b%phtxe + wrfbdy_a%phtys = a * wrfbdy_a%phtys + b * wrfbdy_b%phtys + wrfbdy_a%phtye = a * wrfbdy_a%phtye + b * wrfbdy_b%phtye + + wrfbdy_a%txs = a * wrfbdy_a%txs + b * wrfbdy_b%txs + wrfbdy_a%txe = a * wrfbdy_a%txe + b * wrfbdy_b%txe + wrfbdy_a%tys = a * wrfbdy_a%tys + b * wrfbdy_b%tys + wrfbdy_a%tye = a * wrfbdy_a%tye + b * wrfbdy_b%tye + wrfbdy_a%ttxs = a * wrfbdy_a%ttxs + b * wrfbdy_b%ttxs + wrfbdy_a%ttxe = a * wrfbdy_a%ttxe + b * wrfbdy_b%ttxe + wrfbdy_a%ttys = a * wrfbdy_a%ttys + b * wrfbdy_b%ttys + wrfbdy_a%ttye = a * wrfbdy_a%ttye + b * wrfbdy_b%ttye + + wrfbdy_a%muxs = a * wrfbdy_a%muxs + b * wrfbdy_b%muxs + wrfbdy_a%muxe = a * wrfbdy_a%muxe + b * wrfbdy_b%muxe + wrfbdy_a%muys = a * wrfbdy_a%muys + b * wrfbdy_b%muys + wrfbdy_a%muye = a * wrfbdy_a%muye + b * wrfbdy_b%muye + wrfbdy_a%mutxs = a * wrfbdy_a%mutxs + b * wrfbdy_b%mutxs + wrfbdy_a%mutxe = a * wrfbdy_a%mutxe + b * wrfbdy_b%mutxe + wrfbdy_a%mutys = a * wrfbdy_a%mutys + b * wrfbdy_b%mutys + wrfbdy_a%mutye = a * wrfbdy_a%mutye + b * wrfbdy_b%mutye + + if(wrfbdy_a%n_moist > 0) then + wrfbdy_a%qvxs = a * wrfbdy_a%qvxs + b * wrfbdy_b%qvxs + wrfbdy_a%qvxe = a * wrfbdy_a%qvxe + b * wrfbdy_b%qvxe + wrfbdy_a%qvys = a * wrfbdy_a%qvys + b * wrfbdy_b%qvys + wrfbdy_a%qvye = a * wrfbdy_a%qvye + b * wrfbdy_b%qvye + wrfbdy_a%qvtxs = a * wrfbdy_a%qvtxs + b * wrfbdy_b%qvtxs + wrfbdy_a%qvtxe = a * wrfbdy_a%qvtxe + b * wrfbdy_b%qvtxe + wrfbdy_a%qvtys = a * wrfbdy_a%qvtys + b * wrfbdy_b%qvtys + wrfbdy_a%qvtye = a * wrfbdy_a%qvtye + b * wrfbdy_b%qvtye + endif + + if(wrfbdy_a%n_moist > 1) then + wrfbdy_a%qcxs = a * wrfbdy_a%qcxs + b * wrfbdy_b%qcxs + wrfbdy_a%qcxe = a * wrfbdy_a%qcxe + b * wrfbdy_b%qcxe + wrfbdy_a%qcys = a * wrfbdy_a%qcys + b * wrfbdy_b%qcys + wrfbdy_a%qcye = a * wrfbdy_a%qcye + b * wrfbdy_b%qcye + wrfbdy_a%qctxs = a * wrfbdy_a%qctxs + b * wrfbdy_b%qctxs + wrfbdy_a%qctxe = a * wrfbdy_a%qctxe + b * wrfbdy_b%qctxe + wrfbdy_a%qctys = a * wrfbdy_a%qctys + b * wrfbdy_b%qctys + wrfbdy_a%qctye = a * wrfbdy_a%qctye + b * wrfbdy_b%qctye + endif + if(wrfbdy_a%n_moist > 2) then + wrfbdy_a%qrxs = a * wrfbdy_a%qrxs + b * wrfbdy_b%qrxs + wrfbdy_a%qrxe = a * wrfbdy_a%qrxe + b * wrfbdy_b%qrxe + wrfbdy_a%qrys = a * wrfbdy_a%qrys + b * wrfbdy_b%qrys + wrfbdy_a%qrye = a * wrfbdy_a%qrye + b * wrfbdy_b%qrye + wrfbdy_a%qrtxs = a * wrfbdy_a%qrtxs + b * wrfbdy_b%qrtxs + wrfbdy_a%qrtxe = a * wrfbdy_a%qrtxe + b * wrfbdy_b%qrtxe + wrfbdy_a%qrtys = a * wrfbdy_a%qrtys + b * wrfbdy_b%qrtys + wrfbdy_a%qrtye = a * wrfbdy_a%qrtye + b * wrfbdy_b%qrtye + endif + if(wrfbdy_a%n_moist > 3) then + wrfbdy_a%qixs = a * wrfbdy_a%qixs + b * wrfbdy_b%qixs + wrfbdy_a%qixe = a * wrfbdy_a%qixe + b * wrfbdy_b%qixe + wrfbdy_a%qiys = a * wrfbdy_a%qiys + b * wrfbdy_b%qiys + wrfbdy_a%qiye = a * wrfbdy_a%qiye + b * wrfbdy_b%qiye + wrfbdy_a%qitxs = a * wrfbdy_a%qitxs + b * wrfbdy_b%qitxs + wrfbdy_a%qitxe = a * wrfbdy_a%qitxe + b * wrfbdy_b%qitxe + wrfbdy_a%qitys = a * wrfbdy_a%qitys + b * wrfbdy_b%qitys + wrfbdy_a%qitye = a * wrfbdy_a%qitye + b * wrfbdy_b%qitye + endif + if(wrfbdy_a%n_moist > 4) then + wrfbdy_a%qsxs = a * wrfbdy_a%qsxs + b * wrfbdy_b%qsxs + wrfbdy_a%qsxe = a * wrfbdy_a%qsxe + b * wrfbdy_b%qsxe + wrfbdy_a%qsys = a * wrfbdy_a%qsys + b * wrfbdy_b%qsys + wrfbdy_a%qsye = a * wrfbdy_a%qsye + b * wrfbdy_b%qsye + wrfbdy_a%qstxs = a * wrfbdy_a%qstxs + b * wrfbdy_b%qstxs + wrfbdy_a%qstxe = a * wrfbdy_a%qstxe + b * wrfbdy_b%qstxe + wrfbdy_a%qstys = a * wrfbdy_a%qstys + b * wrfbdy_b%qstys + wrfbdy_a%qstye = a * wrfbdy_a%qstye + b * wrfbdy_b%qstye + endif + if(wrfbdy_a%n_moist > 5) then + wrfbdy_a%qgxs = a * wrfbdy_a%qgxs + b * wrfbdy_b%qgxs + wrfbdy_a%qgxe = a * wrfbdy_a%qgxe + b * wrfbdy_b%qgxe + wrfbdy_a%qgys = a * wrfbdy_a%qgys + b * wrfbdy_b%qgys + wrfbdy_a%qgye = a * wrfbdy_a%qgye + b * wrfbdy_b%qgye + wrfbdy_a%qgtxs = a * wrfbdy_a%qgtxs + b * wrfbdy_b%qgtxs + wrfbdy_a%qgtxe = a * wrfbdy_a%qgtxe + b * wrfbdy_b%qgtxe + wrfbdy_a%qgtys = a * wrfbdy_a%qgtys + b * wrfbdy_b%qgtys + wrfbdy_a%qgtye = a * wrfbdy_a%qgtye + b * wrfbdy_b%qgtye + endif + if(wrfbdy_a%n_moist > 6) then + wrfbdy_a%qnicexs = a * wrfbdy_a%qnicexs + b * wrfbdy_b%qnicexs + wrfbdy_a%qnicexe = a * wrfbdy_a%qnicexe + b * wrfbdy_b%qnicexe + wrfbdy_a%qniceys = a * wrfbdy_a%qniceys + b * wrfbdy_b%qniceys + wrfbdy_a%qniceye = a * wrfbdy_a%qniceye + b * wrfbdy_b%qniceye + wrfbdy_a%qnicetxs = a * wrfbdy_a%qnicetxs + b * wrfbdy_b%qnicetxs + wrfbdy_a%qnicetxe = a * wrfbdy_a%qnicetxe + b * wrfbdy_b%qnicetxe + wrfbdy_a%qnicetys = a * wrfbdy_a%qnicetys + b * wrfbdy_b%qnicetys + wrfbdy_a%qnicetye = a * wrfbdy_a%qnicetye + b * wrfbdy_b%qnicetye + endif + if(wrfbdy_a%n_moist > 7) then + write(*,*) 'n_moist = ',wrfbdy_a%n_moist + call error_handler(E_ERR,'wrfbdy_add', & + 'n_moist is too large.', source, revision, revdate) + stop + endif + +end subroutine wrfbdy_add + +end program ensemble_init + +! +! $URL$ +! $Id$ +! $Revision$ +! $Date$ diff --git a/models/wrf_unified/WRF_DART_utilities/extract.f90 b/models/wrf_unified/WRF_DART_utilities/extract.f90 new file mode 100644 index 0000000000..8b16a26e53 --- /dev/null +++ b/models/wrf_unified/WRF_DART_utilities/extract.f90 @@ -0,0 +1,127 @@ +! DART software - Copyright UCAR. This open source software is provided +! by UCAR, "as is", without charge, subject to all terms of use at +! http://www.image.ucar.edu/DAReS/DART/DART_download +! +! $Id$ + +PROGRAM extract + +use utilities_mod, only : file_exist, open_file, close_file, & + error_handler, E_ERR, initialize_utilities, & + register_module, logfileunit, finalize_utilities +use netcdf + +implicit none + +! version controlled file description for error handling, do not edit +character(len=256), parameter :: source = & + "$URL$" +character(len=32 ), parameter :: revision = "$Revision$" +character(len=128), parameter :: revdate = "$Date$" + +real, allocatable, dimension(:,:) :: psfc +real :: dt + +integer :: iunit, n_file, sn, we + +integer :: ierr, ifile, timeindex +character(len=80) :: file_name, varname, ps_var + +character(len=19) :: time_string, time_string_last + +!---- +! misc stuff + +integer :: nDimensions, nVariables, nAttributes, unlimitedDimID +integer :: var_id, outid, TimeDimID, TimeVarID, weDimID, snDimID, out_var_id, ncid, sn_id, we_id, DateDimID + +call initialize_utilities('Extract') +call register_module(source, revision, revdate) + +ps_var = 'MU' + +read(5,*) n_file + +iunit = open_file('wrfout.list', action = 'read') + +do ifile=1,n_file + + read(iunit, *) file_name + + call check ( nf90_open(file_name, NF90_NOWRITE, ncid) ) + + call check ( nf90_inq_dimid(ncid, "south_north", sn_id) ) + call check ( nf90_inquire_dimension(ncid, sn_id, varname, sn) ) + + call check ( nf90_inq_dimid(ncid, "west_east", we_id) ) + call check ( nf90_inquire_dimension(ncid, we_id, varname, we) ) + allocate(psfc(we,sn)) + + call check( nf90_get_att(ncid, nf90_global, 'DT', dt) ) + + call check ( nf90_inq_varid(ncid, ps_var, var_id)) + call check ( nf90_get_var(ncid, var_id, psfc, start = (/ 1, 1, 1/))) + call check ( nf90_inq_varid(ncid, "Times", var_id)) + call check ( nf90_get_var(ncid, var_id, time_string, start = (/ 1/))) + ierr = NF90_close(ncid) + + if(file_exist('psfc.nc')) then + call check( nf90_open('psfc.nc', nf90_write, outid) ) + call check(NF90_Inquire(outid, nDimensions, nVariables, nAttributes, unlimitedDimID)) + call check(NF90_Inq_Varid(outid, "time", TimeVarID)) + call check(NF90_Inquire_Dimension(outid, unlimitedDimID, varname, timeindex)) + call check ( nf90_get_var(outid, TimeVarID, time_string_last, start = (/ 1, timeindex/))) + timeindex = timeindex + 1 + call check(NF90_Inq_Varid(outid, ps_var, out_var_id)) + else + call check(nf90_create(path = 'psfc.nc', cmode = nf90_share, ncid = outid)) + call check(nf90_def_dim(ncid=outid, name="time", len = nf90_unlimited, dimid = TimeDimID)) + call check(nf90_def_dim(ncid=outid, name="DateStrLen", len = 19, dimid = DateDimID)) + call check(nf90_def_var(outid, name="time", xtype=nf90_char, & + dimids = (/ DateDimID, TimeDimID /), varid = TimeVarID) ) + call check(nf90_def_dim(ncid=outid, name="west_east", len = we, dimid = weDimID)) + call check(nf90_def_dim(ncid=outid, name="south_north", len = sn, dimid = snDimID)) + call check(nf90_def_var(ncid=outid, name=ps_var, xtype=nf90_real, & + dimids = (/ weDimID, snDimID, TimeDimID /), varid = out_var_id)) + call check(nf90_put_att(outid, NF90_GLOBAL, "DT", dt)) + call check(nf90_enddef(outid)) + call check(nf90_sync(outid)) ! sync to disk, but leave open + timeindex = 1 + + endif + +!!$ if (psfc(1,1) /= 0.0 .and. time_string_last /= time_string) then + call check(nf90_put_var( outid, TimeVarID, time_string, start=(/ 1, timeindex /) )) + call check(nf90_put_var( outid, out_var_id, psfc, start=(/ 1, 1, timeindex /) )) +!!$ endif + + ierr = NF90_close(outid) + + deallocate(psfc) + +enddo + +call close_file(iunit) + +write(logfileunit,*)'FINISHED extract.' +write(logfileunit,*) + +call finalize_utilities('Extract') ! closes log file. + +contains + + ! Internal subroutine - checks error status after each netcdf, prints + ! text message each time an error code is returned. + subroutine check(istatus) + integer, intent ( in) :: istatus + if(istatus /= nf90_noerr) call error_handler(E_ERR,'extract', & + trim(nf90_strerror(istatus)), source, revision, revdate) + end subroutine check + +END PROGRAM extract + +! +! $URL$ +! $Id$ +! $Revision$ +! $Date$ diff --git a/models/wrf_unified/WRF_DART_utilities/grid_refl_obs.f90 b/models/wrf_unified/WRF_DART_utilities/grid_refl_obs.f90 new file mode 100644 index 0000000000..d2c20bc5be --- /dev/null +++ b/models/wrf_unified/WRF_DART_utilities/grid_refl_obs.f90 @@ -0,0 +1,409 @@ +! DART software - Copyright UCAR. This open source software is provided +! by UCAR, "as is", without charge, subject to all terms of use at +! http://www.image.ucar.edu/DAReS/DART/DART_download +! +! $Id$ + +PROGRAM grid_refl_obs + +! Extract reflectivity obs from a DART obs_seq.out file and compute the coordinates of +! these observations on a WRF grid (mass grid points). +! +! David Dowell 26 June 2007 +! +! input parameters from command line: +! (1) obs_seq_file -- path name of DART obs_seq.out file +! (2) refl_min -- minimum reflectivity threshold for processing observation +! (3) days_begin -- start of time range to be processed +! (4) seconds_begin -- " " +! (5) days_end -- end of time range to be processed +! (6) seconds_end -- " " +! (7) wrf_file -- path name of WRF netcdf file +! +! output: +! (1) text file + +use types_mod, only : r8, missing_r8, gravity +use obs_sequence_mod, only : read_obs_seq, obs_type, obs_sequence_type, get_first_obs, & + get_obs_from_key, get_obs_def, get_copy_meta_data, & + get_obs_time_range, get_time_range_keys, get_num_obs, & + get_next_obs, get_num_times, get_obs_values, init_obs, & + get_num_copies, static_init_obs_sequence, & + get_qc, destroy_obs_sequence, read_obs_seq_header, & + get_last_obs, destroy_obs, get_num_qc, get_qc_meta_data +use obs_def_mod, only : obs_def_type, get_obs_def_error_variance, get_obs_def_time, & + get_obs_def_location, get_obs_def_type_of_obs +use obs_kind_mod, only : RADAR_REFLECTIVITY +use map_utils, only : proj_info, map_init, map_set, latlon_to_ij, & + PROJ_LATLON, PROJ_MERC, PROJ_LC, PROJ_PS, & + ij_to_latlon, gridwind_to_truewind +use location_mod, only : location_type, get_location, set_location_missing, & + write_location, operator(/=), & + is_vertical +use time_manager_mod, only : time_type, set_date, set_time, get_time, print_time, & + set_calendar_type, print_date, GREGORIAN, & + operator(*), operator(+), operator(-), & + operator(>), operator(<), operator(/), & + operator(/=), operator(<=) +use utilities_mod, only : error_handler, E_ERR, E_MSG, file_exist, & + initialize_utilities, finalize_utilities +use netcdf + +implicit none + +! version controlled file description for error handling, do not edit +character(len=256), parameter :: source = & + "$URL$" +character(len=32 ), parameter :: revision = "$Revision$" +character(len=128), parameter :: revdate = "$Date$" + +! command-line parameters +character(len=129) :: obs_seq_file +real(r8) :: refl_min +integer :: seconds_begin +integer :: days_begin +integer :: seconds_end +integer :: days_end +character(len=129) :: wrf_file + +! local variables +type(obs_sequence_type) :: seq +type(obs_def_type) :: obs_def +type(time_type) :: beg_time, end_time +type(obs_type) :: ob +type(location_type) :: ob_loc + +integer :: num_copies, num_qc, num_obs, max_num_obs, obs_seq_file_id +character(len=129) :: obs_seq_read_format +logical :: pre_I_format +logical :: out_of_range +integer :: key_bounds(2) +integer :: num_obs_in_time_period +integer :: num_refl_obs +integer :: num_refl_obs_in_domain +integer, allocatable :: keys(:) +integer :: obs_kind_ind +integer :: obs_index +real(r8) :: ob_value(1) + +real(r8), allocatable :: lat(:,:) ! latitude at mass grid points (deg) +real(r8), allocatable :: lon(:,:) ! longitude at mass grid points (deg) +real(r8), allocatable :: phb(:,:,:) ! base-state geopotential (m^2 s^-2) +real(r8), allocatable :: ht(:,:,:) ! height MSL of mass grid points (m) +type(proj_info) :: proj ! map projection info. +integer, parameter :: map_sphere = 0, map_lambert = 1, map_polar_stereo = 2, map_mercator = 3 +integer map_proj, proj_code +real(r8) :: dx +real(r8) :: stdlon,truelat1,truelat2 +real(r8) :: xyz_loc(3) +real(r8) :: iloc, jloc, kloc + +real(r8), allocatable :: refl_ob(:,:,:) ! gridded reflectivity observations (dBZ) + +integer :: bt, sn, we ! WRF grid dimensions + +character(len = 150) :: msgstring +integer :: i, j, k, o + + +! netcdf stuff +integer :: var_id, ncid, ierr +character(len=80) :: varname + +! command-line parameters stuff +integer :: status, length +character(len=120) :: string + + +call initialize_utilities('grid_refl_obs') + +! Get command-line parameters, using the fortran 2003 intrinsics. + +if( COMMAND_ARGUMENT_COUNT() .ne. 7 ) then + print*, 'INCORRECT # OF ARGUMENTS ON COMMAND LINE: ', COMMAND_ARGUMENT_COUNT() + call exit(1) +else + + call GET_COMMAND_ARGUMENT(1,obs_seq_file,length,status) + if( status .ne. 0 ) then + print*, 'obs_seq_file NOT RETRIEVED FROM COMMAND LINE: ', status + call exit(1) + endif + + call GET_COMMAND_ARGUMENT(2,string,length,status) + if( status .ne. 0 ) then + print*, 'refl_min NOT RETRIEVED FROM COMMAND LINE: ', status + call exit(1) + else + read(string,*) refl_min + endif + + call GET_COMMAND_ARGUMENT(3,string,length,status) + if( status .ne. 0 ) then + print*, 'days_begin NOT RETRIEVED FROM COMMAND LINE: ', status + call exit(1) + else + read(string,*) days_begin + endif + + call GET_COMMAND_ARGUMENT(4,string,length,status) + if( status .ne. 0 ) then + print*, 'seconds_begin NOT RETRIEVED FROM COMMAND LINE: ', status + call exit(1) + else + read(string,*) seconds_begin + endif + + call GET_COMMAND_ARGUMENT(5,string,length,status) + if( status .ne. 0 ) then + print*, 'days_end NOT RETRIEVED FROM COMMAND LINE: ', status + call exit(1) + else + read(string,*) days_end + endif + + call GET_COMMAND_ARGUMENT(6,string,length,status) + if( status .ne. 0 ) then + print*, 'seconds_end NOT RETRIEVED FROM COMMAND LINE: ', status + call exit(1) + else + read(string,*) seconds_end + endif + + call GET_COMMAND_ARGUMENT(7,wrf_file,length,status) + if( status .ne. 0 ) then + print*, 'wrf_file NOT RETRIEVED FROM COMMAND LINE: ', status + call exit(1) + endif + +endif + + +! Read observations. + +call static_init_obs_sequence() ! Initialize the obs sequence module + +obs_seq_file = trim(adjustl(obs_seq_file)) +if ( file_exist(obs_seq_file) ) then + write(msgstring,*)'opening ', obs_seq_file + call error_handler(E_MSG,'grid_refl_obs',msgstring,source,revision,revdate) +else + write(msgstring,*)obs_seq_file,& + ' does not exist. Finishing up.' + call error_handler(E_MSG,'grid_refl_obs',msgstring,source,revision,revdate) + call exit(1) +endif + +call read_obs_seq_header(obs_seq_file, & + num_copies, num_qc, num_obs, max_num_obs, & + obs_seq_file_id, obs_seq_read_format, pre_I_format, & + close_the_file = .true.) + +print*, 'num_copies = ', num_copies +print*, 'num_qc = ', num_qc +print*, 'num_obs = ', num_obs +print*, 'max_num_obs = ', max_num_obs +print*, 'obs_seq_read_format = ', obs_seq_read_format +print*, 'pre_I_format = ', pre_I_format + +call read_obs_seq(obs_seq_file, 0, 0, 0, seq) + +MetaDataLoop : do i=1, get_num_copies(seq) + if(index(get_copy_meta_data(seq,i), 'observation') > 0) obs_index = i +enddo MetaDataLoop + + +! Open WRF file and obtain grid dimensions. + +call check ( nf90_open(wrf_file, NF90_NOWRITE, ncid) ) + +call check ( nf90_inq_dimid(ncid, 'bottom_top', var_id) ) +call check ( nf90_inquire_dimension(ncid, var_id, varname, bt) ) + +call check ( nf90_inq_dimid(ncid, 'south_north', var_id) ) +call check ( nf90_inquire_dimension(ncid, var_id, varname, sn) ) + +call check ( nf90_inq_dimid(ncid, 'west_east', var_id) ) +call check ( nf90_inquire_dimension(ncid, var_id, varname, we) ) + +call check( nf90_get_att(ncid, nf90_global, 'MAP_PROJ', map_proj) ) +call check( nf90_get_att(ncid, nf90_global, 'DX', dx) ) +call check( nf90_get_att(ncid, nf90_global, 'TRUELAT1', truelat1) ) +call check( nf90_get_att(ncid, nf90_global, 'TRUELAT2', truelat2) ) +call check( nf90_get_att(ncid, nf90_global, 'STAND_LON', stdlon) ) + +! Allocate arrays. + +allocate(lat(we,sn)) +allocate(lon(we,sn)) +allocate(phb(we,sn,bt+1)) +allocate(ht(we,sn,bt)) +allocate(refl_ob(we,sn,bt)) + +refl_ob(:,:,:) = missing_r8 + + +! Read WRF grid information. + +call check ( nf90_inq_varid(ncid, 'XLAT', var_id)) +call check ( nf90_get_var(ncid, var_id, lat, start = (/ 1, 1, 1/))) + +call check ( nf90_inq_varid(ncid, 'XLONG', var_id)) +call check ( nf90_get_var(ncid, var_id, lon, start = (/ 1, 1, 1/))) + +call check ( nf90_inq_varid(ncid, 'PHB', var_id)) +call check ( nf90_get_var(ncid, var_id, phb, start = (/ 1, 1, 1, 1/))) + +ierr = NF90_close(ncid) + + +! Set up map projection structure. + +call map_init(proj) +if(map_proj == map_sphere) then + proj_code = PROJ_LATLON +elseif(map_proj == map_lambert) then + proj_code = PROJ_LC +elseif(map_proj == map_polar_stereo) then + proj_code = PROJ_PS +elseif(map_proj == map_mercator) then + proj_code = PROJ_MERC +else + call error_handler(E_ERR,'grid_refl_obs', & + 'Map projection no supported.', source, revision, revdate) +endif +!call map_set(proj_code,lat(1,1),lon(1,1), & +! 1.0_r8,1.0_r8,dx,stdlon,truelat1,truelat2,proj) +call map_set(proj_code=proj_code, proj=proj, lat1=lat(1,1), lon1=lon(1,1), & + knowni=1.0_r8, knownj=1.0_r8, dx=dx, stdlon=stdlon, truelat1=truelat1, truelat2=truelat2) + +! Compute height (m MSL) of each grid point. + +do k=1, bt + do j=1, sn + do i=1, we + ht(i,j,k) = ( phb(i,j,k) + phb(i,j,k+1) ) / (2.0_r8*gravity) + enddo + enddo +enddo + +! Make sure longitudes are in the range from 0 to 360. + +do j=1, sn + do i=1, we + do while (lon(i,j) < 0.0_r8) + lon(i,j) = lon(i,j) + 360.0_r8 + end do + do while (lon(i,j) > 360.0_r8) + lon(i,j) = lon(i,j) - 360.0_r8 + end do + enddo +enddo + + +! Process observations, assigning to grid points any reflectivity observations that lie within +! the domain and specified time range. + +beg_time = set_time(seconds_begin, days_begin) +end_time = set_time(seconds_end, days_end) +call get_obs_time_range(seq, beg_time, end_time, key_bounds, & + num_obs_in_time_period, out_of_range) + +print*, 'total number of observations in time period = ', num_obs_in_time_period + +allocate(keys(num_obs_in_time_period)) +call get_time_range_keys(seq, key_bounds, num_obs_in_time_period, keys) + +num_refl_obs = 0 +num_refl_obs_in_domain = 0 + +do o = 1, num_obs_in_time_period + + call get_obs_from_key(seq, keys(o), ob) + call get_obs_def(ob, obs_def) + ob_loc = get_obs_def_location(obs_def) + obs_kind_ind = get_obs_def_type_of_obs(obs_def) + + if ( (obs_kind_ind == RADAR_REFLECTIVITY) .and. (is_vertical(ob_loc, "HEIGHT")) ) then + + num_refl_obs = num_refl_obs + 1 + +! xyz_loc(1) is longitude, xyz_loc(2) is latitude, xyz_loc(3) is height + xyz_loc = get_location(ob_loc) + call latlon_to_ij(proj,xyz_loc(2),xyz_loc(1),iloc,jloc) + if ( (iloc >= 1 .and. iloc <= we .and. jloc >= 1 .and. jloc <= sn) ) then + + i = nint(iloc) + j = nint(jloc) + + kloc = 0.0 + do k=1, bt-1 + if ( (xyz_loc(3).ge.ht(i,j,k)) .and. (xyz_loc(3).le.ht(i,j,k+1)) ) then + kloc = real(k) + (xyz_loc(3)-ht(i,j,k)) / (ht(i,j,k+1)-ht(i,j,k)) + endif + enddo + k = nint(kloc) + + if (k.ne.0) then + + num_refl_obs_in_domain = num_refl_obs_in_domain + 1 + call get_obs_values(ob, ob_value, obs_index) + if ( (refl_ob(i,j,k).eq.missing_r8) .or. (ob_value(1).gt.refl_ob(i,j,k)) ) then + refl_ob(i,j,k) = ob_value(1) + endif + + endif + + endif + + end if ! if (obs_kind_ind == RADAR_REFLECTIVITY) + +enddo ! do o = 1, num_obs_in_time_period + +print*, 'number of reflectivity observations in time period = ', num_refl_obs +print*, 'number of reflectivity observations in time period and domain = ', num_refl_obs_in_domain + + +! Output gridded reflectivity values that exceed threshold. + +open(unit=11, file='refl_obs.txt', status='unknown') +do k=1, bt + do j=1, sn + do i=1, we + if ( (refl_ob(i,j,k).ne.missing_r8) .and. (refl_ob(i,j,k).gt.refl_min) ) then + write(11,*) i, j, k, refl_ob(i,j,k) + endif + enddo + enddo +enddo +close(11) + + +! Deallocate arrays. + +deallocate(lat) +deallocate(lon) +deallocate(phb) +deallocate(ht) +deallocate(refl_ob) +deallocate(keys) + +call finalize_utilities('grid_refl_obs') + +contains + + ! Internal subroutine - checks error status after each netcdf, prints + ! text message each time an error code is returned. + subroutine check(istatus) + integer, intent ( in) :: istatus + if(istatus /= nf90_noerr) call error_handler(E_ERR,'grid_refl_obs', & + trim(nf90_strerror(istatus)), source, revision, revdate) + end subroutine check + +END PROGRAM grid_refl_obs + +! +! $URL$ +! $Id$ +! $Revision$ +! $Date$ diff --git a/models/wrf_unified/WRF_DART_utilities/replace_wrf_fields.f90 b/models/wrf_unified/WRF_DART_utilities/replace_wrf_fields.f90 new file mode 100644 index 0000000000..b4e9356f73 --- /dev/null +++ b/models/wrf_unified/WRF_DART_utilities/replace_wrf_fields.f90 @@ -0,0 +1,348 @@ +! DART software - Copyright UCAR. This open source software is provided +! by UCAR, "as is", without charge, subject to all terms of use at +! http://www.image.ucar.edu/DAReS/DART/DART_download +! +! $Id$ + +program replace_wrf_fields + +use types_mod, only : r8 +use utilities_mod, only : register_module, error_handler, E_ERR, E_MSG, & + open_file, close_file, get_next_filename, & + find_namelist_in_file, check_namelist_read, & + do_nml_file, do_nml_term, nmlfileunit, & + initialize_utilities, finalize_utilities +use netcdf_utilities_mod, only : nc_check +use parse_args_mod, only : get_args_from_string + +use netcdf + +implicit none + +! version controlled file description for error handling, do not edit +character(len=256), parameter :: source = & + "$URL$" +character(len=32 ), parameter :: revision = "$Revision$" +character(len=128), parameter :: revdate = "$Date$" + +! variables used to read the netcdf info +integer, parameter :: maxd = 7 +integer :: i, j, ndims, odims, ncrc, etype +integer :: ncinid, ncoutid ! netcdf id for file +integer :: invarid, outvarid +integer :: dimid(maxd), dimlen(maxd), odimid(maxd), odimlen(maxd) +character(128) :: dimname(maxd), odimname(maxd) +integer :: ninDimensions, ninVariables, ninAttributes, inunlimitedDimID +integer :: noutDimensions, noutVariables, noutAttributes, outunlimitedDimID + +! arrays for all possible dimensions +real(r8), pointer :: oned(:) +real(r8), pointer :: twod(:,:) +real(r8), pointer :: threed(:,:,:) +real(r8), pointer :: fourd(:,:,:,:) +real(r8), pointer :: fived(:,:,:,:,:) +real(r8), pointer :: sixd(:,:,:,:,:,:) +real(r8), pointer :: sevend(:,:,:,:,:,:,:) + +logical, save :: module_initialized = .false. + +! arg parsing code +character(len=256) :: argline +integer :: argcount = 2 +character(len=NF90_MAX_NAME) :: argwords(3) + +character(len=NF90_MAX_NAME) :: infile, outfile +character(len=NF90_MAX_NAME) :: nextfield +logical :: from_file + +character(len=128) :: msgstring, msgstring2, tmpstring +integer :: iunit, io +logical :: debug = .false. ! or .true. +logical :: fail_on_missing_field = .true. ! or .false. +character(len=128) :: fieldnames(1000) = '' ! something large +character(len=128) :: fieldlist_file = '' + +! fieldnames here? +namelist /replace_wrf_fields_nml/ debug, fail_on_missing_field, & + fieldnames, fieldlist_file + +! main code here + +! flow: +! initialization +call initialize_utilities('replace_wrf_fields') +call initialize_module() + +! Read the namelist entry +call find_namelist_in_file("input.nml", "replace_wrf_fields_nml", iunit) +read(iunit, nml = replace_wrf_fields_nml, iostat = io) +call check_namelist_read(iunit, io, "replace_wrf_fields_nml") + +! Record the namelist values used for the run ... +if (do_nml_file()) write(nmlfileunit, nml=replace_wrf_fields_nml) +if (do_nml_term()) write( * , nml=replace_wrf_fields_nml) + +if (debug) then + call error_handler(E_MSG, 'replace_wrf_fields', ' debug on') +endif + +! whether to fail or just warn if a field is not found +if (fail_on_missing_field) then + etype = E_ERR +else + etype = E_MSG +endif + +! check inputs - get 2 white-space separated strings from stdin +! eg: echo infile.nc outfile.nc | ./replace_wrf_fields +read(*, '(A)') argline +call get_args_from_string(argline, argcount, argwords) + +if (argcount /= 2) then + msgstring = 'Usage: echo infile.nc outfile.nc | ./replace_wrf_fields' + call error_handler(E_ERR, 'replace_wrf_fields', msgstring, & + source, revision, revdate) +endif + +infile = argwords(1) +outfile = argwords(2) + +! make sure the namelist specifies one or the other but not both +if (fieldnames(1) /= '' .and. fieldlist_file /= '') then + call error_handler(E_ERR,'replace_wrf_fields', & + 'cannot specify both fieldnames and fieldlist_file', & + source,revision,revdate) +endif + +call error_handler(E_MSG, 'replace_wrf_fields', ' reading file: '//trim(infile)) +call error_handler(E_MSG, 'replace_wrf_fields', ' overwriting file: '//trim(outfile)) +if (fieldlist_file /= '') then + call error_handler(E_MSG, 'replace_wrf_fields', ' list of fields file: '//trim(fieldlist_file)) + from_file = .true. +else + call error_handler(E_MSG, 'replace_wrf_fields', ' field names specified in namelist.') + from_file = .false. +endif + +! do they exist? can they be opened? +! infile & outfile are netcdf +! fieldlist is ascii, one wrf fieldname per line + +! open the files +call nc_check(nf90_open( infile, NF90_NOWRITE, ncinid), 'nf90_open', 'infile') +call nc_check(nf90_open(outfile, NF90_WRITE, ncoutid), 'nf90_open', 'outfile') + +if (debug) then + call nc_check(nf90_inquire( ncinid, ninDimensions, ninVariables, & + ninAttributes, inunlimitedDimID), 'nf90_inquire', 'infile') + call nc_check(nf90_inquire(ncoutid, noutDimensions, noutVariables, & + noutAttributes, outunlimitedDimID), 'nf90_inquire', 'outfile') + + write(msgstring, *) ' infile ndim, nvar, nattr:', ninDimensions, & + ninVariables, ninAttributes + call error_handler(E_MSG, 'replace_wrf_fields', msgstring) + write(msgstring, *) 'outfile ndim, nvar, nattr:', noutDimensions, & + noutVariables, noutAttributes + call error_handler(E_MSG, 'replace_wrf_fields', msgstring) +endif + +! input file to get data from +! list of netcdf fields to copy over +! output file to be updated in place + +fieldloop : do i=1, 10000 + + if (from_file) then + nextfield = get_next_filename(fieldlist_file, i) + else + nextfield = fieldnames(i) + endif + if (nextfield == '') exit fieldloop + + ! inquire in input for fieldname + ! inquire in output + ncrc = nf90_inq_varid( ncinid, trim(nextfield), invarid) + if (ncrc /= NF90_NOERR) then + tmpstring = ' not found in input file '//trim(infile) + if (etype == E_ERR) then + msgstring = 'variable '//trim(nextfield)//trim(tmpstring) + else + msgstring = 'skipping variable '//trim(nextfield)//','//trim(tmpstring) + endif + call error_handler(etype, 'replace_wrf_fields', msgstring, source, revision, revdate) + cycle fieldloop + endif + ncrc = nf90_inq_varid(ncoutid, trim(nextfield), outvarid) + if (ncrc /= NF90_NOERR) then + tmpstring = ' exists in input file '//trim(infile) + if (etype == E_ERR) then + msgstring = 'variable '//trim(nextfield)//trim(tmpstring) + else + msgstring = 'skipping variable '//trim(nextfield)//','//trim(tmpstring) + endif + msgstring2 = 'but was not found in output file '//trim(outfile) + call error_handler(etype, 'replace_wrf_fields', msgstring, & + source, revision, revdate, text2=msgstring2) + cycle fieldloop + endif + + if (debug) then + write(msgstring, *) ' invarid: ', trim(nextfield)//' ', invarid + call error_handler(E_MSG, 'replace_wrf_fields', msgstring) + write(msgstring, *) 'outvarid: ', trim(nextfield)//' ', outvarid + call error_handler(E_MSG, 'replace_wrf_fields', msgstring) + endif + + ! get dimensions and make sure they match + + call nc_check(nf90_inquire_variable( ncinid, invarid, ndims=ndims, dimids=dimid), & + 'nf90_inquire_variable', 'infile/'//trim(nextfield)) + call nc_check(nf90_inquire_variable(ncoutid, outvarid, ndims=odims, dimids=odimid), & + 'nf90_inquire_variable', 'outfile/'//trim(nextfield)) + + if (ndims /= odims) then + write(msgstring, *) 'variable ', trim(nextfield), & + ' has different numbers of dimensions in the two files' + call error_handler(E_MSG, 'replace_wrf_fields', msgstring) + write(msgstring, *) 'input dimension size ', ndims, ' does not match output ', odims + call error_handler(E_ERR, 'replace_wrf_fields', msgstring, source, revision, revdate) + endif + + do j=1,ndims + call nc_check( nf90_inquire_dimension( ncinid, dimid(j), dimname(j), dimlen(j)), & + 'nf90_inquire_dimension', 'infile/'//trim( dimname(j)) ) + if (debug) then + write(msgstring, '(2A,I5,A,I8,2A)') trim(infile), ' dim: ', j, ' len: ', dimlen(j), ' name: ', trim(dimname(j)) + call error_handler(E_MSG, 'replace_wrf_fields', msgstring) + endif + call nc_check( nf90_inquire_dimension(ncoutid, odimid(j), odimname(j), odimlen(j)), & + 'nf90_inquire_dimension', 'outfile/'//trim(odimname(j)) ) + if (debug) then + write(msgstring, '(2A,I5,A,I8,2A)') trim(outfile), ' dim: ', j, ' len: ', odimlen(j), ' name: ', trim(odimname(j)) + call error_handler(E_MSG, 'replace_wrf_fields', msgstring) + endif + + if (dimlen(j) /= odimlen(j)) then + write(msgstring, *) 'variable ', trim(nextfield), ' has different dimensions in the two files' + call error_handler(E_MSG, 'replace_wrf_fields', msgstring) + write(msgstring, *) 'input dim length ', dimlen(j), ' does not match output ', odimlen(j) + call error_handler(E_ERR, 'replace_wrf_fields', msgstring, source, revision, revdate) + endif + + ! only possible if the unlimited dim is declared but hasn't been written to + if (dimlen(j) == 0) then + write(msgstring, *) trim(nextfield), 'will be skipped because it is empty in input file' + call error_handler(E_MSG, 'replace_wrf_fields', msgstring) + cycle fieldloop + endif + + enddo + + + select case(ndims) + case (1) + write(tmpstring, '(2A,1I5,A)') trim(nextfield), '(', dimlen(1), ')' + case (2) + write(tmpstring, '(2A,2I5,A)') trim(nextfield), '(', dimlen(1:2), ')' + case (3) + write(tmpstring, '(2A,3I5,A)') trim(nextfield), '(', dimlen(1:3), ')' + case (4) + write(tmpstring, '(2A,4I5,A)') trim(nextfield), '(', dimlen(1:4), ')' + case (5) + write(tmpstring, '(2A,5I5,A)') trim(nextfield), '(', dimlen(1:5), ')' + case (6) + write(tmpstring, '(2A,6I5,A)') trim(nextfield), '(', dimlen(1:6), ')' + case (7) + write(tmpstring, '(2A,7I5,A)') trim(nextfield), '(', dimlen(1:7), ')' + case default + ! "can't happen" + write(msgstring, *) 'array dimension is illegal value: ', ndims + call error_handler(E_ERR, 'replace_wrf_fields', msgstring, source, revision, revdate) + end select + + ! announce what we're about to do + write(msgstring, *) 'copying ', trim(tmpstring) + call error_handler(E_MSG, 'replace_wrf_fields', msgstring) + + ! allocate right dim array + ! read/write and then deallocate + + select case(ndims) + case (1) + allocate(oned(dimlen(1))) + call nc_check(nf90_get_var( ncinid, invarid, oned), 'nf90_get_var', 'infile') + call nc_check(nf90_put_var(ncoutid, outvarid, oned), 'nf90_put_var', 'outfile') + deallocate(oned) + case (2) + allocate(twod(dimlen(1),dimlen(2))) + call nc_check(nf90_get_var( ncinid, invarid, twod), 'nf90_get_var', 'infile') + call nc_check(nf90_put_var(ncoutid, outvarid, twod), 'nf90_put_var', 'outfile') + deallocate(twod) + case (3) + allocate(threed(dimlen(1),dimlen(2),dimlen(3))) + call nc_check(nf90_get_var( ncinid, invarid, threed), 'nf90_get_var', 'infile') + call nc_check(nf90_put_var(ncoutid, outvarid, threed), 'nf90_put_var', 'outfile') + deallocate(threed) + case (4) + allocate(fourd(dimlen(1),dimlen(2),dimlen(3),dimlen(4))) + call nc_check(nf90_get_var( ncinid, invarid, fourd), 'nf90_get_var', 'infile') + call nc_check(nf90_put_var(ncoutid, outvarid, fourd), 'nf90_put_var', 'outfile') + deallocate(fourd) + case (5) + allocate(fived(dimlen(1),dimlen(2),dimlen(3),dimlen(4),dimlen(5))) + call nc_check(nf90_get_var( ncinid, invarid, fived), 'nf90_get_var', 'infile') + call nc_check(nf90_put_var(ncoutid, outvarid, fived), 'nf90_put_var', 'outfile') + deallocate(fived) + case (6) + allocate(sixd(dimlen(1),dimlen(2),dimlen(3),dimlen(4),dimlen(5),dimlen(6))) + call nc_check(nf90_get_var( ncinid, invarid, sixd), 'nf90_get_var', 'infile') + call nc_check(nf90_put_var(ncoutid, outvarid, sixd), 'nf90_put_var', 'outfile') + deallocate(sixd) + case (7) + allocate(sevend(dimlen(1),dimlen(2),dimlen(3),dimlen(4),dimlen(5),dimlen(6),dimlen(7))) + call nc_check(nf90_get_var( ncinid, invarid, sevend), 'nf90_get_var', 'infile') + call nc_check(nf90_put_var(ncoutid, outvarid, sevend), 'nf90_put_var', 'outfile') + deallocate(sevend) + case default + ! "really can't happen" + write(msgstring, *) 'array dimension is illegal value: ', ndims + call error_handler(E_ERR, 'replace_wrf_fields', msgstring, source, revision, revdate) + end select + + +enddo fieldloop + +! close up +call nc_check(nf90_close( ncinid), 'nf90_close', 'infile') +call nc_check(nf90_close(ncoutid), 'nf90_close', 'outfile') + +if (debug) then + write(msgstring, *) 'closing files', trim(infile), ' and ', trim(outfile) + call error_handler(E_MSG, 'replace_wrf_fields', msgstring) +endif + +call finalize_utilities('replace_wrf_fields') + +! end of main code + + +contains + +!---------------------------------------------------------------------- + +subroutine initialize_module + + call register_module(source, revision, revdate) + module_initialized = .true. + +end subroutine initialize_module + +!---------------------------------------------------------------------- + +end program + +! +! $URL$ +! $Id$ +! $Revision$ +! $Date$ diff --git a/models/wrf_unified/WRF_DART_utilities/replace_wrf_fields.nml b/models/wrf_unified/WRF_DART_utilities/replace_wrf_fields.nml new file mode 100644 index 0000000000..3ec7b06810 --- /dev/null +++ b/models/wrf_unified/WRF_DART_utilities/replace_wrf_fields.nml @@ -0,0 +1,7 @@ +&replace_wrf_fields_nml + fieldnames = 'SST', + fieldlist_file = '', + fail_on_missing_field = .true. + debug = .false., + / + diff --git a/models/wrf_unified/WRF_DART_utilities/replace_wrf_fields.rst b/models/wrf_unified/WRF_DART_utilities/replace_wrf_fields.rst new file mode 100644 index 0000000000..68d76875ad --- /dev/null +++ b/models/wrf_unified/WRF_DART_utilities/replace_wrf_fields.rst @@ -0,0 +1,102 @@ +PROGRAM ``replace_wrf_fields`` +============================== + +Overview +-------- + +Program to copy various fields from one WRF netCDF file to another. + +There are many existing utilities to process netCDF files, i.e. the NCO operators and NCL scripts, which have more +functionality than this program. The only purpose for having this one is that it is a standalone program with no +prerequisites or dependencies other than the netCDF libraries. If you already have other tools available they can do the +same functions that this program does. + +This program copies the given data fields from the input file to the output file, failing if their sizes, shapes, or +data types do not match exactly. The expected use is to copy fields which are updated by the WRF program but are not +part of the DART state vector, for example, sea surface temperature or soil fields. After DART has updated the WRF +restart ``wrfinput_d01`` file, this program can be used to update other fields in the file before running the model. + +Namelist +-------- + +This namelist is read from the file ``input.nml``. Namelists start with an ampersand '&' and terminate with a slash '/'. +Character strings that contain a '/' must be enclosed in quotes to prevent them from prematurely terminating the +namelist. + +:: + + &replace_wrf_fields_nml + fieldnames = 'SST', + fieldlist_file = '', + fail_on_missing_field = .true. + debug = .false., + / + +| + +.. container:: + + +-----------------------+------------------------+-------------------------------------------------------------------+ + | Item | Type | Description | + +=======================+========================+===================================================================+ + | fieldnames | character(len=129) (:) | An array of ASCII field names to be copied from the input netCDF | + | | | file to the output netCDF file. The names must match exactly, and | + | | | the size and shape of the data must be the same in the input and | + | | | output files for the data to be copied. If the field names are | + | | | set here, the fieldlist_file item must be ' '. | + +-----------------------+------------------------+-------------------------------------------------------------------+ + | fieldlist_file | character(len=129) | An alternative to an explicit list of field names to copy. This | + | | | is a single string, the name of a file which contains a single | + | | | field name, one per line. If this option is set, the fieldnames | + | | | namelist item must be ' '. | + +-----------------------+------------------------+-------------------------------------------------------------------+ + | fail_on_missing_field | logical | If any fields in the input list are not found in either the input | + | | | or output netcdf files, fail if this is set to true. If false, a | + | | | warning message will be printed but execution will continue. | + +-----------------------+------------------------+-------------------------------------------------------------------+ + | debug | logical | If true, print out debugging messages about which fields are | + | | | found in the input and output files. | + +-----------------------+------------------------+-------------------------------------------------------------------+ + +| + +Modules used +------------ + +:: + + types_mod + utilities_mod + parse_args_mod + +Files +----- + +- input namelist ; ``input.nml`` +- Input - output WRF state netCDF files; ``wrfinput_d01, wrfinput_d02, ...`` +- fieldlist_file (if specified in namelist) + +File formats +~~~~~~~~~~~~ + +This utility works on any pair of netCDF files, doing a simple read and copy from one to the other. + +Error codes and conditions +~~~~~~~~~~~~~~~~~~~~~~~~~~ + ++--------------------+---------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| Routine | Message | Comment | ++====================+=========================================================+==============================================================================================================================================================================================+ +| replace_wrf_fields | Usage: echo infile.nc outfile.nc | ./replace_wrf_fields | The program did not read 2 filenames from the console. | ++--------------------+---------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| replace_wrf_fields | cannot specify both fieldnames and fieldlist_file | In the namelist you must either specify an explicit list of fieldnames to copy between the files, or give a single filename which contains the list of field names. You cannot specify both. | ++--------------------+---------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| replace_wrf_fields | field not found in input/output file | If 'fail_on_missing_field' is true in the namelist and a field is not found in either the input or output file. | ++--------------------+---------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| replace_wrf_fields | field does not match | If the input and output files have different sizes, number of dimensions, or data types, the program cannot copy the data. | ++--------------------+---------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + +References +---------- + +- none diff --git a/models/wrf_unified/WRF_DART_utilities/wrf_dart_obs_preprocess.f90 b/models/wrf_unified/WRF_DART_utilities/wrf_dart_obs_preprocess.f90 new file mode 100644 index 0000000000..e47384b522 --- /dev/null +++ b/models/wrf_unified/WRF_DART_utilities/wrf_dart_obs_preprocess.f90 @@ -0,0 +1,2199 @@ +! DART software - Copyright UCAR. This open source software is provided +! by UCAR, "as is", without charge, subject to all terms of use at +! http://www.image.ucar.edu/DAReS/DART/DART_download +! +! $Id$ + +program wrf_dart_obs_preprocess + +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +! +! wrf_dart_obs_preprocess - WRF-DART utility program that at a +! minimum removes all observations outside +! of a WRF domain and add observations from +! supplimental obs sequences. The program +! assumes all data is from one observation +! time. In addition, this program allows +! the user to do the following functions: +! +! - remove observations near the lateral boundaries +! - increase observation error near lateral boundaries +! - remove observations above certain pressure/height levels +! - remove observations where the model and obs topography are large +! - remove significant level rawinsonde data +! - remove rawinsonde observations near TC core +! - superob aircraft and satellite wind data +! +! created Oct. 2007 Ryan Torn, NCAR/MMM +! +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +use types_mod, only : r8, i8 +use obs_sequence_mod, only : obs_sequence_type, static_init_obs_sequence, & + read_obs_seq_header, destroy_obs_sequence, & + get_num_obs, write_obs_seq +use utilities_mod, only : find_namelist_in_file, check_namelist_read, & + initialize_utilities, finalize_utilities +use netcdf_utilities_mod, only : nc_check +use obs_kind_mod, only : RADIOSONDE_U_WIND_COMPONENT, ACARS_U_WIND_COMPONENT, & + MARINE_SFC_U_WIND_COMPONENT, LAND_SFC_U_WIND_COMPONENT, & + METAR_U_10_METER_WIND, GPSRO_REFRACTIVITY, & + SAT_U_WIND_COMPONENT, PROFILER_U_WIND_COMPONENT, VORTEX_LAT +use time_manager_mod, only : time_type, set_calendar_type, GREGORIAN, set_time +use ensemble_manager_mod, only : ensemble_type, init_ensemble_manager, end_ensemble_manager +use model_mod, only : static_init_model +use netcdf + +implicit none + +! ---------------------------------------------------------------------- +! Declare namelist parameters +! ---------------------------------------------------------------------- + +! Generic parameters +character(len=129) :: file_name_input = 'obs_seq.old', & + file_name_output = 'obs_seq.new', & + sonde_extra = 'obs_seq.rawin', & + acars_extra = 'obs_seq.acars', & + land_sfc_extra = 'obs_seq.land_sfc', & + metar_extra = 'obs_seq.metar', & + marine_sfc_extra = 'obs_seq.marine', & + sat_wind_extra = 'obs_seq.satwnd', & + profiler_extra = 'obs_seq.profiler', & + gpsro_extra = 'obs_seq.gpsro', & + trop_cyclone_extra = 'obs_seq.tc' +integer :: max_num_obs = 100000 ! Largest number of obs in one sequence +logical :: overwrite_obs_time = .false. ! true to overwrite all observation times + +! boundary-specific parameters +real(r8) :: obs_boundary = 0.0_r8 ! number of grid points to remove obs near boundary +logical :: increase_bdy_error = .false. ! true to increase obs error near boundary +real(r8) :: maxobsfac = 2.5_r8 ! maximum increase in obs error near boundary +real(r8) :: obsdistbdy = 15.0_r8 ! number of grid points to increase obs err. + +! parameters used to reduce observations +logical :: sfc_elevation_check = .false. ! remove obs where model-obs topography is large +real(r8) :: sfc_elevation_tol = 300.0_r8 ! largest difference between model and obs. topo. +real(r8) :: obs_pressure_top = 0.0_r8 ! remove all obs at lower pressure +real(r8) :: obs_height_top = 2.0e10_r8 ! remove all obs at higher height + +! Rawinsonde-specific parameters +logical :: include_sig_data = .true. ! include significant-level data +real(r8) :: tc_sonde_radii = -1.0_r8 ! remove sonde obs closer than this to TC + +! aircraft-specific parameters +logical :: superob_aircraft = .false. ! super-ob aircraft data +real(r8) :: aircraft_horiz_int = 36.0_r8 ! horizontal interval for super-ob (km) +real(r8) :: aircraft_pres_int = 2500.0_r8 ! pressure interval for super-ob + +! sat wind specific parameters +logical :: superob_sat_winds = .false. ! super-ob sat wind data +real(r8) :: sat_wind_horiz_int = 100.0_r8 ! horizontal interval for super-ob +real(r8) :: sat_wind_pres_int = 2500.0_r8 ! pressure interval for super-ob +logical :: overwrite_ncep_satwnd_qc = .false. ! true to overwrite NCEP QC (see instructions) + +! surface obs. specific parameters +logical :: overwrite_ncep_sfc_qc = .false. ! true to overwrite NCEP QC (see instructions) + +namelist /wrf_obs_preproc_nml/file_name_input, file_name_output, & + include_sig_data, superob_aircraft, superob_sat_winds, & + sfc_elevation_check, overwrite_ncep_sfc_qc, overwrite_ncep_satwnd_qc, & + aircraft_pres_int, sat_wind_pres_int, sfc_elevation_tol, & + obs_pressure_top, obs_height_top, obs_boundary, sonde_extra, metar_extra, & + acars_extra, land_sfc_extra, marine_sfc_extra, sat_wind_extra, profiler_extra, & + trop_cyclone_extra, gpsro_extra, tc_sonde_radii, increase_bdy_error, & + maxobsfac, obsdistbdy, sat_wind_horiz_int, aircraft_horiz_int, & + overwrite_obs_time + +! ---------------------------------------------------------------------- +! Declare other variables +! ---------------------------------------------------------------------- + +character(len=129) :: obs_seq_read_format +character(len=80) :: name + +integer :: io, iunit, fid, var_id, obs_seq_file_id, num_copies, & + num_qc, num_obs, max_obs_seq, nx, ny, gday, gsec + +real(r8) :: real_nx, real_ny + +logical :: file_exist, pre_I_format + +type(obs_sequence_type) :: seq_all, seq_rawin, seq_sfc, seq_acars, seq_satwnd, & + seq_prof, seq_tc, seq_gpsro, seq_other + +type(time_type) :: anal_time + +type(ensemble_type) :: dummy_ens + +call initialize_utilities("wrf_dart_obs_preprocess") + +print*,'Enter target assimilation time (gregorian day, second): ' +read*,gday,gsec +call set_calendar_type(GREGORIAN) +anal_time = set_time(gsec, gday) + +call static_init_obs_sequence() +call static_init_model() +call init_ensemble_manager(dummy_ens, 1, 1_i8) + +call find_namelist_in_file("input.nml", "wrf_obs_preproc_nml", iunit) +read(iunit, nml = wrf_obs_preproc_nml, iostat = io) +call check_namelist_read(iunit, io, "wrf_obs_preproc_nml") + +! open a wrfinput file, which is on this domain +call nc_check( nf90_open(path = "wrfinput_d01", mode = nf90_nowrite, ncid = fid), & + 'main', 'open wrfinput_d01' ) +call nc_check( nf90_inq_dimid(fid, "west_east", var_id), & + 'main', 'inq. dimid west_east' ) +call nc_check( nf90_inquire_dimension(fid, var_id, name, nx), & + 'main', 'inquire dimension west_east' ) +call nc_check( nf90_inq_dimid(fid, "south_north", var_id), & + 'main', 'inq. dimid south_north' ) +call nc_check( nf90_inquire_dimension(fid, var_id, name, ny), & + 'main', 'inquire dimension south_north' ) +call nc_check( nf90_close(fid), 'main', 'close wrfinput_d01' ) + +! several places need a real(r8) version of nx and ny, so set them up +! here so they're ready to use. previous versions of this code used +! the conversions dble(nx) but that doesn't work if you are compiling +! this code with r8 redefined to be r4. on some platforms it corrupts +! the arguments to the function call. these vars are guarenteed to be +! the right size for code compiled with reals as either r8 or r4. +real_nx = nx +real_ny = ny + +! if obs_seq file exists, read in the data, otherwise, create a blank one. +inquire(file = trim(adjustl(file_name_input)), exist = file_exist) +if ( file_exist ) then + + call read_obs_seq_header(file_name_input, num_copies, num_qc, num_obs, max_obs_seq, & + obs_seq_file_id, obs_seq_read_format, pre_I_format, close_the_file = .true.) + +else + + num_copies = 1 ; num_qc = 1 ; max_obs_seq = max_num_obs * 3 + call create_new_obs_seq(num_copies, num_qc, max_obs_seq, seq_all) + +end if + +! create obs sequences for different obs types +call create_new_obs_seq(num_copies, num_qc, max_obs_seq, seq_rawin) +call create_new_obs_seq(num_copies, num_qc, max_obs_seq, seq_sfc) +call create_new_obs_seq(num_copies, num_qc, max_obs_seq, seq_acars) +call create_new_obs_seq(num_copies, num_qc, max_obs_seq, seq_satwnd) +call create_new_obs_seq(num_copies, num_qc, max_obs_seq, seq_prof) +call create_new_obs_seq(num_copies, num_qc, max_obs_seq, seq_gpsro) +call create_new_obs_seq(num_copies, num_qc, 100, seq_tc) +call create_new_obs_seq(num_copies, num_qc, max_obs_seq, seq_other) + +! read input obs_seq file, divide into platforms +call read_and_parse_input_seq(file_name_input, real_nx, real_ny, obs_boundary, & +include_sig_data, obs_pressure_top, obs_height_top, sfc_elevation_check, & +sfc_elevation_tol, overwrite_ncep_sfc_qc, overwrite_ncep_satwnd_qc, & +overwrite_obs_time, anal_time, seq_rawin, seq_sfc, seq_acars, seq_satwnd, & +seq_tc, seq_gpsro, seq_other) + +! add supplimental rawinsonde observations from file +call add_supplimental_obs(sonde_extra, seq_rawin, max_obs_seq, & +RADIOSONDE_U_WIND_COMPONENT, nx, ny, obs_boundary, include_sig_data, & +obs_pressure_top, obs_height_top, sfc_elevation_check, sfc_elevation_tol, & +overwrite_obs_time, anal_time) + +! add supplimental ACARS observations from file +call add_supplimental_obs(acars_extra, seq_acars, max_obs_seq, & +ACARS_U_WIND_COMPONENT, nx, ny, obs_boundary, include_sig_data, & +obs_pressure_top, obs_height_top, sfc_elevation_check, sfc_elevation_tol, & +overwrite_obs_time, anal_time) + +! add supplimental marine observations from file +call add_supplimental_obs(marine_sfc_extra, seq_sfc, max_obs_seq, & +MARINE_SFC_U_WIND_COMPONENT, nx, ny, obs_boundary, include_sig_data, & +obs_pressure_top, obs_height_top, sfc_elevation_check, sfc_elevation_tol, & +overwrite_obs_time, anal_time) + +! add supplimental land surface observations from file +call add_supplimental_obs(land_sfc_extra, seq_sfc, max_obs_seq, & +LAND_SFC_U_WIND_COMPONENT, nx, ny, obs_boundary, include_sig_data, & +obs_pressure_top, obs_height_top, sfc_elevation_check, sfc_elevation_tol, & +overwrite_obs_time, anal_time) + +! add supplimental metar observations from file +call add_supplimental_obs(metar_extra, seq_sfc, max_obs_seq, & +METAR_U_10_METER_WIND, nx, ny, obs_boundary, include_sig_data, & +obs_pressure_top, obs_height_top, sfc_elevation_check, sfc_elevation_tol, & +overwrite_obs_time, anal_time) + +! add supplimental satellite wind observations from file +call add_supplimental_obs(sat_wind_extra, seq_satwnd, max_obs_seq, & +SAT_U_WIND_COMPONENT, nx, ny, obs_boundary, include_sig_data, & +obs_pressure_top, obs_height_top, sfc_elevation_check, sfc_elevation_tol, & +overwrite_obs_time, anal_time) + +! add supplimental profiler observations from file +call add_supplimental_obs(profiler_extra, seq_prof, max_obs_seq, & +PROFILER_U_WIND_COMPONENT, nx, ny, obs_boundary, include_sig_data, & +obs_pressure_top, obs_height_top, sfc_elevation_check, sfc_elevation_tol, & +overwrite_obs_time, anal_time) + +! add supplimental GPSRO observations from file +call add_supplimental_obs(gpsro_extra, seq_gpsro, max_obs_seq, & +GPSRO_REFRACTIVITY, nx, ny, obs_boundary, include_sig_data, & +obs_pressure_top, obs_height_top, sfc_elevation_check, sfc_elevation_tol, & +overwrite_obs_time, anal_time) + +! add supplimental tropical cyclone vortex observations from file +call add_supplimental_obs(trop_cyclone_extra, seq_tc, max_obs_seq, & +VORTEX_LAT, nx, ny, obs_boundary, include_sig_data, & +obs_pressure_top, obs_height_top, sfc_elevation_check, sfc_elevation_tol, & +overwrite_obs_time, anal_time) + +! remove all sonde observations within radius of TC if desired +if ( tc_sonde_radii > 0.0_r8 ) call remove_sondes_near_tc(seq_tc, & + seq_rawin, tc_sonde_radii) + +! super-ob ACARS data +if ( superob_aircraft ) call superob_aircraft_data(seq_acars, anal_time, & + aircraft_horiz_int, aircraft_pres_int) + +! super-ob satellite wind data +if ( superob_sat_winds ) call superob_sat_wind_data(seq_satwnd, anal_time, & + sat_wind_horiz_int, sat_wind_pres_int) + +max_obs_seq = get_num_obs(seq_tc) + get_num_obs(seq_rawin) + & + get_num_obs(seq_sfc) + get_num_obs(seq_acars) + & + get_num_obs(seq_satwnd) + get_num_obs(seq_prof) + & + get_num_obs(seq_gpsro) + get_num_obs(seq_other) + +call create_new_obs_seq(num_copies, num_qc, max_obs_seq, seq_all) + +call build_master_sequence(seq_tc, seq_all) +call destroy_obs_sequence(seq_tc) + +call build_master_sequence(seq_rawin, seq_all) +call destroy_obs_sequence(seq_rawin) + +call build_master_sequence(seq_sfc, seq_all) +call destroy_obs_sequence(seq_sfc) + +call build_master_sequence(seq_acars, seq_all) +call destroy_obs_sequence(seq_acars) + +call build_master_sequence(seq_gpsro, seq_all) +call destroy_obs_sequence(seq_gpsro) + +call build_master_sequence(seq_satwnd, seq_all) +call destroy_obs_sequence(seq_satwnd) + +call build_master_sequence(seq_prof, seq_all) +call destroy_obs_sequence(seq_prof) + +call build_master_sequence(seq_other, seq_all) +call destroy_obs_sequence(seq_other) + +write(6,*) 'Total number of observations:', get_num_obs(seq_all) + +! increase the observation error along the lateral boundary +if ( increase_bdy_error ) call increase_obs_err_bdy(seq_all, & + obsdistbdy, maxobsfac, real_nx, real_ny) + +! write the observation sequence to file +call write_obs_seq(seq_all, file_name_output) +call destroy_obs_sequence(seq_all) + +call finalize_utilities("wrf_dart_obs_preprocess") + +contains + +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +! +! aircraft_obs_check - function that determines whether to include an +! aircraft observation in the sequence. For now, +! this function is a placeholder and returns true. +! +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +function aircraft_obs_check() + +use types_mod, only : r8 + +implicit none + +logical :: aircraft_obs_check + +aircraft_obs_check = .true. + +return +end function aircraft_obs_check + +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +! +! add_supplimental_obs - subroutine that reads observation data from +! a supplimental obs sequence file, performs +! validation checks and adds it to the +! platform-specific obs sequence. +! +! filename - name of supplimental obs sequence file +! obs_seq - platform-specific obs sequence +! max_obs_seq - maximum number of observations in sequence +! plat_kind - integer kind of platform (used for print statements) +! nx - number of grid points in x direction +! ny - number of grid points in y direction +! obs_bdy - grid point buffer to remove observations +! siglevel - true to include sonde significant level data +! ptop - lowest pressure to include in sequence +! htop - highest height level to include in sequence +! sfcelev - true to perform surface obs. elevation check +! elev_max - maximum difference between model and obs. height +! +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +subroutine add_supplimental_obs(filename, obs_seq, max_obs_seq, plat_kind, & + nx, ny, obs_bdy, siglevel, ptop, htop, & + sfcelev, elev_max, overwrite_time, atime) + +use types_mod, only : r8 +use time_manager_mod, only : time_type, operator(>=) +use location_mod, only : location_type, get_location, is_vertical +use obs_sequence_mod, only : obs_sequence_type, obs_type, init_obs, set_obs_def, & + get_num_copies, get_num_qc, read_obs_seq, copy_obs, & + get_first_obs, get_obs_def, get_next_obs, & + get_last_obs, insert_obs_in_seq, destroy_obs_sequence +use obs_def_mod, only : obs_def_type, get_obs_def_type_of_obs, set_obs_def_time, & + get_obs_def_location, get_obs_def_time +use obs_kind_mod, only : RADIOSONDE_U_WIND_COMPONENT, ACARS_U_WIND_COMPONENT, & + LAND_SFC_U_WIND_COMPONENT, MARINE_SFC_U_WIND_COMPONENT, & + METAR_U_10_METER_WIND, GPSRO_REFRACTIVITY, & + SAT_U_WIND_COMPONENT, VORTEX_LAT +use model_mod, only : get_domain_info + +implicit none + +character(len=129), intent(in) :: filename +type(time_type), intent(in) :: atime +type(obs_sequence_type), intent(inout) :: obs_seq +integer, intent(in) :: max_obs_seq, plat_kind, nx, ny +logical, intent(in) :: siglevel, sfcelev, overwrite_time +real(r8), intent(in) :: obs_bdy, ptop, htop, elev_max + +integer :: nloc, okind, dom_id +logical :: file_exist, last_obs, pass_checks, first_obs +real(r8) :: xyz_loc(3), xloc, yloc +real(r8) :: real_nx, real_ny + + +type(location_type) :: obs_loc_list(max_obs_seq), obs_loc +type(obs_def_type) :: obs_def +type(obs_sequence_type) :: supp_obs_seq +type(obs_type) :: obs_in, prev_obsi, prev_obso, obs +type(time_type) :: obs_time, prev_time + +inquire(file = trim(adjustl(filename)), exist = file_exist) +if ( .not. file_exist ) return + +! see comment in main routine about why these are needed +real_nx = nx +real_ny = ny + +select case (plat_kind) + + case (RADIOSONDE_U_WIND_COMPONENT) + write(6,*) 'Adding Supplimental Rawinsonde Data' + case (ACARS_U_WIND_COMPONENT) + write(6,*) 'Adding Supplimental ACARS Data' + case (MARINE_SFC_U_WIND_COMPONENT) + write(6,*) 'Adding Supplimental Marine Surface Data' + case (LAND_SFC_U_WIND_COMPONENT) + write(6,*) 'Adding Supplimental Land Surface Data' + case (METAR_U_10_METER_WIND) + write(6,*) 'Adding Supplimental METAR Data' + case (SAT_U_WIND_COMPONENT) + write(6,*) 'Adding Supplimental Satellite Wind Data' + case (VORTEX_LAT) + write(6,*) 'Adding Supplimental Tropical Cyclone Data' + case (GPSRO_REFRACTIVITY) + write(6,*) 'Adding Supplimental GPS RO Data' + +end select + +call init_obs(obs_in, get_num_copies(obs_seq), get_num_qc(obs_seq)) +call init_obs(obs, get_num_copies(obs_seq), get_num_qc(obs_seq)) +call init_obs(prev_obsi, get_num_copies(obs_seq), get_num_qc(obs_seq)) +call init_obs(prev_obso, get_num_copies(obs_seq), get_num_qc(obs_seq)) + +! create list of observations in plaform sequence +call build_obs_loc_list(obs_seq, max_obs_seq, nloc, obs_loc_list) + +! find the last observation in the sequence +if ( get_last_obs(obs_seq, prev_obso) ) then + + first_obs = .false. + call get_obs_def(prev_obso, obs_def) + prev_time = get_obs_def_time(obs_def) + +else + + first_obs = .true. + +end if + +last_obs = .false. +call read_obs_seq(trim(adjustl(filename)), 0, 0, 0, supp_obs_seq) +if ( .not. get_first_obs(supp_obs_seq, obs_in) ) last_obs = .true. + +ObsLoop: do while ( .not. last_obs ) ! loop over all observations in a sequence + + ! read data from observation + call get_obs_def(obs_in, obs_def) + okind = get_obs_def_type_of_obs(obs_def) + obs_loc = get_obs_def_location(obs_def) + xyz_loc = get_location(obs_loc) + call get_domain_info(xyz_loc(1),xyz_loc(2),dom_id,xloc,yloc) + + ! check if the observation is within the domain + if ( ((xloc < (obs_bdy+1.0_r8) .or. xloc > (real_nx-obs_bdy-1.0_r8) .or. & + yloc < (obs_bdy+1.0_r8) .or. yloc > (real_ny-obs_bdy-1.0_r8)) .and. & + (dom_id == 1)) .or. dom_id < 1 ) then + + prev_obsi = obs_in + call get_next_obs(supp_obs_seq, prev_obsi, obs_in, last_obs) + cycle ObsLoop + + end if + + ! check if the observation is within vertical bounds of domain + if ( (is_vertical(obs_loc, "PRESSURE") .and. xyz_loc(3) < ptop) .or. & + (is_vertical(obs_loc, "HEIGHT") .and. xyz_loc(3) > htop) ) then + + prev_obsi = obs_in + call get_next_obs(supp_obs_seq, prev_obsi, obs_in, last_obs) + cycle ObsLoop + + end if + + ! check if the observation already exists + if ( .not. original_observation(obs_loc, obs_loc_list, nloc) ) then + + prev_obsi = obs_in + call get_next_obs(supp_obs_seq, prev_obsi, obs_in, last_obs) + cycle ObsLoop + + end if + + ! overwrite the observation time with the analysis time if desired + if ( overwrite_time ) then + + call set_obs_def_time(obs_def, atime) + call set_obs_def(obs_in, obs_def) + + end if + + ! perform platform-specific checks + select case (plat_kind) + + case (RADIOSONDE_U_WIND_COMPONENT) + pass_checks = rawinsonde_obs_check(obs_loc, okind, siglevel, & + sfcelev, elev_max) + case (ACARS_U_WIND_COMPONENT) + pass_checks = aircraft_obs_check() + case (MARINE_SFC_U_WIND_COMPONENT) + pass_checks = surface_obs_check(sfcelev, elev_max, xyz_loc) + case (LAND_SFC_U_WIND_COMPONENT) + pass_checks = surface_obs_check(sfcelev, elev_max, xyz_loc) + case (METAR_U_10_METER_WIND) + pass_checks = surface_obs_check(sfcelev, elev_max, xyz_loc) + case (SAT_U_WIND_COMPONENT) + pass_checks = sat_wind_obs_check() + case default + pass_checks = .true. + + end select + + if ( pass_checks ) then + + call copy_obs(obs, obs_in) + call get_obs_def(obs, obs_def) + obs_time = get_obs_def_time(obs_def) + + if (obs_time >= prev_time .and. (.not. first_obs)) then ! same time or later than previous obs + call insert_obs_in_seq(obs_seq, obs, prev_obso) + else ! earlier, search from start of seq + call insert_obs_in_seq(obs_seq, obs) + end if + + first_obs = .false. + prev_obso = obs + prev_time = obs_time + + end if + + prev_obsi = obs_in + call get_next_obs(supp_obs_seq, prev_obsi, obs_in, last_obs) + +end do ObsLoop + +call destroy_obs_sequence(supp_obs_seq) + +return +end subroutine add_supplimental_obs + +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +! +! create_new_obs_seq - subroutine that is used to create a new +! observation sequence. +! +! num_copies - number of copies associated with each observation +! num_qc - number of quality control reports in each obs. +! max_num - maximum number of observations in sequence +! seq - observation sequence +! +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +subroutine create_new_obs_seq(num_copies, num_qc, max_num, seq) + +use obs_sequence_mod, only : obs_sequence_type, init_obs_sequence, & + set_copy_meta_data, set_qc_meta_data + +implicit none + +integer, intent(in) :: num_copies, num_qc, max_num +type(obs_sequence_type), intent(out) :: seq + +character(len=129) :: copy_meta_data, qc_meta_data +integer :: i + +call init_obs_sequence(seq, num_copies, num_qc, max_num) +do i = 1, num_copies + copy_meta_data = 'NCEP BUFR observation' + call set_copy_meta_data(seq, i, copy_meta_data) +end do +do i = 1, num_qc + qc_meta_data = 'NCEP QC index' + call set_qc_meta_data(seq, i, qc_meta_data) +end do + +return +end subroutine create_new_obs_seq + +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +! +! build_master_sequence - subroutine used to take observations from +! a smaller observation sequence and appends +! them to a larger observation sequence. +! Note that this routine only works if the +! observations are at the same time. +! +! seq_type - observation sequence with one observation type +! seq_all - observation sequence with more observations +! +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +subroutine build_master_sequence(seq_type, seq_all) + +use time_manager_mod, only : time_type, operator(>=) +use obs_sequence_mod, only : obs_type, obs_sequence_type, init_obs, & + get_first_obs, copy_obs, insert_obs_in_seq, & + get_next_obs, get_obs_def, get_num_copies, & + get_num_qc, get_obs_def +use obs_def_mod, only : obs_def_type, get_obs_def_time + +implicit none + +type(obs_sequence_type), intent(in) :: seq_type +type(obs_sequence_type), intent(inout) :: seq_all + +logical :: last_obs, first_obs +type(obs_def_type) :: obs_def +type(obs_type) :: obs_in, obs, prev_obsi, prev_obsa +type(time_type) :: obs_time, prev_time + +last_obs = .false. ; first_obs = .true. +call init_obs(obs_in, get_num_copies(seq_type), get_num_qc(seq_type)) +call init_obs(obs, get_num_copies(seq_type), get_num_qc(seq_type)) +call init_obs(prev_obsi, get_num_copies(seq_type), get_num_qc(seq_type)) +call init_obs(prev_obsa, get_num_copies(seq_type), get_num_qc(seq_type)) + +if ( .not. get_first_obs(seq_type, obs_in) ) return + +do while ( .not. last_obs ) + + call copy_obs(obs, obs_in) + call get_obs_def(obs, obs_def) + obs_time = get_obs_def_time(obs_def) + + if (obs_time >= prev_time .and. (.not. first_obs)) then ! same time or later than previous obs + call insert_obs_in_seq(seq_all, obs, prev_obsa) + else ! earlier, search from start of seq + call insert_obs_in_seq(seq_all, obs) + end if + + first_obs = .false. + prev_obsi = obs_in + prev_obsa = obs + prev_time = obs_time + + call get_next_obs(seq_type, prev_obsi, obs_in, last_obs) + +end do + +return +end subroutine build_master_sequence + +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +! +! build_obs_loc_list - subroutine that creates an array of locations +! of the observations in a sequence. +! +! obs_seq - observation sequence to read locations from +! maxobs - maximum number of observations in a sequence +! nloc - number of individual locations +! obs_loc_list - array of observation locations + +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +subroutine build_obs_loc_list(seq, maxobs, nloc, obs_loc_list) + +use location_mod, only : location_type +use obs_sequence_mod, only : obs_sequence_type, get_num_copies, & + get_num_qc, init_obs, get_first_obs, & + get_obs_def, get_next_obs, obs_type +use obs_def_mod, only : obs_def_type, get_obs_def_location + +implicit none + +integer, intent(in) :: maxobs +type(obs_sequence_type), intent(in) :: seq +integer, intent(out) :: nloc +type(location_type), intent(out) :: obs_loc_list(maxobs) + +logical :: last_obs +type(obs_type) :: obs, prev_obs +type(obs_def_type) :: obs_def +type(location_type) :: obs_loc + +call init_obs(obs, get_num_copies(seq), get_num_qc(seq)) +call init_obs(prev_obs, get_num_copies(seq), get_num_qc(seq)) + +last_obs = .false. ; nloc = 0 +if ( .not. get_first_obs(seq, obs) ) last_obs = .true. + +do while ( .not. last_obs ) ! loop over all observations in a sequence + + call get_obs_def(obs, obs_def) + obs_loc = get_obs_def_location(obs_def) + + ! construct a list of observation locations + if ( original_observation(obs_loc, obs_loc_list, nloc) ) then + + nloc = nloc + 1 + obs_loc_list(nloc) = obs_loc + + end if + prev_obs = obs + call get_next_obs(seq, prev_obs, obs, last_obs) + +end do + +return +end subroutine build_obs_loc_list + +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +! +! create_obs_type - subroutine that is used to create an observation +! type from observation data. +! +! lat - latitude of observation +! lon - longitude of observation +! vloc - vertical location of observation +! vcord - DART vertical coordinate integer +! obsv - observation value +! okind - observation kind +! oerr - observation error +! day - gregorian day of the observation +! sec - gregorian second of the observation +! qc - integer quality control value +! obs - observation type that includes the observation information +! +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +subroutine create_obs_type(lat, lon, vloc, vcord, obsv, okind, oerr, qc, otime, obs) + +use types_mod, only : r8 +use obs_sequence_mod, only : obs_type, set_obs_values, set_qc, set_obs_def +use obs_def_mod, only : obs_def_type, set_obs_def_time, set_obs_def_type_of_obs, & + set_obs_def_error_variance, set_obs_def_location +use location_mod, only : location_type, set_location +use time_manager_mod, only : time_type + +implicit none + +integer, intent(in) :: okind, vcord +real(r8), intent(in) :: lat, lon, vloc, obsv, oerr, qc +type(time_type), intent(in) :: otime +type(obs_type), intent(inout) :: obs + +real(r8) :: obs_val(1), qc_val(1) +type(obs_def_type) :: obs_def + +call set_obs_def_location(obs_def, set_location(lon, lat, vloc, vcord)) +call set_obs_def_type_of_obs(obs_def, okind) +call set_obs_def_time(obs_def, otime) +call set_obs_def_error_variance(obs_def, oerr) +call set_obs_def(obs, obs_def) + +obs_val(1) = obsv +call set_obs_values(obs, obs_val) +qc_val(1) = qc +call set_qc(obs, qc_val) + +return +end subroutine create_obs_type + +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +! +! increase_obs_err_bdy - subroutine that increases the observation +! error based on proximity to the lateral +! boundary. +! +! seq - observation sequence +! obsbdy - number of grid points near boundary to increase error +! maxfac - factor to increase observation error at boundary +! nx - number of grid points in the x direction +! ny - number of grid points in the y direction +! +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +subroutine increase_obs_err_bdy(seq, obsbdy, maxfac, nx, ny) + +use types_mod, only : r8 +use location_mod, only : location_type, get_location +use obs_sequence_mod, only : obs_sequence_type, obs_type, init_obs, & + get_num_copies, get_num_qc, get_first_obs, & + get_obs_def, set_obs_def, set_obs, & + get_next_obs, get_obs_key +use obs_def_mod, only : obs_def_type, get_obs_def_error_variance, & + set_obs_def_error_variance, get_obs_def_location +use model_mod, only : get_domain_info + +implicit none + +type(obs_sequence_type), intent(inout) :: seq +real(r8), intent(in) :: obsbdy, maxfac, nx, ny + +integer :: dom_id +logical :: last_obs +real(r8) :: mobse, bobse, xyz_loc(3), xloc, yloc, bdydist, obsfac + +type(obs_def_type) :: obs_def +type(obs_type) :: obs, prev_obs + +write(6,*) 'Increasing the Observation Error Near the Lateral Boundary' + +call init_obs(obs, get_num_copies(seq), get_num_qc(seq)) +call init_obs(prev_obs, get_num_copies(seq), get_num_qc(seq)) + +! compute slope and intercept for error increase factor +mobse = (maxfac - 1.0_r8) / (1.0_r8 - obsbdy) +bobse = maxfac - mobse + +last_obs = .false. +if ( .not. get_first_obs(seq, obs) ) last_obs = .true. + +do while ( .not. last_obs ) + + ! get location information relative to domain 1 (skip nests) + call get_obs_def(obs, obs_def) + xyz_loc = get_location(get_obs_def_location(obs_def)) + call get_domain_info(xyz_loc(1),xyz_loc(2),dom_id,xloc,yloc,1) + + ! compute distance to boundary, increase based on this distance + bdydist = min(xloc-1.0_r8, yloc-1.0_r8, nx-xloc, ny-yloc) + if ( bdydist <= obsbdy ) then + + obsfac = mobse * bdydist + bobse + call set_obs_def_error_variance(obs_def, & + get_obs_def_error_variance(obs_def) * obsfac * obsfac) + call set_obs_def(obs, obs_def) + call set_obs(seq, obs, get_obs_key(obs)) + + end if + prev_obs = obs + call get_next_obs(seq, prev_obs, obs, last_obs) + +end do + +return +end subroutine increase_obs_err_bdy + +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +! +! isManLevel - function that returns a logical true if the input +! pressure level is a mandatory rawinsonde level. +! +! plevel - pressure level to check (Pa) +! +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +function isManLevel(plevel) + +use types_mod, only : r8 + +implicit none + +real(r8), intent(in) :: plevel + +integer, parameter :: nman = 16 +integer :: kk +logical :: isManLevel +real(r8) :: raw_man_levels(nman) = (/ & + 100000.0_r8, 92500.0_r8, 85000.0_r8, 70000.0_r8, 50000.0_r8, 40000.0_r8, & + 30000.0_r8, 25000.0_r8, 20000.0_r8, 15000.0_r8, 10000.0_r8, 7000.0_r8, & + 5000.0_r8, 3000.0_r8, 2000.0_r8, 1000.0_r8 /) + +isManLevel = .false. +do kk = 1, nman + if ( plevel == raw_man_levels(kk) ) then + isManLevel = .true. + return + end if +end do + +return +end function isManLevel + +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +! +! original_observation - function that returns true if the location +! is not within an array of locations +! +! obsloc - location to check +! obsloc_list - array of locations to look through +! nloc - number of locations in array +! +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +function original_observation(obsloc, obsloc_list, nloc) + +use types_mod, only : r8 +use location_mod, only : location_type, get_dist + +real(r8), parameter :: dist_epsilon = 0.00001_r8 + +integer, intent(in) :: nloc +type(location_type), intent(in) :: obsloc, obsloc_list(nloc) + +integer :: n +logical :: original_observation + +original_observation = .true. + +do n = 1, nloc + + if ( get_dist(obsloc, obsloc_list(n), 1, 1, .true.) <= dist_epsilon ) then + original_observation = .false. + return + end if + +end do + +return +end function original_observation + +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +! +! rawinsonde_obs_check - function that performs obsrvation checks +! specific to rawinsonde observations. +! +! obs_loc - observation location +! obs_kind - DART observation kind +! siglevel - true to include significant level data +! elev_check - true to check differene between model and obs elev. +! elev_max - maximum difference between model and obs elevation +! +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +function rawinsonde_obs_check(obs_loc, obs_kind, siglevel, & + elev_check, elev_max) + +use types_mod, only : r8 +use obs_kind_mod, only : RADIOSONDE_SURFACE_ALTIMETER, QTY_SURFACE_ELEVATION +use model_mod, only : model_interpolate +use location_mod, only : location_type, set_location, get_location + +implicit none + +type(location_type), intent(in) :: obs_loc +integer, intent(in) :: obs_kind +logical, intent(in) :: siglevel, elev_check +real(r8), intent(in) :: elev_max + +integer :: istatus(1) +logical :: rawinsonde_obs_check +real(r8) :: xyz_loc(3), xmod(1), hsfc(1) + +rawinsonde_obs_check = .true. +xyz_loc = get_location(obs_loc) + +if ( obs_kind /= RADIOSONDE_SURFACE_ALTIMETER ) then + + ! check if vertical level is mandatory level + if ( (.not. siglevel) .and. (.not. isManLevel(xyz_loc(3))) ) then + rawinsonde_obs_check = .false. + return + end if + +else + + ! perform elevation check for altimeter + if ( elev_check ) then + + call model_interpolate(dummy_ens, 1, obs_loc, QTY_SURFACE_ELEVATION, hsfc, istatus) + if ( abs(hsfc(1) - xyz_loc(3)) > elev_max ) rawinsonde_obs_check = .false. + + end if + +end if + +return +end function rawinsonde_obs_check + +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +! +! read_and_parse_input_seq - subroutine that reads a generic +! observation sequence and divides the +! obs into sequences for each platform. +! +! filename - name of input obs sequence +! nx - number of grid points in x direction +! ny - number of grid points in y direction +! obs_bdy - grid point buffer to remove observations +! siglevel - true to include sonde significant level data +! ptop - lowest pressure to include in sequence +! htop - highest height level to include in sequence +! sfcelev - true to perform surface obs. elevation check +! elev_max - maximum difference between model and obs. height +! new_sfc_qc - true to replace NCEP surface QC +! new_satwnd_qc - true to replace NCEP sat wind QC over ocean +! rawin_seq - rawinsonde sequence +! sfc_seq - surface sequence +! acars_seq - aircraft sequence +! satwnd_seq - satellite wind sequence +! tc_seq - TC data sequence +! other_seq - remaining observation sequence +! +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +subroutine read_and_parse_input_seq(filename, nx, ny, obs_bdy, siglevel, ptop, & + htop, sfcelev, elev_max, new_sfc_qc, & + new_satwnd_qc, overwrite_time, atime, & + rawin_seq, sfc_seq, acars_seq, satwnd_seq, & + tc_seq, gpsro_seq, other_seq) + +use types_mod, only : r8 +use netcdf_utilities_mod, only : nc_open_file_readonly, nc_close_file, & + nc_get_variable +use time_manager_mod, only : time_type +use location_mod, only : location_type, get_location, is_vertical +use obs_sequence_mod, only : obs_sequence_type, obs_type, init_obs, & + get_num_copies, get_num_qc, get_qc_meta_data, & + get_first_obs, get_obs_def, copy_obs, get_num_qc, & + append_obs_to_seq, get_next_obs, get_qc, set_qc, & + destroy_obs_sequence, read_obs_seq, set_obs_def +use obs_def_mod, only : obs_def_type, get_obs_def_type_of_obs, get_obs_def_location, & + set_obs_def_time +use obs_kind_mod, only : RADIOSONDE_U_WIND_COMPONENT, RADIOSONDE_V_WIND_COMPONENT, & + RADIOSONDE_SURFACE_ALTIMETER, RADIOSONDE_TEMPERATURE, & + RADIOSONDE_SPECIFIC_HUMIDITY, RADIOSONDE_DEWPOINT, & + RADIOSONDE_RELATIVE_HUMIDITY, GPSRO_REFRACTIVITY, & + AIRCRAFT_U_WIND_COMPONENT, AIRCRAFT_V_WIND_COMPONENT, & + AIRCRAFT_TEMPERATURE, AIRCRAFT_SPECIFIC_HUMIDITY, & + ACARS_DEWPOINT, ACARS_RELATIVE_HUMIDITY, & + ACARS_U_WIND_COMPONENT, ACARS_V_WIND_COMPONENT, & + ACARS_TEMPERATURE, ACARS_SPECIFIC_HUMIDITY, & + MARINE_SFC_U_WIND_COMPONENT, MARINE_SFC_V_WIND_COMPONENT, & + MARINE_SFC_TEMPERATURE, MARINE_SFC_SPECIFIC_HUMIDITY, & + MARINE_SFC_RELATIVE_HUMIDITY, MARINE_SFC_DEWPOINT, & + LAND_SFC_U_WIND_COMPONENT, LAND_SFC_V_WIND_COMPONENT, & + LAND_SFC_TEMPERATURE, LAND_SFC_SPECIFIC_HUMIDITY, & + LAND_SFC_RELATIVE_HUMIDITY, LAND_SFC_DEWPOINT, & + METAR_U_10_METER_WIND, METAR_V_10_METER_WIND, & + METAR_TEMPERATURE_2_METER, METAR_SPECIFIC_HUMIDITY_2_METER, & + METAR_DEWPOINT_2_METER, METAR_RELATIVE_HUMIDITY_2_METER, & + METAR_ALTIMETER, MARINE_SFC_ALTIMETER, LAND_SFC_ALTIMETER, & + SAT_U_WIND_COMPONENT, SAT_V_WIND_COMPONENT, & + VORTEX_LAT, VORTEX_LON, VORTEX_PMIN, VORTEX_WMAX +use model_mod, only : get_domain_info + +implicit none + +real(r8), parameter :: satwnd_qc_ok = 15.0_r8 +real(r8), parameter :: sfc_qc_ok1 = 9.0_r8 +real(r8), parameter :: sfc_qc_ok2 = 15.0_r8 +real(r8), parameter :: new_qc_value = 2.0_r8 + +character(len=129), intent(in) :: filename +real(r8), intent(in) :: nx, ny, obs_bdy, ptop, htop, elev_max +logical, intent(in) :: siglevel, sfcelev, new_sfc_qc, & + new_satwnd_qc, overwrite_time +type(time_type), intent(in) :: atime +type(obs_sequence_type), intent(inout) :: rawin_seq, sfc_seq, acars_seq, & + satwnd_seq, tc_seq, gpsro_seq, other_seq + +character(len=129) :: qcmeta +integer :: fid, var_id, okind, dom_id, i, j +logical :: file_exist, last_obs, input_ncep_qc +real(r8), allocatable :: xland(:,:), qc(:) +real(r8) :: xyz_loc(3), xloc, yloc + +type(location_type) :: obs_loc +type(obs_def_type) :: obs_def +type(obs_sequence_type) :: seq +type(obs_type) :: obs, obs_in, prev_obs + +inquire(file = trim(adjustl(filename)), exist = file_exist) +if ( .not. file_exist ) return + +call read_obs_seq(filename, 0, 0, 0, seq) + +call init_obs(obs, get_num_copies(seq), get_num_qc(seq)) +call init_obs(obs_in, get_num_copies(seq), get_num_qc(seq)) +call init_obs(prev_obs, get_num_copies(seq), get_num_qc(seq)) +allocate(qc(get_num_qc(seq))) + +! read land distribution +allocate(xland(nint(nx),nint(ny))) + +fid = nc_open_file_readonly("wrfinput_d01", "read_and_parse_input_seq") +call nc_get_variable(fid, "XLAND", xland) +call nc_close_file(fid, "read_and_parse_input_seq") + +input_ncep_qc = .false. +qcmeta = get_qc_meta_data(seq, 1) +if ( trim(adjustl(qcmeta)) == 'NCEP QC index' ) input_ncep_qc = .true. + +last_obs = .false. +if ( .not. get_first_obs(seq, obs_in) ) last_obs = .true. + +InputObsLoop: do while ( .not. last_obs ) ! loop over all observations in a sequence + + ! Get the observation information, check if it is in the domain + call get_obs_def(obs_in, obs_def) + okind = get_obs_def_type_of_obs(obs_def) + obs_loc = get_obs_def_location(obs_def) + xyz_loc = get_location(obs_loc) + call get_domain_info(xyz_loc(1),xyz_loc(2),dom_id,xloc,yloc) + i = nint(xloc); j = nint(yloc) + + ! check horizontal location + if ( ((xloc < (obs_bdy+1.0_r8) .or. xloc > (nx-obs_bdy-1.0_r8) .or. & + yloc < (obs_bdy+1.0_r8) .or. yloc > (ny-obs_bdy-1.0_r8)) .and. & + (dom_id == 1)) .or. dom_id < 1 ) then + + prev_obs = obs_in + call get_next_obs(seq, prev_obs, obs_in, last_obs) + cycle InputObsLoop + + end if + + ! check vertical location + if ( (is_vertical(obs_loc, "PRESSURE") .and. xyz_loc(3) < ptop) .or. & + (is_vertical(obs_loc, "HEIGHT") .and. xyz_loc(3) > htop) ) then + + prev_obs = obs_in + call get_next_obs(seq, prev_obs, obs_in, last_obs) + cycle InputObsLoop + + end if + + ! overwrite the observation time with the analysis time if desired + if ( overwrite_time ) then + + call set_obs_def_time(obs_def, atime) + call set_obs_def(obs_in, obs_def) + + end if + + ! perform platform-specific checks + select case (okind) + + case ( RADIOSONDE_U_WIND_COMPONENT, RADIOSONDE_V_WIND_COMPONENT, & + RADIOSONDE_TEMPERATURE, RADIOSONDE_SPECIFIC_HUMIDITY, & + RADIOSONDE_DEWPOINT, RADIOSONDE_RELATIVE_HUMIDITY, & + RADIOSONDE_SURFACE_ALTIMETER) + + if ( rawinsonde_obs_check(obs_loc, okind, siglevel, sfcelev, elev_max) ) then + + call copy_obs(obs, obs_in) + call append_obs_to_seq(rawin_seq, obs) + + end if + + case ( LAND_SFC_U_WIND_COMPONENT, LAND_SFC_V_WIND_COMPONENT, & + LAND_SFC_TEMPERATURE, LAND_SFC_SPECIFIC_HUMIDITY, & + LAND_SFC_RELATIVE_HUMIDITY, LAND_SFC_DEWPOINT, & + METAR_U_10_METER_WIND, METAR_V_10_METER_WIND, & + METAR_TEMPERATURE_2_METER, METAR_SPECIFIC_HUMIDITY_2_METER, & + METAR_DEWPOINT_2_METER, METAR_RELATIVE_HUMIDITY_2_METER, & + METAR_ALTIMETER, MARINE_SFC_U_WIND_COMPONENT, & + MARINE_SFC_V_WIND_COMPONENT, MARINE_SFC_TEMPERATURE, & + MARINE_SFC_SPECIFIC_HUMIDITY, MARINE_SFC_DEWPOINT, & + MARINE_SFC_RELATIVE_HUMIDITY, LAND_SFC_ALTIMETER, MARINE_SFC_ALTIMETER ) + + if ( surface_obs_check(sfcelev, elev_max, xyz_loc) ) then + + call copy_obs(obs, obs_in) + if ( new_sfc_qc .and. okind /= LAND_SFC_ALTIMETER .and. & + okind /= METAR_ALTIMETER .and. okind /= MARINE_SFC_ALTIMETER ) then + + call get_qc(obs, qc) + if ( (qc(1) == sfc_qc_ok1 .or. qc(1) == sfc_qc_ok2) .and. input_ncep_qc ) then + qc(1) = new_qc_value + call set_qc(obs, qc) + end if + + end if + call append_obs_to_seq(sfc_seq, obs) + + endif + + case ( AIRCRAFT_U_WIND_COMPONENT, AIRCRAFT_V_WIND_COMPONENT, & + AIRCRAFT_TEMPERATURE, AIRCRAFT_SPECIFIC_HUMIDITY, & + ACARS_RELATIVE_HUMIDITY, ACARS_DEWPOINT, & + ACARS_U_WIND_COMPONENT, ACARS_V_WIND_COMPONENT, & + ACARS_TEMPERATURE, ACARS_SPECIFIC_HUMIDITY ) + + if ( aircraft_obs_check() ) then + + call copy_obs(obs, obs_in) + call append_obs_to_seq(acars_seq, obs) + + end if + + case ( SAT_U_WIND_COMPONENT, SAT_V_WIND_COMPONENT ) + + if ( sat_wind_obs_check() ) then + + call copy_obs(obs, obs_in) + if ( new_satwnd_qc ) then + + call get_qc(obs, qc) + if ( qc(1) == satwnd_qc_ok .and. input_ncep_qc .and. & + xland(i,j) > 1.0_r8 ) then + qc(1) = new_qc_value + call set_qc(obs, qc) + end if + + end if + call append_obs_to_seq(satwnd_seq, obs) + + endif + + case ( VORTEX_LAT, VORTEX_LON, VORTEX_PMIN, VORTEX_WMAX ) + + call copy_obs(obs, obs_in) + call append_obs_to_seq(tc_seq, obs) + + case ( GPSRO_REFRACTIVITY ) + + call copy_obs(obs, obs_in) + call append_obs_to_seq(gpsro_seq, obs) + + case default + + call copy_obs(obs, obs_in) + call append_obs_to_seq(other_seq, obs) + + end select + + prev_obs = obs_in + call get_next_obs(seq, prev_obs, obs_in, last_obs) + +end do InputObsLoop +call destroy_obs_sequence(seq) + +return +end subroutine read_and_parse_input_seq + +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +! +! remove_sondes_near_tc - subroutine that removes all rawinsonde +! observations within a certain distance of +! a TC center. +! +! obs_seq_tc - TC observation sequence +! obs_seq_rawin - rawinsonde observation sequence +! sonde_radii - observation removal distance +! +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +subroutine remove_sondes_near_tc(seq_tc, seq_rawin, sonde_radii) + +use types_mod, only : r8, earth_radius +use obs_sequence_mod, only : obs_sequence_type, init_obs, get_num_copies, & + get_num_qc, get_first_obs, get_obs_def, & + get_next_obs, delete_obs_from_seq, obs_type, & + get_next_obs_from_key, get_obs_key +use obs_def_mod, only : obs_def_type, get_obs_def_location +use location_mod, only : location_type, operator(==), get_dist + +implicit none + +type(obs_sequence_type), intent(in) :: seq_tc +type(obs_sequence_type), intent(inout) :: seq_rawin +real(r8), intent(in) :: sonde_radii + +integer :: numtc, n +logical :: last_obs, not_in_list, use_obs, first_obs + +type(location_type) :: obs_loc, loctc(20) +type(obs_def_type) :: obs_def +type(obs_type) :: obs, prev_obs + +write(6,*) 'Removing Sonde Data near TC' +call init_obs(obs, get_num_copies(seq_rawin), get_num_qc(seq_rawin)) +call init_obs(prev_obs, get_num_copies(seq_rawin), get_num_qc(seq_rawin)) + +last_obs = .false. ; numtc = 0 +if ( .not. get_first_obs(seq_tc, obs) ) last_obs = .true. + +! loop over all TC observations, find locations +do while ( .not. last_obs ) + + call get_obs_def(obs, obs_def) + obs_loc = get_obs_def_location(obs_def) + not_in_list = .true. + do n = 1, numtc + if ( obs_loc == loctc(n) ) not_in_list = .false. + end do + if ( not_in_list ) then + numtc = numtc + 1 + loctc(numtc) = obs_loc + end if + + prev_obs = obs + call get_next_obs(seq_tc, prev_obs, obs, last_obs) + +end do + +if ( numtc == 0 ) return + +last_obs = .false. ; first_obs = .true. +if ( .not. get_first_obs(seq_rawin, obs) ) last_obs = .true. +do while ( .not. last_obs ) ! loop over all rawinsonde obs, remove too close to TC + + call get_obs_def(obs, obs_def) + obs_loc = get_obs_def_location(obs_def) + + use_obs = .true. + do n = 1, numtc + if ( (get_dist(obs_loc,loctc(n),2,2,.true.) * earth_radius) <= sonde_radii ) use_obs = .false. + end do + + if ( use_obs ) then + + prev_obs = obs + call get_next_obs(seq_rawin, prev_obs, obs, last_obs) + first_obs = .false. + + else + + if ( first_obs ) then + call delete_obs_from_seq(seq_rawin, obs) + if( .not. get_first_obs(seq_rawin, obs) ) return + else + call delete_obs_from_seq(seq_rawin, obs) + call get_next_obs_from_key(seq_rawin, get_obs_key(prev_obs), obs, last_obs) + end if + + end if + +end do + +return +end subroutine remove_sondes_near_tc + +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +! +! sat_wind_obs_check - function that determines whether to include an +! satellite wind observation in the sequence. +! For now, this function is a placeholder and +! returns true. +! +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +function sat_wind_obs_check() + +use types_mod, only : r8 + +implicit none + +logical :: sat_wind_obs_check + +sat_wind_obs_check = .true. + +return +end function sat_wind_obs_check + +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +! +! superob_aircraft_data - subroutine that creates superobs of +! aircraft data based on the given +! horizontal and vertical intervals. +! +! seq - aircraft observation sequence +! hdist - horizontal interval of superobs +! vdist - vertical interval of superobs +! +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +subroutine superob_aircraft_data(seq, atime, hdist, vdist) + +use types_mod, only : r8, missing_r8, earth_radius +use time_manager_mod, only : time_type +use location_mod, only : location_type, get_dist, operator(==), & + get_location, VERTISPRESSURE +use obs_sequence_mod, only : obs_sequence_type, obs_type, init_obs, & + get_num_copies, get_num_qc, get_first_obs, & + get_next_obs, destroy_obs_sequence, get_qc, & + get_num_obs, get_obs_values, get_obs_def, & + append_obs_to_seq +use obs_def_mod, only : obs_def_type, get_obs_def_location, & + get_obs_def_type_of_obs, get_obs_def_error_variance, & + get_obs_def_time +use obs_kind_mod, only : AIRCRAFT_U_WIND_COMPONENT, ACARS_U_WIND_COMPONENT, & + AIRCRAFT_V_WIND_COMPONENT, ACARS_V_WIND_COMPONENT, & + AIRCRAFT_TEMPERATURE, ACARS_TEMPERATURE, & + AIRCRAFT_SPECIFIC_HUMIDITY, ACARS_SPECIFIC_HUMIDITY, & + ACARS_DEWPOINT, ACARS_RELATIVE_HUMIDITY + +implicit none + +type(obs_sequence_type), intent(inout) :: seq +type(time_type), intent(in) :: atime +real(r8), intent(in) :: hdist, vdist + +integer :: num_copies, num_qc, nloc, k, locdex, obs_kind, n, & + num_obs, poleward_obs +logical :: last_obs, close_to_greenwich +real(r8) :: nuwnd, latu, lonu, preu, uwnd, erru, qcu, nvwnd, latv, & + lonv, prev, vwnd, errv, qcv, ntmpk, latt, lont, pret, & + tmpk, errt, qct, nqvap, latq, lonq, preq, qvap, errq, & + dwpt, errd, qcd, ndwpt, latd, lond, pred, relh, errr, & + qcr, nrelh, latr, lonr, prer, qcq, obs_dist, & + xyz_loc(3), obs_val(1), qc_val(1), lon_degree_limit +type(location_type) :: obs_loc +type(obs_def_type) :: obs_def +type(obs_type) :: obs, prev_obs + +type airobs_type + + real(r8) :: lat, lon, pressure, uwnd, uwnd_err, uwnd_qc, & + vwnd, vwnd_err, vwnd_qc, tmpk, tmpk_err, tmpk_qc, & + qvap, qvap_err, qvap_qc, dwpt, dwpt_err, dwpt_qc, & + relh, relh_err, relh_qc + type(location_type) :: obs_loc + type(time_type) :: time + +end type airobs_type + +type(airobs_type), allocatable :: airobs(:) + +write(6,*) 'Super-Obing Aircraft Data' + +num_copies = get_num_copies(seq) +num_qc = get_num_qc(seq) +num_obs = get_num_obs(seq) + +allocate(airobs(num_obs)) +call init_obs(obs, num_copies, num_qc) +call init_obs(prev_obs, num_copies, num_qc) + +last_obs = .false. ; nloc = 0 ; poleward_obs = 0 +if ( .not. get_first_obs(seq, obs) ) last_obs = .true. + +! loop over all observations in sequence, add to ACARS observation type +do while ( .not. last_obs ) + + call get_obs_values(obs, obs_val, 1) + call get_qc(obs, qc_val, 1) + + call get_obs_def(obs, obs_def) + obs_loc = get_obs_def_location(obs_def) + obs_kind = get_obs_def_type_of_obs(obs_def) + xyz_loc = get_location(obs_loc) + + locdex = -1 + do k = nloc, 1, -1 + + if ( obs_loc == airobs(k)%obs_loc ) then + locdex = k + exit + end if + + end do + + if ( locdex < 1 ) then ! create new observation location type + + ! test if we are within hdist of either pole, and punt for now on those + ! obs because we can't accurately average points that wrap the poles. + ! (count up obs here and print later) + if (pole_check(xyz_loc(1), xyz_loc(2), hdist)) then + poleward_obs = poleward_obs + 1 + goto 200 + endif + + nloc = nloc + 1 + locdex = nloc + + + airobs(locdex)%lon = xyz_loc(1) + airobs(locdex)%lat = xyz_loc(2) + airobs(locdex)%pressure = xyz_loc(3) + airobs(locdex)%obs_loc = obs_loc + airobs(locdex)%uwnd = missing_r8 + airobs(locdex)%vwnd = missing_r8 + airobs(locdex)%tmpk = missing_r8 + airobs(locdex)%qvap = missing_r8 + airobs(locdex)%dwpt = missing_r8 + airobs(locdex)%relh = missing_r8 + airobs(locdex)%time = get_obs_def_time(obs_def) + + end if + + ! add observation data to type + if ( obs_kind == AIRCRAFT_U_WIND_COMPONENT .or. obs_kind == ACARS_U_WIND_COMPONENT ) then + + airobs(locdex)%uwnd = obs_val(1) + airobs(locdex)%uwnd_qc = qc_val(1) + airobs(locdex)%uwnd_err = get_obs_def_error_variance(obs_def) + + else if ( obs_kind == AIRCRAFT_V_WIND_COMPONENT .or. obs_kind == ACARS_V_WIND_COMPONENT ) then + + airobs(locdex)%vwnd = obs_val(1) + airobs(locdex)%vwnd_qc = qc_val(1) + airobs(locdex)%vwnd_err = get_obs_def_error_variance(obs_def) + + else if ( obs_kind == AIRCRAFT_TEMPERATURE .or. obs_kind == ACARS_TEMPERATURE ) then + + airobs(locdex)%tmpk = obs_val(1) + airobs(locdex)%tmpk_qc = qc_val(1) + airobs(locdex)%tmpk_err = get_obs_def_error_variance(obs_def) + + else if ( obs_kind == AIRCRAFT_SPECIFIC_HUMIDITY .or. obs_kind == ACARS_SPECIFIC_HUMIDITY ) then + + airobs(locdex)%qvap = obs_val(1) + airobs(locdex)%qvap_qc = qc_val(1) + airobs(locdex)%qvap_err = get_obs_def_error_variance(obs_def) + + else if ( obs_kind == ACARS_DEWPOINT ) then + + airobs(locdex)%dwpt = obs_val(1) + airobs(locdex)%dwpt_qc = qc_val(1) + airobs(locdex)%dwpt_err = get_obs_def_error_variance(obs_def) + + else if ( obs_kind == ACARS_RELATIVE_HUMIDITY ) then + + airobs(locdex)%relh = obs_val(1) + airobs(locdex)%relh_qc = qc_val(1) + airobs(locdex)%relh_err = get_obs_def_error_variance(obs_def) + + + end if + +200 continue ! come here to skip this obs + + prev_obs = obs + call get_next_obs(seq, prev_obs, obs, last_obs) + +end do + +if (poleward_obs > 0) then + write(6, *) 'WARNING: skipped ', poleward_obs, ' of ', poleward_obs+nloc, ' aircraft obs because' + write(6, *) 'they were within ', hdist, ' KM of the poles (the superobs distance).' +endif + +call destroy_obs_sequence(seq) +call create_new_obs_seq(num_copies, num_qc, num_obs, seq) +call init_obs(obs, num_copies, num_qc) + +do k = 1, nloc ! loop over all observation locations + + nuwnd = 0.0_r8 ; latu = 0.0_r8 ; lonu = 0.0_r8 ; preu = 0.0_r8 + uwnd = 0.0_r8 ; erru = 0.0_r8 ; qcu = 0.0_r8 + nvwnd = 0.0_r8 ; latv = 0.0_r8 ; lonv = 0.0_r8 ; prev = 0.0_r8 + vwnd = 0.0_r8 ; errv = 0.0_r8 ; qcv = 0.0_r8 + ntmpk = 0.0_r8 ; latt = 0.0_r8 ; lont = 0.0_r8 ; pret = 0.0_r8 + tmpk = 0.0_r8 ; errt = 0.0_r8 ; qct = 0.0_r8 + nqvap = 0.0_r8 ; latq = 0.0_r8 ; lonq = 0.0_r8 ; preq = 0.0_r8 + qvap = 0.0_r8 ; errq = 0.0_r8 ; qcq = 0.0_r8 + ndwpt = 0.0_r8 ; latd = 0.0_r8 ; lond = 0.0_r8 ; pred = 0.0_r8 + dwpt = 0.0_r8 ; errd = 0.0_r8 ; qcd = 0.0_r8 + nrelh = 0.0_r8 ; latr = 0.0_r8 ; lonr = 0.0_r8 ; prer = 0.0_r8 + relh = 0.0_r8 ; errr = 0.0_r8 ; qcr = 0.0_r8 + + + if ( airobs(k)%lat /= missing_r8 ) then ! create initial superob + + call superob_location_check(airobs(k)%lon, airobs(k)%lat, hdist, & + close_to_greenwich, lon_degree_limit) + if (close_to_greenwich) call wrap_lon(airobs(k)%lon, airobs(k)%lon - lon_degree_limit, & + airobs(k)%lon + lon_degree_limit) + + if ( airobs(k)%uwnd /= missing_r8 ) then + nuwnd = nuwnd + 1.0_r8 + latu = latu + airobs(k)%lat + lonu = lonu + airobs(k)%lon + preu = preu + airobs(k)%pressure + uwnd = uwnd + airobs(k)%uwnd + erru = erru + airobs(k)%uwnd_err + qcu = max(qcu,airobs(k)%uwnd_qc) + end if + + if ( airobs(k)%vwnd /= missing_r8 ) then + nvwnd = nvwnd + 1.0_r8 + latv = latv + airobs(k)%lat + lonv = lonv + airobs(k)%lon + prev = prev + airobs(k)%pressure + vwnd = vwnd + airobs(k)%vwnd + errv = errv + airobs(k)%vwnd_err + qcv = max(qcv,airobs(k)%vwnd_qc) + end if + + if ( airobs(k)%tmpk /= missing_r8 ) then + ntmpk = ntmpk + 1.0_r8 + latt = latt + airobs(k)%lat + lont = lont + airobs(k)%lon + pret = pret + airobs(k)%pressure + tmpk = tmpk + airobs(k)%tmpk + errt = errt + airobs(k)%tmpk_err + qct = max(qct,airobs(k)%tmpk_qc) + end if + + if ( airobs(k)%qvap /= missing_r8 ) then + nqvap = nqvap + 1.0_r8 + latq = latq + airobs(k)%lat + lonq = lonq + airobs(k)%lon + preq = preq + airobs(k)%pressure + qvap = qvap + airobs(k)%qvap + errq = errq + airobs(k)%qvap_err + qcq = max(qcq,airobs(k)%qvap_qc) + end if + + if ( airobs(k)%dwpt /= missing_r8 ) then + ndwpt = ndwpt + 1.0_r8 + latd = latd + airobs(k)%lat + lond = lond + airobs(k)%lon + pred = pred + airobs(k)%pressure + dwpt = dwpt + airobs(k)%dwpt + errd = errd + airobs(k)%dwpt_err + qcd = max(qcd,airobs(k)%dwpt_qc) + end if + + if ( airobs(k)%relh /= missing_r8 ) then + nrelh = nrelh + 1.0_r8 + latr = latr + airobs(k)%lat + lonr = lonr + airobs(k)%lon + prer = prer + airobs(k)%pressure + relh = relh + airobs(k)%relh + errr = errr + airobs(k)%relh_err + qcr = max(qcr,airobs(k)%relh_qc) + end if + + do n = (k+1), nloc + + if ( airobs(n)%lat /= missing_r8 ) then + + ! add observation to superob if within the horizontal and vertical bounds + obs_dist = get_dist(airobs(k)%obs_loc, airobs(n)%obs_loc, 2, 2, .true.) * earth_radius + if ( obs_dist <= hdist .and. abs(airobs(k)%pressure-airobs(n)%pressure) <= vdist ) then + + if (close_to_greenwich) call wrap_lon(airobs(n)%lon, airobs(k)%lon - lon_degree_limit, & + airobs(k)%lon + lon_degree_limit) + + if ( airobs(n)%uwnd /= missing_r8 ) then + nuwnd = nuwnd + 1.0_r8 + latu = latu + airobs(n)%lat + lonu = lonu + airobs(n)%lon + preu = preu + airobs(n)%pressure + uwnd = uwnd + airobs(n)%uwnd + erru = erru + airobs(n)%uwnd_err + qcu = max(qcu,airobs(n)%uwnd_qc) + end if + + if ( airobs(n)%vwnd /= missing_r8 ) then + nvwnd = nvwnd + 1.0_r8 + latv = latv + airobs(n)%lat + lonv = lonv + airobs(n)%lon + prev = prev + airobs(n)%pressure + vwnd = vwnd + airobs(n)%vwnd + errv = errv + airobs(n)%vwnd_err + qcv = max(qcv,airobs(n)%vwnd_qc) + end if + + if ( airobs(n)%tmpk /= missing_r8 ) then + ntmpk = ntmpk + 1.0_r8 + latt = latt + airobs(n)%lat + lont = lont + airobs(n)%lon + pret = pret + airobs(n)%pressure + tmpk = tmpk + airobs(n)%tmpk + errt = errt + airobs(n)%tmpk_err + qct = max(qct,airobs(n)%tmpk_qc) + end if + + if ( airobs(n)%qvap /= missing_r8 ) then + nqvap = nqvap + 1.0_r8 + latq = latq + airobs(n)%lat + lonq = lonq + airobs(n)%lon + preq = preq + airobs(n)%pressure + qvap = qvap + airobs(n)%qvap + errq = errq + airobs(n)%qvap_err + qcq = max(qcq,airobs(n)%qvap_qc) + end if + + if ( airobs(n)%dwpt /= missing_r8 ) then + ndwpt = ndwpt + 1.0_r8 + latd = latd + airobs(n)%lat + lond = lond + airobs(n)%lon + pred = pred + airobs(n)%pressure + dwpt = dwpt + airobs(n)%dwpt + errd = errd + airobs(n)%dwpt_err + qcd = max(qcd,airobs(n)%dwpt_qc) + end if + + if ( airobs(n)%relh /= missing_r8 ) then + nrelh = nrelh + 1.0_r8 + latr = latr + airobs(n)%lat + lonr = lonr + airobs(n)%lon + prer = prer + airobs(n)%pressure + relh = relh + airobs(n)%relh + errr = errr + airobs(n)%relh_err + qcr = max(qcr,airobs(n)%relh_qc) + end if + + airobs(n)%lat = missing_r8 + + end if + + end if + + end do + + if ( nuwnd > 0.0_r8 ) then ! write zonal wind superob + + latu = latu / nuwnd + lonu = lonu / nuwnd + if ( lonu >= 360.0_r8 ) lonu = lonu - 360.0_r8 + preu = preu / nuwnd + uwnd = uwnd / nuwnd + erru = erru / nuwnd + + call create_obs_type(latu, lonu, preu, VERTISPRESSURE, uwnd, & + ACARS_U_WIND_COMPONENT, erru, qcu, atime, obs) + call append_obs_to_seq(seq, obs) + + end if + + if ( nvwnd > 0.0_r8 ) then ! write meridional wind superob + + latv = latv / nvwnd + lonv = lonv / nvwnd + if ( lonv >= 360.0_r8 ) lonv = lonv - 360.0_r8 + prev = prev / nvwnd + vwnd = vwnd / nvwnd + errv = errv / nvwnd + + call create_obs_type(latv, lonv, prev, VERTISPRESSURE, vwnd, & + ACARS_V_WIND_COMPONENT, errv, qcv, atime, obs) + call append_obs_to_seq(seq, obs) + + end if + + if ( ntmpk > 0.0_r8 ) then ! write temperature superob + + latt = latt / ntmpk + lont = lont / ntmpk + if ( lont >= 360.0_r8 ) lont = lont - 360.0_r8 + pret = pret / ntmpk + tmpk = tmpk / ntmpk + errt = errt / ntmpk + + call create_obs_type(latt, lont, pret, VERTISPRESSURE, tmpk, & + ACARS_TEMPERATURE, errt, qct, atime, obs) + call append_obs_to_seq(seq, obs) + + end if + + if ( nqvap > 0.0_r8 ) then ! write qvapor superob + + latq = latq / nqvap + lonq = lonq / nqvap + if ( lonq >= 360.0_r8 ) lonq = lonq - 360.0_r8 + preq = preq / nqvap + qvap = qvap / nqvap + errq = errq / nqvap + + call create_obs_type(latq, lonq, preq, VERTISPRESSURE, qvap, & + ACARS_SPECIFIC_HUMIDITY, errq, qcq, atime, obs) + call append_obs_to_seq(seq, obs) + + end if + + if ( ndwpt > 0.0_r8 ) then ! write dewpoint temperature superob + + latd = latd / ndwpt + lond = lond / ndwpt + if ( lond >= 360.0_r8 ) lond = lond - 360.0_r8 + pred = pred / ndwpt + dwpt = dwpt / ndwpt + errd = errd / ndwpt + + call create_obs_type(latd, lond, pred, VERTISPRESSURE, dwpt, & + ACARS_DEWPOINT, errd, qcd, atime, obs) + call append_obs_to_seq(seq, obs) + + end if + + if ( nrelh > 0.0_r8 ) then ! write relative humidity superob + + latr = latr / nrelh + lonr = lonr / nrelh + if ( lonr >= 360.0_r8 ) lonr = lonr - 360.0_r8 + prer = prer / nrelh + relh = relh / nrelh + errr = errr / nrelh + + call create_obs_type(latr, lonr, prer, VERTISPRESSURE, relh, & + ACARS_RELATIVE_HUMIDITY, errr, qcr, atime, obs) + call append_obs_to_seq(seq, obs) + + end if + + end if + +end do + +return +end subroutine superob_aircraft_data + +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +! +! superob_sat_wind_data - subroutine that creates superobs of +! satellite wind data based on the given +! horizontal and vertical intervals. +! +! seq - satellite wind observation sequence +! hdist - horizontal interval of superobs +! vdist - vertical interval of superobs +! +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +subroutine superob_sat_wind_data(seq, atime, hdist, vdist) + +use types_mod, only : r8, missing_r8, earth_radius +use time_manager_mod, only : time_type +use location_mod, only : location_type, get_dist, operator(==), & + get_location, VERTISPRESSURE +use obs_sequence_mod, only : obs_sequence_type, obs_type, init_obs, & + get_num_copies, get_num_qc, get_first_obs, & + get_next_obs, destroy_obs_sequence, get_qc, & + get_num_obs, get_obs_values, get_obs_def, & + append_obs_to_seq +use obs_def_mod, only : obs_def_type, get_obs_def_location, & + get_obs_def_type_of_obs, get_obs_def_error_variance, & + get_obs_def_time +use obs_kind_mod, only : SAT_U_WIND_COMPONENT, SAT_V_WIND_COMPONENT + +implicit none + +type(obs_sequence_type), intent(inout) :: seq +type(time_type), intent(in) :: atime +real(r8), intent(in) :: hdist, vdist + +integer :: num_copies, num_qc, nloc, k, locdex, obs_kind, n, & + num_obs, poleward_obs +logical :: last_obs, close_to_greenwich +real(r8) :: nwnd, lat, lon, pres, uwnd, erru, qcu, vwnd, & + errv, qcv, obs_dist, xyz_loc(3), obs_val(1), qc_val(1), & + lon_degree_limit + +type(location_type) :: obs_loc +type(obs_def_type) :: obs_def +type(obs_type) :: obs, prev_obs + +type satobs_type + + real(r8) :: lat, lon, pressure, uwnd, uwnd_err, uwnd_qc, & + vwnd, vwnd_err, vwnd_qc + type(location_type) :: obs_loc + type(time_type) :: time + +end type satobs_type + +type(satobs_type), allocatable :: satobs(:) + +write(6,*) 'Super-Obing Satellite Wind Data' + +num_copies = get_num_copies(seq) +num_qc = get_num_qc(seq) +num_obs = get_num_obs(seq) + +allocate(satobs(num_obs/2)) +call init_obs(obs, num_copies, num_qc) +call init_obs(prev_obs, num_copies, num_qc) + +last_obs = .false. ; nloc = 0 ; poleward_obs = 0 +if ( .not. get_first_obs(seq, obs) ) last_obs = .true. + +! loop over satellite winds, create list +do while ( .not. last_obs ) + + call get_obs_values(obs, obs_val, 1) + call get_qc(obs, qc_val, 1) + + call get_obs_def(obs, obs_def) + obs_loc = get_obs_def_location(obs_def) + obs_kind = get_obs_def_type_of_obs(obs_def) + xyz_loc = get_location(obs_loc) + + ! determine if observation exists + locdex = -1 + do k = nloc, 1, -1 + + if ( obs_loc == satobs(k)%obs_loc ) then + locdex = k + exit + end if + + end do + + if ( locdex < 1 ) then ! create new observation type + + ! test if we are within hdist of either pole, and punt for now on those + ! obs because we can't accurately average points that wrap the poles. + ! (hdist is radius, in KM, of region of interest.) + if (pole_check(xyz_loc(1), xyz_loc(2), hdist)) then + ! count up obs here and print later + poleward_obs = poleward_obs + 1 + goto 200 + endif + + nloc = nloc + 1 + locdex = nloc + + satobs(locdex)%lon = xyz_loc(1) + satobs(locdex)%lat = xyz_loc(2) + satobs(locdex)%pressure = xyz_loc(3) + satobs(locdex)%obs_loc = obs_loc + satobs(locdex)%uwnd = missing_r8 + satobs(locdex)%vwnd = missing_r8 + satobs(locdex)%time = get_obs_def_time(obs_def) + + end if + + ! add observation information + if ( obs_kind == SAT_U_WIND_COMPONENT ) then + + satobs(locdex)%uwnd = obs_val(1) + satobs(locdex)%uwnd_qc = qc_val(1) + satobs(locdex)%uwnd_err = get_obs_def_error_variance(obs_def) + + else if ( obs_kind == SAT_V_WIND_COMPONENT ) then + + satobs(locdex)%vwnd = obs_val(1) + satobs(locdex)%vwnd_qc = qc_val(1) + satobs(locdex)%vwnd_err = get_obs_def_error_variance(obs_def) + + end if + +200 continue ! come here to skip this obs + + prev_obs = obs + call get_next_obs(seq, prev_obs, obs, last_obs) + +end do + +if (poleward_obs > 0) then + write(6, *) 'WARNING: skipped ', poleward_obs, ' of ', poleward_obs+nloc, ' satwind obs because' + write(6, *) 'they were within ', hdist, ' KM of the poles (the superobs distance).' +endif + +! create new sequence +call destroy_obs_sequence(seq) +call create_new_obs_seq(num_copies, num_qc, num_obs, seq) +call init_obs(obs, num_copies, num_qc) + +do k = 1, nloc ! loop over all locations + + if ( satobs(k)%uwnd /= missing_r8 .and. satobs(k)%vwnd /= missing_r8 ) then + + call superob_location_check(satobs(k)%lon, satobs(k)%lat, hdist, & + close_to_greenwich, lon_degree_limit) + if (close_to_greenwich) call wrap_lon(satobs(k)%lon, satobs(k)%lon - lon_degree_limit, & + satobs(k)%lon + lon_degree_limit) + + nwnd = 1.0_r8 + lat = satobs(k)%lat + lon = satobs(k)%lon + pres = satobs(k)%pressure + uwnd = satobs(k)%uwnd + erru = satobs(k)%uwnd_err + qcu = satobs(k)%uwnd_qc + vwnd = satobs(k)%vwnd + errv = satobs(k)%vwnd_err + qcv = satobs(k)%vwnd_qc + + do n = (k+1), nloc ! loop over remaining obs + + if ( satobs(n)%uwnd /= missing_r8 .and. satobs(n)%vwnd /= missing_r8 ) then + + ! if observation is within horizontal and vertical interval, add it to obs. + obs_dist = get_dist(satobs(k)%obs_loc, satobs(n)%obs_loc, 2, 2, .true.) * earth_radius + if ( obs_dist <= hdist .and. abs(satobs(k)%pressure-satobs(n)%pressure) <= vdist ) then + + if (close_to_greenwich) call wrap_lon(satobs(n)%lon, satobs(k)%lon - lon_degree_limit, & + satobs(k)%lon + lon_degree_limit) + + nwnd = nwnd + 1.0_r8 + lat = lat + satobs(n)%lat + lon = lon + satobs(n)%lon + pres = pres + satobs(n)%pressure + uwnd = uwnd + satobs(n)%uwnd + erru = erru + satobs(n)%uwnd_err + qcu = max(qcu,satobs(n)%uwnd_qc) + vwnd = vwnd + satobs(n)%vwnd + errv = errv + satobs(n)%vwnd_err + qcv = max(qcv,satobs(n)%vwnd_qc) + + satobs(n)%uwnd = missing_r8 + satobs(n)%vwnd = missing_r8 + + end if + + end if + + end do + + ! create superobs + lat = lat / nwnd + lon = lon / nwnd + if ( lon >= 360.0_r8 ) lon = lon - 360.0_r8 + pres = pres / nwnd + uwnd = uwnd / nwnd + erru = erru / nwnd + vwnd = vwnd / nwnd + errv = errv / nwnd + + ! add to observation sequence + call create_obs_type(lat, lon, pres, VERTISPRESSURE, uwnd, & + SAT_U_WIND_COMPONENT, erru, qcu, atime, obs) + call append_obs_to_seq(seq, obs) + + call create_obs_type(lat, lon, pres, VERTISPRESSURE, vwnd, & + SAT_V_WIND_COMPONENT, errv, qcv, atime, obs) + call append_obs_to_seq(seq, obs) + + end if + +end do + +return +end subroutine superob_sat_wind_data + +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +! +! surface_obs_check - function that determines whether to include an +! surface observation in the sequence. +! +! elev_check - true to check elevation difference +! elev_max - maximum difference between model and obs. elevation +! xyz_loc - longitude, latitude and elevation array +! +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +function surface_obs_check(elev_check, elev_max, xyz_loc) + +use types_mod, only : r8 +use obs_kind_mod, only : QTY_SURFACE_ELEVATION +use model_mod, only : model_interpolate +use location_mod, only : set_location, VERTISSURFACE + +implicit none + +logical, intent(in) :: elev_check +real(r8), intent(in) :: xyz_loc(3), elev_max + +integer :: istatus(1) +logical :: surface_obs_check +real(r8) :: xmod(1), hsfc(1) + +surface_obs_check = .true. + +if ( elev_check ) then + + call model_interpolate(dummy_ens, 1, set_location(xyz_loc(1), xyz_loc(2), & + xyz_loc(3), VERTISSURFACE), QTY_SURFACE_ELEVATION, hsfc, istatus) + if ( abs(hsfc(1) - xyz_loc(3)) > elev_max ) surface_obs_check = .false. + +end if + +return +end function surface_obs_check + +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +! +! pole_check - determine if we are within km_dist of either pole. +! function returns true if so, false if not. +! +! lon - longitude in degrees +! lat - latitude in degrees +! km_dist - horizontal superob radius in kilometers +! +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +function pole_check(lon, lat, km_dist) + +use types_mod, only : r8, earth_radius +use location_mod, only : location_type, get_dist, set_location, VERTISUNDEF + +implicit none + +real(r8), intent(in) :: lon, lat, km_dist +logical :: pole_check + +type(location_type) :: thisloc, pole + + +! create a point at this lon/lat, and at the nearest pole +thisloc = set_location(lon, lat, 0.0_r8, VERTISUNDEF) +if (lat >= 0) then + pole = set_location(0.0_r8, 90.0_r8, 0.0_r8, VERTISUNDEF) +else + pole = set_location(0.0_r8, -90.0_r8, 0.0_r8, VERTISUNDEF) +endif + +! are we within km_distance of that pole? +if ( get_dist(thisloc, pole, 1, 1, .true.) * earth_radius <= km_dist ) then + pole_check = .true. +else + pole_check = .false. +endif + +return +end function pole_check + +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +! +! superob_location_check - determine if a point is close to the greenwich +! longitude based on the given latitude and distance. +! if this location is close enough to longitude=0 +! we have to take action in order not to average points +! at 0 and 360 and end up with 180 by mistake. as long +! as we treat all points consistently when doing an average +! the exact determination of 'close enough' isn't critical. +! (the minimum and maximum extents in longitude for a given +! point and radius is left as an exercise for the reader. +! hint, they are not along the same latitude circle.) +! +! lon - longitude in degrees (input) +! lat - latitude in degrees (input) +! km_dist - horizontal superob radius in kilometers (input) +! near_greenwich - returns true if the given lon/lat is potentially within +! km_dist of longitude 0 (output) +! lon_degree_limit - number of degrees along a latitude circle that the +! km_dist equates to, plus a tolerance (output) +! +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +subroutine superob_location_check(lon, lat, km_dist, near_greenwich, lon_degree_limit) + +use types_mod, only : r8, PI, earth_radius, RAD2DEG, DEG2RAD +use location_mod, only : location_type, get_dist, set_location, VERTISUNDEF + +implicit none + +real(r8), intent(in) :: lon, lat, km_dist +logical, intent(out) :: near_greenwich +real(r8), intent(out) :: lon_degree_limit + +real(r8) :: lat_radius +real(r8), parameter :: fudge_factor = 1.2_r8 ! add a flat 20% + + +! the problem with trying to superob in a circle that's specified as a +! radius of kilometers is that it isn't parallel with longitude lines as +! you approach the poles. also when averaging lon values near the prime +! meridian some values are < 360 and some are > 0 but are close in space. +! simple suggestion for all but the highest latitudes: +! if dist between lat/lon and lat/0 is < hdist, add 360 to any values >= 0. +! if the final averaged point >= 360 subtract 360 before setting the location. +! this still isn't good enough as you get closer to the poles; there +! lat/lon averages are a huge pain. hdist could be shorter across the +! pole and therefore a lon that is 180 degrees away could still be +! 'close enough' to average in the same superob box. probably the +! best suggestion for this case is to convert lat/lon into 3d cartesian +! coords, average the x/y/z's separately, and then convert back. +! (no code like this has been implemented here.) +! +! obs_dist_in_km = earth_radius_in_km * dist_in_radians +! (which is what get_dist(loc1, loc2, 0, 0, .true.) returns) +! +! dist_in_radians = obs_dist_in_km / earth_radius_in_km +! + +! figure out how far in degrees km_dist is at this latitude +! if we traveled along the latitude line. and since on a sphere +! the actual closest point to a longitude line isn't found by following +! a latitude circle, add a percentage. as long as all points are +! treated consistently it doesn't matter if the degree value is exact. + +lat_radius = earth_radius * cos(lat*DEG2RAD) +lon_degree_limit = ((km_dist / lat_radius) * RAD2DEG) * fudge_factor + +! are we within 'lon_degree_limit' of the greenwich line? +if (lon <= lon_degree_limit .or. (360.0_r8 - lon) <= lon_degree_limit) then + near_greenwich = .true. +else + near_greenwich = .false. +endif + +return +end subroutine superob_location_check + + +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +! +! wrap_lon - update the incoming longitude possibly + 360 degrees if +! the given limits define a region that crosses long=0. +! all values should be in units of degrees. 'lon' value +! should be between westlon and eastlon. +! +! lon - longitude to update, returns either unchanged or + 360 +! westlon - westernmost longitude of region in degrees +! eastlon - easternmost longitude of region in degrees +! +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +subroutine wrap_lon(lon, westlon, eastlon) + +use types_mod, only : r8 + +! uniform way to treat longitude ranges, in degrees, on a globe. +! adds 360 to the incoming lon if region crosses longitude 0 and +! given point is east of lon=0. + +real(r8), intent(inout) :: lon +real(r8), intent(in) :: westlon, eastlon + +real(r8) :: westl, eastl +real(r8), parameter :: circumf = 360.0_r8 + +! ensure the region boundaries and target point are between 0 and 360. +! the modulo() function handles negative values ok; mod() does not. +westl = modulo(westlon, circumf) +eastl = modulo(eastlon, circumf) +lon = modulo(lon, circumf) + +! if the 'region' is the entire globe you can return now. +if (westl == eastl) return + +! here's where the magic happens: +! normally the western boundary longitude (westl) has a smaller magnitude than +! the eastern one (eastl). but westl will be larger than eastl if the region +! of interest crosses the prime meridian. e.g. westl=100, eastl=120 doesn't +! cross it, westl=340, eastl=10 does. for regions crossing lon=0, a target lon +! west of lon=0 should not be changed; a target lon east of lon=0 needs +360 degrees. +! e.g. lon=350 stays unchanged; lon=5 becomes lon=365. + +if (westl > eastl .and. lon <= eastl) lon = lon + circumf + +return +end subroutine wrap_lon + +end program + +! +! $URL$ +! $Id$ +! $Revision$ +! $Date$ diff --git a/models/wrf_unified/WRF_DART_utilities/wrf_dart_obs_preprocess.nml b/models/wrf_unified/WRF_DART_utilities/wrf_dart_obs_preprocess.nml new file mode 100644 index 0000000000..38d99e2a85 --- /dev/null +++ b/models/wrf_unified/WRF_DART_utilities/wrf_dart_obs_preprocess.nml @@ -0,0 +1,43 @@ +&wrf_obs_preproc_nml + + file_name_input = 'obs_seq.old' + file_name_output = 'obs_seq.new' + + sonde_extra = 'obs_seq.rawin' + land_sfc_extra = 'obs_seq.land_sfc' + metar_extra = 'obs_seq.metar' + marine_sfc_extra = 'obs_seq.marine' + sat_wind_extra = 'obs_seq.satwnd' + profiler_extra = 'obs_seq.profiler' + gpsro_extra = 'obs_seq.gpsro' + acars_extra = 'obs_seq.acars' + trop_cyclone_extra = 'obs_seq.tc' + + overwrite_obs_time = .false. + + obs_boundary = 0.0 + increase_bdy_error = .false. + maxobsfac = 2.5 + obsdistbdy = 15.0 + + sfc_elevation_check = .false. + sfc_elevation_tol = 300.0 + obs_pressure_top = 0.0 + obs_height_top = 2.0e10 + + include_sig_data = .true. + tc_sonde_radii = -1.0 + + superob_aircraft = .false. + aircraft_horiz_int = 36.0 + aircraft_pres_int = 2500.0 + + superob_sat_winds = .false. + sat_wind_horiz_int = 100.0 + sat_wind_pres_int = 2500.0 + + overwrite_ncep_satwnd_qc = .false. + overwrite_ncep_sfc_qc = .false. + +/ + diff --git a/models/wrf_unified/WRF_DART_utilities/wrf_dart_obs_preprocess.rst b/models/wrf_unified/WRF_DART_utilities/wrf_dart_obs_preprocess.rst new file mode 100644 index 0000000000..bd334dd693 --- /dev/null +++ b/models/wrf_unified/WRF_DART_utilities/wrf_dart_obs_preprocess.rst @@ -0,0 +1,172 @@ +PROGRAM ``wrf_dart_obs_preprocess`` +=================================== + +Overview +-------- + +Program to preprocess observations, with specific knowledge of the WRF domain. + +This program will exclude all observations outside of the given WRF domain. There are options to exclude or increase the +error values of obs close to the domain boundaries. The program can superob (average) aircraft and satellite wind obs if +they are too dense. + +This program can read up to 9 additional obs_seq files and merge their data in with the basic obs_sequence file which is +the main input. + +This program can reject surface observations if the elevation encoded in the observation is too different from the wrf +surface elevation. + +This program can exclude observations above a specified height or pressure. + +This program can overwrite the incoming Data QC value with another. + +Namelist +-------- + +This namelist is read from the file ``input.nml``. Namelists start with an ampersand '&' and terminate with a slash '/'. +Character strings that contain a '/' must be enclosed in quotes to prevent them from prematurely terminating the +namelist. + +:: + + &wrf_obs_preproc_nml + + file_name_input = 'obs_seq.old' + file_name_output = 'obs_seq.new' + + sonde_extra = 'obs_seq.rawin' + land_sfc_extra = 'obs_seq.land_sfc' + metar_extra = 'obs_seq.metar' + marine_sfc_extra = 'obs_seq.marine' + sat_wind_extra = 'obs_seq.satwnd' + profiler_extra = 'obs_seq.profiler' + gpsro_extra = 'obs_seq.gpsro' + acars_extra = 'obs_seq.acars' + trop_cyclone_extra = 'obs_seq.tc' + + overwrite_obs_time = .false. + + obs_boundary = 0.0 + increase_bdy_error = .false. + maxobsfac = 2.5 + obsdistbdy = 15.0 + + sfc_elevation_check = .false. + sfc_elevation_tol = 300.0 + obs_pressure_top = 0.0 + obs_height_top = 2.0e10 + + include_sig_data = .true. + tc_sonde_radii = -1.0 + + superob_aircraft = .false. + aircraft_horiz_int = 36.0 + aircraft_pres_int = 2500.0 + + superob_sat_winds = .false. + sat_wind_horiz_int = 100.0 + sat_wind_pres_int = 2500.0 + + overwrite_ncep_satwnd_qc = .false. + overwrite_ncep_sfc_qc = .false. + / + ++---------------------------------------------------------------+--------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| **Item** | **Type** | **Description** | ++---------------------------------------------------------------+--------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| Generic parameters: | | | ++---------------------------------------------------------------+--------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| file_name_input | character(len=129) | The input obs_seq file. | ++---------------------------------------------------------------+--------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| file_name_output | character(len=129) | The output obs_seq file. | ++---------------------------------------------------------------+--------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| | sonde_extra, land_sfc_extra, | character(len=129) | The names of additional input obs_seq files, which if they exist, will be merged in with the obs from the file_name_input obs_seq file. If the files do not exist, they are silently ignored without error. | +| | metar_extra, | | | +| | marine_sfc_extra, sat_wind_extra, | | | +| | profiler_extra, gpsro_extra, | | | +| | acars_extra, trop_cyclone_extra | | | ++---------------------------------------------------------------+--------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| overwrite_obs_time | logical | If true, replace the incoming observation time with the analysis time. Not recommended. | ++---------------------------------------------------------------+--------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| **Boundary-specific parameters:** | | | ++---------------------------------------------------------------+--------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| obs_boundary | real(r8) | Number of grid points around domain boundary which will be considered the new extent of the domain. Observations outside this smaller area will be excluded. | ++---------------------------------------------------------------+--------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| increase_bdy_error | logical | If true, observations near the domain boundary will have their observation error increased by maxobsfac. | ++---------------------------------------------------------------+--------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| maxobsfac | real(r8) | If increase_bdy_error is true, multiply the error by a ramped factor. This item sets the maximum error. | ++---------------------------------------------------------------+--------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| obsdistbdy | real(r8) | If increase_bdy_error is true, this defines the region around the boundary (in number of grid points) where the observation error values will be altered. This is ramped, so when you reach the innermost points the change in observation error is 0.0. | ++---------------------------------------------------------------+--------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| **Parameters to reduce observation count :** | | | ++---------------------------------------------------------------+--------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| sfc_elevation_check | logical | If true, check the height of surface observations against the surface height in the model. | ++---------------------------------------------------------------+--------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| sfc_elevation_tol | real(r8) | If sfc_elevation_check is true, the maximum difference between the elevation of a surface observation and the model surface height, in meters. If the difference is larger than this value, the observation is excluded. | ++---------------------------------------------------------------+--------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| obs_pressure_top | real(r8) | Observations with a vertical coordinate in pressure which are located above this pressure level (i.e. the obs vertical value is smaller than the given pressure) will be excluded. | ++---------------------------------------------------------------+--------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| obs_height_top | real(r8) | Observations with a vertical coordinate in height which are located above this height value (i.e. the obs vertical value is larger than the given height) will be excluded. | ++---------------------------------------------------------------+--------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| **Radio/Rawinsonde-specific parameters :** | | | ++---------------------------------------------------------------+--------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| include_sig_data | logical | If true, include significant level data from radiosondes. | ++---------------------------------------------------------------+--------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| tc_sonde_radii | real(r8) | If greater than 0.0 remove any sonde observations closer than this distance in Kilometers to the center of a Tropical Cyclone. | ++---------------------------------------------------------------+--------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| Aircraft-specific parameters : | | | ++---------------------------------------------------------------+--------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| superob_aircraft | logical | If true, average all aircraft observations within the given radius and output only a single observation. Any observation that is used in computing a superob observation is removed from the list and is not used in any other superob computation. | ++---------------------------------------------------------------+--------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| aircraft_horiz_int | real(r8) | If superob_aircraft is true, the horizontal distance in Kilometers which defines the superob area. All other unused aircraft observations within this radius will be averaged with the current observation. | ++---------------------------------------------------------------+--------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| aircraft_vert_int | real(r8) | If superob_aircraft is true, the vertical distance in Pascals which defines the maximum separation for including an observation in the superob computation. | ++---------------------------------------------------------------+--------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| **Satellite Wind-specific parameters :** | | | ++---------------------------------------------------------------+--------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| superob_sat_winds | logical | If true, average all sat_wind observations within the given radius and output only a single observation. Any observation that is used in computing a superob observation is removed from the list and is not used in any other superob computation. | ++---------------------------------------------------------------+--------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| sat_wind_horiz_int | real(r8) | If superob_sat_winds is true, the horizontal distance in Kilometers which defines the superob area. All other unused sat_wind observations within this radius will be averaged with the current observation. | ++---------------------------------------------------------------+--------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| sat_wind_vert_int | real(r8) | If superob_sat_winds is true, the vertical distance in Pascals which defines the maximum separation for including an observation in the superob computation. | ++---------------------------------------------------------------+--------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| overwrite_ncep_satwnd_qc | logical | If true, replace the incoming Data QC value in satellite wind observations with 2.0. | ++---------------------------------------------------------------+--------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| **Surface Observation-specific parameters :** | | | ++---------------------------------------------------------------+--------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| overwrite_ncep_sfc_qc | logical | If true, replace the incoming Data QC value in surface observations with 2.0. | ++---------------------------------------------------------------+--------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + + + +Modules used +------------ + +:: + + types_mod + obs_sequence_mod + utilities_mod + obs_kind_mod + time_manager_mod + model_mod + netcdf + +Files +----- + +- Input namelist ; ``input.nml`` +- Input WRF state netCDF files; ``wrfinput_d01, wrfinput_d02, ...`` +- Input obs_seq files (as specified in namelist) +- Output obs_seq file (as specified in namelist) + +File formats +~~~~~~~~~~~~ + +This utility can read one or more obs_seq files and combine them while doing the rest of the processing. It uses the +standard DART observation sequence file format. + +References +---------- + +- Generously contributed by Ryan Torn. diff --git a/models/wrf_unified/WRF_DART_utilities/wrf_data_module.f90 b/models/wrf_unified/WRF_DART_utilities/wrf_data_module.f90 new file mode 100644 index 0000000000..cd16724423 --- /dev/null +++ b/models/wrf_unified/WRF_DART_utilities/wrf_data_module.f90 @@ -0,0 +1,1713 @@ +! DART software - Copyright UCAR. This open source software is provided +! by UCAR, "as is", without charge, subject to all terms of use at +! http://www.image.ucar.edu/DAReS/DART/DART_download +! +! $Id$ + +MODULE wrf_data_module + +use types_mod, only : r8 +use utilities_mod, only : register_module, error_handler, E_ERR, E_MSG + +use netcdf + +implicit none +private + +public :: wrf_data, wrf_bdy_data, wrf_open_and_alloc, wrfbdy_open_and_alloc, & + wrf_dealloc, wrfbdy_dealloc, wrf_io, wrfbdy_io, set_wrf_date, get_wrf_date + +! version controlled file description for error handling, do not edit +character(len=256), parameter :: source = & + "$URL$" +character(len=32 ), parameter :: revision = "$Revision$" +character(len=128), parameter :: revdate = "$Date$" + +TYPE wrf_data + + integer :: ncid ! netcdf id for file + integer :: sls_id, sls, bt_id, bt, sn_id, sn, we_id, we + integer :: u_id, v_id, w_id, ph_id, phb_id, t_id, mu_id, mub_id, & + tsk_id, qv_id, qc_id, qr_id, qi_id, qs_id, qg_id, qnice_id, & + u10_id, v10_id, t2_id, th2_id, q2_id, ps_id, & + tslb_id, smois_id, sh2o_id, hdiab_id + + integer :: n_moist + logical :: surf_obs + logical :: soil_data + logical :: h_diab + + +!--- +! arrays for data + + real(r8), pointer :: u(:,:,:) + real(r8), pointer :: v(:,:,:) + real(r8), pointer :: w(:,:,:) + real(r8), pointer :: ph(:,:,:) + real(r8), pointer :: phb(:,:,:) + real(r8), pointer :: t(:,:,:) + real(r8), pointer :: tsk(:,:) + real(r8), pointer :: qv(:,:,:) + real(r8), pointer :: qc(:,:,:) + real(r8), pointer :: qr(:,:,:) + real(r8), pointer :: qi(:,:,:) + real(r8), pointer :: qs(:,:,:) + real(r8), pointer :: qg(:,:,:) + real(r8), pointer :: qnice(:,:,:) + real(r8), pointer :: mu(:,:) + real(r8), pointer :: mub(:,:) + real(r8), pointer :: u10(:,:) + real(r8), pointer :: v10(:,:) + real(r8), pointer :: t2(:,:) + real(r8), pointer :: th2(:,:) + real(r8), pointer :: q2(:,:) + real(r8), pointer :: ps(:,:) + real(r8), pointer :: tslb(:,:,:) + real(r8), pointer :: smois(:,:,:) + real(r8), pointer :: sh2o(:,:,:) + real(r8), pointer :: hdiab(:,:,:) + +end type + +TYPE wrf_bdy_data + + integer :: ncid ! netcdf id for file + integer :: time_id, time, bdywdth_id, bdywdth, & + bt_id, bt, sn_id, sn, we_id, we + integer :: uxs_id , uxe_id , uys_id , uye_id , & + utxs_id, utxe_id, utys_id, utye_id, & + vxs_id , vxe_id , vys_id , vye_id , & + vtxs_id, vtxe_id, vtys_id, vtye_id, & + wxs_id , wxe_id , wys_id , wye_id , & + wtxs_id, wtxe_id, wtys_id, wtye_id, & + phxs_id , phxe_id , phys_id , phye_id , & + phtxs_id, phtxe_id, phtys_id, phtye_id, & + txs_id , txe_id , tys_id , tye_id , & + ttxs_id, ttxe_id, ttys_id, ttye_id, & + muxs_id , muxe_id , muys_id , muye_id , & + mutxs_id, mutxe_id, mutys_id, mutye_id, & + qvxs_id , qvxe_id , qvys_id , qvye_id , & + qvtxs_id, qvtxe_id, qvtys_id, qvtye_id, & + qcxs_id , qcxe_id , qcys_id , qcye_id , & + qctxs_id, qctxe_id, qctys_id, qctye_id, & + qrxs_id , qrxe_id , qrys_id , qrye_id , & + qrtxs_id, qrtxe_id, qrtys_id, qrtye_id, & + qixs_id , qixe_id , qiys_id , qiye_id , & + qitxs_id, qitxe_id, qitys_id, qitye_id, & + qsxs_id , qsxe_id , qsys_id , qsye_id , & + qstxs_id, qstxe_id, qstys_id, qstye_id, & + qgxs_id , qgxe_id , qgys_id , qgye_id , & + qgtxs_id, qgtxe_id, qgtys_id, qgtye_id, & + qnicexs_id , qnicexe_id , qniceys_id , qniceye_id , & + qnicetxs_id, qnicetxe_id, qnicetys_id, qnicetye_id + + integer :: n_moist + + real(r8), pointer :: uxs(:,:,:,:) , uxe(:,:,:,:) , uys(:,:,:,:) , uye(:,:,:,:) + real(r8), pointer :: utxs(:,:,:,:), utxe(:,:,:,:), utys(:,:,:,:), utye(:,:,:,:) + real(r8), pointer :: vxs(:,:,:,:) , vxe(:,:,:,:) , vys(:,:,:,:) , vye(:,:,:,:) + real(r8), pointer :: vtxs(:,:,:,:), vtxe(:,:,:,:), vtys(:,:,:,:), vtye(:,:,:,:) + real(r8), pointer :: wxs(:,:,:,:) , wxe(:,:,:,:) , wys(:,:,:,:) , wye(:,:,:,:) + real(r8), pointer :: wtxs(:,:,:,:), wtxe(:,:,:,:), wtys(:,:,:,:), wtye(:,:,:,:) + real(r8), pointer :: phxs(:,:,:,:) , phxe(:,:,:,:) , phys(:,:,:,:) , phye(:,:,:,:) + real(r8), pointer :: phtxs(:,:,:,:), phtxe(:,:,:,:), phtys(:,:,:,:),phtye(:,:,:,:) + real(r8), pointer :: txs(:,:,:,:) , txe(:,:,:,:) , tys(:,:,:,:) , tye(:,:,:,:) + real(r8), pointer :: ttxs(:,:,:,:), ttxe(:,:,:,:), ttys(:,:,:,:), ttye(:,:,:,:) + real(r8), pointer :: muxs(:,:,:) , muxe(:,:,:) , muys(:,:,:) , muye(:,:,:) + real(r8), pointer :: mutxs(:,:,:), mutxe(:,:,:), mutys(:,:,:),mutye(:,:,:) + real(r8), pointer :: qvxs(:,:,:,:) , qvxe(:,:,:,:) , qvys(:,:,:,:) , qvye(:,:,:,:) + real(r8), pointer :: qvtxs(:,:,:,:), qvtxe(:,:,:,:), qvtys(:,:,:,:),qvtye(:,:,:,:) + real(r8), pointer :: qcxs(:,:,:,:) , qcxe(:,:,:,:) , qcys(:,:,:,:) , qcye(:,:,:,:) + real(r8), pointer :: qctxs(:,:,:,:), qctxe(:,:,:,:), qctys(:,:,:,:),qctye(:,:,:,:) + real(r8), pointer :: qrxs(:,:,:,:) , qrxe(:,:,:,:) , qrys(:,:,:,:) , qrye(:,:,:,:) + real(r8), pointer :: qrtxs(:,:,:,:), qrtxe(:,:,:,:), qrtys(:,:,:,:),qrtye(:,:,:,:) + real(r8), pointer :: qixs(:,:,:,:) , qixe(:,:,:,:) , qiys(:,:,:,:) , qiye(:,:,:,:) + real(r8), pointer :: qitxs(:,:,:,:), qitxe(:,:,:,:), qitys(:,:,:,:),qitye(:,:,:,:) + real(r8), pointer :: qsxs(:,:,:,:) , qsxe(:,:,:,:) , qsys(:,:,:,:) , qsye(:,:,:,:) + real(r8), pointer :: qstxs(:,:,:,:), qstxe(:,:,:,:), qstys(:,:,:,:),qstye(:,:,:,:) + real(r8), pointer :: qgxs(:,:,:,:) , qgxe(:,:,:,:) , qgys(:,:,:,:) , qgye(:,:,:,:) + real(r8), pointer :: qgtxs(:,:,:,:), qgtxe(:,:,:,:), qgtys(:,:,:,:),qgtye(:,:,:,:) + real(r8), pointer :: qnicexs(:,:,:,:) , qnicexe(:,:,:,:) , qniceys(:,:,:,:) , qniceye(:,:,:,:) + real(r8), pointer :: qnicetxs(:,:,:,:), qnicetxe(:,:,:,:), qnicetys(:,:,:,:),qnicetye(:,:,:,:) + +end type + +logical, save :: module_initialized = .false. + +contains + +subroutine initialize_module + + call register_module(source, revision, revdate) + module_initialized = .true. + +end subroutine initialize_module + + +!********************************************************************** + +subroutine wrf_open_and_alloc( wrf, file_name, mode, debug ) + +implicit none + +type(wrf_data) :: wrf +character (len=*) :: file_name ! filename from which dimensions, + ! variable id's are read +integer :: nDimensions, nVariables, nAttributes, unlimitedDimID +integer :: mode, istatus +logical :: debug + +character (len=80) :: name + +if ( .not. module_initialized ) call initialize_module + +if(debug) write(6,*) 'Opening ',file_name +call check ( nf90_open(file_name, mode, wrf%ncid) ) +if(debug) write(6,*) ' wrf%ncid is ',wrf%ncid + +! get wrf grid dimensions + +call check ( nf90_inq_dimid(wrf%ncid, "bottom_top", wrf%bt_id) ) +call check ( nf90_inquire_dimension(wrf%ncid, wrf%bt_id, name, wrf%bt) ) + +call check ( nf90_inq_dimid(wrf%ncid, "south_north", wrf%sn_id) ) +call check ( nf90_inquire_dimension(wrf%ncid, wrf%sn_id, name, wrf%sn) ) + +call check ( nf90_inq_dimid(wrf%ncid, "west_east", wrf%we_id) ) +call check ( nf90_inquire_dimension(wrf%ncid, wrf%we_id, name, wrf%we) ) + +call check ( nf90_inq_dimid(wrf%ncid, "soil_layers_stag", wrf%sls_id) ) +call check ( nf90_inquire_dimension(wrf%ncid, wrf%sls_id, name, wrf%sls) ) + +if(debug) write(6,*) ' dimensions bt, sn, we are ',wrf%bt,wrf%sn,wrf%we + +!--- +! get wrf variable ids and allocate space for wrf variables + +call check ( nf90_inq_varid(wrf%ncid, "U", wrf%u_id)) +if(debug) write(6,*) ' u_id = ',wrf%u_id +allocate(wrf%u(wrf%we+1,wrf%sn,wrf%bt)) + +call check ( nf90_inq_varid(wrf%ncid, "V", wrf%v_id)) +if(debug) write(6,*) ' v_id = ',wrf%v_id +allocate(wrf%v(wrf%we,wrf%sn+1,wrf%bt)) + +call check ( nf90_inq_varid(wrf%ncid, "W", wrf%w_id)) +if(debug) write(6,*) ' w_id = ',wrf%w_id +allocate(wrf%w(wrf%we,wrf%sn,wrf%bt+1)) + +call check ( nf90_inq_varid(wrf%ncid, "PH", wrf%ph_id)) +if(debug) write(6,*) ' ph_id = ',wrf%ph_id +allocate(wrf%ph(wrf%we,wrf%sn,wrf%bt+1)) + +call check ( nf90_inq_varid(wrf%ncid, "PHB", wrf%phb_id)) +if(debug) write(6,*) ' phb_id = ',wrf%phb_id +allocate(wrf%phb(wrf%we,wrf%sn,wrf%bt+1)) + +call check ( nf90_inq_varid(wrf%ncid, "T", wrf%t_id)) +if(debug) write(6,*) ' t_id = ',wrf%t_id +allocate(wrf%t(wrf%we,wrf%sn,wrf%bt)) + +call check ( nf90_inq_varid(wrf%ncid, "MU", wrf%mu_id)) +if(debug) write(6,*) ' mu_id = ',wrf%mu_id +allocate(wrf%mu(wrf%we,wrf%sn)) + +call check ( nf90_inq_varid(wrf%ncid, "MUB", wrf%mub_id)) +if(debug) write(6,*) ' mub_id = ',wrf%mub_id +allocate(wrf%mub(wrf%we,wrf%sn)) + +if(wrf%n_moist > 0) then + call check ( nf90_inq_varid(wrf%ncid, "QVAPOR", wrf%qv_id)) + allocate(wrf%qv(wrf%we,wrf%sn,wrf%bt)) +endif + +if(wrf%n_moist > 1) then + call check ( nf90_inq_varid(wrf%ncid, "QCLOUD", wrf%qc_id)) + allocate(wrf%qc(wrf%we,wrf%sn,wrf%bt)) +endif + +if(wrf%n_moist > 2) then + call check ( nf90_inq_varid(wrf%ncid, "QRAIN", wrf%qr_id)) + allocate(wrf%qr(wrf%we,wrf%sn,wrf%bt)) +endif + +if(wrf%n_moist > 3) then + call check ( nf90_inq_varid(wrf%ncid, "QICE", wrf%qi_id)) + allocate(wrf%qi(wrf%we,wrf%sn,wrf%bt)) +endif + +if(wrf%n_moist > 4) then + call check ( nf90_inq_varid(wrf%ncid, "QSNOW", wrf%qs_id)) + allocate(wrf%qs(wrf%we,wrf%sn,wrf%bt)) +endif + +if(wrf%n_moist > 5) then + call check ( nf90_inq_varid(wrf%ncid, "QGRAUP", wrf%qg_id)) + allocate(wrf%qg(wrf%we,wrf%sn,wrf%bt)) +endif + +if(wrf%n_moist > 6) then + call check ( nf90_inq_varid(wrf%ncid, "QNICE", wrf%qnice_id)) + allocate(wrf%qnice(wrf%we,wrf%sn,wrf%bt)) +endif + +if(wrf%n_moist > 7) then + write(6,*) 'n_moist = ',wrf%n_moist + call error_handler(E_ERR,'wrf_open_and_alloc', & + 'n_moist is too large.', source, revision, revdate) +endif + +if( wrf%surf_obs ) then + + call check ( nf90_inq_varid(wrf%ncid, "U10", wrf%u10_id)) + allocate(wrf%u10(wrf%we,wrf%sn)) + + call check ( nf90_inq_varid(wrf%ncid, "V10", wrf%v10_id)) + allocate(wrf%v10(wrf%we,wrf%sn)) + + call check ( nf90_inq_varid(wrf%ncid, "T2", wrf%t2_id)) + allocate(wrf%t2(wrf%we,wrf%sn)) + + call check ( nf90_inq_varid(wrf%ncid, "TH2", wrf%th2_id)) + allocate(wrf%th2(wrf%we,wrf%sn)) + + call check ( nf90_inq_varid(wrf%ncid, "Q2", wrf%q2_id)) + allocate(wrf%q2(wrf%we,wrf%sn)) + + allocate(wrf%ps(wrf%we,wrf%sn)) + istatus = nf90_inq_varid(wrf%ncid, "PSFC", wrf%ps_id) + if(istatus /= nf90_noerr) then + call error_handler(E_MSG,'PSFC', & + trim(nf90_strerror(istatus)), source, revision, revdate) + if(mode == NF90_WRITE) then + call error_handler(E_MSG,'wrf_open_and_alloc', & + 'creates PSFC', source, revision, revdate) + call check(nf90_Inquire(wrf%ncid, nDimensions, nVariables, nAttributes, unlimitedDimID)) + call check(nf90_Redef(wrf%ncid)) + call check(nf90_def_var(wrf%ncid, name="PSFC", xtype=nf90_real, & + dimids= (/ wrf%we_id, wrf%sn_id, unlimitedDimID/), varid=wrf%ps_id) ) + call check(nf90_enddef(wrf%ncid)) + wrf%ps(:,:) = 0.0_r8 + call check( nf90_put_var(wrf%ncid, wrf%ps_id, wrf%ps, start = (/ 1, 1, 1 /))) + endif + endif + +endif + +if( wrf%soil_data ) then + + call check ( nf90_inq_varid(wrf%ncid, "TSLB", wrf%tslb_id)) + if(debug) write(6,*) ' tslb_id = ',wrf%tslb_id + allocate(wrf%tslb(wrf%we,wrf%sn,wrf%sls)) + + call check ( nf90_inq_varid(wrf%ncid, "SMOIS", wrf%smois_id)) + if(debug) write(6,*) ' smois_id = ',wrf%smois_id + allocate(wrf%smois(wrf%we,wrf%sn,wrf%sls)) + + call check ( nf90_inq_varid(wrf%ncid, "SH2O", wrf%sh2o_id)) + if(debug) write(6,*) ' sh2o_id = ',wrf%sh2o_id + allocate(wrf%sh2o(wrf%we,wrf%sn,wrf%sls)) + + call check ( nf90_inq_varid(wrf%ncid, "TSK", wrf%tsk_id)) + if(debug) write(6,*) ' tsk_id = ',wrf%tsk_id + allocate(wrf%tsk(wrf%we,wrf%sn)) + +endif + +if( wrf%h_diab ) then + + allocate(wrf%hdiab(wrf%we,wrf%sn,wrf%bt)) + istatus = nf90_inq_varid(wrf%ncid, "H_DIABATIC", wrf%hdiab_id) + if(istatus /= nf90_noerr) then + call error_handler(E_MSG,'H_DIABATIC', & + trim(nf90_strerror(istatus)), source, revision, revdate) + if(mode == NF90_WRITE) then + call error_handler(E_MSG,'wrf_open_and_alloc', & + 'creates H_DIABATIC', source, revision, revdate) + call check(nf90_Inquire(wrf%ncid, nDimensions, nVariables, nAttributes, unlimitedDimID)) + call check(nf90_Redef(wrf%ncid)) + call check(nf90_def_var(wrf%ncid, name="H_DIABATIC", xtype=nf90_real, & + dimids= (/ wrf%we_id, wrf%sn_id, wrf%bt_id, unlimitedDimID/), varid=wrf%hdiab_id) ) + call check(nf90_put_att(wrf%ncid, wrf%hdiab_id, "FieldType", 104)) + call check(nf90_put_att(wrf%ncid, wrf%hdiab_id, "MemoryOrder", "XYZ")) + call check(nf90_put_att(wrf%ncid, wrf%hdiab_id, "description", & + "PREVIOUS TIMESTEP CONDENSATIONAL HEATING")) + call check(nf90_put_att(wrf%ncid, wrf%hdiab_id, "units", "")) + call check(nf90_put_att(wrf%ncid, wrf%hdiab_id, "stagger", "")) + call check(nf90_enddef(wrf%ncid)) + wrf%hdiab(:,:,:) = 0.0_r8 + call check( nf90_put_var(wrf%ncid, wrf%hdiab_id, wrf%hdiab, start = (/ 1, 1, 1, 1 /))) + endif + endif + +endif + +contains + + ! Internal subroutine - checks error status after each netcdf, prints + ! text message each time an error code is returned. + subroutine check(istatus) + integer, intent ( in) :: istatus + if(istatus /= nf90_noerr) call error_handler(E_ERR,'wrf_open_and_alloc', & + trim(nf90_strerror(istatus)), source, revision, revdate) + end subroutine check + +end subroutine wrf_open_and_alloc + +!********************************************************************** + +subroutine wrf_dealloc( wrf ) + +implicit none + +type(wrf_data) :: wrf + +if ( .not. module_initialized ) call initialize_module + +deallocate(wrf%u) +deallocate(wrf%v) +deallocate(wrf%w) +deallocate(wrf%ph) +deallocate(wrf%phb) +deallocate(wrf%t) +deallocate(wrf%mu) +deallocate(wrf%mub) +if(wrf%n_moist > 0) then + deallocate(wrf%qv) +endif + +if(wrf%n_moist > 1) then + deallocate(wrf%qc) +endif + +if(wrf%n_moist > 2) then + deallocate(wrf%qr) +endif + +if(wrf%n_moist > 3) then + deallocate(wrf%qi) +endif + +if(wrf%n_moist > 4) then + deallocate(wrf%qs) +endif + +if(wrf%n_moist > 5) then + deallocate(wrf%qg) +endif + +if(wrf%n_moist > 6) then + deallocate(wrf%qnice) +endif + +if(wrf%n_moist > 7) then + write(6,*) 'n_moist = ',wrf%n_moist + call error_handler(E_ERR,'wrf_dealloc', & + 'n_moist is too large.', source, revision, revdate) +endif + +if( wrf%surf_obs ) then + deallocate(wrf%u10) + deallocate(wrf%v10) + deallocate(wrf%t2) + deallocate(wrf%th2) + deallocate(wrf%q2) + deallocate(wrf%ps) +endif + +if( wrf%soil_data ) then + deallocate(wrf%tslb) + deallocate(wrf%smois) + deallocate(wrf%sh2o) + deallocate(wrf%tsk) +endif + +if( wrf%h_diab ) then + deallocate(wrf%hdiab) +endif + +end subroutine wrf_dealloc + +!--------------------------------------------------------------- + +subroutine wrfbdy_open_and_alloc( wrfbdy, file_name, mode, debug ) + +implicit none + +type(wrf_bdy_data) :: wrfbdy +character (len=*) :: file_name ! filename from which dimensions, + ! variable id's are read +integer :: mode +logical :: debug + +character (len=80) :: name + +if ( .not. module_initialized ) call initialize_module + +if (debug) write(6,*) ' in wrfbdy_open_and_alloc, file =', file_name + +call check ( nf90_open(file_name, mode, wrfbdy%ncid) ) +if(debug) write(6,*) ' wrfbdy%ncid is ',wrfbdy%ncid + +! get wrfbdy dimensions + +call check ( nf90_inq_dimid(wrfbdy%ncid, "Time", wrfbdy%time_id) ) +call check ( nf90_inquire_dimension(wrfbdy%ncid, wrfbdy%time_id, name, wrfbdy%time) ) + +call check ( nf90_inq_dimid(wrfbdy%ncid, "bdy_width", wrfbdy%bdywdth_id) ) +call check ( nf90_inquire_dimension(wrfbdy%ncid, wrfbdy%bdywdth_id, name, wrfbdy%bdywdth) ) + +call check ( nf90_inq_dimid(wrfbdy%ncid, "bottom_top", wrfbdy%bt_id) ) +call check ( nf90_inquire_dimension(wrfbdy%ncid, wrfbdy%bt_id, name, wrfbdy%bt) ) + +call check ( nf90_inq_dimid(wrfbdy%ncid, "south_north", wrfbdy%sn_id) ) +call check ( nf90_inquire_dimension(wrfbdy%ncid, wrfbdy%sn_id, name, wrfbdy%sn) ) + +call check ( nf90_inq_dimid(wrfbdy%ncid, "west_east", wrfbdy%we_id) ) +call check ( nf90_inquire_dimension(wrfbdy%ncid, wrfbdy%we_id, name, wrfbdy%we) ) + +if(debug) write(6,*) ' dimensions bt, sn, we are ',wrfbdy%bt,wrfbdy%sn,wrfbdy%we + +!--- +! get wrfbdy variable ids and allocate space for wrfbdy variables + + !-- u on bdy +call check ( nf90_inq_varid(wrfbdy%ncid, "U_BXS", wrfbdy%uxs_id) ) +if(debug) write(6,*) ' uxs_id = ',wrfbdy%uxs_id +allocate( wrfbdy%uxs( wrfbdy%sn, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) + +call check ( nf90_inq_varid(wrfbdy%ncid, "U_BXE", wrfbdy%uxe_id) ) +if(debug) write(6,*) ' uxe_id = ',wrfbdy%uxe_id +allocate( wrfbdy%uxe( wrfbdy%sn, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) + +call check ( nf90_inq_varid(wrfbdy%ncid, "U_BYS", wrfbdy%uys_id) ) +if(debug) write(6,*) ' uys_id = ',wrfbdy%uys_id +allocate( wrfbdy%uys( wrfbdy%we+1, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) + +call check ( nf90_inq_varid(wrfbdy%ncid, "U_BYE", wrfbdy%uye_id) ) +if(debug) write(6,*) ' uye_id = ',wrfbdy%uye_id +allocate( wrfbdy%uye( wrfbdy%we+1, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) + + !-- u tendency on bdy +call check ( nf90_inq_varid(wrfbdy%ncid, "U_BTXS", wrfbdy%utxs_id) ) +if(debug) write(6,*) ' utxs_id = ',wrfbdy%utxs_id +allocate( wrfbdy%utxs( wrfbdy%sn, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) + +call check ( nf90_inq_varid(wrfbdy%ncid, "U_BTXE", wrfbdy%utxe_id) ) +if(debug) write(6,*) ' utxe_id = ',wrfbdy%utxe_id +allocate( wrfbdy%utxe( wrfbdy%sn, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) + +call check ( nf90_inq_varid(wrfbdy%ncid, "U_BTYS", wrfbdy%utys_id) ) +if(debug) write(6,*) ' utys_id = ',wrfbdy%utys_id +allocate( wrfbdy%utys( wrfbdy%we+1, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) + +call check ( nf90_inq_varid(wrfbdy%ncid, "U_BTYE", wrfbdy%utye_id) ) +if(debug) write(6,*) ' utye_id = ',wrfbdy%utye_id +allocate( wrfbdy%utye( wrfbdy%we+1, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) + + !-- v on bdy +call check ( nf90_inq_varid(wrfbdy%ncid, "V_BXS", wrfbdy%vxs_id) ) +if(debug) write(6,*) ' vxs_id = ',wrfbdy%vxs_id +allocate( wrfbdy%vxs( wrfbdy%sn+1, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) + +call check ( nf90_inq_varid(wrfbdy%ncid, "V_BXE", wrfbdy%vxe_id) ) +if(debug) write(6,*) ' vxe_id = ',wrfbdy%vxe_id +allocate( wrfbdy%vxe( wrfbdy%sn+1, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) + +call check ( nf90_inq_varid(wrfbdy%ncid, "V_BYS", wrfbdy%vys_id) ) +if(debug) write(6,*) ' vys_id = ',wrfbdy%vys_id +allocate( wrfbdy%vys( wrfbdy%we, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) + +call check ( nf90_inq_varid(wrfbdy%ncid, "V_BYE", wrfbdy%vye_id) ) +if(debug) write(6,*) ' vye_id = ',wrfbdy%vye_id +allocate( wrfbdy%vye( wrfbdy%we, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) + + !-- v tendency on bdy +call check ( nf90_inq_varid(wrfbdy%ncid, "V_BTXS", wrfbdy%vtxs_id) ) +if(debug) write(6,*) ' vtxs_id = ',wrfbdy%vtxs_id +allocate( wrfbdy%vtxs( wrfbdy%sn+1, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) + +call check ( nf90_inq_varid(wrfbdy%ncid, "V_BTXE", wrfbdy%vtxe_id) ) +if(debug) write(6,*) ' vtxe_id = ',wrfbdy%vtxe_id +allocate( wrfbdy%vtxe( wrfbdy%sn+1, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) + +call check ( nf90_inq_varid(wrfbdy%ncid, "V_BTYS", wrfbdy%vtys_id) ) +if(debug) write(6,*) ' vtys_id = ',wrfbdy%vtys_id +allocate( wrfbdy%vtys( wrfbdy%we, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) + +call check ( nf90_inq_varid(wrfbdy%ncid, "V_BTYE", wrfbdy%vtye_id) ) +if(debug) write(6,*) ' vtye_id = ',wrfbdy%vtye_id +allocate( wrfbdy%vtye( wrfbdy%we, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) + + !-- w on bdy +call check ( nf90_inq_varid(wrfbdy%ncid, "W_BXS", wrfbdy%wxs_id) ) +allocate( wrfbdy%wxs( wrfbdy%sn, wrfbdy%bt+1, wrfbdy%bdywdth, wrfbdy%time ) ) + +call check ( nf90_inq_varid(wrfbdy%ncid, "W_BXE", wrfbdy%wxe_id) ) +allocate( wrfbdy%wxe( wrfbdy%sn, wrfbdy%bt+1, wrfbdy%bdywdth, wrfbdy%time ) ) + +call check ( nf90_inq_varid(wrfbdy%ncid, "W_BYS", wrfbdy%wys_id) ) +allocate( wrfbdy%wys( wrfbdy%we, wrfbdy%bt+1, wrfbdy%bdywdth, wrfbdy%time ) ) + +call check ( nf90_inq_varid(wrfbdy%ncid, "W_BYE", wrfbdy%wye_id) ) +allocate( wrfbdy%wye( wrfbdy%we, wrfbdy%bt+1, wrfbdy%bdywdth, wrfbdy%time ) ) + + !-- w tendency on bdy +call check ( nf90_inq_varid(wrfbdy%ncid, "W_BTXS", wrfbdy%wtxs_id) ) +allocate( wrfbdy%wtxs( wrfbdy%sn, wrfbdy%bt+1, wrfbdy%bdywdth, wrfbdy%time ) ) + +call check ( nf90_inq_varid(wrfbdy%ncid, "W_BTXE", wrfbdy%wtxe_id) ) +allocate( wrfbdy%wtxe( wrfbdy%sn, wrfbdy%bt+1, wrfbdy%bdywdth, wrfbdy%time ) ) + +call check ( nf90_inq_varid(wrfbdy%ncid, "W_BTYS", wrfbdy%wtys_id) ) +allocate( wrfbdy%wtys( wrfbdy%we, wrfbdy%bt+1, wrfbdy%bdywdth, wrfbdy%time ) ) + +call check ( nf90_inq_varid(wrfbdy%ncid, "W_BTYE", wrfbdy%wtye_id) ) +allocate( wrfbdy%wtye( wrfbdy%we, wrfbdy%bt+1, wrfbdy%bdywdth, wrfbdy%time ) ) + + !-- height on bdy +call check ( nf90_inq_varid(wrfbdy%ncid, "PH_BXS", wrfbdy%phxs_id) ) +if(debug) write(6,*) ' phxs_id = ',wrfbdy%phxs_id +allocate( wrfbdy%phxs( wrfbdy%sn, wrfbdy%bt+1, wrfbdy%bdywdth, wrfbdy%time ) ) + +call check ( nf90_inq_varid(wrfbdy%ncid, "PH_BXE", wrfbdy%phxe_id) ) +if(debug) write(6,*) ' phxe_id = ',wrfbdy%phxe_id +allocate( wrfbdy%phxe( wrfbdy%sn, wrfbdy%bt+1, wrfbdy%bdywdth, wrfbdy%time ) ) + +call check ( nf90_inq_varid(wrfbdy%ncid, "PH_BYS", wrfbdy%phys_id) ) +if(debug) write(6,*) ' phys_id = ',wrfbdy%phys_id +allocate( wrfbdy%phys( wrfbdy%we, wrfbdy%bt+1, wrfbdy%bdywdth, wrfbdy%time ) ) + +call check ( nf90_inq_varid(wrfbdy%ncid, "PH_BYE", wrfbdy%phye_id) ) +if(debug) write(6,*) ' phye_id = ',wrfbdy%phye_id +allocate( wrfbdy%phye( wrfbdy%we, wrfbdy%bt+1, wrfbdy%bdywdth, wrfbdy%time ) ) + + !-- height tendency on bdy +call check ( nf90_inq_varid(wrfbdy%ncid, "PH_BTXS", wrfbdy%phtxs_id) ) +if(debug) write(6,*) ' phtxs_id = ',wrfbdy%phtxs_id +allocate( wrfbdy%phtxs( wrfbdy%sn, wrfbdy%bt+1, wrfbdy%bdywdth, wrfbdy%time ) ) + +call check ( nf90_inq_varid(wrfbdy%ncid, "PH_BTXE", wrfbdy%phtxe_id) ) +if(debug) write(6,*) ' phtxe_id = ',wrfbdy%phtxe_id +allocate( wrfbdy%phtxe( wrfbdy%sn, wrfbdy%bt+1, wrfbdy%bdywdth, wrfbdy%time ) ) + +call check ( nf90_inq_varid(wrfbdy%ncid, "PH_BTYS", wrfbdy%phtys_id) ) +if(debug) write(6,*) ' phtys_id = ',wrfbdy%phtys_id +allocate( wrfbdy%phtys( wrfbdy%we, wrfbdy%bt+1, wrfbdy%bdywdth, wrfbdy%time ) ) + +call check ( nf90_inq_varid(wrfbdy%ncid, "PH_BTYE", wrfbdy%phtye_id) ) +if(debug) write(6,*) ' phtye_id = ',wrfbdy%phtye_id +allocate( wrfbdy%phtye( wrfbdy%we, wrfbdy%bt+1, wrfbdy%bdywdth, wrfbdy%time ) ) + + !-- t on bdy +call check ( nf90_inq_varid(wrfbdy%ncid, "T_BXS", wrfbdy%txs_id) ) +if(debug) write(6,*) ' txs_id = ',wrfbdy%txs_id +allocate( wrfbdy%txs( wrfbdy%sn, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) + +call check ( nf90_inq_varid(wrfbdy%ncid, "T_BXE", wrfbdy%txe_id) ) +if(debug) write(6,*) ' txe_id = ',wrfbdy%txe_id +allocate( wrfbdy%txe( wrfbdy%sn, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) + +call check ( nf90_inq_varid(wrfbdy%ncid, "T_BYS", wrfbdy%tys_id) ) +if(debug) write(6,*) ' tys_id = ',wrfbdy%tys_id +allocate( wrfbdy%tys( wrfbdy%we, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) + +call check ( nf90_inq_varid(wrfbdy%ncid, "T_BYE", wrfbdy%tye_id) ) +if(debug) write(6,*) ' tye_id = ',wrfbdy%tye_id +allocate( wrfbdy%tye( wrfbdy%we, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) + + !-- t tendency on bdy +call check ( nf90_inq_varid(wrfbdy%ncid, "T_BTXS", wrfbdy%ttxs_id) ) +if(debug) write(6,*) ' ttxs_id = ',wrfbdy%ttxs_id +allocate( wrfbdy%ttxs( wrfbdy%sn, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) + +call check ( nf90_inq_varid(wrfbdy%ncid, "T_BTXE", wrfbdy%ttxe_id) ) +if(debug) write(6,*) ' ttxe_id = ',wrfbdy%ttxe_id +allocate( wrfbdy%ttxe( wrfbdy%sn, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) + +call check ( nf90_inq_varid(wrfbdy%ncid, "T_BTYS", wrfbdy%ttys_id) ) +if(debug) write(6,*) ' ttys_id = ',wrfbdy%ttys_id +allocate( wrfbdy%ttys( wrfbdy%we, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) + +call check ( nf90_inq_varid(wrfbdy%ncid, "T_BTYE", wrfbdy%ttye_id) ) +if(debug) write(6,*) ' ttye_id = ',wrfbdy%ttye_id +allocate( wrfbdy%ttye( wrfbdy%we, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) + + !-- mu on bdy +call check ( nf90_inq_varid(wrfbdy%ncid, "MU_BXS", wrfbdy%muxs_id) ) +if(debug) write(6,*) ' muxs_id = ',wrfbdy%muxs_id +allocate( wrfbdy%muxs( wrfbdy%sn, wrfbdy%bdywdth, wrfbdy%time ) ) + +call check ( nf90_inq_varid(wrfbdy%ncid, "MU_BXE", wrfbdy%muxe_id) ) +if(debug) write(6,*) ' muxe_id = ',wrfbdy%muxe_id +allocate( wrfbdy%muxe( wrfbdy%sn, wrfbdy%bdywdth, wrfbdy%time ) ) + +call check ( nf90_inq_varid(wrfbdy%ncid, "MU_BYS", wrfbdy%muys_id) ) +if(debug) write(6,*) ' muys_id = ',wrfbdy%muys_id +allocate( wrfbdy%muys( wrfbdy%we, wrfbdy%bdywdth, wrfbdy%time ) ) + +call check ( nf90_inq_varid(wrfbdy%ncid, "MU_BYE", wrfbdy%muye_id) ) +if(debug) write(6,*) ' muye_id = ',wrfbdy%muye_id +allocate( wrfbdy%muye( wrfbdy%we, wrfbdy%bdywdth, wrfbdy%time ) ) + + !-- mu tendency on bdy +call check ( nf90_inq_varid(wrfbdy%ncid, "MU_BTXS", wrfbdy%mutxs_id) ) +if(debug) write(6,*) ' mutxs_id = ',wrfbdy%mutxs_id +allocate( wrfbdy%mutxs( wrfbdy%sn, wrfbdy%bdywdth, wrfbdy%time ) ) + +call check ( nf90_inq_varid(wrfbdy%ncid, "MU_BTXE", wrfbdy%mutxe_id) ) +if(debug) write(6,*) ' mutxe_id = ',wrfbdy%mutxe_id +allocate( wrfbdy%mutxe( wrfbdy%sn, wrfbdy%bdywdth, wrfbdy%time ) ) + +call check ( nf90_inq_varid(wrfbdy%ncid, "MU_BTYS", wrfbdy%mutys_id) ) +if(debug) write(6,*) ' mutys_id = ',wrfbdy%mutys_id +allocate( wrfbdy%mutys( wrfbdy%we, wrfbdy%bdywdth, wrfbdy%time ) ) + +call check ( nf90_inq_varid(wrfbdy%ncid, "MU_BTYE", wrfbdy%mutye_id) ) +if(debug) write(6,*) ' mutye_id = ',wrfbdy%mutye_id +allocate( wrfbdy%mutye( wrfbdy%we, wrfbdy%bdywdth, wrfbdy%time ) ) + +if(wrfbdy%n_moist > 0) then + !-- qv on bdy + call check ( nf90_inq_varid(wrfbdy%ncid, "QVAPOR_BXS", wrfbdy%qvxs_id) ) + allocate( wrfbdy%qvxs( wrfbdy%sn, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) + + call check ( nf90_inq_varid(wrfbdy%ncid, "QVAPOR_BXE", wrfbdy%qvxe_id) ) + allocate( wrfbdy%qvxe( wrfbdy%sn, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) + + call check ( nf90_inq_varid(wrfbdy%ncid, "QVAPOR_BYS", wrfbdy%qvys_id) ) + allocate( wrfbdy%qvys( wrfbdy%we, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) + + call check ( nf90_inq_varid(wrfbdy%ncid, "QVAPOR_BYE", wrfbdy%qvye_id) ) + allocate( wrfbdy%qvye( wrfbdy%we, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) + + !-- qv tendency on bdy + call check ( nf90_inq_varid(wrfbdy%ncid, "QVAPOR_BTXS", wrfbdy%qvtxs_id) ) + allocate( wrfbdy%qvtxs( wrfbdy%sn, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) + + call check ( nf90_inq_varid(wrfbdy%ncid, "QVAPOR_BTXE", wrfbdy%qvtxe_id) ) + allocate( wrfbdy%qvtxe( wrfbdy%sn, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) + + call check ( nf90_inq_varid(wrfbdy%ncid, "QVAPOR_BTYS", wrfbdy%qvtys_id) ) + allocate( wrfbdy%qvtys( wrfbdy%we, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) + + call check ( nf90_inq_varid(wrfbdy%ncid, "QVAPOR_BTYE", wrfbdy%qvtye_id) ) + allocate( wrfbdy%qvtye( wrfbdy%we, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) +endif + +if(wrfbdy%n_moist > 1) then + !-- qc on bdy + call check ( nf90_inq_varid(wrfbdy%ncid, "QCLOUD_BXS", wrfbdy%qcxs_id) ) + allocate( wrfbdy%qcxs( wrfbdy%sn, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) + + call check ( nf90_inq_varid(wrfbdy%ncid, "QCLOUD_BXE", wrfbdy%qcxe_id) ) + allocate( wrfbdy%qcxe( wrfbdy%sn, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) + + call check ( nf90_inq_varid(wrfbdy%ncid, "QCLOUD_BYS", wrfbdy%qcys_id) ) + allocate( wrfbdy%qcys( wrfbdy%we, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) + + call check ( nf90_inq_varid(wrfbdy%ncid, "QCLOUD_BYE", wrfbdy%qcye_id) ) + allocate( wrfbdy%qcye( wrfbdy%we, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) + + !-- qc tendency on bdy + call check ( nf90_inq_varid(wrfbdy%ncid, "QCLOUD_BTXS", wrfbdy%qctxs_id) ) + allocate( wrfbdy%qctxs( wrfbdy%sn, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) + + call check ( nf90_inq_varid(wrfbdy%ncid, "QCLOUD_BTXE", wrfbdy%qctxe_id) ) + allocate( wrfbdy%qctxe( wrfbdy%sn, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) + + call check ( nf90_inq_varid(wrfbdy%ncid, "QCLOUD_BTYS", wrfbdy%qctys_id) ) + allocate( wrfbdy%qctys( wrfbdy%we, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) + + call check ( nf90_inq_varid(wrfbdy%ncid, "QCLOUD_BTYE", wrfbdy%qctye_id) ) + allocate( wrfbdy%qctye( wrfbdy%we, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) +endif + +if(wrfbdy%n_moist > 2) then + !-- qr on bdy + call check ( nf90_inq_varid(wrfbdy%ncid, "QRAIN_BXS", wrfbdy%qrxs_id) ) + allocate( wrfbdy%qrxs( wrfbdy%sn, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) + + call check ( nf90_inq_varid(wrfbdy%ncid, "QRAIN_BXE", wrfbdy%qrxe_id) ) + allocate( wrfbdy%qrxe( wrfbdy%sn, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) + + call check ( nf90_inq_varid(wrfbdy%ncid, "QRAIN_BYS", wrfbdy%qrys_id) ) + allocate( wrfbdy%qrys( wrfbdy%we, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) + + call check ( nf90_inq_varid(wrfbdy%ncid, "QRAIN_BYE", wrfbdy%qrye_id) ) + allocate( wrfbdy%qrye( wrfbdy%we, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) + + !-- qr tendency on bdy + call check ( nf90_inq_varid(wrfbdy%ncid, "QRAIN_BTXS", wrfbdy%qrtxs_id) ) + allocate( wrfbdy%qrtxs( wrfbdy%sn, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) + + call check ( nf90_inq_varid(wrfbdy%ncid, "QRAIN_BTXE", wrfbdy%qrtxe_id) ) + allocate( wrfbdy%qrtxe( wrfbdy%sn, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) + + call check ( nf90_inq_varid(wrfbdy%ncid, "QRAIN_BTYS", wrfbdy%qrtys_id) ) + allocate( wrfbdy%qrtys( wrfbdy%we, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) + + call check ( nf90_inq_varid(wrfbdy%ncid, "QRAIN_BTYE", wrfbdy%qrtye_id) ) + allocate( wrfbdy%qrtye( wrfbdy%we, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) +endif + +if(wrfbdy%n_moist > 3) then + !-- qi on bdy + call check ( nf90_inq_varid(wrfbdy%ncid, "QICE_BXS", wrfbdy%qixs_id) ) + allocate( wrfbdy%qixs( wrfbdy%sn, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) + + call check ( nf90_inq_varid(wrfbdy%ncid, "QICE_BXE", wrfbdy%qixe_id) ) + allocate( wrfbdy%qixe( wrfbdy%sn, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) + + call check ( nf90_inq_varid(wrfbdy%ncid, "QICE_BYS", wrfbdy%qiys_id) ) + allocate( wrfbdy%qiys( wrfbdy%we, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) + + call check ( nf90_inq_varid(wrfbdy%ncid, "QICE_BYE", wrfbdy%qiye_id) ) + allocate( wrfbdy%qiye( wrfbdy%we, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) + + !-- qi tendency on bdy + call check ( nf90_inq_varid(wrfbdy%ncid, "QICE_BTXS", wrfbdy%qitxs_id) ) + allocate( wrfbdy%qitxs( wrfbdy%sn, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) + + call check ( nf90_inq_varid(wrfbdy%ncid, "QICE_BTXE", wrfbdy%qitxe_id) ) + allocate( wrfbdy%qitxe( wrfbdy%sn, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) + + call check ( nf90_inq_varid(wrfbdy%ncid, "QICE_BTYS", wrfbdy%qitys_id) ) + allocate( wrfbdy%qitys( wrfbdy%we, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) + + call check ( nf90_inq_varid(wrfbdy%ncid, "QICE_BTYE", wrfbdy%qitye_id) ) + allocate( wrfbdy%qitye( wrfbdy%we, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) +endif + +if(wrfbdy%n_moist > 4) then + !-- qs on bdy + call check ( nf90_inq_varid(wrfbdy%ncid, "QSNOW_BXS", wrfbdy%qsxs_id) ) + allocate( wrfbdy%qsxs( wrfbdy%sn, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) + + call check ( nf90_inq_varid(wrfbdy%ncid, "QSNOW_BXE", wrfbdy%qsxe_id) ) + allocate( wrfbdy%qsxe( wrfbdy%sn, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) + + call check ( nf90_inq_varid(wrfbdy%ncid, "QSNOW_BYS", wrfbdy%qsys_id) ) + allocate( wrfbdy%qsys( wrfbdy%we, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) + + call check ( nf90_inq_varid(wrfbdy%ncid, "QSNOW_BYE", wrfbdy%qsye_id) ) + allocate( wrfbdy%qsye( wrfbdy%we, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) + + !-- qs tendency on bdy + call check ( nf90_inq_varid(wrfbdy%ncid, "QSNOW_BTXS", wrfbdy%qstxs_id) ) + allocate( wrfbdy%qstxs( wrfbdy%sn, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) + + call check ( nf90_inq_varid(wrfbdy%ncid, "QSNOW_BTXE", wrfbdy%qstxe_id) ) + allocate( wrfbdy%qstxe( wrfbdy%sn, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) + + call check ( nf90_inq_varid(wrfbdy%ncid, "QSNOW_BTYS", wrfbdy%qstys_id) ) + allocate( wrfbdy%qstys( wrfbdy%we, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) + + call check ( nf90_inq_varid(wrfbdy%ncid, "QSNOW_BTYE", wrfbdy%qstye_id) ) + allocate( wrfbdy%qstye( wrfbdy%we, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) +endif + +if(wrfbdy%n_moist > 5) then + !-- qg on bdy + call check ( nf90_inq_varid(wrfbdy%ncid, "QGRAUP_BXS", wrfbdy%qgxs_id) ) + allocate( wrfbdy%qgxs( wrfbdy%sn, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) + + call check ( nf90_inq_varid(wrfbdy%ncid, "QGRAUP_BXE", wrfbdy%qgxe_id) ) + allocate( wrfbdy%qgxe( wrfbdy%sn, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) + + call check ( nf90_inq_varid(wrfbdy%ncid, "QGRAUP_BYS", wrfbdy%qgys_id) ) + allocate( wrfbdy%qgys( wrfbdy%we, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) + + call check ( nf90_inq_varid(wrfbdy%ncid, "QGRAUP_BYE", wrfbdy%qgye_id) ) + allocate( wrfbdy%qgye( wrfbdy%we, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) + + !-- qg tendency on bdy + call check ( nf90_inq_varid(wrfbdy%ncid, "QGRAUP_BTXS", wrfbdy%qgtxs_id) ) + allocate( wrfbdy%qgtxs( wrfbdy%sn, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) + + call check ( nf90_inq_varid(wrfbdy%ncid, "QGRAUP_BTXE", wrfbdy%qgtxe_id) ) + allocate( wrfbdy%qgtxe( wrfbdy%sn, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) + + call check ( nf90_inq_varid(wrfbdy%ncid, "QGRAUP_BTYS", wrfbdy%qgtys_id) ) + allocate( wrfbdy%qgtys( wrfbdy%we, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) + + call check ( nf90_inq_varid(wrfbdy%ncid, "QGRAUP_BTYE", wrfbdy%qgtye_id) ) + allocate( wrfbdy%qgtye( wrfbdy%we, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) +endif + +if(wrfbdy%n_moist > 6) then + !-- qnice on bdy + call check ( nf90_inq_varid(wrfbdy%ncid, "QNICE_BXS", wrfbdy%qnicexs_id) ) + allocate( wrfbdy%qnicexs( wrfbdy%sn, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) + + call check ( nf90_inq_varid(wrfbdy%ncid, "QNICE_BXE", wrfbdy%qnicexe_id) ) + allocate( wrfbdy%qnicexe( wrfbdy%sn, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) + + call check ( nf90_inq_varid(wrfbdy%ncid, "QNICE_BYS", wrfbdy%qniceys_id) ) + allocate( wrfbdy%qniceys( wrfbdy%we, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) + + call check ( nf90_inq_varid(wrfbdy%ncid, "QNICE_BYE", wrfbdy%qniceye_id) ) + allocate( wrfbdy%qniceye( wrfbdy%we, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) + + !-- qnice tendency on bdy + call check ( nf90_inq_varid(wrfbdy%ncid, "QNICE_BTXS", wrfbdy%qnicetxs_id) ) + allocate( wrfbdy%qnicetxs( wrfbdy%sn, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) + + call check ( nf90_inq_varid(wrfbdy%ncid, "QNICE_BTXE", wrfbdy%qnicetxe_id) ) + allocate( wrfbdy%qnicetxe( wrfbdy%sn, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) + + call check ( nf90_inq_varid(wrfbdy%ncid, "QNICE_BTYS", wrfbdy%qnicetys_id) ) + allocate( wrfbdy%qnicetys( wrfbdy%we, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) + + call check ( nf90_inq_varid(wrfbdy%ncid, "QNICE_BTYE", wrfbdy%qnicetye_id) ) + allocate( wrfbdy%qnicetye( wrfbdy%we, wrfbdy%bt, wrfbdy%bdywdth, wrfbdy%time ) ) +endif + +if(wrfbdy%n_moist > 7) then + write(6,*) 'n_moist = ',wrfbdy%n_moist + call error_handler(E_ERR,'wrfbdy_open_and_alloc', & + 'n_moist is too large.', source, revision, revdate) +endif + +contains + + ! Internal subroutine - checks error status after each netcdf, prints + ! text message each time an error code is returned. + subroutine check(istatus) + integer, intent ( in) :: istatus + if(istatus /= nf90_noerr) call error_handler(E_ERR,'wrfbdy_open_and_alloc', & + trim(nf90_strerror(istatus)), source, revision, revdate) + end subroutine check + +end subroutine wrfbdy_open_and_alloc + +!--------------------------------------------------------------- + +subroutine wrfbdy_dealloc( wrfbdy ) + +implicit none + +type(wrf_bdy_data) :: wrfbdy + +if ( .not. module_initialized ) call initialize_module + + !-- u on bdy +deallocate( wrfbdy%uxs ) +deallocate( wrfbdy%uxe ) +deallocate( wrfbdy%uys ) +deallocate( wrfbdy%uye ) + + !-- u tendency on bdy +deallocate( wrfbdy%utxs ) +deallocate( wrfbdy%utxe ) +deallocate( wrfbdy%utys ) +deallocate( wrfbdy%utye ) + + !-- v on bdy +deallocate( wrfbdy%vxs ) +deallocate( wrfbdy%vxe ) +deallocate( wrfbdy%vys ) +deallocate( wrfbdy%vye ) + + !-- v tendency on bdy +deallocate( wrfbdy%vtxs ) +deallocate( wrfbdy%vtxe ) +deallocate( wrfbdy%vtys ) +deallocate( wrfbdy%vtye ) + + !-- w on bdy +deallocate( wrfbdy%wxs ) +deallocate( wrfbdy%wxe ) +deallocate( wrfbdy%wys ) +deallocate( wrfbdy%wye ) + + !-- w tendency on bdy +deallocate( wrfbdy%wtxs ) +deallocate( wrfbdy%wtxe ) +deallocate( wrfbdy%wtys ) +deallocate( wrfbdy%wtye ) + + !-- height on bdy +deallocate( wrfbdy%phxs ) +deallocate( wrfbdy%phxe ) +deallocate( wrfbdy%phys ) +deallocate( wrfbdy%phye ) + + !-- height tendency on bdy +deallocate( wrfbdy%phtxs ) +deallocate( wrfbdy%phtxe ) +deallocate( wrfbdy%phtys ) +deallocate( wrfbdy%phtye ) + + !-- t on bdy +deallocate( wrfbdy%txs ) +deallocate( wrfbdy%txe ) +deallocate( wrfbdy%tys ) +deallocate( wrfbdy%tye ) + + !-- t tendency on bdy +deallocate( wrfbdy%ttxs ) +deallocate( wrfbdy%ttxe ) +deallocate( wrfbdy%ttys ) +deallocate( wrfbdy%ttye ) + + !-- mu on bdy +deallocate( wrfbdy%muxs ) +deallocate( wrfbdy%muxe ) +deallocate( wrfbdy%muys ) +deallocate( wrfbdy%muye ) + + !-- mu tendency on bdy +deallocate( wrfbdy%mutxs ) +deallocate( wrfbdy%mutxe ) +deallocate( wrfbdy%mutys ) +deallocate( wrfbdy%mutye ) + +if(wrfbdy%n_moist > 0) then + !-- qv on bdy + deallocate( wrfbdy%qvxs ) + deallocate( wrfbdy%qvxe ) + deallocate( wrfbdy%qvys ) + deallocate( wrfbdy%qvye ) + + !-- qv tendency on bdy + deallocate( wrfbdy%qvtxs ) + deallocate( wrfbdy%qvtxe ) + deallocate( wrfbdy%qvtys ) + deallocate( wrfbdy%qvtye ) +endif + +if(wrfbdy%n_moist > 1) then + !-- qc on bdy + deallocate( wrfbdy%qcxs ) + deallocate( wrfbdy%qcxe ) + deallocate( wrfbdy%qcys ) + deallocate( wrfbdy%qcye ) + + !-- qc tendency on bdy + deallocate( wrfbdy%qctxs ) + deallocate( wrfbdy%qctxe ) + deallocate( wrfbdy%qctys ) + deallocate( wrfbdy%qctye ) +endif + +if(wrfbdy%n_moist > 2) then + !-- qr on bdy + deallocate( wrfbdy%qrxs ) + deallocate( wrfbdy%qrxe ) + deallocate( wrfbdy%qrys ) + deallocate( wrfbdy%qrye ) + + !-- qr tendency on bdy + deallocate( wrfbdy%qrtxs ) + deallocate( wrfbdy%qrtxe ) + deallocate( wrfbdy%qrtys ) + deallocate( wrfbdy%qrtye ) +endif + +if(wrfbdy%n_moist > 3) then + !-- qi on bdy + deallocate( wrfbdy%qixs ) + deallocate( wrfbdy%qixe ) + deallocate( wrfbdy%qiys ) + deallocate( wrfbdy%qiye ) + + !-- qi tendency on bdy + deallocate( wrfbdy%qitxs ) + deallocate( wrfbdy%qitxe ) + deallocate( wrfbdy%qitys ) + deallocate( wrfbdy%qitye ) +endif + +if(wrfbdy%n_moist > 4) then + !-- qs on bdy + deallocate( wrfbdy%qsxs ) + deallocate( wrfbdy%qsxe ) + deallocate( wrfbdy%qsys ) + deallocate( wrfbdy%qsye ) + + !-- qs tendency on bdy + deallocate( wrfbdy%qstxs ) + deallocate( wrfbdy%qstxe ) + deallocate( wrfbdy%qstys ) + deallocate( wrfbdy%qstye ) +endif + +if(wrfbdy%n_moist > 5) then + !-- qg on bdy + deallocate( wrfbdy%qgxs ) + deallocate( wrfbdy%qgxe ) + deallocate( wrfbdy%qgys ) + deallocate( wrfbdy%qgye ) + + !-- qg tendency on bdy + deallocate( wrfbdy%qgtxs ) + deallocate( wrfbdy%qgtxe ) + deallocate( wrfbdy%qgtys ) + deallocate( wrfbdy%qgtye ) +endif + +if(wrfbdy%n_moist > 6) then + !-- qnice on bdy + deallocate( wrfbdy%qnicexs ) + deallocate( wrfbdy%qnicexe ) + deallocate( wrfbdy%qniceys ) + deallocate( wrfbdy%qniceye ) + + !-- qnice tendency on bdy + deallocate( wrfbdy%qnicetxs ) + deallocate( wrfbdy%qnicetxe ) + deallocate( wrfbdy%qnicetys ) + deallocate( wrfbdy%qnicetye ) +endif + +if(wrfbdy%n_moist > 7) then + write(6,*) 'n_moist = ',wrfbdy%n_moist + call error_handler(E_ERR,'wrfbdy_dealloc', & + 'n_moist is too large.', source, revision, revdate) +endif + +end subroutine wrfbdy_dealloc + +!****************************************************************************** + +subroutine wrf_io( wrf, in_or_out, debug ) + +implicit none + +type(wrf_data) :: wrf +character (len=6) :: in_or_out +logical :: debug + +integer :: k, ndims, lngth, dimids(5), istatus + +!---------------------------------------------------------------------- + +if ( .not. module_initialized ) call initialize_module + +if(debug) then + + if (in_or_out == "OUTPUT") then + call error_handler(E_MSG,'wrf_io','Writing to the WRF restart netCDF file.', & + source,revision,revdate) + else + call error_handler(E_MSG,'wrf_io','Reading the WRF restart netCDF file.', & + source,revision,revdate) + endif + +endif + +!---------------------------------------------------------------------- +! Reading or Writing the variables. ignoring count, stride, map ... +!---------------------------------------------------------------------- + +call check( nf90_inquire_variable(wrf%ncid, wrf%u_id, ndims=ndims, dimids=dimids) ) +call check( nf90_inquire_dimension(wrf%ncid, dimids(ndims), len=lngth) ) + +if(debug) write(6,*) 'len= ',lngth,' n_moist = ',wrf%n_moist + +if (in_or_out == "OUTPUT") then + call check( nf90_put_var(wrf%ncid, wrf%u_id, wrf%u, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrf%ncid, wrf%v_id, wrf%v, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrf%ncid, wrf%w_id, wrf%w, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrf%ncid, wrf%ph_id, wrf%ph, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrf%ncid, wrf%t_id, wrf%t, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrf%ncid, wrf%mu_id, wrf%mu, start = (/ 1, 1, 1 /))) + if(wrf%n_moist > 0) then + call check( nf90_put_var(wrf%ncid, wrf%qv_id, wrf%qv, start = (/ 1, 1, 1, 1 /))) + endif + if(wrf%n_moist > 1) then + call check( nf90_put_var(wrf%ncid, wrf%qc_id, wrf%qc, start = (/ 1, 1, 1, 1 /))) + endif + if(wrf%n_moist > 2) then + call check( nf90_put_var(wrf%ncid, wrf%qr_id, wrf%qr, start = (/ 1, 1, 1, 1 /))) + endif + if(wrf%n_moist > 3) then + call check( nf90_put_var(wrf%ncid, wrf%qi_id, wrf%qi, start = (/ 1, 1, 1, 1 /))) + endif + if(wrf%n_moist > 4) then + call check( nf90_put_var(wrf%ncid, wrf%qs_id, wrf%qs, start = (/ 1, 1, 1, 1 /))) + endif + if(wrf%n_moist > 5) then + call check( nf90_put_var(wrf%ncid, wrf%qg_id, wrf%qg, start = (/ 1, 1, 1, 1 /))) + endif + if(wrf%n_moist > 6) then + call check( nf90_put_var(wrf%ncid, wrf%qnice_id, wrf%qnice, start = (/ 1, 1, 1, 1 /))) + endif + if(wrf%n_moist > 7) then + write(6,*) 'n_moist = ',wrf%n_moist + call error_handler(E_ERR,'wrf_io', & + 'n_moist is too large.', source, revision, revdate) + endif + if( wrf%surf_obs ) then + call check( nf90_put_var(wrf%ncid, wrf%u10_id, wrf%u10, start = (/ 1, 1, 1 /))) + call check( nf90_put_var(wrf%ncid, wrf%v10_id, wrf%v10, start = (/ 1, 1, 1 /))) + call check( nf90_put_var(wrf%ncid, wrf%t2_id, wrf%t2, start = (/ 1, 1, 1 /))) + call check( nf90_put_var(wrf%ncid, wrf%th2_id, wrf%th2, start = (/ 1, 1, 1 /))) + call check( nf90_put_var(wrf%ncid, wrf%q2_id, wrf%q2, start = (/ 1, 1, 1 /))) + istatus = nf90_put_var(wrf%ncid, wrf%ps_id, wrf%ps, start = (/ 1, 1, 1 /)) + if(istatus /= nf90_noerr) then + call error_handler(E_MSG,'PSFC', & + trim(nf90_strerror(istatus)), source, revision, revdate) + call error_handler(E_MSG,'wrf_io', & + 'creates PSFC', source, revision, revdate) + call check(nf90_Redef(wrf%ncid)) + call check(nf90_def_var(wrf%ncid, name="PSFC", xtype=nf90_real, & + dimids= (/ wrf%we_id, wrf%sn_id/), varid=wrf%ps_id) ) + call check(nf90_enddef(wrf%ncid)) + call check( nf90_put_var(wrf%ncid, wrf%ps_id, wrf%ps, start = (/ 1, 1, 1 /))) + endif + endif + if( wrf%soil_data ) then + call check( nf90_put_var(wrf%ncid, wrf%tslb_id, wrf%tslb, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrf%ncid, wrf%smois_id, wrf%smois, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrf%ncid, wrf%sh2o_id, wrf%sh2o, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrf%ncid, wrf%tsk_id, wrf%tsk, start = (/ 1, 1, 1 /))) + endif + if( wrf%h_diab ) then + istatus = nf90_put_var(wrf%ncid, wrf%hdiab_id, wrf%hdiab, start = (/ 1, 1, 1, 1 /)) + if(istatus /= nf90_noerr) then + call error_handler(E_ERR,'H_DIABATIC', & + trim(nf90_strerror(istatus)), source, revision, revdate) + endif + endif +else + call check( nf90_get_var(wrf%ncid, wrf%u_id, wrf%u, start = (/ 1, 1, 1, lngth /))) + call check( nf90_get_var(wrf%ncid, wrf%v_id, wrf%v, start = (/ 1, 1, 1, lngth /))) + call check( nf90_get_var(wrf%ncid, wrf%w_id, wrf%w, start = (/ 1, 1, 1, lngth /))) + call check( nf90_get_var(wrf%ncid, wrf%ph_id, wrf%ph, start = (/ 1, 1, 1, lngth /))) + call check( nf90_get_var(wrf%ncid, wrf%phb_id, wrf%phb, start = (/ 1, 1, 1, lngth /))) + call check( nf90_get_var(wrf%ncid, wrf%t_id, wrf%t, start = (/ 1, 1, 1, lngth /))) + call check( nf90_get_var(wrf%ncid, wrf%mu_id, wrf%mu, start = (/ 1, 1, lngth /))) + call check( nf90_get_var(wrf%ncid, wrf%mub_id, wrf%mub, start = (/ 1, 1, lngth /))) + if(wrf%n_moist > 0) then + call check( nf90_get_var(wrf%ncid, wrf%qv_id, wrf%qv, start = (/ 1, 1, 1, lngth /))) + endif + if(wrf%n_moist > 1) then + call check( nf90_get_var(wrf%ncid, wrf%qc_id, wrf%qc, start = (/ 1, 1, 1, lngth /))) + endif + if(wrf%n_moist > 2) then + call check( nf90_get_var(wrf%ncid, wrf%qr_id, wrf%qr, start = (/ 1, 1, 1, lngth /))) + endif + if(wrf%n_moist > 3) then + call check( nf90_get_var(wrf%ncid, wrf%qi_id, wrf%qi, start = (/ 1, 1, 1, lngth /))) + endif + if(wrf%n_moist > 4) then + call check( nf90_get_var(wrf%ncid, wrf%qs_id, wrf%qs, start = (/ 1, 1, 1, lngth /))) + endif + if(wrf%n_moist > 5) then + call check( nf90_get_var(wrf%ncid, wrf%qg_id, wrf%qg, start = (/ 1, 1, 1, lngth /))) + endif + if(wrf%n_moist > 6) then + call check( nf90_get_var(wrf%ncid, wrf%qnice_id, wrf%qnice, start = (/ 1, 1, 1, lngth /))) + endif + if(wrf%n_moist > 7) then + write(6,*) 'n_moist = ',wrf%n_moist + call error_handler(E_ERR,'wrf_io', & + 'n_moist is too large.', source, revision, revdate) + endif + if( wrf%surf_obs ) then + call check( nf90_get_var(wrf%ncid, wrf%u10_id, wrf%u10, start = (/ 1, 1, lngth /))) + call check( nf90_get_var(wrf%ncid, wrf%v10_id, wrf%v10, start = (/ 1, 1, lngth /))) + call check( nf90_get_var(wrf%ncid, wrf%t2_id, wrf%t2, start = (/ 1, 1, lngth /))) + call check( nf90_get_var(wrf%ncid, wrf%th2_id, wrf%th2, start = (/ 1, 1, lngth /))) + call check( nf90_get_var(wrf%ncid, wrf%q2_id, wrf%q2, start = (/ 1, 1, lngth /))) + istatus = nf90_inq_varid(wrf%ncid, "PSFC", wrf%ps_id) + if(istatus == nf90_noerr) then + call check( nf90_get_var(wrf%ncid, wrf%ps_id, wrf%ps, start = (/ 1, 1, lngth /))) + else + call error_handler(E_MSG,'PSFC', & + trim(nf90_strerror(istatus)), source, revision, revdate) + call error_handler(E_MSG,'wrf_io', & + 'sets PSFC to zero', source, revision, revdate) + wrf%ps(:,:) = 0.0_r8 + endif + endif + if( wrf%soil_data ) then + call check( nf90_get_var(wrf%ncid, wrf%tslb_id, wrf%tslb, start = (/ 1, 1, 1, lngth /))) + call check( nf90_get_var(wrf%ncid, wrf%smois_id, wrf%smois, start = (/ 1, 1, 1, lngth /))) + call check( nf90_get_var(wrf%ncid, wrf%sh2o_id, wrf%sh2o, start = (/ 1, 1, 1, lngth /))) + call check( nf90_get_var(wrf%ncid, wrf%tsk_id, wrf%tsk, start = (/ 1, 1, lngth /))) + endif + if( wrf%h_diab ) then + istatus = nf90_get_var(wrf%ncid, wrf%hdiab_id, wrf%hdiab, start = (/ 1, 1, 1, lngth /)) + if(istatus /= nf90_noerr) then + call error_handler(E_MSG,'H_DIABATIC', & + trim(nf90_strerror(istatus)), source, revision, revdate) + call error_handler(E_MSG,'wrf_io', & + 'sets H_DIABATIC to zero', source, revision, revdate) + wrf%hdiab(:,:,:) = 0.0_r8 + endif + endif +endif + +if(debug) then + + do k=1,wrf%bt + write(6,*) ' k, corner vals for u ' + write(6,*) k, wrf%u(1,1,k),wrf%u(wrf%we+1,1,k), & + wrf%u(1,wrf%sn,k),wrf%u(wrf%we+1,wrf%sn,k) + enddo + + write(6,*) ' ' + + do k=1,wrf%bt + write(6,*) ' k, corner vals for v ' + write(6,*) k, wrf%v(1,1,k),wrf%v(wrf%we,1,k), & + wrf%v(1,wrf%sn+1,k),wrf%v(wrf%we,wrf%sn+1,k) + enddo + + write(6,*) ' ' + + write(6,*) ' corner vals for mu ' + write(6,*) wrf%mu(1,1),wrf%mu(wrf%we,1), & + wrf%mu(1,wrf%sn),wrf%mu(wrf%we,wrf%sn) + + write(6,*) ' ' + + write(6,*) ' corner vals for mub ' + write(6,*) wrf%mub(1,1),wrf%mub(wrf%we,1), & + wrf%mub(1,wrf%sn),wrf%mub(wrf%we,wrf%sn) + +endif + +contains + + ! Internal subroutine - checks error status after each netcdf, prints + ! text message each time an error code is returned. + subroutine check(istatus) + integer, intent ( in) :: istatus + if(istatus /= nf90_noerr) call error_handler(E_ERR,'wrf_io', & + trim(nf90_strerror(istatus)), source, revision, revdate) + end subroutine check + +end subroutine wrf_io + +!--------------------------------------------------------------- + +subroutine wrfbdy_io( wrfbdy, in_or_out, debug ) + +! Reads or writes wrfbdy; includes all variables (bdy fields +! and tendencies) from file specified in wrfbdy%ncid. + +implicit none + +type(wrf_bdy_data) :: wrfbdy +character (len=6) :: in_or_out +logical :: debug + +integer :: ndims, lngth, dimids(5) + +!---------------------------------------------------------------------- + +if ( .not. module_initialized ) call initialize_module + +if(debug) then + + if (in_or_out == "OUTPUT") then + call error_handler(E_MSG,'wrfbdy_io','Writing to the WRF restart netCDF file.', & + source,revision,revdate) + else + call error_handler(E_MSG,'wrfbdy_io','Reading the WRF restart netCDF file.', & + source,revision,revdate) + endif + +endif + +call check( nf90_inquire_variable(wrfbdy%ncid, wrfbdy%uxs_id, ndims=ndims, dimids=dimids) ) +call check( nf90_inquire_dimension(wrfbdy%ncid, dimids(ndims), len=lngth) ) + +if (in_or_out == "OUTPUT") then + !-- u on boundary + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%uxs_id, wrfbdy%uxs, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%uxe_id, wrfbdy%uxe, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%uys_id, wrfbdy%uys, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%uye_id, wrfbdy%uye, start = (/ 1, 1, 1, 1 /))) + !-- u tendencies on boundary + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%utxs_id, wrfbdy%utxs, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%utxe_id, wrfbdy%utxe, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%utys_id, wrfbdy%utys, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%utye_id, wrfbdy%utye, start = (/ 1, 1, 1, 1 /))) + !-- v on boundary + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%vxs_id, wrfbdy%vxs, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%vxe_id, wrfbdy%vxe, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%vys_id, wrfbdy%vys, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%vye_id, wrfbdy%vye, start = (/ 1, 1, 1, 1 /))) + !-- v tendencies on boundary + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%vtxs_id, wrfbdy%vtxs, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%vtxe_id, wrfbdy%vtxe, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%vtys_id, wrfbdy%vtys, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%vtye_id, wrfbdy%vtye, start = (/ 1, 1, 1, 1 /))) + !-- w on boundary + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%wxs_id, wrfbdy%wxs, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%wxe_id, wrfbdy%wxe, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%wys_id, wrfbdy%wys, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%wye_id, wrfbdy%wye, start = (/ 1, 1, 1, 1 /))) + !-- w tendencies on boundary + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%wtxs_id, wrfbdy%wtxs, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%wtxe_id, wrfbdy%wtxe, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%wtys_id, wrfbdy%wtys, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%wtye_id, wrfbdy%wtye, start = (/ 1, 1, 1, 1 /))) + !-- height on boundary + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%phxs_id, wrfbdy%phxs, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%phxe_id, wrfbdy%phxe, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%phys_id, wrfbdy%phys, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%phye_id, wrfbdy%phye, start = (/ 1, 1, 1, 1 /))) + !-- height tendencies on boundary + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%phtxs_id, wrfbdy%phtxs, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%phtxe_id, wrfbdy%phtxe, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%phtys_id, wrfbdy%phtys, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%phtye_id, wrfbdy%phtye, start = (/ 1, 1, 1, 1 /))) + !-- t on boundary + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%txs_id, wrfbdy%txs, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%txe_id, wrfbdy%txe, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%tys_id, wrfbdy%tys, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%tye_id, wrfbdy%tye, start = (/ 1, 1, 1, 1 /))) + !-- t tendencies on boundary + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%ttxs_id, wrfbdy%ttxs, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%ttxe_id, wrfbdy%ttxe, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%ttys_id, wrfbdy%ttys, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%ttye_id, wrfbdy%ttye, start = (/ 1, 1, 1, 1 /))) + !-- mu on boundary + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%muxs_id, wrfbdy%muxs, start = (/ 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%muxe_id, wrfbdy%muxe, start = (/ 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%muys_id, wrfbdy%muys, start = (/ 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%muye_id, wrfbdy%muye, start = (/ 1, 1, 1 /))) + !-- mu tendencies on boundary + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%mutxs_id, wrfbdy%mutxs, start = (/ 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%mutxe_id, wrfbdy%mutxe, start = (/ 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%mutys_id, wrfbdy%mutys, start = (/ 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%mutye_id, wrfbdy%mutye, start = (/ 1, 1, 1 /))) + if(wrfbdy%n_moist > 0) then + !-- qv on boundary + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%qvxs_id, wrfbdy%qvxs, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%qvxe_id, wrfbdy%qvxe, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%qvys_id, wrfbdy%qvys, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%qvye_id, wrfbdy%qvye, start = (/ 1, 1, 1, 1 /))) + !-- qv tendencies on boundary + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%qvtxs_id, wrfbdy%qvtxs, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%qvtxe_id, wrfbdy%qvtxe, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%qvtys_id, wrfbdy%qvtys, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%qvtye_id, wrfbdy%qvtye, start = (/ 1, 1, 1, 1 /))) + endif + if(wrfbdy%n_moist > 1) then + !-- qc on boundary + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%qcxs_id, wrfbdy%qcxs, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%qcxe_id, wrfbdy%qcxe, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%qcys_id, wrfbdy%qcys, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%qcye_id, wrfbdy%qcye, start = (/ 1, 1, 1, 1 /))) + !-- qc tendencies on boundary + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%qctxs_id, wrfbdy%qctxs, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%qctxe_id, wrfbdy%qctxe, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%qctys_id, wrfbdy%qctys, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%qctye_id, wrfbdy%qctye, start = (/ 1, 1, 1, 1 /))) + endif + if(wrfbdy%n_moist > 2) then + !-- qr on boundary + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%qrxs_id, wrfbdy%qrxs, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%qrxe_id, wrfbdy%qrxe, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%qrys_id, wrfbdy%qrys, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%qrye_id, wrfbdy%qrye, start = (/ 1, 1, 1, 1 /))) + !-- qr tendencies on boundary + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%qrtxs_id, wrfbdy%qrtxs, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%qrtxe_id, wrfbdy%qrtxe, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%qrtys_id, wrfbdy%qrtys, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%qrtye_id, wrfbdy%qrtye, start = (/ 1, 1, 1, 1 /))) + endif + if(wrfbdy%n_moist > 3) then + !-- qi on boundary + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%qixs_id, wrfbdy%qixs, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%qixe_id, wrfbdy%qixe, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%qiys_id, wrfbdy%qiys, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%qiye_id, wrfbdy%qiye, start = (/ 1, 1, 1, 1 /))) + !-- qi tendencies on boundary + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%qitxs_id, wrfbdy%qitxs, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%qitxe_id, wrfbdy%qitxe, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%qitys_id, wrfbdy%qitys, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%qitye_id, wrfbdy%qitye, start = (/ 1, 1, 1, 1 /))) + endif + if(wrfbdy%n_moist > 4) then + !-- qs on boundary + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%qsxs_id, wrfbdy%qsxs, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%qsxe_id, wrfbdy%qsxe, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%qsys_id, wrfbdy%qsys, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%qsye_id, wrfbdy%qsye, start = (/ 1, 1, 1, 1 /))) + !-- qs tendencies on boundary + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%qstxs_id, wrfbdy%qstxs, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%qstxe_id, wrfbdy%qstxe, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%qstys_id, wrfbdy%qstys, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%qstye_id, wrfbdy%qstye, start = (/ 1, 1, 1, 1 /))) + endif + if(wrfbdy%n_moist > 5) then + !-- qg on boundary + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%qgxs_id, wrfbdy%qgxs, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%qgxe_id, wrfbdy%qgxe, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%qgys_id, wrfbdy%qgys, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%qgye_id, wrfbdy%qgye, start = (/ 1, 1, 1, 1 /))) + !-- qg tendencies on boundary + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%qgtxs_id, wrfbdy%qgtxs, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%qgtxe_id, wrfbdy%qgtxe, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%qgtys_id, wrfbdy%qgtys, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%qgtye_id, wrfbdy%qgtye, start = (/ 1, 1, 1, 1 /))) + endif + if(wrfbdy%n_moist > 6) then + !-- qnice on boundary + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%qnicexs_id, wrfbdy%qnicexs, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%qnicexe_id, wrfbdy%qnicexe, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%qniceys_id, wrfbdy%qniceys, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%qniceye_id, wrfbdy%qniceye, start = (/ 1, 1, 1, 1 /))) + !-- qnice tendencies on boundary + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%qnicetxs_id, wrfbdy%qnicetxs, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%qnicetxe_id, wrfbdy%qnicetxe, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%qnicetys_id, wrfbdy%qnicetys, start = (/ 1, 1, 1, 1 /))) + call check( nf90_put_var(wrfbdy%ncid, wrfbdy%qnicetye_id, wrfbdy%qnicetye, start = (/ 1, 1, 1, 1 /))) + endif + if(wrfbdy%n_moist > 7) then + write(6,*) 'n_moist = ',wrfbdy%n_moist + call error_handler(E_ERR,'wrfbdy_io', & + 'n_moist is too large.', source, revision, revdate) + endif +else + !-- u on boundary + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%uxs_id, wrfbdy%uxs, start = (/ 1, 1, 1, lngth /))) + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%uxe_id, wrfbdy%uxe, start = (/ 1, 1, 1, lngth /))) + if(debug) write(6,*) ' calling netcdf read for uys ' + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%uys_id, wrfbdy%uys, start = (/ 1, 1, 1, lngth /))) + if(debug) write(6,*) ' calling netcdf read for uye ' + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%uye_id, wrfbdy%uye, start = (/ 1, 1, 1, lngth /))) + !-- u tendencies on boundary + if(debug) write(6,*) ' calling netcdf read for utxs ' + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%utxs_id, wrfbdy%utxs, start = (/ 1, 1, 1, lngth /))) + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%utxe_id, wrfbdy%utxe, start = (/ 1, 1, 1, lngth /))) + if(debug) write(6,*) ' calling netcdf read for utys ' + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%utys_id, wrfbdy%utys, start = (/ 1, 1, 1, lngth /))) + if(debug) write(6,*) ' calling netcdf read for utye ' + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%utye_id, wrfbdy%utye, start = (/ 1, 1, 1, lngth /))) + !-- v on boundary + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%vxs_id, wrfbdy%vxs, start = (/ 1, 1, 1, lngth /))) + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%vxe_id, wrfbdy%vxe, start = (/ 1, 1, 1, lngth /))) + if(debug) write(6,*) ' calling netcdf read for vys ' + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%vys_id, wrfbdy%vys, start = (/ 1, 1, 1, lngth /))) + if(debug) write(6,*) ' calling netcdf read for vye ' + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%vye_id, wrfbdy%vye, start = (/ 1, 1, 1, lngth /))) + !-- v tendencies on boundary + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%vtxs_id, wrfbdy%vtxs, start = (/ 1, 1, 1, lngth /))) + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%vtxe_id, wrfbdy%vtxe, start = (/ 1, 1, 1, lngth /))) + if(debug) write(6,*) ' calling netcdf read for vtys ' + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%vtys_id, wrfbdy%vtys, start = (/ 1, 1, 1, lngth /))) + if(debug) write(6,*) ' calling netcdf read for vtye ' + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%vtye_id, wrfbdy%vtye, start = (/ 1, 1, 1, lngth /))) + !-- w on boundary + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%wxs_id, wrfbdy%wxs, start = (/ 1, 1, 1, lngth /))) + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%wxe_id, wrfbdy%wxe, start = (/ 1, 1, 1, lngth /))) + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%wys_id, wrfbdy%wys, start = (/ 1, 1, 1, lngth /))) + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%wye_id, wrfbdy%wye, start = (/ 1, 1, 1, lngth /))) + !-- w tendencies on boundary + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%wtxs_id, wrfbdy%wtxs, start = (/ 1, 1, 1, lngth /))) + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%wtxe_id, wrfbdy%wtxe, start = (/ 1, 1, 1, lngth /))) + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%wtys_id, wrfbdy%wtys, start = (/ 1, 1, 1, lngth /))) + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%wtye_id, wrfbdy%wtye, start = (/ 1, 1, 1, lngth /))) + !-- height on boundary + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%phxs_id, wrfbdy%phxs, start = (/ 1, 1, 1, lngth /))) + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%phxe_id, wrfbdy%phxe, start = (/ 1, 1, 1, lngth /))) + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%phys_id, wrfbdy%phys, start = (/ 1, 1, 1, lngth /))) + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%phye_id, wrfbdy%phye, start = (/ 1, 1, 1, lngth /))) + !-- height tendencies on boundary + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%phtxs_id, wrfbdy%phtxs, start = (/ 1, 1, 1, lngth /))) + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%phtxe_id, wrfbdy%phtxe, start = (/ 1, 1, 1, lngth /))) + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%phtys_id, wrfbdy%phtys, start = (/ 1, 1, 1, lngth /))) + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%phtye_id, wrfbdy%phtye, start = (/ 1, 1, 1, lngth /))) + !-- t on boundary + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%txs_id, wrfbdy%txs, start = (/ 1, 1, 1, lngth /))) + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%txe_id, wrfbdy%txe, start = (/ 1, 1, 1, lngth /))) + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%tys_id, wrfbdy%tys, start = (/ 1, 1, 1, lngth /))) + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%tye_id, wrfbdy%tye, start = (/ 1, 1, 1, lngth /))) + !-- t tendencies on boundary + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%ttxs_id, wrfbdy%ttxs, start = (/ 1, 1, 1, lngth /))) + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%ttxe_id, wrfbdy%ttxe, start = (/ 1, 1, 1, lngth /))) + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%ttys_id, wrfbdy%ttys, start = (/ 1, 1, 1, lngth /))) + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%ttye_id, wrfbdy%ttye, start = (/ 1, 1, 1, lngth /))) + !-- mu on boundary + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%muxs_id, wrfbdy%muxs, start = (/ 1, 1, lngth /))) + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%muxe_id, wrfbdy%muxe, start = (/ 1, 1, lngth /))) + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%muys_id, wrfbdy%muys, start = (/ 1, 1, lngth /))) + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%muye_id, wrfbdy%muye, start = (/ 1, 1, lngth /))) + !-- mu tendencies on boundary + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%mutxs_id, wrfbdy%mutxs, start = (/ 1, 1, lngth /))) + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%mutxe_id, wrfbdy%mutxe, start = (/ 1, 1, lngth /))) + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%mutys_id, wrfbdy%mutys, start = (/ 1, 1, lngth /))) + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%mutye_id, wrfbdy%mutye, start = (/ 1, 1, lngth /))) + if(wrfbdy%n_moist > 0) then + !-- qv on boundary + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%qvxs_id, wrfbdy%qvxs, start = (/ 1, 1, 1, lngth /))) + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%qvxe_id, wrfbdy%qvxe, start = (/ 1, 1, 1, lngth /))) + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%qvys_id, wrfbdy%qvys, start = (/ 1, 1, 1, lngth /))) + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%qvye_id, wrfbdy%qvye, start = (/ 1, 1, 1, lngth /))) + !-- qv tendencies on boundary + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%qvtxs_id, wrfbdy%qvtxs, start = (/ 1, 1, 1, lngth /))) + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%qvtxe_id, wrfbdy%qvtxe, start = (/ 1, 1, 1, lngth /))) + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%qvtys_id, wrfbdy%qvtys, start = (/ 1, 1, 1, lngth /))) + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%qvtye_id, wrfbdy%qvtye, start = (/ 1, 1, 1, lngth /))) + endif + if(wrfbdy%n_moist > 1) then + !-- qc on boundary + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%qcxs_id, wrfbdy%qcxs, start = (/ 1, 1, 1, lngth /))) + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%qcxe_id, wrfbdy%qcxe, start = (/ 1, 1, 1, lngth /))) + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%qcys_id, wrfbdy%qcys, start = (/ 1, 1, 1, lngth /))) + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%qcye_id, wrfbdy%qcye, start = (/ 1, 1, 1, lngth /))) + !-- qc tendencies on boundary + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%qctxs_id, wrfbdy%qctxs, start = (/ 1, 1, 1, lngth /))) + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%qctxe_id, wrfbdy%qctxe, start = (/ 1, 1, 1, lngth /))) + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%qctys_id, wrfbdy%qctys, start = (/ 1, 1, 1, lngth /))) + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%qctye_id, wrfbdy%qctye, start = (/ 1, 1, 1, lngth /))) + endif + if(wrfbdy%n_moist > 2) then + !-- qr on boundary + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%qrxs_id, wrfbdy%qrxs, start = (/ 1, 1, 1, lngth /))) + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%qrxe_id, wrfbdy%qrxe, start = (/ 1, 1, 1, lngth /))) + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%qrys_id, wrfbdy%qrys, start = (/ 1, 1, 1, lngth /))) + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%qrye_id, wrfbdy%qrye, start = (/ 1, 1, 1, lngth /))) + !-- qr tendencies on boundary + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%qrtxs_id, wrfbdy%qrtxs, start = (/ 1, 1, 1, lngth /))) + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%qrtxe_id, wrfbdy%qrtxe, start = (/ 1, 1, 1, lngth /))) + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%qrtys_id, wrfbdy%qrtys, start = (/ 1, 1, 1, lngth /))) + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%qrtye_id, wrfbdy%qrtye, start = (/ 1, 1, 1, lngth /))) + endif + if(wrfbdy%n_moist > 3) then + !-- qi on boundary + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%qixs_id, wrfbdy%qixs, start = (/ 1, 1, 1, lngth /))) + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%qixe_id, wrfbdy%qixe, start = (/ 1, 1, 1, lngth /))) + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%qiys_id, wrfbdy%qiys, start = (/ 1, 1, 1, lngth /))) + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%qiye_id, wrfbdy%qiye, start = (/ 1, 1, 1, lngth /))) + !-- qi tendencies on boundary + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%qitxs_id, wrfbdy%qitxs, start = (/ 1, 1, 1, lngth /))) + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%qitxe_id, wrfbdy%qitxe, start = (/ 1, 1, 1, lngth /))) + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%qitys_id, wrfbdy%qitys, start = (/ 1, 1, 1, lngth /))) + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%qitye_id, wrfbdy%qitye, start = (/ 1, 1, 1, lngth /))) + endif + if(wrfbdy%n_moist > 4) then + !-- qs on boundary + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%qsxs_id, wrfbdy%qsxs, start = (/ 1, 1, 1, lngth /))) + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%qsxe_id, wrfbdy%qsxe, start = (/ 1, 1, 1, lngth /))) + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%qsys_id, wrfbdy%qsys, start = (/ 1, 1, 1, lngth /))) + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%qsye_id, wrfbdy%qsye, start = (/ 1, 1, 1, lngth /))) + !-- qs tendencies on boundary + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%qstxs_id, wrfbdy%qstxs, start = (/ 1, 1, 1, lngth /))) + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%qstxe_id, wrfbdy%qstxe, start = (/ 1, 1, 1, lngth /))) + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%qstys_id, wrfbdy%qstys, start = (/ 1, 1, 1, lngth /))) + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%qstye_id, wrfbdy%qstye, start = (/ 1, 1, 1, lngth /))) + endif + if(wrfbdy%n_moist > 5) then + !-- qg on boundary + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%qgxs_id, wrfbdy%qgxs, start = (/ 1, 1, 1, lngth /))) + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%qgxe_id, wrfbdy%qgxe, start = (/ 1, 1, 1, lngth /))) + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%qgys_id, wrfbdy%qgys, start = (/ 1, 1, 1, lngth /))) + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%qgye_id, wrfbdy%qgye, start = (/ 1, 1, 1, lngth /))) + !-- qg tendencies on boundary + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%qgtxs_id, wrfbdy%qgtxs, start = (/ 1, 1, 1, lngth /))) + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%qgtxe_id, wrfbdy%qgtxe, start = (/ 1, 1, 1, lngth /))) + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%qgtys_id, wrfbdy%qgtys, start = (/ 1, 1, 1, lngth /))) + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%qgtye_id, wrfbdy%qgtye, start = (/ 1, 1, 1, lngth /))) + endif + if(wrfbdy%n_moist > 6) then + !-- qnice on boundary + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%qnicexs_id, wrfbdy%qnicexs, start = (/ 1, 1, 1, lngth /))) + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%qnicexe_id, wrfbdy%qnicexe, start = (/ 1, 1, 1, lngth /))) + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%qniceys_id, wrfbdy%qniceys, start = (/ 1, 1, 1, lngth /))) + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%qniceye_id, wrfbdy%qniceye, start = (/ 1, 1, 1, lngth /))) + !-- qnice tendencies on boundary + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%qnicetxs_id, wrfbdy%qnicetxs, start = (/ 1, 1, 1, lngth /))) + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%qnicetxe_id, wrfbdy%qnicetxe, start = (/ 1, 1, 1, lngth /))) + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%qnicetys_id, wrfbdy%qnicetys, start = (/ 1, 1, 1, lngth /))) + call check( nf90_get_var(wrfbdy%ncid, wrfbdy%qnicetye_id, wrfbdy%qnicetye, start = (/ 1, 1, 1, lngth /))) + endif + if(wrfbdy%n_moist > 7) then + write(6,*) 'n_moist = ',wrfbdy%n_moist + call error_handler(E_ERR,'wrfbdy_io', & + 'n_moist is too large.', source, revision, revdate) + endif +endif + +contains + + ! Internal subroutine - checks error status after each netcdf, prints + ! text message each time an error code is returned. + subroutine check(istatus) + integer, intent ( in) :: istatus + if(istatus /= nf90_noerr) call error_handler(E_ERR,'wrfbdy_io', & + trim(nf90_strerror(istatus)), source, revision, revdate) + end subroutine check + +end subroutine wrfbdy_io + +!####################################################### + +subroutine set_wrf_date (timestring, year, month, day, hour, minute, second) + +implicit none + +integer, intent(in) :: year, month, day, hour, minute, second +character(len=19), intent(out) :: timestring + +character(len=4) :: ch_year +character(len=2) :: ch_month, ch_day, ch_hour, ch_minute, ch_second + +if ( .not. module_initialized ) call initialize_module + +write(ch_year,'(i4)') year +write(ch_month,'(i2)') month +if (ch_month(1:1) == " ") ch_month(1:1) = "0" +write(ch_day,'(i2)') day +if (ch_day(1:1) == " ") ch_day(1:1) = "0" +write(ch_hour,'(i2)') hour +if (ch_hour(1:1) == " ") ch_hour(1:1) = "0" +write(ch_minute,'(i2)') minute +if (ch_minute(1:1) == " ") ch_minute(1:1) = "0" +write(ch_second,'(i2)') second +if (ch_second(1:1) == " ") ch_second(1:1) = "0" + +timestring(1:4) = ch_year +timestring(5:5) = "-" +timestring(6:7) = ch_month +timestring(8:8) = "-" +timestring(9:10) = ch_day +timestring(11:11) = "_" +timestring(12:13) = ch_hour +timestring(14:14) = ":" +timestring(15:16) = ch_minute +timestring(17:17) = ":" +timestring(18:19) = ch_second + +end subroutine set_wrf_date + +!####################################################### + +subroutine get_wrf_date (tstring, year, month, day, hour, minute, second) + +implicit none +!-------------------------------------------------------- +! Returns integers taken from wrf%timestring +! It is assumed that the tstring char array is as YYYY-MM-DD_hh:mm:ss + +integer, intent(out) :: year, month, day, hour, minute, second +character(len=19), intent(in) :: tstring + +if ( .not. module_initialized ) call initialize_module + +read(tstring(1:4),'(i4)') year +read(tstring(6:7),'(i2)') month +read(tstring(9:10),'(i2)') day +read(tstring(12:13),'(i2)') hour +read(tstring(15:16),'(i2)') minute +read(tstring(18:19),'(i2)') second + +end subroutine get_wrf_date + +END MODULE wrf_data_module + +! +! $URL$ +! $Id$ +! $Revision$ +! $Date$ diff --git a/models/wrf_unified/model_mod.f90 b/models/wrf_unified/model_mod.f90 new file mode 100644 index 0000000000..3198b03096 --- /dev/null +++ b/models/wrf_unified/model_mod.f90 @@ -0,0 +1,3803 @@ +! DART software - Copyright UCAR. This open source software is provided +! by UCAR, "as is", without charge, subject to all terms of use at +! http://www.image.ucar.edu/DAReS/DART/DART_download +! + +module model_mod + +use types_mod, only : r8, i8, MISSING_R8, digits12, & + gas_constant, gas_constant_v, ps0, gravity + +use time_manager_mod, only : time_type, set_time, GREGORIAN, set_date, & + set_calendar_type + +use location_mod, only : location_type, get_close_type, & + set_location, set_location_missing, & + set_vertical_localization_coord, set_vertical, & + VERTISHEIGHT, VERTISLEVEL, VERTISPRESSURE, & + VERTISSURFACE, VERTISUNDEF, VERTISSCALEHEIGHT, & + loc_get_close => get_close, get_location, & + query_location, is_vertical, vertical_localization_on, & + get_dist + +use utilities_mod, only : register_module, error_handler, & + E_ERR, E_MSG, & + nmlfileunit, do_output, do_nml_file, do_nml_term, & + find_namelist_in_file, check_namelist_read, & + to_upper + +use netcdf_utilities_mod, only : nc_add_global_attribute, nc_synchronize_file, & + nc_add_global_creation_time, & + nc_begin_define_mode, nc_end_define_mode, & + NF90_MAX_NAME, nc_get_variable_size, & + nc_get_variable, nc_close_file, nc_check, & + nc_open_file_readonly, nc_get_variable_size, & + nc_get_global_attribute, nc_get_dimension_size + +use state_structure_mod, only : add_domain, get_domain_size, get_model_variable_indices, & + get_dim_name, get_num_dims, get_dart_vector_index, & + get_varid_from_kind, get_varid_from_varname + +use distributed_state_mod, only : get_state_array, get_state + +use obs_kind_mod, only : get_index_for_quantity, & + QTY_U_WIND_COMPONENT, & + QTY_v_WIND_COMPONENT, & + QTY_10M_U_WIND_COMPONENT, & + QTY_10M_V_WIND_COMPONENT, & + QTY_DENSITY, & + QTY_GEOPOTENTIAL_HEIGHT, & + QTY_PRESSURE, & + QTY_SURFACE_TYPE, & + QTY_SURFACE_ELEVATION, & + QTY_LANDMASK, & + QTY_SURFACE_PRESSURE, & + QTY_VAPOR_MIXING_RATIO, & + QTY_TEMPERATURE, & + QTY_POTENTIAL_TEMPERATURE, & + QTY_DENSITY, & + QTY_VERTICAL_VELOCITY, & + QTY_SPECIFIC_HUMIDITY, & + QTY_VAPOR_MIXING_RATIO, & + QTY_SURFACE_PRESSURE, & + QTY_VORTEX_LAT, & + QTY_VORTEX_LON, & + QTY_VORTEX_PMIN,QTY_VORTEX_WMAX, & + QTY_SKIN_TEMPERATURE, & + QTY_SURFACE_TYPE, & + QTY_2M_TEMPERATURE, & + QTY_2M_POTENTIAL_TEMPERATURE, & + QTY_2M_SPECIFIC_HUMIDITY, & + QTY_RAINWATER_MIXING_RATIO, & + QTY_GRAUPEL_MIXING_RATIO, & + QTY_HAIL_MIXING_RATIO, & + QTY_SNOW_MIXING_RATIO, & + QTY_ICE_MIXING_RATIO, & + QTY_CLOUDWATER_MIXING_RATIO, & + QTY_DROPLET_NUMBER_CONCENTR, & + QTY_ICE_NUMBER_CONCENTRATION, & + QTY_SNOW_NUMBER_CONCENTR, & + QTY_RAIN_NUMBER_CONCENTR, & + QTY_GRAUPEL_NUMBER_CONCENTR, & + QTY_HAIL_NUMBER_CONCENTR, & + QTY_SOIL_TEMPERATURE, & + QTY_SOIL_MOISTURE, & + QTY_SOIL_LIQUID_WATER + +use ensemble_manager_mod, only : ensemble_type + +use default_model_mod, only : write_model_time, & + init_time => fail_init_time, & + init_conditions => fail_init_conditions, & + adv_1step + +use map_utils, only : latlon_to_ij, & + proj_info, & + map_set, & + map_init, & + gridwind_to_truewind, & + PROJ_LATLON, & + PROJ_LC, & + PROJ_PS, & + PROJ_PS_WGS84, & + PROJ_MERC, & + PROJ_GAUSS, & + PROJ_CYL, & + PROJ_CASSINI, & + PROJ_ALBERS_NAD83, & + PROJ_ROTLL + +use netcdf ! no get_char in netcdf_utilities_mod + +implicit none +private + +! routines required by DART code - will be called from filter and other +! DART executables. +public :: get_model_size, & + get_state_meta_data, & + model_interpolate, & + end_model, & + static_init_model, & + nc_write_model_atts, & + get_close_obs, & + get_close_state, & + pert_model_copies, & + convert_vertical_obs, & + convert_vertical_state, & + read_model_time, & + adv_1step, & + init_time, & + init_conditions, & + shortest_time_between_assimilations, & + write_model_time + +! for wrf_dart_obs_preprocess.f90 +public :: get_domain_info + + +! module variables +character(len=256), parameter :: source = "wrf/model_mod.f90" +logical :: module_initialized = .false. + +integer, parameter :: MAX_STATE_VARIABLES = 100 +integer, parameter :: NUM_STATE_TABLE_COLUMNS = 4 +integer, parameter :: NUM_BOUNDS_TABLE_COLUMNS = 3 + +integer, allocatable :: wrf_dom(:) ! This needs a better name, it is the id from add_domain + ! for each wrf_domain added to the state + +!-- Namelist with default values -- +logical :: default_state_variables = .true. +character(len=NF90_MAX_NAME) :: wrf_state_variables(MAX_STATE_VARIABLES*NUM_STATE_TABLE_COLUMNS) = 'NULL' +character(len=NF90_MAX_NAME) :: wrf_state_bounds(NUM_BOUNDS_TABLE_COLUMNS,MAX_STATE_VARIABLES) = 'NULL' +integer :: num_domains = 1 +integer :: calendar_type = GREGORIAN +integer :: assimilation_period_seconds = 21600 +! Max height a surface obs can be away from the actual model surface +! and still be accepted (in meters) +real (kind=r8) :: sfc_elev_max_diff = -1.0_r8 ! could be something like 200.0_r8 + +real (kind=r8) :: center_search_half_length = 500000.0_r8 +real(r8) :: circulation_pres_level = 80000.0_r8 +real(r8) :: circulation_radius = 108000.0_r8 +integer :: center_spline_grid_scale = 10 +integer :: vert_localization_coord = VERTISHEIGHT + +! Allow observations above the surface but below the lowest sigma level. +logical :: allow_obs_below_vol = .false. + +! Do the interpolation of pressure values only after taking the log (.true.) +! vs doing a linear interpolation directly in pressure units (.false.) +logical :: log_vert_interp = .true. +logical :: log_horz_interpM = .false. +logical :: log_horz_interpQ = .false. + +! wrf options, apply to domain 1 only +logical :: polar = .false. +logical :: periodic_x = .false. +logical :: periodic_y = .false. + +logical :: allow_perturbed_ics = .false. +!------------------------------- + +logical, parameter :: restrict_polar = .false. !HK what is this for? Hardcoded in original code + +namelist /model_nml/ & +default_state_variables, & +wrf_state_variables, & +wrf_state_bounds, & +num_domains, & +calendar_type, & +assimilation_period_seconds, & +sfc_elev_max_diff, & +center_search_half_length, & +circulation_pres_level, & +circulation_radius, & +center_spline_grid_scale, & +vert_localization_coord, & +allow_perturbed_ics, & +allow_obs_below_vol, & +log_horz_interpM, & +log_horz_interpQ + +type grid_ll + integer :: map_proj ! MAP_PROJ in wrf netcdf file + type(proj_info) :: proj ! wrf map projection structure + real(r8) :: dx + real(r8) :: truelat1, truelat2, stand_lon + real(r8), dimension(:,:), allocatable :: latitude, latitude_u, latitude_v + real(r8), dimension(:,:), allocatable :: longitude, longitude_u, longitude_v + integer :: we, sn ! west-east, south-north number of grid points + integer :: wes, sns ! west-east staggered, south-north staggered number of grid points + integer :: bt ! bottom-top number of grid points + integer :: bts ! staggered bottom-top number of grid points + real(r8) :: dt ! time step + + ! wrf options, apply to domain 1 only. + logical :: polar = .false. + logical :: periodic_x = .false. + logical :: periodic_y = .false. + +end type grid_ll + +type static_data + real(r8), allocatable :: phb(:,:,:) ! base-state geopotential + real(r8), allocatable :: mub(:,:) ! base state dry air mass in column + real(r8), allocatable :: hgt(:,:) ! Terrain Height + real(r8), allocatable :: dnw(:) ! d(eta) values between full (w) level + real(r8), allocatable :: land(:,:) ! land mask (1 for land, 2 for water) + real(r8), allocatable :: zs(:) ! depths of center of soil layers + real(r8) :: p_top ! Pressure top of the model +end type static_data + +! need grid for each domain +type(grid_ll), allocatable :: grid(:) +type(static_data), allocatable :: stat_dat(:) + +! Physical constants +real(r8), parameter :: rd_over_rv = gas_constant / gas_constant_v +real(r8), parameter :: cpovcv = 1.4_r8 ! cp / (cp - gas_constant) +real(r8), parameter :: ts0 = 300.0_r8 ! Base potential temperature for all levels. +real(r8), parameter :: kappa = 2.0_r8/7.0_r8 ! gas_constant / cp + +contains + +!------------------------------------------------------------------ +subroutine static_init_model() + +integer :: iunit, io + +character(len=NF90_MAX_NAME) :: varname(MAX_STATE_VARIABLES) +integer :: state_qty(MAX_STATE_VARIABLES) +logical :: update_var(MAX_STATE_VARIABLES) +real(r8) :: bounds(MAX_STATE_VARIABLES, 2) ! lower, upper +real(r8) :: lower(MAX_STATE_VARIABLES), upper(MAX_STATE_VARIABLES) +character(len=9) :: in_domain(MAX_STATE_VARIABLES) ! assumes <=9 or 999 + +integer :: nfields, n +logical, allocatable :: domain_mask(:) +integer :: i, field ! loop indices +character (len=1) :: idom ! assumes <=9 + +module_initialized = .true. + +call find_namelist_in_file("input.nml", "model_nml", iunit) +read(iunit, nml = model_nml, iostat = io) +call check_namelist_read(iunit, io, "model_nml") + +! Record the namelist values used for the run +if (do_nml_file()) write(nmlfileunit, nml=model_nml) +if (do_nml_term()) write( * , nml=model_nml) + +call set_calendar_type(calendar_type) + +allocate(wrf_dom(num_domains), grid(num_domains), stat_dat(num_domains)) + +call verify_state_variables(nfields, varname, state_qty, update_var, in_domain) +allocate(domain_mask(nfields)) +bounds(:,:) = MISSING_R8 ! default to no clamping + +do i = 1, num_domains + + do field = 1, nfields + domain_mask(field) = variable_is_on_domain(in_domain(field), i) + call get_variable_bounds(varname(field),lower(field),upper(field)) + end do + + n = count(domain_mask) + bounds(1:n, 1) = pack(lower(1:nfields),domain_mask) + bounds(1:n, 2) = pack(upper(1:nfields),domain_mask) + + write( idom , '(I1)') i + wrf_dom(i) = add_domain('wrfinput_d0'//idom, & + num_vars = n, & + var_names = pack(varname(1:nfields), domain_mask), & + kind_list = pack(state_qty(1:nfields), domain_mask), & + clamp_vals = bounds(1:n,:), & + update_list = pack(update_var(1:nfields), domain_mask) ) + +enddo + +call read_grid() +call read_static_data() + +call set_vertical_localization_coord(vert_localization_coord) + +deallocate(domain_mask) + +end subroutine static_init_model + +!------------------------------------------------------------------ +! Returns the number of items in the state vector as an i8 integer. +function get_model_size() + +integer(i8) :: get_model_size +integer :: i + +if ( .not. module_initialized ) call static_init_model + +get_model_size = 0 +do i = 1, num_domains + get_model_size = get_model_size + get_domain_size(wrf_dom(i)) +enddo + +end function get_model_size + +!------------------------------------------------------------------ +subroutine model_interpolate(state_handle, ens_size, location, qty_in, expected_obs, istatus) + + +type(ensemble_type), intent(in) :: state_handle +integer, intent(in) :: ens_size +type(location_type), intent(in) :: location +integer, intent(in) :: qty_in +real(r8), intent(out) :: expected_obs(ens_size) ! array of interpolated values +integer, intent(out) :: istatus(ens_size) + +integer, parameter :: FAILED_BOUNDS_CHECK = 44 +integer, parameter :: CANNOT_INTERPOLATE_QTY = 55 +integer, parameter :: POLAR_RESTRICTED = 10 ! polar observation while restrict_polar = .true. +integer, parameter :: NOT_IN_ANY_DOMAIN = 11 +integer, parameter :: VERTICAL_LOCATION_FAIL = 66 +real(r8) :: lon_lat_vert(3) +real(r8) :: xloc, yloc ! WRF i,j in the grid +integer :: i, j ! grid +real(r8) :: dx, dxm, dy, dym ! grid fractions +integer :: ll(2), ul(2), lr(2), ur(2) !(x,y) of four corners +integer :: rc ! return code getCorners +integer :: id +integer :: k(ens_size) ! level +integer :: which_vert ! vertical coordinate of the observation +real(r8) :: zloc(ens_size) ! vertical location of the obs for each ens member +real(r8) :: fld_k1(ens_size), fld_k2(ens_size) ! value at level k and k+1 +real(r8) :: fld1(ens_size), fld2(ens_size) ! value at level k and k+1 non-negative if required +logical :: fail +integer :: qty + +if ( .not. module_initialized ) call static_init_model + +expected_obs(:) = MISSING_R8 + +istatus(:) = 1 + +which_vert = nint(query_location(location)) +lon_lat_vert = get_location(location) +call get_domain_info(lon_lat_vert(1),lon_lat_vert(2),id,xloc,yloc) ! mass points + +if (id == 0) then + istatus(:) = NOT_IN_ANY_DOMAIN + return +endif + +if (.not. able_to_interpolate_qty(id, qty_in) ) then + istatus(:) = CANNOT_INTERPOLATE_QTY + return +endif + +qty = update_qty_if_location_is_surface(qty_in, location) + +! horizontal location mass point +call toGrid(xloc,i,dx,dxm) +call toGrid(yloc,j,dy,dym) + +if ( .not. within_bounds_horizontal(i, j, id, qty) ) then + istatus(:) = FAILED_BOUNDS_CHECK + return +endif + +call getCorners(i, j, id, qty, ll, ul, lr, ur, rc) + +! vertical location +call get_level_below_obs(which_vert, id, lon_lat_vert, ens_size, state_handle, ll, ul, lr, ur, dx, dy, dxm, dym, k, zloc, fail) +if (fail) then + istatus(:) = VERTICAL_LOCATION_FAIL + return +endif + + +select case (qty) + case (QTY_U_WIND_COMPONENT, QTY_V_WIND_COMPONENT ) + fld_k1 = wind_interpolate(ens_size, state_handle, qty, id, k, xloc, yloc, i, j, dxm, dx, dy, dym, lon_lat_vert(1)) + fld_k2 = wind_interpolate(ens_size, state_handle, qty, id, k+1, xloc, yloc, i, j, dxm, dx, dy, dym, lon_lat_vert(1)) + case (QTY_TEMPERATURE) + fld_k1 = temperature_interpolate(ens_size, state_handle, id, ll, ul, lr, ur, k, dxm, dx, dy, dym) + fld_k2 = temperature_interpolate(ens_size, state_handle, id, ll, ul, lr, ur, k+1, dxm, dx, dy, dym) + case (QTY_POTENTIAL_TEMPERATURE) + fld_k1 = simple_interpolation(ens_size, state_handle, qty, id, ll, ul, lr, ur, k, dxm, dx, dy, dym) + ts0 + fld_k2 = simple_interpolation(ens_size, state_handle, qty, id, ll, ul, lr, ur, k+1, dxm, dx, dy, dym) + ts0 + case (QTY_DENSITY) + fld_k1 = density_interpolate(ens_size, state_handle, id, ll, ul, lr, ur, k, dxm, dx, dy, dym) + fld_k2 = density_interpolate(ens_size, state_handle, id, ll, ul, lr, ur, k+1, dxm, dx, dy, dym) + case (QTY_VERTICAL_VELOCITY) + zloc(:) = zloc(:) + 0.5_r8 ! Adjust zloc for staggered + k(:) = max(1,int(zloc(:))) ! Adjust corresponding level k + fld_k1(:) = simple_interpolation(ens_size, state_handle, qty, id, ll, ul, lr, ur, k, dxm, dx, dy, dym) + fld_k2(:) = simple_interpolation(ens_size, state_handle, qty, id, ll, ul, lr, ur, k+1, dxm, dx, dy, dym) + case (QTY_SPECIFIC_HUMIDITY) + fld_k1 = specific_humidity_interpolate(ens_size, state_handle, qty, id, ll, ul, lr, ur, k, dxm, dx, dy, dym) + fld_k2 = specific_humidity_interpolate(ens_size, state_handle, qty, id, ll, ul, lr, ur, k+1, dxm, dx, dy, dym) + case (QTY_VAPOR_MIXING_RATIO) + fld_k1(:) = surface_type_interpolate(ens_size, id, ll, ul, lr, ur, dxm, dx, dy, dym) + case (QTY_PRESSURE) + fld_k1 = pressure_interpolate(ens_size, state_handle, id, ll, ul, lr, ur, k, dxm, dx, dy, dym) + fld_k2 = pressure_interpolate(ens_size, state_handle, id, ll, ul, lr, ur, k+1, dxm, dx, dy, dym) + case (QTY_SURFACE_PRESSURE) + fld_k1 = surface_pressure_interpolate(ens_size, state_handle, id, ll, ul, lr, ur, dxm, dx, dy, dym) + case (QTY_VORTEX_LAT, QTY_VORTEX_LON, QTY_VORTEX_PMIN, QTY_VORTEX_WMAX) + call vortex() + case (QTY_GEOPOTENTIAL_HEIGHT) + zloc(:) = zloc(:) + 0.5_r8 ! Adjust zloc for staggered + k(:) = max(1,int(zloc(:))) ! Adjust corresponding level k + fld_k1 = geopotential_height_interpolate(ens_size, state_handle, qty, id, ll, ul, lr, ur, k, dxm, dx, dy, dym) + fld_k2 = geopotential_height_interpolate(ens_size, state_handle, qty, id, ll, ul, lr, ur, k+1, dxm, dx, dy, dym) + case (QTY_SURFACE_ELEVATION) + fld_k1 = surface_elevation_interpolate(ens_size, id, ll, ul, lr, ur, dxm, dx, dy, dym) + case (QTY_SURFACE_TYPE) + fld_k1(:) = surface_type_interpolate(ens_size, id, ll, ul, lr, ur, dxm, dx, dy, dym) + case (QTY_SKIN_TEMPERATURE, QTY_10M_U_WIND_COMPONENT, QTY_10M_V_WIND_COMPONENT, QTY_2M_TEMPERATURE, QTY_2M_SPECIFIC_HUMIDITY) + fld_k1(:) = simple_interpolation(ens_size, state_handle, qty, id, ll, ul, lr, ur, k, dxm, dx, dy, dym) + case default ! simple interpolation + ! HK todo 2D variables + fld_k1(:) = simple_interpolation(ens_size, state_handle, qty, id, ll, ul, lr, ur, k, dxm, dx, dy, dym) + fld_k2(:) = simple_interpolation(ens_size, state_handle, qty, id, ll, ul, lr, ur, k+1, dxm, dx, dy, dym) +end select + +if (surface_qty(qty)) then + expected_obs(:) = fld_k1(:) +else + fld1 = force_non_negative_if_required(ens_size, qty, fld_k1) + fld2 = force_non_negative_if_required(ens_size, qty, fld_k2) + expected_obs(:) = vertical_interpolation(ens_size, zloc, fld1, fld2) +endif + +istatus(:) = 0 + +end subroutine model_interpolate + +!------------------------------------------------------------------ +! Returns the smallest increment in time that the model is capable +! of advancing the state in a given implementation, or the shortest +! time you want the model to advance between assimilations. +function shortest_time_between_assimilations() + +type(time_type) :: shortest_time_between_assimilations +integer :: model_dt + +if ( .not. module_initialized ) call static_init_model + +model_dt = nint(grid(1)%dt) ! model time step in seconds lowest res domain + +if (assimilation_period_seconds < model_dt ) then + shortest_time_between_assimilations = set_time(model_dt) +else + shortest_time_between_assimilations = set_time(assimilation_period_seconds) +endif + +end function shortest_time_between_assimilations + +!------------------------------------------------------------------ +! Given an integer index into the state vector, returns the +! associated location and optionally the physical quantity. +subroutine get_state_meta_data(index_in, location, qty_out) + +integer(i8), intent(in) :: index_in +type(location_type), intent(out) :: location +integer, optional, intent(out) :: qty_out + +integer :: i, j, k, id, var_id, state_id, qty + +if ( .not. module_initialized ) call static_init_model + +! wrf domain may not equal state_id +call get_model_variable_indices(index_in, i, j, k, var_id=var_id, dom_id=state_id, kind_index=qty) + +location = convert_indices_to_lon_lat_lev(i, j, k, var_id, state_id) + +! return DART variable qty if requested +if(present(qty_out)) qty_out = qty + +end subroutine get_state_meta_data + +!------------------------------------------------------------------ +! observations have a type and qty +! observation type not taken in to account for wrf get close calculations +subroutine get_close_obs(gc, base_loc, base_type, locs, loc_qtys, loc_types, & + num_close, close_ind, dist, state_handle) + +type(get_close_type), intent(in) :: gc ! handle to a get_close structure +integer, intent(in) :: base_type ! observation TYPE +type(location_type), intent(inout) :: base_loc ! location of interest +type(location_type), intent(inout) :: locs(:) ! obs/state locations +integer, intent(in) :: loc_qtys(:) ! QTYS for obs +integer, intent(in) :: loc_types(:) ! types for obs +integer, intent(out) :: num_close ! how many are close +integer, intent(out) :: close_ind(:) ! indices into the locs array +real(r8), optional, intent(out) :: dist(:) ! distances in radians +type(ensemble_type), optional, intent(in) :: state_handle + +character(len=*), parameter :: routine = 'get_close_obs' +integer :: istatus(1), loc_qtys_ar(1), loc_types_ar(1), i, t_ind +type(location_type) :: loc_ar(1) + +if (vertical_localization_on()) then + loc_ar(1) = base_loc + ! dummy qty 1 not used in convert_vertical_obs + call convert_vertical_obs(state_handle, 1, loc_ar, (/1/), (/base_type/), vert_localization_coord, istatus) + if (istatus(1) /= 0) then + num_close = 0 + return + endif + base_loc = loc_ar(1) +endif + +call loc_get_close(gc, base_loc, base_type, locs, loc_qtys, & + num_close, close_ind) + +if (.not. present(dist)) return + +do i = 1, num_close + t_ind = close_ind(i) + loc_ar(1) = locs(t_ind) + loc_qtys_ar(1) = loc_qtys(t_ind) ! HK not used in convert_vertical_obs todo: use dummy instead? + loc_types_ar(1) = loc_types(t_ind) ! HK not used in convert_vertical_obs todo: use dummy instead? + + istatus(1) = 0 + if (vertical_localization_on()) then + call convert_vertical_obs(state_handle, 1, loc_ar, loc_qtys_ar, loc_types_ar, vert_localization_coord, istatus) + endif + if (istatus(1) == 0) then + dist(i) = get_dist(base_loc, loc_ar(1), base_type, loc_qtys(t_ind)) + else + dist(i) = 1.0e9 + endif +enddo + + +end subroutine get_close_obs + +!------------------------------------------------------------------ +! state only has qty +subroutine get_close_state(gc, base_loc, base_type, locs, loc_qtys, loc_indx, & + num_close, close_ind, dist, state_handle) + +type(get_close_type), intent(in) :: gc +type(location_type), intent(inout) :: base_loc, locs(:) +integer, intent(in) :: base_type, loc_qtys(:) +integer(i8), intent(in) :: loc_indx(:) +integer, intent(out) :: num_close, close_ind(:) +real(r8), optional, intent(out) :: dist(:) +type(ensemble_type), optional, intent(in) :: state_handle + +character(len=*), parameter :: routine = 'get_close_state' +integer :: istatus(1), loc_qtys_ar(1), i, t_ind +integer(i8) :: loc_indx_ar(1) +type(location_type) :: loc_ar(1) + +loc_ar(1) = base_loc +! dummy qty 1 not used in convert_vertical_obs +call convert_vertical_obs(state_handle, 1, loc_ar, (/1/), (/base_type/), vert_localization_coord, istatus) +if (istatus(1) /= 0) then + num_close = 0 + return +endif +base_loc = loc_ar(1) + +call loc_get_close(gc, base_loc, base_type, locs, loc_qtys, num_close, close_ind) + +if (.not. present(dist)) return + +do i = 1, num_close + t_ind = close_ind(i) + loc_ar(1) = locs(t_ind) + loc_qtys_ar(1) = loc_qtys(t_ind) ! HK not used in convert_vertical_state todo: use dummy instead? + loc_indx_ar(1) = loc_indx(t_ind) + + if (vertical_localization_on()) then + if (nint(query_location(loc_ar(1))) /= vert_localization_coord) then + ! convert_vertical_state always returns istatus = 0 + call convert_vertical_state(state_handle, 1, loc_ar, loc_qtys_ar, loc_indx_ar, vert_localization_coord, istatus(1)) + endif + endif + + dist(i) = get_dist(base_loc, loc_ar(1), base_type, loc_qtys(t_ind)) + +enddo + +end subroutine get_close_state + + +!------------------------------------------------------------------ +! write any additional attributes to netcdf files +! HK todo nc_write_model_atts +subroutine nc_write_model_atts(ncid, domain_id) + +integer, intent(in) :: ncid ! netCDF file identifier +integer, intent(in) :: domain_id + +if ( .not. module_initialized ) call static_init_model + +! put file into define mode. + +call nc_begin_define_mode(ncid) + +call nc_add_global_creation_time(ncid) + +call nc_add_global_attribute(ncid, "model_source", source ) +call nc_add_global_attribute(ncid, "model", "template") + +call nc_end_define_mode(ncid) + +! Flush the buffer and leave netCDF file open +call nc_synchronize_file(ncid) + +end subroutine nc_write_model_atts + +!------------------------------------------------------------------ +subroutine pert_model_copies(ens_handle, ens_size, dummy_pert_amp, interf_provided) + +type(ensemble_type), intent(inout) :: ens_handle +integer, intent(in) :: ens_size +real(r8), intent(in) :: dummy_pert_amp ! not used +logical, intent(out) :: interf_provided + +if (.not. allow_perturbed_ics) then +call error_handler(E_ERR,'pert_model_copies', & + 'starting WRF model from a single vector requires additional steps', & + text2='see comments in wrf/model_mod.f90::pert_model_copies()') +endif + +print*, 'not done' + +end subroutine pert_model_copies + +!------------------------------------------------------------------ +function read_model_time(filename) + +character(len=*), intent(in) :: filename +type(time_type) :: read_model_time + +character(len=*), parameter :: routine = 'read_model_time' +integer :: ncid, ret, var_id +integer :: dim_size(2) ! wrf netcdf: char Times(Time, DateStrLen) +character(len=19) :: timestring ! e.g. 2007-04-26_00:00:00 +integer :: year, month, day, hour, minute, second + +ncid = nc_open_file_readonly(filename, routine) + +call nc_get_variable_size(ncid, 'Times', dim_size, routine) + +ret = nf90_inq_varid(ncid, "Times", var_id) +call nc_check(ret, routine, 'inq_varid Times') + +! last slice of Time dimension +ret = nf90_get_var(ncid, var_id, timestring, start = (/ 1, dim_size(2) /)) +call nc_check(ret, routine, 'get_var Times') + +call get_wrf_date(timestring, year, month, day, hour, minute, second) +read_model_time = set_date(year, month, day, hour, minute, second) + +call nc_close_file(ncid, routine) + +end function read_model_time + +!------------------------------------------------------------------ +subroutine end_model() + +deallocate(wrf_dom, grid, stat_dat) + +end subroutine end_model + +!------------------------------------------------------------------ +! end of public routines +!------------------------------------------------------------------ +function force_non_negative_if_required(n, qty, fld) + +integer, intent(in) :: n +integer, intent(in) :: qty +real(r8), intent(in) :: fld(n) + +real(r8) :: force_non_negative_if_required(n) + +select case (qty) + case (QTY_RAINWATER_MIXING_RATIO, & + QTY_GRAUPEL_MIXING_RATIO, & + QTY_HAIL_MIXING_RATIO, & + QTY_SNOW_MIXING_RATIO, & + QTY_ICE_MIXING_RATIO, & + QTY_CLOUDWATER_MIXING_RATIO, & + QTY_DROPLET_NUMBER_CONCENTR, & + QTY_ICE_NUMBER_CONCENTRATION, & + QTY_SNOW_NUMBER_CONCENTR, & + QTY_RAIN_NUMBER_CONCENTR, & + QTY_GRAUPEL_NUMBER_CONCENTR, & + QTY_HAIL_NUMBER_CONCENTR ) + force_non_negative_if_required = max(0.0_r8, fld) + case default + force_non_negative_if_required = fld +end select + +end function force_non_negative_if_required + +!------------------------------------------------------------------ +! 2D surface variables, or soil variables (z = soil_layers_stag) +function surface_qty(qty) + +integer, intent(in) :: qty +logical :: surface_qty + +!var_id get_varid_from_kind(wrf_dom(id), qty) +! HK todo soil_layers_sta +!do i = 1, get_num_dims(var_id) +!if () then +!get_dim_name +!endif + +select case (qty) + + case (QTY_2M_TEMPERATURE, & + QTY_2M_SPECIFIC_HUMIDITY, & + QTY_10M_U_WIND_COMPONENT, & + QTY_10M_V_WIND_COMPONENT, & + QTY_SURFACE_TYPE, & + QTY_SKIN_TEMPERATURE) + surface_qty = .true. + case default + surface_qty = .false. + +end select + +end function surface_qty + +!------------------------------------------------------------------ +! WRF has separate model qtys for surface variables +function update_qty_if_location_is_surface(qty_in, location) result(qty) + +integer, intent(in) :: qty_in +type(location_type), intent(in) :: location +integer :: qty + +if (.not. is_vertical(location,"SURFACE")) then + qty = qty_in + return +endif + +select case (qty_in) + + case (QTY_U_WIND_COMPONENT); qty = QTY_10M_U_WIND_COMPONENT + case (QTY_V_WIND_COMPONENT); qty = QTY_10M_V_WIND_COMPONENT + case (QTY_POTENTIAL_TEMPERATURE); qty = QTY_2M_TEMPERATURE + case (QTY_SPECIFIC_HUMIDITY); qty = QTY_2M_SPECIFIC_HUMIDITY + case (QTY_VAPOR_MIXING_RATIO); qty = QTY_2M_SPECIFIC_HUMIDITY ! Vapor Mixing Ratio (QV, Q2) + case (QTY_PRESSURE); qty = QTY_SURFACE_PRESSURE + case default; qty = qty_in + +end select + +end function update_qty_if_location_is_surface + +!------------------------------------------------------------------ +function simple_interpolation(ens_size, state_handle, qty, id, ll, ul, lr, ur, k, dxm, dx, dy, dym) + +integer, intent(in) :: ens_size +type(ensemble_type), intent(in) :: state_handle +integer, intent(in) :: qty +integer, intent(in) :: id +integer, intent(in) :: ll(2), ul(2), lr(2), ur(2) ! (x,y) at four corners +integer, intent(in) :: k(ens_size) ! k may be different across the ensemble +real(r8), intent(in) :: dxm, dx, dy, dym + +real(r8) :: simple_interpolation(ens_size) + +integer :: e ! loop variable +! lower left, upper left, lower right, upper right +integer(i8), dimension(ens_size) :: ill, iul, ilr, iur ! dart index at four corners +real(r8), dimension(ens_size) :: x_ill, x_iul, x_ilr, x_iur ! state value at four corners +integer :: var_id + + +var_id = get_varid_from_kind(wrf_dom(id), qty) + +do e = 1, ens_size + ! x, y, z, domain, variable + ill(e) = get_dart_vector_index(ll(1), ll(2), k(e), wrf_dom(id), var_id) + iul(e) = get_dart_vector_index(ul(1), ul(2), k(e), wrf_dom(id), var_id) + ilr(e) = get_dart_vector_index(lr(1), lr(2), k(e), wrf_dom(id), var_id) + iur(e) = get_dart_vector_index(ur(1), ur(2), k(e), wrf_dom(id), var_id) + +enddo + +call get_state_array(x_ill, ill, state_handle) +call get_state_array(x_iul, iul, state_handle) +call get_state_array(x_ilr, ilr, state_handle) +call get_state_array(x_iur, iur, state_handle) + +simple_interpolation = dym*( dxm*x_ill(:) + dx*x_ilr(:) ) + dy*( dxm*x_iul(:) + dx*x_iur(:) ) + +end function simple_interpolation + +!------------------------------------------------------------------ +function vertical_interpolation(ens_size, zloc, fld1, fld2) + +integer, intent(in) :: ens_size +real(r8), intent(in) :: zloc(ens_size) +real(r8), intent(in) :: fld1(ens_size), fld2(ens_size) + +real(r8) :: vertical_interpolation(ens_size) + +real(r8) :: dz, dzm +integer :: z ! level +integer :: e + +do e = 1, ens_size + call toGrid(zloc(e), z, dz, dzm) + ! comment from original code: + ! If you get here and zloc < 1.0, then z will be 0, and + ! we should extrapolate. fld(1,:) and fld(2,:) where computed + ! at levels 1 and 2. + + if (z >= 1) then + ! Linearly interpolate between levels + vertical_interpolation(e) = dzm*fld1(e) + dz*fld2(e) + else + ! Extrapolate below first level. + vertical_interpolation(e) = fld1(e) - (fld2(e)-fld1(e))*dzm + endif +enddo + +end function vertical_interpolation + +!------------------------------------------------------------------ +subroutine get_wrf_date(tstring, year, month, day, hour, minute, second) + +character(len=19), intent(in) :: tstring ! YYYY-MM-DD_hh:mm:ss +integer, intent(out) :: year, month, day, hour, minute, second + +read(tstring( 1: 4),'(i4)') year +read(tstring( 6: 7),'(i2)') month +read(tstring( 9:10),'(i2)') day +read(tstring(12:13),'(i2)') hour +read(tstring(15:16),'(i2)') minute +read(tstring(18:19),'(i2)') second + +end subroutine get_wrf_date + +!------------------------------------------------------------------ +! Verify that the namelist was filled in correctly +! and check there are valid entries for the dart qty. +subroutine verify_state_variables(nvar, varname, qty, update, in_domain) + +integer, intent(out) :: nvar +character(len=NF90_MAX_NAME), intent(out) :: varname(MAX_STATE_VARIABLES) +integer, intent(out) :: qty(MAX_STATE_VARIABLES) +logical, intent(out) :: update(MAX_STATE_VARIABLES) +character(len=9), intent(out) :: in_domain(MAX_STATE_VARIABLES) ! assumes <=9 or 999 + +integer :: i +character(len=NF90_MAX_NAME) :: qty_str, update_str +character(len=256) :: string1, string2 + +if ( .not. module_initialized ) call static_init_model + +nvar = 0 +varloop: do i = 1, MAX_STATE_VARIABLES + + if ( wrf_state_variables(NUM_STATE_TABLE_COLUMNS*i -3) == 'NULL ' ) exit varloop ! Found end of list. !HK do you need to test all columns for ' '? + + varname(i) = trim(wrf_state_variables(NUM_STATE_TABLE_COLUMNS*i -3)) + qty_str = trim(wrf_state_variables(NUM_STATE_TABLE_COLUMNS*i -2)) + update_str = trim(wrf_state_variables(NUM_STATE_TABLE_COLUMNS*i -1)) + in_domain(i) = trim(wrf_state_variables(NUM_STATE_TABLE_COLUMNS*i )) + + call to_upper(update_str) + + + ! Make sure DART qty is valid + qty(i) = get_index_for_quantity(qty_str) + if( qty(i) < 0 ) then + write(string1,'(''there is no QTY <'',a,''> in obs_kind_mod.f90'')') trim(qty_str) + call error_handler(E_ERR,'verify_state_variables',string1) + endif + + ! Force QTY_TEMPERATURE to QTY_POTENTIAL_TEMPERATURE + if (qty(i) == QTY_TEMPERATURE) qty(i) = QTY_POTENTIAL_TEMPERATURE + + select case (varname(i)) !HK do we need to worry about case sensitivity? + case ('MU'); qty(i) = QTY_PRESSURE !HK this is a hack to avoid 2 QTY_SURFACE_PRESSUREs + case ('PSFC'); qty(i) = QTY_SURFACE_PRESSURE + case ('T2'); qty(i) = QTY_2M_TEMPERATURE + case ('TH2'); qty(i) = QTY_2M_POTENTIAL_TEMPERATURE + case ('Q2'); qty(i) = QTY_2M_SPECIFIC_HUMIDITY !Q2 is actually a mixing ratio, not a specific humidity + end select + + ! Make sure the update variable has a valid name + select case (update_str) + case ('UPDATE') + update(i) = .true. + case ('NO_COPY_BACK') + update(i) = .false. + case default + write(string1,'(A)') 'only UPDATE or NO_COPY_BACK supported in model_state_variable namelist' + write(string2,'(6A)') 'you provided : ', trim(varname(i)), ', ', trim(qty_str), ', ', trim(update_str) + call error_handler(E_ERR,'verify_state_variables',string1, text2=string2) + end select + + nvar = nvar + 1 + +enddo varloop + +end subroutine verify_state_variables + +!------------------------------------------------------------------ +! matches WRF variable name in bounds table to input name, and assigns +! the bounds and if the variable is in the state +subroutine get_variable_bounds(var_name, lower, upper) + +character(len=*), intent(in) :: var_name +real(r8), intent(out) :: lower,upper ! bounds +character(len=50) :: bound_trim +integer :: ivar + +! defualt to no bounds +lower = MISSING_R8 +upper = MISSING_R8 + +ivar = 1 +do while ( trim(wrf_state_bounds(1,ivar)) /= 'NULL' ) + + if ( trim(wrf_state_bounds(1,ivar)) == trim(var_name) ) then + + bound_trim = trim(wrf_state_bounds(2,ivar)) + if ( bound_trim /= 'NULL' ) then + read(bound_trim,'(d16.8)') lower + else + lower = MISSING_R8 + endif + + bound_trim = trim(wrf_state_bounds(3,ivar)) + if ( bound_trim /= 'NULL' ) then + read(bound_trim,'(d16.8)') upper + else + upper = MISSING_R8 + endif + + endif + + ivar = ivar + 1 + +enddo + +end subroutine get_variable_bounds + + +!------------------------------------------------------------------ +! This is assuming that the number of domains <=9 +! do while loop could be replaced with intrinsic scan +logical function variable_is_on_domain(domain_id_string, id) + +integer, intent(in) :: id +character(len=*), intent(in) :: domain_id_string + +integer :: domain_int, i + +variable_is_on_domain = .false. + +! if '999' then counts all domains +if ( trim(domain_id_string) == '999' ) then + variable_is_on_domain = .true. +else +i = 1 + do while ( domain_id_string(i:i) /= ' ' ) + read(domain_id_string(i:i),'(i1)') domain_int + if ( domain_int == id ) variable_is_on_domain = .true. + i = i+1 + enddo +endif + +end function variable_is_on_domain + +!------------------------------------------------------------------ +subroutine read_grid() + +integer :: ncid, i +character (len=1) :: idom ! assumes <=9 +character(len=*), parameter :: routine = 'read_grid' + +integer :: dim_size(3) ! (west_east, south_north, Time) + +! This is assuming the unlimited dimension is length 1 +! Should we be reading the latest time slice instead? + +do i = 1, num_domains + + write( idom , '(I1)') i + ncid = nc_open_file_readonly('wrfinput_d0'//idom, routine) + + call nc_get_variable_size(ncid, 'XLONG', dim_size) + allocate(grid(i)%longitude(dim_size(1), dim_size(2))) + call nc_get_variable(ncid, 'XLONG', grid(i)%longitude, routine) + grid(i)%we = dim_size(1); grid(i)%sn = dim_size(2) + + call nc_get_variable_size(ncid, 'XLONG_U', dim_size) + allocate(grid(i)%longitude_u(dim_size(1), dim_size(2))) + call nc_get_variable(ncid, 'XLONG_U', grid(i)%longitude_u, routine) + grid(i)%wes = dim_size(1) + + call nc_get_variable_size(ncid, 'XLONG_V', dim_size) + allocate(grid(i)%longitude_v(dim_size(1), dim_size(2))) + call nc_get_variable(ncid, 'XLONG_V', grid(i)%longitude_v, routine) + grid(i)%sns = dim_size(2) + + call nc_get_variable_size(ncid, 'XLAT', dim_size) + allocate(grid(i)%latitude(dim_size(1), dim_size(2))) + call nc_get_variable(ncid, 'XLAT', grid(i)%latitude, routine) + + call nc_get_variable_size(ncid, 'XLAT_U', dim_size) + allocate(grid(i)%latitude_u(dim_size(1), dim_size(2))) + call nc_get_variable(ncid, 'XLAT_U', grid(i)%latitude_u, routine) + + call nc_get_variable_size(ncid, 'XLAT_V', dim_size) + allocate(grid(i)%latitude_v(dim_size(1), dim_size(2))) + call nc_get_variable(ncid, 'XLAT_V', grid(i)%latitude_v, routine) + + call nc_get_global_attribute(ncid, 'MAP_PROJ', grid(i)%map_proj) + call nc_get_global_attribute(ncid, 'DX', grid(i)%dx) + call nc_get_global_attribute(ncid, 'TRUELAT1', grid(i)%truelat1) + call nc_get_global_attribute(ncid, 'TRUELAT2', grid(i)%truelat2) + call nc_get_global_attribute(ncid, 'STAND_LON', grid(i)%stand_lon) + call nc_get_global_attribute(ncid, 'DT', grid(i)%dt) + + grid(i)%bt = nc_get_dimension_size(ncid, 'bottom_top', routine) + grid(i)%bts = nc_get_dimension_size(ncid, 'bottom_top_stag', routine) + + call nc_close_file(ncid, routine) + + call setup_map_projection(i) + + if (i == 1) then + grid(i)%periodic_x = periodic_x + grid(i)%periodic_y = periodic_y + grid(i)%polar = polar + else + grid(i)%periodic_x = .false. + grid(i)%periodic_y = .false. + grid(i)%polar = .false. + endif + +enddo + +end subroutine read_grid + +!------------------------------------------------------------------ +subroutine read_static_data() + +integer :: ncid, i +character (len=1) :: idom ! assumes <=9 +character(len=*), parameter :: routine = 'read_static_data' + +integer :: dim_size(4) ! (west_east, south_north, bottom_top{_stag}, Time) + +do i = 1, num_domains + + write( idom , '(I1)') i + ncid = nc_open_file_readonly('wrfinput_d0'//idom, routine) + + call nc_get_variable_size(ncid, 'PHB', dim_size) + allocate(stat_dat(i)%phb(dim_size(1), dim_size(2), dim_size(3))) + call nc_get_variable(ncid, 'PHB', stat_dat(i)%phb, routine) + + call nc_get_variable_size(ncid, 'MUB', dim_size) + allocate(stat_dat(i)%mub(dim_size(1), dim_size(2))) + call nc_get_variable(ncid, 'MUB', stat_dat(i)%mub, routine) + + call nc_get_variable_size(ncid, 'HGT', dim_size) + allocate(stat_dat(i)%hgt(dim_size(1), dim_size(2))) + call nc_get_variable(ncid, 'HGT', stat_dat(i)%hgt, routine) + + call nc_get_variable_size(ncid, 'DNW', dim_size) + allocate(stat_dat(i)%dnw(dim_size(1))) + call nc_get_variable(ncid, 'DNW', stat_dat(i)%dnw, routine) + + call nc_get_variable_size(ncid, 'XLAND', dim_size) + allocate(stat_dat(i)%land(dim_size(1), dim_size(2))) + call nc_get_variable(ncid, 'XLAND', stat_dat(i)%land, routine) + + call nc_get_variable_size(ncid, 'ZS', dim_size) + allocate(stat_dat(i)%zs(dim_size(1))) ! soil_layers_stag + call nc_get_variable(ncid, 'ZS', stat_dat(i)%zs, routine) + + call nc_get_variable(ncid, 'P_TOP', stat_dat(i)%p_top, routine) + + + call nc_close_file(ncid, routine) + +end do + +end subroutine read_static_data + +!------------------------------------------------------------------ +pure function compute_geometric_height(geopot, lat) + +real(r8), intent(in) :: geopot +real(r8), intent(in) :: lat +real(r8) :: compute_geometric_height + + +real(digits12) :: pi2, latr +real(digits12) :: semi_major_axis, semi_minor_axis, grav_polar, grav_equator +real(digits12) :: earth_omega, grav_constant, flattening, somigliana +real(digits12) :: grav_ratio, sin2, termg, termr, grav, eccentricity + +! Parameters below from WGS-84 model software inside GPS receivers. +parameter(semi_major_axis = 6378.1370d3) ! (m) +parameter(semi_minor_axis = 6356.7523142d3) ! (m) +parameter(grav_polar = 9.8321849378) ! (m/s2) +parameter(grav_equator = 9.7803253359) ! (m/s2) +parameter(earth_omega = 7.292115d-5) ! (rad/s) +parameter(grav_constant = 3.986004418d14) ! (m3/s2) +parameter(grav = 9.80665d0) ! (m/s2) WMO std g at 45 deg lat +parameter(eccentricity = 0.081819d0) ! unitless +parameter(pi2 = 3.14159265358979d0/180.d0) + +! Derived geophysical constants +parameter(flattening = (semi_major_axis-semi_minor_axis) / semi_major_axis) + +parameter(somigliana = (semi_minor_axis/semi_major_axis)*(grav_polar/grav_equator)-1.d0) + +parameter(grav_ratio = (earth_omega*earth_omega * & + semi_major_axis*semi_major_axis * semi_minor_axis)/grav_constant) + +! To use geopotential height uncomment the following two lines: +!compute_geometric_height = geopot +!return + +latr = lat * (pi2) ! in radians +sin2 = sin(latr) * sin(latr) +termg = grav_equator * ( (1.d0+somigliana*sin2) / & + sqrt(1.d0-eccentricity*eccentricity*sin2) ) +termr = semi_major_axis / (1.d0 + flattening + grav_ratio - 2.d0*flattening*sin2) + +compute_geometric_height = (termr*geopot) / ( (termg/grav) * termr - geopot ) + + +end function compute_geometric_height + +!------------------------------------------------------------------ +! This is mass level +subroutine get_level_below_obs(which_vert, id, lon_lat_vert, ens_size, state_handle, & + ll, ul, lr, ur, dx, dy, dxm, dym, & + level_below, zloc, fail) + +integer, intent(in) :: which_vert +integer, intent(in) :: id +real(r8), intent(in) :: lon_lat_vert(3) +integer, intent(in) :: ens_size +type(ensemble_type), intent(in) :: state_handle +integer, dimension(2), intent(in) :: ll, ul, lr, ur ! (x,y) of each corner +real(r8), intent(in) :: dx, dxm, dy, dym ! grid fractions to obs +integer, intent(out) :: level_below(ens_size) +real(r8), intent(out) :: zloc(ens_size) ! vertical location of the obs for each ens member +logical, intent(out) :: fail + +integer :: e ! loop variable +real(r8) :: v_p(0:grid(id)%bt,ens_size) +real(r8) :: v_h(0:grid(id)%bt,ens_size) +logical :: lev0 + +fail = .false. + +select case (which_vert) + case(VERTISLEVEL) + zloc(:) = lon_lat_vert(3); fail = .false. + case(VERTISPRESSURE) + call get_model_pressure_profile(id, ll, ul, lr, ur, dx, dy, dxm, dym, ens_size, state_handle, v_p) + do e = 1, ens_size + call pres_to_zk(lon_lat_vert(3), v_p(:,e), grid(id)%bt, zloc(e), level_below(e), lev0, fail) + if (fail) return + if (lev0) then ! pressure obs below lowest sigma + if (.not. allow_obs_below_vol) then + fail = .true. + return + endif + endif + enddo + case(VERTISHEIGHT) + call get_model_height_profile(ll, ul, lr, ur, dx, dy, dxm, dym, id, v_h, state_handle, ens_size) + do e = 1, ens_size + call height_to_zk(lon_lat_vert(3), v_h(:, e), grid(id)%bt, zloc(e), level_below(e), lev0, fail) + if (fail) return + if (lev0) then ! height obs below lowest sigma + if (.not. allow_obs_below_vol) then + fail = .true. + return + endif + endif + enddo + case(VERTISSURFACE) + zloc(:) = 1.0_r8 + ! HK todo call check to see if the station height is too far away from the model surface height + case(VERTISUNDEF) + zloc = 0.0_r8 + case default + fail = .true. +end select + +end subroutine get_level_below_obs + +!------------------------------------------------------------------ +! Calculate the model pressure profile on half (mass) levels, +! horizontally interpolated at the observation location. +subroutine get_model_pressure_profile(id, ll, ul, lr, ur, dx, dy, dxm, dym, & + ens_size, state_handle, v_p) + +integer, intent(in) :: id +integer, intent(in) :: ll(2), ul(2), lr(2), ur(2) ! (x,y) mass grid corners +real(r8), intent(in) :: dx, dy, dxm, dym +integer, intent(in) :: ens_size +type(ensemble_type), intent(in) :: state_handle +real(r8), intent(out) :: v_p(0:grid(id)%bt, ens_size) + +integer(i8) :: ill, ilr, iul, iur +real(r8), dimension(ens_size) :: x_ill, x_ilr, x_iul, x_iur +real(r8), dimension(ens_size) :: pres1, pres2, pres3, pres4 +real(r8), dimension(ens_size) :: lev2_pres1, lev2_pres2, lev2_pres3, lev2_pres4 + +integer :: var_id, levk +integer :: k(ens_size) + +do levk=1, grid(id)%bt ! number of mass levels + + k(:) = levk + pres1 = model_pressure_t(ll(1), ll(2), k, id, state_handle, ens_size) + pres2 = model_pressure_t(lr(1), lr(2), k, id, state_handle, ens_size) + pres3 = model_pressure_t(ul(1), ul(2), k, id, state_handle, ens_size) + pres4 = model_pressure_t(ur(1), ur(2), k, id, state_handle, ens_size) + + v_p(levk, :) = interp_4pressure(pres1, pres2, pres3, pres4, dx, dxm, dy, dym, ens_size) + + if (levk == 2) then ! store result for extrapolation + lev2_pres1(:) = pres1(:) + lev2_pres2(:) = pres2(:) + lev2_pres3(:) = pres3(:) + lev2_pres4(:) = pres4(:) + endif + +enddo + +var_id = get_varid_from_kind(wrf_dom(id), QTY_SURFACE_PRESSURE) + +if (var_id > 0) then ! surface pressure in domain so get v_p(0,:) from surface pressure + + ill = get_dart_vector_index(ll(1), ll(2), 1, wrf_dom(id), var_id) + ilr = get_dart_vector_index(lr(1), lr(2), 1, wrf_dom(id), var_id) + iul = get_dart_vector_index(ul(1), ul(2), 1, wrf_dom(id), var_id) + iur = get_dart_vector_index(ur(1), ur(2), 1, wrf_dom(id), var_id) + + x_ill = get_state(ill, state_handle) + x_ilr = get_state(ilr, state_handle) + x_iul = get_state(iul, state_handle) + x_iur = get_state(iur, state_handle) + + v_p(0,:) = interp_4pressure(x_ill, x_ilr, x_iul, x_iur, dx, dxm, dy, dym, ens_size) + +!!! Old code: has a check for 0.0 surface pressure +!!! https://github.com/NCAR/DART/blob/9729d784226295a197ca3bf00c917e4aaab5003b/models/wrf/model_mod.f90#L4600-L4606 + +else !extrapolate v_p(0:,) from pressure level 2 and v_p(1:,:) + + v_p(0,:) = extrap_4pressure(lev2_pres1(:), lev2_pres2(:), lev2_pres3(:), lev2_pres4(:), dx, dxm, dy, dym, ens_size, & + edgep=v_p(1,:)) + +endif + +end subroutine get_model_pressure_profile + +!------------------------------------------------------------------ +! returns pressure at a point on the mass grid +function model_pressure_t(i,j,k,id,state_handle, ens_size) + +integer, intent(in) :: ens_size +integer, intent(in) :: i,j,k(ens_size),id +type(ensemble_type), intent(in) :: state_handle +real(r8) :: model_pressure_t(ens_size) + +integer(i8), dimension(ens_size) :: iqv, it +real(r8), dimension(ens_size) :: qvf1, rho, x_iqv, x_it + +integer :: var_idv, var_idt, e + +! Adapted the code from WRF module_big_step_utilities_em.F ---- +! subroutine calc_p_rho_phi Y.-R. Guo (10/20/2004) + +! Simplification: alb*mub = (phb(i,j,k+1) - phb(i,j,k))/dnw(k) + +var_idv = get_varid_from_kind(wrf_dom(id), QTY_VAPOR_MIXING_RATIO) +var_idt = get_varid_from_kind(wrf_dom(id), QTY_POTENTIAL_TEMPERATURE) ! HK original code type_t is this always QTY_POTENTIAL_TEMPERATURE + +if (var_idv < 0 .or. var_idt < 0 ) then + call error_handler(E_ERR, 'model_pressure_t:', 'BOTH QVAPOR and T must be in state vector to compute total pressure', source) +endif + + +do e = 1, ens_size + iqv = get_dart_vector_index(i,j,k(e), wrf_dom(id), var_idv) + it = get_dart_vector_index(i,j,k(e), wrf_dom(id), var_idt) +enddo + +call get_state_array(x_iqv, iqv, state_handle) +call get_state_array(x_it, it, state_handle) + +qvf1(:) = 1.0_r8 + x_iqv(:) / rd_over_rv + +rho(:) = model_rho_t(i,j,k,id,state_handle, ens_size) + +model_pressure_t(:) = ps0 * ( (gas_constant*(ts0+x_it)*qvf1) / & + (ps0/rho(:)) )**cpovcv + +end function model_pressure_t + +!------------------------------------------------------------------ +! returns surface pressure at a point on the mass grid +function model_pressure_s(i, j, id, state_handle, ens_size) + +integer, intent(in) :: i,j,id +type(ensemble_type), intent(in) :: state_handle +integer, intent(in) :: ens_size + +real(r8) :: model_pressure_s(ens_size) + +integer(i8) :: ips, imu +integer :: var_id_psfc, var_id_mu +real(r8) :: x_imu(ens_size), x_ips(ens_size) + +var_id_psfc = get_varid_from_varname(wrf_dom(id), 'PSFC') +var_id_mu = get_varid_from_varname(wrf_dom(id), 'MU') + +if ( var_id_PSFC > 0 ) then + ips = get_dart_vector_index(i,j,1, wrf_dom(id), var_id_psfc) + x_ips = get_state(ips, state_handle) + model_pressure_s = x_ips + +elseif (var_id_mu > 0) then + imu = get_dart_vector_index(i,j,1, wrf_dom(id), var_id_mu) + x_imu = get_state(imu, state_handle) + model_pressure_s = stat_dat(id)%p_top + stat_dat(id)%mub(i,j) + x_imu + +else + call error_handler(E_ERR, 'model_pressure_s:', & + 'One of MU (QTY_PRESSURE) or PSFC (QTY_SURFACE_PRESSURE) must be in state vector to compute surface pressure', & + source) +endif + +end function model_pressure_s + + +!------------------------------------------------------------------ +! Calculate the model level "zk" on half (mass) levels, +! corresponding to pressure "pres" +subroutine pres_to_zk(pres, mdl_v, n3, zk, level_below, lev0, fail) + +real(r8), intent(in) :: pres +real(r8), intent(in) :: mdl_v(0:n3) +integer, intent(in) :: n3 +real(r8), intent(out) :: zk +integer, intent(out) :: level_below +logical, intent(out) :: lev0 +logical, intent(out) :: fail + +integer :: k + +lev0 = .false. +zk = MISSING_R8 +fail = .false. + +! if out of range completely, return missing_r8 and lev0 false +if (pres > mdl_v(0) .or. pres < mdl_v(n3)) then + fail = .true. + return +endif + +! if above surface but below lowest sigma level, return the +! sigma value but set lev0 true +if(pres <= mdl_v(0) .and. pres > mdl_v(1)) then + lev0 = .true. + level_below = 1 + if (log_vert_interp) then + zk = (log(mdl_v(0)) - log(pres))/(log(mdl_v(0)) - log(mdl_v(1))) + else + zk = (mdl_v(0) - pres)/(mdl_v(0) - mdl_v(1)) + endif + return + endif + +! find the 2 sigma levels the value is between and return that +! as a real number, including the fraction between the levels. +do k = 1,n3-1 + if(pres <= mdl_v(k) .and. pres >= mdl_v(k+1)) then + level_below = k + if (log_vert_interp) then + zk = real(k) + (log(mdl_v(k)) - log(pres))/(log(mdl_v(k)) - log(mdl_v(k+1))) + else + zk = real(k) + (mdl_v(k) - pres)/(mdl_v(k) - mdl_v(k+1)) + endif + exit + endif +enddo + +end subroutine pres_to_zk + +!------------------------------------------------------------------ +! Calculate the model height profile on half (mass) levels, +! horizontally interpolated at the observation location. +subroutine get_model_height_profile(ll, ul, lr, ur, dx, dy, dxm, dym, id, v_h, state_handle, ens_size) + +integer, dimension(2), intent(in) :: ll, ul, lr, ur ! (x,y) mass grid corners +integer, intent(in) :: id +real(r8), intent(in) :: dx,dy,dxm,dym +integer, intent(in) :: ens_size +real(r8), intent(out) :: v_h(0:grid(id)%bt, ens_size) +type(ensemble_type), intent(in) :: state_handle +integer e !< for ensemble loop + +real(r8) :: fll(grid(id)%bts, ens_size), geop(ens_size), lat(ens_size) +integer(i8) :: ill, iul, ilr, iur +integer :: k, rc + +real(r8), dimension(ens_size) :: x_ill, x_ilr, x_iul, x_iur +integer :: var_id + +var_id = get_varid_from_kind(wrf_dom(id), QTY_GEOPOTENTIAL_HEIGHT) + +do k = 1, grid(id)%bts ! geopotential height (PH) is on bottom_top_stag + + ill = get_dart_vector_index(ll(1), ll(2), k, wrf_dom(id), var_id) + iul = get_dart_vector_index(ul(1), ul(2), k, wrf_dom(id), var_id) + ilr = get_dart_vector_index(lr(1), lr(2), k, wrf_dom(id), var_id) + iur = get_dart_vector_index(ur(1), ur(2), k, wrf_dom(id), var_id) + + x_ill = get_state(ill, state_handle) + x_ilr = get_state(ilr, state_handle) + x_iul = get_state(iul, state_handle) + x_iur = get_state(iur, state_handle) + + geop(:) = ( dym*( dxm*( stat_dat(id)%phb(ll(1),ll(2),k) + x_ill ) + & + dx*( stat_dat(id)%phb(lr(1),lr(2),k) + x_ilr ) ) + & + dy*( dxm*( stat_dat(id)%phb(ul(1),ul(2),k) + x_iul ) + & + dx*( stat_dat(id)%phb(ur(1),ur(2),k) + x_iur ) ) )/gravity + + lat(:) = ( grid(id)%latitude(ll(1),ll(2)) + & + grid(id)%latitude(lr(1),lr(2)) + & + grid(id)%latitude(ul(1),ul(2)) + & + grid(id)%latitude(ur(1),ur(2)) ) / 4.0_r8 + + do e = 1, ens_size + fll(k, e) = compute_geometric_height(geop(e), lat(e)) + enddo +end do + +do k = 1, grid(id)%bt + v_h(k, :) = 0.5_r8*(fll(k, :) + fll(k+1, :) ) +end do + +v_h(0, :) = dym*( dxm*stat_dat(id)%hgt(ll(1), ll(2)) + & + dx*stat_dat(id)%hgt(lr(1), lr(2)) ) + & + dy*( dxm*stat_dat(id)%hgt(ul(1), ul(2)) + & + dx*stat_dat(id)%hgt(ur(1), ur(2)) ) + + + +end subroutine get_model_height_profile + +!------------------------------------------------------------------ +! Calculate the model level zk on half (mass) levels, +! corresponding to height obs_v. +subroutine height_to_zk(obs_v, mdl_v, n3, zk, level_below, lev0, fail) + +real(r8), intent(in) :: obs_v +integer, intent(in) :: n3 +real(r8), intent(in) :: mdl_v(0:n3) +real(r8), intent(out) :: zk +integer, intent(out) :: level_below +logical, intent(out) :: lev0 +logical, intent(out) :: fail + +integer :: k + +zk = MISSING_R8 +lev0 = .false. +fail = .false. + +! if out of range completely, return missing_r8 and lev0 false +if (obs_v < mdl_v(0) .or. obs_v > mdl_v(n3)) then + fail = .true. + return +endif + +! if above surface but below lowest 3-d height level, return the +! height value but set lev0 true +if(obs_v >= mdl_v(0) .and. obs_v < mdl_v(1)) then + lev0 = .true. + level_below = 1 + zk = (mdl_v(0) - obs_v)/(mdl_v(0) - mdl_v(1)) + return +endif + +! find the 2 height levels the value is between and return that +! as a real number, including the fraction between the levels. +do k = 1,n3-1 + if(obs_v >= mdl_v(k) .and. obs_v <= mdl_v(k+1)) then + level_below = k + zk = real(k) + (mdl_v(k) - obs_v)/(mdl_v(k) - mdl_v(k+1)) + exit + endif +enddo + +end subroutine height_to_zk + +!------------------------------------------------------------------ +! Interpolate pressure inside quad +! Four corners of a quad: +! p1 lower left +! p2 lower right +! p3 upper left +! p4 upper right +! dx is the distance in x, dxm is 1.0-dx +! dy is distance in y, dym is 1.0-dy +function interp_4pressure(p1, p2, p3, p4, dx, dxm, dy, dym, ens_size) + +integer, intent(in) :: ens_size +real(r8), intent(in) :: p1(ens_size), p2(ens_size), p3(ens_size), p4(ens_size) +real(r8), intent(in) :: dx, dxm, dy, dym +real(r8) :: interp_4pressure(ens_size) + +real(r8) :: l1(ens_size), l2(ens_size), l3(ens_size), l4(ens_size) + +if (log_horz_interpQ) then + l1 = log(p1) + l2 = log(p2) + l3 = log(p3) + l4 = log(p4) +endif + +! once we like the results, remove the log_horz_interpQ test. +if (log_horz_interpQ) then + interp_4pressure = exp(dym*( dxm*l1 + dx*l2 ) + dy*( dxm*l3 + dx*l4 )) +else + interp_4pressure = dym*( dxm*p1 + dx*p2 ) + dy*( dxm*p3 + dx*p4 ) +endif + +end function interp_4pressure + +!------------------------------------------------------------------ +! extrapolate quad where edgep is the edge pressure +function extrap_4pressure(p1, p2, p3, p4, dx, dxm, dy, dym, ens_size, edgep) + +integer, intent(in) :: ens_size +real(r8), intent(in) :: p1(ens_size), p2(ens_size), p3(ens_size), p4(ens_size) +real(r8), intent(in) :: dx, dxm, dy, dym +real(r8), intent(in) :: edgep(ens_size) +real(r8) :: extrap_4pressure(ens_size) + +real(r8) :: intermediate(ens_size) +real(r8) :: l1(ens_size), l2(ens_size), l3(ens_size), l4(ens_size) + +if (log_horz_interpQ) then + l1 = log(p1) + l2 = log(p2) + l3 = log(p3) + l4 = log(p4) +endif + +! once we like the results, remove the log_horz_interpQ test. +if (log_horz_interpQ) then + intermediate = (3.0_r8*log(edgep) - & + dym*( dxm*l1 + dx*l2 ) - dy*( dxm*l3 + dx*l4 ))/2.0_r8 + + where (intermediate <= 0.0_r8) + extrap_4pressure = edgep + else where + extrap_4pressure = exp(intermediate) + end where +else + extrap_4pressure = (3.0_r8*edgep - & + dym*( dxm*p1 + dx*p2 ) - dy*( dxm*p3 + dx*p4 ))/2.0_r8 +endif + +end function extrap_4pressure + +!------------------------------------------------------------------ +! Calculate the total density on mass point (half (mass) levels, T-point). +function model_rho_t(i,j,k,id,state_handle, ens_size) + +integer, intent(in) :: ens_size +integer, intent(in) :: i,j,k(ens_size),id +type(ensemble_type), intent(in) :: state_handle +real(r8) :: model_rho_t(ens_size) + +integer(i8), dimension(ens_size) :: imu,iph,iphp1 +real(r8), dimension(ens_size) :: ph_e, x_imu, x_iph, x_iphp1 +integer :: var_id_mu, var_id_ph, e + +! Adapted the code from WRF module_big_step_utilities_em.F ---- +! subroutine calc_p_rho_phi Y.-R. Guo (10/20/2004) + +! Simplification: alb*mub = (phb(i,j,k+1) - phb(i,j,k))/dnw(k) + +var_id_mu = get_varid_from_varname(wrf_dom(id), 'MU') +var_id_ph = get_varid_from_kind(wrf_dom(id), QTY_GEOPOTENTIAL_HEIGHT) + +if (var_id_mu < 0 .or. var_id_ph < 0) then + call error_handler(E_ERR, 'model_rho_t:', 'BOTH MU and PH must be in state vector to compute total density', source) +endif + +do e = 1, ens_size + imu = get_dart_vector_index(i,j,1, wrf_dom(id), var_id_mu) + iph = get_dart_vector_index(i,j,k(e), wrf_dom(id), var_id_ph) + iphp1 = get_dart_vector_index(i,j,k(e)+1, wrf_dom(id), var_id_ph) +enddo + +call get_state_array(x_imu, imu, state_handle) +call get_state_array(x_iph, iph, state_handle) +call get_state_array(x_iphp1, iphp1, state_handle) + +do e = 1, ens_size + ph_e(e) = ( (x_iphp1(e) + stat_dat(id)%phb(i,j,k(e)+1)) & + - (x_iph(e) + stat_dat(id)%phb(i,j,k(e) )) ) / stat_dat(id)%dnw(k(e)) +enddo + +! rho = - mu / dphi/deta +model_rho_t(:) = - (stat_dat(id)%mub(i,j)+x_imu) / ph_e + +end function model_rho_t + + +!------------------------------------------------------------------ +function density_interpolate(ens_size, state_handle, id, ll, ul, lr, ur, k, dxm, dx, dy, dym) + +integer, intent(in) :: ens_size +type(ensemble_type), intent(in) :: state_handle +integer, intent(in) :: id +integer, intent(in) :: ll(2), ul(2), lr(2), ur(2) ! (x,y) at four corners +integer, intent(in) :: k(ens_size) ! k may be different across the ensemble +real(r8), intent(in) :: dxm, dx, dy, dym +real(r8) :: density_interpolate(ens_size) + +real(r8), dimension(ens_size) :: rho1, rho2, rho3, rho4 + +rho1 = model_rho_t(ll(1), ll(2), k, id, state_handle, ens_size) +rho2 = model_rho_t(lr(1), lr(2), k, id, state_handle, ens_size) +rho3 = model_rho_t(ul(1), ul(2), k, id, state_handle, ens_size) +rho4 = model_rho_t(ur(1), ur(2), k, id, state_handle, ens_size) + +density_interpolate = dym*( dxm*rho1(:) + dx*rho2(:) ) + dy*( dxm*rho3(:) + dx*rho4(:) ) + +end function density_interpolate + +!------------------------------------------------------------------ +! wrfinput land mask XLAND 1 = land, 2 = water +! obs_def_rttov_mod 0 = land, 1 = water, 2 = sea ice +function surface_type_interpolate(ens_size, id, ll, ul, lr, ur, dxm, dx, dy, dym) + +integer, intent(in) :: ens_size +integer, intent(in) :: id +integer, intent(in) :: ll(2), ul(2), lr(2), ur(2) ! (x,y) at four corners +real(r8), intent(in) :: dxm, dx, dy, dym +real(r8) :: surface_type_interpolate(ens_size) ! same across the ensemble + +surface_type_interpolate(:) = -1 + dym*( dxm*stat_dat(id)%land(ll(1), ll(2)) + & + dx*stat_dat(id)%land(lr(1), lr(2)) ) + & + dy*( dxm*stat_dat(id)%land(ul(1), ul(2)) + & + dx*stat_dat(id)%land(ur(1), ur(2)) ) + +end function surface_type_interpolate + +!------------------------------------------------------------------ +function surface_elevation_interpolate(ens_size, id, ll, ul, lr, ur, dxm, dx, dy, dym) + +integer, intent(in) :: ens_size +integer, intent(in) :: id +integer, intent(in) :: ll(2), ul(2), lr(2), ur(2) ! (x,y) at four corners +real(r8), intent(in) :: dxm, dx, dy, dym +real(r8) :: surface_elevation_interpolate(ens_size) + +surface_elevation_interpolate(:) = dym*( dxm*stat_dat(id)%hgt(ll(1), ll(2)) + & + dx*stat_dat(id)%hgt(lr(1), lr(2)) ) + & + dy*( dxm*stat_dat(id)%hgt(ul(1), ul(2)) + & + dx*stat_dat(id)%hgt(ur(1), ur(2)) ) + +end function surface_elevation_interpolate + +!------------------------------------------------------------------ +function geopotential_height_interpolate(ens_size, state_handle, qty, id, ll, ul, lr, ur, k, dxm, dx, dy, dym) + +integer, intent(in) :: ens_size +type(ensemble_type), intent(in) :: state_handle +integer, intent(in) :: qty +integer, intent(in) :: id +integer, intent(in) :: ll(2), ul(2), lr(2), ur(2) ! (x,y) at four corners +integer, intent(in) :: k(ens_size) ! k may be different across the ensemble +real(r8), intent(in) :: dxm, dx, dy, dym +real(r8) :: geopotential_height_interpolate(ens_size) + +real(r8), dimension(ens_size) :: a1 + +a1 = simple_interpolation(ens_size, state_handle, qty, id, ll, ul, lr, ur, k, dxm, dx, dy, dym) + +! phb is constant across the ensemble, so use k(1) +geopotential_height_interpolate = ( a1 + & + dym* ( dxm*stat_dat(id)%phb(ll(1), ll(2), k(1) ) + & + dx * stat_dat(id)%phb(lr(1), lr(2), k(1)) ) + & + dy * ( dxm*stat_dat(id)%phb(ul(1), ul(2), k(1) ) + & + dx * stat_dat(id)%phb(ur(1), ur(2), k(1)) ) ) / gravity + +end function geopotential_height_interpolate + +!------------------------------------------------------------------ +function temperature_interpolate(ens_size, state_handle, id, ll, ul, lr, ur, k, dxm, dx, dy, dym) + +integer, intent(in) :: ens_size +type(ensemble_type), intent(in) :: state_handle +integer, intent(in) :: id +integer, intent(in) :: ll(2), ul(2), lr(2), ur(2) ! (x,y) at four corners +integer, intent(in) :: k(ens_size) ! k may be different across the ensemble +real(r8), intent(in) :: dxm, dx, dy, dym +real(r8) :: temperature_interpolate(ens_size) + +real(r8), dimension(ens_size) :: a1, pres, pres1, pres2, pres3, pres4 + +! In terms of perturbation potential temperature +a1 = simple_interpolation(ens_size, state_handle, QTY_POTENTIAL_TEMPERATURE, id, ll, ul, lr, ur, k, dxm, dx, dy, dym) + +pres1 = model_pressure_t(ll(1), ll(2), k, id, state_handle, ens_size) +pres2 = model_pressure_t(lr(1), lr(2), k, id, state_handle, ens_size) +pres3 = model_pressure_t(ul(1), ul(2), k, id, state_handle, ens_size) +pres4 = model_pressure_t(ur(1), ur(2), k, id, state_handle, ens_size) + +! Pressure at location +pres = dym*( dxm*pres1 + dx*pres2 ) + dy*( dxm*pres3 + dx*pres4 ) + +! Full sensible temperature field +temperature_interpolate = (ts0 + a1(:))*(pres(:)/ps0)**kappa + +end function temperature_interpolate + +!------------------------------------------------------------------ +function pressure_interpolate(ens_size, state_handle, id, ll, ul, lr, ur, k, dxm, dx, dy, dym) + +integer, intent(in) :: ens_size +type(ensemble_type), intent(in) :: state_handle +integer, intent(in) :: id +integer, intent(in) :: ll(2), ul(2), lr(2), ur(2) ! (x,y) at four corners +integer, intent(in) :: k(ens_size) ! k may be different across the ensemble +real(r8), intent(in) :: dxm, dx, dy, dym +real(r8) :: pressure_interpolate(ens_size) + +real(r8), dimension(ens_size) :: pres1, pres2, pres3, pres4 + +pres1 = model_pressure_t(ll(1), ll(2), k, id, state_handle, ens_size) +pres2 = model_pressure_t(lr(1), lr(2), k, id, state_handle, ens_size) +pres3 = model_pressure_t(ul(1), ul(2), k, id, state_handle, ens_size) +pres4 = model_pressure_t(ur(1), ur(2), k, id, state_handle, ens_size) + +! Pressure at location +pressure_interpolate = dym*( dxm*pres1 + dx*pres2 ) + dy*( dxm*pres3 + dx*pres4 ) + +end function pressure_interpolate + +!------------------------------------------------------------------ +function surface_pressure_interpolate(ens_size, state_handle, id, ll, ul, lr, ur, dxm, dx, dy, dym) + +integer, intent(in) :: ens_size +type(ensemble_type), intent(in) :: state_handle +integer, intent(in) :: id +integer, intent(in) :: ll(2), ul(2), lr(2), ur(2) ! (x,y) at four corners +real(r8), intent(in) :: dxm, dx, dy, dym +real(r8) :: surface_pressure_interpolate(ens_size) + +real(r8), dimension(ens_size) :: pres1, pres2, pres3, pres4 +integer :: e + +pres1 = model_pressure_s(ll(1), ll(2), id, state_handle, ens_size) +pres2 = model_pressure_s(lr(1), lr(2), id, state_handle, ens_size) +pres3 = model_pressure_s(ul(1), ul(2), id, state_handle, ens_size) +pres4 = model_pressure_s(ur(1), ur(2), id, state_handle, ens_size) + +! Pressure at location + +do e = 1, ens_size + ! HK todo original code comment: + ! I'm not quite sure where this comes from, but I will trust them on it.... + if ( pres1 (e) /= 0.0_r8 .and. pres2(e) /= 0.0_r8 .and. pres3(e) /= 0.0_r8 .and. & + pres4(e) /= 0.0_r8 ) then + + surface_pressure_interpolate(e) = dym*( dxm*pres1(e) + dx*pres2(e) ) + dy*( dxm*pres3(e) + dx*pres4(e) ) + endif !HK todo initialize to missing_r8? +enddo + + +end function surface_pressure_interpolate + +!------------------------------------------------------------------ +function specific_humidity_interpolate(ens_size, state_handle, qty, id, ll, ul, lr, ur, k, dxm, dx, dy, dym) + +integer, intent(in) :: ens_size +type(ensemble_type), intent(in) :: state_handle +integer, intent(in) :: qty +integer, intent(in) :: id +integer, intent(in) :: ll(2), ul(2), lr(2), ur(2) ! (x,y) at four corners +integer, intent(in) :: k(ens_size) ! k may be different across the ensemble +real(r8), intent(in) :: dxm, dx, dy, dym +real(r8) :: specific_humidity_interpolate(ens_size) + +real(r8), dimension(ens_size) :: a1 + +a1 = simple_interpolation(ens_size, state_handle, qty, id, ll, ul, lr, ur, k, dxm, dx, dy, dym) + +specific_humidity_interpolate = a1(:) /(1.0_r8 + a1(:)) + +end function specific_humidity_interpolate + +!------------------------------------------------------------------ +function wind_interpolate(ens_size, state_handle, qty, id, k, xloc, yloc, i, j, dxm, dx, dy, dym, lon) + +integer, intent(in) :: ens_size +type(ensemble_type), intent(in) :: state_handle +integer, intent(in) :: qty +integer, intent(in) :: id +integer, intent(in) :: k(ens_size) ! k may be different across the ensemble +real(r8), intent(in) :: xloc, yloc ! location on mass grid +integer, intent(in) :: i,j ! ll corners of mass grid +real(r8), intent(in) :: dxm, dx, dy, dym +real(r8), intent(in) :: lon ! Longitude of point in degrees +real(r8) :: wind_interpolate(ens_size) + +real(r8), dimension(ens_size) :: u_wind_grid, v_wind_grid, u_wind, v_wind +real(r8) :: xloc_u, yloc_v ! x ugrid, y vgrid +real(r8) :: dx_u, dxm_u, dy_v, dym_v +integer :: i_u, j_v +integer :: ll(2), ul(2), lr(2), ur(2) ! (x,y) at four corners +integer :: e, rc + +! HK TODO relationship between mass grid and u grid and v grid +! Original code adds 0.5 to xloc, yloc. But what if you are on the edge of a domain? +! https://github.com/NCAR/DART/blob/70e6af803a52d14b9f77f872c94b1fe11d5dc2d9/models/wrf/model_mod.f90#L1425-L1432 + +! xloc and yloc are indices on mass-grid. If we are on a periodic longitude domain, +! then xloc can range from [1 wes). This means that simply adding 0.5 to xloc has +! the potential to render xloc_u out of the valid mass-grid index bounds (>wes). +xloc_u = xloc + 0.5_r8 +yloc_v = yloc + 0.5_r8 + +! HK TODO what about periodic_y? +if ( grid(id)%periodic_x .and. xloc_u > real(grid(id)%wes,r8) ) xloc_u = xloc_u - real(grid(id)%we,r8) + +call toGrid(xloc_u,i_u,dx_u,dxm_u) +call toGrid(yloc_v,j_v,dy_v,dym_v) + +call getCorners(i_u, j, id, QTY_U_WIND_COMPONENT, ll, ul, lr, ur, rc) +u_wind_grid = simple_interpolation(ens_size, state_handle, QTY_U_WIND_COMPONENT, id, ll, ul, lr, ur, k, dxm_u, dx_u, dy, dym) +call getCorners(i, j_v, id, QTY_V_WIND_COMPONENT, ll, ul, lr, ur, rc) +v_wind_grid = simple_interpolation(ens_size, state_handle, QTY_V_WIND_COMPONENT, id, ll, ul, lr, ur, k, dxm, dx, dy_v, dym_v) + +do e = 1, ens_size + call gridwind_to_truewind(lon, grid(id)%proj, u_wind_grid(e), v_wind_grid(e), u_wind(e), v_wind(e)) +enddo + +if ( qty == QTY_U_WIND_COMPONENT ) then + wind_interpolate = u_wind +else + wind_interpolate = v_wind +endif + +end function wind_interpolate + +!------------------------------------------------------------------ +! If there are other domains in the state: +! wrf domain id =/ state domain id +function get_wrf_domain(state_id) + +integer, intent(in) :: state_id +integer :: get_wrf_domain + +integer :: i + +do i = 1, num_domains + if (wrf_dom(i) == state_id) then + get_wrf_domain = i + return + endif +enddo + +end function get_wrf_domain + +!------------------------------------------------------------------ +subroutine convert_vertical_state(state_handle, num, locs, loc_qtys, loc_indx, & + which_vert, istatus) + +type(ensemble_type), intent(in) :: state_handle +integer, intent(in) :: num +type(location_type), intent(inout) :: locs(num) ! location for each state element +integer, intent(in) :: loc_qtys(num) ! qty for each state element +integer(i8), intent(in) :: loc_indx(num) ! index into the state vector +integer, intent(in) :: which_vert ! vertical coordinate to be converted to +integer, intent(out) :: istatus + +integer :: ip, jp, kp, id ! x,y,z of state index +integer :: var_id, state_id +real(r8) :: vert ! vertical after conversion +integer :: i +integer :: vert_coord_in +real(r8) :: lon_lat_vert(3) + +istatus = 0 ! can not fail + +do i = 1, num + + lon_lat_vert = get_location(locs(i)) + vert_coord_in = nint(query_location(locs(i))) + + if (vert_coord_in == which_vert) then ! conversion is already done + cycle + endif + + call get_model_variable_indices(loc_indx(i), ip, jp, kp, var_id=var_id, dom_id=state_id) + id = get_wrf_domain(state_id) + + if (which_vert == VERTISLEVEL) then + + if (on_w_grid(state_id, var_id)) then + vert = real(kp) - 0.5_r8 + else + vert = real(kp) + endif + + elseif (which_vert == VERTISPRESSURE) then + + vert = model_pressure(ip, jp, kp, id, var_id, state_id, state_handle) + + elseif (which_vert == VERTISHEIGHT) then + + vert = model_height(ip, jp, kp, id, loc_qtys(i), var_id, state_id, state_handle) + + elseif (which_vert == VERTISSCALEHEIGHT) then + + vert = -log(model_pressure(ip, jp, kp, id, var_id, state_id, state_handle) / & + model_surface_pressure(ip, jp, id, var_id, state_id, state_handle)) + + endif + + call set_vertical(locs(i), vert, which_vert) + +enddo + + + +end subroutine convert_vertical_state + +!------------------------------------------------------------------ +subroutine convert_vertical_obs(state_handle, num, locs, loc_qtys, loc_types, & + which_vert, istatus) + +type(ensemble_type), intent(in) :: state_handle +integer, intent(in) :: num +type(location_type), intent(inout) :: locs(num) +integer, intent(in) :: loc_qtys(num) +integer, intent(in) :: loc_types(num) +integer, intent(in) :: which_vert +integer, intent(out) :: istatus(num) + +integer :: vert_coord_in, id +real(r8) :: lon_lat_vert(3) +integer :: ll(2), ul(2), lr(2), ur(2) !(x,y) of four corners +integer :: i, j ! grid +real(r8) :: xloc, yloc ! WRF i,j in the grid +real(r8) :: dx, dxm, dy, dym ! grid fractions +real(r8) :: zout(1), zk(1), zk1(1), geop(1), zloc(1) +real(r8) :: pres1(1), pres2(1), pres3(1), pres4(1) +integer :: ens_size, rc, k(1) +logical :: fail +integer :: ob ! loop variable +real(r8) :: dz, dzm ! dummys for toGrid call + +integer, parameter :: FAILED_BOUNDS_CHECK = 144 +integer, parameter :: CANNOT_INTERPOLATE_QTY = 155 +integer, parameter :: NOT_IN_ANY_DOMAIN = 111 +integer, parameter :: VERTICAL_LOCATION_FAIL = 166 + +ens_size = 1 ! working with the mean state + +do ob = 1, num + + lon_lat_vert = get_location(locs(ob)) + vert_coord_in = nint(query_location(locs(ob))) + + if (vert_coord_in == which_vert) then ! conversion is already done + istatus(ob) = 0 + cycle + endif + + if (lon_lat_vert(3) == VERTISUNDEF) then ! no vertical, no conversion + istatus(ob) = 0 + cycle + endif + + if (lon_lat_vert(3) == MISSING_R8) then ! vertical is missing, no conversion + call set_vertical(locs(ob), MISSING_R8, which_vert) + istatus(ob) = 1 ! HK todo original code does not set success for this + cycle + endif + + ! convert to which_vert + call get_domain_info(lon_lat_vert(1),lon_lat_vert(2),id,xloc,yloc) + if (id == 0) then + call set_vertical(locs(ob), MISSING_R8, which_vert) !HK original code - why set to missing? + istatus(ob) = NOT_IN_ANY_DOMAIN + cycle + endif + + ! horizontal location mass point + call toGrid(xloc,i,dx,dxm) + call toGrid(yloc,j,dy,dym) + + if ( .not. within_bounds_horizontal(i, j, id, QTY_POTENTIAL_TEMPERATURE) ) then ! HK origianal code mass grid qty + call set_vertical(locs(ob), MISSING_R8, which_vert) + istatus(ob) = FAILED_BOUNDS_CHECK + cycle + endif + + call getCorners(i, j, id, QTY_POTENTIAL_TEMPERATURE, ll, ul, lr, ur, rc) !HK todo what qty to pick? + + ! vertical location mass level + call get_level_below_obs(vert_coord_in, id, lon_lat_vert, ens_size, state_handle, ll, ul, lr, ur, dx, dy, dxm, dym, k, zloc, fail) + if (fail) then + istatus(ob) = VERTICAL_LOCATION_FAIL + ! set vertical? + cycle + endif + + select case (which_vert) + + case (VERTISLEVEL) + + if (vert_coord_in == VERTISSURFACE) then + zout = 1.0_r8 + else + zout = zloc + endif + + case (VERTISPRESSURE) + + if (vert_coord_in == VERTISSURFACE) then + ! compute surface pressure at all neighboring mass points + pres1 = model_pressure_s(ll(1), ll(2), id, state_handle, ens_size) + pres2 = model_pressure_s(lr(1), lr(2), id, state_handle, ens_size) + pres3 = model_pressure_s(ul(1), ul(2), id, state_handle, ens_size) + pres4 = model_pressure_s(ur(1), ur(2), id, state_handle, ens_size) + zout = dym*( dxm*pres1(1) + dx*pres2(1) ) + dy*( dxm*pres3(1) + dx*pres4(1) ) + + else + zk = pressure_interpolate(ens_size, state_handle, id, ll, ul, lr, ur, k, dxm, dx, dy, dym) + zk1 = pressure_interpolate(ens_size, state_handle, id, ll, ul, lr, ur, k+1, dxm, dx, dy, dym) + zout = vertical_interpolation(ens_size, zloc, zk, zk1) + endif + + case (VERTISHEIGHT) + + if (vert_coord_in == VERTISSURFACE) then + ! a surface ob is assumed to have height as vertical coordinate. + ! this code needs to be revised if this is not true + ! (in that case uncomment lines below to get terrain height + ! from model) + zout = lon_lat_vert(3) + !! or: directly interpolate terrain height at neighboring mass points + !zout = dym*( dxm*stat_dat(id)%hgt(i, j) + & + ! dx*stat_dat(id)%hgt(i+1,j) ) + & + ! dy*( dxm*stat_dat(id)%hgt(i, j+1) + & + ! dx*stat_dat(id)%hgt(i+1,j+1) ) + else + ! adding 0.5 to get to the staggered vertical grid for height + zloc = zloc + 0.5_r8 ! Adjust zloc for staggered + call toGrid(zloc(1),k(1),dz,dzm) + + ! This method does not give bitwise with main + !zk = geopotential_height_interpolate(ens_size, state_handle, QTY_GEOPOTENTIAL_HEIGHT, id, ll, ul, lr, ur, k, dxm, dx, dy, dym) + !zk1 = geopotential_height_interpolate(ens_size, state_handle, QTY_GEOPOTENTIAL_HEIGHT, id, ll, ul, lr, ur, k+1, dxm, dx, dy, dym) + !geop = vertical_interpolation(ens_size, zloc, zk, zk1) + !zout = compute_geometric_height(geop(1), lon_lat_vert(2)) + + zk = interpolate_geometric_height(ens_size, state_handle, id, ll, ul, lr, ur, k, dxm, dx, dy, dym) + zk1 = interpolate_geometric_height(ens_size, state_handle, id, ll, ul, lr, ur, k+1, dxm, dx, dy, dym) + zout = vertical_interpolation(ens_size, zloc, zk, zk1) + endif + + case (VERTISSCALEHEIGHT) + if (vert_coord_in == VERTISSURFACE) then + zout = -log(1.0_r8) + elseif (vert_coord_in == VERTISPRESSURE) then + ! surface pressure + pres1 = model_pressure_s(ll(1), ll(2), id, state_handle, ens_size) + pres2 = model_pressure_s(lr(1), lr(2), id, state_handle, ens_size) + pres3 = model_pressure_s(ul(1), ul(2), id, state_handle, ens_size) + pres4 = model_pressure_s(ur(1), ur(2), id, state_handle, ens_size) + + zout = -log(lon_lat_vert(3) / (dym*( dxm*pres1(1) + dx*pres2(1) ) + dy*( dxm*pres3(1) + dx*pres4(1) ))) + + else ! vert_cood_in == VERTISHEIGHT + + zk = pressure_interpolate(ens_size, state_handle, id, ll, ul, lr, ur, k, dxm, dx, dy, dym) + zk1 = pressure_interpolate(ens_size, state_handle, id, ll, ul, lr, ur, k+1, dxm, dx, dy, dym) + zout = vertical_interpolation(ens_size, zloc, zk, zk1) + + ! surface pressure + pres1 = model_pressure_s(ll(1), ll(2), id, state_handle, ens_size) + pres2 = model_pressure_s(lr(1), lr(2), id, state_handle, ens_size) + pres3 = model_pressure_s(ul(1), ul(2), id, state_handle, ens_size) + pres4 = model_pressure_s(ur(1), ur(2), id, state_handle, ens_size) + + zout = -log(zout / (dym*( dxm*pres1(1) + dx*pres2(1) ) + dy*( dxm*pres3(1) + dx*pres4(1) ))) + + endif + + + end select + + !HK original code uses set_location, you could use set_vertical here see #621 + locs(ob) = set_location(lon_lat_vert(1), lon_lat_vert(2), zout(1), which_vert) + istatus(ob) = 0 + +enddo + +end subroutine convert_vertical_obs + + +!------------------------------------------------------------------ +function interpolate_geometric_height(ens_size, state_handle, id, ll, ul, lr, ur, k, dxm, dx, dy, dym) + +integer, intent(in) :: ens_size +type(ensemble_type), intent(in) :: state_handle +integer, intent(in) :: id +integer, intent(in) :: ll(2), ul(2), lr(2), ur(2) ! (x,y) at four corners +integer, intent(in) :: k(ens_size) ! k may be different across the ensemble +real(r8), intent(in) :: dxm, dx, dy, dym +real(r8) :: interpolate_geometric_height(ens_size) + +integer :: e ! loop variable +! lower left, upper left, lower right, upper right +integer(i8), dimension(ens_size) :: ill, iul, ilr, iur ! dart index at four corners +real(r8), dimension(ens_size) :: x_ill, x_iul, x_ilr, x_iur ! state value at four corners +real(r8), dimension(ens_size) :: geop_ll, geop_ul, geop_lr, geop_ur ! geopotential height at four corners +real(r8), dimension(ens_size) :: geomet_ll, geomet_ul, geomet_lr, geomet_ur ! geometric height at four corners +integer :: var_id + +var_id = get_varid_from_kind(wrf_dom(id), QTY_GEOPOTENTIAL_HEIGHT) + +do e = 1, ens_size + ! x, y, z, domain, variable + ill(e) = get_dart_vector_index(ll(1), ll(2), k(e), wrf_dom(id), var_id) + iul(e) = get_dart_vector_index(ul(1), ul(2), k(e), wrf_dom(id), var_id) + ilr(e) = get_dart_vector_index(lr(1), lr(2), k(e), wrf_dom(id), var_id) + iur(e) = get_dart_vector_index(ur(1), ur(2), k(e), wrf_dom(id), var_id) + +enddo + +call get_state_array(x_ill, ill, state_handle) +call get_state_array(x_iul, iul, state_handle) +call get_state_array(x_ilr, ilr, state_handle) +call get_state_array(x_iur, iur, state_handle) + +do e = 1, ens_size + geop_ll(e) = (stat_dat(id)%phb(ll(1), ll(2), k(e)) + x_ill(e)) / gravity + geop_ul(e) = (stat_dat(id)%phb(ul(1), ul(2), k(e)) + x_iul(e)) / gravity + geop_lr(e) = (stat_dat(id)%phb(lr(1), lr(2), k(e)) + x_ilr(e)) / gravity + geop_ur(e) = (stat_dat(id)%phb(ur(1), ur(2), k(e)) + x_iur(e)) / gravity + + geomet_ll(e) = compute_geometric_height(geop_ll(e), grid(id)%latitude(ll(1), ll(2))) + geomet_ul(e) = compute_geometric_height(geop_ul(e), grid(id)%latitude(ul(1), ul(2))) + geomet_lr(e) = compute_geometric_height(geop_lr(e), grid(id)%latitude(lr(1), lr(2))) + geomet_ur(e) = compute_geometric_height(geop_ur(e), grid(id)%latitude(ur(1), ur(2))) + +enddo + +interpolate_geometric_height = dym*( dxm*geomet_ll(:) + dx*geomet_lr(:) ) + dy*( dxm*geomet_ul(:) + dx*geomet_ur(:) ) + +end function interpolate_geometric_height + +!------------------------------------------------------------------ +! model height any grid u,v,w,t +! used in convert_vertical_state so ens_size = 1 +function model_height(i, j, k, id, qty, var_id, state_id, state_handle) + +type(ensemble_type), intent(in) :: state_handle +integer, intent(in) :: i, j, k, id, qty +integer, intent(in) :: var_id,state_id +real(r8) :: model_height + +integer(i8) :: i1, i2, i3, i4 +integer :: off +real(r8) :: x_i1(1), x_i2(1), x_i3(1), x_i4(1) +real(r8) :: geop, lat +integer :: gz_id + +gz_id = get_varid_from_kind(state_id, QTY_GEOPOTENTIAL_HEIGHT) + +!HK todo for these special cases would it be better to check by variable name +! instead of QTY? +if( qty == QTY_PRESSURE .or. & ! MU + qty == QTY_SURFACE_PRESSURE .or. & ! PSFC SFC PRESSUR + qty == QTY_SKIN_TEMPERATURE) then ! TSK SURFACE SKIN TEMPERATURE + + model_height = stat_dat(id)%hgt(i,j) + +elseif( qty == QTY_SOIL_TEMPERATURE .or. & ! TSLB SOIL TEMPERATURE + qty == QTY_SOIL_MOISTURE .or. & ! SMOIS SOIL MOISTURE + qty == QTY_SOIL_LIQUID_WATER ) then ! SH2O SOIL LIQUID WATER + + model_height = stat_dat(id)%hgt(i,j) - stat_dat(id)%zs(k) + +elseif( qty == QTY_10M_U_WIND_COMPONENT .or. & + qty == QTY_10M_V_WIND_COMPONENT ) then + + model_height = stat_dat(id)%hgt(i,j) + 10.0_r8 + +elseif( qty == QTY_2M_TEMPERATURE .or. & + qty == QTY_2M_POTENTIAL_TEMPERATURE .or. & ! TH2 POT TEMP at 2 M + qty == QTY_2M_SPECIFIC_HUMIDITY ) then ! Q2 QV at 2 M + + model_height = stat_dat(id)%hgt(i,j) + 2.0_r8 + +! If W-grid (on ZNW levels), native to GZ +elseif( on_w_grid(state_id, var_id) ) then + + i1 = get_dart_vector_index(i,j,k, state_id, gz_id) + x_i1 = get_state(i1, state_handle) + + geop = minval(stat_dat(id)%phb(i,j,k)+x_i1)/gravity + model_height = compute_geometric_height(geop, grid(id)%latitude(i, j)) + +! If U-grid, then height is defined between U points, both in horizontal +! and in vertical, so average -- averaging depends on longitude periodicity +elseif( on_u_grid(state_id, var_id) ) then + + if( i == grid(id)%wes ) then + + ! Check to see if periodic in longitude + if ( grid(id)%periodic_x ) then + + ! We are at the seam in longitude, so take first and last mass points + i1 = get_dart_vector_index(i-1,j,k , state_id, gz_id) + i2 = get_dart_vector_index(i-1,j,k+1, state_id, gz_id) + i3 = get_dart_vector_index(1, j,k , state_id, gz_id) + i4 = get_dart_vector_index(1, j,k+1, state_id, gz_id) + + x_i1 = get_state(i1, state_handle) + x_i2 = get_state(i2, state_handle) + x_i3 = get_state(i3, state_handle) + x_i4 = get_state(i4, state_handle) + +! HK todo what is minval for? Is it just for converting an array to a scalar? + geop = minval(( (stat_dat(id)%phb(i-1,j,k ) + x_i1) & + +(stat_dat(id)%phb(i-1,j,k+1) + x_i2) & + +(stat_dat(id)%phb(1 ,j,k ) + x_i3) & + +(stat_dat(id)%phb(1 ,j,k+1) + x_i4) )/(4.0_r8*gravity)) + + lat = ( grid(id)%latitude(i-1,j) & + +grid(id)%latitude(i-1,j) & + +grid(id)%latitude(1 ,j) & + +grid(id)%latitude(1 ,j) ) / 4.0_r8 + + model_height = compute_geometric_height(geop, lat) + + else + + ! If not periodic, then try extrapolating + i1 = get_dart_vector_index(i-1,j,k , state_id, gz_id) + i2 = get_dart_vector_index(i-1,j,k+1, state_id, gz_id) + + x_i1 = get_state(i1, state_handle) + x_i2 = get_state(i2, state_handle) + x_i3 = get_state(i1 -1, state_handle) + x_i4 = get_state(i2 -1, state_handle) + + + geop = minval(( 3.0_r8*(stat_dat(id)%phb(i-1,j,k )+x_i1) & + +3.0_r8*(stat_dat(id)%phb(i-1,j,k+1)+x_i2) & + -(stat_dat(id)%phb(i-2,j,k )+x_i3) & + -(stat_dat(id)%phb(i-2,j,k+1)+x_i4) )/(4.0_r8*gravity)) + + lat = ( 3.0_r8*grid(id)%latitude(i-1,j) & + +3.0_r8*grid(id)%latitude(i-1,j) & + -grid(id)%latitude(i-2,j) & + -grid(id)%latitude(i-2,j)) / 4.0_r8 + + model_height = compute_geometric_height(geop, lat) + + endif + + elseif( i == 1 ) then + + ! Check to see if periodic in longitude + if ( grid(id)%periodic_x ) then + + ! We are at the seam in longitude, so take first and last mass points + off = grid(id)%we + i1 = get_dart_vector_index(i ,j,k ,state_id, gz_id) + i2 = get_dart_vector_index(i ,j,k+1,state_id, gz_id) + i3 = get_dart_vector_index(off,j,k ,state_id, gz_id) + i4 = get_dart_vector_index(off,j,k+1,state_id, gz_id) + + x_i1 = get_state(i1, state_handle) + x_i2 = get_state(i2, state_handle) + x_i3 = get_state(i3, state_handle) + x_i4 = get_state(i4, state_handle) + + geop = minval(( (stat_dat(id)%phb(i ,j,k ) + x_i1) & + +(stat_dat(id)%phb(i ,j,k+1) + x_i2) & + +(stat_dat(id)%phb(off,j,k ) + x_i3) & + +(stat_dat(id)%phb(off,j,k+1) + x_i4) )/(4.0_r8*gravity)) + + lat = ( grid(id)%latitude(i ,j) & + +grid(id)%latitude(i ,j) & + +grid(id)%latitude(off,j) & + +grid(id)%latitude(off,j)) / 4.0_r8 + + model_height = compute_geometric_height(geop, lat) + + else + + ! If not periodic, then try extrapolating + i1 = get_dart_vector_index(i,j,k ,state_id, gz_id) + i2 = get_dart_vector_index(i,j,k+1,state_id, gz_id) + + x_i1 = get_state(i1, state_handle) + x_i2 = get_state(i2, state_handle) + x_i3 = get_state(i1 +1, state_handle) + x_i4 = get_state(i2 +1, state_handle) + + + geop = minval(( 3.0_r8*(stat_dat(id)%phb(i ,j,k )+x_i1) & + +3.0_r8*(stat_dat(id)%phb(i ,j,k+1)+x_i2) & + -(stat_dat(id)%phb(i+1,j,k )+x_i3) & + -(stat_dat(id)%phb(i+1,j,k+1)+x_i4) )/(4.0_r8*gravity)) + + lat = ( 3.0_r8*grid(id)%latitude(i ,j) & + +3.0_r8*grid(id)%latitude(i ,j) & + -grid(id)%latitude(i+1,j) & + -grid(id)%latitude(i+1,j)) / 4.0_r8 + + model_height = compute_geometric_height(geop, lat) + + endif + + else + + i1 = get_dart_vector_index(i,j,k ,state_id, gz_id) + i2 = get_dart_vector_index(i,j,k+1,state_id, gz_id) + + x_i1 = get_state(i1, state_handle) + x_i2 = get_state(i2, state_handle) + x_i3 = get_state(i1 -1, state_handle) + x_i4 = get_state(i2 -1, state_handle) + + + geop = minval(( (stat_dat(id)%phb(i ,j,k )+x_i1) & + +(stat_dat(id)%phb(i ,j,k+1)+x_i2) & + +(stat_dat(id)%phb(i-1,j,k )+x_i3) & + +(stat_dat(id)%phb(i-1,j,k+1)+x_i4) )/(4.0_r8*gravity)) + + lat = ( grid(id)%latitude(i ,j) & + +grid(id)%latitude(i ,j) & + +grid(id)%latitude(i-1,j) & + +grid(id)%latitude(i-1,j)) / 4.0_r8 + + model_height = compute_geometric_height(geop, lat) + + endif + +! If V-grid, then pressure is defined between V points, both in horizontal +! and in vertical, so average -- averaging depends on polar periodicity +elseif( on_v_grid(state_id, var_id) ) then + + if( j == grid(id)%sns ) then + + ! Check to see if periodic in latitude (polar) + if ( grid(id)%polar ) then + + ! The upper corner is 180 degrees of longitude away + off = i + grid(id)%we/2 + if ( off > grid(id)%we ) off = off - grid(id)%we + + i1 = get_dart_vector_index(off,j-1,k , state_id, gz_id) + i2 = get_dart_vector_index(off,j-1,k+1, state_id, gz_id) + i3 = get_dart_vector_index(i ,j-1,k , state_id, gz_id) + i4 = get_dart_vector_index(i ,j-1,k+1, state_id, gz_id) + + x_i1 = get_state(i1, state_handle) + x_i2 = get_state(i2, state_handle) + x_i3 = get_state(i3, state_handle) + x_i4 = get_state(i4, state_handle) + + geop = minval(( (stat_dat(id)%phb(off,j-1,k )+x_i1) & + +(stat_dat(id)%phb(off,j-1,k+1)+x_i2) & + +(stat_dat(id)%phb(i ,j-1,k )+x_i3) & + +(stat_dat(id)%phb(i ,j-1,k+1)+x_i4) )/(4.0_r8*gravity)) + + lat = ( grid(id)%latitude(off,j-1) & + +grid(id)%latitude(off,j-1) & + +grid(id)%latitude(i ,j-1) & + +grid(id)%latitude(i ,j-1)) / 4.0_r8 + + model_height = compute_geometric_height(geop, lat) + + else + + ! If not periodic, then try extrapolating + i1 = get_dart_vector_index(i,j-1,k , state_id, gz_id) + i2 = get_dart_vector_index(i,j-1,k+1, state_id, gz_id) + i3 = get_dart_vector_index(i,j-2,k , state_id, gz_id) + i4 = get_dart_vector_index(i,j-2,k+1, state_id, gz_id) + + x_i1 = get_state(i1, state_handle) + x_i2 = get_state(i2, state_handle) + x_i3 = get_state(i3, state_handle) + x_i4 = get_state(i4, state_handle) + + geop = minval(( 3.0_r8*(stat_dat(id)%phb(i,j-1,k )+x_i1) & + +3.0_r8*(stat_dat(id)%phb(i,j-1,k+1)+x_i2) & + -(stat_dat(id)%phb(i,j-2,k )+x_i3) & + -(stat_dat(id)%phb(i,j-2,k+1)+x_i4) )/(4.0_r8*gravity)) + + lat = ( 3.0_r8*grid(id)%latitude(i,j-1) & + +3.0_r8*grid(id)%latitude(i,j-1) & + -grid(id)%latitude(i,j-2) & + -grid(id)%latitude(i,j-2)) / 4.0_r8 + + model_height = compute_geometric_height(geop, lat) + + endif + + elseif( j == 1 ) then + + ! Check to see if periodic in latitude (polar) + if ( grid(id)%polar ) then + + ! The lower corner is 180 degrees of longitude away + off = i + grid(id)%we/2 + if ( off > grid(id)%we ) off = off - grid(id)%we + + i1 = get_dart_vector_index(off,j,k , state_id, gz_id) + i2 = get_dart_vector_index(off,j,k+1, state_id, gz_id) + i3 = get_dart_vector_index(i ,j,k , state_id, gz_id) + i4 = get_dart_vector_index(i ,j,k+1, state_id, gz_id) + + x_i1 = get_state(i1, state_handle) + x_i2 = get_state(i2, state_handle) + x_i3 = get_state(i3, state_handle) + x_i4 = get_state(i4, state_handle) + + geop = minval(( (stat_dat(id)%phb(off,j,k )+x_i1) & + +(stat_dat(id)%phb(off,j,k+1)+x_i2) & + +(stat_dat(id)%phb(i ,j,k )+x_i3) & + +(stat_dat(id)%phb(i ,j,k+1)+x_i4) )/(4.0_r8*gravity)) + + lat = ( grid(id)%latitude(off,j) & + +grid(id)%latitude(off,j) & + +grid(id)%latitude(i ,j) & + +grid(id)%latitude(i ,j)) / 4.0_r8 + + model_height = compute_geometric_height(geop, lat) + + else + + ! If not periodic, then try extrapolating + i1 = get_dart_vector_index(i,j ,k , state_id, gz_id) + i2 = get_dart_vector_index(i,j ,k+1, state_id, gz_id) + i3 = get_dart_vector_index(i,j+1,k , state_id, gz_id) + i4 = get_dart_vector_index(i,j+1,k+1, state_id, gz_id) + + x_i1 = get_state(i1, state_handle) + x_i2 = get_state(i2, state_handle) + x_i3 = get_state(i3, state_handle) + x_i4 = get_state(i4, state_handle) + + geop = minval(( 3.0_r8*(stat_dat(id)%phb(i,j ,k )+x_i1) & + +3.0_r8*(stat_dat(id)%phb(i,j ,k+1)+x_i2) & + -(stat_dat(id)%phb(i,j+1,k )+x_i3) & + -(stat_dat(id)%phb(i,j+1,k+1)+x_i4) )/(4.0_r8*gravity)) + + lat = ( 3.0_r8*grid(id)%latitude(i,j ) & + +3.0_r8*grid(id)%latitude(i,j ) & + -grid(id)%latitude(i,j+1) & + -grid(id)%latitude(i,j+1)) / 4.0_r8 + + model_height = compute_geometric_height(geop, lat) + + endif + + else + + i1 = get_dart_vector_index(i,j ,k , state_id, gz_id) + i2 = get_dart_vector_index(i,j ,k+1, state_id, gz_id) + i3 = get_dart_vector_index(i,j-1,k , state_id, gz_id) + i4 = get_dart_vector_index(i,j-1,k+1, state_id, gz_id) + + x_i1 = get_state(i1, state_handle) + x_i2 = get_state(i2, state_handle) + x_i3 = get_state(i3, state_handle) + x_i4 = get_state(i4, state_handle) + + geop = minval(( (stat_dat(id)%phb(i,j ,k )+x_i1) & + +(stat_dat(id)%phb(i,j ,k+1)+x_i2) & + +(stat_dat(id)%phb(i,j-1,k )+x_i3) & + +(stat_dat(id)%phb(i,j-1,k+1)+x_i4) )/(4.0_r8*gravity)) + + lat = ( grid(id)%latitude(i,j ) & + +grid(id)%latitude(i,j ) & + +grid(id)%latitude(i,j-1) & + +grid(id)%latitude(i,j-1)) / 4.0_r8 + + model_height = compute_geometric_height(geop, lat) + + endif + +else + + i1 = get_dart_vector_index(i,j,k , state_id, gz_id) + i2 = get_dart_vector_index(i,j,k+1, state_id, gz_id) + + x_i1 = get_state(i1, state_handle) + x_i2 = get_state(i2, state_handle) + + geop = minval(( (stat_dat(id)%phb(i,j,k )+x_i1) & + +(stat_dat(id)%phb(i,j,k+1)+x_i2) )/(2.0_r8*gravity)) + + lat = grid(id)%latitude(i,j) + + model_height = compute_geometric_height(geop, lat) + +endif + +end function model_height + +!------------------------------------------------------------------ +pure function interp_pressure(p1, p2, use_log) + +real(r8), intent(in) :: p1(1), p2(1) +logical, intent(in) :: use_log +real(r8) :: interp_pressure + +if (use_log) then + interp_pressure = exp((log(p1(1)) + log(p2(1)))/2.0_r8) +else + interp_pressure = (p1(1) + p2(1))/2.0_r8 +endif + +end function interp_pressure + +!------------------------------------------------------------------ +pure function extrap_pressure(p1, p2, use_log) + +real(r8), intent(in) :: p1(1), p2(1) +logical, intent(in) :: use_log + +real(r8) :: extrap_pressure +real(r8) :: intermediate + +if (use_log) then + intermediate = (3.0_r8*log(p1(1)) - log(p2(1)))/2.0_r8 + if (intermediate <= 0.0_r8) then + extrap_pressure = p1(1) + else + extrap_pressure = exp(intermediate) + endif +else + extrap_pressure = (3.0_r8*p1(1) - p2(1))/2.0_r8 +endif + +end function extrap_pressure + +!------------------------------------------------------------------ +! model pressure any grid (w,u,v,t) +! used in convert_vertical_state so ens_size = 1 +function model_pressure(i, j, kp, id, var_id, state_id, state_handle) + +type(ensemble_type), intent(in) :: state_handle +integer, intent(in) :: i,j,kp,id +integer, intent(in) :: var_id +integer, intent(in) :: state_id +real(r8) :: model_pressure + +real(r8) :: p(1), p_one(1), p_two(1) ! only using the mean, so calling model_pressure_t with ens_size=1 +integer :: k(1), off, n + +k(1) = kp ! array version + +! HK this is for soil variables +do n = 1, get_num_dims(state_id, var_id) + if ( get_dim_name(state_id, var_id, n) == 'soil_layers_stag' ) then + p = model_pressure_s(i, j, id, state_handle, 1) + model_pressure = p(1) + return + endif +enddo + +if (get_num_dims(state_id, var_id) == 2) then ! surface qty + p = model_pressure_s(i, j, id, state_handle, 1) + model_pressure = p(1) + return +endif + +if (on_w_grid(state_id, var_id)) then ! average in the vertical + + if (kp==1) then ! on boundary, extrapolate in the vertical + + p_one = model_pressure_t(i, j, k, id, state_handle, 1) + p_two = model_pressure_t(i, j, k+1, id, state_handle, 1) + model_pressure = extrap_pressure(p_one, p_two, log_vert_interp) + + elseif ( kp==grid(id)%bts ) then ! on boundary, extrapolate in the vertical + + p_one = model_pressure_t(i, j, k-1, id, state_handle, 1) + p_two = model_pressure_t(i, j, k-2, id, state_handle, 1) + model_pressure = extrap_pressure(p_one, p_two, log_vert_interp) + + else ! interpolate + + p_one = model_pressure_t(i, j, k, id, state_handle, 1) + p_two = model_pressure_t(i, j, k-1,id, state_handle, 1) + model_pressure = interp_pressure(p_one, p_two, log_vert_interp) + + endif + +elseif (on_u_grid(state_id, var_id)) then ! average in the horizontal u direction + + if (i==grid(id)%wes) then + + if ( grid(id)%periodic_x ) then + + ! We are at seam in longitude, take first and last M-grid points + p_one = model_pressure_t(i-1,j, k, id, state_handle, 1) + p_two = model_pressure_t(1, j, k, id, state_handle, 1) + model_pressure = interp_pressure(p_one, p_two, log_horz_interpM) + + else + + ! If not periodic, then try extrapolating + p_one = model_pressure_t(i-1, j, k, id, state_handle, 1) + p_two = model_pressure_t(i-2, j, k, id, state_handle, 1) + model_pressure = extrap_pressure(p_one, p_two, log_horz_interpM) + + endif + + elseif (i==1) then + + if ( grid(id)%periodic_x ) then + + ! We are at seam in longitude, take first and last M-grid points + p_one = model_pressure_t(i, j, k, id, state_handle, 1) + p_two = model_pressure_t(grid(id)%we, j, k, id, state_handle, 1) + model_pressure = interp_pressure(p_one, p_two, log_horz_interpM) + else + + ! If not periodic, then try extrapolating + p_one = model_pressure_t(i, j, k, id, state_handle, 1) + p_two = model_pressure_t(i+1, j, k, id, state_handle, 1) + model_pressure = extrap_pressure(p_one, p_two, log_horz_interpM) + + endif + + else + p_one = model_pressure_t(i, j, k, id, state_handle, 1) + p_two = model_pressure_t(i-1, j, k, id, state_handle, 1) + model_pressure = interp_pressure(p_one, p_two, log_horz_interpM) + endif + +elseif (on_v_grid(state_id, var_id)) then ! average in the horizontal v direction + + if (j==grid(id)%sns) then + + ! Check to see if periodic in latitude (polar) + if ( grid(id)%polar ) then + + ! The upper corner is 180 degrees of longitude away + off = i + grid(id)%we/2 + if ( off > grid(id)%we ) off = off - grid(id)%we + + p_one = model_pressure_t(off, j-1, k, id, state_handle, 1) + p_two = model_pressure_t(i, j-1, k, id, state_handle, 1) + model_pressure = interp_pressure(p_one, p_two, log_horz_interpM) + + ! If not periodic, then try extrapolating + else + + p_one = model_pressure_t(i,j-1,k,id, state_handle, 1) + p_two = model_pressure_t(i,j-2,k,id, state_handle, 1) + model_pressure = extrap_pressure(p_one, p_two, log_horz_interpM) + + endif + + elseif (j==1) then + + ! Check to see if periodic in latitude (polar) + if ( grid(id)%polar ) then + + ! The lower corner is 180 degrees of longitude away + off = i + grid(id)%we/2 + if ( off > grid(id)%we ) off = off - grid(id)%we + + p_one = model_pressure_t(off,j,k,id, state_handle, 1) + p_two = model_pressure_t(i, j,k,id, state_handle, 1) + model_pressure = interp_pressure(p_one, p_two, log_horz_interpM) + + ! If not periodic, then try extrapolating + else + p_one = model_pressure_t(i, j, k, id, state_handle, 1) + p_two = model_pressure_t(i, j+1, k, id, state_handle, 1) + model_pressure = extrap_pressure(p_one, p_two, log_horz_interpM) + endif + + else + p_one = model_pressure_t(i, j ,k, id, state_handle, 1) + p_two = model_pressure_t(i, j-1, k, id, state_handle, 1) + model_pressure = interp_pressure(p_one, p_two, log_horz_interpM) + + endif + +else ! on t_grid + + p = model_pressure_t(i, j, k, id, state_handle, 1) + model_pressure = p(1) + +endif + +end function model_pressure + +!------------------------------------------------------------------ +! model surface pressure any grid u,v,w,t +function model_surface_pressure(i, j, id, var_id, state_id, state_handle) + +! Calculate the surface pressure at grid point (i,j), domain id. +! The grid is defined according to var_type. + +type(ensemble_type), intent(in) :: state_handle +integer, intent(in) :: i, j, id +integer, intent(in) :: var_id +integer, intent(in) :: state_id +real(r8) :: model_surface_pressure + +integer :: off +integer :: ens_size ! only using the mean, so calling model_pressure_s with ens_size=1 +real(r8) :: pres1(1), pres2(1) + +ens_size = 1 + +model_surface_pressure = missing_r8 + +! If U-grid, then pressure is defined between U points, so average -- +! averaging depends on longitude periodicity +if( on_u_grid(state_id, var_id) ) then + + if (i==grid(id)%wes) then + + ! Check to see if periodic in longitude + if ( grid(id)%periodic_x ) then + + ! We are at seam in longitude, take first and last M-grid points + pres1 = model_pressure_s(i-1,j,id, state_handle, ens_size) + pres2 = model_pressure_s(1, j,id, state_handle, ens_size) + model_surface_pressure = interp_pressure(pres1, pres2, log_horz_interpM) + + else + + ! If not periodic, then try extrapolating + pres1 = model_pressure_s(i-1,j,id, state_handle, ens_size) + pres2 = model_pressure_s(i-2,j,id, state_handle, ens_size) + model_surface_pressure = extrap_pressure(pres1, pres2, log_horz_interpM) + + endif + + elseif( i == 1 ) then + + ! Check to see if periodic in longitude + if ( grid(id)%periodic_x ) then + + ! We are at seam in longitude, take first and last M-grid points + pres1 = model_pressure_s(i, j,id, state_handle, ens_size) + pres2 = model_pressure_s(grid(id)%we,j,id, state_handle, ens_size) + model_surface_pressure = interp_pressure(pres1, pres2, log_horz_interpM) + + else + + ! If not periodic, then try extrapolating + pres1 = model_pressure_s(i, j,id, state_handle, ens_size) + pres2 = model_pressure_s(i+1,j,id, state_handle, ens_size) + model_surface_pressure = extrap_pressure(pres1, pres2, log_horz_interpM) + + endif + + else + + pres1 = model_pressure_s(i, j,id, state_handle, ens_size) + pres2 = model_pressure_s(i-1,j,id, state_handle, ens_size) + model_surface_pressure = interp_pressure(pres1, pres2, log_horz_interpM) + + endif + +! If V-grid, then pressure is defined between V points, so average -- +! averaging depends on polar periodicity +elseif( on_v_grid(state_id, var_id) ) then + + if (j==grid(id)%sns) then + + ! Check to see if periodic in latitude (polar) + if ( grid(id)%polar ) then + + ! The upper corner is 180 degrees of longitude away + off = i + grid(id)%we/2 + if ( off > grid(id)%we ) off = off - grid(id)%we + + pres1 = model_pressure_s(off,j-1,id, state_handle, ens_size) + pres2 = model_pressure_s(i ,j-1,id, state_handle, ens_size) + model_surface_pressure = interp_pressure(pres1, pres2, log_horz_interpM) + + ! If not periodic, then try extrapolating + else + + pres1 = model_pressure_s(i,j-1,id, state_handle, ens_size) + pres2 = model_pressure_s(i,j-2,id, state_handle, ens_size) + model_surface_pressure = extrap_pressure(pres1, pres2, log_horz_interpM) + + endif + + elseif( j == 1 ) then + + ! Check to see if periodic in latitude (polar) + if ( grid(id)%polar ) then + + ! The lower corner is 180 degrees of longitude away + off = i + grid(id)%we/2 + if ( off > grid(id)%we ) off = off - grid(id)%we + + pres1 = model_pressure_s(off,j,id, state_handle, ens_size) + pres2 = model_pressure_s(i, j,id, state_handle, ens_size) + model_surface_pressure = interp_pressure(pres1, pres2, log_horz_interpM) + + ! If not periodic, then try extrapolating + else + + pres1 = model_pressure_s(i,j, id, state_handle, ens_size) + pres2 = model_pressure_s(i,j+1,id, state_handle, ens_size) + model_surface_pressure = extrap_pressure(pres1, pres2, log_horz_interpM) + + endif + + else + + pres1 = model_pressure_s(i,j, id, state_handle, ens_size) + pres2 = model_pressure_s(i,j-1,id, state_handle, ens_size) + model_surface_pressure = interp_pressure(pres1, pres2, log_horz_interpM) + + endif + +else + + pres1 = model_pressure_s(i,j,id, state_handle, ens_size) + model_surface_pressure = pres1(1) + +endif + +end function model_surface_pressure + +!------------------------------------------------------------------ +function convert_indices_to_lon_lat_lev(i, j, k, var_id, state_id) + +integer, intent(in) :: i, j, k, var_id, state_id +type(location_type) :: convert_indices_to_lon_lat_lev + +real(r8) :: long, lat, lev +integer :: dom_id + +dom_id = get_wrf_domain(state_id) + +if ( on_u_grid(state_id, var_id) ) then + long = grid(dom_id)%longitude_u(i,j) + lat = grid(dom_id)%latitude_u(i,j) +elseif ( on_v_grid(state_id, var_id) ) then + long = grid(dom_id)%longitude_v(i,j) + lat = grid(dom_id)%latitude_v(i,j) +else ! on mass grid + long = grid(dom_id)%longitude(i,j) + lat = grid(dom_id)%latitude(i,j) +endif + +! dart expects longitude [0,360] +do while (long < 0.0_r8) + long = long + 360.0_r8 +end do +do while (long > 360.0_r8) + long = long - 360.0_r8 +end do + + +if ( on_w_grid(state_id, var_id) ) then + lev = real(k) - 0.5_r8 +else + lev = real(k) +endif + +convert_indices_to_lon_lat_lev = set_location(long,lat,lev, VERTISLEVEL) + +end function convert_indices_to_lon_lat_lev + +!------------------------------------------------------------------ +! which grid a variable is on. +! querying dimension here, could do by qty? +!------------------------------------------------------------------ +function on_u_grid(state_id, ivar) +integer, intent(in) :: state_id, ivar +logical :: on_u_grid + +on_u_grid = (get_dim_name(state_id, ivar, 1) == 'west_east_stag') + +end function + +!------------------------------------------------------------------ +function on_v_grid(state_id, ivar) +integer, intent(in) :: state_id, ivar +logical :: on_v_grid + +on_v_grid = (get_dim_name(state_id, ivar, 2) == 'south_north_stag') + +end function + +!------------------------------------------------------------------ +function on_w_grid(state_id, ivar) +integer, intent(in) :: state_id, ivar +logical :: on_w_grid + +if (get_num_dims(state_id, ivar) > 2) then + on_w_grid = (get_dim_name(state_id, ivar, 3) == 'bottom_top_stag') +else + on_w_grid = .false. +endif + +end function on_w_grid + +!------------------------------------------------------------------ +function on_t_grid(state_id, ivar) +integer, intent(in) :: state_id, ivar +logical :: on_t_grid + +on_t_grid = (get_dim_name(state_id, ivar, 1) == 'west_east') .and. & + (get_dim_name(state_id, ivar, 2) == 'south_north') + +end function on_t_grid + + +!------------------------------------------------------------------ +!------------------------------------------------------------------ + +function within_bounds_horizontal(i, j, id, qty_in) + +integer, intent(in) :: i, j +integer, intent(in) :: id +integer, intent(in) :: qty_in +logical :: within_bounds_horizontal + +integer :: var_id, qty + +! Some qtys we can interpolate, but the qty is not in the state. +! using QTY_POTENTIAL_TEMPERATURE because this is on the mass grid. +! internal to model_mod, QTY_TEMPERATURE is QTY_POTENTIAL_TEMPERATURE +select case (qty_in) + + case (QTY_SURFACE_TYPE); qty = QTY_POTENTIAL_TEMPERATURE ! uses land mask XLAND which is static data on mass grid + case (QTY_LANDMASK); qty = QTY_POTENTIAL_TEMPERATURE ! land mask XLAND is static data on mass grid + case (QTY_SURFACE_ELEVATION); qty = QTY_POTENTIAL_TEMPERATURE ! terrain height HGT is static data on mass grid + case (QTY_TEMPERATURE); qty = QTY_POTENTIAL_TEMPERATURE ! Force QTY_TEMPERATURE to QTY_POTENTIAL_TEMPERATURE + case default + qty = qty_in +end select + +var_id = get_varid_from_kind(wrf_dom(id), qty) + +within_bounds_horizontal = (bounds_check_lon(i, id, var_id) .and. bounds_check_lat(j, id, var_id)) + +end function within_bounds_horizontal + +!------------------------------------------------------------------ +function able_to_interpolate_qty(id, qty) + +integer, intent(in) :: id +integer, intent(in) :: qty + +logical :: able_to_interpolate_qty + +select case (qty) + case (QTY_U_WIND_COMPONENT, QTY_V_WIND_COMPONENT) + able_to_interpolate_qty = qty_in_domain(id, QTY_U_WIND_COMPONENT) .and. & + qty_in_domain(id, QTY_V_WIND_COMPONENT) + + case (QTY_10M_U_WIND_COMPONENT, QTY_10M_V_WIND_COMPONENT) + able_to_interpolate_qty = qty_in_domain(id, QTY_10M_U_WIND_COMPONENT) .and. & + qty_in_domain(id, QTY_10M_V_WIND_COMPONENT) + + case (QTY_DENSITY) + able_to_interpolate_qty = qty_in_domain(id, QTY_GEOPOTENTIAL_HEIGHT) .and. & + qty_in_domain(id, QTY_PRESSURE) + + case (QTY_SURFACE_TYPE) + able_to_interpolate_qty = .true. ! land mask XLAND is static data + + case (QTY_LANDMASK) + able_to_interpolate_qty = .true. ! land mask XLAND is static data + + case (QTY_SURFACE_ELEVATION) + able_to_interpolate_qty = .true. ! terrain height HGT is static data + + case (QTY_TEMPERATURE, QTY_POTENTIAL_TEMPERATURE) + able_to_interpolate_qty = qty_in_domain(id, QTY_POTENTIAL_TEMPERATURE) + + case default + able_to_interpolate_qty = qty_in_domain(id, qty) + +end select + + +end function able_to_interpolate_qty + +!------------------------------------------------------------------ +function qty_in_domain(id, qty) + +integer, intent(in) :: id +integer, intent(in) :: qty + +logical :: qty_in_domain + +integer :: varid + +varid = get_varid_from_kind(wrf_dom(id), qty) + +if (varid > 0) then + qty_in_domain = .true. +else + qty_in_domain = .false. +endif + +end function qty_in_domain + +!------------------------------------------------------------------ +! returns closest domain id and horizontal mass point grid points (iloc,jloc) +subroutine get_domain_info(obslon,obslat,id,iloc,jloc,domain_id_start) + +real(r8), intent(in) :: obslon, obslat +integer, intent(out) :: id +real(r8), intent(out) :: iloc, jloc +integer, intent(in), optional :: domain_id_start ! HK this is used in wrf_dart_obs_preprocess.f90 + +integer :: n ! domain to start from + +if (present(domain_id_start)) then + n = domain_id_start +else + n = num_domains +endif + +do id = n, 1, -1 + +! From module_map_utils.f90 +! latlon_to_ij(proj, lat, lon, i, j) +! ij_to_latlon(proj, i, j, lat, lon) +! +! It is incumbent upon the calling routine to determine whether or +! not the values returned are within your domain's bounds. All values +! of i, j, lat, and lon are REAL values. + + call latlon_to_ij(grid(id)%proj,min(max(obslat,-89.9999999_r8),89.9999999_r8),obslon,iloc,jloc) + + if (found_in_domain(id, iloc,jloc)) return + +enddo + +! domain not found +id=0 + +end subroutine get_domain_info + +!------------------------------------------------------------------ +function found_in_domain(id, i,j) +integer, intent(in) :: id +real(r8), intent(in) :: i, j +logical :: found_in_domain + +found_in_domain = .true. + +if (id > 1) then + + found_in_domain = ( i >= 1.0_r8 .and. i <= real(grid(id)%we,r8) .and. & + j >= 1.0_r8 .and. j <= real(grid(id)%sn,r8) ) + +else ! have to check periodic + +! Array bound checking depends on whether periodic or not -- these are +! real-valued indices here, so we cannot use boundsCheck :( + + if ( grid(id)%periodic_x .and. .not. grid(id)%periodic_y ) then + if ( grid(id)%polar ) then + ! Periodic X & M_grid ==> [1 we+1) + ! Periodic Y & M_grid ==> [0.5 sn+0.5] + found_in_domain = ( i >= 1.0_r8 .and. i < real(grid(id)%we,r8)+1.0_r8 .and. & + j >= 0.5_r8 .and. j <= real(grid(id)%sn,r8)+0.5_r8 ) + else + ! Periodic X & M_grid ==> [1 we+1) + ! NOT Periodic Y & M_grid ==> [1 sn] + found_in_domain = ( i >= 1.0_r8 .and. i < real(grid(id)%we,r8)+1.0_r8 .and. & + j >= 1.0_r8 .and. j <= real(grid(id)%sn,r8) ) + endif + + elseif ( grid(id)%periodic_x .and. grid(id)%periodic_y ) then + ! Periodic X & M_grid ==> [1 we+1) + ! Periodic Y & M_grid ==> [1 sn+1] + found_in_domain = ( i >= 1.0_r8 .and. i < real(grid(id)%we,r8)+1.0_r8 .and. & + j >= 1.0_r8 .and. j <= real(grid(id)%sn,r8)+1.0_r8 ) + + else + if ( grid(id)%polar ) then + ! NOT Periodic X & M_grid ==> [1 we] + ! Periodic Y & M_grid ==> [0.5 sn+0.5] + found_in_domain = ( i >= 1.0_r8 .and. i <= real(grid(id)%we,r8) .and. & + j >= 0.5_r8 .and. j <= real(grid(id)%sn,r8)+0.5_r8 ) + else + ! NOT Periodic X & M_grid ==> [1 we] + ! NOT Periodic Y & M_grid ==> [1 sn] + found_in_domain = ( i >= 1.0_r8 .and. i <= real(grid(id)%we,r8) .and. & + j >= 1.0_r8 .and. j <= real(grid(id)%sn,r8) ) + endif + endif +endif + + +end function found_in_domain + +!------------------------------------------------------------------ +subroutine getCorners(i, j, id, qty, ll, ul, lr, ur, rc) + +integer, intent(in) :: i, j, id, qty +integer, dimension(2), intent(out) :: ll, ul, lr, ur ! (x,y) of each corner +integer, intent(out) :: rc + +integer :: var_id + +! set return code to 0, and change this if necessary +rc = 0 + +var_id = get_varid_from_kind(wrf_dom(id), qty) + + +!---------------- +! LOWER LEFT ll +!---------------- +! i and j are the lower left (ll) corner already +! +! NOTE :: once we allow for polar periodicity, the incoming j index could actually +! be 0, which would imply a ll(2) value of 1, with a ll(1) value 180 degrees +! of longitude away from the incoming i index! But we have not included +! this possibility yet. + +! As of 22 Oct 2007, this option is not allowed! +! Note that j = 0 can only happen if we are on the M (or U) wrt to latitude +if ( grid(id)%polar .and. j == 0 .and. .not. restrict_polar ) then + + ! j = 0 should be mapped to j = 1 (ll is on other side of globe) + ll(2) = 1 + + ! Need to map i index 180 degrees away + ll(1) = i + grid(id)%we/2 + + ! Check validity of bounds & adjust by periodicity if necessary + if ( ll(1) > grid(id)%we ) ll(1) = ll(1) - grid(id)%we + + ! We shouldn't be able to get this return code if restrict_polar = .true. + rc = 1 + print*, 'model_mod.f90 :: getCorners :: Tried to do polar bc -- rc = ', rc + +else + + ll(1) = i + ll(2) = j + +endif + + +!---------------- +! LOWER RIGHT lr +!---------------- + +! Most of the time, the lower right (lr) corner will simply be (i+1,j), but we need to check +! Summary of x-direction corners: +! Periodic & M_grid has ind = [1 wes) +! ind = [1 we) ==> ind_p_1 = ind + 1 +! ind = [we wes) ==> ind_p_1 = 1 +! Periodic & U_grid has ind = [1 wes) +! ind = [1 we) ==> ind_p_1 = ind + 1 +! ind = [we wes) ==> ind_p_1 = wes ( keep in mind that U(1) = U(wes) if periodic ) +! NOT Periodic & M_grid has ind = [1 we) +! ind = [1 we-1) ==> ind_p_1 = ind + 1 +! ind = [we-1 we) ==> ind_p_1 = we +! NOT Periodic & U_grid has ind = [1 wes) +! ind = [1 we) ==> ind_p_1 = ind + 1 +! ind = [we wes) ==> ind_p_1 = wes + +if ( grid(id)%periodic_x ) then + + ! Check to see what grid we have, M vs. U + if (on_u_grid(wrf_dom(id), var_id) ) then + ! U-grid is always i+1 -- do this in reference to already adjusted ll points + lr(1) = ll(1) + 1 + lr(2) = ll(2) + else + ! M-grid is i+1 except if we <= ind < wes, in which case it's 1 + if ( i < grid(id)%we ) then + lr(1) = ll(1) + 1 + else + lr(1) = 1 + endif + lr(2) = ll(2) + endif + +else + + ! Regardless of grid, NOT Periodic always has i+1 + lr(1) = ll(1) + 1 + lr(2) = ll(2) + +endif + + +!---------------- +! UPPER LEFT ul +!---------------- + +!** NOTE: For now are disallowing observation locations that occur poleward of the +! first and last M-grid gridpoints. This need not be the case because +! the information should be available for proper interpolation across the +! poles, but it will require more clever thinking. Hopefully this can +! be added in later. + +! Most of the time, the upper left (ul) corner will simply be (i,j+1), but we need to check +! Summary of y-direction corners: +! Periodic & M_grid has ind = [0 sns) *though in practice, [1 sn)* +! ind = [1 sn-1) ==> ind_p_1 = ind + 1 +! ind = [sn-1 sn) ==> ind_p_1 = sn +! Periodic & V_grid has ind = [1 sns) +! ind = [1 sn) ==> ind_p_1 = ind + 1 +! ind = [sn sns) ==> ind_p_1 = sns +! NOT Periodic & M_grid has ind = [1 sn) +! ind = [1 sn-1) ==> ind_p_1 = ind + 1 +! ind = [sn-1 sn) ==> ind_p_1 = sn +! NOT Periodic & V_grid has ind = [1 sns) +! ind = [1 sn) ==> ind_p_1 = ind + 1 +! ind = [sn sns) ==> ind_p_1 = sns +! +! Hence, with our current polar obs restrictions, all four possible cases DO map into +! ul = (i,j+1). But this will not always be the case. + +if ( grid(id)%polar ) then + + ! Check to see what grid we have, M vs. V + if ( on_v_grid(wrf_dom(id), var_id) ) then + ! V-grid is always j+1, even if we allow for full [1 sns) range + ul(1) = ll(1) + ul(2) = ll(2) + 1 + else + ! M-grid changes depending on polar restriction + if ( restrict_polar ) then + ! If restricted, then we can simply add 1 + ul(1) = ll(1) + ul(2) = ll(2) + 1 + else + ! If not restricted, then we can potentially wrap over the north pole, which + ! means that ul(2) is set to sn and ul(1) is shifted by 180 deg. + + if ( j == grid(id)%sn ) then + ! j > sn should be mapped to j = sn (ul is on other side of globe) + ul(2) = grid(id)%sn + + ! Need to map i index 180 degrees away + ul(1) = ll(1) + grid(id)%we/2 + + ! Check validity of bounds & adjust by periodicity if necessary + if ( ul(1) > grid(id)%we ) ul(1) = ul(1) - grid(id)%we + + ! We shouldn't be able to get this return code if restrict_polar = .true. + rc = 1 + print*, 'model_mod.f90 :: getCorners :: Tried to do polar bc -- rc = ', rc + + elseif ( j == 0 ) then + ! In this case, we have place ll on the other side of the globe, so we + ! cannot reference ul to ll + ul(1) = i + ul(2) = 1 + + else + ! We can confidently set to j+1 + ul(1) = ll(1) + ul(2) = ll(2) + 1 + endif + + endif + endif + +elseif ( grid(id)%periodic_y ) then + + ! Check to see what grid we have, M vs. V + if ( on_v_grid(wrf_dom(id), var_id) ) then + ! V-grid is always j+1 -- do this in reference to already adjusted ll points + ul(1) = ll(1) + ul(2) = ll(2)+1 + else + ! M-grid is j+1 except if we <= ind < wes, in which case it's 1 + if ( j < grid(id)%sn ) then + ul(2) = ll(2) + 1 + else + ul(2) = 1 + endif + ul(1) = ll(1) + endif + +else + + ! Regardless of grid, NOT Periodic always has j+1 + ul(1) = ll(1) + ul(2) = ll(2) + 1 + +endif + + +!---------------- +! UPPER RIGHT ur +!---------------- + +!*** NOTE: For now are disallowing observation locations that occur poleward of the +! first and last M-grid gridpoints. This need not be the case because +! the information should be available for proper interpolation across the +! poles, but it will require more clever thinking. Hopefully this can +! be added in later. + +! Most of the time, the upper right (ur) corner will simply be (i+1,j+1), but we need to check +! In fact, we can largely get away with ur = (lr(1),ul(2)). Where this will NOT work is +! where we have had to re-map the i index to the other side of the globe (180 deg) due to +! the polar boundary condition. There are no situations where ur(2) will not be equal to +! ul(2). + +ur(2) = ul(2) + +! Need to check if ur(1) .ne. lr(1) +if ( grid(id)%polar .and. .not. restrict_polar ) then + + ! Only if j == 0 or j == sn + if ( j == 0 .or. j == grid(id)%sn) then + ! j == 0 means that ll(1) = i + 180 deg, so we cannot use lr(1) -- hence, we will + ! add 1 to ul(1), unless doing so spans the longitude seam point. + ! j == sn means that ul(1) = i + 180 deg. Here we cannot use lr(1) either because + ! it will be half a domain away from ul(1)+1. Be careful of longitude seam point. + + ! Here we need to check longitude periodicity and the type of grid + if ( grid(id)%periodic_x ) then + + ! Check to see what grid we have, M vs. U + if ( on_u_grid(wrf_dom(id), var_id) ) then + ! U-grid is always i+1 -- do this in reference to already adjusted ll points + ur(1) = ul(1) + 1 + else + ! M-grid is i+1 except if we <= ind < wes, in which case it's 1 + if ( ul(1) < grid(id)%we ) then + ur(1) = ul(1) + 1 + else + ur(1) = 1 + endif + endif + + else + + ! Regardless of grid, NOT Periodic always has i+1 + ur(1) = ul(1) + 1 + + endif + + ! If not a special j value, then we are set for the ur(1) = lr(1) + else + + ur(1) = lr(1) + + endif + +! If not an unrestricted polar periodic domain, then we have nothing to worry about +else + + ur(1) = lr(1) + +endif + +end subroutine getCorners + + +!------------------------------------------------------------------ +! Transfer obs. x to grid j and calculate its +! distance to grid j and j+1 +subroutine toGrid (x, j, dx, dxm) + +real(r8), intent(in) :: x +real(r8), intent(out) :: dx, dxm +integer, intent(out) :: j + +j = int(x) +dx = x - real(j) ! HK todo this should be real(j, KIND=r8)? +dxm= 1.0_r8 - dx + +end subroutine toGrid + +!------------------------------------------------------------------ +subroutine setup_map_projection(id) + +integer, intent(in) :: id +logical, parameter :: debug = .false. + +real(r8) :: latinc,loninc,stdlon +real(r8) :: truelat1, truelat2 + +call map_init(grid(id)%proj) + +! Populate the map projection structure + +!nc -- added in case structures for CASSINI and CYL +!nc -- global wrfinput_d01 has truelat1 = 1.e20, so we need to change it where needed +!nc -- for PROJ_LATLON stdlon and truelat1 have different meanings -- +!nc -- stdlon --> loninc and truelat1 --> latinc +!JPH --- this latinc/loninc calculations are only valid for global domains + +!if ( wrf%dom(id)%scm ) then +!! JPH -- set to zero which should cause the map utils to return NaNs if called +! latinc = 0.0_r8 +! loninc = 0.0_r8 +!else +! latinc = 180.0_r8/wrf%dom(id)%sn +! loninc = 360.0_r8/wrf%dom(id)%we +!endif + +latinc = 180.0_r8/size(grid(id)%longitude(:,1)) ! west_east +loninc = 360.0_r8/size(grid(id)%longitude(1,:)) ! north_south + +if(grid(id)%map_proj == PROJ_LATLON) then !HK why are these different to the wrfinput file? + truelat1 = latinc + stdlon = loninc +else + truelat1 = grid(id)%truelat1 + truelat2 = grid(id)%truelat2 + stdlon = grid(id)%stand_lon +endif + +!nc -- specified inputs to hopefully handle ALL map projections -- hopefully map_set will +! just ignore the inputs it doesn't need for its map projection of interest (?) +! +! NOTE:: We are NOT yet supporting the Gaussian grid or the Rotated Lat/Lon, so we +! are going to skip the entries: nlon, nlat, ixdim, jydim, stagger, phi, lambda +! +! + Gaussian grid uses nlat & nlon +! + Rotated Lat/Lon uses ixdim, jydim, stagger, phi, & lambda +! +call map_set( proj_code=grid(id)%map_proj, & + proj=grid(id)%proj, & + lat1=grid(id)%latitude(1,1), & + lon1=grid(id)%longitude(1,1), & + lat0=90.0_r8, & + lon0=0.0_r8, & + knowni=1.0_r8, & + knownj=1.0_r8, & + dx=grid(id)%dx, & + latinc=latinc, & + loninc=loninc, & + stdlon=stdlon, & + truelat1=truelat1, & + truelat2=truelat2 ) + +end subroutine setup_map_projection + + +!------------------------------------------------------------------ +!------------------------------------------------------------------ +! Bounds checking routines: + ! Summary of Allowable REAL-VALUED Index Values ==> INTEGER Index Values + ! + ! In longitude (x) direction + ! Periodic & M_grid ==> [1 we+1) ==> [1 wes) + ! Periodic & U_grid ==> [1 wes) ==> [1 wes) + ! NOT Periodic & M_grid ==> [1 we] ==> [1 we) + ! NOT Periodic & U_grid ==> [1.5 we+0.5] ==> [1 wes) + ! In latitude (y) direction + ! Periodic & M_grid ==> [0.5 sn+0.5] ==> [0 sns) *though in practice, [1 sn)* + ! Periodic & V_grid ==> [1 sns] ==> [1 sns) *though allowable range, [1.5 sn+.5]* + ! NOT Periodic & M_grid ==> [1 sn] ==> [1 sn) + ! NOT Periodic & V_grid ==> [1.5 sn+0.5] ==> [1 sns) + ! In vertical (z) direction + ! M_grid ==> [1 bt] ==> [1 bt) + ! W_grid ==> [1.5 bt+0.5] ==> [1 bts) + +!------------------------------------------------------------------ + +!------------------------------------------------------------------ +! Determines whether real-valued location indices are +! within a sensible range based on the assumed (un)staggered grid and based on +! whether the domain is assumed to be periodic in a given direction. +function bounds_check_lon(ind, id, var_id) ! or variable? + +integer, intent(in) :: ind ! index into the grid +integer, intent(in) :: id ! domain id +integer, intent(in) :: var_id + +logical :: bounds_check_lon + +! Consider cases in REAL-VALUED indexing: +! +! I. Longitude -- x-direction +! A. PERIODIC (period_x = .true.) +! +! Consider Mass-grid (& V-grid) longitude grid with 4 west-east gridpoints +! Values :: [ -135 -45 45 135 ] .. {225} +! Indices :: [ 1 2 3 4 ] .. {1,5} +! Complementary U-grid +! Values :: [ -180 -90 0 90 180 ] +! Indices :: [ 1 2 3 4 5 ] +! +! What are the allowable values for a real-valued index on each of these grids? +! 1. M-grid ---> [1 5) ---> [1 we+1) +! ---> [-135 225) +! 2. U-grid ---> [1 5) ---> [1 wes) +! ---> [-180 180) +! [Note that above "allowable values" reflect that one should be able to have +! an observation anywhere on a given longitude circle -- the information +! exists in order to successfully interpolate to anywhere over [0 360).] +! +! It is up to the routine calling "boundsCheck" to have handled the 0.5 offset +! in indices between the M-grid & U-grid. Hence, two examples: +! a. If there is an observation location at -165 longitude, then: +! * An observation of TYPE_T (on the M-grid) would have ind = 4.667 +! * An observation of TYPE_U (on the U-grid) would have ind = 1.167 +! b. If there is an observation location at 0 longitude, then: +! * An observation of TYPE_T (on the M-grid) would have ind = 2.5 +! * An observation of TYPE_U (on the U-grid) would have ind = 3.0 +! +! B. NOT periodic (period_x = .false.) +! +! Consider Mass-grid (& V-grid) longitude grid with 4 west-east gridpoints +! Values :: [ 95 105 115 125 ] +! Indices :: [ 1 2 3 4 ] +! Complementary U-grid +! Values :: [ 90 100 110 120 130 ] +! Indices :: [ 1 2 3 4 5 ] +! +! What are the allowable values for a real-valued index on each of these grids? +! 1. M-grid ---> [1 4] ---> [1 we] +! ---> [95 125] +! 2. U-grid ---> [1.5 4.5] ---> [1.5 we+0.5] +! ---> [95 125] +! [Note that above "allowable values" reflect that one should only be able to +! have an observation within the M-grid, since that is the only way to +! guarantee that the necessary information exists in order to successfully +! interpolate to a specified location.] +! +! It is up to the routine calling "boundsCheck" to have handled the 0.5 offset +! in indices between the M-grid & U-grid. Hence, two examples: +! a. If there is an observation location at 96 longitude, then: +! * An observation of TYPE_T (on the M-grid) would have ind = 1.1 +! * An observation of TYPE_U (on the U-grid) would have ind = 1.6 +! b. If there is an observation location at 124 longitude, then: +! * An observation of TYPE_T (on the M-grid) would have ind = 3.9 +! * An observation of TYPE_U (on the U-grid) would have ind = 4.4 +! + +bounds_check_lon = .false. + +! Next check periodicity +if ( grid(id)%periodic_x ) then + + ! If periodic in longitude, then no need to check staggering because both + ! M and U grids allow integer indices from [1 wes) + if ( ind >= 1 .and. ind < grid(id)%wes ) bounds_check_lon = .true. + +else + + ! If NOT periodic in longitude, then we need to check staggering because + ! M and U grids allow different index ranges + + ! Check staggering by comparing var_size(dim,type) to the staggered dimension + if ( on_u_grid(wrf_dom(id),var_id) ) then + ! U-grid allows integer range of [1 wes) + if ( ind >= 1 .and. ind < grid(id)%wes ) bounds_check_lon = .true. + else + ! M & V-grid allow [1 we) + if ( ind >= 1 .and. ind < grid(id)%we ) bounds_check_lon = .true. + endif + +endif + +end function bounds_check_lon + +!------------------------------------------------------------------ +function bounds_check_lat(ind, id, var_id) + +integer, intent(in) :: ind ! index into the grid +integer, intent(in) :: id ! domain id +integer, intent(in) :: var_id + +logical :: bounds_check_lat +! II. Latitude -- y-direction +! A. PERIODIC (polar = .true.) +! +! Consider Mass-grid (& U-Grid) latitude grid with 4 south-north gridpoints +! Values :: [ -67.5 -22.5 22.5 67.5 ] +! Indices :: [ 1 2 3 4 ] +! Complementary V-grid +! Values :: [ -90 -45 0 45 90 ] +! Indices :: [ 1 2 3 4 5 ] +! +! What are the allowable values for a real-valued index on each of these grids? +! 1. M-grid ---> [0.5 4.5] ---> [0.5 sn+0.5] +! ---> [-90 90] +! 2. U-grid ---> [1 5] ---> [1 sns] +! ---> [-90 90] +! [Note that above "allowable values" reflect that one should be able to have +! an observation anywhere along a give latitude circle -- the information +! exists in order to successfully interpolate to anywhere over [-90 90]; +! however, in latitude this poses a special challenge since the seams join +! two separate columns of data over the pole, as opposed to in longitude +! where the seam wraps back on a single row of data.] +! +! It is up to the routine calling "boundsCheck" to have handled the 0.5 offset +! in indices between the M-grid & V-grid. Hence, two examples: +! a. If there is an observation location at -75 latitude, then: +! * An observation of TYPE_T (on the M-grid) would have ind = 0.833 +! * An observation of TYPE_V (on the V-grid) would have ind = 1.333 +! b. If there is an observation location at 0 latitude, then: +! * An observation of TYPE_T (on the M-grid) would have ind = 2.5 +! * An observation of TYPE_V (on the V-grid) would have ind = 3.0 +! +! B. NOT periodic (polar = .false.) +! +! Consider Mass-grid (& U-Grid) latitude grid with 4 south-north gridpoints +! Values :: [ 10 20 30 40 ] +! Indices :: [ 1 2 3 4 ] +! Complementary V-grid +! Values :: [ 5 15 25 35 45 ] +! Indices :: [ 1 2 3 4 5 ] +! +! What are the allowable values for a real-valued index on each of these grids? +! 1. M-grid ---> [1 4] ---> [1 sn] +! ---> [10 40] +! 2. U-grid ---> [1.5 4.5] ---> [1.5 sn+0.5] +! ---> [10 40] +! [Note that above "allowable values" reflect that one should only be able to +! have an observation within the M-grid, since that is the only way to +! guarantee that the necessary information exists in order to successfully +! interpolate to a specified location.] +! +! It is up to the routine calling "boundsCheck" to have handled the 0.5 offset +! in indices between the M-grid & V-grid. Hence, two examples: +! a. If there is an observation location at 11 latitude, then: +! * An observation of TYPE_T (on the M-grid) would have ind = 1.1 +! * An observation of TYPE_V (on the V-grid) would have ind = 1.6 +! b. If there is an observation location at 25 latitude, then: +! * An observation of TYPE_T (on the M-grid) would have ind = 2.5 +! * An observation of TYPE_V (on the V-grid) would have ind = 3.0 +! + +if ( grid(id)%periodic_y ) then + + ! We need to check staggering because M and V grids allow different indices + +!*** NOTE: For now are disallowing observation locations that occur poleward of the +! first and last M-grid gridpoints. This means that this function will +! return false for polar observations. This need not be the case because +! the information should be available for proper interpolation across the +! poles, but it will require more clever thinking. Hopefully this can +! be added in later. + + ! Check staggering by comparing var_size(dim,type) to the staggered dimension + if ( on_v_grid(wrf_dom(id), var_id) ) then + ! V-grid allows integer range [1 sns) + if ( ind >= 1 .and. ind < grid(id)%sns ) bounds_check_lat = .true. + else + ! For now we will set a logical flag to more restrictively check the array + ! bounds under our no-polar-obs assumptions + if ( restrict_polar ) then + ! M & U-grid allow integer range [1 sn) in practice (though properly, [0 sns) ) + if ( ind >= 1 .and. ind < grid(id)%sn ) bounds_check_lat = .true. + else + ! M & U-grid allow integer range [0 sns) in unrestricted circumstances + if ( ind >= 0 .and. ind < grid(id)%sns ) bounds_check_lat = .true. + endif + endif + + else + + ! We need to check staggering because M and V grids allow different indices + if ( on_v_grid(wrf_dom(id), var_id) ) then + ! V-grid allows [1 sns) + if ( ind >= 1 .and. ind < grid(id)%sns ) bounds_check_lat = .true. + else + ! M & U-grid allow [1 sn) + if ( ind >= 1 .and. ind < grid(id)%sn ) bounds_check_lat = .true. + endif + + endif + +end function bounds_check_lat + + +!------------------------------------------------------------------ +function bounds_check_vertical(ind, id, var_id) + +integer, intent(in) :: ind ! index into the grid +integer, intent(in) :: id ! domain id +integer, intent(in) :: var_id + +logical :: bounds_check_vertical +! III. Vertical -- z-direction (periodicity not an issue) +! +! Consider Mass vertical grid with 4 bottom-top gridpoints +! Values :: [ 0.875 0.625 0.375 0.125 ] +! Indices :: [ 1 2 3 4 ] +! Complementary W-grid +! Values :: [ 1 0.75 0.50 0.25 0 ] +! Indices :: [ 1 2 3 4 5 ] +! +! What are the allowable values for a real-valued index on each of these grids? +! 1. M-grid ---> [1 4] ---> [1 bt] +! ---> [0.875 0.125] +! 2. W-grid ---> [1.5 4.5] ---> [1.5 bt+0.5] +! ---> [0.875 0.125] +! +! [Note that above "allowable values" reflect that one should only be able to +! have an observation within the M-grid, since that is the only way to +! guarantee that the necessary information exists in order to successfully +! interpolate to a specified location.] + +bounds_check_vertical = .false. + +if ( on_w_grid(wrf_dom(id), var_id) ) then + ! W vertical grid allows [1 bts) + if ( ind >= 1 .and. ind < grid(id)%bts ) bounds_check_vertical = .true. +else + ! M vertical grid allows [1 bt) + if ( ind >= 1 .and. ind < grid(id)%bt ) bounds_check_vertical = .true. +endif + +end function bounds_check_vertical + +!------------------------------------------------------------------ +! Summary of Allowable REAL-VALUED Index Values ==> INTEGER Index Values +! +! In longitude (x) direction +! Periodic & M_grid ==> [1 we+1) ==> [1 wes) +! Periodic & U_grid ==> [1 wes) ==> [1 wes) +! NOT Periodic & M_grid ==> [1 we] ==> [1 we) +! NOT Periodic & U_grid ==> [1.5 we+0.5] ==> [1 wes) +! In latitude (y) direction +! Periodic & M_grid ==> [0.5 sn+0.5] ==> [0 sns) *though in practice, [1 sn)* +! Periodic & V_grid ==> [1 sns] ==> [1 sns) *though allowable range, [1.5 sn+.5]* +! NOT Periodic & M_grid ==> [1 sn] ==> [1 sn) +! NOT Periodic & V_grid ==> [1.5 sn+0.5] ==> [1 sns) +! In vertical (z) direction +! M_grid ==> [1 bt] ==> [1 bt) +! W_grid ==> [1.5 bt+0.5] ==> [1 bts) +!------------------------------------------------------------------ + +!------------------------------------------------------------------ +! Vortex + +subroutine vortex() + +print*, 'Do vortex' + +end subroutine vortex + +end module model_mod diff --git a/models/wrf_unified/model_mod.nml b/models/wrf_unified/model_mod.nml new file mode 100644 index 0000000000..adea8d1c2a --- /dev/null +++ b/models/wrf_unified/model_mod.nml @@ -0,0 +1,78 @@ + +# Notes for model_nml: +# (1) vert_localization_coord must be one of: +# 1 = model level +# 2 = pressure +# 3 = height +# (2) see bottom of this file for explanations of polar, periodic_x, +# periodic_y, and scm +# (3) calendar = 3 is GREGORIAN, which is what WRF uses. +# (4) if 'default_state_variables' is .true. the model_mod.f90 code will +# fill the state variable table with the following wrf vars: +# U, V, W, PH, T, MU +# you must set it to false before you change the value +# of 'wrf_state_variables' and have it take effect. +# (5) the format for 'wrf_state_variables' is an array of 5 strings: +# wrf netcdf variable name, dart QTY_xxx string, type string (must be +# unique, will soon be obsolete, we hope), 'UPDATE', and '999' if the +# array is part of all domains. otherwise, it is a string with the domain +# numbers (e.g. '12' for domains 1 and 2, '13' for domains 1 and 3). +# example: +# wrf_state_variables='U','QTY_U_WIND_COMPONENT','TYPE_U','UPDATE','999', +# 'V','QTY_V_WIND_COMPONENT','TYPE_V','UPDATE','999', +# 'W','QTY_VERTICAL_VELOCITY','TYPE_W','UPDATE','999', +# 'T','QTY_POTENTIAL_TEMPERATURE','TYPE_T','UPDATE','999', +# 'PH','QTY_GEOPOTENTIAL_HEIGHT','TYPE_GZ','UPDATE','999', +# 'MU','QTY_PRESSURE','TYPE_MU','UPDATE','999', +# 'QVAPOR','QTY_VAPOR_MIXING_RATIO','TYPE_QV','UPDATE','999', +# 'QCLOUD','QTY_CLOUD_LIQUID_WATER','TYPE_QC','UPDATE','999', +# 'QRAIN','QTY_RAINWATER_MIXING_RATIO','TYPE_QR','UPDATE','999', +# 'U10','QTY_U_WIND_COMPONENT','TYPE_U10','UPDATE','999', +# 'V10','QTY_V_WIND_COMPONENT','TYPE_V10','UPDATE','999', +# 'T2','QTY_TEMPERATURE','TYPE_T2','UPDATE','999', +# 'TH2','QTY_POTENTIAL_TEMPERATURE','TYPE_TH2','UPDATE','999', +# 'Q2','QTY_SPECIFIC_HUMIDITY','TYPE_Q2','UPDATE','999', +# 'PSFC','QTY_PRESSURE','TYPE_PS','UPDATE','999', +# (6) the format for 'wrf_state_bounds' is an array of 4 strings: +# wrf netcdf variable name, minimum value, maximum value, and either +# FAIL or CLAMP. FAIL will halt the program if an out of range value +# is detected. CLAMP will set out of range values to the min or max. +# The special string 'NULL' will map to plus or minus infinity and will +# not change the values. arrays not listed in this table will not +# be changed as they are read or written. + + +&model_nml + default_state_variables = .true. + wrf_state_variables = 'NULL' + wrf_state_bounds = 'NULL' + num_domains = 1 + calendar_type = 3 + assimilation_period_seconds = 21600 + allow_obs_below_vol = .false. + vert_localization_coord = 3 + center_search_half_length = 500000. + center_spline_grid_scale = 10 + circulation_pres_level = 80000.0 + circulation_radius = 108000.0 + sfc_elev_max_diff = -1.0 + polar = .false. + periodic_x = .false. + periodic_y = .false. + scm = .false. + allow_perturbed_ics = .false. + / + +# polar and periodic_x are used in global wrf. if polar is true, the +# grid interpolation routines will wrap over the north & south poles. +# if periodic_x is true, when the east and west edges of the grid are +# reached the interpolation will wrap. note this is a separate issue +# from regional models which cross the GMT line; those grids are marked +# as having a negative offset and do not need to wrap; this flag controls +# what happens when the edges of the grid are reached. + +# the scm flag is used for the 'single column model' version of WRF. +# it needs the periodic_x and periodic_y flags set to true, in which +# case the X and Y directions are periodic; no collapsing of the grid +# into a single location like the 3d-spherical polar flag implies. + diff --git a/models/wrf_unified/module_map_utils.f90 b/models/wrf_unified/module_map_utils.f90 new file mode 100644 index 0000000000..f874e7a9e4 --- /dev/null +++ b/models/wrf_unified/module_map_utils.f90 @@ -0,0 +1,2348 @@ +! This code is not protected by the DART copyright agreement. + +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +! MODULE CONSTANTS_MODULE +! +! This module defines constants that are used by other modules +! +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +module constants_module + + use types_mod, only : r8, digits12 + + real (kind=r8), parameter :: PI = 3.141592653589793_r8 + real (kind=r8), parameter :: OMEGA_E = 0.00007292_r8 ! Angular rotation rate of the earth + + real (kind=r8), parameter :: DEG_PER_RAD = 180.0_r8 / PI + real (kind=r8), parameter :: RAD_PER_DEG = PI / 180.0_r8 + + ! Mean Earth Radius in m. The value below is consistent + ! with NCEP's routines and grids. + real (kind=r8), parameter :: A_WGS84 = 6378137.0_r8 + real (kind=r8), parameter :: B_WGS84 = 6356752.314_r8 + real (kind=r8), parameter :: RE_WGS84 = A_WGS84 + real (kind=r8), parameter :: E_WGS84 = 0.081819192_r8 + + real (kind=r8), parameter :: A_NAD83 = 6378137.0_r8 + real (kind=r8), parameter :: RE_NAD83 = A_NAD83 + real (kind=r8), parameter :: E_NAD83 = 0.0818187034_r8 + + real (kind=r8), parameter :: EARTH_RADIUS_M = 6370000.0_r8 ! same as MM5 system + real (kind=r8), parameter :: EARTH_CIRC_M = 2.0_r8 * PI * EARTH_RADIUS_M + +end module constants_module + + +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +! MODULE MISC_DEFINITIONS_MODULE +! +! This module defines various non-meteorological constants that are used +! by other modules for readability. +! +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +module misc_definitions_module + + use types_mod, only : r8 + + real (kind=r8), parameter :: NAN=1.E20_r8 + + real (kind=r8), parameter :: NOT_MASKED = -2.0_r8, & + MASKED_BOTH = -1.0_r8, & + MASKED_WATER = 0.0_r8, & + MASKED_LAND = 1.0_r8 + + integer, parameter :: OUTSIDE_DOMAIN=1E8, NOT_PROCESSED=1E9, INVALID=1E9 + + integer, parameter :: SIXTEEN_POINT=1, FOUR_POINT=2, N_NEIGHBOR=3, & + AVERAGE4=4, AVERAGE16=5, W_AVERAGE4=6, W_AVERAGE16=7, & + SEARCH=8 + + integer, parameter :: BOTTOM_TOP=1, TOP_BOTTOM=2 + + integer, parameter :: CONTINUOUS=0, CATEGORICAL=1, SP_CONTINUOUS=2 + + integer, parameter :: M=1, U=2, V=3, HH=4, VV=5 + + integer, parameter :: ONETWOONE=1, SMTHDESMTH=2, SMTHDESMTH_SPECIAL=3 + + integer, parameter :: BINARY=1, NETCDF=2, GRIB1=3, HDF=4 + + integer, parameter :: BIG_ENDIAN=0, LITTLE_ENDIAN=1 + + ! Projection codes for proj_info structure: + INTEGER, PUBLIC, PARAMETER :: PROJ_LATLON = 0 + INTEGER, PUBLIC, PARAMETER :: PROJ_LC = 1 + INTEGER, PUBLIC, PARAMETER :: PROJ_PS = 2 + INTEGER, PUBLIC, PARAMETER :: PROJ_PS_WGS84 = 102 + INTEGER, PUBLIC, PARAMETER :: PROJ_MERC = 3 + INTEGER, PUBLIC, PARAMETER :: PROJ_GAUSS = 4 + INTEGER, PUBLIC, PARAMETER :: PROJ_CYL = 5 + INTEGER, PUBLIC, PARAMETER :: PROJ_CASSINI = 6 + INTEGER, PUBLIC, PARAMETER :: PROJ_ALBERS_NAD83 = 105 + INTEGER, PUBLIC, PARAMETER :: PROJ_ROTLL = 203 + +end module misc_definitions_module + + +MODULE map_utils + +! Module that defines constants, data structures, and +! subroutines used to convert grid indices to lat/lon +! and vice versa. +! +! SUPPORTED PROJECTIONS +! --------------------- +! Cylindrical Lat/Lon (code = PROJ_LATLON) +! Mercator (code = PROJ_MERC) +! Lambert Conformal (code = PROJ_LC) +! Gaussian (code = PROJ_GAUSS) +! Polar Stereographic (code = PROJ_PS) +! Rotated Lat/Lon (code = PROJ_ROTLL) +! +! REMARKS +! ------- +! The routines contained within were adapted from routines +! obtained from NCEP's w3 library. The original NCEP routines were less +! flexible (e.g., polar-stereo routines only supported truelat of 60N/60S) +! than what we needed, so modifications based on equations in Hoke, Hayes, and +! Renninger (AFGWC/TN/79-003) were added to improve the flexibility. +! Additionally, coding was improved to F90 standards and the routines were +! combined into this module. +! +! ASSUMPTIONS +! ----------- +! Grid Definition: +! For mercator, lambert conformal, and polar-stereographic projections, +! the routines within assume the following: +! +! 1. Grid is dimensioned (i,j) where i is the East-West direction, +! positive toward the east, and j is the north-south direction, +! positive toward the north. +! 2. Origin is at (1,1) and is located at the southwest corner, +! regardless of hemispere. +! 3. Grid spacing (dx) is always positive. +! 4. Values of true latitudes must be positive for NH domains +! and negative for SH domains. +! +! For the latlon and Gaussian projection, the grid origin may be at any +! of the corners, and the deltalat and deltalon values can be signed to +! account for this using the following convention: +! Origin Location Deltalat Sign Deltalon Sign +! --------------- ------------- ------------- +! SW Corner + + +! NE Corner - - +! NW Corner - + +! SE Corner + - +! +! Data Definitions: +! 1. Any arguments that are a latitude value are expressed in +! degrees north with a valid range of -90 -> 90 +! 2. Any arguments that are a longitude value are expressed in +! degrees east with a valid range of -180 -> 180. +! 3. Distances are in meters and are always positive. +! 4. The standard longitude (stdlon) is defined as the longitude +! line which is parallel to the grid's y-axis (j-direction), along +! which latitude increases (NOT the absolute value of latitude, but +! the actual latitude, such that latitude increases continuously +! from the south pole to the north pole) as j increases. +! 5. One true latitude value is required for polar-stereographic and +! mercator projections, and defines at which latitude the +! grid spacing is true. For lambert conformal, two true latitude +! values must be specified, but may be set equal to each other to +! specify a tangent projection instead of a secant projection. +! +! USAGE +! ----- +! To use the routines in this module, the calling routines must have the +! following statement at the beginning of its declaration block: +! USE map_utils +! +! The use of the module not only provides access to the necessary routines, +! but also defines a structure of TYPE (proj_info) that can be used +! to declare a variable of the same type to hold your map projection +! information. It also defines some integer parameters that contain +! the projection codes so one only has to use those variable names rather +! than remembering the acutal code when using them. The basic steps are +! as follows: +! +! 1. Ensure the "USE map_utils" is in your declarations. +! 2. Declare the projection information structure as type(proj_info): +! TYPE(proj_info) :: proj +! 3. Populate your structure by calling the map_set routine: +! CALL map_set(code,lat1,lon1,knowni,knownj,dx,stdlon,truelat1,truelat2,proj) +! where: +! code (input) = one of PROJ_LATLON, PROJ_MERC, PROJ_LC, PROJ_PS, +! PROJ_GAUSS, or PROJ_ROTLL +! lat1 (input) = Latitude of grid origin point (i,j)=(1,1) +! (see assumptions!) +! lon1 (input) = Longitude of grid origin +! knowni (input) = origin point, x-location +! knownj (input) = origin point, y-location +! dx (input) = grid spacing in meters (ignored for LATLON projections) +! stdlon (input) = Standard longitude for PROJ_PS and PROJ_LC, +! deltalon (see assumptions) for PROJ_LATLON, +! ignored for PROJ_MERC +! truelat1 (input) = 1st true latitude for PROJ_PS, PROJ_LC, and +! PROJ_MERC, deltalat (see assumptions) for PROJ_LATLON +! truelat2 (input) = 2nd true latitude for PROJ_LC, +! ignored for all others. +! proj (output) = The structure of type (proj_info) that will be fully +! populated after this call +! +! 4. Now that the proj structure is populated, you may call either +! of the following routines: +! +! latlon_to_ij(proj, lat, lon, i, j) +! ij_to_latlon(proj, i, j, lat, lon) +! +! It is incumbent upon the calling routine to determine whether or +! not the values returned are within your domain's bounds. All values +! of i, j, lat, and lon are REAL values. +! +! +! REFERENCES +! ---------- +! Hoke, Hayes, and Renninger, "Map Projections and Grid Systems for +! Meteorological Applications." AFGWC/TN-79/003(Rev), Air Weather +! Service, 1985. +! +! NCAR MM5v3 Modeling System, REGRIDDER program, module_first_guess_map.F +! NCEP routines w3fb06, w3fb07, w3fb08, w3fb09, w3fb11, w3fb12 +! +! HISTORY +! ------- +! 27 Mar 2001 - Original Version +! Brent L. Shaw, NOAA/FSL (CSU/CIRA) +! +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + ! other modules included in this .f90 file + use constants_module + use misc_definitions_module + + ! external modules + use types_mod, only : r8 +! use utilities_mod, only : register_module + + ! Define some private constants + INTEGER, PRIVATE, PARAMETER :: HIGH = digits12 + + TYPE proj_info + + INTEGER :: code ! Integer code for projection TYPE + INTEGER :: nlat ! For Gaussian -- number of latitude points + ! north of the equator + INTEGER :: nlon ! + ! + INTEGER :: ixdim ! For Rotated Lat/Lon -- number of mass points + ! in an odd row + INTEGER :: jydim ! For Rotated Lat/Lon -- number of rows + INTEGER :: stagger ! For Rotated Lat/Lon -- mass or velocity grid + REAL(r8) :: phi ! For Rotated Lat/Lon -- domain half-extent in + ! degrees latitude + REAL(r8) :: lambda ! For Rotated Lat/Lon -- domain half-extend in + ! degrees longitude + REAL(r8) :: lat1 ! SW latitude (1,1) in degrees (-90->90N) + REAL(r8) :: lon1 ! SW longitude (1,1) in degrees (-180->180E) + REAL(r8) :: lat0 ! For Cassini, latitude of projection pole + REAL(r8) :: lon0 ! For Cassini, longitude of projection pole + REAL(r8) :: dx ! Grid spacing in meters at truelats, used + ! only for ps, lc, and merc projections + REAL(r8) :: dy ! Grid spacing in meters at truelats, used + ! only for ps, lc, and merc projections + REAL(r8) :: latinc ! Latitude increment for cylindrical lat/lon + REAL(r8) :: loninc ! Longitude increment for cylindrical lat/lon + ! also the lon increment for Gaussian grid + REAL(r8) :: dlat ! Lat increment for lat/lon grids + REAL(r8) :: dlon ! Lon increment for lat/lon grids + REAL(r8) :: stdlon ! Longitude parallel to y-axis (-180->180E) + REAL(r8) :: truelat1 ! First true latitude (all projections) + REAL(r8) :: truelat2 ! Second true lat (LC only) + REAL(r8) :: hemi ! 1 for NH, -1 for SH + REAL(r8) :: cone ! Cone factor for LC projections + REAL(r8) :: polei ! Computed i-location of pole point + REAL(r8) :: polej ! Computed j-location of pole point + REAL(r8) :: rsw ! Computed radius to SW corner + REAL(r8) :: rebydx ! Earth radius divided by dx + REAL(r8) :: knowni ! X-location of known lat/lon + REAL(r8) :: knownj ! Y-location of known lat/lon + REAL(r8) :: re_m ! Radius of spherical earth, meters + REAL(r8) :: rho0 ! For Albers equal area + REAL(r8) :: nc ! For Albers equal area + REAL(r8) :: bigc ! For Albers equal area + LOGICAL :: init ! Flag to indicate if this struct is + ! ready for use + LOGICAL :: wrap ! For Gaussian -- flag to indicate wrapping + ! around globe? + REAL(r8), POINTER, DIMENSION(:) :: gauss_lat ! Latitude array for Gaussian grid + + END TYPE proj_info + + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + CONTAINS + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + SUBROUTINE map_init(proj) + ! Initializes the map projection structure to missing values + + IMPLICIT NONE + TYPE(proj_info), INTENT(INOUT) :: proj + + proj%lat1 = -999.9_r8 + proj%lon1 = -999.9_r8 + proj%lat0 = -999.9_r8 + proj%lon0 = -999.9_r8 + proj%dx = -999.9_r8 + proj%dy = -999.9_r8 + proj%latinc = -999.9_r8 + proj%loninc = -999.9_r8 + proj%stdlon = -999.9_r8 + proj%truelat1 = -999.9_r8 + proj%truelat2 = -999.9_r8 + proj%phi = -999.9_r8 + proj%lambda = -999.9_r8 + proj%ixdim = -999 + proj%jydim = -999 + proj%stagger = HH + proj%nlat = 0 + proj%nlon = 0 + proj%hemi = 0.0_r8 + proj%cone = -999.9_r8 + proj%polei = -999.9_r8 + proj%polej = -999.9_r8 + proj%rsw = -999.9_r8 + proj%knowni = -999.9_r8 + proj%knownj = -999.9_r8 + proj%re_m = EARTH_RADIUS_M + proj%init = .FALSE. + proj%wrap = .FALSE. + proj%rho0 = 0.0_r8 + proj%nc = 0.0_r8 + proj%bigc = 0.0_r8 + nullify(proj%gauss_lat) + + END SUBROUTINE map_init + + +!nc -- Global WRF assumes proj_code = PROJ_CASSINI (=6 in misc_definitions_module) + + SUBROUTINE map_set(proj_code, proj, lat1, lon1, lat0, lon0, knowni, knownj, dx, latinc, & + loninc, stdlon, truelat1, truelat2, nlat, nlon, ixdim, jydim, & + stagger, phi, lambda, r_earth) + ! Given a partially filled proj_info structure, this routine computes + ! polei, polej, rsw, and cone (if LC projection) to complete the + ! structure. This allows us to eliminate redundant calculations when + ! calling the coordinate conversion routines multiple times for the + ! same map. + ! This will generally be the first routine called when a user wants + ! to be able to use the coordinate conversion routines, and it + ! will call the appropriate subroutines based on the + ! proj%code which indicates which projection type this is. + + IMPLICIT NONE + + ! Declare arguments + INTEGER, INTENT(IN) :: proj_code + INTEGER, INTENT(IN), OPTIONAL :: nlat + INTEGER, INTENT(IN), OPTIONAL :: nlon + INTEGER, INTENT(IN), OPTIONAL :: ixdim + INTEGER, INTENT(IN), OPTIONAL :: jydim + INTEGER, INTENT(IN), OPTIONAL :: stagger + REAL(r8), INTENT(IN), OPTIONAL :: latinc + REAL(r8), INTENT(IN), OPTIONAL :: loninc + REAL(r8), INTENT(IN), OPTIONAL :: lat1 + REAL(r8), INTENT(IN), OPTIONAL :: lon1 + REAL(r8), INTENT(IN), OPTIONAL :: lat0 + REAL(r8), INTENT(IN), OPTIONAL :: lon0 + REAL(r8), INTENT(IN), OPTIONAL :: dx + REAL(r8), INTENT(IN), OPTIONAL :: stdlon + REAL(r8), INTENT(IN), OPTIONAL :: truelat1 + REAL(r8), INTENT(IN), OPTIONAL :: truelat2 + REAL(r8), INTENT(IN), OPTIONAL :: knowni + REAL(r8), INTENT(IN), OPTIONAL :: knownj + REAL(r8), INTENT(IN), OPTIONAL :: phi + REAL(r8), INTENT(IN), OPTIONAL :: lambda + REAL(r8), INTENT(IN), OPTIONAL :: r_earth + TYPE(proj_info), INTENT(OUT) :: proj + + INTEGER :: iter + REAL(r8) :: dummy_lon1 + REAL(r8) :: dummy_lon0 + REAL(r8) :: dummy_stdlon + + ! First, verify that mandatory parameters are present for the specified proj_code + IF ( proj_code == PROJ_LC ) THEN + IF ( .NOT.PRESENT(truelat1) .OR. & + .NOT.PRESENT(truelat2) .OR. & + .NOT.PRESENT(lat1) .OR. & + .NOT.PRESENT(lon1) .OR. & + .NOT.PRESENT(knowni) .OR. & + .NOT.PRESENT(knownj) .OR. & + .NOT.PRESENT(stdlon) .OR. & + .NOT.PRESENT(dx) ) THEN + PRINT '(A,I2)', 'The following are mandatory parameters for projection code : ', proj_code + PRINT '(A)', ' truelat1, truelat2, lat1, lon1, knowni, knownj, stdlon, dx' + write(6,*) 'ERROR: MAP_INIT' + END IF + ELSE IF ( proj_code == PROJ_PS ) THEN + IF ( .NOT.PRESENT(truelat1) .OR. & + .NOT.PRESENT(lat1) .OR. & + .NOT.PRESENT(lon1) .OR. & + .NOT.PRESENT(knowni) .OR. & + .NOT.PRESENT(knownj) .OR. & + .NOT.PRESENT(stdlon) .OR. & + .NOT.PRESENT(dx) ) THEN + PRINT '(A,I2)', 'The following are mandatory parameters for projection code : ', proj_code + PRINT '(A)', ' truelat1, lat1, lon1, knonwi, knownj, stdlon, dx' + write(6,*) 'ERROR: MAP_INIT' + END IF + ELSE IF ( proj_code == PROJ_PS_WGS84 ) THEN + IF ( .NOT.PRESENT(truelat1) .OR. & + .NOT.PRESENT(lat1) .OR. & + .NOT.PRESENT(lon1) .OR. & + .NOT.PRESENT(knowni) .OR. & + .NOT.PRESENT(knownj) .OR. & + .NOT.PRESENT(stdlon) .OR. & + .NOT.PRESENT(dx) ) THEN + PRINT '(A,I2)', 'The following are mandatory parameters for projection code : ', proj_code + PRINT '(A)', ' truelat1, lat1, lon1, knonwi, knownj, stdlon, dx' + write(6,*) 'ERROR: MAP_INIT' + END IF + ELSE IF ( proj_code == PROJ_ALBERS_NAD83 ) THEN + IF ( .NOT.PRESENT(truelat1) .OR. & + .NOT.PRESENT(truelat2) .OR. & + .NOT.PRESENT(lat1) .OR. & + .NOT.PRESENT(lon1) .OR. & + .NOT.PRESENT(knowni) .OR. & + .NOT.PRESENT(knownj) .OR. & + .NOT.PRESENT(stdlon) .OR. & + .NOT.PRESENT(dx) ) THEN + PRINT '(A,I2)', 'The following are mandatory parameters for projection code : ', proj_code + PRINT '(A)', ' truelat1, truelat2, lat1, lon1, knonwi, knownj, stdlon, dx' + write(6,*) 'ERROR: MAP_INIT' + END IF + ELSE IF ( proj_code == PROJ_MERC ) THEN + IF ( .NOT.PRESENT(truelat1) .OR. & + .NOT.PRESENT(lat1) .OR. & + .NOT.PRESENT(lon1) .OR. & + .NOT.PRESENT(knowni) .OR. & + .NOT.PRESENT(knownj) .OR. & + .NOT.PRESENT(dx) ) THEN + PRINT '(A,I2)', 'The following are mandatory parameters for projection code : ', proj_code + PRINT '(A)', ' truelat1, lat1, lon1, knowni, knownj, dx' + write(6,*) 'ERROR: MAP_INIT' + END IF + ELSE IF ( proj_code == PROJ_LATLON ) THEN + IF ( .NOT.PRESENT(latinc) .OR. & + .NOT.PRESENT(loninc) .OR. & + .NOT.PRESENT(knowni) .OR. & + .NOT.PRESENT(knownj) .OR. & + .NOT.PRESENT(lat1) .OR. & + .NOT.PRESENT(lon1) ) THEN + PRINT '(A,I2)', 'The following are mandatory parameters for projection code : ', proj_code + PRINT '(A)', ' latinc, loninc, knowni, knownj, lat1, lon1' + write(6,*) 'ERROR: MAP_INIT' + END IF + ELSE IF ( proj_code == PROJ_CYL ) THEN + IF ( .NOT.PRESENT(latinc) .OR. & + .NOT.PRESENT(loninc) .OR. & + .NOT.PRESENT(stdlon) ) THEN + PRINT '(A,I2)', 'The following are mandatory parameters for projection code : ', proj_code + PRINT '(A)', ' latinc, loninc, stdlon' + write(6,*) 'ERROR: MAP_INIT' + END IF + ELSE IF ( proj_code == PROJ_CASSINI ) THEN + IF ( .NOT.PRESENT(latinc) .OR. & + .NOT.PRESENT(loninc) .OR. & + .NOT.PRESENT(lat1) .OR. & + .NOT.PRESENT(lon1) .OR. & + .NOT.PRESENT(lat0) .OR. & + .NOT.PRESENT(lon0) .OR. & + .NOT.PRESENT(knowni) .OR. & + .NOT.PRESENT(knownj) .OR. & + .NOT.PRESENT(stdlon) ) THEN + PRINT '(A,I2)', 'The following are mandatory parameters for projection code : ', proj_code + PRINT '(A)', ' latinc, loninc, lat1, lon1, knowni, knownj, lat0, lon0, stdlon' + write(6,*) 'ERROR: MAP_INIT' + END IF + ELSE IF ( proj_code == PROJ_GAUSS ) THEN + IF ( .NOT.PRESENT(nlat) .OR. & + .NOT.PRESENT(lat1) .OR. & + .NOT.PRESENT(lon1) .OR. & + .NOT.PRESENT(loninc) ) THEN + PRINT '(A,I2)', 'The following are mandatory parameters for projection code : ', proj_code + PRINT '(A)', ' nlat, lat1, lon1, loninc' + write(6,*) 'ERROR: MAP_INIT' + END IF + ELSE IF ( proj_code == PROJ_ROTLL ) THEN + IF ( .NOT.PRESENT(ixdim) .OR. & + .NOT.PRESENT(jydim) .OR. & + .NOT.PRESENT(phi) .OR. & + .NOT.PRESENT(lambda) .OR. & + .NOT.PRESENT(lat1) .OR. & + .NOT.PRESENT(lon1) .OR. & + .NOT.PRESENT(stagger) ) THEN + PRINT '(A,I2)', 'The following are mandatory parameters for projection code : ', proj_code + PRINT '(A)', ' ixdim, jydim, phi, lambda, lat1, lon1, stagger' + write(6,*) 'ERROR: MAP_INIT' + END IF + ELSE + PRINT '(A,I2)', 'Unknown projection code: ', proj_code + write(6,*) 'ERROR: MAP_INIT' + END IF + + ! Check for validity of mandatory variables in proj + IF ( PRESENT(lat1) ) THEN + IF ( ABS(lat1) .GT. 90.0_r8 ) THEN + PRINT '(A)', 'Latitude of origin corner required as follows:' + PRINT '(A)', ' -90N <= lat1 < = 90.N' + write(6,*) 'ERROR: MAP_INIT' + ENDIF + ENDIF + + IF ( PRESENT(lon1) ) THEN + dummy_lon1 = lon1 + IF ( ABS(dummy_lon1) .GT. 180.0_r8 ) THEN + iter = 0 + DO WHILE (ABS(dummy_lon1) > 180.0_r8 .AND. iter < 10) + IF (dummy_lon1 < -180.0_r8) dummy_lon1 = dummy_lon1 + 360.0_r8 + IF (dummy_lon1 > 180.0_r8) dummy_lon1 = dummy_lon1 - 360.0_r8 + iter = iter + 1 + END DO + IF (abs(dummy_lon1) > 180.0_r8) THEN + PRINT '(A)', 'Longitude of origin required as follows:' + PRINT '(A)', ' -180E <= lon1 <= 180W' + write(6,*) 'ERROR: MAP_INIT' + ENDIF + ENDIF + ENDIF + + IF ( PRESENT(lon0) ) THEN + dummy_lon0 = lon0 + IF ( ABS(dummy_lon0) .GT. 180.0_r8) THEN + iter = 0 + DO WHILE (ABS(dummy_lon0) > 180.0_r8 .AND. iter < 10) + IF (dummy_lon0 < -180.0_r8) dummy_lon0 = dummy_lon0 + 360.0_r8 + IF (dummy_lon0 > 180.0_r8) dummy_lon0 = dummy_lon0 - 360.0_r8 + iter = iter + 1 + END DO + IF (abs(dummy_lon0) > 180.0_r8) THEN + PRINT '(A)', 'Longitude of pole required as follows:' + PRINT '(A)', ' -180E <= lon0 <= 180W' + write(6,*) 'ERROR: MAP_INIT' + ENDIF + ENDIF + ENDIF + + IF ( PRESENT(dx) ) THEN + IF ((dx .LE. 0.).AND.(proj_code .NE. PROJ_LATLON)) THEN + PRINT '(A)', 'Require grid spacing (dx) in meters be positive!' + write(6,*) 'ERROR: MAP_INIT' + ENDIF + ENDIF + + IF ( PRESENT(stdlon) ) THEN + dummy_stdlon = stdlon + IF ((ABS(dummy_stdlon) > 180.0_r8).AND.(proj_code /= PROJ_MERC)) THEN + iter = 0 + DO WHILE (ABS(dummy_stdlon) > 180.0_r8 .AND. iter < 10) + IF (dummy_stdlon < -180.0_r8) dummy_stdlon = dummy_stdlon + 360.0_r8 + IF (dummy_stdlon > 180.0_r8) dummy_stdlon = dummy_stdlon - 360.0_r8 + iter = iter + 1 + END DO + IF (abs(dummy_stdlon) > 180.0_r8) THEN + PRINT '(A)', 'Need orientation longitude (stdlon) as: ' + PRINT '(A)', ' -180E <= stdlon <= 180W' + write(6,*) 'ERROR: MAP_INIT' + ENDIF + ENDIF + ENDIF + + IF ( PRESENT(truelat1) ) THEN + IF (ABS(truelat1).GT.90.0_r8) THEN + PRINT '(A)', 'Set true latitude 1 for all projections!' + write(6,*) 'ERROR: MAP_INIT' + ENDIF + ENDIF + + CALL map_init(proj) + proj%code = proj_code + IF ( PRESENT(lat1) ) proj%lat1 = lat1 + IF ( PRESENT(lon1) ) proj%lon1 = dummy_lon1 + IF ( PRESENT(lat0) ) proj%lat0 = lat0 + IF ( PRESENT(lon0) ) proj%lon0 = dummy_lon0 + IF ( PRESENT(latinc) ) proj%latinc = latinc + IF ( PRESENT(loninc) ) proj%loninc = loninc + IF ( PRESENT(knowni) ) proj%knowni = knowni + IF ( PRESENT(knownj) ) proj%knownj = knownj + IF ( PRESENT(dx) ) proj%dx = dx + IF ( PRESENT(stdlon) ) proj%stdlon = dummy_stdlon + IF ( PRESENT(truelat1) ) proj%truelat1 = truelat1 + IF ( PRESENT(truelat2) ) proj%truelat2 = truelat2 + IF ( PRESENT(nlat) ) proj%nlat = nlat + IF ( PRESENT(nlon) ) proj%nlon = nlon + IF ( PRESENT(ixdim) ) proj%ixdim = ixdim + IF ( PRESENT(jydim) ) proj%jydim = jydim + IF ( PRESENT(stagger) ) proj%stagger = stagger + IF ( PRESENT(phi) ) proj%phi = phi + IF ( PRESENT(lambda) ) proj%lambda = lambda + IF ( PRESENT(r_earth) ) proj%re_m = r_earth + + IF ( PRESENT(dx) ) THEN + IF ( (proj_code == PROJ_LC) .OR. (proj_code == PROJ_PS) .OR. & + (proj_code == PROJ_PS_WGS84) .OR. (proj_code == PROJ_ALBERS_NAD83) .OR. & + (proj_code == PROJ_MERC) ) THEN + proj%dx = dx + IF (truelat1 .LT. 0.0_r8) THEN + proj%hemi = -1.0_r8 + ELSE + proj%hemi = 1.0_r8 + ENDIF + proj%rebydx = proj%re_m / dx + ENDIF + ENDIF + + pick_proj: SELECT CASE(proj%code) + + CASE(PROJ_PS) + CALL set_ps(proj) + + CASE(PROJ_PS_WGS84) + CALL set_ps_wgs84(proj) + + CASE(PROJ_ALBERS_NAD83) + CALL set_albers_nad83(proj) + + CASE(PROJ_LC) + IF (ABS(proj%truelat2) .GT. 90.0_r8) THEN + proj%truelat2=proj%truelat1 + ENDIF + CALL set_lc(proj) + + CASE (PROJ_MERC) + CALL set_merc(proj) + + CASE (PROJ_LATLON) + + CASE (PROJ_GAUSS) + CALL set_gauss(proj) + + CASE (PROJ_CYL) + CALL set_cyl(proj) + + CASE (PROJ_CASSINI) + CALL set_cassini(proj) + + CASE (PROJ_ROTLL) + + END SELECT pick_proj + proj%init = .TRUE. + + RETURN + + END SUBROUTINE map_set + + + SUBROUTINE latlon_to_ij(proj, lat, lon, i, j) + ! Converts input lat/lon values to the cartesian (i,j) value + ! for the given projection. + + IMPLICIT NONE + TYPE(proj_info), INTENT(IN) :: proj + REAL(r8), INTENT(IN) :: lat + REAL(r8), INTENT(IN) :: lon + REAL(r8), INTENT(OUT) :: i + REAL(r8), INTENT(OUT) :: j + + IF (.NOT.proj%init) THEN + PRINT '(A)', 'You have not called map_set for this projection!' + write(6,*) 'ERROR: LATLON_TO_IJ' + ENDIF + + SELECT CASE(proj%code) + + CASE(PROJ_LATLON) + CALL llij_latlon(lat,lon,proj,i,j) + + CASE(PROJ_MERC) + CALL llij_merc(lat,lon,proj,i,j) + + CASE(PROJ_PS) + CALL llij_ps(lat,lon,proj,i,j) + + CASE(PROJ_PS_WGS84) + CALL llij_ps_wgs84(lat,lon,proj,i,j) + + CASE(PROJ_ALBERS_NAD83) + CALL llij_albers_nad83(lat,lon,proj,i,j) + + CASE(PROJ_LC) + CALL llij_lc(lat,lon,proj,i,j) + + CASE(PROJ_GAUSS) + CALL llij_gauss(lat,lon,proj,i,j) + + CASE(PROJ_CYL) + CALL llij_cyl(lat,lon,proj,i,j) + + CASE(PROJ_CASSINI) + CALL llij_cassini(lat,lon,proj,i,j) + + CASE(PROJ_ROTLL) + CALL llij_rotlatlon(lat,lon,proj,i,j) + + CASE DEFAULT + PRINT '(A,I2)', 'Unrecognized map projection code: ', proj%code + write(6,*) 'ERROR: LATLON_TO_IJ' + + END SELECT + + RETURN + + END SUBROUTINE latlon_to_ij + + + SUBROUTINE ij_to_latlon(proj, i, j, lat, lon) + ! Computes geographical latitude and longitude for a given (i,j) point + ! in a grid with a projection of proj + + IMPLICIT NONE + TYPE(proj_info),INTENT(IN) :: proj + REAL(r8), INTENT(IN) :: i + REAL(r8), INTENT(IN) :: j + REAL(r8), INTENT(OUT) :: lat + REAL(r8), INTENT(OUT) :: lon + + IF (.NOT.proj%init) THEN + PRINT '(A)', 'You have not called map_set for this projection!' + write(6,*) 'ERROR: IJ_TO_LATLON' + ENDIF + SELECT CASE (proj%code) + + CASE (PROJ_LATLON) + CALL ijll_latlon(i, j, proj, lat, lon) + + CASE (PROJ_MERC) + CALL ijll_merc(i, j, proj, lat, lon) + + CASE (PROJ_PS) + CALL ijll_ps(i, j, proj, lat, lon) + + CASE (PROJ_PS_WGS84) + CALL ijll_ps_wgs84(i, j, proj, lat, lon) + + CASE (PROJ_ALBERS_NAD83) + CALL ijll_albers_nad83(i, j, proj, lat, lon) + + CASE (PROJ_LC) + CALL ijll_lc(i, j, proj, lat, lon) + + CASE (PROJ_CYL) + CALL ijll_cyl(i, j, proj, lat, lon) + + CASE (PROJ_CASSINI) + CALL ijll_cassini(i, j, proj, lat, lon) + + CASE (PROJ_ROTLL) + CALL ijll_rotlatlon(i, j, proj, lat, lon) + + CASE DEFAULT + PRINT '(A,I2)', 'Unrecognized map projection code: ', proj%code + write(6,*) 'ERROR: IJ_TO_LATLON' + + END SELECT + RETURN + END SUBROUTINE ij_to_latlon + + + SUBROUTINE set_ps(proj) + ! Initializes a polar-stereographic map projection from the partially + ! filled proj structure. This routine computes the radius to the + ! southwest corner and computes the i/j location of the pole for use + ! in llij_ps and ijll_ps. + IMPLICIT NONE + + ! Declare args + TYPE(proj_info), INTENT(INOUT) :: proj + + ! Local vars + REAL(r8) :: ala1 + REAL(r8) :: alo1 + REAL(r8) :: reflon + REAL(r8) :: scale_top + + ! Executable code + + ! Thanks to Kevin Manning for the 'cone' fix. It must be set to 1.0 + ! to fix wind rotation for polar stereographic projection + reflon = proj%stdlon + 90.0_r8 + proj%cone = 1.0_r8 + + ! Compute numerator term of map scale factor + scale_top = 1.0_r8 + proj%hemi * SIN(proj%truelat1 * rad_per_deg) + + ! Compute radius to lower-left (SW) corner + ala1 = proj%lat1 * rad_per_deg + proj%rsw = proj%rebydx*COS(ala1)*scale_top/(1.0_r8+proj%hemi*SIN(ala1)) + + ! Find the pole point + alo1 = (proj%lon1 - reflon) * rad_per_deg + proj%polei = proj%knowni - proj%rsw * COS(alo1) + proj%polej = proj%knownj - proj%hemi * proj%rsw * SIN(alo1) + + RETURN + + END SUBROUTINE set_ps + + + SUBROUTINE llij_ps(lat,lon,proj,i,j) + ! Given latitude (-90 to 90), longitude (-180 to 180), and the + ! standard polar-stereographic projection information via the + ! public proj structure, this routine returns the i/j indices which + ! if within the domain range from 1->nx and 1->ny, respectively. + + IMPLICIT NONE + + ! Delcare input arguments + REAL(r8), INTENT(IN) :: lat + REAL(r8), INTENT(IN) :: lon + TYPE(proj_info),INTENT(IN) :: proj + + ! Declare output arguments + REAL(r8), INTENT(OUT) :: i !(x-index) + REAL(r8), INTENT(OUT) :: j !(y-index) + + ! Declare local variables + + REAL(r8) :: reflon + REAL(r8) :: scale_top + REAL(r8) :: ala + REAL(r8) :: alo + REAL(r8) :: rm + + ! BEGIN CODE + + reflon = proj%stdlon + 90.0_r8 + + ! Compute numerator term of map scale factor + + scale_top = 1.0_r8 + proj%hemi * SIN(proj%truelat1 * rad_per_deg) + + ! Find radius to desired point + ala = lat * rad_per_deg + rm = proj%rebydx * COS(ala) * scale_top/(1.0_r8 + proj%hemi *SIN(ala)) + alo = (lon - reflon) * rad_per_deg + i = proj%polei + rm * COS(alo) + j = proj%polej + proj%hemi * rm * SIN(alo) + + RETURN + + END SUBROUTINE llij_ps + + + SUBROUTINE ijll_ps(i, j, proj, lat, lon) + + ! This is the inverse subroutine of llij_ps. It returns the + ! latitude and longitude of an i/j point given the projection info + ! structure. + + IMPLICIT NONE + + ! Declare input arguments + REAL(r8), INTENT(IN) :: i ! Column + REAL(r8), INTENT(IN) :: j ! Row + TYPE (proj_info), INTENT(IN) :: proj + + ! Declare output arguments + REAL(r8), INTENT(OUT) :: lat ! -90 -> 90 north + REAL(r8), INTENT(OUT) :: lon ! -180 -> 180 East + + ! Local variables + REAL(r8) :: reflon + REAL(r8) :: scale_top + REAL(r8) :: xx,yy + REAL(r8) :: gi2, r2 + REAL(r8) :: arccos + + ! Begin Code + + ! Compute the reference longitude by rotating 90 degrees to the east + ! to find the longitude line parallel to the positive x-axis. + reflon = proj%stdlon + 90.0_r8 + + ! Compute numerator term of map scale factor + scale_top = 1.0_r8 + proj%hemi * SIN(proj%truelat1 * rad_per_deg) + + ! Compute radius to point of interest + xx = i - proj%polei + yy = (j - proj%polej) * proj%hemi + r2 = xx**2 + yy**2 + + ! Now the magic code + IF (r2 .EQ. 0.0_r8) THEN + lat = proj%hemi * 90.0_r8 + lon = reflon + ELSE + gi2 = (proj%rebydx * scale_top)**2. + lat = deg_per_rad * proj%hemi * ASIN((gi2-r2)/(gi2+r2)) + arccos = ACOS(xx/SQRT(r2)) + IF (yy .GT. 0.0_r8) THEN + lon = reflon + deg_per_rad * arccos + ELSE + lon = reflon - deg_per_rad * arccos + ENDIF + ENDIF + + ! Convert to a -180 -> 180 East convention + IF (lon .GT. 180.0_r8) lon = lon - 360.0_r8 + IF (lon .LT. -180.0_r8) lon = lon + 360.0_r8 + + RETURN + + END SUBROUTINE ijll_ps + + + SUBROUTINE set_ps_wgs84(proj) + ! Initializes a polar-stereographic map projection (WGS84 ellipsoid) + ! from the partially filled proj structure. This routine computes the + ! radius to the southwest corner and computes the i/j location of the + ! pole for use in llij_ps and ijll_ps. + + IMPLICIT NONE + + ! Arguments + TYPE(proj_info), INTENT(INOUT) :: proj + + ! Local variables + real(r8) :: h, mc, tc, t, rho + + h = proj%hemi + + mc = cos(h*proj%truelat1*rad_per_deg)/sqrt(1.0_r8-(E_WGS84*sin(h*proj%truelat1*rad_per_deg))**2.0) + tc = sqrt(((1.0_r8-sin(h*proj%truelat1*rad_per_deg))/(1.0_r8+sin(h*proj%truelat1*rad_per_deg)))* & + (((1.0_r8+E_WGS84*sin(h*proj%truelat1*rad_per_deg))/(1.0_r8 - & + E_WGS84*sin(h*proj%truelat1*rad_per_deg)))**E_WGS84 )) + + ! Find the i/j location of reference lat/lon with respect to the pole of the projection + t = sqrt(((1.0_r8-sin(h*proj%lat1*rad_per_deg))/(1.0_r8+sin(h*proj%lat1*rad_per_deg)))* & + (((1.0_r8+E_WGS84*sin(h*proj%lat1*rad_per_deg))/(1.0_r8 - & + E_WGS84*sin(h*proj%lat1*rad_per_deg)) )**E_WGS84 ) ) + rho = h * (A_WGS84 / proj%dx) * mc * t / tc + proj%polei = rho * sin((h*proj%lon1 - h*proj%stdlon)*rad_per_deg) + proj%polej = -rho * cos((h*proj%lon1 - h*proj%stdlon)*rad_per_deg) + + RETURN + + END SUBROUTINE set_ps_wgs84 + + + SUBROUTINE llij_ps_wgs84(lat,lon,proj,i,j) + ! Given latitude (-90 to 90), longitude (-180 to 180), and the + ! standard polar-stereographic projection information via the + ! public proj structure, this routine returns the i/j indices which + ! if within the domain range from 1->nx and 1->ny, respectively. + + IMPLICIT NONE + + ! Arguments + REAL(r8), INTENT(IN) :: lat + REAL(r8), INTENT(IN) :: lon + REAL(r8), INTENT(OUT) :: i !(x-index) + REAL(r8), INTENT(OUT) :: j !(y-index) + TYPE(proj_info),INTENT(IN) :: proj + + ! Local variables + real(r8) :: h, mc, tc, t, rho + + h = proj%hemi + + mc = cos(h*proj%truelat1*rad_per_deg)/sqrt(1.0_r8-(E_WGS84*sin(h*proj%truelat1*rad_per_deg))**2.0) + tc = sqrt(((1.0_r8-sin(h*proj%truelat1*rad_per_deg))/(1.0_r8+sin(h*proj%truelat1*rad_per_deg)))* & + (((1.0_r8+E_WGS84*sin(h*proj%truelat1*rad_per_deg))/(1.0_r8 - & + E_WGS84*sin(h*proj%truelat1*rad_per_deg)))**E_WGS84 )) + + t = sqrt(((1.0_r8-sin(h*lat*rad_per_deg))/(1.0_r8+sin(h*lat*rad_per_deg))) * & + (((1.0_r8+E_WGS84*sin(h*lat*rad_per_deg))/(1.0_r8 - & + E_WGS84*sin(h*lat*rad_per_deg)))**E_WGS84)) + + ! Find the x/y location of the requested lat/lon with respect to the pole of the projection + rho = (A_WGS84 / proj%dx) * mc * t / tc + i = h * rho * sin((h*lon - h*proj%stdlon)*rad_per_deg) + j = h *(-rho)* cos((h*lon - h*proj%stdlon)*rad_per_deg) + + ! Get i/j relative to reference i/j + i = proj%knowni + (i - proj%polei) + j = proj%knownj + (j - proj%polej) + + RETURN + + END SUBROUTINE llij_ps_wgs84 + + + SUBROUTINE ijll_ps_wgs84(i, j, proj, lat, lon) + + ! This is the inverse subroutine of llij_ps. It returns the + ! latitude and longitude of an i/j point given the projection info + ! structure. + + implicit none + + ! Arguments + REAL(r8), INTENT(IN) :: i ! Column + REAL(r8), INTENT(IN) :: j ! Row + REAL(r8), INTENT(OUT) :: lat ! -90 -> 90 north + REAL(r8), INTENT(OUT) :: lon ! -180 -> 180 East + TYPE (proj_info), INTENT(IN) :: proj + + ! Local variables + real(r8) :: h, mc, tc, t, rho, x, y + real(r8) :: chi, a, b, c, d + + h = proj%hemi + x = (i - proj%knowni + proj%polei) + y = (j - proj%knownj + proj%polej) + + mc = cos(h*proj%truelat1*rad_per_deg)/sqrt(1.0_r8-(E_WGS84*sin(h*proj%truelat1*rad_per_deg))**2.0) + tc = sqrt(((1.0_r8-sin(h*proj%truelat1*rad_per_deg))/(1.0_r8+sin(h*proj%truelat1*rad_per_deg))) * & + (((1.0_r8+E_WGS84*sin(h*proj%truelat1*rad_per_deg))/(1.0_r8 - & + E_WGS84*sin(h*proj%truelat1*rad_per_deg)))**E_WGS84 )) + + rho = sqrt((x*proj%dx)**2.0 + (y*proj%dx)**2.0) + t = rho * tc / (A_WGS84 * mc) + + lon = h*proj%stdlon + h*atan2(h*x,h*(-y)) + + chi = PI/2.0_r8-2.0_r8*atan(t) + a = (1.0_r8/2.0_r8)*E_WGS84**2. + (5.0_r8/24.0_r8)*E_WGS84**4. + (1.0_r8/40.0_r8)*E_WGS84**6. + & + (73.0_r8/2016.0_r8)*E_WGS84**8. + b = (7.0_r8/24.0_r8)*E_WGS84**4. + (29.0_r8/120.0_r8)*E_WGS84**6. + & + (54113.0_r8/40320.0_r8)*E_WGS84**8. + c = (7.0_r8/30.0_r8)*E_WGS84**6. + (81.0_r8/280.0_r8)*E_WGS84**8. + d = (4279.0_r8/20160.0_r8)*E_WGS84**8. + + lat = chi + sin(2.0_r8*chi)*(a + cos(2.0_r8*chi)*(b + cos(2.0_r8*chi)*(c + d*cos(2.0_r8*chi)))) + lat = h * lat + + lat = lat*deg_per_rad + lon = lon*deg_per_rad + + RETURN + + END SUBROUTINE ijll_ps_wgs84 + + + SUBROUTINE set_albers_nad83(proj) + ! Initializes an Albers equal area map projection (NAD83 ellipsoid) + ! from the partially filled proj structure. This routine computes the + ! radius to the southwest corner and computes the i/j location of the + ! pole for use in llij_albers_nad83 and ijll_albers_nad83. + + IMPLICIT NONE + + ! Arguments + TYPE(proj_info), INTENT(INOUT) :: proj + + ! Local variables + real(r8) :: h, m1, m2, q1, q2, theta, q, sinphi + + h = proj%hemi + + m1 = cos(h*proj%truelat1*rad_per_deg)/sqrt(1.0_r8-(E_NAD83*sin(h*proj%truelat1*rad_per_deg))**2.0) + m2 = cos(h*proj%truelat2*rad_per_deg)/sqrt(1.0_r8-(E_NAD83*sin(h*proj%truelat2*rad_per_deg))**2.0) + + sinphi = sin(proj%truelat1*rad_per_deg) + q1 = (1.0_r8-E_NAD83**2.0) * & + ((sinphi/(1.0_r8-(E_NAD83*sinphi)**2.0)) - 1.0_r8/(2.0_r8*E_NAD83) * & + log((1.0_r8-E_NAD83*sinphi)/(1.0_r8+E_NAD83*sinphi))) + + sinphi = sin(proj%truelat2*rad_per_deg) + q2 = (1.0_r8-E_NAD83**2.0) * & + ((sinphi/(1.0_r8-(E_NAD83*sinphi)**2.0)) - 1.0_r8/(2.0_r8*E_NAD83) * & + log((1.0_r8-E_NAD83*sinphi)/(1.0_r8+E_NAD83*sinphi))) + + if (proj%truelat1 == proj%truelat2) then + proj%nc = sin(proj%truelat1*rad_per_deg) + else + proj%nc = (m1**2.0 - m2**2.0) / (q2 - q1) + end if + + proj%bigc = m1**2.0 + proj%nc*q1 + + ! Find the i/j location of reference lat/lon with respect to the pole of the projection + sinphi = sin(proj%lat1*rad_per_deg) + q = (1.0_r8-E_NAD83**2.0) * & + ((sinphi/(1.0_r8-(E_NAD83*sinphi)**2.0)) - 1.0_r8/(2.0_r8*E_NAD83) * & + log((1.0_r8-E_NAD83*sinphi)/(1.0_r8+E_NAD83*sinphi))) + + proj%rho0 = h * (A_NAD83 / proj%dx) * sqrt(proj%bigc - proj%nc * q) / proj%nc + theta = proj%nc*(proj%lon1 - proj%stdlon)*rad_per_deg + + proj%polei = proj%rho0 * sin(h*theta) + proj%polej = proj%rho0 - proj%rho0 * cos(h*theta) + + RETURN + + END SUBROUTINE set_albers_nad83 + + + SUBROUTINE llij_albers_nad83(lat,lon,proj,i,j) + ! Given latitude (-90 to 90), longitude (-180 to 180), and the + ! standard projection information via the + ! public proj structure, this routine returns the i/j indices which + ! if within the domain range from 1->nx and 1->ny, respectively. + + IMPLICIT NONE + + ! Arguments + REAL(r8), INTENT(IN) :: lat + REAL(r8), INTENT(IN) :: lon + REAL(r8), INTENT(OUT) :: i !(x-index) + REAL(r8), INTENT(OUT) :: j !(y-index) + TYPE(proj_info),INTENT(IN) :: proj + + ! Local variables + real(r8) :: h, q, rho, theta, sinphi + + h = proj%hemi + + sinphi = sin(h*lat*rad_per_deg) + + ! Find the x/y location of the requested lat/lon with respect to the pole of the projection + q = (1.0_r8-E_NAD83**2.0) * & + ((sinphi/(1.0_r8-(E_NAD83*sinphi)**2.0)) - 1.0_r8/(2.0_r8*E_NAD83) * & + log((1.0_r8-E_NAD83*sinphi)/(1.0_r8+E_NAD83*sinphi))) + + rho = h * (A_NAD83 / proj%dx) * sqrt(proj%bigc - proj%nc * q) / proj%nc + theta = proj%nc * (h*lon - h*proj%stdlon)*rad_per_deg + + i = h*rho*sin(theta) + j = h*proj%rho0 - h*rho*cos(theta) + + ! Get i/j relative to reference i/j + i = proj%knowni + (i - proj%polei) + j = proj%knownj + (j - proj%polej) + + RETURN + + END SUBROUTINE llij_albers_nad83 + + + SUBROUTINE ijll_albers_nad83(i, j, proj, lat, lon) + + ! This is the inverse subroutine of llij_albers_nad83. It returns the + ! latitude and longitude of an i/j point given the projection info + ! structure. + + implicit none + + ! Arguments + REAL(r8), INTENT(IN) :: i ! Column + REAL(r8), INTENT(IN) :: j ! Row + REAL(r8), INTENT(OUT) :: lat ! -90 -> 90 north + REAL(r8), INTENT(OUT) :: lon ! -180 -> 180 East + TYPE (proj_info), INTENT(IN) :: proj + + ! Local variables + real(r8) :: h, q, rho, theta, beta, x, y + real(r8) :: a, b, c + + h = proj%hemi + + x = (i - proj%knowni + proj%polei) + y = (j - proj%knownj + proj%polej) + + rho = sqrt(x**2.0 + (proj%rho0 - y)**2.0) + theta = atan2(x, proj%rho0-y) + + q = (proj%bigc - (rho*proj%nc*proj%dx/A_NAD83)**2.0) / proj%nc + + beta = asin(q/(1.0_r8 - & + log((1.0_r8-E_NAD83)/(1.0_r8+E_NAD83))*(1.0_r8-E_NAD83**2.0)/(2.0_r8*E_NAD83))) + a = (1.0_r8/3.0_r8)*E_NAD83**2 + (31.0_r8/180.0_r8)*E_NAD83**4 + (517.0_r8/5040.0_r8)*E_NAD83**6 + b = (23.0_r8/360.0_r8)*E_NAD83**4 + (251.0_r8/3780.0_r8)*E_NAD83**6 + c = (761.0_r8/45360.0_r8)*E_NAD83**6 + + lat = beta + a*sin(2.0_r8*beta) + b*sin(4.0_r8*beta) + c*sin(6.0_r8*beta) + + lat = h*lat*deg_per_rad + lon = proj%stdlon + theta*deg_per_rad/proj%nc + + RETURN + + END SUBROUTINE ijll_albers_nad83 + + + SUBROUTINE set_lc(proj) + ! Initialize the remaining items in the proj structure for a + ! lambert conformal grid. + + IMPLICIT NONE + + TYPE(proj_info), INTENT(INOUT) :: proj + + REAL(r8) :: arg + REAL(r8) :: deltalon1 + REAL(r8) :: tl1r + REAL(r8) :: ctl1r + + ! Compute cone factor + CALL lc_cone(proj%truelat1, proj%truelat2, proj%cone) + + ! Compute longitude differences and ensure we stay out of the + ! forbidden "cut zone" + deltalon1 = proj%lon1 - proj%stdlon + IF (deltalon1 .GT. +180.0_r8) deltalon1 = deltalon1 - 360.0_r8 + IF (deltalon1 .LT. -180.0_r8) deltalon1 = deltalon1 + 360.0_r8 + + ! Convert truelat1 to radian and compute COS for later use + tl1r = proj%truelat1 * rad_per_deg + ctl1r = COS(tl1r) + + ! Compute the radius to our known lower-left (SW) corner + proj%rsw = proj%rebydx * ctl1r/proj%cone * & + (TAN((90.0_r8*proj%hemi-proj%lat1)*rad_per_deg/2.0_r8) / & + TAN((90.0_r8*proj%hemi-proj%truelat1)*rad_per_deg/2.0_r8))**proj%cone + + ! Find pole point + arg = proj%cone*(deltalon1*rad_per_deg) + proj%polei = proj%hemi*proj%knowni - proj%hemi * proj%rsw * SIN(arg) + proj%polej = proj%hemi*proj%knownj + proj%rsw * COS(arg) + + RETURN + + END SUBROUTINE set_lc + + + SUBROUTINE lc_cone(truelat1, truelat2, cone) + + ! Subroutine to compute the cone factor of a Lambert Conformal projection + + IMPLICIT NONE + + ! Input Args + REAL(r8), INTENT(IN) :: truelat1 ! (-90 -> 90 degrees N) + REAL(r8), INTENT(IN) :: truelat2 ! " " " " " + + ! Output Args + REAL(r8), INTENT(OUT) :: cone + + ! Locals + + ! BEGIN CODE + + ! First, see if this is a secant or tangent projection. For tangent + ! projections, truelat1 = truelat2 and the cone is tangent to the + ! Earth's surface at this latitude. For secant projections, the cone + ! intersects the Earth's surface at each of the distinctly different + ! latitudes + IF (ABS(truelat1-truelat2) .GT. 0.10_r8) THEN + cone = LOG10(COS(truelat1*rad_per_deg)) - & + LOG10(COS(truelat2*rad_per_deg)) + cone = cone /(LOG10(TAN((45.0_r8 - ABS(truelat1)/2.0_r8) * rad_per_deg)) - & + LOG10(TAN((45.0_r8 - ABS(truelat2)/2.0_r8) * rad_per_deg))) + ELSE + cone = SIN(ABS(truelat1)*rad_per_deg ) + ENDIF + + RETURN + + END SUBROUTINE lc_cone + + + SUBROUTINE ijll_lc( i, j, proj, lat, lon) + + ! Subroutine to convert from the (i,j) cartesian coordinate to the + ! geographical latitude and longitude for a Lambert Conformal projection. + + ! History: + ! 25 Jul 01: Corrected by B. Shaw, NOAA/FSL + ! + IMPLICIT NONE + + ! Input Args + REAL(r8), INTENT(IN) :: i ! Cartesian X coordinate + REAL(r8), INTENT(IN) :: j ! Cartesian Y coordinate + TYPE(proj_info),INTENT(IN) :: proj ! Projection info structure + + ! Output Args + REAL(r8), INTENT(OUT) :: lat ! Latitude (-90->90 deg N) + REAL(r8), INTENT(OUT) :: lon ! Longitude (-180->180 E) + + ! Locals + REAL(r8) :: inew + REAL(r8) :: jnew + REAL(r8) :: r + REAL(r8) :: chi,chi1,chi2 + REAL(r8) :: r2 + REAL(r8) :: xx + REAL(r8) :: yy + + ! BEGIN CODE + + chi1 = (90.0_r8 - proj%hemi*proj%truelat1)*rad_per_deg + chi2 = (90.0_r8 - proj%hemi*proj%truelat2)*rad_per_deg + + ! See if we are in the southern hemispere and flip the indices + ! if we are. + inew = proj%hemi * i + jnew = proj%hemi * j + + ! Compute radius**2 to i/j location + xx = inew - proj%polei + yy = proj%polej - jnew + r2 = (xx*xx + yy*yy) + r = SQRT(r2)/proj%rebydx + + ! Convert to lat/lon + IF (r2 .EQ. 0.0_r8) THEN + lat = proj%hemi * 90.0_r8 + lon = proj%stdlon + ELSE + + ! Longitude + lon = proj%stdlon + deg_per_rad * ATAN2(proj%hemi*xx,yy)/proj%cone + lon = MOD(lon+360.0_r8, 360.0_r8) + + ! Latitude. Latitude determined by solving an equation adapted + ! from: + ! Maling, D.H., 1973: Coordinate Systems and Map Projections + ! Equations #20 in Appendix I. + + IF (chi1 .EQ. chi2) THEN + chi = 2.0_r8*ATAN( ( r/TAN(chi1) )**(1.0_r8/proj%cone) * TAN(chi1*0.5_r8) ) + ELSE + chi = 2.0_r8*ATAN( (r*proj%cone/SIN(chi1))**(1.0_r8/proj%cone) * TAN(chi1*0.5_r8)) + ENDIF + lat = (90.0_r8-chi*deg_per_rad)*proj%hemi + + ENDIF + + IF (lon .GT. +180.0_r8) lon = lon - 360.0_r8 + IF (lon .LT. -180.0_r8) lon = lon + 360.0_r8 + + RETURN + + END SUBROUTINE ijll_lc + + + SUBROUTINE llij_lc( lat, lon, proj, i, j) + + ! Subroutine to compute the geographical latitude and longitude values + ! to the cartesian x/y on a Lambert Conformal projection. + + IMPLICIT NONE + + ! Input Args + REAL(r8), INTENT(IN) :: lat ! Latitude (-90->90 deg N) + REAL(r8), INTENT(IN) :: lon ! Longitude (-180->180 E) + TYPE(proj_info),INTENT(IN) :: proj ! Projection info structure + + ! Output Args + REAL(r8), INTENT(OUT) :: i ! Cartesian X coordinate + REAL(r8), INTENT(OUT) :: j ! Cartesian Y coordinate + + ! Locals + REAL(r8) :: arg + REAL(r8) :: deltalon + REAL(r8) :: tl1r + REAL(r8) :: rm + REAL(r8) :: ctl1r + + + ! BEGIN CODE + + ! Compute deltalon between known longitude and standard lon and ensure + ! it is not in the cut zone + deltalon = lon - proj%stdlon + IF (deltalon .GT. +180.0_r8) deltalon = deltalon - 360.0_r8 + IF (deltalon .LT. -180.0_r8) deltalon = deltalon + 360.0_r8 + + ! Convert truelat1 to radian and compute COS for later use + tl1r = proj%truelat1 * rad_per_deg + ctl1r = COS(tl1r) + + ! Radius to desired point + rm = proj%rebydx * ctl1r/proj%cone * & + (TAN((90.0_r8*proj%hemi-lat)*rad_per_deg/2.0_r8) / & + TAN((90.0_r8*proj%hemi-proj%truelat1)*rad_per_deg/2.0_r8))**proj%cone + + arg = proj%cone*(deltalon*rad_per_deg) + i = proj%polei + proj%hemi * rm * SIN(arg) + j = proj%polej - rm * COS(arg) + + ! Finally, if we are in the southern hemisphere, flip the i/j + ! values to a coordinate system where (1,1) is the SW corner + ! (what we assume) which is different than the original NCEP + ! algorithms which used the NE corner as the origin in the + ! southern hemisphere (left-hand vs. right-hand coordinate?) + i = proj%hemi * i + j = proj%hemi * j + + RETURN + END SUBROUTINE llij_lc + + + SUBROUTINE set_merc(proj) + + ! Sets up the remaining basic elements for the mercator projection + + IMPLICIT NONE + TYPE(proj_info), INTENT(INOUT) :: proj + REAL(r8) :: clain + + + ! Preliminary variables + + clain = COS(rad_per_deg*proj%truelat1) + proj%dlon = proj%dx / (proj%re_m * clain) + + ! Compute distance from equator to origin, and store in the + ! proj%rsw tag. + + proj%rsw = 0.0_r8 + IF (proj%lat1 .NE. 0.0_r8) THEN + proj%rsw = (LOG(TAN(0.50_r8*((proj%lat1+90.0_r8)*rad_per_deg))))/proj%dlon + ENDIF + + RETURN + + END SUBROUTINE set_merc + + + SUBROUTINE llij_merc(lat, lon, proj, i, j) + + ! Compute i/j coordinate from lat lon for mercator projection + + IMPLICIT NONE + REAL(r8), INTENT(IN) :: lat + REAL(r8), INTENT(IN) :: lon + TYPE(proj_info),INTENT(IN) :: proj + REAL(r8),INTENT(OUT) :: i + REAL(r8),INTENT(OUT) :: j + REAL(r8) :: deltalon, i2 + + deltalon = lon - proj%lon1 + IF (deltalon .LT. -180.0_r8) deltalon = deltalon + 360.0_r8 + IF (deltalon .GT. 180.0_r8) deltalon = deltalon - 360.0_r8 + i = proj%knowni + (deltalon/(proj%dlon*deg_per_rad)) + i2 = proj%knowni + ((deltalon+360.0_r8)/(proj%dlon*deg_per_rad)) + if ( i < 0.0_r8 ) i = i2 + j = proj%knownj + (LOG(TAN(0.50_r8*((lat + 90.0_r8) * rad_per_deg)))) / & + proj%dlon - proj%rsw + + RETURN + + END SUBROUTINE llij_merc + + + SUBROUTINE ijll_merc(i, j, proj, lat, lon) + + ! Compute the lat/lon from i/j for mercator projection + + IMPLICIT NONE + REAL(r8),INTENT(IN) :: i + REAL(r8),INTENT(IN) :: j + TYPE(proj_info),INTENT(IN) :: proj + REAL(r8), INTENT(OUT) :: lat + REAL(r8), INTENT(OUT) :: lon + + + lat = 2.00_r8*ATAN(EXP(proj%dlon*(proj%rsw + j-proj%knownj)))*deg_per_rad - 90.0_r8 + lon = (i-proj%knowni)*proj%dlon*deg_per_rad + proj%lon1 + IF (lon.GT.180.0_r8) lon = lon - 360.0_r8 + IF (lon.LT.-180.0_r8) lon = lon + 360.0_r8 + RETURN + + END SUBROUTINE ijll_merc + + + SUBROUTINE llij_latlon(lat, lon, proj, i, j) + + ! Compute the i/j location of a lat/lon on a LATLON grid. + IMPLICIT NONE + REAL(r8), INTENT(IN) :: lat + REAL(r8), INTENT(IN) :: lon + TYPE(proj_info), INTENT(IN) :: proj + REAL(r8), INTENT(OUT) :: i + REAL(r8), INTENT(OUT) :: j + + REAL(r8) :: deltalat + REAL(r8) :: deltalon + + ! Compute deltalat and deltalon as the difference between the input + ! lat/lon and the origin lat/lon + deltalat = lat - proj%lat1 + deltalon = lon - proj%lon1 + + ! Compute i/j + i = deltalon/proj%loninc + j = deltalat/proj%latinc + + i = i + proj%knowni + j = j + proj%knownj + + RETURN + + END SUBROUTINE llij_latlon + + + SUBROUTINE ijll_latlon(i, j, proj, lat, lon) + + ! Compute the lat/lon location of an i/j on a LATLON grid. + IMPLICIT NONE + REAL(r8), INTENT(IN) :: i + REAL(r8), INTENT(IN) :: j + TYPE(proj_info), INTENT(IN) :: proj + REAL(r8), INTENT(OUT) :: lat + REAL(r8), INTENT(OUT) :: lon + + REAL(r8) :: i_work, j_work + REAL(r8) :: deltalat + REAL(r8) :: deltalon + + i_work = i - proj%knowni + j_work = j - proj%knownj + + ! Compute deltalat and deltalon + deltalat = j_work*proj%latinc + deltalon = i_work*proj%loninc + + lat = proj%lat1 + deltalat + lon = proj%lon1 + deltalon + + RETURN + + END SUBROUTINE ijll_latlon + + + SUBROUTINE set_cyl(proj) + + implicit none + + ! Arguments + type(proj_info), intent(inout) :: proj + + proj%hemi = 1.00_r8 + + END SUBROUTINE set_cyl + + + SUBROUTINE llij_cyl(lat, lon, proj, i, j) + + implicit none + + ! Arguments + real(r8), intent(in) :: lat, lon + real(r8), intent(out) :: i, j + type(proj_info), intent(in) :: proj + + ! Local variables + real(r8) :: deltalat + real(r8) :: deltalon + + ! Compute deltalat and deltalon as the difference between the input + ! lat/lon and the origin lat/lon + deltalat = lat - proj%lat1 +! deltalon = lon - proj%stdlon + deltalon = lon - proj%lon1 + + if (deltalon < 0.0_r8) deltalon = deltalon + 360.0_r8 + if (deltalon > 360.0_r8) deltalon = deltalon - 360.0_r8 + + ! Compute i/j + i = deltalon/proj%loninc + j = deltalat/proj%latinc + + if (i <= 0.0_r8) i = i + 360.0_r8/proj%loninc + if (i > 360.0_r8/proj%loninc) i = i - 360.0_r8/proj%loninc + + i = i + proj%knowni +!nc -- typo in original I am assuming +!nc -- orig --> j = j + proj%knowni + j = j + proj%knownj + + END SUBROUTINE llij_cyl + + + SUBROUTINE ijll_cyl(i, j, proj, lat, lon) + + implicit none + + ! Arguments + real(r8), intent(in) :: i, j + real(r8), intent(out) :: lat, lon + type(proj_info), intent(in) :: proj + + ! Local variables + real(r8) :: deltalat + real(r8) :: deltalon + real(r8) :: i_work, j_work + + i_work = i - proj%knowni + j_work = j - proj%knownj + + if (i_work < 0.0_r8) i_work = i_work + 360.0_r8/proj%loninc + if (i_work >= 360.0_r8/proj%loninc) i_work = i_work - 360.0_r8/proj%loninc + + ! Compute deltalat and deltalon + deltalat = j_work*proj%latinc + deltalon = i_work*proj%loninc + + lat = deltalat + proj%lat1 +! lon = deltalon + proj%stdlon + lon = deltalon + proj%lon1 + + if (lon < -180.0_r8) lon = lon + 360.0_r8 + if (lon > 180.0_r8) lon = lon - 360.0_r8 + + END SUBROUTINE ijll_cyl + + + SUBROUTINE set_cassini(proj) + + implicit none + + ! Arguments + type(proj_info), intent(inout) :: proj + + ! Local variables + real(r8) :: comp_lat, comp_lon + logical :: global_domain + + proj%hemi = 1.00_r8 + + ! Try to determine whether this domain has global coverage + if (abs(proj%lat1 - proj%latinc/2.0_r8 + 90.0_r8) < 0.001_r8 .and. & + abs(mod(proj%lon1 - proj%loninc/2.0_r8 - proj%stdlon,360.0_r8)) < 0.001_r8) then + global_domain = .true. + else + global_domain = .false. + end if + + if (abs(proj%lat0) /= 90.0_r8 .and. .not.global_domain) then + call rotate_coords(proj%lat1,proj%lon1,comp_lat,comp_lon,proj%lat0,proj%lon0,proj%stdlon,-1) + proj%lat1 = comp_lat + proj%lon1 = comp_lon + end if + + END SUBROUTINE set_cassini + + + SUBROUTINE llij_cassini(lat, lon, proj, i, j) + + implicit none + + ! Arguments + real(r8), intent(in) :: lat, lon + real(r8), intent(out) :: i, j + type(proj_info), intent(in) :: proj + + ! Local variables + real(r8) :: comp_lat, comp_lon + + ! Convert geographic to computational lat/lon + if (abs(proj%lat0) /= 90.0_r8) then + call rotate_coords(lat,lon,comp_lat,comp_lon,proj%lat0,proj%lon0,proj%stdlon,-1) + else + comp_lat = lat + comp_lon = lon + end if + + ! Convert computational lat/lon to i/j + call llij_cyl(comp_lat, comp_lon, proj, i, j) + + END SUBROUTINE llij_cassini + + + SUBROUTINE ijll_cassini(i, j, proj, lat, lon) + + implicit none + + ! Arguments + real(r8), intent(in) :: i, j + real(r8), intent(out) :: lat, lon + type(proj_info), intent(in) :: proj + + ! Local variables + real(r8) :: comp_lat, comp_lon + + ! Convert i/j to computational lat/lon + call ijll_cyl(i, j, proj, comp_lat, comp_lon) + + ! Convert computational to geographic lat/lon + if (abs(proj%lat0) /= 90.0_r8) then + call rotate_coords(comp_lat,comp_lon,lat,lon,proj%lat0,proj%lon0,proj%stdlon,1) + else + lat = comp_lat + lon = comp_lon + end if + + END SUBROUTINE ijll_cassini + + + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + ! Purpose: Converts between computational and geographic lat/lon for Cassini + ! + ! Notes: This routine was provided by Bill Skamarock, 2007-03-27 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + SUBROUTINE rotate_coords(ilat,ilon,olat,olon,lat_np,lon_np,lon_0,direction) + + IMPLICIT NONE + + REAL(r8), INTENT(IN ) :: ilat, ilon + REAL(r8), INTENT( OUT) :: olat, olon + REAL(r8), INTENT(IN ) :: lat_np, lon_np, lon_0 + INTEGER, INTENT(IN ), OPTIONAL :: direction + ! >=0, default : computational -> geographical + ! < 0 : geographical -> computational + + REAL(r8) :: rlat, rlon + REAL(r8) :: phi_np, lam_np, lam_0, dlam + REAL(r8) :: sinphi, cosphi, coslam, sinlam + + ! Convert all angles to radians + phi_np = lat_np * rad_per_deg + lam_np = lon_np * rad_per_deg + lam_0 = lon_0 * rad_per_deg + rlat = ilat * rad_per_deg + rlon = ilon * rad_per_deg + + IF (PRESENT(direction) .AND. (direction < 0)) THEN + ! The equations are exactly the same except for one small difference + ! with respect to longitude ... + dlam = PI - lam_0 + ELSE + dlam = lam_np + END IF + sinphi = COS(phi_np)*COS(rlat)*COS(rlon-dlam) + SIN(phi_np)*SIN(rlat) + cosphi = SQRT(1.0_r8-sinphi*sinphi) + coslam = SIN(phi_np)*COS(rlat)*COS(rlon-dlam) - COS(phi_np)*SIN(rlat) + sinlam = COS(rlat)*SIN(rlon-dlam) + IF ( cosphi /= 0.0_r8 ) THEN + coslam = coslam/cosphi + sinlam = sinlam/cosphi + END IF + olat = deg_per_rad*ASIN(sinphi) + olon = deg_per_rad*(ATAN2(sinlam,coslam)-dlam-lam_0+lam_np) + ! Both of my F90 text books prefer the DO-EXIT form, and claim it is faster + ! when optimization is turned on (as we will always do...) + DO + IF (olon >= -180.0_r8) EXIT + olon = olon + 360.0_r8 + END DO + DO + IF (olon <= 180.0_r8) EXIT + olon = olon - 360.0_r8 + END DO + + END SUBROUTINE rotate_coords + + + SUBROUTINE llij_rotlatlon(lat, lon, proj, i, j) + + IMPLICIT NONE + + ! Arguments + REAL(r8), INTENT(IN) :: lat, lon + REAL(r8), INTENT(OUT) :: i, j + TYPE (proj_info), INTENT(IN) :: proj + + ! Local variables -- pi is a parameter in constants_module! -- local value will + ! be "ppi" + REAL(KIND=HIGH) :: dphd,dlmd !Grid increments, degrees + INTEGER :: ii,jj,jmt,ncol,nrow + REAL(KIND=HIGH) :: glatd !Geographic latitude, positive north + REAL(KIND=HIGH) :: glond !Geographic longitude, positive west + REAL(KIND=HIGH) :: col,d1,d2,d2r,dlm,dlm1,dlm2,dph,glat,glon, & + ppi,r2d,row,tlat,tlat1,tlat2, & + tlon,tlon1,tlon2,tph0,tlm0,x,y,z + + glatd = REAL(lat,HIGH) ! HIGH + glond = -REAL(lon,HIGH) ! HIGH + + dphd = REAL(proj%phi,HIGH)/REAL((proj%jydim-1)/2,HIGH) ! HIGH + dlmd = REAL(proj%lambda,HIGH)/REAL(proj%ixdim-1,HIGH) ! HIGH + + ppi = ACOS(-1.0_HIGH) ! HIGH (same name as module variable) + d2r = ppi/180.0_HIGH ! HIGH + r2d = 1.0_HIGH/d2r ! HIGH + + jmt = proj%jydim/2+1 ! INTEGER + + glat = glatd*d2r ! HIGH + glon = glond*d2r ! HIGH + dph = dphd*d2r ! HIGH + dlm = dlmd*d2r ! HIGH + tph0 = REAL(proj%lat1,HIGH)*d2r ! HIGH + tlm0 = -REAL(proj%lon1,HIGH)*d2r ! HIGH + + x = COS(tph0)*COS(glat)*COS(glon-tlm0)+SIN(tph0)*SIN(glat) ! HIGH + y = -COS(glat)*SIN(glon-tlm0) ! HIGH + z = COS(tph0)*SIN(glat)-SIN(tph0)*COS(glat)*COS(glon-tlm0) ! HIGH + tlat = r2d*ATAN(z/SQRT(x*x+y*y)) ! HIGH + tlon = r2d*ATAN(y/x) ! HIGH + + row = tlat/dphd+REAL(jmt,HIGH) ! HIGH + col = tlon/dlmd+REAL(proj%ixdim,HIGH) ! HIGH + + nrow = NINT(row) ! INTEGER + ncol = NINT(col) ! INTEGER + + tlat = tlat*d2r ! HIGH + tlon = tlon*d2r ! HIGH + + IF (proj%stagger == HH) THEN + + IF ((abs(MOD(nrow,2)) == 1 .AND. abs(MOD(ncol,2)) == 1) .OR. & + (MOD(nrow,2) == 0 .AND. MOD(ncol,2) == 0)) THEN + + tlat1 = REAL((nrow-jmt),HIGH)*dph + tlat2 = tlat1+dph + tlon1 = REAL((ncol-proj%ixdim),HIGH)*dlm + tlon2 = tlon1+dlm + + dlm1 = tlon-tlon1 + dlm2 = tlon-tlon2 + d1 = ACOS(COS(tlat)*COS(tlat1)*COS(dlm1)+SIN(tlat)*SIN(tlat1)) + d2 = ACOS(COS(tlat)*COS(tlat2)*COS(dlm2)+SIN(tlat)*SIN(tlat2)) + + IF (d1 > d2) THEN + nrow = nrow+1 + ncol = ncol+1 + END IF + + ELSE + tlat1 = REAL((nrow+1-jmt),HIGH)*dph + tlat2 = tlat1-dph + tlon1 = REAL((ncol-proj%ixdim),HIGH)*dlm + tlon2 = tlon1+dlm + dlm1 = tlon-tlon1 + dlm2 = tlon-tlon2 + d1 = ACOS(COS(tlat)*COS(tlat1)*COS(dlm1)+SIN(tlat)*SIN(tlat1)) + d2 = ACOS(COS(tlat)*COS(tlat2)*COS(dlm2)+SIN(tlat)*SIN(tlat2)) + + IF (d1 < d2) THEN + nrow = nrow+1 + ELSE + ncol = ncol+1 + END IF + END IF + + ELSE IF (proj%stagger == VV) THEN + + IF ((MOD(nrow,2) == 0 .AND. abs(MOD(ncol,2)) == 1) .OR. & + (abs(MOD(nrow,2)) == 1 .AND. MOD(ncol,2) == 0)) THEN + tlat1 = REAL((nrow-jmt),HIGH)*dph + tlat2 = tlat1+dph + tlon1 = REAL((ncol-proj%ixdim),HIGH)*dlm + tlon2 = tlon1+dlm + dlm1 = tlon-tlon1 + dlm2 = tlon-tlon2 + d1 = ACOS(COS(tlat)*COS(tlat1)*COS(dlm1)+SIN(tlat)*SIN(tlat1)) + d2 = ACOS(COS(tlat)*COS(tlat2)*COS(dlm2)+SIN(tlat)*SIN(tlat2)) + + IF (d1 > d2) THEN + nrow = nrow+1 + ncol = ncol+1 + END IF + + ELSE + tlat1 = REAL((nrow+1-jmt),HIGH)*dph + tlat2 = tlat1-dph + tlon1 = REAL((ncol-proj%ixdim),HIGH)*dlm + tlon2 = tlon1+dlm + dlm1 = tlon-tlon1 + dlm2 = tlon-tlon2 + d1 = ACOS(COS(tlat)*COS(tlat1)*COS(dlm1)+SIN(tlat)*SIN(tlat1)) + d2 = ACOS(COS(tlat)*COS(tlat2)*COS(dlm2)+SIN(tlat)*SIN(tlat2)) + + IF (d1 < d2) THEN + nrow = nrow+1 + ELSE + ncol = ncol+1 + END IF + END IF + END IF + + +!!! Added next line as a Kludge - not yet understood why needed + if (ncol .le. 0) ncol=ncol-1 + + jj = nrow + ii = ncol/2 + + IF (proj%stagger == HH) THEN + IF (abs(MOD(jj,2)) == 1) ii = ii+1 + ELSE IF (proj%stagger == VV) THEN + IF (MOD(jj,2) == 0) ii=ii+1 + END IF + + i = REAL(ii,r8) + j = REAL(jj,r8) + + END SUBROUTINE llij_rotlatlon + + + SUBROUTINE ijll_rotlatlon(i, j, proj, lat,lon) + + IMPLICIT NONE + + ! Arguments + REAL(R8), INTENT(IN) :: i, j + REAL(R8), INTENT(OUT) :: lat, lon + TYPE (proj_info), INTENT(IN) :: proj + + ! Local variables + INTEGER :: ih,jh + INTEGER :: midcol,midrow,ncol + REAL(KIND=HIGH) :: dphd,dlmd !Grid increments, degrees + REAL(KIND=HIGH) :: arg1,arg2,d2r,fctr,glatr,glatd,glond,ppi, & + r2d,tlatd,tlond,tlatr,tlonr,tlm0,tph0 + + ih = NINT(i) + jh = NINT(j) + + dphd = REAL(proj%phi,HIGH)/REAL((proj%jydim-1)/2,HIGH) + dlmd = REAL(proj%lambda,HIGH)/REAL(proj%ixdim-1,HIGH) + + ppi = ACOS(-1.0_HIGH) + d2r = ppi/180.0_HIGH + r2d = 1.0_HIGH/d2r + tph0 = REAL(proj%lat1,HIGH)*d2r + tlm0 = -REAL(proj%lon1,HIGH)*d2r + + midrow = (proj%jydim+1)/2 + midcol = proj%ixdim + +! IF (proj%stagger == HH) THEN + +!!! ncol = 2*ih-1+MOD(jh+1,2) + ncol = 2*ih-1+abs(MOD(jh+1,2)) + tlatd = REAL((jh-midrow),HIGH)*dphd + tlond = REAL((ncol-midcol),HIGH)*dlmd + IF (proj%stagger == VV) THEN + if (mod(jh,2) .eq. 0) then + tlond = tlond - DLMD + else + tlond = tlond + DLMD + end if + END IF + + tlatr = tlatd*d2r + tlonr = tlond*d2r + arg1 = SIN(tlatr)*COS(tph0)+COS(tlatr)*SIN(tph0)*COS(tlonr) + glatr = ASIN(arg1) + + glatd = glatr*r2d + + arg2 = COS(tlatr)*COS(tlonr)/(COS(glatr)*COS(tph0))-TAN(glatr)*TAN(tph0) + IF (ABS(arg2) > 1.0_HIGH) arg2 = ABS(arg2)/arg2 + fctr = 1.0_HIGH + IF (tlond > 0.0_HIGH) fctr = -1.0_HIGH + + glond = tlm0*r2d+fctr*ACOS(arg2)*r2d + + lat = REAL(glatd,r8) + lon = -REAL(glond,r8) + + IF (lon .GT. +180.0_r8) lon = lon - 360.0_r8 + IF (lon .LT. -180.0_r8) lon = lon + 360.0_r8 + + END SUBROUTINE ijll_rotlatlon + + + SUBROUTINE set_gauss(proj) + + IMPLICIT NONE + + ! Argument + type (proj_info), intent(inout) :: proj + + ! Initialize the array that will hold the Gaussian latitudes. + + IF ( ASSOCIATED( proj%gauss_lat ) ) THEN + DEALLOCATE ( proj%gauss_lat ) + END IF + + ! Get the needed space for our array. + + ALLOCATE ( proj%gauss_lat(proj%nlat*2) ) + + ! Compute the Gaussian latitudes. + + CALL gausll( proj%nlat*2 , proj%gauss_lat ) + + ! Now, these could be upside down from what we want, so let's check. + ! We take advantage of the equatorial symmetry to remove any sort of + ! array re-ordering. + + IF ( ABS(proj%gauss_lat(1) - proj%lat1) .GT. 0.01_r8 ) THEN + proj%gauss_lat = -1.0_r8 * proj%gauss_lat + END IF + + ! Just a sanity check. + + IF ( ABS(proj%gauss_lat(1) - proj%lat1) .GT. 0.01_r8 ) THEN + PRINT '(A)','Oops, something is not right with the Gaussian latitude computation.' + PRINT '(A,F8.3,A)','The input data gave the starting latitude as ',proj%lat1,'.' + PRINT '(A,F8.3,A)','This routine computed the starting latitude as +-',ABS(proj%gauss_lat(1)),'.' + PRINT '(A,F8.3,A)','The difference is larger than 0.01 degrees, which is not expected.' + write(6,*) 'ERROR: Gaussian_latitude_computation' + END IF + + END SUBROUTINE set_gauss + + + SUBROUTINE gausll ( nlat , lat_sp ) + + IMPLICIT NONE + + INTEGER :: nlat , i + REAL (KIND=HIGH) , PARAMETER :: ppi = 3.141592653589793_HIGH + REAL (KIND=HIGH) , DIMENSION(nlat) :: cosc , gwt , sinc , colat , wos2 , lat + REAL(R8) , DIMENSION(nlat) :: lat_sp + + CALL lggaus(nlat, cosc, gwt, sinc, colat, wos2) + + DO i = 1, nlat + lat(i) = ACOS(sinc(i)) * 180.0_HIGH / ppi + IF (i.gt.nlat/2) lat(i) = -lat(i) + END DO + + lat_sp = REAL(lat,r8) + + END SUBROUTINE gausll + + + SUBROUTINE lggaus( nlat, cosc, gwt, sinc, colat, wos2 ) + + IMPLICIT NONE + + ! LGGAUS finds the Gaussian latitudes by finding the roots of the + ! ordinary Legendre polynomial of degree NLAT using Newton's + ! iteration method. + + ! On entry: + integer NLAT ! the number of latitudes (degree of the polynomial) + + ! On exit: for each Gaussian latitude + ! COSC - cos(colatitude) or sin(latitude) + ! GWT - the Gaussian weights + ! SINC - sin(colatitude) or cos(latitude) + ! COLAT - the colatitudes in radians + ! WOS2 - Gaussian weight over sin**2(colatitude) + + REAL (KIND=HIGH) , DIMENSION(nlat) :: cosc , gwt , sinc , colat , wos2 + REAL (KIND=HIGH) , PARAMETER :: ppi = 3.141592653589793_HIGH + + ! Convergence criterion for iteration of cos latitude + + REAL(KIND=HIGH) , PARAMETER :: xlim = 1.0E-14_HIGH + + INTEGER :: nzero, i, j + REAL (KIND=HIGH) :: fi, fi1, a, b, g, gm, gp, gt, delta, c, d + + ! The number of zeros between pole and equator + + nzero = nlat/2 + + ! Set first guess for cos(colat) + + DO i=1,nzero + cosc(i) = SIN( (REAL(i,HIGH)-0.5_HIGH)*ppi/REAL(nlat,HIGH) + ppi*0.5_HIGH ) + END DO + + ! Constants for determining the derivative of the polynomial + fi = nlat + fi1 = fi + 1.0_HIGH + a = fi*fi1 / SQRT(4.0_HIGH * fi1*fi1 - 1.0_HIGH) + b = fi1*fi / SQRT(4.0_HIGH * fi*fi - 1.0_HIGH) + + ! Loop over latitudes, iterating the search for each root + + DO i=1,nzero + j=0 + + ! Determine the value of the ordinary Legendre polynomial for + ! the current guess root + + DO + CALL lgord( g, cosc(i), nlat ) + + ! Determine the derivative of the polynomial at this point + + CALL lgord( gm, cosc(i), nlat-1 ) + CALL lgord( gp, cosc(i), nlat+1 ) + gt = (cosc(i)*cosc(i)-1.0_HIGH) / (a*gp-b*gm) + + ! Update the estimate of the root + + delta = g*gt + cosc(i) = cosc(i) - delta + + ! If convergence criterion has not been met, keep trying + + j = j+1 + IF( ABS(delta).GT.xlim ) CYCLE + + ! Determine the Gaussian weights + + c = 2.0_HIGH *( 1.0_HIGH - cosc(i)*cosc(i) ) + CALL lgord( d, cosc(i), nlat-1 ) + d = d*d*fi*fi + gwt(i) = c *( fi-0.5_HIGH ) / d + EXIT + + END DO + + END DO + + ! Determine the colatitudes and sin(colat) and weights over sin**2 + + DO i=1,nzero + colat(i)= ACOS(cosc(i)) + sinc(i) = SIN(colat(i)) + wos2(i) = gwt(i) /( sinc(i)*sinc(i) ) + END DO + + ! If NLAT is odd, set values at the equator + + IF( MOD(nlat,2) .NE. 0 ) THEN + i = nzero+1 + cosc(i) = 0.0_HIGH + c = 2.0_HIGH + CALL lgord( d, cosc(i), nlat-1 ) + d = d*d*fi*fi + gwt(i) = c *( fi-0.5_HIGH ) / d + colat(i)= ppi*0.5_HIGH + sinc(i) = 1.0_HIGH + wos2(i) = gwt(i) + END IF + + ! Determine the southern hemisphere values by symmetry + + DO i=nlat-nzero+1,nlat + cosc(i) =-cosc(nlat+1-i) + gwt(i) = gwt(nlat+1-i) + colat(i)= ppi-colat(nlat+1-i) + sinc(i) = sinc(nlat+1-i) + wos2(i) = wos2(nlat+1-i) + END DO + + END SUBROUTINE lggaus + + + SUBROUTINE lgord( f, cosc, n ) + + IMPLICIT NONE + + ! LGORD calculates the value of an ordinary Legendre polynomial at a + ! specific latitude. + + ! On entry: + ! cosc - COS(colatitude) + ! n - the degree of the polynomial + + ! On exit: + ! f - the value of the Legendre polynomial of degree N at + ! latitude ASIN(cosc) + + REAL (KIND=HIGH) :: s1, c4, a, b, fk, f, cosc, colat, c1, fn, ang + INTEGER :: n, k + + ! Determine the colatitude + + colat = ACOS(cosc) + + c1 = SQRT(2.0_HIGH) + DO k=1,n + c1 = c1 * SQRT( 1.0_HIGH - 1.0_HIGH/REAL((4*k*k),HIGH) ) + END DO + + fn = n + ang= fn * colat + s1 = 0.0_HIGH + c4 = 1.0_HIGH + a =-1.0_HIGH + b = 0.0_HIGH + DO k=0,n,2 + IF (k.eq.n) c4 = 0.5_HIGH * c4 + s1 = s1 + c4 * COS(ang) + a = a + 2.0_HIGH + b = b + 1.0_HIGH + fk = k + ang= colat * (fn-fk-2.0_HIGH) + c4 = ( a * (fn-b+1.0_HIGH) / ( b * (fn+fn-a) ) ) * c4 + END DO + + f = s1 * c1 + + END SUBROUTINE lgord + + + SUBROUTINE llij_gauss (lat, lon, proj, i, j) + + IMPLICIT NONE + + REAL(R8) , INTENT(IN) :: lat, lon + REAL(R8) , INTENT(OUT) :: i, j + TYPE (proj_info), INTENT(IN) :: proj + + INTEGER :: n , n_low + LOGICAL :: found = .FALSE. + REAL(R8) :: diff_1 , diff_nlat + + ! The easy one first, get the x location. The calling routine has already made + ! sure that the necessary assumptions concerning the sign of the deltalon and the + ! relative east/west'ness of the longitude and the starting longitude are consistent + ! to allow this easy computation. + + i = ( lon - proj%lon1 ) / proj%loninc + 1.0_r8 + + ! Since this is a global data set, we need to be concerned about wrapping the + ! fields around the globe. + +! IF ( ( proj%loninc .GT. 0 ) .AND. & +! ( FLOOR((lon-proj%lon1)/proj%loninc) + 1 .GE. proj%ixdim ) .AND. & +! ( lon + proj%loninc .GE. proj%lon1 + 360 ) ) THEN +!! BUG: We may need to set proj%wrap, but proj is intent(in) +!! WHAT IS THIS USED FOR? +!! proj%wrap = .TRUE. +! i = proj%ixdim +! ELSE IF ( ( proj%loninc .LT. 0 ) .AND. & +! ( FLOOR((lon-proj%lon1)/proj%loninc) + 1 .GE. proj%ixdim ) .AND. & +! ( lon + proj%loninc .LE. proj%lon1 - 360 ) ) THEN +! ! BUG: We may need to set proj%wrap, but proj is intent(in) +! ! WHAT IS THIS USED FOR? +! ! proj%wrap = .TRUE. +! i = proj%ixdim +! END IF + + ! Yet another quicky test, can we find bounding values? If not, then we may be + ! dealing with putting data to a polar projection, so just give them them maximal + ! value for the location. This is an OK assumption for the interpolation across the + ! top of the pole, given how close the longitude lines are. + + IF ( ABS(lat) .GT. ABS(proj%gauss_lat(1)) ) THEN + + diff_1 = lat - proj%gauss_lat(1) + diff_nlat = lat - proj%gauss_lat(proj%nlat*2) + + IF ( ABS(diff_1) .LT. ABS(diff_nlat) ) THEN + j = 1 + ELSE + j = proj%nlat*2 + END IF + + ! If the latitude is between the two bounding values, we have to search and interpolate. + + ELSE + + DO n = 1 , proj%nlat*2 -1 + IF ( ( proj%gauss_lat(n) - lat ) * ( proj%gauss_lat(n+1) - lat ) .LE. 0.0_r8 ) THEN + found = .TRUE. + n_low = n + EXIT + END IF + END DO + + ! Everything still OK? + + IF ( .NOT. found ) THEN + PRINT '(A)','Troubles in river city. No bounding values of latitude found in the Gaussian routines.' + write(6,*) 'ERROR: Gee_no_bounding_lats_Gaussian' + END IF + + j = ( ( proj%gauss_lat(n_low) - lat ) * REAL( (n_low + 1),r8 ) + & + ( lat - proj%gauss_lat(n_low+1) ) * REAL( n_low,r8 ) ) / & + ( proj%gauss_lat(n_low) - proj%gauss_lat(n_low+1) ) + + END IF + + END SUBROUTINE llij_gauss + +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +!nc -- This subroutine was in the original module_map_utils.f90 (before PROJ_CASSINI), and +! the model_mod.f90 would still like it to be, hence we are including it. + SUBROUTINE gridwind_to_truewind(lon,proj,ugrid,vgrid,utrue,vtrue) + + ! Subroutine to convert a wind from grid north to true north. + + IMPLICIT NONE + + ! Arguments + REAL(r8), INTENT(IN) :: lon ! Longitude of point in degrees + TYPE(proj_info),INTENT(IN) :: proj ! Projection info structure + REAL(r8), INTENT(IN) :: ugrid ! U-component, grid-relative + REAL(r8), INTENT(IN) :: vgrid ! V-component, grid-relative + REAL(r8), INTENT(OUT) :: utrue ! U-component, earth-relative + REAL(r8), INTENT(OUT) :: vtrue ! V-component, earth-relative + + ! Locals + REAL(r8) :: alpha + REAL(r8) :: diff + + IF ((proj%code .EQ. PROJ_PS).OR.(proj%code .EQ. PROJ_LC))THEN + diff = lon - proj%stdlon + IF (diff .GT. 180.0_r8) diff = diff - 360.0_r8 + IF (diff .LT.-180.0_r8) diff = diff + 360.0_r8 + alpha = diff * proj%cone * rad_per_deg * SIGN(1.0_r8,proj%truelat1) + utrue = vgrid * SIN(alpha) + ugrid * COS(alpha) + vtrue = vgrid * COS(alpha) - ugrid * SIN(alpha) +!nc -- we added in a case structure for CASSINI and CYL + ELSEIF ((proj%code .EQ. PROJ_MERC).OR.(proj%code .EQ. PROJ_LATLON) & + .OR.(proj%code .EQ. PROJ_CASSINI).OR.(proj%code .EQ. PROJ_CYL))THEN + utrue = ugrid + vtrue = vgrid + ELSE + PRINT '(A)', 'Unrecognized projection.' + STOP 'GRIDWIND_TO_TRUEWIND' + ENDIF + + RETURN + END SUBROUTINE gridwind_to_truewind +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +!nc -- This subroutine was in the original module_map_utils.f90 (before PROJ_CASSINI), and +! the model_mod.f90 would still like it to be, hence we are including it. + SUBROUTINE truewind_to_gridwind(lon,proj,utrue,vtrue,ugrid,vgrid) + + ! Subroutine to compute grid-relative u/v wind components from the earth- + ! relative values for a given projection. + + IMPLICIT NONE + + ! Arguments + REAL(r8), INTENT(IN) :: lon ! Longitude of point in degrees + TYPE(proj_info),INTENT(IN) :: proj ! Projection info structure + REAL(r8), INTENT(IN) :: utrue ! U-component, earth-relative + REAL(r8), INTENT(IN) :: vtrue ! V-component, earth-relative + REAL(r8), INTENT(OUT) :: ugrid ! U-component, grid-relative + REAL(r8), INTENT(OUT) :: vgrid ! V-component, grid-relative + + ! Locals + REAL(r8) :: alpha + REAL(r8) :: diff + + IF ((proj%code .EQ. PROJ_PS).OR.(proj%code .EQ. PROJ_LC))THEN + + diff = proj%stdlon - lon + IF (diff .GT. 180.0_r8) diff = diff - 360.0_r8 + IF (diff .LT.-180.0_r8) diff = diff + 360.0_r8 + alpha = diff * proj%cone * rad_per_deg * SIGN(1.0_r8,proj%truelat1) + ugrid = vtrue * SIN(alpha) + utrue * COS(alpha) + vgrid = vtrue * COS(alpha) - utrue * SIN(alpha) +!nc -- we added in a case structure for CASSINI and CYL + ELSEIF ((proj%code .EQ. PROJ_MERC).OR.(proj%code .EQ. PROJ_LATLON) & + .OR.(proj%code .EQ. PROJ_CASSINI).OR.(proj%code .EQ. PROJ_CYL))THEN + ugrid = utrue + vgrid = vtrue + ELSE + PRINT '(A)', 'Unrecognized map projection.' + STOP 'TRUEWIND_TO_GRIDWIND' + ENDIF + RETURN + END SUBROUTINE truewind_to_gridwind +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +END MODULE map_utils + diff --git a/models/wrf_unified/readme.rst b/models/wrf_unified/readme.rst new file mode 100644 index 0000000000..0f37ad09fe --- /dev/null +++ b/models/wrf_unified/readme.rst @@ -0,0 +1,369 @@ +WRF +=== + +Overview +-------- + + +DART interface module for the Weather Research and Forecasting +`(WRF) `__ +model. This page documents the details of the +module compiled into DART that interfaces with the WRF data in the state vector. +**The WRF-DART interface is compatible with WRF versions 4 and later, and is +no longer backwards compatible with WRFv3.9 and earlier.** +For more information on the interface changes required between +different WRF versions see the WRF tutorial link in the next section. + +WRF+DART Tutorial +----------------- + +**There is additional overview and tutorial documentation for running a WRF/DART +assimilation in** :doc:`./tutorial/README` + +Please work through the tutorial in order to learn how to run WRF and DART. + +Items of Note +~~~~~~~~~~~~~ + +- The ``model_mod`` reads WRF netCDF files directly to acquire the model state + data. The ``wrf_to_dart`` and ``dart_to_wrf`` programs are no longer + necessary. +- A netCDF file named ``wrfinput_d01`` is required and must be at the same + resolution and have the same surface elevation data as the files converted to + create the DART initial conditions. No data will be read from this file, but + the grid information must match exactly. + +The model interface code supports WRF configurations with multiple domains. Data +for all domains is read into the DART state vector. During the computation of +the forward operators (getting the estimated observation values from each +ensemble member), the search starts in the domain with the highest number, which +is generally the finest nest or one of multiple finer nests. The search stops as +soon as a domain contains the observation location, working its way from largest +number to smallest number domain, ending with domain 1. For example, in a 4 +domain case the data in the state vector that came from ``wrfinput_d04`` is +searched first, then ``wrfinput_d03``, ``wrfinput_d02``, and finally +``wrfinput_d01``. + +The forward operator is computed from the first domain grid that contains the +lat/lon of the observation. During the assimilation phase, when the state values +are adjusted based on the correlations and assimilation increments, all points +in all domains that are within the localization radius are adjusted, regardless +of domain. The impact of an observation on the state depends only on the +distance between the observation and the state vector point, and the regression +coefficient based on the correlation between the distributions of the ensemble +of state vector points and the ensemble of observation forward operator values. + +The fields from WRF that are copied into the DART state vector are controlled by +namelist. See below for the documentation on the &model_nml entries. The state +vector should include all fields needed to restart a WRF run. There may be +additional fields needed depending on the microphysics scheme selected. See the +ascii file ``wrf_state_variables_table`` in the ``models/wrf`` directory for a +list of fields that are often included in the DART state. + +Namelist +-------- + +The ``&model_nml`` namelist is read from the ``input.nml`` file. Namelists +start with an ampersand ``&`` and terminate with a slash ``/``. Character +strings that contain a ``/`` must be enclosed in quotes to prevent them from +prematurely terminating the namelist. + +.. code-block:: + + &model_nml + default_state_variables = .true. + wrf_state_variables = 'NULL' + wrf_state_bounds = 'NULL' + num_domains = 1 + calendar_type = 3 + assimilation_period_seconds = 21600 + allow_obs_below_vol = .false. + vert_localization_coord = 3 + center_search_half_length = 500000. + center_spline_grid_scale = 10 + circulation_pres_level = 80000.0 + circulation_radius = 108000.0 + sfc_elev_max_diff = -1.0 + polar = .false. + periodic_x = .false. + periodic_y = .false. + scm = .false. + allow_perturbed_ics = .false. # testing purposes only + / + + # Notes for model_nml: + # (1) vert_localization_coord must be one of: + # 1 = model level + # 2 = pressure + # 3 = height + # 4 = scale height + # (2) see bottom of this file for explanations of polar, periodic_x, + # periodic_y, and scm + # (3) calendar = 3 is GREGORIAN, which is what WRF uses. + # (4) if 'default_state_variables' is .true. the model_mod.f90 code will + # fill the state variable table with the following wrf vars: + # U, V, W, PH, T, MU + # you must set it to false before you change the value + # of 'wrf_state_variables' and have it take effect. + # (5) the format for 'wrf_state_variables' is an array of 5 strings: + # wrf netcdf variable name, dart QTY_xxx string, type string (must be + # unique, will soon be obsolete, we hope), 'UPDATE', and '999' if the + # array is part of all domains. otherwise, it is a string with the domain + # numbers (e.g. '12' for domains 1 and 2, '13' for domains 1 and 3). + # example: + # wrf_state_variables='U','QTY_U_WIND_COMPONENT','TYPE_U','UPDATE','999', + # 'V','QTY_V_WIND_COMPONENT','TYPE_V','UPDATE','999', + # 'W','QTY_VERTICAL_VELOCITY','TYPE_W','UPDATE','999', + # 'T','QTY_POTENTIAL_TEMPERATURE','TYPE_T','UPDATE','999', + # 'PH','QTY_GEOPOTENTIAL_HEIGHT','TYPE_GZ','UPDATE','999', + # 'MU','QTY_PRESSURE','TYPE_MU','UPDATE','999', + # 'QVAPOR','QTY_VAPOR_MIXING_RATIO','TYPE_QV','UPDATE','999', + # 'QCLOUD','QTY_CLOUD_LIQUID_WATER','TYPE_QC','UPDATE','999', + # 'QRAIN','QTY_RAINWATER_MIXING_RATIO','TYPE_QR','UPDATE','999', + # 'U10','QTY_U_WIND_COMPONENT','TYPE_U10','UPDATE','999', + # 'V10','QTY_V_WIND_COMPONENT','TYPE_V10','UPDATE','999', + # 'T2','QTY_TEMPERATURE','TYPE_T2','UPDATE','999', + # 'TH2','QTY_POTENTIAL_TEMPERATURE','TYPE_TH2','UPDATE','999', + # 'Q2','QTY_SPECIFIC_HUMIDITY','TYPE_Q2','UPDATE','999', + # 'PSFC','QTY_PRESSURE','TYPE_PS','UPDATE','999', + # (6) the format for 'wrf_state_bounds' is an array of 4 strings: + # wrf netcdf variable name, minimum value, maximum value, and either + # FAIL or CLAMP. FAIL will halt the program if an out of range value + # is detected. CLAMP will set out of range values to the min or max. + # The special string 'NULL' will map to plus or minus infinity and will + # not change the values. arrays not listed in this table will not + # be changed as they are read or written. + # + # + # polar and periodic_x are used in global wrf. if polar is true, the + # grid interpolation routines will wrap over the north and south poles. + # if periodic_x is true, when the east and west edges of the grid are + # reached the interpolation will wrap. note this is a separate issue + # from regional models which cross the GMT line; those grids are marked + # as having a negative offset and do not need to wrap; this flag controls + # what happens when the edges of the grid are reached. + + # the scm flag is used for the 'single column model' version of WRF. + # it needs the periodic_x and periodic_y flags set to true, in which + # case the X and Y directions are periodic; no collapsing of the grid + # into a single location like the 3d-spherical polar flag implies. + +Description of each namelist entry +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ++---------------------------------------+-------------------+---------------------------------------+ +| Item | Type | Description | ++=======================================+===================+=======================================+ +| default_state_variables | logical | If *.true.*, the dart state vector | +| | | contains the fields U, V, W, PH, T, | +| | | MU, in that order, and only those. | +| | | Any values listed in the | +| | | *wrf_state_variables* namelist item | +| | | will be ignored. | ++---------------------------------------+-------------------+---------------------------------------+ +| wrf_state_variables | character(:, 5) | A 2D array of strings, 5 per wrf | +| | | array to be added to the dart state | +| | | vector. If *default_state_variables* | +| | | is *.true.*, this is ignored. When | +| | | *.false.*, this list of array names | +| | | controls which arrays and the order | +| | | that they are added to the state | +| | | vector. The 5 strings are: | +| | | | +| | | #. WRF field name - must match netcdf | +| | | name exactly | +| | | #. DART KIND name - must match a | +| | | valid DART QTY_xxx exactly | +| | | #. TYPE_NN - will hopefully be | +| | | obsolete, but for now NN should | +| | | match the field name. | +| | | #. the string UPDATE. at some future | +| | | point, non-updatable fields may | +| | | become part of the state vector. | +| | | #. A numeric string listing the | +| | | domain numbers this array is part | +| | | of. The specical string 999 means | +| | | all domains. For example, '12' | +| | | means domains 1 and 2, '13' means | +| | | 1 and 3. | ++---------------------------------------+-------------------+---------------------------------------+ +| wrf_state_bounds | character(:, 4) | A 2D array of strings, 4 per wrf | +| | | array. During the copy of data to and | +| | | from the wrf netcdf file, variables | +| | | listed here will have minimum and | +| | | maximum values enforced. The 4 | +| | | strings are: | +| | | | +| | | #. WRF field name - must match netcdf | +| | | name exactly | +| | | #. Minimum -- specified as a string | +| | | but must be a numeric value (e.g. | +| | | '0.1') Can be 'NULL' to allow any | +| | | minimum value. | +| | | #. Maximum -- specified as a string | +| | | but must be a numeric value (e.g. | +| | | '0.1') Can be 'NULL' to allow any | +| | | maximum value. | +| | | #. Action -- valid strings are | +| | | 'CLAMP', 'FAIL'. 'FAIL' means if a | +| | | value is found outside the range, | +| | | the code fails with an error. | +| | | 'CLAMP' simply sets the out of | +| | | range values to the given minimum | +| | | or maximum without error. | ++---------------------------------------+-------------------+---------------------------------------+ +| num_domains | integer | Total number of WRF domains, | +| | | including nested domains. | ++---------------------------------------+-------------------+---------------------------------------+ +| calendar_type | integer | Calendar type. Should be 3 | +| | | (GREGORIAN) for WRF. | ++---------------------------------------+-------------------+---------------------------------------+ +| assimilation_period_seconds | integer | The time (in seconds) between | +| | | assimilations. This is modified if | +| | | necessary to be an integer multiple | +| | | of the underlying model timestep. | ++---------------------------------------+-------------------+---------------------------------------+ +| periodic_x | logical | If *.true.*, the grid is periodic in | +| | | longitude, and points above the last | +| | | grid cell and points below the first | +| | | grid cell are wrapped. Note this is | +| | | not the same as a grid which crosses | +| | | the prime meridian. WRF handles that | +| | | with an offset in longitude and | +| | | points beyond the last grid index are | +| | | outside the domain. | ++---------------------------------------+-------------------+---------------------------------------+ +| periodic_y | logical | Used for the Single Column Model to | +| | | make the grid wrap in Y (see scm | +| | | below). This is NOT the same as | +| | | wrapping in latitude (see polar | +| | | below). | ++---------------------------------------+-------------------+---------------------------------------+ +| polar | logical | If *.true.*, points at the poles are | +| | | wrapped across the grid. It is not | +| | | clear this is a good idea since the | +| | | grid is degnerate here. | ++---------------------------------------+-------------------+---------------------------------------+ +| scm | logical | If *.true.* the Single Column Model | +| | | is assumed. The grid is a single | +| | | vertical column, and there are 9 | +| | | cells arranged in a 3x3 grid. See the | +| | | WRF documentation for more | +| | | information on this configuration. | +| | | *periodic_x* and *periodic_y* should | +| | | also be *.true.* in this case. | ++---------------------------------------+-------------------+---------------------------------------+ +| sfc_elev_max_diff | real(r8) | If > 0, the maximum difference, in | +| | | meters, between an observation marked | +| | | as a 'surface obs' as the vertical | +| | | type (with the surface elevation, in | +| | | meters, as the numerical vertical | +| | | location), and the surface elevation | +| | | as defined by the model. Observations | +| | | further away from the surface than | +| | | this threshold are rejected and not | +| | | assimilated. If the value is | +| | | negative, this test is skipped. | ++---------------------------------------+-------------------+---------------------------------------+ +| allow_obs_below_vol | logical | If *.false.* then if an observation | +| | | with a vertical coordinate of | +| | | pressure or height (i.e. not a | +| | | surface observation) is below the | +| | | lowest 3d sigma level, it is outside | +| | | the field volume and the | +| | | interpolation routine rejects it. If | +| | | this is set to *.true.* and the | +| | | observation is above the surface | +| | | elevation but below the lowest field | +| | | volume level, the code will | +| | | extrapolate downward from data values | +| | | at levels 1 and 2. | ++---------------------------------------+-------------------+---------------------------------------+ +| center_search_half_length | real(r8) | The model_mod now contains two | +| | | schemes for searching for a vortex | +| | | center location. If the **old** | +| | | scheme is compiled in, then this and | +| | | the center_spline_grid_scale namelist | +| | | items are used. (Search code for | +| | | 'use_old_vortex'.) Half length (in | +| | | meters) of a square box for searching | +| | | the vortex center. | ++---------------------------------------+-------------------+---------------------------------------+ +| center_spline_grid_scale | integer | The model_mod now contains two | +| | | schemes for searching for a vortex | +| | | center location. If the **old** | +| | | scheme is compiled in, then this and | +| | | the center_search_half_length | +| | | namelist items are used. (Search code | +| | | for 'use_old_vortex'.) Ratio of | +| | | refining grid for | +| | | spline-interpolation in determining | +| | | the vortex center. | ++---------------------------------------+-------------------+---------------------------------------+ +| circulation_pres_level | real(r8) | The model_mod now contains two | +| | | schemes for searching for a vortex | +| | | center location. If the **new** | +| | | scheme is compiled in, then this and | +| | | the circulation_radius namelist items | +| | | are used. (Search code for | +| | | 'use_old_vortex'.) Pressure, in | +| | | pascals, of the level at which the | +| | | circulation is computed when | +| | | searching for the vortex center. | ++---------------------------------------+-------------------+---------------------------------------+ +| circulation_radius | real(r8) | The model_mod now contains two | +| | | schemes for searching for a vortex | +| | | center location. If the **new** | +| | | scheme is compiled in, then this and | +| | | the circulation_pres_level namelist | +| | | items are used. (Search code for | +| | | 'use_old_vortex'.) Radius, in meters, | +| | | of the circle over which the | +| | | circulation calculation is done when | +| | | searching for the vortex center. | ++---------------------------------------+-------------------+---------------------------------------+ +| vert_localization_coord | integer | Vertical coordinate for vertical | +| | | localization. | +| | | | +| | | - 1 = model level | +| | | - 2 = pressure (in pascals) | +| | | - 3 = height (in meters) | +| | | - 4 = scale height (unitless) | ++---------------------------------------+-------------------+---------------------------------------+ +| allow_perturbed_ics | logical | *allow_perturbed_ics* should not be | +| | | used in most cases. It is provided | +| | | only as a means to create a tiny | +| | | ensemble for non-advancing tests. | +| | | Creating an initial ensemble is | +| | | covered in :doc:`./tutorial/README` | ++---------------------------------------+-------------------+---------------------------------------+ + + +The following items used to be in the WRF namelist but have been removed. The +first 4 are no longer needed, and the last one was moved to the +``&dart_to_wrf_nml`` namelist in 2010. In the Lanai release having these values +in the namelist does not cause a fatal error, but more recent versions of the +code will fail if any of these values are specified. Remove them from your +namelist to avoid errors. + +=================== ================= ========================================= +Item Type Description +=================== ================= ========================================= +``surf_obs`` logical OBSOLETE -- now an error to specify this. +``soil_data`` logical OBSOLETE -- now an error to specify this. +``h_diab`` logical OBSOLETE -- now an error to specify this. +``num_moist_vars`` integer OBSOLETE -- now an error to specify this. +``adv_mod_command`` character(len=32) OBSOLETE -- now an error to specify this. +=================== ================= ========================================= + +Files +----- + +- model_nml in input.nml +- wrfinput_d01, wrfinput_d02, ... (one file for each domain) +- netCDF output state diagnostics files + +References +---------- + +https://www2.mmm.ucar.edu/wrf/users/docs/user_guide_v4/contents.html diff --git a/models/wrf_unified/select.f90 b/models/wrf_unified/select.f90 new file mode 100644 index 0000000000..343e9354fe --- /dev/null +++ b/models/wrf_unified/select.f90 @@ -0,0 +1,167 @@ +! DART software - Copyright UCAR. This open source software is provided +! by UCAR, "as is", without charge, subject to all terms of use at +! http://www.image.ucar.edu/DAReS/DART/DART_download +! +! $Id$ + +PROGRAM select + +use types_mod, only : r8, metadatalength +use utilities_mod, only : initialize_utilities, finalize_utilities, & + register_module, error_handler, E_MSG +use obs_sequence_mod, only : obs_type, obs_sequence_type, init_obs_sequence, & + insert_obs_in_seq, get_first_obs, get_next_obs, & + write_obs_seq, & + assignment(=), & + init_obs, static_init_obs_sequence, & + get_num_obs, get_num_copies, get_num_qc, & + get_obs_def, read_obs_seq, & + get_copy_meta_data, set_copy_meta_data, & + get_qc_meta_data, set_qc_meta_data +use obs_kind_mod, only : RADIOSONDE_U_WIND_COMPONENT, & + RADIOSONDE_V_WIND_COMPONENT, & + RADIOSONDE_SURFACE_PRESSURE, & + RADIOSONDE_TEMPERATURE, & + RADIOSONDE_SPECIFIC_HUMIDITY +use obs_def_mod, only : obs_def_type, get_obs_def_type_of_obs, & + get_obs_def_time, get_obs_def_location +use location_mod, only : location_type, get_location +use time_manager_mod, only : time_type, operator(/=), get_time, print_time, & + set_calendar_type, GREGORIAN + +implicit none + +! version controlled file description for error handling, do not edit +character(len=256), parameter :: source = & + "$URL$" +character(len=32 ), parameter :: revision = "$Revision$" +character(len=128), parameter :: revdate = "$Date$" + +type(obs_sequence_type) :: seq, real_seq +type(obs_type) :: obs, prev_obs, real_obs +type(obs_def_type) :: real_obs_def +type(location_type) :: location +type(time_type) :: time, prev_time + +integer :: seconds, days, kind, delta +real(r8), dimension(3) :: loc + +integer :: i +integer :: num_obs, num_copies, num_qc, real_seq_num_obs + +character(len = metadatalength) :: meta_data + +character(len = 129) :: out_file_name = 'obs_seq.out', & + in_file_name = 'obs_seq.in' +integer :: calendar_type = GREGORIAN + +!------------------------------------------------------------------------------ + +logical :: is_this_last, is_there_one + +call initialize_utilities('Select') +call register_module(source, revision, revdate) + +call set_calendar_type(calendar_type) + +call static_init_obs_sequence() + +! ------------------------------------------------------------------- +! Initialize the counters: + +num_obs = 0 + +delta = 10800 + +call read_obs_seq(in_file_name, 0, 0, 0, real_seq) + +num_copies = get_num_copies(real_seq) +real_seq_num_obs = get_num_obs(real_seq) +num_qc = get_num_qc(real_seq) + +! Initialize an obs_sequence structure +call init_obs_sequence(seq, num_copies, num_qc, real_seq_num_obs) + +do i = 1, num_copies + meta_data = get_copy_meta_data(real_seq, i) + call set_copy_meta_data(seq, i, meta_data) +enddo + +do i = 1, num_qc + meta_data = get_qc_meta_data(real_seq, i) + call set_qc_meta_data(seq, i, meta_data) +enddo + +call init_obs(obs, num_copies, num_qc) + +print*,'Number of observations in original file: ',real_seq_num_obs + +do i = 1, real_seq_num_obs + + if(i > 1) then + + call get_next_obs(real_seq, real_obs, real_obs, is_this_last) + prev_time = time + + else + + is_there_one = get_first_obs(real_seq, real_obs) + + endif + + call get_obs_def(real_obs, real_obs_def) + + time = get_obs_def_time(real_obs_def) + + call get_time(time, seconds, days) + +!!$ if(time /= prev_time) call print_time(time) + + location = get_obs_def_location(real_obs_def) + loc = get_location(location) + + kind = get_obs_def_type_of_obs(real_obs_def) + + if ( & +!!$ seconds >= (86401 - delta) .or. & +!!$ seconds <= delta .or. & +!!$ (seconds >= (43201 - delta) .and. seconds <= (43200 + delta)) .and. & + (loc(2) >= 0.0_r8) .and. & ! NH + ((kind == RADIOSONDE_U_WIND_COMPONENT) .or. & + (kind == RADIOSONDE_V_WIND_COMPONENT) .or. & + (kind == RADIOSONDE_TEMPERATURE) .or. & + (kind == RADIOSONDE_SPECIFIC_HUMIDITY)) & + ) then + + obs = real_obs + + num_obs = num_obs + 1 + + if(num_obs == 1) then + call insert_obs_in_seq(seq, obs) + else + call insert_obs_in_seq(seq, obs, prev_obs) + endif + + prev_obs = obs + + endif + +enddo + +write(unit=*, fmt='(5x,a,i6,a)') & + 'Total number of observations: ', num_obs + +call write_obs_seq(seq, out_file_name) + +call error_handler(E_MSG,'select','FINISHED select.') +call error_handler(E_MSG,'select','Finished successfully.',source,revision,revdate) +call finalize_utilities() + +END PROGRAM select + +! +! $URL$ +! $Id$ +! $Revision$ +! $Date$ diff --git a/models/wrf_unified/work/input.nml b/models/wrf_unified/work/input.nml new file mode 100644 index 0000000000..2c19329ded --- /dev/null +++ b/models/wrf_unified/work/input.nml @@ -0,0 +1,560 @@ +&probit_transform_nml + / + +&algorithm_info_nml + qceff_table_filename = '' + / + +&perfect_model_obs_nml + read_input_state_from_file = .true. + single_file_in = .false. + input_state_files = 'wrfinput_d01' + init_time_days = -1 + init_time_seconds = -1 + + write_output_state_to_file = .false. + single_file_out = .false. + output_state_files = 'perfect_output_d01.nc' + output_interval = 1 + + obs_seq_in_file_name = "obs_seq.in" + obs_seq_out_file_name = "obs_seq.out" + first_obs_days = -1 + first_obs_seconds = -1 + last_obs_days = -1 + last_obs_seconds = -1 + + async = 0 + adv_ens_command = "../shell_scripts/advance_model.csh" + + trace_execution = .true. + output_timestamps = .false. + print_every_nth_obs = -1 + output_forward_op_errors = .true. + silence = .false. + / + +&filter_nml + async = 0, + adv_ens_command = "../shell_scripts/advance_model.csh", + ens_size = 3, + obs_sequence_in_name = "obs_seq.out", + obs_sequence_out_name = "obs_seq.final", + input_state_file_list = "input_list.txt" + output_state_file_list = "output_list.txt" + init_time_days = -1, + init_time_seconds = -1, + first_obs_days = -1, + first_obs_seconds = -1, + last_obs_days = -1, + last_obs_seconds = -1, + num_output_state_members = 3, + num_output_obs_members = 3, + output_interval = 1, + num_groups = 1, + distributed_state = .true. + compute_posterior = .true. + output_forward_op_errors = .false., + output_timestamps = .false., + trace_execution = .false., + + stages_to_write = 'preassim', 'output' + output_members = .false. + output_mean = .true. + output_sd = .false. + write_all_stages_at_end = .false. + + inf_flavor = 2, 0, + inf_initial_from_restart = .true., .false., + inf_sd_initial_from_restart = .true., .false., + inf_initial = 1.0, 1.00, + inf_lower_bound = 1.0, 1.0, + inf_upper_bound = 1000000.0, 1000000.0, + inf_damping = 0.9, 1.0, + inf_sd_initial = 0.6, 0.0, + inf_sd_lower_bound = 0.6, 0.0, + inf_sd_max_change = 1.05, 1.05, + / + +&quality_control_nml + input_qc_threshold = 3.0, + outlier_threshold = 3.0, + enable_special_outlier_code = .false. + / + +&fill_inflation_restart_nml + write_prior_inf = .true. + prior_inf_mean = 1.00 + prior_inf_sd = 0.6 + + write_post_inf = .false. + post_inf_mean = 1.00 + post_inf_sd = 0.6 + + input_state_files = 'wrfinput_d01', 'wrfinput_d02' + single_file = .false. + verbose = .false. + / + + +# cutoff is in radians; for the earth, 0.05 is about 300 km. +# cutoff is defined to be the half-width of the localization radius, +# so 0.05 radians for cutoff is about an 600 km effective +# localization radius, where the influence of an obs decreases +# to ~half at 300 km, and ~0 at the edges of the area. +&assim_tools_nml + cutoff = 0.05, + sort_obs_inc = .false., + spread_restoration = .false., + sampling_error_correction = .true., + adaptive_localization_threshold = -1, + output_localization_diagnostics = .false., + localization_diagnostics_file = 'localization_diagnostics', + convert_all_state_verticals_first = .true. + convert_all_obs_verticals_first = .true. + print_every_nth_obs = 0, + / + +&cov_cutoff_nml + select_localization = 1, + / + +&obs_sequence_nml + write_binary_obs_sequence = .false., + / + +&preprocess_nml + overwrite_output = .true. + input_obs_qty_mod_file = '../../../assimilation_code/modules/observations/DEFAULT_obs_kind_mod.F90' + output_obs_qty_mod_file = '../../../assimilation_code/modules/observations/obs_kind_mod.f90' + input_obs_def_mod_file = '../../../observations/forward_operators/DEFAULT_obs_def_mod.F90' + output_obs_def_mod_file = '../../../observations/forward_operators/obs_def_mod.f90' + quantity_files = '../../../assimilation_code/modules/observations/atmosphere_quantities_mod.f90' + obs_type_files = '../../../observations/forward_operators/obs_def_reanalysis_bufr_mod.f90', + '../../../observations/forward_operators/obs_def_radar_mod.f90', + '../../../observations/forward_operators/obs_def_metar_mod.f90', + '../../../observations/forward_operators/obs_def_dew_point_mod.f90', + '../../../observations/forward_operators/obs_def_rel_humidity_mod.f90', + '../../../observations/forward_operators/obs_def_altimeter_mod.f90', + '../../../observations/forward_operators/obs_def_gps_mod.f90', + '../../../observations/forward_operators/obs_def_vortex_mod.f90', + '../../../observations/forward_operators/obs_def_gts_mod.f90' + / + +&obs_kind_nml + assimilate_these_obs_types = 'RADIOSONDE_TEMPERATURE', + 'RADIOSONDE_U_WIND_COMPONENT', + 'RADIOSONDE_V_WIND_COMPONENT', + 'SAT_U_WIND_COMPONENT', + 'SAT_V_WIND_COMPONENT', + 'AIRCRAFT_U_WIND_COMPONENT', + 'AIRCRAFT_V_WIND_COMPONENT', + 'AIRCRAFT_TEMPERATURE', + 'ACARS_U_WIND_COMPONENT', + 'ACARS_V_WIND_COMPONENT', + 'ACARS_TEMPERATURE', + 'GPSRO_REFRACTIVITY', + 'LAND_SFC_ALTIMETER' + evaluate_these_obs_types = 'RADIOSONDE_SPECIFIC_HUMIDITY', + / + +# Notes for obs_def_radar_mod_nml: +# (1) Reflectivity limit can be applied to observations and/or forward operator. +# (2) The default constants below match the WRF defaults. They will need to +# be changed for other cases, depending on which microphysics scheme is used. +# + +&obs_def_radar_mod_nml + apply_ref_limit_to_obs = .false., + reflectivity_limit_obs = -10.0, + lowest_reflectivity_obs = -10.0, + apply_ref_limit_to_fwd_op = .false., + reflectivity_limit_fwd_op = -10.0, + lowest_reflectivity_fwd_op = -10.0, + max_radial_vel_obs = 1000000, + allow_wet_graupel = .false., + microphysics_type = 2 , + allow_dbztowt_conv = .false., + dielectric_factor = 0.224, + n0_rain = 8.0e6, + n0_graupel = 4.0e6, + n0_snow = 3.0e6, + rho_rain = 1000.0, + rho_graupel = 400.0, + rho_snow = 100.0, + / + + +# Notes for model_nml: +# (1) vert_localization_coord must be one of: +# 1 = model level +# 2 = pressure +# 3 = height +# 4 = scale height + +# set default_state_variables to .false. to use the explicit list. +# otherwise it uses a hardcoded default list: U, V, W, PH, T, MU, QV only. +# see ../wrf_state_variables_table for a full list of what wrf fields are +# supported in the DART state vector, and what settings should be used here. +# 'UPDATE' and 'NO_COPY_BACK' are supported in the 4th column; 'NO_UPDATE' is +# not yet supported. + +&model_nml + default_state_variables = .false., + wrf_state_variables = 'U', 'QTY_U_WIND_COMPONENT', 'UPDATE','2', + 'V', 'QTY_V_WIND_COMPONENT', 'UPDATE','999', + 'W', 'QTY_VERTICAL_VELOCITY', 'UPDATE','999', + 'PH', 'QTY_GEOPOTENTIAL_HEIGHT', 'UPDATE','999', + 'T', 'QTY_POTENTIAL_TEMPERATURE','UPDATE','999', + 'MU', 'QTY_PRESSURE', 'UPDATE','999', + 'QVAPOR','QTY_VAPOR_MIXING_RATIO', 'UPDATE','12', + 'PSFC', 'QTY_SURFACE_PRESSURE', 'UPDATE','1', + wrf_state_bounds = 'QVAPOR','0.0','NULL', + 'QRAIN', '0.0','NULL', + 'QCLOUD','0.0','NULL', + num_domains = 2, + calendar_type = 3, + assimilation_period_seconds = 21600, + vert_localization_coord = 3, + center_search_half_length = 500000., + center_spline_grid_scale = 10, + sfc_elev_max_diff = -1.0, + circulation_pres_level = 80000.0, + circulation_radius = 108000.0, + allow_obs_below_vol = .false. + / + +# vert_normalization_X is amount of X equiv to 1 radian in horiz. +# vert localization is 'cutoff' times the pressure/height/levels, +# only if horiz_dist_only is set to .false. in the namelist below. +# the default nlon/nlat should be good for most experiments. it sets +# an internal structure that speeds up searches. don't change it +# based on your grid size. nlon must be an odd number. +&location_nml + horiz_dist_only = .false., + vert_normalization_pressure = 6666666.7, + vert_normalization_height = 5000000.0, + vert_normalization_level = 2666.7, + vert_normalization_scale_height = 10.0, + approximate_distance = .false., + nlon = 71, + nlat = 36, + output_box_info = .false., + / + +&utilities_nml + TERMLEVEL = 1, + module_details = .false., + logfilename = 'dart_log.out', + nmlfilename = 'dart_log.nml', + write_nml = 'file', + / + +&mpi_utilities_nml + / + +®_factor_nml + select_regression = 1, + input_reg_file = "time_mean_reg", + save_reg_diagnostics = .false., + reg_diagnostics_file = "reg_diagnostics", + / + +# layout = 2 spreads the IO tasks across the nodes. +# This can greatly improve the performance in IO if +# tasks_per_node is set to match your hardware +&ensemble_manager_nml + layout = 2, + tasks_per_node = 16 + / + +&obs_def_gps_nml + max_gpsro_obs = 100000, + / + +&obs_def_tpw_nml + / + +# The times in the namelist for the obs_diag program are vectors +# that follow the following sequence: +# year month day hour minute second +# max_num_bins can be used to specify a fixed number of bins, +# in which case last_bin_center should be safely in the future. +# +# Acceptable latitudes range from [-90, 90] +# Acceptable longitudes range from [ 0, Inf] + +&obs_diag_nml + obs_sequence_name = 'obs_seq.final', + obs_sequence_list = '', + first_bin_center = 2003, 1, 1, 0, 0, 0 , + last_bin_center = 2003, 1, 2, 0, 0, 0 , + bin_separation = 0, 0, 0,12, 0, 0 , + bin_width = 0, 0, 0, 6, 0, 0 , + time_to_skip = 0, 0, 0, 0, 0, 0 , + max_num_bins = 1000, + trusted_obs = 'null', + Nregions = 4, + lonlim1 = 0.0, 0.0, 0.0, 235.0, + lonlim2 = 360.0, 360.0, 360.0, 295.0, + latlim1 = 20.0, -80.0, -20.0, 25.0, + latlim2 = 80.0, -20.0, 20.0, 55.0, + reg_names = 'Northern Hemisphere', 'Southern Hemisphere', 'Tropics', 'North America', + print_mismatched_locs = .false., + create_rank_histogram = .true., + outliers_in_histogram = .true., + use_zero_error_obs = .false., + verbose = .false. + / + +&schedule_nml + calendar = 'Gregorian', + first_bin_start = 1601, 1, 1, 0, 0, 0, + first_bin_end = 2999, 1, 1, 0, 0, 0, + last_bin_end = 2999, 1, 1, 0, 0, 0, + bin_interval_days = 1000000, + bin_interval_seconds = 0, + max_num_bins = 1000, + print_table = .true., + / + +&obs_seq_to_netcdf_nml + obs_sequence_name = 'obs_seq.final', + obs_sequence_list = '', + append_to_netcdf = .false., + lonlim1 = 0.0, + lonlim2 = 360.0, + latlim1 = -90.0, + latlim2 = 90.0, + verbose = .false., + / + +# There is one GIGANTIC difference between the obsdef_mask.txt and .nc +# The netCDF file intentionally ignores the effect of nTmin/nTmax. +# The netCDF file has ALL matching stations, regardless of temporal coverage. + +&obs_seq_coverage_nml + obs_sequences = '' + obs_sequence_list = 'obs_coverage_list.txt' + obs_of_interest = 'METAR_U_10_METER_WIND' + textfile_out = 'METAR_U_10_METER_WIND_obsdef_mask.txt' + netcdf_out = 'METAR_U_10_METER_WIND_obsdef_mask.nc' + first_analysis = 2003, 1, 1, 0, 0, 0 + last_analysis = 2003, 1, 2, 0, 0, 0 + forecast_length_days = 1 + forecast_length_seconds = 0 + verification_interval_seconds = 21600 + temporal_coverage_percent = 100.0 + lonlim1 = 0.0 + lonlim2 = 360.0 + latlim1 = -90.0 + latlim2 = 90.0 + verbose = .true. + / + +# selections_file is a list of obs_defs output +# from the obs_seq_coverage utility. + +&obs_selection_nml + filename_seq = 'obs_seq.out', + filename_seq_list = '', + filename_out = 'obs_seq.processed', + selections_file = 'obsdef_mask.txt', + selections_is_obs_seq = .false., + print_only = .false., + calendar = 'gregorian', + / + +&obs_seq_verify_nml + obs_sequences = '' + obs_sequence_list = 'obs_verify_list.txt' + input_template = 'obsdef_mask.nc' + netcdf_out = 'forecast.nc' + obtype_string = 'METAR_U_10_METER_WIND' + print_every = 10000 + verbose = .true. + debug = .false. + / + +&obs_sequence_tool_nml + num_input_files = 1, + filename_seq = 'obs_seq.out', + filename_out = 'obs_seq.processed', + first_obs_days = -1, + first_obs_seconds = -1, + last_obs_days = -1, + last_obs_seconds = -1, + obs_types = '', + keep_types = .false., + print_only = .false., + min_lat = -90.0, + max_lat = 90.0, + min_lon = 0.0, + max_lon = 360.0, + / + +&replace_wrf_fields_nml + debug = .false., + fail_on_missing_field = .false., + fieldnames = "SNOWC", + "ALBBCK", + "TMN", + "TSK", + "SH2O", + "SMOIS", + "SEAICE", + "HGT_d01", + "TSLB", + "SST", + "SNOWH", + "SNOW", + fieldlist_file = '', + / + +&obs_common_subset_nml + num_to_compare_at_once = 2, + filename_seq = 'obs_seq1.final', 'obs_seq2.final', + filename_seq_list = '', + filename_out_suffix = '.common' , + calendar = 'Gregorian', + print_every = 1000, + dart_qc_threshold = 3, + print_only = .false., + / + +&wrf_dart_to_fields_nml + include_slp = .true., + include_wind_components = .true., + include_height_on_pres = .true., + include_temperature = .true., + include_rel_humidity = .true., + include_surface_fields = .false., + include_sat_ir_temp = .false., + pres_levels = 70000., + / + +&ncepobs_nml + year = 2010, + month = 06, + day = 00, + tot_days = 1, + max_num = 1000000, + ObsBase = 'temp_obs.', + select_obs = 0, + ADPUPA = .false., + AIRCAR = .false., + AIRCFT = .false., + SATEMP = .false., + SFCSHP = .false., + ADPSFC = .false., + SATWND = .true., + obs_U = .false., + obs_V = .false., + obs_T = .false., + obs_PS = .false., + obs_QV = .false., + daily_file = .true., + obs_time = .false., + lat1 = 10.00, + lat2 = 60.00, + lon1 = 210.0, + lon2 = 300.0 + / + +&prep_bufr_nml + obs_window_upa = 1.0, + obs_window_air = 1.0, + obs_window_cw = 1.0, + otype_use = 242.0, 243.0, 245.0, 246.0, 251.0, 252.0, 253.0, 257.0, 259.0 + qctype_use = 0, 1, 2, 3, 4, 9, 15 + / + +&convert_cosmic_gps_nml + gpsro_netcdf_file = '', + gpsro_netcdf_filelist = 'flist', + gpsro_out_file = 'obs_seq.gpsro', + local_operator = .true., + obs_levels = 0.22, 0.55, 1.1, 1.8, 2.7, 3.7, 4.9, + 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, + ray_ds = 5000.0, + ray_htop = 13000.1, + / + +&wrf_obs_preproc_nml + + file_name_input = 'obs_seq20110901' + file_name_output = 'obs_seq.europe.prev' + + overwrite_obs_time = .false. + + obs_boundary = 0.0 + increase_bdy_error = .false. + maxobsfac = 2.5 + obsdistbdy = 1.0 + + sfc_elevation_check = .false. + sfc_elevation_tol = 3000.0 + obs_pressure_top = 0.0 + obs_height_top = 2.0e10 + + include_sig_data = .true. + tc_sonde_radii = -1.0 + + superob_aircraft = .true. + aircraft_horiz_int = 800.0 + aircraft_pres_int = 25000.0 + + superob_sat_winds = .true. + sat_wind_horiz_int = 800.0 + sat_wind_pres_int = 25000.0 + + overwrite_ncep_satwnd_qc = .false. + overwrite_ncep_sfc_qc = .false. + / + +! sonde_extra = 'obs_seq.rawin' +! land_sfc_extra = 'obs_seq.land_sfc' +! metar_extra = 'obs_seq.metar' +! marine_sfc_extra = 'obs_seq.marine' +! sat_wind_extra = 'obs_seq.satwnd' +! profiler_extra = 'obs_seq.profiler' +! gpsro_extra = 'obs_seq.gpsro' +! acars_extra = 'obs_seq.acars' +! trop_cyclone_extra = 'obs_seq.tc' + +&state_vector_io_nml + single_precision_output = .true., + / + +&compare_states_nml + / + +&closest_member_tool_nml + input_restart_file_list = 'input_file_list_d01.txt', + output_file_name = 'closest_results.txt' + ens_size = 3, + single_restart_file_in = .false., + difference_method = 4, + use_only_qtys = 'QTY_U_WIND_COMPONENT' + / + +# To test both domains, you must change 'model_nml:num_domains = 2' + +&model_mod_check_nml + input_state_files = 'wrfinput_d01', 'wrfinput_d02' + output_state_files = 'mmc_output1.nc', 'mmc_output2.nc' + test1thru = 4 + run_tests = 1,2,3,4,5 + x_ind = 87370 + loc_of_interest = 231.0, 40.0, 500.0 + quantity_of_interest = 'QTY_POTENTIAL_TEMPERATURE' + interp_test_dlon = 0.1 + interp_test_dlat = 0.1 + interp_test_dvert = 1000.0 + interp_test_lonrange = 250.0, 260.0 + interp_test_latrange = 30.0, 45.0 + interp_test_vertrange = 2000.0, 4000.0 + interp_test_vertcoord = 'VERTISHEIGHT' + verbose = .false. + / + diff --git a/models/wrf_unified/work/quickbuild.sh b/models/wrf_unified/work/quickbuild.sh new file mode 100755 index 0000000000..d891488fd7 --- /dev/null +++ b/models/wrf_unified/work/quickbuild.sh @@ -0,0 +1,72 @@ +#!/usr/bin/env bash + +# DART software - Copyright UCAR. This open source software is provided +# by UCAR, "as is", without charge, subject to all terms of use at +# http://www.image.ucar.edu/DAReS/DART/DART_download + +main() { + +export DART=$(git rev-parse --show-toplevel) +source "$DART"/build_templates/buildfunctions.sh + +MODEL=wrf_unified +LOCATION=threed_sphere +EXCLUDE=experiments + + +programs=( +closest_member_tool +filter +model_mod_check +perfect_model_obs +perturb_single_instance +wakeup_filter +) + +serial_programs=( +#radiance_obs_seq_to_netcdf # needs rttov +advance_time +create_fixed_network_seq +create_obs_sequence +fill_inflation_restart +obs_common_subset +obs_diag +obs_selection +obs_seq_coverage +obs_seq_to_netcdf +obs_seq_verify +obs_sequence_tool +) + + +model_serial_programs=( +WRF_DART_utilities/add_pert_where_high_refl +WRF_DART_utilities/advance_cymdh +WRF_DART_utilities/convertdate +WRF_DART_utilities/ensemble_init +WRF_BC/pert_wrf_bc +WRF_DART_utilities/replace_wrf_fields +select +WRF_BC/update_wrf_bc +WRF_DART_utilities/wrf_dart_obs_preprocess +WRF_DART_utilities/extract +WRF_DART_utilities/grid_refl_obs +) + +arguments "$@" + +# clean the directory +\rm -f -- *.o *.mod Makefile .cppdefs + +# build and run preprocess before making any other DART executables +buildpreprocess + +# build DART +buildit + +# clean up +\rm -f -- *.o *.mod + +} + +main "$@" diff --git a/models/wrf_unified/work/runme_filter b/models/wrf_unified/work/runme_filter new file mode 100755 index 0000000000..9774d56639 --- /dev/null +++ b/models/wrf_unified/work/runme_filter @@ -0,0 +1,317 @@ +#!/bin/tcsh +# +# DART software - Copyright UCAR. This open source software is provided +# by UCAR, "as is", without charge, subject to all terms of use at +# http://www.image.ucar.edu/DAReS/DART/DART_download +# +# $Id$ +# +# start at a generic run script for the mpi version. this should probably +# end up in the shell scripts directory - but it is here for now. nsc. +#============================================================================= +# This block of directives constitutes the preamble for the LSF queuing system +# LSF is used on the IBM Linux cluster 'lightning' +# LSF is used on the IMAGe Linux cluster 'coral' +# LSF is used on the IBM 'bluevista' +# The queues on lightning and bluevista are supposed to be similar. +# +# the normal way to submit to the queue is: bsub < runme_filter +# +# an explanation of the most common directives follows: +# -J Job name (master script job.csh presumes filter_server.xxxx.log) +# -o STDOUT filename +# -e STDERR filename +# -P account +# -q queue cheapest == [standby, economy, (regular,debug), premium] == $$$$ +# -n number of processors (really) +##============================================================================= +#BSUB -J filter +#BSUB -o filter.%J.log +#BSUB -q regular +#BSUB -n 16 +##BSUB -P nnnnnnnn +#BSUB -W 1:00 +# +# +##============================================================================= +## This block of directives constitutes the preamble for the PBS queuing system +## PBS is used on the CGD Linux cluster 'bangkok' +## PBS is used on the CGD Linux cluster 'calgary' +## +## the normal way to submit to the queue is: qsub runme_filter +## +## an explanation of the most common directives follows: +## -N Job name +## -r n Declare job non-rerunable +## -e filename for standard error +## -o filename for standard out +## -q Queue name (small, medium, long, verylong) +## -l nodes=xx:ppn=2 requests BOTH processors on the node. On both bangkok +## and calgary, there is no way to 'share' the processors +## on the node with another job, so you might as well use +## them both. (ppn == Processors Per Node) +##============================================================================= +#PBS -N filter +#PBS -r n +#PBS -e filter.err +#PBS -o filter.log +#PBS -q medium +#PBS -l nodes=16:ppn=2 + + +# if async=2, e.g. you are going to run './wrf.exe', single process +# (or possibly 'mpirun -np 1 ./wrf.exe'), so each processor advances +# one ensemble independently of the others, leave this as false. +# +# if async=4, e.g. all the processors advance each wrf.exe in turn with +# mpirun -np 64 wrf.exe (or whatever) for as many ensembles as you have, +# set this to "true" + +# if async=4, also check that the call to advance_model.csh +# has the right number of ensemble members below; it must match +# the input.nml number. + +set parallel_model = "false" + +set num_ens = 16 + +# A common strategy for the beginning is to check for the existence of +# some variables that get set by the different queuing mechanisms. +# This way, we know which queuing mechanism we are working with, +# and can set 'queue-independent' variables for use for the remainder +# of the script. + +if ($?LS_SUBCWD) then + + # LSF has a list of processors already in a variable (LSB_HOSTS) + # alias submit 'bsub < \!*' + + # each filter task advances the ensembles, each running on 1 proc. + if ( "$parallel_model" == "false" ) then + + mpirun.lsf ./filter + + else + + # filter runs in parallel until time to do a model advance, + # and then this script starts up the wrf.exe jobs, each one + # running in parallel. then it runs wakeup_filter to wake + # up filter so it can continue. + + rm -f model_to_filter.lock filter_to_model.lock + mkfifo model_to_filter.lock filter_to_model.lock + + set filterhome = ~/.filter + if ( ! -e $filterhome) mkdir $filterhome + + # this starts filter but also returns control back to + # this script immediately. + + (setenv HOME $filterhome; mpirun.lsf ./filter) & + + while ( -e filter_to_model.lock ) + + set todo=`( echo $< ) < filter_to_model.lock` + echo todo received, value = ${todo} + + if ( "${todo}" == "finished" ) then + echo main script: filter done. + wait + break + + else if ( "${todo}" == "advance" ) then + + # the second number below must match the number + # of ensembles. and in input.nml, the advance model + # command must have -np N with N equal to the number + # of processors this job is using. + + echo calling model advance now: + ./advance_model.csh 0 ${num_ens} filter_control00000 true + + echo restarting filter. + mpirun.lsf ./wakeup_filter + + else + + echo main script: unexpected value received. + break + + endif + + end + + echo filter finished, removing pipes. + rm -f model_to_filter.lock filter_to_model.lock + + if ( -d $filterhome) rmdir $filterhome + endif + + +else if ($?PBS_O_WORKDIR) then + + # PBS has a list of processors in a file whose name is (PBS_NODEFILE) + # alias submit 'qsub \!*' + + # each filter task advances the ensembles, each running on 1 proc. + if ( "$parallel_model" == "false" ) then + + mpirun ./filter + + else + + # filter runs in parallel until time to do a model advance, + # and then this script starts up the wrf.exe jobs, each one + # running in parallel. then it runs wakeup_filter to wake + # up filter so it can continue. + + rm -f model_to_filter.lock filter_to_model.lock + mkfifo model_to_filter.lock filter_to_model.lock + + set filterhome = ~/.filter + if ( ! -e $filterhome) mkdir $filterhome + + # this starts filter but also returns control back to + # this script immediately. + + (setenv HOME $filterhome; mpirun ./filter) & + + while ( -e filter_to_model.lock ) + + set todo=`( echo $< ) < filter_to_model.lock` + echo todo received, value = ${todo} + + if ( "${todo}" == "finished" ) then + echo main script: filter done. + wait + break + + else if ( "${todo}" == "advance" ) then + + # the second number below must match the number + # of ensembles. and in input.nml, the advance model + # command must have -np N with N equal to the number + # of processors this job is using. + + echo calling model advance now: + ./advance_model.csh 0 ${num_ens} filter_control00000 true + + echo restarting filter. + mpirun ./wakeup_filter + + else + + echo main script: unexpected value received. + break + + endif + + end + + echo filter finished, removing pipes. + rm -f model_to_filter.lock filter_to_model.lock + + if ( -d $filterhome) rmdir $filterhome + endif + +else if ($?OCOTILLO_MPINODES) then + + # If you have a linux cluster with no queuing software, use this + # section. The list of computational nodes is given to the mpirun + # command and it assigns them as they appear in the file. In some + # cases it seems to be necessary to wrap the command in a small + # script that changes to the current directory before running. + # (ocotillo is a local ncar cluster, and also a type of desert tree) + + echo "running on ocotillo" + + # before running this script, do this once. the syntax is + # node name : how many tasks you can run on it + #setenv OCOTILLO_MPINODES ~/nodelist + #echo "node7:2" >! $OCOTILLO_MPINODES + #echo "node5:2" >> $OCOTILLO_MPINODES + #echo "node3:2" >> $OCOTILLO_MPINODES + #echo "node1:2" >> $OCOTILLO_MPINODES + + setenv NUM_PROCS 8 + echo "running with $NUM_PROCS nodes specified from $OCOTILLO_MPINODES" + + # each filter task advances the ensembles, each running on 1 proc. + if ( "$parallel_model" == "false" ) then + + mpirun -np $NUM_PROCS -nolocal -machinefile $OCOTILLO_MPINODES ./filter + + else + + # filter runs in parallel until time to do a model advance, + # and then this script starts up the wrf.exe jobs, each one + # running in parallel. then it runs wakeup_filter to wake + # up filter so it can continue. + + rm -f model_to_filter.lock filter_to_model.lock + mkfifo model_to_filter.lock filter_to_model.lock + + set filterhome = ~/.filter + if ( ! -e $filterhome) mkdir $filterhome + + # this starts filter but also returns control back to + # this script immediately. + + (setenv HOME $filterhome; \ + mpirun -np $NUM_PROCS -nolocal -machinefile $OCOTILLO_MPINODES ./filter ) & + + while ( -e filter_to_model.lock ) + + set todo=`( echo $< ) < filter_to_model.lock` + echo todo received, value = ${todo} + + if ( "${todo}" == "finished" ) then + echo main script: filter done. + wait + break + + else if ( "${todo}" == "advance" ) then + + # the second number below must match the number + # of ensembles. and in input.nml, the advance model + # command must have -np N with N equal to the number + # of processors this job is using. + + echo calling model advance now: + ./advance_model.csh 0 ${num_ens} filter_control00000 true + + echo restarting filter. + mpirun -np $NUM_PROCS -nolocal -machinefile $OCOTILLO_MPINODES ./wakeup_filter + + else + + echo main script: unexpected value received. + break + + endif + + end + + echo filter finished, removing pipes. + rm -f model_to_filter.lock filter_to_model.lock + + if ( -d $filterhome) rmdir $filterhome + endif + +else + + # interactive - assume you are using 'lam-mpi' and that you have + # already run 'lamboot' once to start the lam server, or that you + # are running with a machine that has mpich installed. + + echo "running interactively" + mpirun -np 4 ./filter + +endif + +exit 0 + +# +# $URL$ +# $Revision$ +# $Date$