module phys_buffer 73,10 !----------------------------------------------------------------------- ! ! Purpose: ! Implement a physics buffer to hold arrays that must persist ! across timesteps or between calls to different physics packages. ! ! Current implementation only supports one buffer. ! ! Author: B. Eaton ! !----------------------------------------------------------------------- use shr_kind_mod, only: r8 => shr_kind_r8 use infnan, only: nan use spmd_utils, only: masterproc use ppgrid, only: pcols, begchunk, endchunk use phys_grid, only: physgrid_set, write_field_from_chunk, read_chunk_from_field, & get_ncols_p use dyn_grid, only: ptimelevels use string_utils, only: to_upper use abortutils, only: endrun use cam_logfile, only: iulog #ifdef SPMD use mpishorthand, only: mpicom, mpiint #endif use pio, only: file_desc_t, var_desc_t, io_desc_t, & pio_setdebuglevel, pio_seterrorhandling, & pio_double, pio_int, pio_noerr, & pio_inq_dimid, pio_inq_varid, & pio_def_dim, pio_def_var, & pio_put_var, pio_get_var, & pio_write_darray, pio_read_darray, & pio_freedecomp, & pio_bcast_error, pio_internal_error implicit none private save ! Public methods public ::& pbuf_defaultopts, &! set namelist defaults pbuf_setopts, &! set user specified namelist values pbuf_init, &! initialize physics buffer pbuf_add, &! add field to physics buffer pbuf_print, &! print summary of fields in physics buffer pbuf_get_fld_idx, &! get index of specified field in the physics buffer pbuf_get_fld_name, &! get name of specified field given index pbuf_old_tim_idx, &! return the index for the oldest time pbuf_next_tim_idx, &! return the index for the next time pbuf_update_tim_idx,&! update the index for the oldest time pbuf_allocate, &! allocate memory for physics buffer fields pbuf_deallocate, &! deallocate memory for physics buffer fields pbuf_setval, &! set value for a field in the buffer pbuf_write_restart, &! write physics buffer to restart file pbuf_read_restart ! read physics buffer from restart file public :: pbuf_init_restart type(var_desc_t) :: timeidx_desc ! Public types and data type, public :: pbuf_fld character(len=16) :: name character(len=16) :: scope integer :: fdim, mdim, ldim real(r8), pointer, dimension(:,:,:,:,:) :: fld_ptr type(var_desc_t) :: vardesc type(var_desc_t), pointer :: vardesc_list(:) ! used to seperate variables of > 4MB (required for netcdf) type(io_desc_t), pointer :: iodesc end type pbuf_fld integer, public, parameter :: pbuf_size_max=1000 type(pbuf_fld), public, dimension(pbuf_size_max) :: pbuf integer, public :: pbuf_times ! number of time levels in physics buffer (dycore dependent) ! Private module data integer :: pbuf_size = 0 integer :: old_time_idx = 1 logical :: global_allocate_all = .true. ! allocate all buffers as global integer, parameter :: maxvarsize = 536870910 !========================================================================================= contains !========================================================================================= subroutine pbuf_defaultopts(pbuf_global_allocate_out) 1 !----------------------------------------------------------------------- ! Purpose: Return default runtime options !----------------------------------------------------------------------- logical, intent(out), optional :: pbuf_global_allocate_out !----------------------------------------------------------------------- if ( present(pbuf_global_allocate_out) ) then pbuf_global_allocate_out = global_allocate_all endif end subroutine pbuf_defaultopts !========================================================================================= subroutine pbuf_setopts(pbuf_global_allocate_in) 1 !----------------------------------------------------------------------- ! Purpose: Set runtime options !----------------------------------------------------------------------- logical, intent(in), optional :: pbuf_global_allocate_in !----------------------------------------------------------------------- if ( present(pbuf_global_allocate_in) ) then global_allocate_all = pbuf_global_allocate_in endif end subroutine pbuf_setopts !========================================================================================= subroutine pbuf_init() 1 ! Initialize physics buffer. implicit none !----------------------------------------------------------------------------------------- integer :: i pbuf_times = ptimelevels - 1 do i=1,pbuf_size_max nullify(pbuf(i)%fld_ptr) nullify(pbuf(i)%iodesc) end do end subroutine pbuf_init !========================================================================================= subroutine pbuf_add(name, scope, fdim, mdim, ldim, index) 115,4 ! Add a field to the physics buffer implicit none character(len=*), intent(in) :: name ! field name (case insensitive) character(len=*), intent(in) :: scope ! field scope, either 'global' or 'physpkg' integer, intent(in) :: fdim ! first generic field dimension integer, intent(in) :: mdim ! middle generic field dimension integer, intent(in) :: ldim ! last generic field dimension integer, intent(out) :: index ! index in the physics buffer ! Local variables character(len=*), parameter :: sub = 'pbuf_add' integer :: i character(len=len(name)) :: uname ! =to_upper(name) !----------------------------------------------------------------------------------------- if ( pbuf_size >= pbuf_size_max ) then call endrun (sub//': max number physics buffer fields exceeded. Increase pbuf_size_max in phys_buffer.F90') end if uname = to_upper(name) do i = 1, pbuf_size if ( pbuf(i)%name == uname ) then call endrun (sub//': ERROR: field name '//name//' is already in use.') end if end do if ( scope /= 'global' .and. scope /= 'physpkg' ) then call endrun (sub//': scope must be set to global or physpkg. Current value is: '//scope) end if pbuf_size = pbuf_size + 1 index = pbuf_size pbuf(index)%name = name pbuf(index)%scope = scope pbuf(index)%fdim = fdim pbuf(index)%mdim = mdim pbuf(index)%ldim = ldim end subroutine pbuf_add !========================================================================================= subroutine pbuf_print() 1 ! Print summary of fields in physics buffer ! Local variables integer :: i !----------------------------------------------------------------------------------------- write(iulog,*) ' Fields in physics buffer' write(iulog,*) ' name, scope, fdim, mdim, ldim' do i = 1, pbuf_size write(iulog,*) pbuf(i)%name, pbuf(i)%scope, pbuf(i)%fdim, pbuf(i)%mdim, pbuf(i)%ldim end do end subroutine pbuf_print !========================================================================================= function pbuf_get_fld_name(idx) 1 character(len=16) :: pbuf_get_fld_name integer, intent(in) :: idx pbuf_get_fld_name = pbuf(idx)%name return end function pbuf_get_fld_name !========================================================================================= function pbuf_get_fld_idx(name, failcode) 195,3 ! Get index of specified field in the physics buffer. String matching is case insensitive. ! Call endrun if name not found implicit none character(len=*), intent(in) :: name ! field name integer, intent(in), optional :: failcode ! Return value integer :: pbuf_get_fld_idx ! Local variables integer :: i character(len=len(name)) :: Uname ! =to_upper(name) !----------------------------------------------------------------------------------------- ! ! Search for specified field in physics buffer, assuming that case of ! argument "name" matches definition in pbuf structure. ! do i = 1, pbuf_size if ( pbuf(i)%name == name ) then pbuf_get_fld_idx = i return end if end do ! ! Search for specified field in physics buffer, assuming that case of ! argument "name" does not matches definition in pbuf structure. In this ! instance, convert all names to upper case. ! Uname = to_upper(name) do i = 1, pbuf_size if ( to_upper(pbuf(i)%name) == Uname ) then pbuf_get_fld_idx = i return end if end do if ( present(failcode)) then pbuf_get_fld_idx = failcode return else call endrun ('PBUF_GET_FLD_IDX: index not found for '//name) endif end function pbuf_get_fld_idx !========================================================================================= function pbuf_old_tim_idx() 20 ! Return index of oldest time sample in the physics buffer. implicit none ! Return value integer :: pbuf_old_tim_idx !----------------------------------------------------------------------------------------- pbuf_old_tim_idx = old_time_idx end function pbuf_old_tim_idx !========================================================================================= function pbuf_next_tim_idx(idx) ! Return index of next time sample in the physics buffer. implicit none integer, intent(in) :: idx ! Return value integer :: pbuf_next_tim_idx !----------------------------------------------------------------------------------------- pbuf_next_tim_idx = mod(idx, pbuf_times) + 1 end function pbuf_next_tim_idx !========================================================================================= subroutine pbuf_update_tim_idx() 1 ! Update index of old time sample in the physics buffer. implicit none !----------------------------------------------------------------------------------------- old_time_idx = mod(old_time_idx, pbuf_times) + 1 end subroutine pbuf_update_tim_idx !========================================================================================= subroutine pbuf_allocate(scope) 3,1 ! Allocate storage for fields in the physics buffer with the specified scope. ! If global_allocate_all=.true. then storage for both global and physpkg scope ! is allocated just once, when scope='global'. ! ! N.B. This routine must be called after phys_grid_init because that's ! where begchunk and endchunk are set implicit none character(len=*), intent(in) :: scope ! Local variables character(len=*), parameter :: sub = 'pbuf_allocate' integer :: i, fdim, mdim, ldim, istat logical :: allocate_all !----------------------------------------------------------------------------------------- if ( .not. physgrid_set ) then call endrun (sub//': ERROR: called before physics grid initialized.') end if ! allocate_all is used to force allocation of all fields at same time as allocation ! for global scope. allocate_all = .false. if ( global_allocate_all ) then if ( scope == 'global' ) then allocate_all = .true. else return end if end if do i = 1, pbuf_size if ( pbuf(i)%scope == scope .or. allocate_all ) then fdim = pbuf(i)%fdim mdim = pbuf(i)%mdim ldim = pbuf(i)%ldim allocate(pbuf(i)%fld_ptr(fdim,pcols,mdim,begchunk:endchunk,ldim), stat=istat) if ( istat /= 0 ) then call endrun (sub//': ERROR: allocate failed for '//pbuf(i)%name) end if pbuf(i)%fld_ptr = nan end if end do end subroutine pbuf_allocate !========================================================================================= subroutine pbuf_deallocate(scope) 1,1 ! Deallocate storage for fields in the physics buffer with the specified scope. ! If global_allocate_all=.true. then storage for both global and physpkg scope ! is deallocated just once, when scope='global'. implicit none character(len=*), intent(in) :: scope ! Local variables character(len=*), parameter :: sub = 'pbuf_deallocate' integer :: i, fdim, mdim, ldim logical :: deallocate_all !----------------------------------------------------------------------------------------- ! deallocate_all is used to force allocation of all fields at same time as allocation ! for global scope. deallocate_all = .false. if ( global_allocate_all ) then if ( scope == 'global' ) then deallocate_all = .true. else return end if end if do i = 1, pbuf_size if ( pbuf(i)%scope == scope .or. deallocate_all ) then if (associated(pbuf(i)%fld_ptr)) then deallocate(pbuf(i)%fld_ptr) else call endrun (sub//': ERROR: '//pbuf(i)%name//' is not allocated') end if end if end do end subroutine pbuf_deallocate !========================================================================================= subroutine pbuf_setval(name, value),4 ! Set a value for a field in the physics buffer. implicit none character(len=*), intent(in) :: name ! field name real(r8), intent(in) :: value ! Local variables character(len=*), parameter :: sub = 'pbuf_setval' integer :: idx, lchnk, ncols integer :: failcode = -1 !----------------------------------------------------------------------------------------- ! get field index idx = pbuf_get_fld_idx(name, failcode) ! check return value if (idx == failcode) then call endrun (sub//': ERROR: name not found:'//name) end if ! check that field pointer is associated -- if so then set the value if (associated(pbuf(idx)%fld_ptr)) then do lchnk = begchunk, endchunk ncols = get_ncols_p(lchnk) pbuf(idx)%fld_ptr(:,1:ncols,:,lchnk,:) = value end do else call endrun (sub//': ERROR: field '//name//' is not allocated') end if end subroutine pbuf_setval !========================================================================================= subroutine pbuf_init_restart(file, hdimids, hdim1_d, hdim2_d, pver_id, pverp_id) 1,2 use ppgrid, only : pver, pverp type(file_desc_t), intent(inout) :: file integer, intent(in) :: hdimids(:) integer, intent(in) :: hdim1_d, hdim2_d, pver_id, pverp_id integer :: ncdimid, londimid, latdimid integer :: dimids(5), ndims, dimlens(5) integer :: i, j, ierr, hdimcnt integer :: totalsize integer, pointer :: ldof(:) character(len=20) :: dimname, tmpname hdimcnt = size(hdimids) dimlens(1)=hdim1_d dimlens(2)=hdim2_d do i = 1, pbuf_size nullify(pbuf(i)%vardesc_list) ndims=hdimcnt dimids(1:ndims) = hdimids totalsize = 1 do j = 1, ndims totalsize = totalsize*dimlens(j) enddo if ( pbuf(i)%scope == 'global' ) then if(pbuf(i)%fdim>1) then dimname = trim(pbuf(I)%name)//'_fdim' ndims=ndims+1 ierr = pio_def_dim(file, dimname, pbuf(i)%fdim, dimids(ndims)) dimlens(ndims)=pbuf(i)%fdim totalsize = totalsize*dimlens(ndims) end if ! ! Assume that if the mdim is the same as pver or pverp then it is pver or pverp. ! This is almost always the case and should not hurt if it isnt if(pbuf(i)%mdim>1) then ndims=ndims+1 if(pbuf(i)%mdim==pver) then dimids(ndims) = pver_id dimlens(ndims)=pver else if(pbuf(i)%mdim==pverp) then dimids(ndims) = pverp_id dimlens(ndims)=pverp else dimname = trim(pbuf(I)%name)//'_mdim' ierr = pio_def_dim(file, trim(dimname), pbuf(i)%mdim, dimids(ndims)) dimlens(ndims)=pbuf(i)%mdim end if totalsize = totalsize*dimlens(ndims) end if if(pbuf(i)%ldim>1) then totalsize=totalsize*pbuf(i)%ldim ! max bytes per field is 4GB for netcdf, 8 bytes per element for double if(totalsize < maxvarsize) then dimname = trim(pbuf(I)%name)//'_ldim' ndims=ndims+1 ierr = pio_def_dim(file, dimname, pbuf(i)%ldim, dimids(ndims)) dimlens(ndims)=pbuf(i)%ldim else allocate(pbuf(i)%vardesc_list(pbuf(i)%ldim)) end if end if if(totalsize < maxvarsize) then ierr = pio_def_var(file, trim(pbuf(i)%name), pio_double, dimids(1:ndims), pbuf(i)%vardesc) else if(pbuf(i)%ldim==1) then call endrun('size of var exceeds netcdf limitations') end if do j=1,pbuf(i)%ldim write(tmpname,'(a,i3.3)') trim(pbuf(i)%name),j ierr = pio_def_var(file, tmpname, pio_double, dimids(1:ndims), pbuf(i)%vardesc_list(j)) end do end if end if end do ierr = pio_def_var(File, 'pbuf_time_idx', pio_int, timeidx_desc) end subroutine pbuf_init_restart subroutine pbuf_write_restart(File) 1,4 ! write physics buffer to restart file use ppgrid, only: pver, pverp use cam_pio_utils, only: get_phys_decomp type(file_desc_t), intent(inout) :: file ! Local variables character(len=*), parameter :: sub = 'pbuf_write_restart' integer :: i, j, ierr real(r8) :: mold(1), null(0) ! required for transfer statement !----------------------------------------------------------------------------------------- do i = 1, pbuf_size if ( pbuf(i)%scope == 'global' ) then if(associated(pbuf(i)%vardesc_list)) then call get_phys_decomp(pbuf(i)%iodesc, pbuf(i)%fdim, pbuf(i)%mdim, 1,pio_double) do j=1,pbuf(i)%ldim call pio_write_darray(file, pbuf(i)%vardesc_list(j), pbuf(i)%iodesc, pbuf(i)%fld_ptr(:,:,:,:,j), ierr) end do else call get_phys_decomp(pbuf(i)%iodesc, pbuf(i)%fdim, pbuf(i)%mdim, pbuf(i)%ldim,pio_double) call pio_write_darray(file, pbuf(i)%vardesc, pbuf(i)%iodesc, pbuf(i)%fld_ptr, ierr) end if end if end do ierr = pio_put_var(File, timeidx_desc, (/old_time_idx/)) end subroutine pbuf_write_restart !========================================================================================= subroutine pbuf_read_restart(File) 1,3 use ppgrid, only: pver, pverp use cam_pio_utils, only: get_phys_decomp, fillvalue ! write physics buffer to restart file type(file_desc_t), intent(inout) :: File type(var_desc_t) :: vardesc ! Local variables character(len=*), parameter :: sub = 'pbuf_read_restart' integer :: i, ierr real(r8), allocatable :: tmpfld(:) integer :: fdim, mdim, ldim, j character(len=20) :: tmpname !----------------------------------------------------------------------------------------- ierr = pio_inq_varid(File, 'pbuf_time_idx', vardesc) ierr = pio_get_var(File, vardesc, old_time_idx) do i = 1, pbuf_size if ( pbuf(i)%scope == 'global' ) then call get_phys_decomp(pbuf(i)%iodesc, pbuf(i)%fdim, pbuf(i)%mdim, pbuf(i)%ldim,pio_double) allocate(tmpfld(size(pbuf(i)%fld_ptr))) tmpfld(:) = fillvalue fdim = pbuf(i)%fdim mdim = pbuf(i)%mdim ldim = pbuf(i)%ldim call pio_seterrorhandling(File, pio_bcast_error) ierr = pio_inq_varid(File, trim(pbuf(i)%name), vardesc) if(ierr==PIO_NOERR) then call pio_seterrorhandling(File, pio_internal_error) call pio_read_darray(File, vardesc, pbuf(i)%iodesc, tmpfld, ierr) pbuf(i)%fld_ptr = reshape(tmpfld,(/fdim, pcols, mdim, endchunk-begchunk+1, ldim/)) else if (ldim>1) then write(tmpname,'(a,i3.3)') trim(pbuf(i)%name),1 ierr = pio_inq_varid(File, tmpname, vardesc) if(ierr==PIO_NOERR) then call pio_seterrorhandling(File, pio_internal_error) call pio_read_darray(File, vardesc, pbuf(i)%iodesc, tmpfld, ierr) pbuf(i)%fld_ptr(:,:,:,:,1) = reshape(tmpfld,(/fdim, pcols, mdim, endchunk-begchunk+1/)) do j=2,ldim write(tmpname,'(a,i3.3)') trim(pbuf(i)%name),j ierr = pio_inq_varid(File, tmpname, vardesc) call pio_read_darray(File, vardesc, pbuf(i)%iodesc, tmpfld, ierr) pbuf(i)%fld_ptr(:,:,:,:,j) = reshape(tmpfld,(/fdim, pcols, mdim, endchunk-begchunk+1/)) end do end if if(ierr/=PIO_NOERR) then write(iulog,*) __FILE__,__LINE__,'error getting ', pbuf(i)%name, file%fh end if end if deallocate(tmpfld) end if end do end subroutine pbuf_read_restart end module phys_buffer