diff --git a/.testing/tc2/MOM_input b/.testing/tc2/MOM_input index ca84d1c382..c7d2a35aa6 100644 --- a/.testing/tc2/MOM_input +++ b/.testing/tc2/MOM_input @@ -297,7 +297,7 @@ BOUND_CORIOLIS = True ! [Boolean] default = False ! v-points, and similarly at v-points. This option would ! have no effect on the SADOURNY Coriolis scheme if it ! were possible to use centered difference thickness fluxes. -PGF_STANLEY_T2_DET_COEFF = 0.5 ! [nondim] default = -1.0 +PGF_STANLEY_T2_DET_COEFF = -1.0 ! [nondim] default = -1.0 ! The coefficient correlating SGS temperature variance with the mean temperature ! gradient in the deterministic part of the Stanley form of the Brankart ! correction. Negative values disable the scheme. @@ -430,7 +430,7 @@ KHTH = 1.0 ! [m2 s-1] default = 0.0 ! The background horizontal thickness diffusivity. KHTH_MAX = 900.0 ! [m2 s-1] default = 0.0 ! The maximum horizontal thickness diffusivity. -STANLEY_PRM_DET_COEFF = 0.5 ! [nondim] default = -1.0 +STANLEY_PRM_DET_COEFF = -1.0 ! [nondim] default = -1.0 ! The coefficient correlating SGS temperature variance with the mean temperature ! gradient in the deterministic part of the Stanley parameterization. Negative ! values disable the scheme. diff --git a/config_src/drivers/FMS_cap/ocean_model_MOM.F90 b/config_src/drivers/FMS_cap/ocean_model_MOM.F90 index 21992f5833..5e1c512e98 100644 --- a/config_src/drivers/FMS_cap/ocean_model_MOM.F90 +++ b/config_src/drivers/FMS_cap/ocean_model_MOM.F90 @@ -276,7 +276,7 @@ subroutine ocean_model_init(Ocean_sfc, OS, Time_init, Time_in, wind_stagger, gas OS%Time = Time_in ; OS%Time_dyn = Time_in call initialize_MOM(OS%Time, Time_init, param_file, OS%dirs, OS%MOM_CSp, & OS%restart_CSp, Time_in, offline_tracer_mode=OS%offline_tracer_mode, & - diag_ptr=OS%diag, count_calls=.true.) + diag_ptr=OS%diag, count_calls=.true., waves_CSp=OS%Waves) call get_MOM_state_elements(OS%MOM_CSp, G=OS%grid, GV=OS%GV, US=OS%US, C_p=OS%C_p, & C_p_scaled=OS%fluxes%C_p, use_temp=use_temperature) diff --git a/config_src/drivers/mct_cap/mom_ocean_model_mct.F90 b/config_src/drivers/mct_cap/mom_ocean_model_mct.F90 index 11159903ab..5b1a980de1 100644 --- a/config_src/drivers/mct_cap/mom_ocean_model_mct.F90 +++ b/config_src/drivers/mct_cap/mom_ocean_model_mct.F90 @@ -274,7 +274,7 @@ subroutine ocean_model_init(Ocean_sfc, OS, Time_init, Time_in, gas_fields_ocn, i call initialize_MOM(OS%Time, Time_init, param_file, OS%dirs, OS%MOM_CSp, & OS%restart_CSp, Time_in, offline_tracer_mode=OS%offline_tracer_mode, & input_restart_file=input_restart_file, & - diag_ptr=OS%diag, count_calls=.true.) + diag_ptr=OS%diag, count_calls=.true., waves_CSp=OS%Waves) call get_MOM_state_elements(OS%MOM_CSp, G=OS%grid, GV=OS%GV, US=OS%US, C_p=OS%C_p, & C_p_scaled=OS%fluxes%C_p, use_temp=use_temperature) diff --git a/config_src/drivers/nuopc_cap/mom_cap.F90 b/config_src/drivers/nuopc_cap/mom_cap.F90 index 806aaf033e..b01e2019da 100644 --- a/config_src/drivers/nuopc_cap/mom_cap.F90 +++ b/config_src/drivers/nuopc_cap/mom_cap.F90 @@ -123,6 +123,8 @@ module MOM_cap_mod character(len=64) :: stdname character(len=64) :: shortname character(len=64) :: transferOffer + integer :: ungridded_lbound = 0 + integer :: ungridded_ubound = 0 end type fld_list_type integer,parameter :: fldsMax = 100 @@ -652,8 +654,7 @@ subroutine InitializeAdvertise(gcomp, importState, exportState, clock, rc) ocean_public%is_ocean_pe = .true. call ocean_model_init(ocean_public, ocean_state, time0, time_start, input_restart_file=trim(restartfiles)) - ! GMM, this call is not needed for NCAR. Check with EMC. - ! If this can be deleted, perhaps we should also delete ocean_model_flux_init + ! GMM, this call is not needed in CESM. Check with EMC if it can be deleted. call ocean_model_flux_init(ocean_state) call ocean_model_init_sfc(ocean_state, ocean_public) @@ -678,9 +679,7 @@ subroutine InitializeAdvertise(gcomp, importState, exportState, clock, rc) Ice_ocean_boundary% ice_fraction (isc:iec,jsc:jec), & Ice_ocean_boundary% u10_sqr (isc:iec,jsc:jec), & Ice_ocean_boundary% p (isc:iec,jsc:jec), & - Ice_ocean_boundary% lrunoff_hflx (isc:iec,jsc:jec), & - Ice_ocean_boundary% frunoff_hflx (isc:iec,jsc:jec), & - Ice_ocean_boundary% lrunoff (isc:iec,jsc:jec), & + Ice_ocean_boundary% lrunoff (isc:iec,jsc:jec), & Ice_ocean_boundary% frunoff (isc:iec,jsc:jec)) Ice_ocean_boundary%u_flux = 0.0 @@ -701,28 +700,38 @@ subroutine InitializeAdvertise(gcomp, importState, exportState, clock, rc) Ice_ocean_boundary%ice_fraction = 0.0 Ice_ocean_boundary%u10_sqr = 0.0 Ice_ocean_boundary%p = 0.0 - Ice_ocean_boundary%lrunoff_hflx = 0.0 - Ice_ocean_boundary%frunoff_hflx = 0.0 Ice_ocean_boundary%lrunoff = 0.0 Ice_ocean_boundary%frunoff = 0.0 + if (cesm_coupled) then + allocate (Ice_ocean_boundary% hrain (isc:iec,jsc:jec), & + Ice_ocean_boundary% hsnow (isc:iec,jsc:jec), & + Ice_ocean_boundary% hrofl (isc:iec,jsc:jec), & + Ice_ocean_boundary% hrofi (isc:iec,jsc:jec), & + Ice_ocean_boundary% hevap (isc:iec,jsc:jec), & + Ice_ocean_boundary% hcond (isc:iec,jsc:jec)) + + Ice_ocean_boundary%hrain = 0.0 + Ice_ocean_boundary%hsnow = 0.0 + Ice_ocean_boundary%hrofl = 0.0 + Ice_ocean_boundary%hrofi = 0.0 + Ice_ocean_boundary%hevap = 0.0 + Ice_ocean_boundary%hcond = 0.0 + endif + call query_ocean_state(ocean_state, use_waves=use_waves, wave_method=wave_method) if (use_waves) then - call query_ocean_state(ocean_state, NumWaveBands=Ice_ocean_boundary%num_stk_bands) if (wave_method == "EFACTOR") then allocate( Ice_ocean_boundary%lamult(isc:iec,jsc:jec) ) Ice_ocean_boundary%lamult = 0.0 - else - allocate ( Ice_ocean_boundary% ustk0 (isc:iec,jsc:jec), & - Ice_ocean_boundary% vstk0 (isc:iec,jsc:jec), & - Ice_ocean_boundary% ustkb (isc:iec,jsc:jec,Ice_ocean_boundary%num_stk_bands), & - Ice_ocean_boundary% vstkb (isc:iec,jsc:jec,Ice_ocean_boundary%num_stk_bands), & - Ice_ocean_boundary%stk_wavenumbers (Ice_ocean_boundary%num_stk_bands)) - Ice_ocean_boundary%ustk0 = 0.0 - Ice_ocean_boundary%vstk0 = 0.0 + else if (wave_method == "SURFACE_BANDS") then + call query_ocean_state(ocean_state, NumWaveBands=Ice_ocean_boundary%num_stk_bands) + allocate(Ice_ocean_boundary%ustkb(isc:iec,jsc:jec,Ice_ocean_boundary%num_stk_bands), source=0.0) + allocate(Ice_ocean_boundary%vstkb(isc:iec,jsc:jec,Ice_ocean_boundary%num_stk_bands), source=0.0) + allocate(Ice_ocean_boundary%stk_wavenumbers(Ice_ocean_boundary%num_stk_bands), source=0.0) call query_ocean_state(ocean_state, WaveNumbers=Ice_ocean_boundary%stk_wavenumbers, unscale=.true.) - Ice_ocean_boundary%ustkb = 0.0 - Ice_ocean_boundary%vstkb = 0.0 + else + call MOM_error(FATAL, "Unsupported WAVE_METHOD encountered in NUOPC cap.") endif endif ! Consider adding this: @@ -758,22 +767,39 @@ subroutine InitializeAdvertise(gcomp, importState, exportState, clock, rc) call fld_list_add(fldsToOcn_num, fldsToOcn, "So_duu10n" , "will provide") !-> wind^2 at 10m call fld_list_add(fldsToOcn_num, fldsToOcn, "mean_fresh_water_to_ocean_rate", "will provide") call fld_list_add(fldsToOcn_num, fldsToOcn, "net_heat_flx_to_ocn" , "will provide") - !These are not currently used and changing requires a nuopc dictionary change - !call fld_list_add(fldsToOcn_num, fldsToOcn, "mean_runoff_heat_flx" , "will provide") - !call fld_list_add(fldsToOcn_num, fldsToOcn, "mean_calving_heat_flx" , "will provide") + + if (cesm_coupled) then + call fld_list_add(fldsToOcn_num, fldsToOcn, "heat_content_lprec", "will provide") + call fld_list_add(fldsToOcn_num, fldsToOcn, "heat_content_fprec", "will provide") + call fld_list_add(fldsToOcn_num, fldsToOcn, "heat_content_evap" , "will provide") + call fld_list_add(fldsToOcn_num, fldsToOcn, "heat_content_cond" , "will provide") + call fld_list_add(fldsToOcn_num, fldsToOcn, "heat_content_rofl" , "will provide") + call fld_list_add(fldsToOcn_num, fldsToOcn, "heat_content_rofi" , "will provide") + endif + if (use_waves) then if (wave_method == "EFACTOR") then call fld_list_add(fldsToOcn_num, fldsToOcn, "Sw_lamult" , "will provide") - else - if (Ice_ocean_boundary%num_stk_bands > 3) then - call MOM_error(FATAL, "Number of Stokes Bands > 3, NUOPC cap not set up for this") + else if (wave_method == "SURFACE_BANDS") then + if (cesm_coupled) then + call fld_list_add(fldsToOcn_num, fldsToOcn, "Sw_pstokes_x", "will provide", & + ungridded_lbound=1, ungridded_ubound=Ice_ocean_boundary%num_stk_bands) + call fld_list_add(fldsToOcn_num, fldsToOcn, "Sw_pstokes_y", "will provide", & + ungridded_lbound=1, ungridded_ubound=Ice_ocean_boundary%num_stk_bands) + else ! below is the old approach of importing partitioned stokes drift components. after the planned ww3 nuopc + ! cap unification, this else block should be removed in favor of the more flexible import approach above. + if (Ice_ocean_boundary%num_stk_bands > 3) then + call MOM_error(FATAL, "Number of Stokes Bands > 3, NUOPC cap not set up for this") + endif + call fld_list_add(fldsToOcn_num, fldsToOcn, "eastward_partitioned_stokes_drift_1" , "will provide") + call fld_list_add(fldsToOcn_num, fldsToOcn, "northward_partitioned_stokes_drift_1", "will provide") + call fld_list_add(fldsToOcn_num, fldsToOcn, "eastward_partitioned_stokes_drift_2" , "will provide") + call fld_list_add(fldsToOcn_num, fldsToOcn, "northward_partitioned_stokes_drift_2", "will provide") + call fld_list_add(fldsToOcn_num, fldsToOcn, "eastward_partitioned_stokes_drift_3" , "will provide") + call fld_list_add(fldsToOcn_num, fldsToOcn, "northward_partitioned_stokes_drift_3", "will provide") endif - call fld_list_add(fldsToOcn_num, fldsToOcn, "eastward_partitioned_stokes_drift_1" , "will provide") - call fld_list_add(fldsToOcn_num, fldsToOcn, "northward_partitioned_stokes_drift_1", "will provide") - call fld_list_add(fldsToOcn_num, fldsToOcn, "eastward_partitioned_stokes_drift_2" , "will provide") - call fld_list_add(fldsToOcn_num, fldsToOcn, "northward_partitioned_stokes_drift_2", "will provide") - call fld_list_add(fldsToOcn_num, fldsToOcn, "eastward_partitioned_stokes_drift_3" , "will provide") - call fld_list_add(fldsToOcn_num, fldsToOcn, "northward_partitioned_stokes_drift_3", "will provide") + else + call MOM_error(FATAL, "Unsupported WAVE_METHOD encountered in NUOPC cap.") endif endif @@ -1647,7 +1673,7 @@ subroutine ModelAdvance(gcomp, rc) ! Import data !--------------- - call mom_import(ocean_public, ocean_grid, importState, ice_ocean_boundary, rc=rc) + call mom_import(ocean_public, ocean_grid, importState, ice_ocean_boundary, cesm_coupled, rc=rc) if (ChkErr(rc,__LINE__,u_FILE_u)) return !--------------- @@ -1655,7 +1681,8 @@ subroutine ModelAdvance(gcomp, rc) !--------------- if(profile_memory) call ESMF_VMLogMemInfo("Entering MOM update_ocean_model: ") - call update_ocean_model(Ice_ocean_boundary, ocean_state, ocean_public, Time, Time_step_coupled) + call update_ocean_model(Ice_ocean_boundary, ocean_state, ocean_public, Time, Time_step_coupled, & + cesm_coupled) if(profile_memory) call ESMF_VMLogMemInfo("Leaving MOM update_ocean_model: ") !--------------- @@ -2097,28 +2124,44 @@ subroutine MOM_RealizeFields(state, nfields, field_defs, tag, grid, mesh, rc) if (present(grid)) then - field = ESMF_FieldCreate(grid, ESMF_TYPEKIND_R8, indexflag=ESMF_INDEX_DELOCAL, & - name=field_defs(i)%shortname, rc=rc) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - - ! initialize fldptr to zero - call ESMF_FieldGet(field, farrayPtr=fldptr2d, rc=rc) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - fldptr2d(:,:) = 0.0 + if (field_defs(i)%ungridded_lbound > 0 .and. field_defs(i)%ungridded_ubound > 0) then + call ESMF_LogWrite(trim(subname)//": ERROR ungridded dimensions not supported in MOM6 nuopc cap when "//& + "ESMF_GEOMTYPE_GRID is used. Use ESMF_GEOMTYPE_MESH instead.", ESMF_LOGMSG_ERROR) + rc = ESMF_FAILURE + else + field = ESMF_FieldCreate(grid, ESMF_TYPEKIND_R8, indexflag=ESMF_INDEX_DELOCAL, & + name=field_defs(i)%shortname, rc=rc) + if (ChkErr(rc,__LINE__,u_FILE_u)) return + + ! initialize fldptr to zero + call ESMF_FieldGet(field, farrayPtr=fldptr2d, rc=rc) + if (ChkErr(rc,__LINE__,u_FILE_u)) return + fldptr2d(:,:) = 0.0 + endif else if (present(mesh)) then - field = ESMF_FieldCreate(mesh=mesh, typekind=ESMF_TYPEKIND_R8, meshloc=ESMF_MESHLOC_ELEMENT, & - name=field_defs(i)%shortname, rc=rc) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - - ! initialize fldptr to zero - call ESMF_FieldGet(field, farrayPtr=fldptr1d, rc=rc) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - fldptr1d(:) = 0.0 + if (field_defs(i)%ungridded_lbound > 0 .and. field_defs(i)%ungridded_ubound > 0) then + field = ESMF_FieldCreate(mesh=mesh, typekind=ESMF_TYPEKIND_R8, meshloc=ESMF_MESHLOC_ELEMENT, & + name=field_defs(i)%shortname, ungriddedLbound=(/field_defs(i)%ungridded_lbound/), & + ungriddedUbound=(/field_defs(i)%ungridded_ubound/), gridToFieldMap=(/2/), rc=rc) + if (ChkErr(rc,__LINE__,u_FILE_u)) return + ! initialize fldptr to zero + call ESMF_FieldGet(field, farrayPtr=fldptr2d, rc=rc) + if (ChkErr(rc,__LINE__,u_FILE_u)) return + fldptr2d(:,:) = 0.0 + else + field = ESMF_FieldCreate(mesh=mesh, typekind=ESMF_TYPEKIND_R8, meshloc=ESMF_MESHLOC_ELEMENT, & + name=field_defs(i)%shortname, rc=rc) + if (ChkErr(rc,__LINE__,u_FILE_u)) return + + ! initialize fldptr to zero + call ESMF_FieldGet(field, farrayPtr=fldptr1d, rc=rc) + if (ChkErr(rc,__LINE__,u_FILE_u)) return + fldptr1d(:) = 0.0 + endif endif - endif ! Realize connected field @@ -2172,12 +2215,14 @@ end subroutine MOM_RealizeFields !=============================================================================== !> Set up list of field information -subroutine fld_list_add(num, fldlist, stdname, transferOffer, shortname) +subroutine fld_list_add(num, fldlist, stdname, transferOffer, shortname, ungridded_lbound, ungridded_ubound) integer, intent(inout) :: num type(fld_list_type), intent(inout) :: fldlist(:) character(len=*), intent(in) :: stdname character(len=*), intent(in) :: transferOffer character(len=*), optional, intent(in) :: shortname + integer, optional, intent(in) :: ungridded_lbound + integer, optional, intent(in) :: ungridded_ubound ! local variables integer :: rc @@ -2199,6 +2244,10 @@ subroutine fld_list_add(num, fldlist, stdname, transferOffer, shortname) fldlist(num)%shortname = trim(stdname) endif fldlist(num)%transferOffer = trim(transferOffer) + if (present(ungridded_lbound) .and. present(ungridded_ubound)) then + fldlist(num)%ungridded_lbound = ungridded_lbound + fldlist(num)%ungridded_ubound = ungridded_ubound + end if end subroutine fld_list_add @@ -2356,7 +2405,7 @@ end subroutine shr_file_getLogUnit !! infrastructure when it's time for MOM to advance in time. During this subroutine, there is a !! call into the MOM update routine: !! -!! call update_ocean_model(Ice_ocean_boundary, Ocean_state, Ocean_public, Time, Time_step_coupled) +!! call update_ocean_model(Ice_ocean_boundary, Ocean_state, Ocean_public, Time, Time_step_coupled, cesm_coupled) !! !! Priori to the call to `update_ocean_model()`, the cap performs these steps !! - the `Time` and `Time_step_coupled` parameters, based on FMS types, are derived from the incoming ESMF clock @@ -2467,13 +2516,6 @@ end subroutine shr_file_getLogUnit !! !! !! -!! mean_calving_heat_flx -!! W m-2 -!! calving_hflx -!! heat flux, relative to 0C, of frozen land water into ocean -!! -!! -!! !! mean_calving_rate !! kg m-2 s-1 !! calving @@ -2544,10 +2586,45 @@ end subroutine shr_file_getLogUnit !! !! !! -!! mean_runoff_heat_flx +!! heat_content_lprec +!! W m-2 +!! hrain +!! heat content (enthalpy) of liquid water entering the ocean +!! +!! +!! +!! heat_content_fprec +!! W m-2 +!! hsnow +!! heat content (enthalpy) of frozen water entering the ocean +!! +!! +!! +!! heat_content_evap +!! W m-2 +!! hevap +!! heat content (enthalpy) of water leaving the ocean +!! +!! +!! +!! heat_content_cond +!! W m-2 +!! hcond +!! heat content (enthalpy) of liquid water entering the ocean due to condensation +!! +!! +!! +!! heat_content_rofl +!! W m-2 +!! hrofl +!! heat content (enthalpy) of liquid runoff +!! +!! +!! +!! heat_content_rofi !! W m-2 -!! runoff_hflx -!! heat flux, relative to 0C, of liquid land water into ocean +!! hrofi +!! heat content (enthalpy) of frozen runoff !! !! !! diff --git a/config_src/drivers/nuopc_cap/mom_cap_methods.F90 b/config_src/drivers/nuopc_cap/mom_cap_methods.F90 index a0b6b525d4..083e92eaf6 100644 --- a/config_src/drivers/nuopc_cap/mom_cap_methods.F90 +++ b/config_src/drivers/nuopc_cap/mom_cap_methods.F90 @@ -32,7 +32,11 @@ module MOM_cap_methods public :: state_diagnose public :: ChkErr -private :: State_getImport +interface State_getImport + module procedure State_getImport_2d + module procedure State_getImport_3d ! third dimension being an ungridded dimension +end interface + private :: State_setExport !> Get field pointer @@ -68,21 +72,25 @@ end subroutine mom_set_geomtype !> This function has a few purposes: !! (1) it imports surface fluxes using data from the mediator; and !! (2) it can apply restoring in SST and SSS. -subroutine mom_import(ocean_public, ocean_grid, importState, ice_ocean_boundary, rc) +subroutine mom_import(ocean_public, ocean_grid, importState, ice_ocean_boundary, cesm_coupled, rc) type(ocean_public_type) , intent(in) :: ocean_public !< Ocean surface state type(ocean_grid_type) , intent(in) :: ocean_grid !< Ocean model grid type(ESMF_State) , intent(inout) :: importState !< incoming data from mediator type(ice_ocean_boundary_type) , intent(inout) :: ice_ocean_boundary !< Ocean boundary forcing + logical , intent(in) :: cesm_coupled !< Flag to check if coupled with cesm integer , intent(inout) :: rc !< Return code ! Local Variables - integer :: i, j, ig, jg, n + integer :: i, j, ib, ig, jg, n integer :: isc, iec, jsc, jec + integer :: nsc ! number of stokes drift components character(len=128) :: fldname real(ESMF_KIND_R8), allocatable :: taux(:,:) real(ESMF_KIND_R8), allocatable :: tauy(:,:) real(ESMF_KIND_R8), allocatable :: stkx1(:,:),stkx2(:,:),stkx3(:,:) real(ESMF_KIND_R8), allocatable :: stky1(:,:),stky2(:,:),stky3(:,:) + real(ESMF_KIND_R8), allocatable :: stkx(:,:,:) + real(ESMF_KIND_R8), allocatable :: stky(:,:,:) character(len=*) , parameter :: subname = '(mom_import)' rc = ESMF_SUCCESS @@ -209,17 +217,53 @@ subroutine mom_import(ocean_public, ocean_grid, importState, ice_ocean_boundary, isc, iec, jsc, jec, ice_ocean_boundary%frunoff, areacor=med2mod_areacor, rc=rc) if (ChkErr(rc,__LINE__,u_FILE_u)) return - ! heat content of lrunoff - ice_ocean_boundary%lrunoff_hflx(:,:) = 0._ESMF_KIND_R8 - call state_getimport(importState, 'mean_runoff_heat_flx', & - isc, iec, jsc, jec, ice_ocean_boundary%lrunoff_hflx, areacor=med2mod_areacor, rc=rc) - if (ChkErr(rc,__LINE__,u_FILE_u)) return + !---- + ! Enthalpy terms (only in CESM) + !---- + if (cesm_coupled) then + !---- + ! enthalpy from liquid precipitation (hrain) + !---- + call state_getimport(importState, 'heat_content_lprec', & + isc, iec, jsc, jec, ice_ocean_boundary%hrain, areacor=med2mod_areacor, rc=rc) + if (ChkErr(rc,__LINE__,u_FILE_u)) return - ! heat content of frunoff - ice_ocean_boundary%frunoff_hflx(:,:) = 0._ESMF_KIND_R8 - call state_getimport(importState, 'mean_calving_heat_flx', & - isc, iec, jsc, jec, ice_ocean_boundary%frunoff_hflx, areacor=med2mod_areacor, rc=rc) - if (ChkErr(rc,__LINE__,u_FILE_u)) return + !---- + ! enthalpy from frozen precipitation (hsnow) + !---- + call state_getimport(importState, 'heat_content_fprec', & + isc, iec, jsc, jec, ice_ocean_boundary%hsnow, areacor=med2mod_areacor, rc=rc) + if (ChkErr(rc,__LINE__,u_FILE_u)) return + + !---- + ! enthalpy from liquid runoff (hrofl) + !---- + call state_getimport(importState, 'heat_content_rofl', & + isc, iec, jsc, jec, ice_ocean_boundary%hrofl, areacor=med2mod_areacor, rc=rc) + if (ChkErr(rc,__LINE__,u_FILE_u)) return + + !---- + ! enthalpy from frozen runoff (hrofi) + !---- + call state_getimport(importState, 'heat_content_rofi', & + isc, iec, jsc, jec, ice_ocean_boundary%hrofi, areacor=med2mod_areacor, rc=rc) + if (ChkErr(rc,__LINE__,u_FILE_u)) return + + !---- + ! enthalpy from evaporation (hevap) + !---- + call state_getimport(importState, 'heat_content_evap', & + isc, iec, jsc, jec, ice_ocean_boundary%hevap, areacor=med2mod_areacor, rc=rc) + if (ChkErr(rc,__LINE__,u_FILE_u)) return + + !---- + ! enthalpy from condensation (hcond) + !---- + call state_getimport(importState, 'heat_content_cond', & + isc, iec, jsc, jec, ice_ocean_boundary%hcond, areacor=med2mod_areacor, rc=rc) + if (ChkErr(rc,__LINE__,u_FILE_u)) return + + endif !---- ! salt flux from ice @@ -285,49 +329,81 @@ subroutine mom_import(ocean_public, ocean_grid, importState, ice_ocean_boundary, ! Partitioned Stokes Drift Components !---- if ( associated(ice_ocean_boundary%ustkb) ) then - allocate(stkx1(isc:iec,jsc:jec)) - allocate(stky1(isc:iec,jsc:jec)) - allocate(stkx2(isc:iec,jsc:jec)) - allocate(stky2(isc:iec,jsc:jec)) - allocate(stkx3(isc:iec,jsc:jec)) - allocate(stky3(isc:iec,jsc:jec)) - - call state_getimport(importState,'eastward_partitioned_stokes_drift_1' , isc, iec, jsc, jec, stkx1,rc=rc) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - call state_getimport(importState,'northward_partitioned_stokes_drift_1', isc, iec, jsc, jec, stky1,rc=rc) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - call state_getimport(importState,'eastward_partitioned_stokes_drift_2' , isc, iec, jsc, jec, stkx2,rc=rc) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - call state_getimport(importState,'northward_partitioned_stokes_drift_2', isc, iec, jsc, jec, stky2,rc=rc) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - call state_getimport(importState,'eastward_partitioned_stokes_drift_3' , isc, iec, jsc, jec, stkx3,rc=rc) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - call state_getimport(importState,'northward_partitioned_stokes_drift_3', isc, iec, jsc, jec, stky3,rc=rc) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - ! rotate from true zonal/meridional to local coordinates - do j = jsc, jec - jg = j + ocean_grid%jsc - jsc - do i = isc, iec - ig = i + ocean_grid%isc - isc - ice_ocean_boundary%ustkb(i,j,1) = ocean_grid%cos_rot(ig,jg)*stkx1(i,j) & - - ocean_grid%sin_rot(ig,jg)*stky1(i,j) - ice_ocean_boundary%vstkb(i,j,1) = ocean_grid%cos_rot(ig,jg)*stky1(i,j) & - + ocean_grid%sin_rot(ig,jg)*stkx1(i,j) - - ice_ocean_boundary%ustkb(i,j,2) = ocean_grid%cos_rot(ig,jg)*stkx2(i,j) & - - ocean_grid%sin_rot(ig,jg)*stky2(i,j) - ice_ocean_boundary%vstkb(i,j,2) = ocean_grid%cos_rot(ig,jg)*stky2(i,j) & - + ocean_grid%sin_rot(ig,jg)*stkx2(i,j) - - ice_ocean_boundary%ustkb(i,j,3) = ocean_grid%cos_rot(ig,jg)*stkx3(i,j) & - - ocean_grid%sin_rot(ig,jg)*stky3(i,j) - ice_ocean_boundary%vstkb(i,j,3) = ocean_grid%cos_rot(ig,jg)*stky3(i,j) & - + ocean_grid%sin_rot(ig,jg)*stkx3(i,j) + if (cesm_coupled) then + nsc = Ice_ocean_boundary%num_stk_bands + allocate(stkx(isc:iec,jsc:jec,1:nsc)) + allocate(stky(isc:iec,jsc:jec,1:nsc)) + + call state_getimport(importState,'Sw_pstokes_x', isc, iec, jsc, jec, 1, nsc, stkx, rc=rc) + if (ChkErr(rc,__LINE__,u_FILE_u)) return + call state_getimport(importState,'Sw_pstokes_y', isc, iec, jsc, jec, 1, nsc, stky, rc=rc) + if (ChkErr(rc,__LINE__,u_FILE_u)) return + + ! rotate from true zonal/meridional to local coordinates + do j = jsc, jec + jg = j + ocean_grid%jsc - jsc + do i = isc, iec + ig = i + ocean_grid%isc - isc + !rotate + do ib = 1, nsc + ice_ocean_boundary%ustkb(i,j,ib) = ocean_grid%cos_rot(ig,jg)*stkx(i,j,ib) & + - ocean_grid%sin_rot(ig,jg)*stky(i,j,ib) + ice_ocean_boundary%vstkb(i,j,ib) = ocean_grid%cos_rot(ig,jg)*stky(i,j,ib) & + + ocean_grid%sin_rot(ig,jg)*stkx(i,j,ib) + enddo + ! apply masks + ice_ocean_boundary%ustkb(i,j,:) = ice_ocean_boundary%ustkb(i,j,:) * ocean_grid%mask2dT(ig,jg) + ice_ocean_boundary%vstkb(i,j,:) = ice_ocean_boundary%vstkb(i,j,:) * ocean_grid%mask2dT(ig,jg) + enddo enddo - enddo + deallocate(stkx,stky) + + else ! below is the old approach of importing partitioned stokes drift components. after the planned ww3 nuopc + ! cap unification, this else block should be removed in favor of the more flexible import approach above. + allocate(stkx1(isc:iec,jsc:jec)) + allocate(stky1(isc:iec,jsc:jec)) + allocate(stkx2(isc:iec,jsc:jec)) + allocate(stky2(isc:iec,jsc:jec)) + allocate(stkx3(isc:iec,jsc:jec)) + allocate(stky3(isc:iec,jsc:jec)) + + call state_getimport(importState,'eastward_partitioned_stokes_drift_1' , isc, iec, jsc, jec, stkx1,rc=rc) + if (ChkErr(rc,__LINE__,u_FILE_u)) return + call state_getimport(importState,'northward_partitioned_stokes_drift_1', isc, iec, jsc, jec, stky1,rc=rc) + if (ChkErr(rc,__LINE__,u_FILE_u)) return + call state_getimport(importState,'eastward_partitioned_stokes_drift_2' , isc, iec, jsc, jec, stkx2,rc=rc) + if (ChkErr(rc,__LINE__,u_FILE_u)) return + call state_getimport(importState,'northward_partitioned_stokes_drift_2', isc, iec, jsc, jec, stky2,rc=rc) + if (ChkErr(rc,__LINE__,u_FILE_u)) return + call state_getimport(importState,'eastward_partitioned_stokes_drift_3' , isc, iec, jsc, jec, stkx3,rc=rc) + if (ChkErr(rc,__LINE__,u_FILE_u)) return + call state_getimport(importState,'northward_partitioned_stokes_drift_3', isc, iec, jsc, jec, stky3,rc=rc) + if (ChkErr(rc,__LINE__,u_FILE_u)) return - deallocate(stkx1,stkx2,stkx3,stky1,stky2,stky3) + ! rotate from true zonal/meridional to local coordinates + do j = jsc, jec + jg = j + ocean_grid%jsc - jsc + do i = isc, iec + ig = i + ocean_grid%isc - isc + ice_ocean_boundary%ustkb(i,j,1) = ocean_grid%cos_rot(ig,jg)*stkx1(i,j) & + - ocean_grid%sin_rot(ig,jg)*stky1(i,j) + ice_ocean_boundary%vstkb(i,j,1) = ocean_grid%cos_rot(ig,jg)*stky1(i,j) & + + ocean_grid%sin_rot(ig,jg)*stkx1(i,j) + + ice_ocean_boundary%ustkb(i,j,2) = ocean_grid%cos_rot(ig,jg)*stkx2(i,j) & + - ocean_grid%sin_rot(ig,jg)*stky2(i,j) + ice_ocean_boundary%vstkb(i,j,2) = ocean_grid%cos_rot(ig,jg)*stky2(i,j) & + + ocean_grid%sin_rot(ig,jg)*stkx2(i,j) + + ice_ocean_boundary%ustkb(i,j,3) = ocean_grid%cos_rot(ig,jg)*stkx3(i,j) & + - ocean_grid%sin_rot(ig,jg)*stky3(i,j) + ice_ocean_boundary%vstkb(i,j,3) = ocean_grid%cos_rot(ig,jg)*stky3(i,j) & + + ocean_grid%sin_rot(ig,jg)*stkx3(i,j) + enddo + enddo + deallocate(stkx1,stkx2,stkx3,stky1,stky2,stky3) + endif endif end subroutine mom_import @@ -648,8 +724,8 @@ subroutine State_GetFldPtr_2d(State, fldname, fldptr, rc) end subroutine State_GetFldPtr_2d -!> Map import state field to output array -subroutine State_GetImport(state, fldname, isc, iec, jsc, jec, output, do_sum, areacor, rc) +!> Map 2d import state field to output array +subroutine State_GetImport_2d(state, fldname, isc, iec, jsc, jec, output, do_sum, areacor, rc) type(ESMF_State) , intent(in) :: state !< ESMF state character(len=*) , intent(in) :: fldname !< Field name integer , intent(in) :: isc !< The start i-index of cell centers within @@ -672,7 +748,7 @@ subroutine State_GetImport(state, fldname, isc, iec, jsc, jec, output, do_sum, a integer :: lbnd1,lbnd2 real(ESMF_KIND_R8), pointer :: dataPtr1d(:) real(ESMF_KIND_R8), pointer :: dataPtr2d(:,:) - character(len=*) , parameter :: subname='(MOM_cap_methods:state_getimport)' + character(len=*) , parameter :: subname='(MOM_cap_methods:state_getimport_2d)' ! ---------------------------------------------- rc = ESMF_SUCCESS @@ -731,7 +807,80 @@ subroutine State_GetImport(state, fldname, isc, iec, jsc, jec, output, do_sum, a endif -end subroutine State_GetImport +end subroutine State_GetImport_2d + +!> Map 3d import state field to output array (where 3rd dim is an ungridded dimension) +subroutine State_GetImport_3d(state, fldname, isc, iec, jsc, jec, lbd, ubd, output, do_sum, areacor, rc) + type(ESMF_State) , intent(in) :: state !< ESMF state + character(len=*) , intent(in) :: fldname !< Field name + integer , intent(in) :: isc !< The start i-index of cell centers within + !! the computational domain + integer , intent(in) :: iec !< The end i-index of cell centers within the + !! computational domain + integer , intent(in) :: jsc !< The start j-index of cell centers within + !! the computational domain + integer , intent(in) :: jec !< The end j-index of cell centers within + !! the computational domain + integer , intent(in) :: lbd !< lower bound of ungridded dimension + integer , intent(in) :: ubd !< upper bound of ungridded dimension + real (ESMF_KIND_R8) , intent(inout) :: output(isc:iec,jsc:jec,lbd:ubd)!< Output 3D array + logical, optional , intent(in) :: do_sum !< If true, sums the data + real (ESMF_KIND_R8), optional, intent(in) :: areacor(:) !< flux area correction factors + !! applicable to meshes + integer , intent(out) :: rc !< Return code + + ! local variables + type(ESMF_StateItem_Flag) :: itemFlag + integer :: n, i, j, i1, j1, u + integer :: lbnd1,lbnd2 + real(ESMF_KIND_R8), pointer :: dataPtr2d(:,:) + character(len=*) , parameter :: subname='(MOM_cap_methods:state_getimport_3d)' + ! ---------------------------------------------- + + rc = ESMF_SUCCESS + + call ESMF_StateGet(State, trim(fldname), itemFlag, rc=rc) + if (itemFlag /= ESMF_STATEITEM_NOTFOUND) then + + if (geomtype == ESMF_GEOMTYPE_MESH) then + + ! get field pointer + call state_getfldptr(state, trim(fldname), dataptr2d, rc) + if (ChkErr(rc,__LINE__,u_FILE_u)) return + + ! determine output array and apply area correction if present + do u = lbd, ubd ! ungridded dims + n = 0 + do j = jsc,jec + do i = isc,iec + n = n + 1 + if (present(do_sum)) then + if (present(areacor)) then + output(i,j,u) = output(i,j,u) + dataPtr2d(u,n) * areacor(n) + else + output(i,j,u) = output(i,j,u) + dataPtr2d(u,n) + end if + else + if (present(areacor)) then + output(i,j,u) = dataPtr2d(u,n) * areacor(n) + else + output(i,j,u) = dataPtr2d(u,n) + end if + endif + enddo + enddo + enddo + + else if (geomtype == ESMF_GEOMTYPE_GRID) then + call ESMF_LogWrite(trim(subname)//": ERROR ungridded dimensions not supported in MOM6 nuopc cap when "// & + "ESMF_GEOMTYPE_GRID is used. Use ESMF_GEOMTYPE_MESH instead.", ESMF_LOGMSG_ERROR) + rc = ESMF_FAILURE + return + endif + + endif + +end subroutine State_GetImport_3d !> Map input array to export state subroutine State_SetExport(state, fldname, isc, iec, jsc, jec, input, ocean_grid, areacor, rc) diff --git a/config_src/drivers/nuopc_cap/mom_ocean_model_nuopc.F90 b/config_src/drivers/nuopc_cap/mom_ocean_model_nuopc.F90 index a44e126461..dddac936d4 100644 --- a/config_src/drivers/nuopc_cap/mom_ocean_model_nuopc.F90 +++ b/config_src/drivers/nuopc_cap/mom_ocean_model_nuopc.F90 @@ -284,7 +284,7 @@ subroutine ocean_model_init(Ocean_sfc, OS, Time_init, Time_in, gas_fields_ocn, i call initialize_MOM(OS%Time, Time_init, param_file, OS%dirs, OS%MOM_CSp, & OS%restart_CSp, Time_in, offline_tracer_mode=OS%offline_tracer_mode, & input_restart_file=input_restart_file, & - diag_ptr=OS%diag, count_calls=.true.) + diag_ptr=OS%diag, count_calls=.true., waves_CSp=OS%Waves) call get_MOM_state_elements(OS%MOM_CSp, G=OS%grid, GV=OS%GV, US=OS%US, C_p=OS%C_p, & C_p_scaled=OS%fluxes%C_p, use_temp=use_temperature) @@ -378,6 +378,8 @@ subroutine ocean_model_init(Ocean_sfc, OS, Time_init, Time_in, gas_fields_ocn, i call get_param(param_file, mdl, "USE_CFC_CAP", use_CFC, & default=.false., do_not_log=.true.) + call get_param(param_file, mdl, "USE_WAVES", OS%Use_Waves, & + "If true, enables surface wave modules.", default=.false.) ! Consider using a run-time flag to determine whether to do the diagnostic ! vertical integrals, since the related 3-d sums are not negligible in cost. @@ -398,15 +400,14 @@ subroutine ocean_model_init(Ocean_sfc, OS, Time_init, Time_in, gas_fields_ocn, i call allocate_forcing_type(OS%grid, OS%fluxes, shelf=.true.) endif - call allocate_forcing_type(OS%grid, OS%fluxes, waves=.true.) - call get_param(param_file, mdl, "USE_WAVES", OS%Use_Waves, & - "If true, enables surface wave modules.", default=.false.) if (OS%Use_Waves) then call get_param(param_file, mdl, "WAVE_METHOD", OS%wave_method, default="EMPTY", do_not_log=.true.) endif + call allocate_forcing_type(OS%grid, OS%fluxes, waves=.true., lamult=(trim(OS%wave_method)=="EFACTOR")) + ! MOM_wave_interface_init is called regardless of the value of USE_WAVES because ! it also initializes statistical waves. - call MOM_wave_interface_init(OS%Time, OS%grid, OS%GV, OS%US, param_file, OS%Waves, OS%diag) + call MOM_wave_interface_init(OS%Time, OS%grid, OS%GV, OS%US, param_file, OS%Waves, OS%diag, OS%restart_CSp) if (associated(OS%grid%Domain%maskmap)) then call initialize_ocean_public_type(OS%grid%Domain%mpp_domain, Ocean_sfc, & @@ -458,7 +459,7 @@ end subroutine ocean_model_init !! storing the new ocean properties in Ocean_state. subroutine update_ocean_model(Ice_ocean_boundary, OS, Ocean_sfc, & time_start_update, Ocean_coupling_time_step, & - update_dyn, update_thermo, Ocn_fluxes_used) + cesm_coupled, update_dyn, update_thermo, Ocn_fluxes_used) type(ice_ocean_boundary_type), & intent(in) :: Ice_ocean_boundary !< A structure containing the !! various forcing fields coming from the ice. @@ -473,6 +474,7 @@ subroutine update_ocean_model(Ice_ocean_boundary, OS, Ocean_sfc, & type(time_type), intent(in) :: time_start_update !< The time at the beginning of the update step. type(time_type), intent(in) :: Ocean_coupling_time_step !< The amount of time over !! which to advance the ocean. + logical, intent(in) :: cesm_coupled !< Flag to check if coupled with cesm logical, optional, intent(in) :: update_dyn !< If present and false, do not do updates !! due to the ocean dynamics. logical, optional, intent(in) :: update_thermo !< If present and false, do not do updates @@ -522,7 +524,6 @@ subroutine update_ocean_model(Ice_ocean_boundary, OS, Ocean_sfc, & do_dyn = .true. ; if (present(update_dyn)) do_dyn = update_dyn do_thermo = .true. ; if (present(update_thermo)) do_thermo = update_thermo - ! This is benign but not necessary if ocean_model_init_sfc was called or if ! OS%sfc_state%tr_fields was spawned in ocean_model_init. Consider removing it. is = OS%grid%isc ; ie = OS%grid%iec ; js = OS%grid%jsc ; je = OS%grid%jec @@ -689,7 +690,12 @@ subroutine update_ocean_model(Ice_ocean_boundary, OS, Ocean_sfc, & call mech_forcing_diags(OS%forces, dt_coupling, OS%grid, OS%Time, OS%diag, OS%forcing_CSp%handles) if (OS%fluxes%fluxes_used) then - call forcing_diagnostics(OS%fluxes, OS%sfc_state, OS%grid, OS%US, OS%Time, OS%diag, OS%forcing_CSp%handles) + if (cesm_coupled) then + call forcing_diagnostics(OS%fluxes, OS%sfc_state, OS%grid, OS%US, OS%Time, OS%diag, & + OS%forcing_CSp%handles, enthalpy=.true.) + else + call forcing_diagnostics(OS%fluxes, OS%sfc_state, OS%grid, OS%US, OS%Time, OS%diag, OS%forcing_CSp%handles) + endif endif ! Translate state into Ocean. diff --git a/config_src/drivers/nuopc_cap/mom_surface_forcing_nuopc.F90 b/config_src/drivers/nuopc_cap/mom_surface_forcing_nuopc.F90 index da4c15e528..c45a59c221 100644 --- a/config_src/drivers/nuopc_cap/mom_surface_forcing_nuopc.F90 +++ b/config_src/drivers/nuopc_cap/mom_surface_forcing_nuopc.F90 @@ -81,6 +81,8 @@ module MOM_surface_forcing_nuopc !! pressure limited by max_p_surf instead of the !! full atmospheric pressure. The default is true. logical :: use_CFC !< enables the MOM_CFC_cap tracer package. + logical :: enthalpy_cpl !< Controls if enthalpy terms are provided by the coupler or computed + !! internally. real :: gust_const !< constant unresolved background gustiness for ustar [R L Z T-1 ~> Pa] logical :: read_gust_2d !< If true, use a 2-dimensional gustiness supplied !! from an input file. @@ -180,8 +182,12 @@ module MOM_surface_forcing_nuopc real, pointer, dimension(:,:) :: ustar_berg =>NULL() !< frictional velocity beneath icebergs [m/s] real, pointer, dimension(:,:) :: area_berg =>NULL() !< area covered by icebergs[m2/m2] real, pointer, dimension(:,:) :: mass_berg =>NULL() !< mass of icebergs(kg/m2) - real, pointer, dimension(:,:) :: lrunoff_hflx =>NULL() !< heat content of liquid runoff [W/m2] - real, pointer, dimension(:,:) :: frunoff_hflx =>NULL() !< heat content of frozen runoff [W/m2] + real, pointer, dimension(:,:) :: hrofl =>NULL() !< heat content from liquid runoff [W/m2] + real, pointer, dimension(:,:) :: hrofi =>NULL() !< heat content from frozen runoff [W/m2] + real, pointer, dimension(:,:) :: hrain =>NULL() !< heat content from liquid precipitation [W/m2] + real, pointer, dimension(:,:) :: hsnow =>NULL() !< heat content from frozen precipitation [W/m2] + real, pointer, dimension(:,:) :: hevap =>NULL() !< heat content from evaporation [W/m2] + real, pointer, dimension(:,:) :: hcond =>NULL() !< heat content from condensation [W/m2] real, pointer, dimension(:,:) :: p =>NULL() !< pressure of overlying ice and atmosphere !< on ocean surface [Pa] real, pointer, dimension(:,:) :: ice_fraction =>NULL() !< fractional ice area [nondim] @@ -192,8 +198,6 @@ module MOM_surface_forcing_nuopc !! for divergence damping, as determined !! outside of the ocean model in [m3/s] real, pointer, dimension(:,:) :: lamult => NULL() !< Langmuir enhancement factor [nondim] - real, pointer, dimension(:,:) :: ustk0 => NULL() !< Surface Stokes drift, zonal [m/s] - real, pointer, dimension(:,:) :: vstk0 => NULL() !< Surface Stokes drift, meridional [m/s] real, pointer, dimension(:) :: stk_wavenumbers => NULL() !< The central wave number of Stokes bands [rad/m] real, pointer, dimension(:,:,:) :: ustkb => NULL() !< Stokes Drift spectrum, zonal [m/s] !! Horizontal - u points @@ -305,7 +309,7 @@ subroutine convert_IOB_to_fluxes(IOB, fluxes, index_bounds, Time, valid_time, G, if (fluxes%dt_buoy_accum < 0) then call allocate_forcing_type(G, fluxes, water=.true., heat=.true., ustar=.true., & press=.true., fix_accum_bug=CS%fix_ustar_gustless_bug, & - cfc=CS%use_CFC) + cfc=CS%use_CFC, hevap=CS%enthalpy_cpl) call safe_alloc_ptr(fluxes%sw_vis_dir,isd,ied,jsd,jed) call safe_alloc_ptr(fluxes%sw_vis_dif,isd,ied,jsd,jed) @@ -488,13 +492,6 @@ subroutine convert_IOB_to_fluxes(IOB, fluxes, index_bounds, Time, valid_time, G, if (associated(IOB%mass_berg)) & fluxes%mass_berg(i,j) = US%m_to_Z*US%kg_m3_to_R * IOB%mass_berg(i-i0,j-j0) * G%mask2dT(i,j) - if (associated(IOB%lrunoff_hflx)) & - fluxes%heat_content_lrunoff(i,j) = US%W_m2_to_QRZ_T * IOB%lrunoff_hflx(i-i0,j-j0) * G%mask2dT(i,j) - - if (associated(IOB%frunoff_hflx)) & - fluxes%heat_content_frunoff(i,j) = US%W_m2_to_QRZ_T * kg_m2_s_conversion * & - IOB%frunoff_hflx(i-i0,j-j0) * G%mask2dT(i,j) - if (associated(IOB%lw_flux)) & fluxes%lw(i,j) = US%W_m2_to_QRZ_T * IOB%lw_flux(i-i0,j-j0) * G%mask2dT(i,j) @@ -545,6 +542,27 @@ subroutine convert_IOB_to_fluxes(IOB, fluxes, index_bounds, Time, valid_time, G, fluxes%sw(i,j) = fluxes%sw_vis_dir(i,j) + fluxes%sw_vis_dif(i,j) + & fluxes%sw_nir_dir(i,j) + fluxes%sw_nir_dif(i,j) + ! enthalpy terms + if (CS%enthalpy_cpl) then + if (associated(IOB%hrofl)) & + fluxes%heat_content_lrunoff(i,j) = US%W_m2_to_QRZ_T * IOB%hrofl(i-i0,j-j0) * G%mask2dT(i,j) + + if (associated(IOB%hrofi)) & + fluxes%heat_content_frunoff(i,j) = US%W_m2_to_QRZ_T * IOB%hrofi(i-i0,j-j0) * G%mask2dT(i,j) + + if (associated(IOB%hrain)) & + fluxes%heat_content_lprec(i,j) = US%W_m2_to_QRZ_T * IOB%hrain(i-i0,j-j0) * G%mask2dT(i,j) + + if (associated(IOB%hsnow)) & + fluxes%heat_content_fprec(i,j) = US%W_m2_to_QRZ_T * IOB%hsnow(i-i0,j-j0) * G%mask2dT(i,j) + + if (associated(IOB%hevap)) & + fluxes%heat_content_evap(i,j) = US%W_m2_to_QRZ_T * IOB%hevap(i-i0,j-j0) * G%mask2dT(i,j) + + if (associated(IOB%hcond)) & + fluxes%heat_content_cond(i,j) = US%W_m2_to_QRZ_T * IOB%hcond(i-i0,j-j0) * G%mask2dT(i,j) + endif + ! sea ice fraction [nondim] if (associated(IOB%ice_fraction) .and. associated(fluxes%ice_fraction)) & fluxes%ice_fraction(i,j) = G%mask2dT(i,j) * IOB%ice_fraction(i-i0,j-j0) @@ -892,17 +910,13 @@ subroutine convert_IOB_to_forces(IOB, forces, index_bounds, Time, G, US, CS) if ( associated(IOB%ustkb) ) then forces%stk_wavenumbers(:) = IOB%stk_wavenumbers - do j=js,je; do i=is,ie - forces%ustk0(i,j) = IOB%ustk0(i-I0,j-J0) ! How to be careful here that the domains are right? - forces%vstk0(i,j) = IOB%vstk0(i-I0,j-J0) - enddo ; enddo - call pass_vector(forces%ustk0,forces%vstk0, G%domain ) do istk = 1,IOB%num_stk_bands do j=js,je; do i=is,ie forces%ustkb(i,j,istk) = IOB%ustkb(i-I0,j-J0,istk) forces%vstkb(i,j,istk) = IOB%vstkb(i-I0,j-J0,istk) enddo; enddo - call pass_vector(forces%ustkb(:,:,istk),forces%vstkb(:,:,istk), G%domain ) + call pass_var(forces%ustkb(:,:,istk), G%domain ) + call pass_var(forces%vstkb(:,:,istk), G%domain ) enddo endif @@ -1206,6 +1220,10 @@ subroutine surface_forcing_init(Time, G, US, param_file, diag, CS, restore_salt, call get_param(param_file, mdl, "USE_CFC_CAP", CS%use_CFC, & default=.false., do_not_log=.true.) + call get_param(param_file, mdl, "ENTHALPY_FROM_COUPLER", CS%enthalpy_cpl, & + "If True, the heat (enthalpy) associated with mass entering/leaving the "//& + "ocean is provided via coupler.", default=.false.) + if (restore_salt) then call get_param(param_file, mdl, "FLUXCONST", CS%Flux_const, & "The constant that relates the restoring surface fluxes to the relative "//& @@ -1512,6 +1530,26 @@ subroutine ice_ocn_bnd_type_chksum(id, timestep, iobt) chks = field_chksum( iobt%mass_berg ) ; if (root) write(outunit,100) 'iobt%mass_berg ', chks endif + ! enthalpy + if (associated(iobt%hrofl)) then + chks = field_chksum( iobt%hrofl ) ; if (root) write(outunit,100) 'iobt%hrofl ', chks + endif + if (associated(iobt%hrofi)) then + chks = field_chksum( iobt%hrofi ) ; if (root) write(outunit,100) 'iobt%hrofi ', chks + endif + if (associated(iobt%hrain)) then + chks = field_chksum( iobt%hrain ) ; if (root) write(outunit,100) 'iobt%hrain ', chks + endif + if (associated(iobt%hsnow)) then + chks = field_chksum( iobt%hsnow ) ; if (root) write(outunit,100) 'iobt%hsnow ', chks + endif + if (associated(iobt%hevap)) then + chks = field_chksum( iobt%hevap ) ; if (root) write(outunit,100) 'iobt%hevap ', chks + endif + if (associated(iobt%hcond)) then + chks = field_chksum( iobt%hcond ) ; if (root) write(outunit,100) 'iobt%hcond ', chks + endif + 100 FORMAT(" CHECKSUM::",A20," = ",Z20) call coupler_type_write_chksums(iobt%fluxes, outunit, 'iobt%') diff --git a/config_src/drivers/solo_driver/MOM_driver.F90 b/config_src/drivers/solo_driver/MOM_driver.F90 index d6630a0f17..deab29be50 100644 --- a/config_src/drivers/solo_driver/MOM_driver.F90 +++ b/config_src/drivers/solo_driver/MOM_driver.F90 @@ -286,14 +286,15 @@ program MOM_main Time = segment_start_time call initialize_MOM(Time, Start_time, param_file, dirs, MOM_CSp, restart_CSp, & segment_start_time, offline_tracer_mode=offline_tracer_mode, & - diag_ptr=diag, tracer_flow_CSp=tracer_flow_CSp, ice_shelf_CSp=ice_shelf_CSp) + diag_ptr=diag, tracer_flow_CSp=tracer_flow_CSp, ice_shelf_CSp=ice_shelf_CSp, & + waves_CSp=Waves_CSp) else ! In this case, the segment starts at a time read from the MOM restart file ! or is left at Start_time by MOM_initialize. Time = Start_time call initialize_MOM(Time, Start_time, param_file, dirs, MOM_CSp, restart_CSp, & offline_tracer_mode=offline_tracer_mode, diag_ptr=diag, & - tracer_flow_CSp=tracer_flow_CSp, ice_shelf_CSp=ice_shelf_CSp) + tracer_flow_CSp=tracer_flow_CSp, ice_shelf_CSp=ice_shelf_CSp, waves_CSp=Waves_CSp) endif call get_MOM_state_elements(MOM_CSp, G=grid, GV=GV, US=US, C_p_scaled=fluxes%C_p) diff --git a/src/ALE/MOM_regridding.F90 b/src/ALE/MOM_regridding.F90 index 95a77f503d..8361a040c5 100644 --- a/src/ALE/MOM_regridding.F90 +++ b/src/ALE/MOM_regridding.F90 @@ -497,7 +497,6 @@ subroutine initialize_regridding(CS, GV, US, max_depth, param_file, mdl, coord_m call setCoordinateResolution(dz, CS, scale=1.0) elseif (coordinateMode(coord_mode) == REGRIDDING_RHO) then call setCoordinateResolution(dz, CS, scale=US%kg_m3_to_R) - CS%coord_scale = US%R_to_kg_m3 elseif (coordinateMode(coord_mode) == REGRIDDING_ADAPTIVE) then call setCoordinateResolution(dz, CS, scale=GV%m_to_H) CS%coord_scale = GV%H_to_m @@ -507,6 +506,14 @@ subroutine initialize_regridding(CS, GV, US, max_depth, param_file, mdl, coord_m endif endif + ! set coord_scale for RHO regridding independent of allocation status of dz + if (coordinateMode(coord_mode) == REGRIDDING_RHO) then + CS%coord_scale = US%R_to_kg_m3 + endif + + ! ensure CS%ref_pressure is rescaled properly + CS%ref_pressure = (US%kg_m3_to_R * US%m_s_to_L_T**2) * CS%ref_pressure + if (allocated(rho_target)) then call set_target_densities(CS, US%kg_m3_to_R*rho_target) deallocate(rho_target) diff --git a/src/core/MOM.F90 b/src/core/MOM.F90 index c4f3d40343..0a08b9bc88 100644 --- a/src/core/MOM.F90 +++ b/src/core/MOM.F90 @@ -109,6 +109,7 @@ module MOM use MOM_shared_initialization, only : write_ocean_geometry_file use MOM_sponge, only : init_sponge_diags, sponge_CS use MOM_state_initialization, only : MOM_initialize_state +use MOM_stoch_eos, only : MOM_stoch_eos_init,MOM_stoch_eos_run,MOM_stoch_eos_CS,mom_calc_varT use MOM_sum_output, only : write_energy, accumulate_net_input use MOM_sum_output, only : MOM_sum_output_init, MOM_sum_output_end use MOM_sum_output, only : sum_output_CS @@ -137,7 +138,7 @@ module MOM use MOM_verticalGrid, only : verticalGrid_type, verticalGridInit, verticalGridEnd use MOM_verticalGrid, only : fix_restart_scaling use MOM_verticalGrid, only : get_thickness_units, get_flux_units, get_tr_flux_units -use MOM_wave_interface, only : wave_parameters_CS, waves_end +use MOM_wave_interface, only : wave_parameters_CS, waves_end, waves_register_restarts use MOM_wave_interface, only : Update_Stokes_Drift use MOM_porous_barriers, only : porous_widths @@ -243,6 +244,7 @@ module MOM logical :: use_ALE_algorithm !< If true, use the ALE algorithm rather than layered !! isopycnal/stacked shallow water mode. This logical is set by calling the !! function useRegridding() from the MOM_regridding module. + type(MOM_stoch_eos_CS) :: stoch_eos_CS !< structure containing random pattern for stoch EOS logical :: alternate_first_direction !< If true, alternate whether the x- or y-direction !! updates occur first in directionally split parts of the calculation. real :: first_dir_restart = -1.0 !< A real copy of G%first_direction for use in restart files @@ -445,6 +447,8 @@ module MOM integer :: id_clock_other integer :: id_clock_offline_tracer integer :: id_clock_unit_tests +integer :: id_clock_stoch +integer :: id_clock_varT !>@} contains @@ -691,16 +695,16 @@ subroutine step_MOM(forces_in, fluxes_in, sfc_state, Time_start, time_int_in, CS else CS%p_surf_end => forces%p_surf endif - if (CS%UseWaves) then ! Update wave information, which is presently kept static over each call to step_mom call enable_averages(time_interval, Time_start + real_to_time(US%T_to_s*time_interval), CS%diag) - call Update_Stokes_Drift(G, GV, US, Waves, h, forces%ustar) + call Update_Stokes_Drift(G, GV, US, Waves, h, forces%ustar, time_interval, do_dyn) call disable_averaging(CS%diag) endif else ! not do_dyn. - if (CS%UseWaves) & ! Diagnostics are not enabled in this call. - call Update_Stokes_Drift(G, GV, US, Waves, h, fluxes%ustar) + if (CS%UseWaves) then ! Diagnostics are not enabled in this call. + call Update_Stokes_Drift(G, GV, US, Waves, h, fluxes%ustar, time_interval, do_dyn) + endif endif if (CS%debug) then @@ -1047,7 +1051,7 @@ subroutine step_MOM_dynamics(forces, p_surf_begin, p_surf_end, dt, dt_thermo, & ! barotropic time step needs to be updated. logical :: showCallTree - integer :: is, ie, js, je, Isq, Ieq, Jsq, Jeq, nz + integer :: i, j, k, is, ie, js, je, Isq, Ieq, Jsq, Jeq, nz integer :: isd, ied, jsd, jed, IsdB, IedB, JsdB, JedB real, dimension(SZI_(CS%G),SZJ_(CS%G),SZK_(CS%G)+1) :: eta_por ! layer interface heights @@ -1061,6 +1065,15 @@ subroutine step_MOM_dynamics(forces, p_surf_begin, p_surf_end, dt, dt_thermo, & showCallTree = callTree_showQuery() call cpu_clock_begin(id_clock_dynamics) + call cpu_clock_begin(id_clock_stoch) + if (CS%stoch_eos_CS%use_stoch_eos) call MOM_stoch_eos_run(G,u,v,dt,Time_local,CS%stoch_eos_CS,CS%diag) + call cpu_clock_end(id_clock_stoch) + call cpu_clock_begin(id_clock_varT) + if (CS%stoch_eos_CS%stanley_coeff >= 0.0) then + call MOM_calc_varT(G,GV,h,CS%tv,CS%stoch_eos_CS,dt) + call pass_var(CS%tv%varT, G%Domain,clock=id_clock_pass,halo=1) + endif + call cpu_clock_end(id_clock_varT) if ((CS%t_dyn_rel_adv == 0.0) .and. CS%thickness_diffuse .and. CS%thickness_diffuse_first) then @@ -1136,6 +1149,30 @@ subroutine step_MOM_dynamics(forces, p_surf_begin, p_surf_end, dt, dt_thermo, & endif ! -------------------------------------------------- end SPLIT + ! Update the model's current to reflect wind-wave growth + if (Waves%Stokes_DDT .and. (.not.Waves%Passive_Stokes_DDT)) then + do J=jsq,jeq ; do i=is,ie + v(i,J,:) = v(i,J,:) + Waves%ddt_us_y(i,J,:)*dt + enddo; enddo + do j=js,je ; do I=isq,ieq + u(I,j,:) = u(I,j,:) + Waves%ddt_us_x(I,j,:)*dt + enddo; enddo + call pass_vector(u,v,G%Domain) + endif + ! Added an additional output to track Stokes drift time tendency. + ! It is mostly for debugging, and perhaps doesn't need to hang + ! around permanently. + if (Waves%Stokes_DDT .and. (Waves%id_3dstokes_y_from_ddt>0)) then + do J=jsq,jeq ; do i=is,ie + Waves%us_y_from_ddt(i,J,:) = Waves%us_y_from_ddt(i,J,:) + Waves%ddt_us_y(i,J,:)*dt + enddo; enddo + endif + if (Waves%Stokes_DDT .and. (Waves%id_3dstokes_x_from_ddt>0)) then + do j=js,je ; do I=isq,ieq + Waves%us_x_from_ddt(I,j,:) = Waves%us_x_from_ddt(I,j,:) + Waves%ddt_us_x(I,j,:)*dt + enddo; enddo + endif + if (CS%use_particles .and. CS%do_dynamics) then ! Run particles whether or not stepping is split call particles_run(CS%particles, Time_local, CS%u, CS%v, CS%h, CS%tv) ! Run the particles model endif @@ -1203,6 +1240,9 @@ subroutine step_MOM_dynamics(forces, p_surf_begin, p_surf_end, dt, dt_thermo, & if (IDs%id_u > 0) call post_data(IDs%id_u, u, CS%diag) if (IDs%id_v > 0) call post_data(IDs%id_v, v, CS%diag) if (IDs%id_h > 0) call post_data(IDs%id_h, h, CS%diag) + if (CS%stoch_eos_CS%id_stoch_eos > 0) call post_data(CS%stoch_eos_CS%id_stoch_eos, CS%stoch_eos_CS%pattern, CS%diag) + if (CS%stoch_eos_CS%id_stoch_phi > 0) call post_data(CS%stoch_eos_CS%id_stoch_phi, CS%stoch_eos_CS%phi, CS%diag) + if (CS%stoch_eos_CS%id_tvar_sgs > 0) call post_data(CS%stoch_eos_CS%id_tvar_sgs, CS%tv%varT, CS%diag) call disable_averaging(CS%diag) call cpu_clock_end(id_clock_diagnostics) ; call cpu_clock_end(id_clock_other) @@ -1703,7 +1743,7 @@ end subroutine step_offline !! initializing the ocean state variables, and initializing subsidiary modules subroutine initialize_MOM(Time, Time_init, param_file, dirs, CS, restart_CSp, & Time_in, offline_tracer_mode, input_restart_file, diag_ptr, & - count_calls, tracer_flow_CSp, ice_shelf_CSp) + count_calls, tracer_flow_CSp, ice_shelf_CSp, waves_CSp) type(time_type), target, intent(inout) :: Time !< model time, set in this routine type(time_type), intent(in) :: Time_init !< The start time for the coupled model's calendar type(param_file_type), intent(out) :: param_file !< structure indicating parameter file to parse @@ -1725,6 +1765,8 @@ subroutine initialize_MOM(Time, Time_init, param_file, dirs, CS, restart_CSp, & !! calls to step_MOM instead of the number of !! dynamics timesteps. type(ice_shelf_CS), optional, pointer :: ice_shelf_CSp !< A pointer to an ice shelf control structure + type(Wave_parameters_CS), & + optional, pointer :: Waves_CSp !< An optional pointer to a wave property CS ! local variables type(ocean_grid_type), pointer :: G => NULL() ! A pointer to the metric grid use for the run type(ocean_grid_type), pointer :: G_in => NULL() ! Pointer to the input grid @@ -1800,6 +1842,7 @@ subroutine initialize_MOM(Time, Time_init, param_file, dirs, CS, restart_CSp, & integer :: first_direction ! An integer that indicates which direction is to be ! updated first in directionally split parts of the ! calculation. + logical :: use_KPP ! If true, diabatic is using KPP vertical mixing integer :: nkml, nkbl, verbosity, write_geom integer :: dynamics_stencil ! The computational stencil for the calculations ! in the dynamic core. @@ -2327,13 +2370,18 @@ subroutine initialize_MOM(Time, Time_init, param_file, dirs, CS, restart_CSp, & call register_tracer(CS%tv%T, CS%tracer_Reg, param_file, HI, GV, & tr_desc=vd_T, registry_diags=.true., flux_nameroot='T', & flux_units='W', flux_longname='Heat', & + net_surfflux_name='KPP_QminusSW', NLT_budget_name='KPP_NLT_temp_budget', & + net_surfflux_longname='Net temperature flux ignoring short-wave, as used by [CVMix] KPP', & flux_scale=conv2watt, convergence_units='W m-2', & - convergence_scale=conv2watt, CMOR_tendprefix="opottemp", diag_form=2) + convergence_scale=conv2watt, CMOR_tendprefix="opottemp", diag_form=2, & + Tr_out=CS%tv%tr_T) call register_tracer(CS%tv%S, CS%tracer_Reg, param_file, HI, GV, & tr_desc=vd_S, registry_diags=.true., flux_nameroot='S', & flux_units=S_flux_units, flux_longname='Salt', & + net_surfflux_name='KPP_netSalt', NLT_budget_name='KPP_NLT_saln_budget', & flux_scale=conv2salt, convergence_units='kg m-2 s-1', & - convergence_scale=0.001*GV%H_to_kg_m2, CMOR_tendprefix="osalt", diag_form=2) + convergence_scale=0.001*GV%H_to_kg_m2, CMOR_tendprefix="osalt", diag_form=2, & + Tr_out=CS%tv%tr_S) endif endif @@ -2462,6 +2510,10 @@ subroutine initialize_MOM(Time, Time_init, param_file, dirs, CS, restart_CSp, & param_file, restart_CSp, use_temperature) endif + if (present(waves_CSp)) then + call waves_register_restarts(waves_CSp, HI, GV, param_file, restart_CSp) + endif + call callTree_waypoint("restart registration complete (initialize_MOM)") call restart_registry_lock(restart_CSp) @@ -2722,6 +2774,8 @@ subroutine initialize_MOM(Time, Time_init, param_file, dirs, CS, restart_CSp, & call set_visc_init(Time, G, GV, US, param_file, diag, CS%visc, CS%set_visc_CSp, restart_CSp, CS%OBC) call thickness_diffuse_init(Time, G, GV, US, param_file, diag, CS%CDp, CS%thickness_diffuse_CSp) + new_sim = is_new_run(restart_CSp) + call MOM_stoch_eos_init(G,Time,param_file,CS%stoch_eos_CS,restart_CSp,diag) if (CS%split) then allocate(eta(SZI_(G),SZJ_(G)), source=0.0) call initialize_dyn_split_RK2(CS%u, CS%v, CS%h, CS%uh, CS%vh, eta, Time, & @@ -2803,14 +2857,14 @@ subroutine initialize_MOM(Time, Time_init, param_file, dirs, CS, restart_CSp, & call register_surface_diags(Time, G, US, CS%sfc_IDs, CS%diag, CS%tv) call register_diags(Time, G, GV, US, CS%IDs, CS%diag) call register_transport_diags(Time, G, GV, US, CS%transport_IDs, CS%diag) + call extract_diabatic_member(CS%diabatic_CSp, use_KPP=use_KPP) call register_tracer_diagnostics(CS%tracer_Reg, CS%h, Time, diag, G, GV, US, & - CS%use_ALE_algorithm) + CS%use_ALE_algorithm, use_KPP) if (CS%use_ALE_algorithm) then call ALE_register_diags(Time, G, GV, US, diag, CS%ALE_CSp) endif ! This subroutine initializes any tracer packages. - new_sim = is_new_run(restart_CSp) call tracer_flow_control_init(.not.new_sim, Time, G, GV, US, CS%h, param_file, & CS%diag, CS%OBC, CS%tracer_flow_CSp, CS%sponge_CSp, & CS%ALE_sponge_CSp, CS%tv) @@ -3016,6 +3070,7 @@ subroutine register_diags(Time, G, GV, US, IDs, diag) v_extensive=.true.) IDs%id_ssh_inst = register_diag_field('ocean_model', 'SSH_inst', diag%axesT1, & Time, 'Instantaneous Sea Surface Height', 'm', conversion=US%Z_to_m) + end subroutine register_diags !> Set up CPU clock IDs for timing various subroutines. @@ -3048,6 +3103,8 @@ subroutine MOM_timing_init(CS) if (CS%offline_tracer_mode) then id_clock_offline_tracer = cpu_clock_id('Ocean offline tracers', grain=CLOCK_SUBCOMPONENT) endif + id_clock_stoch = cpu_clock_id('(Stochastic EOS)', grain=CLOCK_MODULE) + id_clock_varT = cpu_clock_id('(SGS Temperature Variance)', grain=CLOCK_MODULE) end subroutine MOM_timing_init diff --git a/src/core/MOM_CoriolisAdv.F90 b/src/core/MOM_CoriolisAdv.F90 index c1dfd500dc..6aacc479af 100644 --- a/src/core/MOM_CoriolisAdv.F90 +++ b/src/core/MOM_CoriolisAdv.F90 @@ -18,6 +18,7 @@ module MOM_CoriolisAdv use MOM_unit_scaling, only : unit_scale_type use MOM_variables, only : accel_diag_ptrs, porous_barrier_ptrs use MOM_verticalGrid, only : verticalGrid_type +use MOM_wave_interface, only : wave_parameters_CS implicit none ; private @@ -85,6 +86,7 @@ module MOM_CoriolisAdv integer :: id_h_gKEu = -1, id_h_gKEv = -1 integer :: id_h_rvxu = -1, id_h_rvxv = -1 integer :: id_intz_rvxu_2d = -1, id_intz_rvxv_2d = -1 + integer :: id_CAuS = -1, id_CAvS = -1 !>@} end type CoriolisAdv_CS @@ -120,7 +122,7 @@ module MOM_CoriolisAdv contains !> Calculates the Coriolis and momentum advection contributions to the acceleration. -subroutine CorAdCalc(u, v, h, uh, vh, CAu, CAv, OBC, AD, G, GV, US, CS, pbv) +subroutine CorAdCalc(u, v, h, uh, vh, CAu, CAv, OBC, AD, G, GV, US, CS, pbv, Waves) type(ocean_grid_type), intent(in) :: G !< Ocean grid structure type(verticalGrid_type), intent(in) :: GV !< Vertical grid structure real, dimension(SZIB_(G),SZJ_(G),SZK_(GV)), intent(in) :: u !< Zonal velocity [L T-1 ~> m s-1] @@ -139,10 +141,12 @@ subroutine CorAdCalc(u, v, h, uh, vh, CAu, CAv, OBC, AD, G, GV, US, CS, pbv) type(unit_scale_type), intent(in) :: US !< A dimensional unit scaling type type(CoriolisAdv_CS), intent(in) :: CS !< Control structure for MOM_CoriolisAdv type(porous_barrier_ptrs), intent(in) :: pbv !< porous barrier fractional cell metrics + type(Wave_parameters_CS), optional, pointer :: Waves !< An optional pointer to Stokes drift CS ! Local variables real, dimension(SZIB_(G),SZJB_(G)) :: & q, & ! Layer potential vorticity [H-1 T-1 ~> m-1 s-1 or m2 kg-1 s-1]. + qS, & ! Layer Stokes vorticity [H-1 T-1 ~> m-1 s-1 or m2 kg-1 s-1]. Ih_q, & ! The inverse of thickness interpolated to q points [H-1 ~> m-1 or m2 kg-1]. Area_q ! The sum of the ocean areas at the 4 adjacent thickness points [L2 ~> m2]. @@ -176,12 +180,16 @@ subroutine CorAdCalc(u, v, h, uh, vh, CAu, CAv, OBC, AD, G, GV, US, CS, pbv) ! discretization [H-1 T-1 ~> m-1 s-1 or m2 kg-1 s-1]. real, dimension(SZIB_(G),SZJB_(G)) :: & dvdx, dudy, & ! Contributions to the circulation around q-points [L2 T-1 ~> m2 s-1] + dvSdx, duSdy, & ! idem. for Stokes drift [L2 T-1 ~> m2 s-1] rel_vort, & ! Relative vorticity at q-points [T-1 ~> s-1]. abs_vort, & ! Absolute vorticity at q-points [T-1 ~> s-1]. + stk_vort, & ! Stokes vorticity at q-points [T-1 ~> s-1]. q2 ! Relative vorticity over thickness [H-1 T-1 ~> m-1 s-1 or m2 kg-1 s-1]. real, dimension(SZIB_(G),SZJB_(G),SZK_(GV)) :: & PV, & ! A diagnostic array of the potential vorticities [H-1 T-1 ~> m-1 s-1 or m2 kg-1 s-1]. RV ! A diagnostic array of the relative vorticities [T-1 ~> s-1]. + real, dimension(SZIB_(G),SZJ_(G),SZK_(G)) :: CAuS ! + real, dimension(SZI_(G),SZJB_(G),SZK_(G)) :: CAvS ! real :: fv1, fv2, fv3, fv4 ! (f+rv)*v [L T-2 ~> m s-2]. real :: fu1, fu2, fu3, fu4 ! -(f+rv)*u [L T-2 ~> m s-2]. real :: max_fv, max_fu ! The maximum or minimum of the neighboring Coriolis @@ -217,6 +225,7 @@ subroutine CorAdCalc(u, v, h, uh, vh, CAu, CAv, OBC, AD, G, GV, US, CS, pbv) real :: UHeff, VHeff ! More temporary variables [H L2 T-1 ~> m3 s-1 or kg s-1]. real :: QUHeff,QVHeff ! More temporary variables [H L2 T-2 ~> m3 s-2 or kg s-2]. integer :: i, j, k, n, is, ie, js, je, Isq, Ieq, Jsq, Jeq, nz + logical :: Stokes_VF ! To work, the following fields must be set outside of the usual ! is to ie range before this subroutine is called: @@ -263,9 +272,14 @@ subroutine CorAdCalc(u, v, h, uh, vh, CAu, CAv, OBC, AD, G, GV, US, CS, pbv) (Area_h(i+1,j) + Area_h(i,j+1)) enddo ; enddo + Stokes_VF = .false. + if (present(Waves)) then ; if (associated(Waves)) then + Stokes_VF = Waves%Stokes_VF + endif ; endif + !$OMP parallel do default(private) shared(u,v,h,uh,vh,CAu,CAv,G,GV,CS,AD,Area_h,Area_q,& !$OMP RV,PV,is,ie,js,je,Isq,Ieq,Jsq,Jeq,nz,vol_neglect,h_tiny,OBC,eps_vel, & - !$OMP pbv) + !$OMP pbv, Stokes_VF) do k=1,nz ! Here the second order accurate layer potential vorticities, q, @@ -273,10 +287,34 @@ subroutine CorAdCalc(u, v, h, uh, vh, CAu, CAv, OBC, AD, G, GV, US, CS, pbv) ! vorticity is second order accurate everywhere with free slip b.c.s, ! but only first order accurate at boundaries with no slip b.c.s. ! First calculate the contributions to the circulation around the q-point. - do J=Jsq-1,Jeq+1 ; do I=Isq-1,Ieq+1 - dvdx(I,J) = (v(i+1,J,k)*G%dyCv(i+1,J) - v(i,J,k)*G%dyCv(i,J)) - dudy(I,J) = (u(I,j+1,k)*G%dxCu(I,j+1) - u(I,j,k)*G%dxCu(I,j)) - enddo ; enddo + if (Stokes_VF) then + if (CS%id_CAuS>0 .or. CS%id_CAvS>0) then + do J=Jsq-1,Jeq+1 ; do I=Isq-1,Ieq+1 + dvSdx(I,J) = ((-Waves%us_y(i+1,J,k))*G%dyCv(i+1,J) - & + (-Waves%us_y(i,J,k))*G%dyCv(i,J)) + duSdy(I,J) = ((-Waves%us_x(I,j+1,k))*G%dxCu(I,j+1) - & + (-Waves%us_x(I,j,k))*G%dxCu(I,j)) + enddo; enddo + endif + if (.not. Waves%Passive_Stokes_VF) then + do J=Jsq-1,Jeq+1 ; do I=Isq-1,Ieq+1 + dvdx(I,J) = ((v(i+1,J,k)-Waves%us_y(i+1,J,k))*G%dyCv(i+1,J) - & + (v(i,J,k)-Waves%us_y(i,J,k))*G%dyCv(i,J)) + dudy(I,J) = ((u(I,j+1,k)-Waves%us_x(I,j+1,k))*G%dxCu(I,j+1) - & + (u(I,j,k)-Waves%us_x(I,j,k))*G%dxCu(I,j)) + enddo; enddo + else + do J=Jsq-1,Jeq+1 ; do I=Isq-1,Ieq+1 + dvdx(I,J) = (v(i+1,J,k)*G%dyCv(i+1,J) - v(i,J,k)*G%dyCv(i,J)) + dudy(I,J) = (u(I,j+1,k)*G%dxCu(I,j+1) - u(I,j,k)*G%dxCu(I,j)) + enddo; enddo + endif + else + do J=Jsq-1,Jeq+1 ; do I=Isq-1,Ieq+1 + dvdx(I,J) = (v(i+1,J,k)*G%dyCv(i+1,J) - v(i,J,k)*G%dyCv(i,J)) + dudy(I,J) = (u(I,j+1,k)*G%dxCu(I,j+1) - u(I,j,k)*G%dxCu(I,j)) + enddo; enddo + endif do J=Jsq-1,Jeq+1 ; do i=Isq-1,Ieq+2 hArea_v(i,J) = 0.5*(Area_h(i,j) * h(i,j,k) + Area_h(i,j+1) * h(i,j+1,k)) enddo ; enddo @@ -420,11 +458,25 @@ subroutine CorAdCalc(u, v, h, uh, vh, CAu, CAv, OBC, AD, G, GV, US, CS, pbv) if (CS%no_slip) then do J=Jsq-1,Jeq+1 ; do I=Isq-1,Ieq+1 rel_vort(I,J) = (2.0 - G%mask2dBu(I,J)) * (dvdx(I,J) - dudy(I,J)) * G%IareaBu(I,J) - enddo ; enddo + enddo; enddo + if (Stokes_VF) then + if (CS%id_CAuS>0 .or. CS%id_CAvS>0) then + do J=Jsq-1,Jeq+1 ; do I=Isq-1,Ieq+1 + stk_vort(I,J) = (2.0 - G%mask2dBu(I,J)) * (dvSdx(I,J) - duSdy(I,J)) * G%IareaBu(I,J) + enddo; enddo + endif + endif else do J=Jsq-1,Jeq+1 ; do I=Isq-1,Ieq+1 rel_vort(I,J) = G%mask2dBu(I,J) * (dvdx(I,J) - dudy(I,J)) * G%IareaBu(I,J) - enddo ; enddo + enddo; enddo + if (Stokes_VF) then + if (CS%id_CAuS>0 .or. CS%id_CAvS>0) then + do J=Jsq-1,Jeq+1 ; do I=Isq-1,Ieq+1 + stk_vort(I,J) = (2.0 - G%mask2dBu(I,J)) * (dvSdx(I,J) - duSdy(I,J)) * G%IareaBu(I,J) + enddo; enddo + endif + endif endif do J=Jsq-1,Jeq+1 ; do I=Isq-1,Ieq+1 @@ -435,7 +487,15 @@ subroutine CorAdCalc(u, v, h, uh, vh, CAu, CAv, OBC, AD, G, GV, US, CS, pbv) hArea_q = (hArea_u(I,j) + hArea_u(I,j+1)) + (hArea_v(i,J) + hArea_v(i+1,J)) Ih_q(I,J) = Area_q(I,J) / (hArea_q + vol_neglect) q(I,J) = abs_vort(I,J) * Ih_q(I,J) - enddo ; enddo + enddo; enddo + + if (Stokes_VF) then + if (CS%id_CAuS>0 .or. CS%id_CAvS>0) then + do J=Jsq-1,Jeq+1 ; do I=Isq-1,Ieq+1 + qS(I,J) = stk_vort(I,J) * Ih_q(I,J) + enddo; enddo + endif + endif if (CS%id_rv > 0) then do J=Jsq-1,Jeq+1 ; do I=Isq-1,Ieq+1 @@ -659,6 +719,17 @@ subroutine CorAdCalc(u, v, h, uh, vh, CAu, CAv, OBC, AD, G, GV, US, CS, pbv) (ep_u(i,j)*uh(I-1,j,k) - ep_u(i+1,j)*uh(I+1,j,k)) * G%IdxCu(I,j) enddo ; enddo ; endif + if (Stokes_VF) then + if (CS%id_CAuS>0 .or. CS%id_CAvS>0) then + ! Computing the diagnostic Stokes contribution to CAu + do j=js,je ; do I=Isq,Ieq + CAuS(I,j,k) = 0.25 * & + (qS(I,J) * (vh(i+1,J,k) + vh(i,J,k)) + & + qS(I,J-1) * (vh(i,J-1,k) + vh(i+1,J-1,k))) * G%IdxCu(I,j) + enddo ; enddo + endif + endif + if (CS%bound_Coriolis) then do j=js,je ; do I=Isq,Ieq fv1 = abs_vort(I,J) * v(i+1,J,k) @@ -772,6 +843,17 @@ subroutine CorAdCalc(u, v, h, uh, vh, CAu, CAv, OBC, AD, G, GV, US, CS, pbv) (ep_v(i,j)*vh(i,J-1,k) - ep_v(i,j+1)*vh(i,J+1,k)) * G%IdyCv(i,J) enddo ; enddo ; endif + if (Stokes_VF) then + if (CS%id_CAuS>0 .or. CS%id_CAvS>0) then + ! Computing the diagnostic Stokes contribution to CAv + do J=Jsq,Jeq ; do i=is,ie + CAvS(I,j,k) = 0.25 * & + (qS(I,J) * (uh(I,j+1,k) + uh(I,j,k)) + & + qS(I,J-1) * (uh(I-1,j,k) + uh(I-1,j+1,k))) * G%IdyCv(i,J) + enddo; enddo + endif + endif + if (CS%bound_Coriolis) then do J=Jsq,Jeq ; do i=is,ie fu1 = -abs_vort(I,J) * u(I,j+1,k) @@ -848,6 +930,10 @@ subroutine CorAdCalc(u, v, h, uh, vh, CAu, CAv, OBC, AD, G, GV, US, CS, pbv) if (CS%id_gKEv>0) call post_data(CS%id_gKEv, AD%gradKEv, CS%diag) if (CS%id_rvxu > 0) call post_data(CS%id_rvxu, AD%rv_x_u, CS%diag) if (CS%id_rvxv > 0) call post_data(CS%id_rvxv, AD%rv_x_v, CS%diag) + if (Stokes_VF) then + if (CS%id_CAuS > 0) call post_data(CS%id_CAuS, CAuS, CS%diag) + if (CS%id_CAvS > 0) call post_data(CS%id_CAvS, CAvS, CS%diag) + endif ! Diagnostics for terms multiplied by fractional thicknesses @@ -1124,6 +1210,14 @@ subroutine CoriolisAdv_init(Time, G, GV, US, param_file, diag, AD, CS) CS%id_rvxv = register_diag_field('ocean_model', 'rvxv', diag%axesCuL, Time, & 'Zonal Acceleration from Relative Vorticity', 'm s-2', conversion=US%L_T2_to_m_s2) + CS%id_CAuS = register_diag_field('ocean_model', 'CAu_Stokes', diag%axesCuL, Time, & + 'Zonal Acceleration from Stokes Vorticity', 'm-1 s-2', conversion=US%L_T2_to_m_s2) + ! add to AD + + CS%id_CAvS = register_diag_field('ocean_model', 'CAv_Stokes', diag%axesCvL, Time, & + 'Meridional Acceleration from Stokes Vorticity', 'm-1 s-2', conversion=US%L_T2_to_m_s2) + ! add to AD + !CS%id_hf_gKEu = register_diag_field('ocean_model', 'hf_gKEu', diag%axesCuL, Time, & ! 'Fractional Thickness-weighted Zonal Acceleration from Grad. Kinetic Energy', & ! 'm s-2', v_extensive=.true., conversion=US%L_T2_to_m_s2) diff --git a/src/core/MOM_PressureForce_FV.F90 b/src/core/MOM_PressureForce_FV.F90 index 2a79486a5f..d50ce4b364 100644 --- a/src/core/MOM_PressureForce_FV.F90 +++ b/src/core/MOM_PressureForce_FV.F90 @@ -58,11 +58,12 @@ module MOM_PressureForce_FV integer :: Recon_Scheme !< Order of the polynomial of the reconstruction of T & S !! for the finite volume pressure gradient calculation. !! By the default (1) is for a piecewise linear method - real :: Stanley_T2_det_coeff !< The coefficient correlating SGS temperature variance with - !! the mean temperature gradient in the deterministic part of - !! the Stanley form of the Brankart correction. + + logical :: use_stanley_pgf !< If true, turn on Stanley parameterization in the PGF integer :: id_e_tidal = -1 !< Diagnostic identifier - integer :: id_tvar_sgs = -1 !< Diagnostic identifier + integer :: id_rho_pgf = -1 !< Diagnostic identifier + integer :: id_rho_stanley_pgf = -1 !< Diagnostic identifier + integer :: id_p_stanley = -1 !< Diagnostic identifier type(tidal_forcing_CS), pointer :: tides_CSp => NULL() !< Tides control structure end type PressureForce_FV_CS @@ -167,7 +168,7 @@ subroutine PressureForce_FV_nonBouss(h, tv, PFu, PFv, G, GV, US, CS, ALE_CSp, p_ if (.not.CS%initialized) call MOM_error(FATAL, & "MOM_PressureForce_FV_nonBouss: Module must be initialized before it is used.") - if (CS%Stanley_T2_det_coeff>=0.) call MOM_error(FATAL, & + if (CS%use_stanley_pgf) call MOM_error(FATAL, & "MOM_PressureForce_FV_nonBouss: The Stanley parameterization is not yet"//& "implemented in non-Boussinesq mode.") @@ -473,6 +474,13 @@ subroutine PressureForce_FV_Bouss(h, tv, PFu, PFv, G, GV, US, CS, ALE_CSp, p_atm real, dimension(SZI_(G),SZJ_(G),SZK_(GV)) :: & S_t, S_b, T_t, T_b ! Top and bottom edge values for linear reconstructions ! of salinity and temperature within each layer. + real, dimension(SZI_(G),SZJ_(G),SZK_(G)) :: & + rho_pgf, rho_stanley_pgf ! Density [kg m-3] from EOS with and without SGS T variance + ! in Stanley parameterization. + real, dimension(SZI_(G),SZJ_(G),SZK_(G)) :: & + p_stanley ! Pressure [Pa] estimated with Rho_0 + real :: rho_stanley_scalar ! Scalar quantity to hold density [kg m-3] in Stanley diagnostics. + real :: p_stanley_scalar ! Scalar quantity to hold pressure [Pa] in Stanley diagnostics. real :: rho_in_situ(SZI_(G)) ! The in situ density [R ~> kg m-3]. real :: p_ref(SZI_(G)) ! The pressure used to calculate the coordinate ! density, [R L2 T-2 ~> Pa] (usually 2e7 Pa = 2000 dbar). @@ -487,11 +495,6 @@ subroutine PressureForce_FV_Bouss(h, tv, PFu, PFv, G, GV, US, CS, ALE_CSp, p_atm logical :: use_ALE ! If true, use an ALE pressure reconstruction. logical :: use_EOS ! If true, density is calculated from T & S using an equation of state. type(thermo_var_ptrs) :: tv_tmp! A structure of temporary T & S. - real :: Tl(5) ! copy and T in local stencil [degC] - real :: mn_T ! mean of T in local stencil [degC] - real :: mn_T2 ! mean of T**2 in local stencil [degC2] - real :: hl(5) ! Copy of local stencil of H [H ~> m] - real :: r_sm_H ! Reciprocal of sum of H in local stencil [H-1 ~> m-1] real, parameter :: C1_6 = 1.0/6.0 integer, dimension(2) :: EOSdom ! The i-computational domain for the equation of state integer :: is, ie, js, je, Isq, Ieq, Jsq, Jeq, nz, nkmb @@ -511,49 +514,6 @@ subroutine PressureForce_FV_Bouss(h, tv, PFu, PFv, G, GV, US, CS, ALE_CSp, p_atm use_ALE = .false. if (associated(ALE_CSp)) use_ALE = CS%reconstruct .and. use_EOS - if (CS%Stanley_T2_det_coeff>=0.) then - if (.not. associated(tv%varT)) call safe_alloc_ptr(tv%varT, G%isd, G%ied, G%jsd, G%jed, GV%ke) - do k=1, nz ; do j=js-1,je+1 ; do i=is-1,ie+1 - ! Strictly speaking we should estimate the *horizontal* grid-scale variance - ! but neither of the following blocks make a rotation to the horizontal - ! and instead work along coordinate. - - ! This block calculates a simple |delta T| along coordinates and does - ! not allow vanishing layer thicknesses or layers tracking topography - !! SGS variance in i-direction [degC2] - !dTdi2 = ( ( G%mask2dCu(I ,j) * G%IdxCu(I ,j) * ( tv%T(i+1,j,k) - tv%T(i,j,k) ) & - ! + G%mask2dCu(I-1,j) * G%IdxCu(I-1,j) * ( tv%T(i,j,k) - tv%T(i-1,j,k) ) & - ! ) * G%dxT(i,j) * 0.5 )**2 - !! SGS variance in j-direction [degC2] - !dTdj2 = ( ( G%mask2dCv(i,J ) * G%IdyCv(i,J ) * ( tv%T(i,j+1,k) - tv%T(i,j,k) ) & - ! + G%mask2dCv(i,J-1) * G%IdyCv(i,J-1) * ( tv%T(i,j,k) - tv%T(i,j-1,k) ) & - ! ) * G%dyT(i,j) * 0.5 )**2 - !tv%varT(i,j,k) = CS%Stanley_T2_det_coeff * 0.5 * ( dTdi2 + dTdj2 ) - - ! This block does a thickness weighted variance calculation and helps control for - ! extreme gradients along layers which are vanished against topography. It is - ! still a poor approximation in the interior when coordinates are strongly tilted. - hl(1) = h(i,j,k) * G%mask2dT(i,j) - hl(2) = h(i-1,j,k) * G%mask2dCu(I-1,j) - hl(3) = h(i+1,j,k) * G%mask2dCu(I,j) - hl(4) = h(i,j-1,k) * G%mask2dCv(i,J-1) - hl(5) = h(i,j+1,k) * G%mask2dCv(i,J) - r_sm_H = 1. / ( ( hl(1) + ( ( hl(2) + hl(3) ) + ( hl(4) + hl(5) ) ) ) + GV%H_subroundoff ) - ! Mean of T - Tl(1) = tv%T(i,j,k) ; Tl(2) = tv%T(i-1,j,k) ; Tl(3) = tv%T(i+1,j,k) - Tl(4) = tv%T(i,j-1,k) ; Tl(5) = tv%T(i,j+1,k) - mn_T = ( hl(1)*Tl(1) + ( ( hl(2)*Tl(2) + hl(3)*Tl(3) ) + ( hl(4)*Tl(4) + hl(5)*Tl(5) ) ) ) * r_sm_H - ! Adjust T vectors to have zero mean - Tl(:) = Tl(:) - mn_T ; mn_T = 0. - ! Variance of T - mn_T2 = ( hl(1)*Tl(1)*Tl(1) + ( ( hl(2)*Tl(2)*Tl(2) + hl(3)*Tl(3)*Tl(3) ) & - + ( hl(4)*Tl(4)*Tl(4) + hl(5)*Tl(5)*Tl(5) ) ) ) * r_sm_H - ! Variance should be positive but round-off can violate this. Calculating - ! variance directly would fix this but requires more operations. - tv%varT(i,j,k) = CS%Stanley_T2_det_coeff * max(0., mn_T2) - enddo ; enddo ; enddo - endif - h_neglect = GV%H_subroundoff dz_neglect = GV%H_subroundoff * GV%H_to_Z I_Rho0 = 1.0 / GV%Rho0 @@ -690,7 +650,6 @@ subroutine PressureForce_FV_Bouss(h, tv, PFu, PFv, G, GV, US, CS, ALE_CSp, p_atm do k=1,nz ! Calculate 4 integrals through the layer that are required in the ! subsequent calculation. - if (use_EOS) then ! The following routine computes the integrals that are needed to ! calculate the pressure gradient force. Linear profiles for T and S are @@ -701,13 +660,13 @@ subroutine PressureForce_FV_Bouss(h, tv, PFu, PFv, G, GV, US, CS, ALE_CSp, p_atm if ( CS%Recon_Scheme == 1 ) then call int_density_dz_generic_plm(k, tv, T_t, T_b, S_t, S_b, e, & rho_ref, CS%Rho0, GV%g_Earth, dz_neglect, G%bathyT, & - G%HI, GV, tv%eqn_of_state, US, dpa, intz_dpa, intx_dpa, inty_dpa, & + G%HI, GV, tv%eqn_of_state, US, CS%use_stanley_pgf, dpa, intz_dpa, intx_dpa, inty_dpa, & useMassWghtInterp=CS%useMassWghtInterp, & use_inaccurate_form=CS%use_inaccurate_pgf_rho_anom, Z_0p=G%Z_ref) elseif ( CS%Recon_Scheme == 2 ) then call int_density_dz_generic_ppm(k, tv, T_t, T_b, S_t, S_b, e, & rho_ref, CS%Rho0, GV%g_Earth, dz_neglect, G%bathyT, & - G%HI, GV, tv%eqn_of_state, US, dpa, intz_dpa, intx_dpa, inty_dpa, & + G%HI, GV, tv%eqn_of_state, US, CS%use_stanley_pgf, dpa, intz_dpa, intx_dpa, inty_dpa, & useMassWghtInterp=CS%useMassWghtInterp, Z_0p=G%Z_ref) endif else @@ -797,8 +756,26 @@ subroutine PressureForce_FV_Bouss(h, tv, PFu, PFv, G, GV, US, CS, ALE_CSp, p_atm endif endif + if (CS%use_stanley_pgf) then + do j=js,je ; do i=is,ie ; + p_stanley_scalar=0.0 + do k=1, nz + p_stanley_scalar = p_stanley_scalar + 0.5 * h(i,j,k) * GV%H_to_Pa !Pressure at mid-point of layer + call calculate_density(tv%T(i,j,k), tv%S(i,j,k), p_stanley_scalar, 0.0, 0.0, 0.0, & + rho_stanley_scalar, tv%eqn_of_state) + rho_pgf(i,j,k) = rho_stanley_scalar + call calculate_density(tv%T(i,j,k), tv%S(i,j,k), p_stanley_scalar, tv%varT(i,j,k), 0.0, 0.0, & + rho_stanley_scalar, tv%eqn_of_state) + rho_stanley_pgf(i,j,k) = rho_stanley_scalar + p_stanley(i,j,k) = p_stanley_scalar + p_stanley_scalar = p_stanley_scalar + 0.5 * h(i,j,k) * GV%H_to_Pa !Pressure at bottom of layer + enddo; enddo; enddo + endif + if (CS%id_e_tidal>0) call post_data(CS%id_e_tidal, e_tidal, CS%diag) - if (CS%id_tvar_sgs>0) call post_data(CS%id_tvar_sgs, tv%varT, CS%diag) + if (CS%id_rho_pgf>0) call post_data(CS%id_rho_pgf, rho_pgf, CS%diag) + if (CS%id_rho_stanley_pgf>0) call post_data(CS%id_rho_stanley_pgf, rho_stanley_pgf, CS%diag) + if (CS%id_p_stanley>0) call post_data(CS%id_p_stanley, p_stanley, CS%diag) end subroutine PressureForce_FV_Bouss @@ -859,14 +836,16 @@ subroutine PressureForce_FV_init(Time, G, GV, US, param_file, diag, CS, tides_CS "boundary cells is extrapolated, rather than using PCM "//& "in these cells. If true, the same order polynomial is "//& "used as is used for the interior cells.", default=.true.) - call get_param(param_file, mdl, "PGF_STANLEY_T2_DET_COEFF", CS%Stanley_T2_det_coeff, & - "The coefficient correlating SGS temperature variance with "// & - "the mean temperature gradient in the deterministic part of "// & - "the Stanley form of the Brankart correction. "// & - "Negative values disable the scheme.", units="nondim", default=-1.0) - if (CS%Stanley_T2_det_coeff>=0.) then - CS%id_tvar_sgs = register_diag_field('ocean_model', 'tvar_sgs_pgf', diag%axesTL, & - Time, 'SGS temperature variance used in PGF', 'degC2') + call get_param(param_file, mdl, "USE_STANLEY_PGF", CS%use_stanley_pgf, & + "If true, turn on Stanley SGS T variance parameterization "// & + "in PGF code.", default=.false.) + if (CS%use_stanley_pgf) then + CS%id_rho_pgf = register_diag_field('ocean_model', 'rho_pgf', diag%axesTL, & + Time, 'rho in PGF', 'kg m3') + CS%id_rho_stanley_pgf = register_diag_field('ocean_model', 'rho_stanley_pgf', diag%axesTL, & + Time, 'rho in PGF with Stanley correction', 'kg m3') + CS%id_p_stanley = register_diag_field('ocean_model', 'p_stanley', diag%axesTL, & + Time, 'p in PGF with Stanley correction', 'Pa') endif if (CS%tides) then CS%id_e_tidal = register_diag_field('ocean_model', 'e_tidal', diag%axesT1, & diff --git a/src/core/MOM_density_integrals.F90 b/src/core/MOM_density_integrals.F90 index 4b7ba9454a..7950cff185 100644 --- a/src/core/MOM_density_integrals.F90 +++ b/src/core/MOM_density_integrals.F90 @@ -337,7 +337,7 @@ end subroutine int_density_dz_generic_pcm !> Compute pressure gradient force integrals by quadrature for the case where !! T and S are linear profiles. subroutine int_density_dz_generic_plm(k, tv, T_t, T_b, S_t, S_b, e, rho_ref, & - rho_0, G_e, dz_subroundoff, bathyT, HI, GV, EOS, US, dpa, & + rho_0, G_e, dz_subroundoff, bathyT, HI, GV, EOS, US, use_stanley_eos, dpa, & intz_dpa, intx_dpa, inty_dpa, useMassWghtInterp, & use_inaccurate_form, Z_0p) integer, intent(in) :: k !< Layer index to calculate integrals for @@ -364,6 +364,7 @@ subroutine int_density_dz_generic_plm(k, tv, T_t, T_b, S_t, S_b, e, rho_ref, & intent(in) :: bathyT !< The depth of the bathymetry [Z ~> m] type(EOS_type), intent(in) :: EOS !< Equation of state structure type(unit_scale_type), intent(in) :: US !< A dimensional unit scaling type + logical, intent(in) :: use_stanley_eos !< If true, turn on Stanley SGS T variance parameterization real, dimension(SZI_(HI),SZJ_(HI)), & intent(inout) :: dpa !< The change in the pressure anomaly across the layer [R L2 T-2 ~> Pa] real, dimension(SZI_(HI),SZJ_(HI)), & @@ -435,7 +436,6 @@ subroutine int_density_dz_generic_plm(k, tv, T_t, T_b, S_t, S_b, e, rho_ref, & real :: hWght ! A topographically limited thickness weight [Z ~> m] real :: hL, hR ! Thicknesses to the left and right [Z ~> m] real :: iDenom ! The denominator of the thickness weight expressions [Z-2 ~> m-2] - logical :: use_stanley_eos ! True is SGS variance fields exist in tv. logical :: use_rho_ref ! Pass rho_ref to the equation of state for more accurate calculation ! of density anomalies. logical :: use_varT, use_varS, use_covarTS ! Logicals for SGS variances fields @@ -458,10 +458,15 @@ subroutine int_density_dz_generic_plm(k, tv, T_t, T_b, S_t, S_b, e, rho_ref, & if (use_inaccurate_form) use_rho_ref = .not. use_inaccurate_form endif - use_varT = associated(tv%varT) - use_covarTS = associated(tv%covarTS) - use_varS = associated(tv%varS) - use_stanley_eos = use_varT .or. use_covarTS .or. use_varS + use_varT = .false. !ensure initialized + use_covarTS = .false. + use_varS = .false. + if (use_stanley_eos) then + use_varT = associated(tv%varT) + use_covarTS = associated(tv%covarTS) + use_varS = associated(tv%varS) + endif + T25(:) = 0. TS5(:) = 0. S25(:) = 0. @@ -488,7 +493,7 @@ subroutine int_density_dz_generic_plm(k, tv, T_t, T_b, S_t, S_b, e, rho_ref, & if (use_covarTS) TS5(i*5+1:i*5+5) = tv%covarTS(i,j,k) if (use_varS) S25(i*5+1:i*5+5) = tv%varS(i,j,k) enddo - if (use_Stanley_eos) then + if (use_stanley_eos) then if (rho_scale /= 1.0) then call calculate_density(T5, S5, p5, T25, TS5, S25, r5, 1, (ieq-isq+2)*5, EOS, & rho_ref=rho_ref_mks, scale=rho_scale) @@ -780,7 +785,7 @@ end subroutine int_density_dz_generic_plm !> Compute pressure gradient force integrals for layer "k" and the case where T and S !! are parabolic profiles subroutine int_density_dz_generic_ppm(k, tv, T_t, T_b, S_t, S_b, e, & - rho_ref, rho_0, G_e, dz_subroundoff, bathyT, HI, GV, EOS, US, & + rho_ref, rho_0, G_e, dz_subroundoff, bathyT, HI, GV, EOS, US, use_stanley_eos, & dpa, intz_dpa, intx_dpa, inty_dpa, useMassWghtInterp, Z_0p) integer, intent(in) :: k !< Layer index to calculate integrals for type(hor_index_type), intent(in) :: HI !< Ocean horizontal index structures for the input arrays @@ -806,6 +811,7 @@ subroutine int_density_dz_generic_ppm(k, tv, T_t, T_b, S_t, S_b, e, & intent(in) :: bathyT !< The depth of the bathymetry [Z ~> m] type(EOS_type), intent(in) :: EOS !< Equation of state structure type(unit_scale_type), intent(in) :: US !< A dimensional unit scaling type + logical, intent(in) :: use_stanley_eos !< If true, turn on Stanley SGS T variance parameterization real, dimension(SZI_(HI),SZJ_(HI)), & intent(inout) :: dpa !< The change in the pressure anomaly across the layer [R L2 T-2 ~> Pa] real, dimension(SZI_(HI),SZJ_(HI)), & @@ -867,7 +873,6 @@ subroutine int_density_dz_generic_ppm(k, tv, T_t, T_b, S_t, S_b, e, & real :: iDenom ! The denominator of the thickness weight expressions [Z-2 ~> m-2] integer :: Isq, Ieq, Jsq, Jeq, i, j, m, n logical :: use_PPM ! If false, assume zero curvature in reconstruction, i.e. PLM - logical :: use_stanley_eos ! True is SGS variance fields exist in tv. logical :: use_varT, use_varS, use_covarTS Isq = HI%IscB ; Ieq = HI%IecB ; Jsq = HI%JscB ; Jeq = HI%JecB @@ -887,10 +892,15 @@ subroutine int_density_dz_generic_ppm(k, tv, T_t, T_b, S_t, S_b, e, & t6 = 0. use_PPM = .true. ! This is a place-holder to allow later re-use of this function - use_varT = associated(tv%varT) - use_covarTS = associated(tv%covarTS) - use_varS = associated(tv%varS) - use_stanley_eos = use_varT .or. use_covarTS .or. use_varS + use_varT = .false. !ensure initialized + use_covarTS = .false. + use_varS = .false. + if (use_stanley_eos) then + use_varT = associated(tv%varT) + use_covarTS = associated(tv%covarTS) + use_varS = associated(tv%varS) + endif + T25(:) = 0. TS5(:) = 0. S25(:) = 0. @@ -1002,7 +1012,6 @@ subroutine int_density_dz_generic_ppm(k, tv, T_t, T_b, S_t, S_b, e, & S5(n) = wt_t(n) * S_top + wt_b(n) * ( S_bot + s6 * wt_t(n) ) T5(n) = wt_t(n) * T_top + wt_b(n) * ( T_bot + t6 * wt_t(n) ) enddo - if (use_stanley_eos) then if (use_varT) T25(:) = w_left*tv%varT(i,j,k) + w_right*tv%varT(i+1,j,k) if (use_covarTS) TS5(:) = w_left*tv%covarTS(i,j,k) + w_right*tv%covarTS(i+1,j,k) diff --git a/src/core/MOM_dynamics_split_RK2.F90 b/src/core/MOM_dynamics_split_RK2.F90 index e78209b5ff..003033659e 100644 --- a/src/core/MOM_dynamics_split_RK2.F90 +++ b/src/core/MOM_dynamics_split_RK2.F90 @@ -67,7 +67,7 @@ module MOM_dynamics_split_RK2 use MOM_vert_friction, only : updateCFLtruncationValue use MOM_verticalGrid, only : verticalGrid_type, get_thickness_units use MOM_verticalGrid, only : get_flux_units, get_tr_flux_units -use MOM_wave_interface, only: wave_parameters_CS +use MOM_wave_interface, only: wave_parameters_CS, Stokes_PGF implicit none ; private @@ -78,11 +78,13 @@ module MOM_dynamics_split_RK2 real ALLOCABLE_, dimension(NIMEMB_PTR_,NJMEM_,NKMEM_) :: & CAu, & !< CAu = f*v - u.grad(u) [L T-2 ~> m s-2] PFu, & !< PFu = -dM/dx [L T-2 ~> m s-2] + PFu_Stokes, & !< PFu_Stokes = -d/dx int_r (u_L*duS/dr) [L T-2 ~> m s-2] diffu !< Zonal acceleration due to convergence of the along-isopycnal stress tensor [L T-2 ~> m s-2] real ALLOCABLE_, dimension(NIMEM_,NJMEMB_PTR_,NKMEM_) :: & CAv, & !< CAv = -f*u - u.grad(v) [L T-2 ~> m s-2] PFv, & !< PFv = -dM/dy [L T-2 ~> m s-2] + PFv_Stokes, & !< PFv_Stokes = -d/dy int_r (v_L*dvS/dr) [L T-2 ~> m s-2] diffv !< Meridional acceleration due to convergence of the along-isopycnal stress tensor [L T-2 ~> m s-2] real ALLOCABLE_, dimension(NIMEMB_PTR_,NJMEM_,NKMEM_) :: visc_rem_u @@ -355,6 +357,7 @@ subroutine step_MOM_dyn_split_RK2(u, v, h, tv, visc, Time_local, dt, forces, p_s logical :: BT_cont_BT_thick ! If true, use the BT_cont_type to estimate the ! relative weightings of the layers in calculating ! the barotropic accelerations. + logical :: Use_Stokes_PGF ! If true, add Stokes PGF to hydrostatic PGF !---For group halo pass logical :: showCallTree, sym @@ -452,6 +455,31 @@ subroutine step_MOM_dyn_split_RK2(u, v, h, tv, visc, Time_local, dt, forces, p_s eta_PF_start(i,j) = CS%eta_PF(i,j) - pres_to_eta * (p_surf_begin(i,j) - p_surf_end(i,j)) enddo ; enddo endif + ! Stokes shear force contribution to pressure gradient + Use_Stokes_PGF = present(Waves) + if (Use_Stokes_PGF) then + Use_Stokes_PGF = associated(Waves) + if (Use_Stokes_PGF) Use_Stokes_PGF = Waves%Stokes_PGF + if (Use_Stokes_PGF) then + call Stokes_PGF(G, GV, h, u, v, CS%PFu_Stokes, CS%PFv_Stokes, Waves) + + ! We are adding Stokes_PGF to hydrostatic PGF here. The diag PFu/PFv + ! will therefore report the sum total PGF and we avoid other + ! modifications in the code. The PFu_Stokes is output within the waves routines. + if (.not.Waves%Passive_Stokes_PGF) then + do k=1,nz + do j=js,je ; do I=Isq,Ieq + CS%PFu(I,j,k) = CS%PFu(I,j,k) + CS%PFu_Stokes(I,j,k) + enddo ; enddo + enddo + do k=1,nz + do J=Jsq,Jeq ; do i=is,ie + CS%PFv(i,J,k) = CS%PFv(i,J,k) + CS%PFv_Stokes(i,J,k) + enddo ; enddo + enddo + endif + endif + endif call cpu_clock_end(id_clock_pres) call disable_averaging(CS%diag) if (showCallTree) call callTree_wayPoint("done with PressureForce (step_MOM_dyn_split_RK2)") @@ -468,7 +496,7 @@ subroutine step_MOM_dyn_split_RK2(u, v, h, tv, visc, Time_local, dt, forces, p_s ! CAu = -(f+zeta_av)/h_av vh + d/dx KE_av call cpu_clock_begin(id_clock_Cor) call CorAdCalc(u_av, v_av, h_av, uh, vh, CS%CAu, CS%CAv, CS%OBC, CS%ADp, & - G, Gv, US, CS%CoriolisAdv, pbv) + G, Gv, US, CS%CoriolisAdv, pbv, Waves=Waves) call cpu_clock_end(id_clock_Cor) if (showCallTree) call callTree_wayPoint("done with CorAdCalc (step_MOM_dyn_split_RK2)") @@ -687,6 +715,27 @@ subroutine step_MOM_dyn_split_RK2(u, v, h, tv, visc, Time_local, dt, forces, p_s call cpu_clock_begin(id_clock_pres) call PressureForce(hp, tv, CS%PFu, CS%PFv, G, GV, US, CS%PressureForce_CSp, & CS%ALE_CSp, p_surf, CS%pbce, CS%eta_PF) + ! Stokes shear force contribution to pressure gradient + Use_Stokes_PGF = present(Waves) + if (Use_Stokes_PGF) then + Use_Stokes_PGF = associated(Waves) + if (Use_Stokes_PGF) Use_Stokes_PGF = Waves%Stokes_PGF + if (Use_Stokes_PGF) then + call Stokes_PGF(G, GV, h, u, v, CS%PFu_Stokes, CS%PFv_Stokes, Waves) + if (.not.Waves%Passive_Stokes_PGF) then + do k=1,nz + do j=js,je ; do I=Isq,Ieq + CS%PFu(I,j,k) = CS%PFu(I,j,k) + CS%PFu_Stokes(I,j,k) + enddo ; enddo + enddo + do k=1,nz + do J=Jsq,Jeq ; do i=is,ie + CS%PFv(i,J,k) = CS%PFv(i,J,k) + CS%PFv_Stokes(i,J,k) + enddo ; enddo + enddo + endif + endif + endif call cpu_clock_end(id_clock_pres) if (showCallTree) call callTree_wayPoint("done with PressureForce[hp=(1-b).h+b.h] (step_MOM_dyn_split_RK2)") endif @@ -721,7 +770,7 @@ subroutine step_MOM_dyn_split_RK2(u, v, h, tv, visc, Time_local, dt, forces, p_s ! CAu = -(f+zeta_av)/h_av vh + d/dx KE_av call cpu_clock_begin(id_clock_Cor) call CorAdCalc(u_av, v_av, h_av, uh, vh, CS%CAu, CS%CAv, CS%OBC, CS%ADp, & - G, GV, US, CS%CoriolisAdv, pbv) + G, GV, US, CS%CoriolisAdv, pbv, Waves=Waves) call cpu_clock_end(id_clock_Cor) if (showCallTree) call callTree_wayPoint("done with CorAdCalc (step_MOM_dyn_split_RK2)") @@ -1187,6 +1236,8 @@ subroutine initialize_dyn_split_RK2(u, v, h, uh, vh, eta, Time, G, GV, US, param ALLOC_(CS%u_accel_bt(IsdB:IedB,jsd:jed,nz)) ; CS%u_accel_bt(:,:,:) = 0.0 ALLOC_(CS%v_accel_bt(isd:ied,JsdB:JedB,nz)) ; CS%v_accel_bt(:,:,:) = 0.0 + ALLOC_(CS%PFu_Stokes(IsdB:IedB,jsd:jed,nz)) ; CS%PFu_Stokes(:,:,:) = 0.0 + ALLOC_(CS%PFv_Stokes(isd:ied,JsdB:JedB,nz)) ; CS%PFv_Stokes(:,:,:) = 0.0 MIS%diffu => CS%diffu MIS%diffv => CS%diffv diff --git a/src/core/MOM_forcing_type.F90 b/src/core/MOM_forcing_type.F90 index 227623f5eb..768dfd628c 100644 --- a/src/core/MOM_forcing_type.F90 +++ b/src/core/MOM_forcing_type.F90 @@ -99,27 +99,27 @@ module MOM_forcing_type ! water mass fluxes into the ocean [R Z T-1 ~> kg m-2 s-1]; these fluxes impact the ocean mass real, pointer, dimension(:,:) :: & - evap => NULL(), & !< (-1)*fresh water flux evaporated out of the ocean [R Z T-1 ~> kg m-2 s-1] - lprec => NULL(), & !< precipitating liquid water into the ocean [R Z T-1 ~> kg m-2 s-1] - fprec => NULL(), & !< precipitating frozen water into the ocean [R Z T-1 ~> kg m-2 s-1] - vprec => NULL(), & !< virtual liquid precip associated w/ SSS restoring [R Z T-1 ~> kg m-2 s-1] - lrunoff => NULL(), & !< liquid river runoff entering ocean [R Z T-1 ~> kg m-2 s-1] - frunoff => NULL(), & !< frozen river runoff (calving) entering ocean [R Z T-1 ~> kg m-2 s-1] - seaice_melt => NULL() !< snow/seaice melt (positive) or formation (negative) [R Z T-1 ~> kg m-2 s-1] + evap => NULL(), & !< (-1)*fresh water flux evaporated out of the ocean [R Z T-1 ~> kg m-2 s-1] + lprec => NULL(), & !< precipitating liquid water into the ocean [R Z T-1 ~> kg m-2 s-1] + fprec => NULL(), & !< precipitating frozen water into the ocean [R Z T-1 ~> kg m-2 s-1] + vprec => NULL(), & !< virtual liquid precip associated w/ SSS restoring [R Z T-1 ~> kg m-2 s-1] + lrunoff => NULL(), & !< liquid river runoff entering ocean [R Z T-1 ~> kg m-2 s-1] + frunoff => NULL(), & !< frozen river runoff (calving) entering ocean [R Z T-1 ~> kg m-2 s-1] + seaice_melt => NULL() !< snow/seaice melt (positive) or formation (negative) [R Z T-1 ~> kg m-2 s-1] ! Integrated water mass fluxes into the ocean, used for passive tracer sources [H ~> m or kg m-2] real, pointer, dimension(:,:) :: & - netMassIn => NULL(), & !< Sum of water mass fluxes into the ocean integrated over a - !! forcing timestep [H ~> m or kg m-2] - netMassOut => NULL() !< Net water mass flux out of the ocean integrated over a forcing timestep, - !! with negative values for water leaving the ocean [H ~> m or kg m-2] + netMassIn => NULL(), & !< Sum of water mass fluxes into the ocean integrated over a + !! forcing timestep [H ~> m or kg m-2] + netMassOut => NULL(), & !< Net water mass flux out of the ocean integrated over a forcing timestep, + !! with negative values for water leaving the ocean [H ~> m or kg m-2] + KPP_salt_flux => NULL() !< KPP effective salt flux [ppt m s-1] ! heat associated with water crossing ocean surface real, pointer, dimension(:,:) :: & heat_content_cond => NULL(), & !< heat content associated with condensating water [Q R Z T-1 ~> W m-2] + heat_content_evap => NULL(), & !< heat content associated with evaporating water [Q R Z T-1 ~> W m-2] heat_content_lprec => NULL(), & !< heat content associated with liquid >0 precip [Q R Z T-1 ~> W m-2] - heat_content_icemelt => NULL(), & !< heat content associated with snow and seaice - !! melt and formation [Q R Z T-1 ~> W m-2] heat_content_fprec => NULL(), & !< heat content associated with frozen precip [Q R Z T-1 ~> W m-2] heat_content_vprec => NULL(), & !< heat content associated with virtual >0 precip [Q R Z T-1 ~> W m-2] heat_content_lrunoff => NULL(), & !< heat content associated with liquid runoff [Q R Z T-1 ~> W m-2] @@ -193,8 +193,8 @@ module MOM_forcing_type ! CFC-related arrays needed in the MOM_CFC_cap module real, pointer, dimension(:,:) :: & - cfc11_flux => NULL(), & !< flux of cfc_11 into the ocean [CU R Z T-1 kg m-3 ~> mol m-2 s-1] - cfc12_flux => NULL(), & !< flux of cfc_12 into the ocean [CU R Z T-1 kg m-3 ~> mol m-2 s-1] + cfc11_flux => NULL(), & !< flux of cfc_11 into the ocean [CU R Z T-1 ~> mol m-2 s-1] + cfc12_flux => NULL(), & !< flux of cfc_12 into the ocean [CU R Z T-1 ~> mol m-2 s-1] ice_fraction => NULL(), & !< fraction of sea ice coverage at h-cells, from 0 to 1 [nondim]. u10_sqr => NULL() !< wind magnitude at 10 m squared [L2 T-2 ~> m2 s-2] @@ -264,9 +264,6 @@ module MOM_forcing_type logical :: accumulate_rigidity = .false. !< If true, the rigidity due to various types of !! ice needs to be accumulated, and the rigidity explicitly !! reset to zero at the driver level when appropriate. - real, pointer, dimension(:,:) :: & - ustk0 => NULL(), & !< Surface Stokes drift, zonal [m s-1] - vstk0 => NULL() !< Surface Stokes drift, meridional [m s-1] real, pointer, dimension(:) :: & stk_wavenumbers => NULL() !< The central wave number of Stokes bands [rad m-1] real, pointer, dimension(:,:,:) :: & @@ -316,10 +313,11 @@ module MOM_forcing_type integer :: id_heat_content_lrunoff= -1, id_heat_content_frunoff = -1 integer :: id_heat_content_lprec = -1, id_heat_content_fprec = -1 integer :: id_heat_content_cond = -1, id_heat_content_surfwater= -1 + integer :: id_heat_content_evap = -1 integer :: id_heat_content_vprec = -1, id_heat_content_massout = -1 integer :: id_heat_added = -1, id_heat_content_massin = -1 integer :: id_hfrainds = -1, id_hfrunoffds = -1 - integer :: id_seaice_melt_heat = -1, id_heat_content_icemelt = -1 + integer :: id_seaice_melt_heat = -1 ! global area integrated heat flux diagnostic handles integer :: id_total_net_heat_coupler = -1, id_total_net_heat_surface = -1 @@ -330,9 +328,10 @@ module MOM_forcing_type integer :: id_total_heat_content_lrunoff= -1, id_total_heat_content_frunoff = -1 integer :: id_total_heat_content_lprec = -1, id_total_heat_content_fprec = -1 integer :: id_total_heat_content_cond = -1, id_total_heat_content_surfwater= -1 + integer :: id_total_heat_content_evap = -1 integer :: id_total_heat_content_vprec = -1, id_total_heat_content_massout = -1 integer :: id_total_heat_added = -1, id_total_heat_content_massin = -1 - integer :: id_total_seaice_melt_heat = -1, id_total_heat_content_icemelt = -1 + integer :: id_total_seaice_melt_heat = -1 ! global area averaged heat flux diagnostic handles integer :: id_net_heat_coupler_ga = -1, id_net_heat_surface_ga = -1 @@ -472,6 +471,7 @@ subroutine extractFluxes1d(G, GV, US, fluxes, optics, nsw, j, dt, & real :: I_Cp_Hconvert ! Unit conversion factors divided by the heat capacity ! [degC H R-1 Z-1 Q-1 ~> degC m3 J-1 or kg degC J-1] logical :: calculate_diags ! Indicate to calculate/update diagnostic arrays + logical :: do_enthalpy ! If true (default) enthalpy terms are computed in MOM6 character(len=200) :: mesg integer :: is, ie, nz, i, k, n @@ -491,6 +491,13 @@ subroutine extractFluxes1d(G, GV, US, fluxes, optics, nsw, j, dt, & if (present(pen_sw_bnd_rate)) do_PSWBR = .true. !}BGR + ! GMM: by default heat content from mass entering and leaving the ocean (enthalpy) + ! is diagnosed in this subroutine. When heat_content_evap is associated, + ! the enthalpy terms are provided via coupler and, therefore, they do not need + ! to be computed again. + do_enthalpy = .true. + if (associated(fluxes%heat_content_evap)) do_enthalpy = .false. + Ih_limit = 0.0 ; if (FluxRescaleDepth > 0.0) Ih_limit = 1.0 / FluxRescaleDepth I_Cp = 1.0 / fluxes%C_p I_Cp_Hconvert = 1.0 / (GV%H_to_RZ * fluxes%C_p) @@ -601,7 +608,7 @@ subroutine extractFluxes1d(G, GV, US, fluxes, optics, nsw, j, dt, & ! evap > 0 means condensating water is added into ocean. ! evap < 0 means evaporation of water from the ocean, in - ! which case heat_content_evap is computed in MOM_diabatic_driver.F90 + ! which case heat_content_massout is computed in MOM_diabatic_driver.F90 if (fluxes%evap(i,j) < 0.0) netMassOut(i) = netMassOut(i) + fluxes%evap(i,j) ! if (associated(fluxes%heat_content_cond)) fluxes%heat_content_cond(i,j) = 0.0 !??? --AJA @@ -627,6 +634,7 @@ subroutine extractFluxes1d(G, GV, US, fluxes, optics, nsw, j, dt, & ! (H=m for Bouss, H=kg/m2 for non-Bouss) ! CIME provides heat flux from snow&ice melt (seaice_melt_heat), so this is added below + ! Note: this term accounts for the enthalpy associated with water flux due to sea ice melting/freezing if (associated(fluxes%seaice_melt_heat)) then net_heat(i) = scale * dt * I_Cp_Hconvert * & ( fluxes%sw(i,j) + (((fluxes%lw(i,j) + fluxes%latent(i,j)) + fluxes%sens(i,j)) + & @@ -699,6 +707,14 @@ subroutine extractFluxes1d(G, GV, US, fluxes, optics, nsw, j, dt, & ! (fluxes%heat_content_cond(i,j) + fluxes%heat_content_vprec(i,j)))))) ! endif + ! When enthalpy terms are provided via coupler, they must be included in net_heat + if (.not. do_enthalpy) then + net_heat(i) = net_heat(i) + (scale * dt * I_Cp_Hconvert * & + (fluxes%heat_content_lrunoff(i,j) + fluxes%heat_content_frunoff(i,j) + & + fluxes%heat_content_lprec(i,j) + fluxes%heat_content_fprec(i,j) + & + fluxes%heat_content_evap(i,j) + fluxes%heat_content_cond(i,j))) + endif + if (fluxes%num_msg < fluxes%max_msg) then if (Pen_SW_tot(i) > 1.000001 * I_Cp_Hconvert*scale*dt*fluxes%sw(i,j)) then fluxes%num_msg = fluxes%num_msg + 1 @@ -735,7 +751,7 @@ subroutine extractFluxes1d(G, GV, US, fluxes, optics, nsw, j, dt, & endif ! Diagnostics follow... - if (calculate_diags) then + if (calculate_diags .and. do_enthalpy) then ! Initialize heat_content_massin that is diagnosed in mixedlayer_convection or ! applyBoundaryFluxes such that the meaning is as the sum of all incoming components. @@ -793,15 +809,6 @@ subroutine extractFluxes1d(G, GV, US, fluxes, optics, nsw, j, dt, & endif endif - ! Following lprec and fprec, water flux due to sea ice melt (seaice_melt) enters at SST - GMM - if (associated(fluxes%heat_content_icemelt)) then - if (fluxes%seaice_melt(i,j) > 0.0) then - fluxes%heat_content_icemelt(i,j) = fluxes%C_p*fluxes%seaice_melt(i,j)*T(i,1) - else - fluxes%heat_content_icemelt(i,j) = 0.0 - endif - endif - ! virtual precip associated with salinity restoring ! vprec > 0 means add water to ocean, assumed to be at SST ! vprec < 0 means remove water from ocean; set heat_content_vprec in MOM_diabatic_driver.F90 @@ -841,7 +848,31 @@ subroutine extractFluxes1d(G, GV, US, fluxes, optics, nsw, j, dt, & endif endif - endif ! calculate_diags + elseif (.not. do_enthalpy) then + + ! virtual precip associated with salinity restoring. Heat content associated with + ! that is *not* provided by the coupler and must be calculated by MOM6. + ! vprec > 0 means add water to ocean, assumed to be at SST + ! vprec < 0 means remove water from ocean; set heat_content_vprec in MOM_diabatic_driver.F90 + if (associated(fluxes%heat_content_vprec)) then + if (fluxes%vprec(i,j) > 0.0) then + fluxes%heat_content_vprec(i,j) = fluxes%C_p*fluxes%vprec(i,j)*T(i,1) + else + fluxes%heat_content_vprec(i,j) = 0.0 + endif + endif + + if (associated(tv%TempxPmE)) then + tv%TempxPmE(i,j) = (I_Cp*dt*scale) * & + (fluxes%heat_content_lprec(i,j) + & + fluxes%heat_content_fprec(i,j) + & + fluxes%heat_content_lrunoff(i,j) + & + fluxes%heat_content_frunoff(i,j) + & + fluxes%heat_content_evap(i,j) + & + fluxes%heat_content_cond(i,j)) + endif + + endif ! calculate_diags and do_enthalpy enddo ! i-loop @@ -1022,6 +1053,7 @@ subroutine calculateBuoyancyFlux2d(G, GV, US, fluxes, optics, h, Temp, Salt, tv, !! [degC H T-1 ~> degC m s-1 or degC kg m-2 s-1] real, dimension(SZI_(G),SZJ_(G)), intent(inout) :: netSalt !< Net surface salt flux !! [ppt H T-1 ~> ppt m s-1 or ppt kg m-2 s-1] + ! local variables integer :: j @@ -1123,15 +1155,18 @@ subroutine MOM_forcing_chksum(mesg, fluxes, G, US, haloshift) if (associated(fluxes%heat_content_fprec)) & call hchksum(fluxes%heat_content_fprec, mesg//" fluxes%heat_content_fprec", G%HI, & haloshift=hshift, scale=US%QRZ_T_to_W_m2) - if (associated(fluxes%heat_content_icemelt)) & - call hchksum(fluxes%heat_content_icemelt, mesg//" fluxes%heat_content_icemelt", G%HI, & - haloshift=hshift, scale=US%QRZ_T_to_W_m2) if (associated(fluxes%heat_content_cond)) & call hchksum(fluxes%heat_content_cond, mesg//" fluxes%heat_content_cond", G%HI, & haloshift=hshift, scale=US%QRZ_T_to_W_m2) + if (associated(fluxes%heat_content_evap)) & + call hchksum(fluxes%heat_content_evap, mesg//" fluxes%heat_content_evap", G%HI, & + haloshift=hshift, scale=US%QRZ_T_to_W_m2) if (associated(fluxes%heat_content_massout)) & call hchksum(fluxes%heat_content_massout, mesg//" fluxes%heat_content_massout", G%HI, & haloshift=hshift, scale=US%QRZ_T_to_W_m2) + if (associated(fluxes%heat_content_massin)) & + call hchksum(fluxes%heat_content_massin, mesg//" fluxes%heat_content_massin", G%HI, & + haloshift=hshift, scale=US%QRZ_T_to_W_m2) end subroutine MOM_forcing_chksum !> Write out chksums for the driving mechanical forces. @@ -1230,10 +1265,12 @@ subroutine forcing_SinglePointPrint(fluxes, G, i, j, mesg) call locMsg(fluxes%heat_content_frunoff,'heat_content_frunoff') call locMsg(fluxes%heat_content_lprec,'heat_content_lprec') call locMsg(fluxes%heat_content_fprec,'heat_content_fprec') - call locMsg(fluxes%heat_content_icemelt,'heat_content_icemelt') call locMsg(fluxes%heat_content_vprec,'heat_content_vprec') call locMsg(fluxes%heat_content_cond,'heat_content_cond') call locMsg(fluxes%heat_content_cond,'heat_content_massout') + call locMsg(fluxes%heat_content_evap,'heat_content_evap') + call locMsg(fluxes%heat_content_massout,'heat_content_massout') + call locMsg(fluxes%heat_content_massin,'heat_content_massin') contains !> Format and write a message depending on associated state of array @@ -1327,7 +1364,7 @@ subroutine register_forcing_type_diags(Time, diag, US, use_temperature, handles, 'Fraction of cell area covered by sea ice', 'm2 m-2') handles%id_u10_sqr = register_diag_field('ocean_model', 'u10_sqr', diag%axesT1, Time, & - 'Wind magnitude at 10m, squared', 'm2 s-2', conversion=US%Z_to_m**2*US%s_to_T**2) + 'Wind magnitude at 10m, squared', 'm2 s-2', conversion=US%L_to_m**2*US%s_to_T**2) endif endif @@ -1551,10 +1588,6 @@ subroutine register_forcing_type_diags(Time, diag, US, use_temperature, handles, diag%axesT1,Time,'Heat content (relative to 0degC) of frozen prec entering ocean',& 'W m-2', conversion=US%QRZ_T_to_W_m2) - handles%id_heat_content_icemelt = register_diag_field('ocean_model', 'heat_content_icemelt',& - diag%axesT1,Time,'Heat content (relative to 0degC) of water flux due to sea ice melting/freezing',& - 'W m-2', conversion=US%QRZ_T_to_W_m2) - handles%id_heat_content_vprec = register_diag_field('ocean_model', 'heat_content_vprec', & diag%axesT1,Time,'Heat content (relative to 0degC) of virtual precip entering ocean',& 'W m-2', conversion=US%QRZ_T_to_W_m2) @@ -1563,6 +1596,10 @@ subroutine register_forcing_type_diags(Time, diag, US, use_temperature, handles, diag%axesT1,Time,'Heat content (relative to 0degC) of water condensing into ocean',& 'W m-2', conversion=US%QRZ_T_to_W_m2) + handles%id_heat_content_evap = register_diag_field('ocean_model', 'heat_content_evap', & + diag%axesT1,Time,'Heat content (relative to 0degC) of water evaporating from ocean',& + 'W m-2', conversion=US%QRZ_T_to_W_m2) + handles%id_hfrainds = register_diag_field('ocean_model', 'hfrainds', & diag%axesT1,Time,'Heat content (relative to 0degC) of liquid+frozen precip entering ocean', & 'W m-2', conversion=US%QRZ_T_to_W_m2, & @@ -1694,11 +1731,6 @@ subroutine register_forcing_type_diags(Time, diag, US, use_temperature, handles, long_name='Area integrated heat content (relative to 0C) of frozen precip',& units='W') - handles%id_total_heat_content_icemelt = register_scalar_field('ocean_model', & - 'total_heat_content_icemelt', Time, diag,long_name= & - 'Area integrated heat content (relative to 0C) of water flux due sea ice melting/freezing', & - units='W') - handles%id_total_heat_content_vprec = register_scalar_field('ocean_model', & 'total_heat_content_vprec', Time, diag, & long_name='Area integrated heat content (relative to 0C) of virtual precip',& @@ -1709,6 +1741,11 @@ subroutine register_forcing_type_diags(Time, diag, US, use_temperature, handles, long_name='Area integrated heat content (relative to 0C) of condensate',& units='W') + handles%id_total_heat_content_evap = register_scalar_field('ocean_model', & + 'total_heat_content_evap', Time, diag, & + long_name='Area integrated heat content (relative to 0C) of evaporation',& + units='W') + handles%id_total_heat_content_surfwater = register_scalar_field('ocean_model', & 'total_heat_content_surfwater', Time, diag, & long_name='Area integrated heat content (relative to 0C) of water crossing surface',& @@ -2056,6 +2093,11 @@ subroutine fluxes_accumulate(flux_tmp, fluxes, G, wt2, forces) fluxes%heat_content_cond(i,j) = wt1*fluxes%heat_content_cond(i,j) + wt2*flux_tmp%heat_content_cond(i,j) enddo ; enddo endif + if (associated(fluxes%heat_content_evap) .and. associated(flux_tmp%heat_content_evap)) then + do j=js,je ; do i=is,ie + fluxes%heat_content_evap(i,j) = wt1*fluxes%heat_content_evap(i,j) + wt2*flux_tmp%heat_content_evap(i,j) + enddo ; enddo + endif if (associated(fluxes%heat_content_lprec) .and. associated(flux_tmp%heat_content_lprec)) then do j=js,je ; do i=is,ie fluxes%heat_content_lprec(i,j) = wt1*fluxes%heat_content_lprec(i,j) + wt2*flux_tmp%heat_content_lprec(i,j) @@ -2066,11 +2108,6 @@ subroutine fluxes_accumulate(flux_tmp, fluxes, G, wt2, forces) fluxes%heat_content_fprec(i,j) = wt1*fluxes%heat_content_fprec(i,j) + wt2*flux_tmp%heat_content_fprec(i,j) enddo ; enddo endif - if (associated(fluxes%heat_content_icemelt) .and. associated(flux_tmp%heat_content_icemelt)) then - do j=js,je ; do i=is,ie - fluxes%heat_content_icemelt(i,j) = wt1*fluxes%heat_content_icemelt(i,j) + wt2*flux_tmp%heat_content_icemelt(i,j) - enddo ; enddo - endif if (associated(fluxes%heat_content_vprec) .and. associated(flux_tmp%heat_content_vprec)) then do j=js,je ; do i=is,ie fluxes%heat_content_vprec(i,j) = wt1*fluxes%heat_content_vprec(i,j) + wt2*flux_tmp%heat_content_vprec(i,j) @@ -2086,11 +2123,6 @@ subroutine fluxes_accumulate(flux_tmp, fluxes, G, wt2, forces) fluxes%heat_content_frunoff(i,j) = wt1*fluxes%heat_content_frunoff(i,j) + wt2*flux_tmp%heat_content_frunoff(i,j) enddo ; enddo endif - if (associated(fluxes%heat_content_icemelt) .and. associated(flux_tmp%heat_content_icemelt)) then - do j=js,je ; do i=is,ie - fluxes%heat_content_icemelt(i,j) = wt1*fluxes%heat_content_icemelt(i,j) + wt2*flux_tmp%heat_content_icemelt(i,j) - enddo ; enddo - endif if (associated(fluxes%ustar_shelf) .and. associated(flux_tmp%ustar_shelf)) then do i=isd,ied ; do j=jsd,jed @@ -2333,7 +2365,7 @@ end subroutine mech_forcing_diags !> Offer buoyancy forcing fields for diagnostics for those !! fields registered as part of register_forcing_type_diags. -subroutine forcing_diagnostics(fluxes_in, sfc_state, G_in, US, time_end, diag, handles) +subroutine forcing_diagnostics(fluxes_in, sfc_state, G_in, US, time_end, diag, handles, enthalpy) type(forcing), target, intent(in) :: fluxes_in !< A structure containing thermodynamic forcing fields type(surface), intent(in) :: sfc_state !< A structure containing fields that !! describe the surface state of the ocean. @@ -2342,6 +2374,11 @@ subroutine forcing_diagnostics(fluxes_in, sfc_state, G_in, US, time_end, diag, h type(time_type), intent(in) :: time_end !< The end time of the diagnostic interval. type(diag_ctrl), intent(inout) :: diag !< diagnostic regulator type(forcing_diags), intent(inout) :: handles !< diagnostic ids + logical, optional, intent(in ) :: enthalpy !< If present and true, the heat content associated + !! with mass entering/leaving the ocean is provided + !! by the coupler. Diagnostics net_heat_surface and + !! heat_content_surfwater are computed using + !! heat_content_evap instead of heat_content_massout. ! local variables type(ocean_grid_type), pointer :: G ! Grid metric on model index map @@ -2353,10 +2390,14 @@ subroutine forcing_diagnostics(fluxes_in, sfc_state, G_in, US, time_end, diag, h real :: I_dt ! inverse time step [T-1 ~> s-1] real :: ppt2mks ! conversion between ppt and mks units [nondim] integer :: turns ! Number of index quarter turns + logical :: mom_enthalpy ! If true (default) enthalpy terms are computed in MOM6 integer :: i, j, is, ie, js, je call cpu_clock_begin(handles%id_clock_forcing) + mom_enthalpy = .true. + if (present(enthalpy)) mom_enthalpy = .not. enthalpy + ! NOTE: post_data expects data to be on the rotated index map, so any ! rotations must be applied before saving the output. turns = diag%G%HI%turns @@ -2576,13 +2617,6 @@ subroutine forcing_diagnostics(fluxes_in, sfc_state, G_in, US, time_end, diag, h call post_data(handles%id_total_heat_content_fprec, total_transport, diag) endif - if ((handles%id_heat_content_icemelt > 0) .and. associated(fluxes%heat_content_icemelt)) & - call post_data(handles%id_heat_content_icemelt, fluxes%heat_content_icemelt, diag) - if ((handles%id_total_heat_content_icemelt > 0) .and. associated(fluxes%heat_content_icemelt)) then - total_transport = global_area_integral(fluxes%heat_content_icemelt, G, scale=US%QRZ_T_to_W_m2) - call post_data(handles%id_total_heat_content_icemelt, total_transport, diag) - endif - if ((handles%id_heat_content_vprec > 0) .and. associated(fluxes%heat_content_vprec)) & call post_data(handles%id_heat_content_vprec, fluxes%heat_content_vprec, diag) if ((handles%id_total_heat_content_vprec > 0) .and. associated(fluxes%heat_content_vprec)) then @@ -2597,6 +2631,13 @@ subroutine forcing_diagnostics(fluxes_in, sfc_state, G_in, US, time_end, diag, h call post_data(handles%id_total_heat_content_cond, total_transport, diag) endif + if ((handles%id_heat_content_evap > 0) .and. associated(fluxes%heat_content_evap)) & + call post_data(handles%id_heat_content_evap, fluxes%heat_content_evap, diag) + if ((handles%id_total_heat_content_evap > 0) .and. associated(fluxes%heat_content_evap)) then + total_transport = global_area_integral(fluxes%heat_content_evap, G, scale=US%QRZ_T_to_W_m2) + call post_data(handles%id_total_heat_content_evap, total_transport, diag) + endif + if ((handles%id_heat_content_massout > 0) .and. associated(fluxes%heat_content_massout)) & call post_data(handles%id_heat_content_massout, fluxes%heat_content_massout, diag) if ((handles%id_total_heat_content_massout > 0) .and. associated(fluxes%heat_content_massout)) then @@ -2645,22 +2686,25 @@ subroutine forcing_diagnostics(fluxes_in, sfc_state, G_in, US, time_end, diag, h !if (associated(sfc_state%TempXpme)) then ! res(i,j) = res(i,j) + sfc_state%TempXpme(i,j) * fluxes%C_p * I_dt !else - if (associated(fluxes%heat_content_lrunoff)) & - res(i,j) = res(i,j) + fluxes%heat_content_lrunoff(i,j) - if (associated(fluxes%heat_content_frunoff)) & - res(i,j) = res(i,j) + fluxes%heat_content_frunoff(i,j) - if (associated(fluxes%heat_content_lprec)) & - res(i,j) = res(i,j) + fluxes%heat_content_lprec(i,j) - if (associated(fluxes%heat_content_fprec)) & - res(i,j) = res(i,j) + fluxes%heat_content_fprec(i,j) - if (associated(fluxes%heat_content_icemelt)) & - res(i,j) = res(i,j) + fluxes%heat_content_icemelt(i,j) - if (associated(fluxes%heat_content_vprec)) & - res(i,j) = res(i,j) + fluxes%heat_content_vprec(i,j) - if (associated(fluxes%heat_content_cond)) & - res(i,j) = res(i,j) + fluxes%heat_content_cond(i,j) + if (associated(fluxes%heat_content_lrunoff)) & + res(i,j) = res(i,j) + fluxes%heat_content_lrunoff(i,j) + if (associated(fluxes%heat_content_frunoff)) & + res(i,j) = res(i,j) + fluxes%heat_content_frunoff(i,j) + if (associated(fluxes%heat_content_lprec)) & + res(i,j) = res(i,j) + fluxes%heat_content_lprec(i,j) + if (associated(fluxes%heat_content_fprec)) & + res(i,j) = res(i,j) + fluxes%heat_content_fprec(i,j) + if (associated(fluxes%heat_content_vprec)) & + res(i,j) = res(i,j) + fluxes%heat_content_vprec(i,j) + if (associated(fluxes%heat_content_cond)) & + res(i,j) = res(i,j) + fluxes%heat_content_cond(i,j) + if (mom_enthalpy) then if (associated(fluxes%heat_content_massout)) & res(i,j) = res(i,j) + fluxes%heat_content_massout(i,j) + else + if (associated(fluxes%heat_content_evap)) & + res(i,j) = res(i,j) + fluxes%heat_content_evap(i,j) + endif !endif if (associated(fluxes%heat_added)) res(i,j) = res(i,j) + fluxes%heat_added(i,j) enddo ; enddo @@ -2682,14 +2726,17 @@ subroutine forcing_diagnostics(fluxes_in, sfc_state, G_in, US, time_end, diag, h ! if (associated(sfc_state%TempXpme)) then ! res(i,j) = res(i,j) + sfc_state%TempXpme(i,j) * fluxes%C_p * I_dt ! else - if (associated(fluxes%heat_content_lrunoff)) res(i,j) = res(i,j) + fluxes%heat_content_lrunoff(i,j) - if (associated(fluxes%heat_content_frunoff)) res(i,j) = res(i,j) + fluxes%heat_content_frunoff(i,j) - if (associated(fluxes%heat_content_lprec)) res(i,j) = res(i,j) + fluxes%heat_content_lprec(i,j) - if (associated(fluxes%heat_content_icemelt)) res(i,j) = res(i,j) + fluxes%heat_content_icemelt(i,j) - if (associated(fluxes%heat_content_fprec)) res(i,j) = res(i,j) + fluxes%heat_content_fprec(i,j) - if (associated(fluxes%heat_content_vprec)) res(i,j) = res(i,j) + fluxes%heat_content_vprec(i,j) - if (associated(fluxes%heat_content_cond)) res(i,j) = res(i,j) + fluxes%heat_content_cond(i,j) - if (associated(fluxes%heat_content_massout)) res(i,j) = res(i,j) + fluxes%heat_content_massout(i,j) + if (associated(fluxes%heat_content_lrunoff)) res(i,j) = res(i,j) + fluxes%heat_content_lrunoff(i,j) + if (associated(fluxes%heat_content_frunoff)) res(i,j) = res(i,j) + fluxes%heat_content_frunoff(i,j) + if (associated(fluxes%heat_content_lprec)) res(i,j) = res(i,j) + fluxes%heat_content_lprec(i,j) + if (associated(fluxes%heat_content_fprec)) res(i,j) = res(i,j) + fluxes%heat_content_fprec(i,j) + if (associated(fluxes%heat_content_vprec)) res(i,j) = res(i,j) + fluxes%heat_content_vprec(i,j) + if (associated(fluxes%heat_content_cond)) res(i,j) = res(i,j) + fluxes%heat_content_cond(i,j) + if (mom_enthalpy) then + if (associated(fluxes%heat_content_massout)) res(i,j) = res(i,j) + fluxes%heat_content_massout(i,j) + else + if (associated(fluxes%heat_content_evap)) res(i,j) = res(i,j) + fluxes%heat_content_evap(i,j) + endif ! endif enddo ; enddo if (handles%id_heat_content_surfwater > 0) call post_data(handles%id_heat_content_surfwater, res, diag) @@ -2938,7 +2985,7 @@ end subroutine forcing_diagnostics !> Conditionally allocate fields within the forcing type subroutine allocate_forcing_by_group(G, fluxes, water, heat, ustar, press, & shelf, iceberg, salt, fix_accum_bug, cfc, waves, & - shelf_sfc_accumulation) + shelf_sfc_accumulation, lamult, hevap) type(ocean_grid_type), intent(in) :: G !< Ocean grid structure type(forcing), intent(inout) :: fluxes !< A structure containing thermodynamic forcing fields logical, optional, intent(in) :: water !< If present and true, allocate water fluxes @@ -2955,10 +3002,18 @@ subroutine allocate_forcing_by_group(G, fluxes, water, heat, ustar, press, & logical, optional, intent(in) :: shelf_sfc_accumulation !< If present and true, and shelf is true, !! then allocate surface flux deposition from the atmosphere !! over ice shelves and ice sheets. + logical, optional, intent(in) :: lamult !< If present and true, allocate langmuir enhancement factor + logical, optional, intent(in) :: hevap !< If present and true, allocate heat content evap. + !! This field must be allocated when enthalpy is provided + !! via coupler. ! Local variables integer :: isd, ied, jsd, jed, IsdB, IedB, JsdB, JedB - logical :: shelf_sfc_acc + logical :: shelf_sfc_acc, enthalpy_mom + + ! if true, allocate fluxes needed to calculate enthalpy terms in MOM6 + enthalpy_mom = .true. + if (present (hevap)) enthalpy_mom = .not. hevap isd = G%isd ; ied = G%ied ; jsd = G%jsd ; jed = G%jed IsdB = G%IsdB ; IedB = G%IedB ; JsdB = G%JsdB ; JedB = G%JedB @@ -2991,14 +3046,14 @@ subroutine allocate_forcing_by_group(G, fluxes, water, heat, ustar, press, & if (present(heat) .and. present(water)) then ; if (heat .and. water) then call myAlloc(fluxes%heat_content_cond,isd,ied,jsd,jed, .true.) - call myAlloc(fluxes%heat_content_icemelt,isd,ied,jsd,jed, .true.) + call myAlloc(fluxes%heat_content_evap,isd,ied,jsd,jed, .not. enthalpy_mom) call myAlloc(fluxes%heat_content_lprec,isd,ied,jsd,jed, .true.) call myAlloc(fluxes%heat_content_fprec,isd,ied,jsd,jed, .true.) call myAlloc(fluxes%heat_content_vprec,isd,ied,jsd,jed, .true.) call myAlloc(fluxes%heat_content_lrunoff,isd,ied,jsd,jed, .true.) call myAlloc(fluxes%heat_content_frunoff,isd,ied,jsd,jed, .true.) - call myAlloc(fluxes%heat_content_massout,isd,ied,jsd,jed, .true.) - call myAlloc(fluxes%heat_content_massin,isd,ied,jsd,jed, .true.) + call myAlloc(fluxes%heat_content_massout,isd,ied,jsd,jed, enthalpy_mom) + call myAlloc(fluxes%heat_content_massin,isd,ied,jsd,jed, enthalpy_mom) endif ; endif call myAlloc(fluxes%p_surf,isd,ied,jsd,jed, press) @@ -3024,7 +3079,7 @@ subroutine allocate_forcing_by_group(G, fluxes, water, heat, ustar, press, & !These fields should only on allocated when wave coupling is activated. call myAlloc(fluxes%ice_fraction,isd,ied,jsd,jed, waves) - call myAlloc(fluxes%lamult,isd,ied,jsd,jed, waves) + call myAlloc(fluxes%lamult,isd,ied,jsd,jed, lamult) if (present(fix_accum_bug)) fluxes%gustless_accum_bug = .not.fix_accum_bug end subroutine allocate_forcing_by_group @@ -3118,8 +3173,6 @@ subroutine allocate_mech_forcing_by_group(G, forces, stress, ustar, shelf, & call myAlloc(forces%mass_berg,isd,ied,jsd,jed, iceberg) !These fields should only be allocated when waves - call myAlloc(forces%ustk0,isd,ied,jsd,jed, waves) - call myAlloc(forces%vstk0,isd,ied,jsd,jed, waves) if (present(waves)) then; if (waves) then; if (.not. present(num_stk_bands)) then call MOM_error(FATAL,"Requested to & @@ -3127,20 +3180,13 @@ subroutine allocate_mech_forcing_by_group(G, forces, stress, ustar, shelf, & endif if (num_stk_bands > 0) then if (.not.associated(forces%ustkb)) then - allocate(forces%stk_wavenumbers(num_stk_bands)) - forces%stk_wavenumbers(:) = 0.0 - allocate(forces%ustkb(isd:ied,jsd:jed,num_stk_bands)) - forces%ustkb(isd:ied,jsd:jed,:) = 0.0 + allocate(forces%stk_wavenumbers(num_stk_bands), source=0.0) + allocate(forces%ustkb(isd:ied,jsd:jed,num_stk_bands), source=0.0) + allocate(forces%vstkb(isd:ied,jsd:jed,num_stk_bands), source=0.0) endif endif endif ; endif - - if (present(waves)) then; if (waves) then; if (.not.associated(forces%vstkb)) then - allocate(forces%vstkb(isd:ied,jsd:jed,num_stk_bands)) - forces%vstkb(isd:ied,jsd:jed,:) = 0.0 - endif ; endif ; endif - end subroutine allocate_mech_forcing_by_group @@ -3254,10 +3300,10 @@ subroutine deallocate_forcing_type(fluxes) if (associated(fluxes%heat_added)) deallocate(fluxes%heat_added) if (associated(fluxes%heat_content_lrunoff)) deallocate(fluxes%heat_content_lrunoff) if (associated(fluxes%heat_content_frunoff)) deallocate(fluxes%heat_content_frunoff) - if (associated(fluxes%heat_content_icemelt)) deallocate(fluxes%heat_content_icemelt) if (associated(fluxes%heat_content_lprec)) deallocate(fluxes%heat_content_lprec) if (associated(fluxes%heat_content_fprec)) deallocate(fluxes%heat_content_fprec) if (associated(fluxes%heat_content_cond)) deallocate(fluxes%heat_content_cond) + if (associated(fluxes%heat_content_evap)) deallocate(fluxes%heat_content_evap) if (associated(fluxes%heat_content_massout)) deallocate(fluxes%heat_content_massout) if (associated(fluxes%heat_content_massin)) deallocate(fluxes%heat_content_massin) if (associated(fluxes%evap)) deallocate(fluxes%evap) @@ -3358,14 +3404,17 @@ subroutine rotate_forcing(fluxes_in, fluxes, turns) if (do_heat .and. do_water) then call rotate_array(fluxes_in%heat_content_cond, turns, fluxes%heat_content_cond) - call rotate_array(fluxes_in%heat_content_icemelt, turns, fluxes%heat_content_icemelt) call rotate_array(fluxes_in%heat_content_lprec, turns, fluxes%heat_content_lprec) call rotate_array(fluxes_in%heat_content_fprec, turns, fluxes%heat_content_fprec) call rotate_array(fluxes_in%heat_content_vprec, turns, fluxes%heat_content_vprec) call rotate_array(fluxes_in%heat_content_lrunoff, turns, fluxes%heat_content_lrunoff) call rotate_array(fluxes_in%heat_content_frunoff, turns, fluxes%heat_content_frunoff) - call rotate_array(fluxes_in%heat_content_massout, turns, fluxes%heat_content_massout) - call rotate_array(fluxes_in%heat_content_massin, turns, fluxes%heat_content_massin) + if (associated (fluxes_in%heat_content_evap)) then + call rotate_array(fluxes_in%heat_content_evap, turns, fluxes%heat_content_evap) + else + call rotate_array(fluxes_in%heat_content_massout, turns, fluxes%heat_content_massout) + call rotate_array(fluxes_in%heat_content_massin, turns, fluxes%heat_content_massin) + endif endif if (do_press) then @@ -3605,7 +3654,6 @@ subroutine homogenize_forcing(fluxes, G, GV, US) if (do_heat .and. do_water) then call homogenize_field_t(fluxes%heat_content_cond, G, tmp_scale=US%QRZ_T_to_W_m2) - call homogenize_field_t(fluxes%heat_content_icemelt, G, tmp_scale=US%QRZ_T_to_W_m2) call homogenize_field_t(fluxes%heat_content_lprec, G, tmp_scale=US%QRZ_T_to_W_m2) call homogenize_field_t(fluxes%heat_content_fprec, G, tmp_scale=US%QRZ_T_to_W_m2) call homogenize_field_t(fluxes%heat_content_vprec, G, tmp_scale=US%QRZ_T_to_W_m2) diff --git a/src/core/MOM_isopycnal_slopes.F90 b/src/core/MOM_isopycnal_slopes.F90 index 38a3544703..25cad71b06 100644 --- a/src/core/MOM_isopycnal_slopes.F90 +++ b/src/core/MOM_isopycnal_slopes.F90 @@ -8,6 +8,7 @@ module MOM_isopycnal_slopes use MOM_variables, only : thermo_var_ptrs use MOM_verticalGrid, only : verticalGrid_type use MOM_EOS, only : calculate_density_derivs +use MOM_EOS, only : calculate_density_second_derivs use MOM_open_boundary, only : ocean_OBC_type, OBC_NONE use MOM_open_boundary, only : OBC_DIRECTION_E, OBC_DIRECTION_W, OBC_DIRECTION_N, OBC_DIRECTION_S @@ -26,7 +27,7 @@ module MOM_isopycnal_slopes !> Calculate isopycnal slopes, and optionally return other stratification dependent functions such as N^2 !! and dz*S^2*g-prime used, or calculable from factors used, during the calculation. -subroutine calc_isoneutral_slopes(G, GV, US, h, e, tv, dt_kappa_smooth, & +subroutine calc_isoneutral_slopes(G, GV, US, h, e, tv, dt_kappa_smooth, use_stanley, & slope_x, slope_y, N2_u, N2_v, dzu, dzv, dzSxN, dzSyN, halo, OBC) !, eta_to_m) type(ocean_grid_type), intent(in) :: G !< The ocean's grid structure type(verticalGrid_type), intent(in) :: GV !< The ocean's vertical grid structure @@ -38,6 +39,7 @@ subroutine calc_isoneutral_slopes(G, GV, US, h, e, tv, dt_kappa_smooth, & !! thermodynamic variables real, intent(in) :: dt_kappa_smooth !< A smoothing vertical diffusivity !! times a smoothing timescale [Z2 ~> m2]. + logical, intent(in) :: use_stanley !< turn on stanley param in slope real, dimension(SZIB_(G),SZJ_(G),SZK_(GV)+1), intent(inout) :: slope_x !< Isopycnal slope in i-dir [Z L-1 ~> nondim] real, dimension(SZI_(G),SZJB_(G),SZK_(GV)+1), intent(inout) :: slope_y !< Isopycnal slope in j-dir [Z L-1 ~> nondim] real, dimension(SZIB_(G),SZJ_(G),SZK_(GV)+1), & @@ -70,12 +72,15 @@ subroutine calc_isoneutral_slopes(G, GV, US, h, e, tv, dt_kappa_smooth, & ! Rho ! Density itself, when a nonlinear equation of state is not in use [R ~> kg m-3]. real, dimension(SZI_(G), SZJ_(G),SZK_(GV)+1) :: & pres ! The pressure at an interface [R L2 T-2 ~> Pa]. + real, dimension(SZI_(G)) :: scrap ! An array to pass to calculate_density_second_derivs() that will be ingored. real, dimension(SZIB_(G)) :: & drho_dT_u, & ! The derivative of density with temperature at u points [R degC-1 ~> kg m-3 degC-1]. drho_dS_u ! The derivative of density with salinity at u points [R ppt-1 ~> kg m-3 ppt-1]. real, dimension(SZI_(G)) :: & drho_dT_v, & ! The derivative of density with temperature at v points [R degC-1 ~> kg m-3 degC-1]. - drho_dS_v ! The derivative of density with salinity at v points [R ppt-1 ~> kg m-3 ppt-1]. + drho_dS_v, & ! The derivative of density with salinity at v points [R ppt-1 ~> kg m-3 ppt-1]. + drho_dT_dT_h, & ! The second derivative of density with temperature at h points [R degC-2 ~> kg m-3 degC-2] + drho_dT_dT_hr ! The second derivative of density with temperature at h (+1) points [R degC-2 ~> kg m-3 degC-2] real, dimension(SZIB_(G)) :: & T_u, & ! Temperature on the interface at the u-point [degC]. S_u, & ! Salinity on the interface at the u-point [ppt]. @@ -84,6 +89,13 @@ subroutine calc_isoneutral_slopes(G, GV, US, h, e, tv, dt_kappa_smooth, & T_v, & ! Temperature on the interface at the v-point [degC]. S_v, & ! Salinity on the interface at the v-point [ppt]. pres_v ! Pressure on the interface at the v-point [R L2 T-2 ~> Pa]. + real, dimension(SZI_(G)) :: & + T_h, & ! Temperature on the interface at the h-point [degC]. + S_h, & ! Salinity on the interface at the h-point [ppt] + pres_h, & ! Pressure on the interface at the h-point [R L2 T-2 ~> Pa]. + T_hr, & ! Temperature on the interface at the h (+1) point [degC]. + S_hr, & ! Salinity on the interface at the h (+1) point [ppt] + pres_hr ! Pressure on the interface at the h (+1) point [R L2 T-2 ~> Pa]. real :: drdiA, drdiB ! Along layer zonal- and meridional- potential density real :: drdjA, drdjB ! gradients in the layers above (A) and below (B) the ! interface times the grid spacing [R ~> kg m-3]. @@ -214,9 +226,10 @@ subroutine calc_isoneutral_slopes(G, GV, US, h, e, tv, dt_kappa_smooth, & !$OMP parallel do default(none) shared(nz,is,ie,js,je,IsdB,use_EOS,G,GV,US,pres,T,S,tv,h,e, & !$OMP h_neglect,dz_neglect,Z_to_L,L_to_Z,H_to_Z,h_neglect2, & !$OMP present_N2_u,G_Rho0,N2_u,slope_x,dzSxN,EOSdom_u,local_open_u_BC, & - !$OMP dzu,OBC) & + !$OMP dzu,OBC,use_stanley) & !$OMP private(drdiA,drdiB,drdkL,drdkR,pres_u,T_u,S_u, & !$OMP drho_dT_u,drho_dS_u,hg2A,hg2B,hg2L,hg2R,haA, & + !$OMP drho_dT_dT_h,scrap,pres_h,T_h,S_h, & !$OMP haB,haL,haR,dzaL,dzaR,wtA,wtB,wtL,wtR,drdz, & !$OMP drdx,mag_grad2,slope,l_seg) do j=js,je ; do K=nz,2,-1 @@ -236,6 +249,19 @@ subroutine calc_isoneutral_slopes(G, GV, US, h, e, tv, dt_kappa_smooth, & tv%eqn_of_state, EOSdom_u) endif + if (use_stanley) then + do i=is-1,ie+1 + pres_h(i) = pres(i,j,K) + T_h(i) = 0.5*(T(i,j,k) + T(i,j,k-1)) + S_h(i) = 0.5*(S(i,j,k) + S(i,j,k-1)) + enddo + ! The second line below would correspond to arguments + ! drho_dS_dS, drho_dS_dT, drho_dT_dT, drho_dS_dP, drho_dT_dP, & + call calculate_density_second_derivs(T_h, S_h, pres_h, & + scrap, scrap, drho_dT_dT_h, scrap, scrap, & + is-1, ie-is+3, tv%eqn_of_state) + endif + do I=is-1,ie if (use_EOS) then ! Estimate the horizontal density gradients along layers. @@ -250,7 +276,14 @@ subroutine calc_isoneutral_slopes(G, GV, US, h, e, tv, dt_kappa_smooth, & drdkR = (drho_dT_u(I) * (T(i+1,j,k)-T(i+1,j,k-1)) + & drho_dS_u(I) * (S(i+1,j,k)-S(i+1,j,k-1))) endif - + if (use_stanley) then + ! Correction to the horizontal density gradient due to nonlinearity in + ! the EOS rectifying SGS temperature anomalies + drdiA = drdiA + 0.5 * ((drho_dT_dT_h(i+1) * tv%varT(i+1,j,k-1)) - & + (drho_dT_dT_h(i) * tv%varT(i,j,k-1)) ) + drdiB = drdiB + 0.5 * ((drho_dT_dT_h(i+1) * tv%varT(i+1,j,k)) - & + (drho_dT_dT_h(i) * tv%varT(i,j,k)) ) + endif hg2A = h(i,j,k-1)*h(i+1,j,k-1) + h_neglect2 hg2B = h(i,j,k)*h(i+1,j,k) + h_neglect2 @@ -324,9 +357,11 @@ subroutine calc_isoneutral_slopes(G, GV, US, h, e, tv, dt_kappa_smooth, & !$OMP parallel do default(none) shared(nz,is,ie,js,je,IsdB,use_EOS,G,GV,US,pres,T,S,tv, & !$OMP h,h_neglect,e,dz_neglect,Z_to_L,L_to_Z,H_to_Z, & !$OMP h_neglect2,present_N2_v,G_Rho0,N2_v,slope_y,dzSyN,EOSdom_v, & - !$OMP dzv,local_open_v_BC,OBC) & + !$OMP dzv,local_open_v_BC,OBC,use_stanley) & !$OMP private(drdjA,drdjB,drdkL,drdkR,pres_v,T_v,S_v, & !$OMP drho_dT_v,drho_dS_v,hg2A,hg2B,hg2L,hg2R,haA, & + !$OMP drho_dT_dT_h,scrap,pres_h,T_h,S_h, & + !$OMP drho_dT_dT_hr,pres_hr,T_hr,S_hr, & !$OMP haB,haL,haR,dzaL,dzaR,wtA,wtB,wtL,wtR,drdz, & !$OMP drdy,mag_grad2,slope,l_seg) do j=js-1,je ; do K=nz,2,-1 @@ -344,6 +379,25 @@ subroutine calc_isoneutral_slopes(G, GV, US, h, e, tv, dt_kappa_smooth, & call calculate_density_derivs(T_v, S_v, pres_v, drho_dT_v, drho_dS_v, tv%eqn_of_state, & EOSdom_v) endif + if (use_stanley) then + do i=is,ie + pres_h(i) = pres(i,j,K) + T_h(i) = 0.5*(T(i,j,k) + T(i,j,k-1)) + S_h(i) = 0.5*(S(i,j,k) + S(i,j,k-1)) + + pres_hr(i) = pres(i,j+1,K) + T_hr(i) = 0.5*(T(i,j+1,k) + T(i,j+1,k-1)) + S_hr(i) = 0.5*(S(i,j+1,k) + S(i,j+1,k-1)) + enddo + ! The second line below would correspond to arguments + ! drho_dS_dS, drho_dS_dT, drho_dT_dT, drho_dS_dP, drho_dT_dP, & + call calculate_density_second_derivs(T_h, S_h, pres_h, & + scrap, scrap, drho_dT_dT_h, scrap, scrap, & + is, ie-is+1, tv%eqn_of_state) + call calculate_density_second_derivs(T_hr, S_hr, pres_hr, & + scrap, scrap, drho_dT_dT_hr, scrap, scrap, & + is, ie-is+1, tv%eqn_of_state) + endif do i=is,ie if (use_EOS) then ! Estimate the horizontal density gradients along layers. @@ -358,6 +412,14 @@ subroutine calc_isoneutral_slopes(G, GV, US, h, e, tv, dt_kappa_smooth, & drdkR = (drho_dT_v(i) * (T(i,j+1,k)-T(i,j+1,k-1)) + & drho_dS_v(i) * (S(i,j+1,k)-S(i,j+1,k-1))) endif + if (use_stanley) then + ! Correction to the horizontal density gradient due to nonlinearity in + ! the EOS rectifying SGS temperature anomalies + drdjA = drdjA + 0.5 * ((drho_dT_dT_hr(i) * tv%varT(i,j+1,k-1)) - & + (drho_dT_dT_h(i) * tv%varT(i,j,k-1)) ) + drdjB = drdjB + 0.5 * ((drho_dT_dT_hr(i) * tv%varT(i,j+1,k)) - & + (drho_dT_dT_h(i) * tv%varT(i,j,k)) ) + endif hg2A = h(i,j,k-1)*h(i,j+1,k-1) + h_neglect2 hg2B = h(i,j,k)*h(i,j+1,k) + h_neglect2 diff --git a/src/core/MOM_stoch_eos.F90 b/src/core/MOM_stoch_eos.F90 new file mode 100644 index 0000000000..bc5e15af4e --- /dev/null +++ b/src/core/MOM_stoch_eos.F90 @@ -0,0 +1,220 @@ +!> Provides the ocean stochastic equation of state +module MOM_stoch_eos + +! This file is part of MOM6. See LICENSE.md for the license. +use MOM_grid, only : ocean_grid_type +use MOM_hor_index, only : hor_index_type +use MOM_file_parser, only : get_param, param_file_type +use MOM_random, only : PRNG,random_2d_constructor,random_2d_norm +use MOM_time_manager, only : time_type +use MOM_io, only : vardesc, var_desc +use MOM_restart, only : MOM_restart_CS,is_new_run +use MOM_diag_mediator, only : register_diag_field,post_data,diag_ctrl,safe_alloc_ptr +use MOM_variables, only : thermo_var_ptrs +use MOM_verticalGrid, only : verticalGrid_type +use MOM_restart, only : register_restart_field +use MOM_isopycnal_slopes,only : vert_fill_TS +!use random_numbers_mod, only : getRandomNumbers,initializeRandomNumberStream,randomNumberStream + +implicit none; private +#include + +public MOM_stoch_eos_init +public MOM_stoch_eos_run +public MOM_calc_varT + +!> Describes parameters of the stochastic component of the EOS +!! correction, described in Stanley et al. JAMES 2020. +type, public :: MOM_stoch_eos_CS + real ALLOCABLE_, dimension(NIMEM_,NJMEM_) :: l2_inv + !< One over sum of the T cell side side lengths squared + real ALLOCABLE_, dimension(NIMEM_,NJMEM_) :: rgauss + !< nondimensional random Gaussian + real :: tfac=0.27 !< Nondimensional decorrelation time factor, ~1/3.7 + real :: amplitude=0.624499 !< Nondimensional std dev of Gaussian + integer :: seed !< PRNG seed + type(PRNG) :: rn_CS !< PRNG control structure + real ALLOCABLE_, dimension(NIMEM_,NJMEM_) :: pattern + !< Random pattern for stochastic EOS [nondim] + real ALLOCABLE_, dimension(NIMEM_,NJMEM_) :: phi + !< temporal correlation stochastic EOS [nondim] + logical :: use_stoch_eos!< If true, use the stochastic equation of state (Stanley et al. 2020) + real :: stanley_coeff !< Coefficient correlating the temperature gradient + !! and SGS T variance; if <0, turn off scheme in all codes + real :: stanley_a !< a in exp(aX) in stochastic coefficient + real :: kappa_smooth !< A diffusivity for smoothing T/S in vanished layers [Z2 T-1 ~> m2 s-1] + + !>@{ Diagnostic IDs + integer :: id_stoch_eos = -1, id_stoch_phi = -1, id_tvar_sgs = -1 + !>@} + +end type MOM_stoch_eos_CS + +contains + +!> Initializes MOM_stoch_eos module. +subroutine MOM_stoch_eos_init(G,Time,param_file,CS,restart_CS,diag) + type(param_file_type), intent(in) :: param_file !< structure indicating parameter file to parse + type(ocean_grid_type), intent(in) :: G !< The ocean's grid structure. + type(time_type), intent(in) :: Time !< Time for stochastic process + type(MOM_stoch_eos_CS), intent(inout) :: CS !< Stochastic control structure + type(MOM_restart_CS), pointer :: restart_CS !< A pointer to the restart control structure. + type(diag_ctrl), target, intent(inout) :: diag !< to control diagnostics + + ! local variables + integer :: i,j + type(vardesc) :: vd + CS%seed=0 + ! contants + !pi=2*acos(0.0) + call get_param(param_file, "MOM_stoch_eos", "STOCH_EOS", CS%use_stoch_eos, & + "If true, stochastic perturbations are applied "//& + "to the EOS in the PGF.", default=.false.) + call get_param(param_file, "MOM_stoch_eos", "STANLEY_COEFF", CS%stanley_coeff, & + "Coefficient correlating the temperature gradient "//& + "and SGS T variance.", default=-1.0) + call get_param(param_file, "MOM_stoch_eos", "STANLEY_A", CS%stanley_a, & + "Coefficient a which scales chi in stochastic perturbation of the "//& + "SGS T variance.", default=1.0) + call get_param(param_file, "MOM_stoch_eos", "KD_SMOOTH", CS%kappa_smooth, & + "A diapycnal diffusivity that is used to interpolate "//& + "more sensible values of T & S into thin layers.", & + units="m2 s-1", default=1.0e-6) + + !don't run anything if STANLEY_COEFF < 0 + if (CS%stanley_coeff >= 0.0) then + + ALLOC_(CS%pattern(G%isd:G%ied,G%jsd:G%jed)) ; CS%pattern(:,:) = 0.0 + vd = var_desc("stoch_eos_pattern","nondim","Random pattern for stoch EOS",'h','1') + call register_restart_field(CS%pattern, vd, .false., restart_CS) + ALLOC_(CS%phi(G%isd:G%ied,G%jsd:G%jed)) ; CS%phi(:,:) = 0.0 + ALLOC_(CS%l2_inv(G%isd:G%ied,G%jsd:G%jed)) + ALLOC_(CS%rgauss(G%isd:G%ied,G%jsd:G%jed)) + call get_param(param_file, "MOM_stoch_eos", "SEED_STOCH_EOS", CS%seed, & + "Specfied seed for random number sequence ", default=0) + call random_2d_constructor(CS%rn_CS, G%HI, Time, CS%seed) + call random_2d_norm(CS%rn_CS, G%HI, CS%rgauss) + ! fill array with approximation of grid area needed for decorrelation + ! time-scale calculation + do j=G%jsc,G%jec + do i=G%isc,G%iec + CS%l2_inv(i,j)=1.0/(G%dxT(i,j)**2+G%dyT(i,j)**2) + enddo + enddo + if (is_new_run(restart_CS)) then + do j=G%jsc,G%jec + do i=G%isc,G%iec + CS%pattern(i,j)=CS%amplitude*CS%rgauss(i,j) + enddo + enddo + endif + + !register diagnostics + CS%id_tvar_sgs = register_diag_field('ocean_model', 'tvar_sgs', diag%axesTL, Time, & + 'Parameterized SGS Temperature Variance ', 'None') + if (CS%use_stoch_eos) then + CS%id_stoch_eos = register_diag_field('ocean_model', 'stoch_eos', diag%axesT1, Time, & + 'random pattern for EOS', 'None') + CS%id_stoch_phi = register_diag_field('ocean_model', 'stoch_phi', diag%axesT1, Time, & + 'phi for EOS', 'None') + endif + endif + +end subroutine MOM_stoch_eos_init + +!> Generates a pattern in space and time for the ocean stochastic equation of state +subroutine MOM_stoch_eos_run(G,u,v,delt,Time,CS,diag) + type(ocean_grid_type), intent(in) :: G !< The ocean's grid structure. + real, dimension(SZIB_(G),SZJ_(G),SZK_(G)), & + intent(in) :: u !< The zonal velocity [L T-1 ~> m s-1]. + real, dimension(SZI_(G),SZJB_(G),SZK_(G)), & + intent(in) :: v !< The meridional velocity [L T-1 ~> m s-1]. + real, intent(in) :: delt !< Time step size for AR1 process [T ~> s]. + type(time_type), intent(in) :: Time !< Time for stochastic process + type(MOM_stoch_eos_CS), intent(inout) :: CS !< Stochastic control structure + type(diag_ctrl), target, intent(inout) :: diag !< to control diagnostics + + ! local variables + integer :: i,j + integer :: yr,mo,dy,hr,mn,sc + real :: phi,ubar,vbar + + call random_2d_constructor(CS%rn_CS, G%HI, Time, CS%seed) + call random_2d_norm(CS%rn_CS, G%HI, CS%rgauss) + + ! advance AR(1) + do j=G%jsc,G%jec + do i=G%isc,G%iec + ubar=0.5*(u(I,j,1)*G%mask2dCu(I,j)+u(I-1,j,1)*G%mask2dCu(I-1,j)) + vbar=0.5*(v(i,J,1)*G%mask2dCv(i,J)+v(i,J-1,1)*G%mask2dCv(i,J-1)) + phi=exp(-delt*CS%tfac*sqrt((ubar**2+vbar**2)*CS%l2_inv(i,j))) + CS%pattern(i,j)=phi*CS%pattern(i,j) + CS%amplitude*sqrt(1-phi**2)*CS%rgauss(i,j) + CS%phi(i,j)=phi + enddo + enddo + +end subroutine MOM_stoch_eos_run + +!> Computes a parameterization of the SGS temperature variance +subroutine MOM_calc_varT(G,GV,h,tv,CS,dt) + type(ocean_grid_type), intent(in) :: G !< The ocean's grid structure. + type(verticalGrid_type), intent(in) :: GV !< Vertical grid structure + real, dimension(SZI_(G),SZJ_(G),SZK_(G)), & + intent(in) :: h !< Layer thickness [H ~> m] + type(thermo_var_ptrs), intent(inout) :: tv !< Thermodynamics structure + type(MOM_stoch_eos_CS), intent(inout) :: CS !< Stochastic control structure + real, intent(in) :: dt !< Time increment [T ~> s] + + ! local variables + real, dimension(SZI_(G), SZJ_(G), SZK_(GV)) :: & + T, & !> The temperature (or density) [degC], with the values in + !! in massless layers filled vertically by diffusion. + S !> The filled salinity [ppt], with the values in + !! in massless layers filled vertically by diffusion. + integer :: i,j,k + real :: hl(5) !> Copy of local stencil of H [H ~> m] + real :: dTdi2, dTdj2 !> Differences in T variance [degC2] + + ! This block does a thickness weighted variance calculation and helps control for + ! extreme gradients along layers which are vanished against topography. It is + ! still a poor approximation in the interior when coordinates are strongly tilted. + if (.not. associated(tv%varT)) call safe_alloc_ptr(tv%varT, G%isd, G%ied, G%jsd, G%jed, GV%ke) + + call vert_fill_TS(h, tv%T, tv%S, CS%kappa_smooth*dt, T, S, G, GV, halo_here=1, larger_h_denom=.true.) + + do k=1,G%ke + do j=G%jsc,G%jec + do i=G%isc,G%iec + hl(1) = h(i,j,k) * G%mask2dT(i,j) + hl(2) = h(i-1,j,k) * G%mask2dCu(I-1,j) + hl(3) = h(i+1,j,k) * G%mask2dCu(I,j) + hl(4) = h(i,j-1,k) * G%mask2dCv(i,J-1) + hl(5) = h(i,j+1,k) * G%mask2dCv(i,J) + + ! SGS variance in i-direction [degC2] + dTdi2 = ( ( G%mask2dCu(I ,j) * G%IdxCu(I ,j) * ( T(i+1,j,k) - T(i,j,k) ) & + + G%mask2dCu(I-1,j) * G%IdxCu(I-1,j) * ( T(i,j,k) - T(i-1,j,k) ) & + ) * G%dxT(i,j) * 0.5 )**2 + ! SGS variance in j-direction [degC2] + dTdj2 = ( ( G%mask2dCv(i,J ) * G%IdyCv(i,J ) * ( T(i,j+1,k) - T(i,j,k) ) & + + G%mask2dCv(i,J-1) * G%IdyCv(i,J-1) * ( T(i,j,k) - T(i,j-1,k) ) & + ) * G%dyT(i,j) * 0.5 )**2 + tv%varT(i,j,k) = CS%stanley_coeff * ( dTdi2 + dTdj2 ) + ! Turn off scheme near land + tv%varT(i,j,k) = tv%varT(i,j,k) * (minval(hl) / (maxval(hl) + GV%H_subroundoff)) + enddo + enddo + enddo + ! if stochastic, perturb + if (CS%use_stoch_eos) then + do k=1,G%ke + do j=G%jsc,G%jec + do i=G%isc,G%iec + tv%varT(i,j,k) = exp (CS%stanley_a * CS%pattern(i,j)) * tv%varT(i,j,k) + enddo + enddo + enddo + endif +end subroutine MOM_calc_varT + +end module MOM_stoch_eos diff --git a/src/core/MOM_variables.F90 b/src/core/MOM_variables.F90 index 7cdf3e8e71..c864d90ddb 100644 --- a/src/core/MOM_variables.F90 +++ b/src/core/MOM_variables.F90 @@ -13,6 +13,7 @@ module MOM_variables use MOM_grid, only : ocean_grid_type use MOM_unit_scaling, only : unit_scale_type use MOM_verticalGrid, only : verticalGrid_type +use MOM_tracer_types, only : tracer_type implicit none ; private @@ -124,6 +125,8 @@ module MOM_variables real, pointer :: varS(:,:,:) => NULL() !< SGS variance of salinity [ppt2]. real, pointer :: covarTS(:,:,:) => NULL() !< SGS covariance of salinity and potential !! temperature [degC ppt]. + type(tracer_type), pointer :: tr_T => NULL() !< pointer to temp in tracer registry + type(tracer_type), pointer :: tr_S => NULL() !< pointer to salinty in tracer registry end type thermo_var_ptrs !> Pointers to all of the prognostic variables allocated in MOM_variables.F90 and MOM.F90. diff --git a/src/diagnostics/MOM_diagnostics.F90 b/src/diagnostics/MOM_diagnostics.F90 index 9c22ab5eb5..e2edb588ef 100644 --- a/src/diagnostics/MOM_diagnostics.F90 +++ b/src/diagnostics/MOM_diagnostics.F90 @@ -106,6 +106,8 @@ module MOM_diagnostics integer :: id_rhopot0 = -1, id_rhopot2 = -1 integer :: id_drho_dT = -1, id_drho_dS = -1 integer :: id_h_pre_sync = -1 + integer :: id_tosq = -1, id_sosq = -1 + !>@} type(wave_speed_CS) :: wave_speed !< Wave speed control struct @@ -399,16 +401,28 @@ subroutine calculate_diagnostic_fields(u, v, h, uh, vh, tv, ADp, CDp, p_surf, & ! Internal T&S variables are conservative temperature & absolute salinity, ! so they need to converted to potential temperature and practical salinity ! for some diagnostics using TEOS-10 function calls. - if ((CS%id_Tpot > 0) .or. (CS%id_tob > 0)) then + if ((CS%id_Tpot > 0) .or. (CS%id_tob > 0) .or. (CS%id_tosq > 0)) then do k=1,nz ; do j=js,je ; do i=is,ie work_3d(i,j,k) = gsw_pt_from_ct(tv%S(i,j,k),tv%T(i,j,k)) enddo ; enddo ; enddo if (CS%id_Tpot > 0) call post_data(CS%id_Tpot, work_3d, CS%diag) if (CS%id_tob > 0) call post_data(CS%id_tob, work_3d(:,:,nz), CS%diag, mask=G%mask2dT) + if (CS%id_tosq > 0) then + do k=1,nz ; do j=js,je ; do i=is,ie + work_3d(i,j,k) = work_3d(i,j,k)*work_3d(i,j,k) + enddo ; enddo ; enddo + call post_data(CS%id_tosq, work_3d, CS%diag) + endif endif else ! Internal T&S variables are potential temperature & practical salinity if (CS%id_tob > 0) call post_data(CS%id_tob, tv%T(:,:,nz), CS%diag, mask=G%mask2dT) + if (CS%id_tosq > 0) then + do k=1,nz ; do j=js,je ; do i=is,ie + work_3d(i,j,k) = tv%T(i,j,k)*tv%T(i,j,k) + enddo ; enddo ; enddo + call post_data(CS%id_tosq, work_3d, CS%diag) + endif endif ! Calculate additional, potentially derived salinity diagnostics @@ -416,16 +430,28 @@ subroutine calculate_diagnostic_fields(u, v, h, uh, vh, tv, ADp, CDp, p_surf, & ! Internal T&S variables are conservative temperature & absolute salinity, ! so they need to converted to potential temperature and practical salinity ! for some diagnostics using TEOS-10 function calls. - if ((CS%id_Sprac > 0) .or. (CS%id_sob > 0)) then + if ((CS%id_Sprac > 0) .or. (CS%id_sob > 0) .or. (CS%id_sosq >0)) then do k=1,nz ; do j=js,je ; do i=is,ie work_3d(i,j,k) = gsw_sp_from_sr(tv%S(i,j,k)) enddo ; enddo ; enddo if (CS%id_Sprac > 0) call post_data(CS%id_Sprac, work_3d, CS%diag) if (CS%id_sob > 0) call post_data(CS%id_sob, work_3d(:,:,nz), CS%diag, mask=G%mask2dT) + if (CS%id_sosq > 0) then + do k=1,nz ; do j=js,je ; do i=is,ie + work_3d(i,j,k) = work_3d(i,j,k)*work_3d(i,j,k) + enddo ; enddo ; enddo + call post_data(CS%id_sosq, work_3d, CS%diag) + endif endif else ! Internal T&S variables are potential temperature & practical salinity if (CS%id_sob > 0) call post_data(CS%id_sob, tv%S(:,:,nz), CS%diag, mask=G%mask2dT) + if (CS%id_sosq > 0) then + do k=1,nz ; do j=js,je ; do i=is,ie + work_3d(i,j,k) = tv%S(i,j,k)*tv%S(i,j,k) + enddo ; enddo ; enddo + call post_data(CS%id_sosq, work_3d, CS%diag) + endif endif ! volume mean potential temperature @@ -1611,6 +1637,13 @@ subroutine MOM_diagnostics_init(MIS, ADp, CDp, Time, G, GV, US, param_file, diag long_name='Sea Water Salinity at Sea Floor', & standard_name='sea_water_salinity_at_sea_floor', units='psu') + CS%id_tosq = register_diag_field('ocean_model', 'tosq', diag%axesTL,& + Time, 'Square of Potential Temperature', 'degc2', & + standard_name='Potential Temperature Squared') + CS%id_sosq = register_diag_field('ocean_model', 'sosq', diag%axesTL,& + Time, 'Square of Salinity', 'psu2', & + standard_name='Salinity Squared') + CS%id_temp_layer_ave = register_diag_field('ocean_model', 'temp_layer_ave', & diag%axesZL, Time, 'Layer Average Ocean Temperature', 'degC') CS%id_salt_layer_ave = register_diag_field('ocean_model', 'salt_layer_ave', & diff --git a/src/diagnostics/MOM_sum_output.F90 b/src/diagnostics/MOM_sum_output.F90 index 33f3edcfd4..f0856893ca 100644 --- a/src/diagnostics/MOM_sum_output.F90 +++ b/src/diagnostics/MOM_sum_output.F90 @@ -993,7 +993,14 @@ subroutine accumulate_net_input(fluxes, sfc_state, tv, dt, G, US, CS) ! enddo ; enddo ; endif ! smg: old code - if (associated(tv%TempxPmE)) then + if (associated(fluxes%heat_content_evap)) then + do j=js,je ; do i=is,ie + heat_in(i,j) = heat_in(i,j) + dt * QRZL2_to_J * G%areaT(i,j) * & + (fluxes%heat_content_evap(i,j) + fluxes%heat_content_lprec(i,j) + & + fluxes%heat_content_cond(i,j) + fluxes%heat_content_fprec(i,j) + & + fluxes%heat_content_lrunoff(i,j) + fluxes%heat_content_frunoff(i,j)) + enddo ; enddo + elseif (associated(tv%TempxPmE)) then do j=js,je ; do i=is,ie heat_in(i,j) = heat_in(i,j) + (fluxes%C_p * QRZL2_to_J*G%areaT(i,j)) * tv%TempxPmE(i,j) enddo ; enddo diff --git a/src/equation_of_state/MOM_EOS.F90 b/src/equation_of_state/MOM_EOS.F90 index 80a8d3f866..8d02845aa0 100644 --- a/src/equation_of_state/MOM_EOS.F90 +++ b/src/equation_of_state/MOM_EOS.F90 @@ -64,9 +64,9 @@ module MOM_EOS !> Calculates density of sea water from T, S and P interface calculate_density - module procedure calculate_density_scalar, calculate_density_array, calculate_density_1d - module procedure calculate_stanley_density_scalar, calculate_stanley_density_array - module procedure calculate_stanley_density_1d + module procedure calculate_density_scalar, calculate_density_array, calculate_density_1d, & + calculate_stanley_density_scalar, calculate_stanley_density_array, & + calculate_stanley_density_1d end interface calculate_density !> Calculates specific volume of sea water from T, S and P @@ -430,18 +430,18 @@ subroutine calculate_stanley_density_1d(T, S, pressure, Tvar, TScov, Svar, rho, select case (EOS%form_of_EOS) case (EOS_LINEAR) - call calculate_density_linear(T, S, pres, rho, 1, npts, & + call calculate_density_linear(T, S, pres, rho, is, npts, & EOS%Rho_T0_S0, EOS%dRho_dT, EOS%dRho_dS, rho_ref) call calculate_density_second_derivs_linear(T, S, pres, d2RdSS, d2RdST, & - d2RdTT, d2RdSp, d2RdTP, 1, npts) + d2RdTT, d2RdSp, d2RdTP, is, npts) case (EOS_WRIGHT) - call calculate_density_wright(T, S, pres, rho, 1, npts, rho_ref) + call calculate_density_wright(T, S, pres, rho, is, npts, rho_ref) call calculate_density_second_derivs_wright(T, S, pres, d2RdSS, d2RdST, & - d2RdTT, d2RdSp, d2RdTP, 1, npts) + d2RdTT, d2RdSp, d2RdTP, is, npts) case (EOS_TEOS10) - call calculate_density_teos10(T, S, pres, rho, 1, npts, rho_ref) + call calculate_density_teos10(T, S, pres, rho, is, npts, rho_ref) call calculate_density_second_derivs_teos10(T, S, pres, d2RdSS, d2RdST, & - d2RdTT, d2RdSp, d2RdTP, 1, npts) + d2RdTT, d2RdSp, d2RdTP, is, npts) case default call MOM_error(FATAL, "calculate_stanley_density_scalar: EOS is not valid.") end select diff --git a/src/equation_of_state/TEOS10/gsw_mod_error_functions.f90 b/src/equation_of_state/TEOS10/gsw_mod_error_functions.f90 new file mode 120000 index 0000000000..1c3b7bfb3c --- /dev/null +++ b/src/equation_of_state/TEOS10/gsw_mod_error_functions.f90 @@ -0,0 +1 @@ +../../../pkg/GSW-Fortran/modules/gsw_mod_error_functions.f90 \ No newline at end of file diff --git a/src/framework/MOM_diag_remap.F90 b/src/framework/MOM_diag_remap.F90 index 63e6bcba7a..b665dcd748 100644 --- a/src/framework/MOM_diag_remap.F90 +++ b/src/framework/MOM_diag_remap.F90 @@ -217,7 +217,7 @@ subroutine diag_remap_configure_axes(remap_cs, GV, US, param_file) allocate(interfaces(remap_cs%nz+1)) allocate(layers(remap_cs%nz)) - interfaces(:) = getCoordinateInterfaces(remap_cs%regrid_cs) + interfaces(:) = getCoordinateInterfaces(remap_cs%regrid_cs, undo_scaling=.true.) layers(:) = 0.5 * ( interfaces(1:remap_cs%nz) + interfaces(2:remap_cs%nz+1) ) remap_cs%interface_axes_id = MOM_diag_axis_init(lowercase(trim(remap_cs%diag_coord_name))//'_i', & diff --git a/src/framework/MOM_random.F90 b/src/framework/MOM_random.F90 index fbdc916346..f5e996d3e4 100644 --- a/src/framework/MOM_random.F90 +++ b/src/framework/MOM_random.F90 @@ -159,6 +159,7 @@ subroutine random_2d_constructor(CS, HI, Time, seed) if (.not. allocated(CS%stream2d)) allocate( CS%stream2d(HI%isd:HI%ied,HI%jsd:HI%jed) ) tseed = seed_from_time(Time) + tseed = ieor(tseed*9007, seed) do j = HI%jsd,HI%jed do i = HI%isd,HI%ied diff --git a/src/parameterizations/lateral/MOM_MEKE.F90 b/src/parameterizations/lateral/MOM_MEKE.F90 index bde8632170..1cd20d3c96 100644 --- a/src/parameterizations/lateral/MOM_MEKE.F90 +++ b/src/parameterizations/lateral/MOM_MEKE.F90 @@ -721,7 +721,7 @@ subroutine MEKE_equilibrium(CS, MEKE, G, GV, US, SN_u, SN_v, drag_rate_visc, I_m SN = min(SN_u(I,j), SN_u(I-1,j), SN_v(i,J), SN_v(i,J-1)) if (CS%MEKE_equilibrium_alt) then - MEKE%MEKE(i,j) = (CS%MEKE_GEOMETRIC_alpha * SN * US%Z_to_m*depth_tot(i,j))**2 / cd2 + MEKE%MEKE(i,j) = (CS%MEKE_GEOMETRIC_alpha * SN * US%Z_to_L*depth_tot(i,j))**2 / cd2 else FatH = 0.25*((G%CoriolisBu(I,J) + G%CoriolisBu(I-1,J-1)) + & (G%CoriolisBu(I-1,J) + G%CoriolisBu(I,J-1))) ! Coriolis parameter at h points diff --git a/src/parameterizations/lateral/MOM_lateral_mixing_coeffs.F90 b/src/parameterizations/lateral/MOM_lateral_mixing_coeffs.F90 index 88e1c772a2..0871737d20 100644 --- a/src/parameterizations/lateral/MOM_lateral_mixing_coeffs.F90 +++ b/src/parameterizations/lateral/MOM_lateral_mixing_coeffs.F90 @@ -59,6 +59,7 @@ module MOM_lateral_mixing_coeffs !! This parameter is set depending on other parameters. logical :: calculate_Eady_growth_rate !< If true, calculate all the Eady growth rate. !! This parameter is set depending on other parameters. + logical :: use_stanley_iso !< If true, use Stanley parameterization in MOM_isopycnal_slopes logical :: use_simpler_Eady_growth_rate !< If true, use a simpler method to calculate the !! Eady growth rate that avoids division by layer thickness. !! This parameter is set depending on other parameters. @@ -471,14 +472,14 @@ subroutine calc_slope_functions(h, tv, dt, G, GV, US, CS, OBC) if (CS%calculate_Eady_growth_rate) then if (CS%use_simpler_Eady_growth_rate) then call find_eta(h, tv, G, GV, US, e, halo_size=2) - call calc_isoneutral_slopes(G, GV, US, h, e, tv, dt*CS%kappa_smooth, & + call calc_isoneutral_slopes(G, GV, US, h, e, tv, dt*CS%kappa_smooth, CS%use_stanley_iso, & CS%slope_x, CS%slope_y, N2_u=N2_u, N2_v=N2_v, dzu=dzu, dzv=dzv, & dzSxN=dzSxN, dzSyN=dzSyN, halo=1, OBC=OBC) call calc_Eady_growth_rate_2D(CS, G, GV, US, OBC, h, e, dzu, dzv, dzSxN, dzSyN, CS%SN_u, CS%SN_v) else call find_eta(h, tv, G, GV, US, e, halo_size=2) if (CS%use_stored_slopes) then - call calc_isoneutral_slopes(G, GV, US, h, e, tv, dt*CS%kappa_smooth, & + call calc_isoneutral_slopes(G, GV, US, h, e, tv, dt*CS%kappa_smooth, CS%use_stanley_iso, & CS%slope_x, CS%slope_y, N2_u=N2_u, N2_v=N2_v, halo=1, OBC=OBC) call calc_Visbeck_coeffs_old(h, CS%slope_x, CS%slope_y, N2_u, N2_v, G, GV, US, CS, OBC) else @@ -1267,6 +1268,9 @@ subroutine VarMix_init(Time, G, GV, US, param_file, diag, CS) call get_param(param_file, mdl, "DEBUG", CS%debug, default=.false., do_not_log=.true.) + call get_param(param_file, mdl, "USE_STANLEY_ISO", CS%use_stanley_iso, & + "If true, turn on Stanley SGS T variance parameterization "// & + "in isopycnal slope code.", default=.false.) if (CS%Resoln_use_ebt .or. CS%khth_use_ebt_struct) then in_use = .true. @@ -1290,6 +1294,7 @@ subroutine VarMix_init(Time, G, GV, US, param_file, diag, CS) endif if (CS%use_stored_slopes) then + ! CS%calculate_Eady_growth_rate=.true. in_use = .true. allocate(CS%slope_x(IsdB:IedB,jsd:jed,GV%ke+1), source=0.0) allocate(CS%slope_y(isd:ied,JsdB:JedB,GV%ke+1), source=0.0) diff --git a/src/parameterizations/lateral/MOM_mixed_layer_restrat.F90 b/src/parameterizations/lateral/MOM_mixed_layer_restrat.F90 index e1dff4f5bd..864669a217 100644 --- a/src/parameterizations/lateral/MOM_mixed_layer_restrat.F90 +++ b/src/parameterizations/lateral/MOM_mixed_layer_restrat.F90 @@ -59,6 +59,7 @@ module MOM_mixed_layer_restrat logical :: debug = .false. !< If true, calculate checksums of fields for debugging. type(diag_ctrl), pointer :: diag !< A structure that is used to regulate the !! timing of diagnostic output. + logical :: use_stanley_ml !< If true, use the Stanley parameterization of SGS T variance real, dimension(:,:), allocatable :: & MLD_filtered, & !< Time-filtered MLD [H ~> m or kg m-2] @@ -178,6 +179,8 @@ subroutine mixedlayer_restrat_general(h, uhtr, vhtr, tv, forces, dt, MLD_in, Var real, dimension(SZI_(G)) :: dK, dKm1 ! Depths of layer centers [H ~> m or kg m-2]. real, dimension(SZI_(G)) :: pRef_MLD ! A reference pressure for calculating the mixed layer ! densities [R L2 T-2 ~> Pa]. + real, dimension(SZI_(G)) :: covTS, & !SGS TS covariance in Stanley param; currently 0 [degC ppt] + varS !SGS S variance in Stanley param; currently 0 [ppt2] real :: aFac, bFac ! Nondimensional ratios [nondim] real :: ddRho ! A density difference [R ~> kg m-3] real :: hAtVel ! Thickness at the velocity points [H ~> m or kg m-2] @@ -194,6 +197,8 @@ subroutine mixedlayer_restrat_general(h, uhtr, vhtr, tv, forces, dt, MLD_in, Var Isq = G%IscB ; Ieq = G%IecB ; Jsq = G%JscB ; Jeq = G%JecB h_min = 0.5*GV%Angstrom_H ! This should be GV%Angstrom_H, but that value would change answers. + covTS(:)=0.0 !!Functionality not implemented yet; in future, should be passed in tv + varS(:)=0.0 if (.not.associated(tv%eqn_of_state)) call MOM_error(FATAL, "MOM_mixedlayer_restrat: "// & "An equation of state must be used with this module.") @@ -207,7 +212,12 @@ subroutine mixedlayer_restrat_general(h, uhtr, vhtr, tv, forces, dt, MLD_in, Var EOSdom(:) = EOS_domain(G%HI, halo=1) do j = js-1, je+1 dK(:) = 0.5 * h(:,j,1) ! Depth of center of surface layer - call calculate_density(tv%T(:,j,1), tv%S(:,j,1), pRef_MLD, rhoSurf, tv%eqn_of_state, EOSdom) + if (CS%use_stanley_ml) then + call calculate_density(tv%T(:,j,1), tv%S(:,j,1), pRef_MLD, tv%varT(:,j,1), covTS, varS, & + rhoSurf, tv%eqn_of_state, EOSdom) + else + call calculate_density(tv%T(:,j,1), tv%S(:,j,1), pRef_MLD, rhoSurf, tv%eqn_of_state, EOSdom) + endif deltaRhoAtK(:) = 0. MLD_fast(:,j) = 0. do k = 2, nz @@ -215,7 +225,12 @@ subroutine mixedlayer_restrat_general(h, uhtr, vhtr, tv, forces, dt, MLD_in, Var dK(:) = dK(:) + 0.5 * ( h(:,j,k) + h(:,j,k-1) ) ! Depth of center of layer K ! Mixed-layer depth, using sigma-0 (surface reference pressure) deltaRhoAtKm1(:) = deltaRhoAtK(:) ! Store value from previous iteration of K - call calculate_density(tv%T(:,j,k), tv%S(:,j,k), pRef_MLD, deltaRhoAtK, tv%eqn_of_state, EOSdom) + if (CS%use_stanley_ml) then + call calculate_density(tv%T(:,j,k), tv%S(:,j,k), pRef_MLD, tv%varT(:,j,k), covTS, varS, & + deltaRhoAtK, tv%eqn_of_state, EOSdom) + else + call calculate_density(tv%T(:,j,k), tv%S(:,j,k), pRef_MLD, deltaRhoAtK, tv%eqn_of_state, EOSdom) + endif do i = is-1,ie+1 deltaRhoAtK(i) = deltaRhoAtK(i) - rhoSurf(i) ! Density difference between layer K and surface enddo @@ -312,7 +327,12 @@ subroutine mixedlayer_restrat_general(h, uhtr, vhtr, tv, forces, dt, MLD_in, Var h_avail(i,j,k) = max(I4dt*G%areaT(i,j)*(h(i,j,k)-GV%Angstrom_H),0.0) enddo if (keep_going) then - call calculate_density(tv%T(:,j,k), tv%S(:,j,k), p0, rho_ml(:), tv%eqn_of_state, EOSdom) + if (CS%use_stanley_ml) then + call calculate_density(tv%T(:,j,k), tv%S(:,j,k), p0, tv%varT(:,j,k), covTS, varS, & + rho_ml(:), tv%eqn_of_state, EOSdom) + else + call calculate_density(tv%T(:,j,k), tv%S(:,j,k), p0, rho_ml(:), tv%eqn_of_state, EOSdom) + endif line_is_empty = .true. do i=is-1,ie+1 if (htot_fast(i,j) < MLD_fast(i,j)) then @@ -628,6 +648,7 @@ subroutine mixedlayer_restrat_BML(h, uhtr, vhtr, tv, forces, dt, G, GV, US, CS) if ((nkml<2) .or. (CS%ml_restrat_coef<=0.0)) return + h_min = 0.5*GV%Angstrom_H ! This should be GV%Angstrom_H, but that value would change answers. uDml(:) = 0.0 ; vDml(:) = 0.0 I4dt = 0.25 / dt @@ -639,6 +660,10 @@ subroutine mixedlayer_restrat_BML(h, uhtr, vhtr, tv, forces, dt, G, GV, US, CS) if (.not.use_EOS) call MOM_error(FATAL, "MOM_mixedlayer_restrat: "// & "An equation of state must be used with this module.") + if (CS%use_stanley_ml) call MOM_error(FATAL, & + "MOM_mixedlayer_restrat: The Stanley parameterization is not"//& + "available with the BML.") + ! Fix this later for nkml >= 3. p0(:) = 0.0 @@ -849,6 +874,9 @@ logical function mixedlayer_restrat_init(Time, G, GV, US, param_file, diag, CS, "geostrophic kinetic energy or 1 plus the square of the "//& "grid spacing over the deformation radius, as detailed "//& "by Fox-Kemper et al. (2010)", units="nondim", default=0.0) + call get_param(param_file, mdl, "USE_STANLEY_ML", CS%use_stanley_ml, & + "If true, turn on Stanley SGS T variance parameterization "// & + "in ML restrat code.", default=.false.) ! We use GV%nkml to distinguish between the old and new implementation of MLE. ! The old implementation only works for the layer model with nkml>0. if (GV%nkml==0) then diff --git a/src/parameterizations/lateral/MOM_thickness_diffuse.F90 b/src/parameterizations/lateral/MOM_thickness_diffuse.F90 index 88376e83b9..89ba800e92 100644 --- a/src/parameterizations/lateral/MOM_thickness_diffuse.F90 +++ b/src/parameterizations/lateral/MOM_thickness_diffuse.F90 @@ -83,6 +83,7 @@ module MOM_thickness_diffuse real :: Stanley_det_coeff !< The coefficient correlating SGS temperature variance with the mean !! temperature gradient in the deterministic part of the Stanley parameterization. !! Negative values disable the scheme. [nondim] + logical :: use_stanley_gm !< If true, also use the Stanley parameterization in MOM_thickness_diffuse type(diag_ctrl), pointer :: diag => NULL() !< structure used to regulate timing of diagnostics real, allocatable :: GMwork(:,:) !< Work by thickness diffusivity [R Z L2 T-3 ~> W m-2] @@ -615,15 +616,15 @@ subroutine thickness_diffuse_full(h, e, Kh_u, Kh_v, tv, uhD, vhD, cg1, dt, G, GV h_avail_rsum ! The running sum of h_avail above an interface [H L2 T-1 ~> m3 s-1 or kg s-1]. real, dimension(SZIB_(G)) :: & drho_dT_u, & ! The derivative of density with temperature at u points [R degC-1 ~> kg m-3 degC-1] - drho_dS_u, & ! The derivative of density with salinity at u points [R ppt-1 ~> kg m-3 ppt-1]. - drho_dT_dT_u ! The second derivative of density with temperature at u points [R degC-2 ~> kg m-3 degC-2] - real, dimension(SZIB_(G)) :: scrap ! An array to pass to calculate_density_second_derivs() that will be ignored. + drho_dS_u ! The derivative of density with salinity at u points [R ppt-1 ~> kg m-3 ppt-1]. + real, dimension(SZI_(G)) :: scrap ! An array to pass to calculate_density_second_derivs() that will be ingored. real, dimension(SZI_(G)) :: & drho_dT_v, & ! The derivative of density with temperature at v points [R degC-1 ~> kg m-3 degC-1] drho_dS_v, & ! The derivative of density with salinity at v points [R ppt-1 ~> kg m-3 ppt-1]. - drho_dT_dT_v ! The second derivative of density with temperature at v points [R degC-2 ~> kg m-3 degC-2] - real :: uhtot(SZIB_(G),SZJ_(G)) ! The vertical sum of uhD [H L2 T-1 ~> m3 s-1 or kg s-1]. - real :: vhtot(SZI_(G),SZJB_(G)) ! The vertical sum of vhD [H L2 T-1 ~> m3 s-1 or kg s-1]. + drho_dT_dT_h, & ! The second derivative of density with temperature at h points [R degC-2 ~> kg m-3 degC-2] + drho_dT_dT_hr ! The second derivative of density with temperature at h (+1) points [R degC-2 ~> kg m-3 degC-2] + real :: uhtot(SZIB_(G), SZJ_(G)) ! The vertical sum of uhD [H L2 T-1 ~> m3 s-1 or kg s-1]. + real :: vhtot(SZI_(G), SZJB_(G)) ! The vertical sum of vhD [H L2 T-1 ~> m3 s-1 or kg s-1]. real, dimension(SZIB_(G)) :: & T_u, & ! Temperature on the interface at the u-point [degC]. S_u, & ! Salinity on the interface at the u-point [ppt]. @@ -631,9 +632,15 @@ subroutine thickness_diffuse_full(h, e, Kh_u, Kh_v, tv, uhD, vhD, cg1, dt, G, GV real, dimension(SZI_(G)) :: & T_v, & ! Temperature on the interface at the v-point [degC]. S_v, & ! Salinity on the interface at the v-point [ppt]. - pres_v ! Pressure on the interface at the v-point [R L2 T-2 ~> Pa]. - real :: Work_u(SZIB_(G),SZJ_(G)) ! The work being done by the thickness - real :: Work_v(SZI_(G),SZJB_(G)) ! diffusion integrated over a cell [R Z L4 T-3 ~> W] + pres_v, & ! Pressure on the interface at the v-point [R L2 T-2 ~> Pa]. + T_h, & ! Temperature on the interface at the h-point [degC]. + S_h, & ! Salinity on the interface at the h-point [ppt]. + pres_h, & ! Pressure on the interface at the h-point [R L2 T-2 ~> Pa]. + T_hr, & ! Temperature on the interface at the h (+1) point [degC]. + S_hr, & ! Salinity on the interface at the h (+1) point [ppt]. + pres_hr ! Pressure on the interface at the h (+1) point [R L2 T-2 ~> Pa]. + real :: Work_u(SZIB_(G), SZJ_(G)) ! The work being done by the thickness + real :: Work_v(SZI_(G), SZJB_(G)) ! diffusion integrated over a cell [R Z L4 T-3 ~> W ] real :: Work_h ! The work averaged over an h-cell [R Z L2 T-3 ~> W m-2]. real :: PE_release_h ! The amount of potential energy released by GM averaged over an h-cell [L4 Z-1 T-3 ~> m3 s-3] ! The calculation is equal to h * S^2 * N^2 * kappa_GM. @@ -693,7 +700,6 @@ subroutine thickness_diffuse_full(h, e, Kh_u, Kh_v, tv, uhD, vhD, cg1, dt, G, GV real :: hl(5) ! Copy of local stencil of H [H ~> m] real :: r_sm_H ! Reciprocal of sum of H in local stencil [H-1 ~> m-1] real :: Tsgs2(SZI_(G),SZJ_(G),SZK_(GV)) ! Sub-grid temperature variance [degC2] - real :: diag_sfn_x(SZIB_(G),SZJ_(G),SZK_(GV)+1) ! Diagnostic of the x-face streamfunction ! [H L2 T-1 ~> m3 s-1 or kg s-1] real :: diag_sfn_unlim_x(SZIB_(G),SZJ_(G),SZK_(GV)+1) ! Diagnostic of the x-face streamfunction before @@ -707,7 +713,7 @@ subroutine thickness_diffuse_full(h, e, Kh_u, Kh_v, tv, uhD, vhD, cg1, dt, G, GV ! state calculations at u-points. integer, dimension(2) :: EOSdom_v ! The shifted I-computational domain to use for equation of ! state calculations at v-points. - logical :: use_Stanley + logical :: use_stanley integer :: is, ie, js, je, nz, IsdB, halo integer :: i, j, k is = G%isc ; ie = G%iec ; js = G%jsc ; je = G%jec ; nz = GV%ke ; IsdB = G%IsdB @@ -724,7 +730,8 @@ subroutine thickness_diffuse_full(h, e, Kh_u, Kh_v, tv, uhD, vhD, cg1, dt, G, GV use_EOS = associated(tv%eqn_of_state) present_slope_x = PRESENT(slope_x) present_slope_y = PRESENT(slope_y) - use_Stanley = CS%Stanley_det_coeff >= 0. + + use_stanley = CS%use_stanley_gm nk_linear = max(GV%nkml, 1) @@ -738,7 +745,6 @@ subroutine thickness_diffuse_full(h, e, Kh_u, Kh_v, tv, uhD, vhD, cg1, dt, G, GV if (use_EOS) then halo = 1 ! Default halo to fill is 1 - if (use_Stanley) halo = 2 ! Need wider valid halo for gradients of T call vert_fill_TS(h, tv%T, tv%S, CS%kappa_smooth*dt, T, S, G, GV, halo, larger_h_denom=.true.) endif @@ -758,42 +764,6 @@ subroutine thickness_diffuse_full(h, e, Kh_u, Kh_v, tv, uhD, vhD, cg1, dt, G, GV h_frac(i,j,1) = 1.0 pres(i,j,2) = pres(i,j,1) + (GV%g_Earth*GV%H_to_RZ) * h(i,j,1) enddo ; enddo - if (use_Stanley) then - !$OMP do - do k=1, nz ; do j=js-1,je+1 ; do i=is-1,ie+1 - !! SGS variance in i-direction [degC2] - !dTdi2 = ( ( G%mask2dCu(I ,j) * G%IdxCu(I ,j) * ( T(i+1,j,k) - T(i,j,k) ) & - ! + G%mask2dCu(I-1,j) * G%IdxCu(I-1,j) * ( T(i,j,k) - T(i-1,j,k) ) & - ! ) * G%dxT(i,j) * 0.5 )**2 - !! SGS variance in j-direction [degC2] - !dTdj2 = ( ( G%mask2dCv(i,J ) * G%IdyCv(i,J ) * ( T(i,j+1,k) - T(i,j,k) ) & - ! + G%mask2dCv(i,J-1) * G%IdyCv(i,J-1) * ( T(i,j,k) - T(i,j-1,k) ) & - ! ) * G%dyT(i,j) * 0.5 )**2 - !Tsgs2(i,j,k) = CS%Stanley_det_coeff * 0.5 * ( dTdi2 + dTdj2 ) - ! This block does a thickness weighted variance calculation and helps control for - ! extreme gradients along layers which are vanished against topography. It is - ! still a poor approximation in the interior when coordinates are strongly tilted. - hl(1) = h(i,j,k) * G%mask2dT(i,j) - hl(2) = h(i-1,j,k) * G%mask2dCu(I-1,j) - hl(3) = h(i+1,j,k) * G%mask2dCu(I,j) - hl(4) = h(i,j-1,k) * G%mask2dCv(i,J-1) - hl(5) = h(i,j+1,k) * G%mask2dCv(i,J) - r_sm_H = 1. / ( ( hl(1) + ( ( hl(2) + hl(3) ) + ( hl(4) + hl(5) ) ) ) + GV%H_subroundoff ) - ! Mean of T - Tl(1) = T(i,j,k) ; Tl(2) = T(i-1,j,k) ; Tl(3) = T(i+1,j,k) - Tl(4) = T(i,j-1,k) ; Tl(5) = T(i,j+1,k) - mn_T = ( hl(1)*Tl(1) + ( ( hl(2)*Tl(2) + hl(3)*Tl(3) ) + ( hl(4)*Tl(4) + hl(5)*Tl(5) ) ) ) * r_sm_H - ! Adjust T vectors to have zero mean - Tl(:) = Tl(:) - mn_T ; mn_T = 0. - ! Variance of T - mn_T2 = ( hl(1)*Tl(1)*Tl(1) + ( ( hl(2)*Tl(2)*Tl(2) + hl(3)*Tl(3)*Tl(3) ) & - + ( hl(4)*Tl(4)*Tl(4) + hl(5)*Tl(5)*Tl(5) ) ) ) * r_sm_H - ! Variance should be positive but round-off can violate this. Calculating - ! variance directly would fix this but requires more operations. - Tsgs2(i,j,k) = CS%Stanley_det_coeff * max(0., mn_T2) - enddo ; enddo ; enddo - endif - !$OMP do do j=js-1,je+1 do k=2,nz ; do i=is-1,ie+1 h_avail(i,j,k) = max(I4dt*G%areaT(i,j)*(h(i,j,k)-GV%Angstrom_H),0.0) @@ -828,7 +798,7 @@ subroutine thickness_diffuse_full(h, e, Kh_u, Kh_v, tv, uhD, vhD, cg1, dt, G, GV !$OMP present_slope_x,G_rho0,Slope_x_PE,hN2_x_PE) & !$OMP private(drdiA,drdiB,drdkL,drdkR,pres_u,T_u,S_u, & !$OMP drho_dT_u,drho_dS_u,hg2A,hg2B,hg2L,hg2R,haA, & - !$OMP drho_dT_dT_u,scrap, & + !$OMP drho_dT_dT_h,scrap,pres_h,T_h,S_h, & !$OMP haB,haL,haR,dzaL,dzaR,wtA,wtB,wtL,wtR,drdz, & !$OMP drdx,mag_grad2,Slope,slope2_Ratio_u,hN2_u, & !$OMP Sfn_unlim_u,drdi_u,drdkDe_u,h_harm,c2_h_u, & @@ -842,7 +812,7 @@ subroutine thickness_diffuse_full(h, e, Kh_u, Kh_v, tv, uhD, vhD, cg1, dt, G, GV endif calc_derivatives = use_EOS .and. (k >= nk_linear) .and. & - (find_work .or. .not. present_slope_x .or. CS%use_FGNV_streamfn .or. use_Stanley) + (find_work .or. .not. present_slope_x .or. CS%use_FGNV_streamfn .or. use_stanley) ! Calculate the zonal fluxes and gradients. if (calc_derivatives) then @@ -854,12 +824,17 @@ subroutine thickness_diffuse_full(h, e, Kh_u, Kh_v, tv, uhD, vhD, cg1, dt, G, GV call calculate_density_derivs(T_u, S_u, pres_u, drho_dT_u, drho_dS_u, & tv%eqn_of_state, EOSdom_u) endif - if (use_Stanley) then + if (use_stanley) then + do i=is-1,ie+1 + pres_h(i) = pres(i,j,K) + T_h(i) = 0.5*(T(i,j,k) + T(i,j,k-1)) + S_h(i) = 0.5*(S(i,j,k) + S(i,j,k-1)) + enddo ! The second line below would correspond to arguments ! drho_dS_dS, drho_dS_dT, drho_dT_dT, drho_dS_dP, drho_dT_dP, & - call calculate_density_second_derivs(T_u, S_u, pres_u, & - scrap, scrap, drho_dT_dT_u, scrap, scrap, & - (is-IsdB+1)-1, ie-is+2, tv%eqn_of_state) + call calculate_density_second_derivs(T_h, S_h, pres_h, & + scrap, scrap, drho_dT_dT_h, scrap, scrap, & + is-1, ie-is+3, tv%eqn_of_state) endif do I=is-1,ie @@ -879,11 +854,13 @@ subroutine thickness_diffuse_full(h, e, Kh_u, Kh_v, tv, uhD, vhD, cg1, dt, G, GV elseif (find_work) then ! This is used in pure stacked SW mode drdkDe_u(I,K) = drdkR * e(i+1,j,K) - drdkL * e(i,j,K) endif - if (use_Stanley) then + if (use_stanley) then ! Correction to the horizontal density gradient due to nonlinearity in ! the EOS rectifying SGS temperature anomalies - drdiA = drdiA + drho_dT_dT_u(I) * 0.5 * ( Tsgs2(i+1,j,k-1)-Tsgs2(i,j,k-1) ) - drdiB = drdiB + drho_dT_dT_u(I) * 0.5 * ( Tsgs2(i+1,j,k)-Tsgs2(i,j,k) ) + drdiA = drdiA + 0.5 * ((drho_dT_dT_h(i+1) * tv%varT(i+1,j,k-1)) - & + (drho_dT_dT_h(i) * tv%varT(i,j,k-1)) ) + drdiB = drdiB + 0.5 * ((drho_dT_dT_h(i+1) * tv%varT(i+1,j,k)) - & + (drho_dT_dT_h(i) * tv%varT(i,j,k)) ) endif if (find_work) drdi_u(I,k) = drdiB @@ -1094,13 +1071,13 @@ subroutine thickness_diffuse_full(h, e, Kh_u, Kh_v, tv, uhD, vhD, cg1, dt, G, GV !$OMP h_neglect2,int_slope_v,KH_v,vhtot,h_frac,h_avail_rsum, & !$OMP vhD,h_avail,G_scale,Work_v,CS,slope_y,cg1,diag_sfn_y, & !$OMP diag_sfn_unlim_y,N2_floor,EOSdom_v,use_stanley,Tsgs2, & - !$OMP present_slope_y,G_rho0,Slope_y_PE,hN2_y_PE) & - !$OMP private(drdjA,drdjB,drdkL,drdkR,pres_v,T_v,S_v, & - !$OMP drho_dT_v,drho_dS_v,hg2A,hg2B,hg2L,hg2R,haA, & - !$OMP drho_dT_dT_v,scrap, & - !$OMP haB,haL,haR,dzaL,dzaR,wtA,wtB,wtL,wtR,drdz, & - !$OMP drdy,mag_grad2,Slope,slope2_Ratio_v,hN2_v, & - !$OMP Sfn_unlim_v,drdj_v,drdkDe_v,h_harm,c2_h_v, & + !$OMP present_slope_y,G_rho0,Slope_y_PE,hN2_y_PE) & + !$OMP private(drdjA,drdjB,drdkL,drdkR,pres_v,T_v,S_v,S_h,S_hr, & + !$OMP drho_dT_v,drho_dS_v,hg2A,hg2B,hg2L,hg2R,haA, & + !$OMP drho_dT_dT_h,drho_dT_dT_hr, scrap,pres_h,T_h,T_hr, & + !$OMP haB,haL,haR,dzaL,dzaR,wtA,wtB,wtL,wtR,drdz,pres_hr, & + !$OMP drdy,mag_grad2,Slope,slope2_Ratio_v,hN2_v, & + !$OMP Sfn_unlim_v,drdj_v,drdkDe_v,h_harm,c2_h_v, & !$OMP Sfn_safe,Sfn_est,Sfn_in_h,calc_derivatives) do J=js-1,je do K=nz,2,-1 @@ -1110,7 +1087,7 @@ subroutine thickness_diffuse_full(h, e, Kh_u, Kh_v, tv, uhD, vhD, cg1, dt, G, GV endif calc_derivatives = use_EOS .and. (k >= nk_linear) .and. & - (find_work .or. .not. present_slope_y .or. CS%use_FGNV_streamfn .or. use_Stanley) + (find_work .or. .not. present_slope_y .or. CS%use_FGNV_streamfn .or. use_stanley) if (calc_derivatives) then do i=is,ie @@ -1121,11 +1098,23 @@ subroutine thickness_diffuse_full(h, e, Kh_u, Kh_v, tv, uhD, vhD, cg1, dt, G, GV call calculate_density_derivs(T_v, S_v, pres_v, drho_dT_v, drho_dS_v, & tv%eqn_of_state, EOSdom_v) endif - if (use_Stanley) then + if (use_stanley) then + do i=is,ie + pres_h(i) = pres(i,j,K) + T_h(i) = 0.5*(T(i,j,k) + T(i,j,k-1)) + S_h(i) = 0.5*(S(i,j,k) + S(i,j,k-1)) + + pres_hr(i) = pres(i,j+1,K) + T_hr(i) = 0.5*(T(i,j+1,k) + T(i,j+1,k-1)) + S_hr(i) = 0.5*(S(i,j+1,k) + S(i,j+1,k-1)) + enddo ! The second line below would correspond to arguments ! drho_dS_dS, drho_dS_dT, drho_dT_dT, drho_dS_dP, drho_dT_dP, & - call calculate_density_second_derivs(T_v, S_v, pres_v, & - scrap, scrap, drho_dT_dT_v, scrap, scrap, & + call calculate_density_second_derivs(T_h, S_h, pres_h, & + scrap, scrap, drho_dT_dT_h, scrap, scrap, & + is, ie-is+1, tv%eqn_of_state) + call calculate_density_second_derivs(T_hr, S_hr, pres_hr, & + scrap, scrap, drho_dT_dT_hr, scrap, scrap, & is, ie-is+1, tv%eqn_of_state) endif do i=is,ie @@ -1145,11 +1134,13 @@ subroutine thickness_diffuse_full(h, e, Kh_u, Kh_v, tv, uhD, vhD, cg1, dt, G, GV elseif (find_work) then ! This is used in pure stacked SW mode drdkDe_v(i,K) = drdkR * e(i,j+1,K) - drdkL * e(i,j,K) endif - if (use_Stanley) then + if (use_stanley) then ! Correction to the horizontal density gradient due to nonlinearity in ! the EOS rectifying SGS temperature anomalies - drdjA = drdjA + drho_dT_dT_v(I) * 0.5 * ( Tsgs2(i,j+1,k-1)-Tsgs2(i,j,k-1) ) - drdjB = drdjB + drho_dT_dT_v(I) * 0.5 * ( Tsgs2(i,j+1,k)-Tsgs2(i,j,k) ) + drdjA = drdjA + 0.5 * ((drho_dT_dT_hr(i) * tv%varT(i,j+1,k-1)) - & + (drho_dT_dT_h(i) * tv%varT(i,j,k-1)) ) + drdjB = drdjB + 0.5 * ((drho_dT_dT_hr(i) * tv%varT(i,j+1,k)) - & + (drho_dT_dT_h(i) * tv%varT(i,j,k)) ) endif if (find_work) drdj_v(i,k) = drdjB @@ -2049,10 +2040,9 @@ subroutine thickness_diffuse_init(Time, G, GV, US, param_file, diag, CDp, CS) "streamfunction formulation, expressed as a fraction of planetary "//& "rotation, OMEGA. This should be tiny but non-zero to avoid degeneracy.", & default=1.e-15, units="nondim", do_not_log=.not.CS%use_FGNV_streamfn) - call get_param(param_file, mdl, "STANLEY_PRM_DET_COEFF", CS%Stanley_det_coeff, & - "The coefficient correlating SGS temperature variance with the mean "//& - "temperature gradient in the deterministic part of the Stanley parameterization. "//& - "Negative values disable the scheme.", units="nondim", default=-1.0) + call get_param(param_file, mdl, "USE_STANLEY_GM", CS%use_stanley_gm, & + "If true, turn on Stanley SGS T variance parameterization "// & + "in GM code.", default=.false.) call get_param(param_file, mdl, "OMEGA", omega, & "The rotation rate of the earth.", & default=7.2921e-5, units="s-1", scale=US%T_to_s, do_not_log=.not.CS%use_FGNV_streamfn) diff --git a/src/parameterizations/vertical/MOM_CVMix_KPP.F90 b/src/parameterizations/vertical/MOM_CVMix_KPP.F90 index 30818a6f1f..4f495a1f0b 100644 --- a/src/parameterizations/vertical/MOM_CVMix_KPP.F90 +++ b/src/parameterizations/vertical/MOM_CVMix_KPP.F90 @@ -19,6 +19,7 @@ module MOM_CVMix_KPP use MOM_domains, only : pass_var use MOM_cpu_clock, only : cpu_clock_id, cpu_clock_begin, cpu_clock_end use MOM_cpu_clock, only : CLOCK_MODULE, CLOCK_ROUTINE +use MOM_tracer_types, only : tracer_type use CVMix_kpp, only : CVMix_init_kpp, CVMix_put_kpp, CVMix_get_kpp_real use CVMix_kpp, only : CVMix_coeffs_kpp @@ -39,6 +40,7 @@ module MOM_CVMix_KPP public :: KPP_end public :: KPP_NonLocalTransport_temp public :: KPP_NonLocalTransport_saln +public :: KPP_NonLocalTransport public :: KPP_get_BLD ! Enumerated constants @@ -92,7 +94,7 @@ module MOM_CVMix_KPP logical :: debug !< If True, calculate checksums and write debugging information character(len=30) :: MatchTechnique !< Method used in CVMix for setting diffusivity and NLT profile functions integer :: NLT_shape !< MOM6 over-ride of CVMix NLT shape function - logical :: applyNonLocalTrans !< If True, apply non-local transport to heat and scalars + logical :: applyNonLocalTrans !< If True, apply non-local transport to all tracers integer :: n_smooth !< Number of times smoothing operator is applied on OBLdepth. logical :: deepen_only !< If true, apply OBLdepth smoothing at a cell only if the OBLdepth gets deeper. logical :: KPPzeroDiffusivity !< If True, will set diffusivity and viscosity from KPP to zero @@ -127,7 +129,6 @@ module MOM_CVMix_KPP integer :: id_Ws = -1, id_Vt2 = -1 integer :: id_BulkUz2 = -1, id_BulkDrho = -1 integer :: id_uStar = -1, id_buoyFlux = -1 - integer :: id_QminusSW = -1, id_netS = -1 integer :: id_sigma = -1, id_Kv_KPP = -1 integer :: id_Kt_KPP = -1, id_Ks_KPP = -1 integer :: id_Tsurf = -1, id_Ssurf = -1 @@ -135,10 +136,6 @@ module MOM_CVMix_KPP integer :: id_Kd_in = -1 integer :: id_NLTt = -1 integer :: id_NLTs = -1 - integer :: id_NLT_dSdt = -1 - integer :: id_NLT_dTdt = -1 - integer :: id_NLT_temp_budget = -1 - integer :: id_NLT_saln_budget = -1 integer :: id_EnhK = -1, id_EnhVt2 = -1 integer :: id_EnhW = -1 integer :: id_La_SL = -1 @@ -226,7 +223,7 @@ logical function KPP_init(paramFile, G, GV, US, diag, Time, CS, passive) if (present(passive)) passive=CS%passiveMode ! This is passed back to the caller so ! the caller knows to not use KPP output call get_param(paramFile, mdl, 'APPLY_NONLOCAL_TRANSPORT', CS%applyNonLocalTrans, & - 'If True, applies the non-local transport to heat and scalars. '// & + 'If True, applies the non-local transport to all tracers. '// & 'If False, calculates the non-local transport and tendencies but '//& 'purely for diagnostic purposes.', & default=.not. CS%passiveMode) @@ -536,10 +533,6 @@ logical function KPP_init(paramFile, G, GV, US, diag, Time, CS, passive) 'Friction velocity, u*, as used by [CVMix] KPP', 'm/s', conversion=US%Z_to_m*US%s_to_T) CS%id_buoyFlux = register_diag_field('ocean_model', 'KPP_buoyFlux', diag%axesTi, Time, & 'Surface (and penetrating) buoyancy flux, as used by [CVMix] KPP', 'm2/s3', conversion=US%L_to_m**2*US%s_to_T**3) - CS%id_QminusSW = register_diag_field('ocean_model', 'KPP_QminusSW', diag%axesT1, Time, & - 'Net temperature flux ignoring short-wave, as used by [CVMix] KPP', 'K m/s', conversion=GV%H_to_m*US%s_to_T) - CS%id_netS = register_diag_field('ocean_model', 'KPP_netSalt', diag%axesT1, Time, & - 'Effective net surface salt flux, as used by [CVMix] KPP', 'ppt m/s', conversion=GV%H_to_m*US%s_to_T) CS%id_Kt_KPP = register_diag_field('ocean_model', 'KPP_Kheat', diag%axesTi, Time, & 'Heat diffusivity due to KPP, as calculated by [CVMix] KPP', 'm2/s') CS%id_Kd_in = register_diag_field('ocean_model', 'KPP_Kd_in', diag%axesTi, Time, & @@ -552,18 +545,6 @@ logical function KPP_init(paramFile, G, GV, US, diag, Time, CS, passive) 'Non-local transport (Cs*G(sigma)) for heat, as calculated by [CVMix] KPP', 'nondim') CS%id_NLTs = register_diag_field('ocean_model', 'KPP_NLtransport_salt', diag%axesTi, Time, & 'Non-local tranpsort (Cs*G(sigma)) for scalars, as calculated by [CVMix] KPP', 'nondim') - CS%id_NLT_dTdt = register_diag_field('ocean_model', 'KPP_NLT_dTdt', diag%axesTL, Time, & - 'Temperature tendency due to non-local transport of heat, as calculated by [CVMix] KPP', & - 'K/s', conversion=US%s_to_T) - CS%id_NLT_dSdt = register_diag_field('ocean_model', 'KPP_NLT_dSdt', diag%axesTL, Time, & - 'Salinity tendency due to non-local transport of salt, as calculated by [CVMix] KPP', & - 'ppt/s', conversion=US%s_to_T) - CS%id_NLT_temp_budget = register_diag_field('ocean_model', 'KPP_NLT_temp_budget', diag%axesTL, Time, & - 'Heat content change due to non-local transport, as calculated by [CVMix] KPP', & - 'W/m^2', conversion=US%QRZ_T_to_W_m2) - CS%id_NLT_saln_budget = register_diag_field('ocean_model', 'KPP_NLT_saln_budget', diag%axesTL, Time, & - 'Salt content change due to non-local transport, as calculated by [CVMix] KPP', & - 'kg/(sec*m^2)', conversion=US%RZ_T_to_kg_m2s) CS%id_Tsurf = register_diag_field('ocean_model', 'KPP_Tsurf', diag%axesT1, Time, & 'Temperature of surface layer (10% of OBL depth) as passed to [CVMix] KPP', 'C') CS%id_Ssurf = register_diag_field('ocean_model', 'KPP_Ssurf', diag%axesT1, Time, & @@ -1115,7 +1096,7 @@ subroutine KPP_compute_BLD(CS, G, GV, US, h, Temp, Salt, u, v, tv, uStar, buoyFl enddo ! k-loop finishes - if (CS%LT_K_ENHANCEMENT .or. CS%LT_VT2_ENHANCEMENT) then + if ( (CS%LT_K_ENHANCEMENT .or. CS%LT_VT2_ENHANCEMENT) .and. .not. present(lamult)) then MLD_GUESS = max( 1.*US%m_to_Z, abs(US%m_to_Z*CS%OBLdepthprev(i,j) ) ) call get_Langmuir_Number(LA, G, GV, US, MLD_guess, uStar(i,j), i, j, & H=H(i,j,:), U_H=U_H, V_H=V_H, WAVES=WAVES) @@ -1379,10 +1360,75 @@ subroutine KPP_get_BLD(CS, BLD, G, US, m_to_BLD_units) end subroutine KPP_get_BLD -!> Apply KPP non-local transport of surface fluxes for temperature. -subroutine KPP_NonLocalTransport_temp(CS, G, GV, h, nonLocalTrans, surfFlux, & - dt, scalar, C_p) +!> Apply KPP non-local transport of surface fluxes for a given tracer +subroutine KPP_NonLocalTransport(CS, G, GV, h, nonLocalTrans, surfFlux, & + dt, diag, tr_ptr, scalar, flux_scale) + type(KPP_CS), intent(in) :: CS !< Control structure + type(ocean_grid_type), intent(in) :: G !< Ocean grid + type(verticalGrid_type), intent(in) :: GV !< Ocean vertical grid + real, dimension(SZI_(G),SZJ_(G),SZK_(GV)), intent(in) :: h !< Layer/level thickness [H ~> m or kg m-2] + real, dimension(SZI_(G),SZJ_(G),SZK_(GV)+1), intent(in) :: nonLocalTrans !< Non-local transport [nondim] + real, dimension(SZI_(G),SZJ_(G)), intent(in) :: surfFlux !< Surface flux of scalar + !! [conc H T-1 ~> conc m s-1 or conc kg m-2 s-1] + real, intent(in) :: dt !< Time-step [T ~> s] + type(diag_ctrl), target, intent(in) :: diag !< Diagnostics + type(tracer_type), pointer, intent(in) :: tr_ptr !< tracer_type has diagnostic ids on it + real, dimension(SZI_(G),SZJ_(G),SZK_(GV)), intent(inout) :: scalar !< Scalar (scalar units [conc]) + real, optional, intent(in) :: flux_scale !< Scale factor to get surfFlux + !! into proper units + + integer :: i, j, k + real, dimension(SZI_(G),SZJ_(G),SZK_(GV)) :: dtracer ! Rate of tracer change [conc T-1 ~> conc s-1] + real, dimension(SZI_(G),SZJ_(G)) :: surfFlux_loc + + ! term used to scale + if (present(flux_scale)) then + do j = G%jsc, G%jec ; do i = G%isc, G%iec + surfFlux_loc(i,j) = surfFlux(i,j) * flux_scale + enddo ; enddo + else + surfFlux_loc(:,:) = surfFlux(:,:) + endif + + ! Post surface flux diagnostic + if (tr_ptr%id_net_surfflux > 0) call post_data(tr_ptr%id_net_surfflux, surfFlux_loc(:,:), diag) + + ! Only continue if we are applying the nonlocal tendency + ! or the nonlocal tendency diagnostic has been requested + if ((tr_ptr%id_NLT_tendency > 0) .or. (CS%applyNonLocalTrans)) then + + !$OMP parallel do default(none) shared(dtracer, nonLocalTrans, h, G, GV, surfFlux_loc) + do k = 1, GV%ke ; do j = G%jsc, G%jec ; do i = G%isc, G%iec + dtracer(i,j,k) = ( nonLocalTrans(i,j,k) - nonLocalTrans(i,j,k+1) ) / & + ( h(i,j,k) + GV%H_subroundoff ) * surfFlux_loc(i,j) + enddo ; enddo ; enddo + + ! Update tracer due to non-local redistribution of surface flux + if (CS%applyNonLocalTrans) then + !$OMP parallel do default(none) shared(G, GV, dt, scalar, dtracer) + do k = 1, GV%ke ; do j = G%jsc, G%jec ; do i = G%isc, G%iec + scalar(i,j,k) = scalar(i,j,k) + dt * dtracer(i,j,k) + enddo ; enddo ; enddo + endif + if (tr_ptr%id_NLT_tendency > 0) call post_data(tr_ptr%id_NLT_tendency, dtracer, diag) + + endif + + + if (tr_ptr%id_NLT_budget > 0) then + !$OMP parallel do default(none) shared(G, GV, dtracer, nonLocalTrans, surfFlux_loc) + do k = 1, GV%ke ; do j = G%jsc, G%jec ; do i = G%isc, G%iec + ! Here dtracer has units of [Q R Z T-1 ~> W m-2]. + dtracer(i,j,k) = (nonLocalTrans(i,j,k) - nonLocalTrans(i,j,k+1)) * surfFlux_loc(i,j) + enddo ; enddo ; enddo + call post_data(tr_ptr%id_NLT_budget, dtracer(:,:,:), diag) + endif + +end subroutine KPP_NonLocalTransport + +!> Apply KPP non-local transport of surface fluxes for temperature. +subroutine KPP_NonLocalTransport_temp(CS, G, GV, h, nonLocalTrans, surfFlux, dt, tr_ptr, scalar, C_p) type(KPP_CS), intent(in) :: CS !< Control structure type(ocean_grid_type), intent(in) :: G !< Ocean grid type(verticalGrid_type), intent(in) :: GV !< Ocean vertical grid @@ -1391,116 +1437,32 @@ subroutine KPP_NonLocalTransport_temp(CS, G, GV, h, nonLocalTrans, surfFlux, & real, dimension(SZI_(G),SZJ_(G)), intent(in) :: surfFlux !< Surface flux of temperature !! [degC H T-1 ~> degC m s-1 or degC kg m-2 s-1] real, intent(in) :: dt !< Time-step [T ~> s] + type(tracer_type), pointer, intent(in) :: tr_ptr !< tracer_type has diagnostic ids on it real, dimension(SZI_(G),SZJ_(G),SZK_(GV)), intent(inout) :: scalar !< temperature [degC] real, intent(in) :: C_p !< Seawater specific heat capacity !! [Q degC-1 ~> J kg-1 degC-1] - integer :: i, j, k - real, dimension( SZI_(G), SZJ_(G),SZK_(GV) ) :: dtracer ! Rate of tracer change [degC T-1 ~> degC s-1] - - - dtracer(:,:,:) = 0.0 - !$OMP parallel do default(none) shared(dtracer, nonLocalTrans, h, G, GV, surfFlux) - do k = 1, GV%ke - do j = G%jsc, G%jec - do i = G%isc, G%iec - dtracer(i,j,k) = ( nonLocalTrans(i,j,k) - nonLocalTrans(i,j,k+1) ) / & - ( h(i,j,k) + GV%H_subroundoff ) * surfFlux(i,j) - enddo - enddo - enddo - - ! Update tracer due to non-local redistribution of surface flux - if (CS%applyNonLocalTrans) then - !$OMP parallel do default(none) shared(dt, scalar, dtracer, G, GV) - do k = 1, GV%ke - do j = G%jsc, G%jec - do i = G%isc, G%iec - scalar(i,j,k) = scalar(i,j,k) + dt * dtracer(i,j,k) - enddo - enddo - enddo - endif - - ! Diagnostics - if (CS%id_QminusSW > 0) call post_data(CS%id_QminusSW, surfFlux, CS%diag) - if (CS%id_NLT_dTdt > 0) call post_data(CS%id_NLT_dTdt, dtracer, CS%diag) - if (CS%id_NLT_temp_budget > 0) then - dtracer(:,:,:) = 0.0 - !$OMP parallel do default(none) shared(dtracer, nonLocalTrans, surfFlux, C_p, G, GV) - do k = 1, GV%ke - do j = G%jsc, G%jec - do i = G%isc, G%iec - ! Here dtracer has units of [Q R Z T-1 ~> W m-2]. - dtracer(i,j,k) = (nonLocalTrans(i,j,k) - nonLocalTrans(i,j,k+1)) * & - surfFlux(i,j) * C_p * GV%H_to_RZ - enddo - enddo - enddo - call post_data(CS%id_NLT_temp_budget, dtracer, CS%diag) - endif + call KPP_NonLocalTransport(CS, G, GV, h, nonLocalTrans, surfFlux, dt, CS%diag, & + tr_ptr, scalar) end subroutine KPP_NonLocalTransport_temp !> Apply KPP non-local transport of surface fluxes for salinity. -!> This routine is a useful prototype for other material tracers. -subroutine KPP_NonLocalTransport_saln(CS, G, GV, h, nonLocalTrans, surfFlux, dt, scalar) - - type(KPP_CS), intent(in) :: CS !< Control structure - type(ocean_grid_type), intent(in) :: G !< Ocean grid - type(verticalGrid_type), intent(in) :: GV !< Ocean vertical grid - real, dimension(SZI_(G),SZJ_(G),SZK_(GV)), intent(in) :: h !< Layer/level thickness [H ~> m or kg m-2] +subroutine KPP_NonLocalTransport_saln(CS, G, GV, h, nonLocalTrans, surfFlux, dt, tr_ptr, scalar) + type(KPP_CS), intent(in) :: CS !< Control structure + type(ocean_grid_type), intent(in) :: G !< Ocean grid + type(verticalGrid_type), intent(in) :: GV !< Ocean vertical grid + real, dimension(SZI_(G),SZJ_(G),SZK_(GV)), intent(in) :: h !< Layer/level thickness [H ~> m or kg m-2] real, dimension(SZI_(G),SZJ_(G),SZK_(GV)+1), intent(in) :: nonLocalTrans !< Non-local transport [nondim] - real, dimension(SZI_(G),SZJ_(G)), intent(in) :: surfFlux !< Surface flux of salt + real, dimension(SZI_(G),SZJ_(G)), intent(in) :: surfFlux !< Surface flux of salt !! [ppt H T-1 ~> ppt m s-1 or ppt kg m-2 s-1] - real, intent(in) :: dt !< Time-step [T ~> s] - real, dimension(SZI_(G),SZJ_(G),SZK_(GV)), intent(inout) :: scalar !< Salinity [ppt] - - integer :: i, j, k - real, dimension( SZI_(G), SZJ_(G),SZK_(GV) ) :: dtracer ! Rate of tracer change [ppt T-1 ~> ppt s-1] - - - dtracer(:,:,:) = 0.0 - !$OMP parallel do default(none) shared(dtracer, nonLocalTrans, h, G, GV, surfFlux) - do k = 1, GV%ke - do j = G%jsc, G%jec - do i = G%isc, G%iec - dtracer(i,j,k) = ( nonLocalTrans(i,j,k) - nonLocalTrans(i,j,k+1) ) / & - ( h(i,j,k) + GV%H_subroundoff ) * surfFlux(i,j) - enddo - enddo - enddo + real, intent(in) :: dt !< Time-step [T ~> s] + real, dimension(SZI_(G),SZJ_(G),SZK_(GV)), intent(inout) :: scalar !< Salinity [ppt] + type(tracer_type), pointer, intent(in) :: tr_ptr !< tracer_type has diagnostic ids on it - ! Update tracer due to non-local redistribution of surface flux - if (CS%applyNonLocalTrans) then - !$OMP parallel do default(none) shared(G, GV, dt, scalar, dtracer) - do k = 1, GV%ke - do j = G%jsc, G%jec - do i = G%isc, G%iec - scalar(i,j,k) = scalar(i,j,k) + dt * dtracer(i,j,k) - enddo - enddo - enddo - endif - - ! Diagnostics - if (CS%id_netS > 0) call post_data(CS%id_netS, surfFlux, CS%diag) - if (CS%id_NLT_dSdt > 0) call post_data(CS%id_NLT_dSdt, dtracer, CS%diag) - if (CS%id_NLT_saln_budget > 0) then - dtracer(:,:,:) = 0.0 - !$OMP parallel do default(none) shared(G, GV, dtracer, nonLocalTrans, surfFlux) - do k = 1, GV%ke - do j = G%jsc, G%jec - do i = G%isc, G%iec - ! Here dtracer has units of [ppt R Z T-1 ~> ppt kg m-2 s-1] - dtracer(i,j,k) = (nonLocalTrans(i,j,k) - nonLocalTrans(i,j,k+1)) * & - surfFlux(i,j) * GV%H_to_RZ - enddo - enddo - enddo - call post_data(CS%id_NLT_saln_budget, dtracer, CS%diag) - endif + call KPP_NonLocalTransport(CS, G, GV, h, nonLocalTrans, surfFlux, dt, CS%diag, & + tr_ptr, scalar) end subroutine KPP_NonLocalTransport_saln diff --git a/src/parameterizations/vertical/MOM_diabatic_aux.F90 b/src/parameterizations/vertical/MOM_diabatic_aux.F90 index 405251eaee..2a1f6b7ea6 100644 --- a/src/parameterizations/vertical/MOM_diabatic_aux.F90 +++ b/src/parameterizations/vertical/MOM_diabatic_aux.F90 @@ -1030,7 +1030,9 @@ subroutine applyBoundaryFluxesInOut(CS, G, GV, US, dt, fluxes, optics, nsw, h, t real :: dThickness, dTemp, dSalt real :: fractionOfForcing, hOld, Ithickness real :: RivermixConst ! A constant used in implementing river mixing [R Z2 T-1 ~> Pa s]. - + real :: EnthalpyConst ! A constant used to control the enthalpy calculation + ! By default EnthalpyConst = 1.0. If fluxes%heat_content_evap + ! is associated enthalpy is provided via coupler and EnthalpyConst = 0.0. real, dimension(SZI_(G)) :: & d_pres, & ! pressure change across a layer [R L2 T-2 ~> Pa] p_lay, & ! average pressure in a layer [R L2 T-2 ~> Pa] @@ -1094,6 +1096,9 @@ subroutine applyBoundaryFluxesInOut(CS, G, GV, US, dt, fluxes, optics, nsw, h, t ! Only apply forcing if fluxes%sw is associated. if (.not.associated(fluxes%sw) .and. .not.calculate_energetics) return + EnthalpyConst = 1.0 + if (associated(fluxes%heat_content_evap)) EnthalpyConst = 0.0 + if (calculate_buoyancy) then SurfPressure(:) = 0.0 GoRho = US%L_to_Z**2*GV%g_Earth / GV%Rho0 @@ -1115,7 +1120,8 @@ subroutine applyBoundaryFluxesInOut(CS, G, GV, US, dt, fluxes, optics, nsw, h, t !$OMP nonPenSW,hGrounding,CS,Idt,aggregate_FW_forcing, & !$OMP minimum_forcing_depth,evap_CFL_limit,dt,EOSdom, & !$OMP calculate_buoyancy,netPen_rate,SkinBuoyFlux,GoRho, & - !$OMP calculate_energetics,dSV_dT,dSV_dS,cTKE,g_Hconv2) & + !$OMP calculate_energetics,dSV_dT,dSV_dS,cTKE,g_Hconv2, & + !$OMP EnthalpyConst) & !$OMP private(opacityBand,h2d,T2d,netMassInOut,netMassOut, & !$OMP netHeat,netSalt,Pen_SW_bnd,fractionOfForcing, & !$OMP IforcingDepthScale, & @@ -1257,17 +1263,19 @@ subroutine applyBoundaryFluxesInOut(CS, G, GV, US, dt, fluxes, optics, nsw, h, t ! This line accounts for the temperature of the mass exchange Temp_in = T2d(i,k) Salin_in = 0.0 - dTemp = dTemp + dThickness*Temp_in + dTemp = dTemp + dThickness*Temp_in*EnthalpyConst ! Diagnostics of heat content associated with mass fluxes - if (associated(fluxes%heat_content_massin)) & - fluxes%heat_content_massin(i,j) = fluxes%heat_content_massin(i,j) + & - T2d(i,k) * max(0.,dThickness) * GV%H_to_RZ * fluxes%C_p * Idt - if (associated(fluxes%heat_content_massout)) & - fluxes%heat_content_massout(i,j) = fluxes%heat_content_massout(i,j) + & - T2d(i,k) * min(0.,dThickness) * GV%H_to_RZ * fluxes%C_p * Idt - if (associated(tv%TempxPmE)) tv%TempxPmE(i,j) = tv%TempxPmE(i,j) + & - T2d(i,k) * dThickness * GV%H_to_RZ + if (.not. associated(fluxes%heat_content_evap)) then + if (associated(fluxes%heat_content_massin)) & + fluxes%heat_content_massin(i,j) = fluxes%heat_content_massin(i,j) + & + T2d(i,k) * max(0.,dThickness) * GV%H_to_RZ * fluxes%C_p * Idt + if (associated(fluxes%heat_content_massout)) & + fluxes%heat_content_massout(i,j) = fluxes%heat_content_massout(i,j) + & + T2d(i,k) * min(0.,dThickness) * GV%H_to_RZ * fluxes%C_p * Idt + if (associated(tv%TempxPmE)) tv%TempxPmE(i,j) = tv%TempxPmE(i,j) + & + T2d(i,k) * dThickness * GV%H_to_RZ + endif ! Determine the energetics of river mixing before updating the state. if (calculate_energetics .and. associated(fluxes%lrunoff) .and. CS%do_rivermix) then @@ -1340,17 +1348,19 @@ subroutine applyBoundaryFluxesInOut(CS, G, GV, US, dt, fluxes, optics, nsw, h, t netSalt(i) = netSalt(i) - dSalt ! This line accounts for the temperature of the mass exchange - dTemp = dTemp + dThickness*T2d(i,k) + dTemp = dTemp + dThickness*T2d(i,k)*EnthalpyConst ! Diagnostics of heat content associated with mass fluxes - if (associated(fluxes%heat_content_massin)) & - fluxes%heat_content_massin(i,j) = fluxes%heat_content_massin(i,j) + & - T2d(i,k) * max(0.,dThickness) * GV%H_to_RZ * fluxes%C_p * Idt - if (associated(fluxes%heat_content_massout)) & - fluxes%heat_content_massout(i,j) = fluxes%heat_content_massout(i,j) + & - T2d(i,k) * min(0.,dThickness) * GV%H_to_RZ * fluxes%C_p * Idt - if (associated(tv%TempxPmE)) tv%TempxPmE(i,j) = tv%TempxPmE(i,j) + & - T2d(i,k) * dThickness * GV%H_to_RZ + if (.not. associated(fluxes%heat_content_evap)) then + if (associated(fluxes%heat_content_massin)) & + fluxes%heat_content_massin(i,j) = fluxes%heat_content_massin(i,j) + & + T2d(i,k) * max(0.,dThickness) * GV%H_to_RZ * fluxes%C_p * Idt + if (associated(fluxes%heat_content_massout)) & + fluxes%heat_content_massout(i,j) = fluxes%heat_content_massout(i,j) + & + T2d(i,k) * min(0.,dThickness) * GV%H_to_RZ * fluxes%C_p * Idt + if (associated(tv%TempxPmE)) tv%TempxPmE(i,j) = tv%TempxPmE(i,j) + & + T2d(i,k) * dThickness * GV%H_to_RZ + endif ! Update state by the appropriate increment. hOld = h2d(i,k) ! Keep original thickness in hand diff --git a/src/parameterizations/vertical/MOM_diabatic_driver.F90 b/src/parameterizations/vertical/MOM_diabatic_driver.F90 index 1a5471c79d..8e519eafdc 100644 --- a/src/parameterizations/vertical/MOM_diabatic_driver.F90 +++ b/src/parameterizations/vertical/MOM_diabatic_driver.F90 @@ -250,7 +250,7 @@ module MOM_diabatic_driver real, allocatable, dimension(:,:,:) :: KPP_buoy_flux !< KPP forcing buoyancy flux [L2 T-3 ~> m2 s-3] real, allocatable, dimension(:,:) :: KPP_temp_flux !< KPP effective temperature flux !! [degC H T-1 ~> degC m s-1 or degC kg m-2 s-1] - real, allocatable, dimension(:,:) :: KPP_salt_flux !< KPP effective salt flux + real, pointer, dimension(:,:) :: KPP_salt_flux => NULL() !< KPP effective salt flux !! [ppt H T-1 ~> ppt m s-1 or ppt kg m-2 s-1] type(time_type), pointer :: Time !< Pointer to model time (needed for sponges) @@ -711,9 +711,9 @@ subroutine diabatic_ALE_legacy(u, v, h, tv, Hml, fluxes, visc, ADp, CDp, dt, Tim ! Apply non-local transport of heat and salt ! Changes: tv%T, tv%S call KPP_NonLocalTransport_temp(CS%KPP_CSp, G, GV, h, CS%KPP_NLTheat, CS%KPP_temp_flux, & - dt, tv%T, tv%C_p) + dt, tv%tr_T, tv%T, tv%C_p) call KPP_NonLocalTransport_saln(CS%KPP_CSp, G, GV, h, CS%KPP_NLTscalar, CS%KPP_salt_flux, & - dt, tv%S) + dt, tv%tr_S, tv%S) call cpu_clock_end(id_clock_kpp) if (showCallTree) call callTree_waypoint("done with KPP_applyNonLocalTransport (diabatic)") if (CS%debugConservation) call MOM_state_stats('KPP_applyNonLocalTransport', u, v, h, tv%T, tv%S, G, GV, US) @@ -723,6 +723,7 @@ subroutine diabatic_ALE_legacy(u, v, h, tv, Hml, fluxes, visc, ADp, CDp, dt, Tim call MOM_forcing_chksum("after KPP_applyNLT ", fluxes, G, US, haloshift=0) call MOM_thermovar_chksum("after KPP_applyNLT ", tv, G, US) endif + if (.not.associated(fluxes%KPP_salt_flux)) fluxes%KPP_salt_flux => CS%KPP_salt_flux endif ! endif for KPP ! This is the "old" method for applying differential diffusion. @@ -1030,7 +1031,9 @@ subroutine diabatic_ALE_legacy(u, v, h, tv, Hml, fluxes, visc, ADp, CDp, dt, Tim ! For passive tracers, the changes in thickness due to boundary fluxes has yet to be applied call call_tracer_column_fns(h_orig, h, ent_s(:,:,1:nz), ent_s(:,:,2:nz+1), fluxes, Hml, dt, & G, GV, US, tv, CS%optics, CS%tracer_flow_CSp, CS%debug, & - evap_CFL_limit = CS%evap_CFL_limit, & + KPP_CSp=CS%KPP_CSp, & + nonLocalTrans=CS%KPP_NLTscalar, & + evap_CFL_limit=CS%evap_CFL_limit, & minimum_forcing_depth=CS%minimum_forcing_depth) call cpu_clock_end(id_clock_tracers) @@ -1284,9 +1287,9 @@ subroutine diabatic_ALE(u, v, h, tv, Hml, fluxes, visc, ADp, CDp, dt, Time_end, ! Apply non-local transport of heat and salt ! Changes: tv%T, tv%S call KPP_NonLocalTransport_temp(CS%KPP_CSp, G, GV, h, CS%KPP_NLTheat, CS%KPP_temp_flux, & - dt, tv%T, tv%C_p) + dt, tv%tr_T, tv%T, tv%C_p) call KPP_NonLocalTransport_saln(CS%KPP_CSp, G, GV, h, CS%KPP_NLTscalar, CS%KPP_salt_flux, & - dt, tv%S) + dt, tv%tr_S, tv%S) call cpu_clock_end(id_clock_kpp) if (showCallTree) call callTree_waypoint("done with KPP_applyNonLocalTransport (diabatic)") if (CS%debugConservation) call MOM_state_stats('KPP_applyNonLocalTransport', u, v, h, tv%T, tv%S, G, GV, US) @@ -1296,6 +1299,7 @@ subroutine diabatic_ALE(u, v, h, tv, Hml, fluxes, visc, ADp, CDp, dt, Time_end, call MOM_forcing_chksum("after KPP_applyNLT ", fluxes, G, US, haloshift=0) call MOM_thermovar_chksum("after KPP_applyNLT ", tv, G, US) endif + if (.not.associated(fluxes%KPP_salt_flux)) fluxes%KPP_salt_flux => CS%KPP_salt_flux endif ! endif for KPP ! Calculate vertical mixing due to convection (computed via CVMix) @@ -1534,6 +1538,8 @@ subroutine diabatic_ALE(u, v, h, tv, Hml, fluxes, visc, ADp, CDp, dt, Time_end, ! For passive tracers, the changes in thickness due to boundary fluxes has yet to be applied call call_tracer_column_fns(h_orig, h, ent_s(:,:,1:nz), ent_s(:,:,2:nz+1), fluxes, Hml, dt, & G, GV, US, tv, CS%optics, CS%tracer_flow_CSp, CS%debug, & + KPP_CSp=CS%KPP_CSp, & + nonLocalTrans=CS%KPP_NLTscalar, & evap_CFL_limit=CS%evap_CFL_limit, & minimum_forcing_depth=CS%minimum_forcing_depth) @@ -1881,7 +1887,7 @@ subroutine layered_diabatic(u, v, h, tv, Hml, fluxes, visc, ADp, CDp, dt, Time_e call hchksum(Kd_lay, "after KPP Kd_lay", G%HI, haloshift=0, scale=US%Z2_T_to_m2_s) call hchksum(Kd_Int, "after KPP Kd_Int", G%HI, haloshift=0, scale=US%Z2_T_to_m2_s) endif - + if (.not.associated(fluxes%KPP_salt_flux)) fluxes%KPP_salt_flux => CS%KPP_salt_flux endif ! endif for KPP ! Add vertical diff./visc. due to convection (computed via CVMix) @@ -1900,9 +1906,9 @@ subroutine layered_diabatic(u, v, h, tv, Hml, fluxes, visc, ADp, CDp, dt, Time_e ! Apply non-local transport of heat and salt ! Changes: tv%T, tv%S call KPP_NonLocalTransport_temp(CS%KPP_CSp, G, GV, h, CS%KPP_NLTheat, CS%KPP_temp_flux, & - dt, tv%T, tv%C_p) + dt, tv%tr_T, tv%T, tv%C_p) call KPP_NonLocalTransport_saln(CS%KPP_CSp, G, GV, h, CS%KPP_NLTscalar, CS%KPP_salt_flux, & - dt, tv%S) + dt, tv%tr_S, tv%S) call cpu_clock_end(id_clock_kpp) if (showCallTree) call callTree_waypoint("done with KPP_applyNonLocalTransport (diabatic)") if (CS%debugConservation) call MOM_state_stats('KPP_applyNonLocalTransport', u, v, h, tv%T, tv%S, G, GV, US) @@ -2295,7 +2301,9 @@ subroutine layered_diabatic(u, v, h, tv, Hml, fluxes, visc, ADp, CDp, dt, Time_e enddo call call_tracer_column_fns(hold, h, eatr, ebtr, fluxes, Hml, dt, G, GV, US, tv, & - CS%optics, CS%tracer_flow_CSp, CS%debug) + CS%optics, CS%tracer_flow_CSp, CS%debug, & + KPP_CSp=CS%KPP_CSp, & + nonLocalTrans=CS%KPP_NLTscalar) elseif (CS%double_diffuse) then ! extra diffusivity for passive tracers @@ -2316,11 +2324,15 @@ subroutine layered_diabatic(u, v, h, tv, Hml, fluxes, visc, ADp, CDp, dt, Time_e enddo ; enddo ; enddo call call_tracer_column_fns(hold, h, eatr, ebtr, fluxes, Hml, dt, G, GV, US, tv, & - CS%optics, CS%tracer_flow_CSp, CS%debug) + CS%optics, CS%tracer_flow_CSp, CS%debug, & + KPP_CSp=CS%KPP_CSp, & + nonLocalTrans=CS%KPP_NLTscalar) else call call_tracer_column_fns(hold, h, ea, eb, fluxes, Hml, dt, G, GV, US, tv, & - CS%optics, CS%tracer_flow_CSp, CS%debug) + CS%optics, CS%tracer_flow_CSp, CS%debug, & + KPP_CSp=CS%KPP_CSp, & + nonLocalTrans=CS%KPP_NLTscalar) endif ! (CS%mix_boundary_tracers) @@ -2529,7 +2541,7 @@ end subroutine layered_diabatic !> Returns pointers or values of members within the diabatic_CS type. For extensibility, !! each returned argument is an optional argument subroutine extract_diabatic_member(CS, opacity_CSp, optics_CSp, evap_CFL_limit, minimum_forcing_depth, & - KPP_CSp, energetic_PBL_CSp, diabatic_aux_CSp, diabatic_halo) + KPP_CSp, energetic_PBL_CSp, diabatic_aux_CSp, diabatic_halo, use_KPP) type(diabatic_CS), target, intent(in) :: CS !< module control structure ! All output arguments are optional type(opacity_CS), optional, pointer :: opacity_CSp !< A pointer to be set to the opacity control structure @@ -2544,6 +2556,7 @@ subroutine extract_diabatic_member(CS, opacity_CSp, optics_CSp, evap_CFL_limit, !! control structure integer, optional, intent( out) :: diabatic_halo !< The halo size where the diabatic algorithms !! assume thermodynamics properties are valid. + logical, optional, intent( out) :: use_KPP !< If true, diabatic is using KPP vertical mixing ! Pointers to control structures if (present(opacity_CSp)) opacity_CSp => CS%opacity @@ -2556,6 +2569,7 @@ subroutine extract_diabatic_member(CS, opacity_CSp, optics_CSp, evap_CFL_limit, if (present(evap_CFL_limit)) evap_CFL_limit = CS%evap_CFL_limit if (present(minimum_forcing_depth)) minimum_forcing_depth = CS%minimum_forcing_depth if (present(diabatic_halo)) diabatic_halo = CS%halo_TS_diff + if (present(use_KPP)) use_KPP = CS%use_KPP end subroutine extract_diabatic_member !> Routine called for adiabatic physics diff --git a/src/parameterizations/vertical/MOM_tidal_mixing.F90 b/src/parameterizations/vertical/MOM_tidal_mixing.F90 index 4728fefdff..b85cd455b1 100644 --- a/src/parameterizations/vertical/MOM_tidal_mixing.F90 +++ b/src/parameterizations/vertical/MOM_tidal_mixing.F90 @@ -597,12 +597,15 @@ logical function tidal_mixing_init(Time, G, GV, US, param_file, int_tide_CSp, di CS%id_N2_int = register_diag_field('ocean_model','N2_int',diag%axesTi,Time, & 'Bouyancy frequency squared, at interfaces', 's-2', conversion=US%s_to_T**2) !> TODO: add units - CS%id_Simmons_coeff = register_diag_field('ocean_model','Simmons_coeff',diag%axesT1,Time, & - 'time-invariant portion of the tidal mixing coefficient using the Simmons', '') - CS%id_Schmittner_coeff = register_diag_field('ocean_model','Schmittner_coeff',diag%axesTL,Time, & - 'time-invariant portion of the tidal mixing coefficient using the Schmittner', '') - CS%id_tidal_qe_md = register_diag_field('ocean_model','tidal_qe_md',diag%axesTL,Time, & - 'input tidal energy dissipated locally interpolated to model vertical coordinates', '') + if (CS%CVMix_tidal_scheme .eq. SIMMONS) then + CS%id_Simmons_coeff = register_diag_field('ocean_model','Simmons_coeff',diag%axesT1,Time, & + 'time-invariant portion of the tidal mixing coefficient using the Simmons', '') + else if (CS%CVMix_tidal_scheme .eq. SCHMITTNER) then + CS%id_Schmittner_coeff = register_diag_field('ocean_model','Schmittner_coeff',diag%axesTL,Time, & + 'time-invariant portion of the tidal mixing coefficient using the Schmittner', '') + CS%id_tidal_qe_md = register_diag_field('ocean_model','tidal_qe_md',diag%axesTL,Time, & + 'input tidal energy dissipated locally interpolated to model vertical coordinates', '') + endif CS%id_vert_dep = register_diag_field('ocean_model','vert_dep',diag%axesTi,Time, & 'vertical deposition function needed for Simmons et al tidal mixing', '') diff --git a/src/tracer/MOM_CFC_cap.F90 b/src/tracer/MOM_CFC_cap.F90 index d83dc8a6c2..0e78c351a8 100644 --- a/src/tracer/MOM_CFC_cap.F90 +++ b/src/tracer/MOM_CFC_cap.F90 @@ -11,14 +11,17 @@ module MOM_CFC_cap use MOM_forcing_type, only : forcing use MOM_hor_index, only : hor_index_type use MOM_grid, only : ocean_grid_type +use MOM_CVMix_KPP, only : KPP_NonLocalTransport, KPP_CS use MOM_io, only : file_exists, MOM_read_data, slasher use MOM_io, only : vardesc, var_desc, query_vardesc, stdout +use MOM_tracer_registry, only : tracer_type use MOM_open_boundary, only : ocean_OBC_type use MOM_restart, only : query_initialized, MOM_restart_CS use MOM_spatial_means, only : global_mass_int_EFP use MOM_time_manager, only : time_type use time_interp_external_mod, only : init_external_field, time_interp_external -use MOM_tracer_registry, only : register_tracer, tracer_registry_type +use MOM_tracer_registry, only : register_tracer +use MOM_tracer_types, only : tracer_registry_type use MOM_tracer_diabatic, only : tracer_vertdiff, applyTracerBoundaryFluxesInOut use MOM_tracer_Z_init, only : tracer_Z_init use MOM_unit_scaling, only : unit_scale_type @@ -35,6 +38,17 @@ module MOM_CFC_cap integer, parameter :: NTR = 2 !< the number of tracers in this module. +!> Contains the concentration array, a pointer to Tr in Tr_reg, and some metadata for a single CFC tracer +type, private :: CFC_tracer_data + type(vardesc) :: desc !< A set of metadata for the tracer + real :: IC_val = 0.0 !< The initial value assigned to the tracer [mol kg-1]. + real :: land_val = -1.0 !< The value of the tracer used where land is masked out [mol kg-1]. + character(len=32) :: name !< Tracer variable name + integer :: id_cmor !< Diagnostic ID + real, pointer, dimension(:,:,:) :: conc !< The tracer concentration [mol kg-1]. + type(tracer_type), pointer :: tr_ptr !< pointer to tracer inside Tr_reg + end type CFC_tracer_data + !> The control structure for the CFC_cap tracer package type, public :: CFC_cap_CS ; private character(len=200) :: IC_file !< The file in which the CFC initial values can @@ -42,28 +56,13 @@ module MOM_CFC_cap logical :: Z_IC_file !< If true, the IC_file is in Z-space. The default is false. type(time_type), pointer :: Time => NULL() !< A pointer to the ocean model's clock. type(tracer_registry_type), pointer :: tr_Reg => NULL() !< A pointer to the MOM6 tracer registry - real, pointer, dimension(:,:,:) :: & - CFC11 => NULL(), & !< The CFC11 concentration [mol kg-1]. - CFC12 => NULL() !< The CFC12 concentration [mol kg-1]. - ! In the following variables a suffix of _11 refers to CFC11 and _12 to CFC12. - real :: CFC11_IC_val = 0.0 !< The initial value assigned to CFC11 [mol kg-1]. - real :: CFC12_IC_val = 0.0 !< The initial value assigned to CFC12 [mol kg-1]. - real :: CFC11_land_val = -1.0 !< The value of CFC11 used where land is masked out [mol kg-1]. - real :: CFC12_land_val = -1.0 !< The value of CFC12 used where land is masked out [mol kg-1]. logical :: tracers_may_reinit !< If true, tracers may be reset via the initialization code !! if they are not found in the restart files. - character(len=16) :: CFC11_name !< CFC11 variable name - character(len=16) :: CFC12_name !< CFC12 variable name type(diag_ctrl), pointer :: diag => NULL() !< A structure that is used to regulate !! the timing of diagnostic output. type(MOM_restart_CS), pointer :: restart_CSp => NULL() !< Model restart control structure - ! The following vardesc types contain a package of metadata about each tracer. - type(vardesc) :: CFC11_desc !< A set of metadata for the CFC11 tracer - type(vardesc) :: CFC12_desc !< A set of metadata for the CFC12 tracer - !>@{ Diagnostic IDs - integer :: id_cfc11_cmor = -1, id_cfc12_cmor = -1 - !>@} + type(CFC_tracer_data), dimension(2) :: CFC_data !< per-tracer parameters / metadata end type CFC_cap_CS contains @@ -87,8 +86,9 @@ function register_CFC_cap(HI, GV, param_file, CS, tr_Reg, restart_CS) #include "version_variable.h" real, dimension(:,:,:), pointer :: tr_ptr => NULL() character(len=200) :: dummy ! Dummy variable to store params that need to be logged here. + character :: m2char logical :: register_CFC_cap - integer :: isd, ied, jsd, jed, nz + integer :: isd, ied, jsd, jed, nz, m isd = HI%isd ; ied = HI%ied ; jsd = HI%jsd ; jed = HI%jed ; nz = GV%ke @@ -119,12 +119,12 @@ function register_CFC_cap(HI, GV, param_file, CS, tr_Reg, restart_CS) "if they are not found in the restart files. Otherwise "//& "it is a fatal error if tracers are not found in the "//& "restart files of a restarted run.", default=.false.) - call get_param(param_file, mdl, "CFC11_IC_VAL", CS%CFC11_IC_val, & - "Value that CFC_11 is set to when it is not read from a file.", & - units="mol kg-1", default=0.0) - call get_param(param_file, mdl, "CFC12_IC_VAL", CS%CFC12_IC_val, & - "Value that CFC_12 is set to when it is not read from a file.", & - units="mol kg-1", default=0.0) + do m=1,2 + write(m2char, "(I1)") m + call get_param(param_file, mdl, "CFC1"//m2char//"_IC_VAL", CS%CFC_data(m)%IC_val, & + "Value that CFC_1"//m2char//" is set to when it is not read from a file.", & + units="mol kg-1", default=0.0) + enddo ! the following params are not used in this module. Instead, they are used in ! the cap but are logged here to keep all the CFC cap params together. @@ -149,25 +149,25 @@ function register_CFC_cap(HI, GV, param_file, CS, tr_Reg, restart_CS) ! The following vardesc types contain a package of metadata about each tracer, ! including, the name; units; longname; and grid information. - CS%CFC11_name = "CFC_11" ; CS%CFC12_name = "CFC_12" - CS%CFC11_desc = var_desc(CS%CFC11_name,"mol kg-1","Moles Per Unit Mass of CFC-11 in sea water", caller=mdl) - CS%CFC12_desc = var_desc(CS%CFC12_name,"mol kg-1","Moles Per Unit Mass of CFC-12 in sea water", caller=mdl) - - allocate(CS%CFC11(isd:ied,jsd:jed,nz), source=0.0) - allocate(CS%CFC12(isd:ied,jsd:jed,nz), source=0.0) - - ! This pointer assignment is needed to force the compiler not to do a copy in - ! the registration calls. Curses on the designers and implementers of F90. - tr_ptr => CS%CFC11 - ! Register CFC11 for horizontal advection, diffusion, and restarts. - call register_tracer(tr_ptr, tr_Reg, param_file, HI, GV, & - tr_desc=CS%CFC11_desc, registry_diags=.true., & - restart_CS=restart_CS, mandatory=.not.CS%tracers_may_reinit) - ! Do the same for CFC12 - tr_ptr => CS%CFC12 - call register_tracer(tr_ptr, Tr_Reg, param_file, HI, GV, & - tr_desc=CS%CFC12_desc, registry_diags=.true., & - restart_CS=restart_CS, mandatory=.not.CS%tracers_may_reinit) + do m=1,2 + write(m2char, "(I1)") m + write(CS%CFC_data(m)%name, "(2A)") "CFC_1", m2char + CS%CFC_data(m)%desc = var_desc(CS%CFC_data(m)%name, & + "mol kg-1", & + "Moles Per Unit Mass of CFC-1"//m2char//" in sea water", & + caller=mdl) + + allocate(CS%CFC_data(m)%conc(isd:ied,jsd:jed,nz), source=0.0) + + ! This pointer assignment is needed to force the compiler not to do a copy in + ! the registration calls. Curses on the designers and implementers of F90. + tr_ptr => CS%CFC_data(m)%conc + ! Register CFC tracer for horizontal advection, diffusion, and restarts. + call register_tracer(tr_ptr, tr_Reg, param_file, HI, GV, & + tr_desc=CS%CFC_data(m)%desc, registry_diags=.true., & + restart_CS=restart_CS, mandatory=.not.CS%tracers_may_reinit, & + Tr_out=CS%CFC_data(m)%tr_ptr) + enddo CS%tr_Reg => tr_Reg CS%restart_CSp => restart_CS @@ -193,29 +193,29 @@ subroutine initialize_CFC_cap(restart, day, G, GV, US, h, diag, OBC, CS) type(CFC_cap_CS), pointer :: CS !< The control structure returned by a !! previous call to register_CFC_cap. + ! local variables + integer :: m + character :: m2char + if (.not.associated(CS)) return CS%Time => day CS%diag => diag - if (.not.restart .or. (CS%tracers_may_reinit .and. & - .not.query_initialized(CS%CFC11, CS%CFC11_name, CS%restart_CSp))) & - call init_tracer_CFC(h, CS%CFC11, CS%CFC11_name, CS%CFC11_land_val, & - CS%CFC11_IC_val, G, GV, US, CS) - - if (.not.restart .or. (CS%tracers_may_reinit .and. & - .not.query_initialized(CS%CFC12, CS%CFC12_name, CS%restart_CSp))) & - call init_tracer_CFC(h, CS%CFC12, CS%CFC12_name, CS%CFC12_land_val, & - CS%CFC12_IC_val, G, GV, US, CS) + do m=1,2 + if (.not.restart .or. (CS%tracers_may_reinit .and. & + .not.query_initialized(CS%CFC_data(m)%conc, CS%CFC_data(m)%name, CS%restart_CSp))) & + call init_tracer_CFC(h, CS%CFC_data(m)%conc, CS%CFC_data(m)%name, CS%CFC_data(m)%land_val, & + CS%CFC_data(m)%IC_val, G, GV, US, CS) + ! cmor diagnostics + ! CFC11 cmor conventions: http://clipc-services.ceda.ac.uk/dreq/u/42625c97b8fe75124a345962c4430982.html + ! CFC12 cmor conventions: http://clipc-services.ceda.ac.uk/dreq/u/3ab8e10027d7014f18f9391890369235.html + write(m2char, "(I1)") m + CS%CFC_data(m)%id_cmor = register_diag_field('ocean_model', 'cfc1'//m2char, diag%axesTL, day, & + 'Mole Concentration of CFC1'//m2char//' in Sea Water', 'mol m-3') + enddo - ! cmor diagnostics - ! CFC11 cmor conventions: http://clipc-services.ceda.ac.uk/dreq/u/42625c97b8fe75124a345962c4430982.html - CS%id_cfc11_cmor = register_diag_field('ocean_model', 'cfc11', diag%axesTL, day, & - 'Mole Concentration of CFC11 in Sea Water', 'mol m-3') - ! CFC12 cmor conventions: http://clipc-services.ceda.ac.uk/dreq/u/3ab8e10027d7014f18f9391890369235.html - CS%id_cfc12_cmor = register_diag_field('ocean_model', 'cfc12', diag%axesTL, day, & - 'Mole Concentration of CFC12 in Sea Water', 'mol m-3') if (associated(OBC)) then ! Steal from updated DOME in the fullness of time. @@ -272,8 +272,8 @@ end subroutine init_tracer_CFC !> Applies diapycnal diffusion, souces and sinks and any other column !! tracer physics to the CFC cap tracers. CFCs are relatively simple, !! as they are passive tracers with only a surface flux as a source. -subroutine CFC_cap_column_physics(h_old, h_new, ea, eb, fluxes, dt, G, GV, US, CS, & - evap_CFL_limit, minimum_forcing_depth) +subroutine CFC_cap_column_physics(h_old, h_new, ea, eb, fluxes, dt, G, GV, US, CS, KPP_CSp, & + nonLocalTrans, evap_CFL_limit, minimum_forcing_depth) type(ocean_grid_type), intent(in) :: G !< The ocean's grid structure type(verticalGrid_type), intent(in) :: GV !< The ocean's vertical grid structure real, dimension(SZI_(G),SZJ_(G),SZK_(GV)), & @@ -294,6 +294,8 @@ subroutine CFC_cap_column_physics(h_old, h_new, ea, eb, fluxes, dt, G, GV, US, C type(unit_scale_type), intent(in) :: US !< A dimensional unit scaling type type(CFC_cap_CS), pointer :: CS !< The control structure returned by a !! previous call to register_CFC_cap. + type(KPP_CS), optional, pointer :: KPP_CSp !< KPP control structure + real, optional, intent(in) :: nonLocalTrans(:,:,:) !< Non-local transport [nondim] real, optional, intent(in) :: evap_CFL_limit !< Limit on the fraction of the water that can !! be fluxed out of the top layer in a timestep [nondim] real, optional, intent(in) :: minimum_forcing_depth !< The smallest depth over which @@ -304,39 +306,59 @@ subroutine CFC_cap_column_physics(h_old, h_new, ea, eb, fluxes, dt, G, GV, US, C ! Local variables real, dimension(SZI_(G),SZJ_(G),SZK_(GV)) :: h_work ! Used so that h can be modified [H ~> m or kg m-2] + real :: flux_scale integer :: i, j, k, is, ie, js, je, nz is = G%isc ; ie = G%iec ; js = G%jsc ; je = G%jec ; nz = GV%ke if (.not.associated(CS)) return + ! Compute KPP nonlocal term if necessary + if (present(KPP_CSp)) then + if (associated(KPP_CSp) .and. present(nonLocalTrans)) then + flux_scale = GV%Z_to_H / GV%rho0 + + call KPP_NonLocalTransport(KPP_CSp, G, GV, h_old, nonLocalTrans, fluxes%cfc11_flux(:,:), dt, CS%diag, & + CS%CFC_data(1)%tr_ptr, CS%CFC_data(1)%conc(:,:,:), & + flux_scale=flux_scale) + call KPP_NonLocalTransport(KPP_CSp, G, GV, h_old, nonLocalTrans, fluxes%cfc12_flux(:,:), dt, CS%diag, & + CS%CFC_data(2)%tr_ptr, CS%CFC_data(2)%conc(:,:,:), & + flux_scale=flux_scale) + endif + endif + ! Use a tridiagonal solver to determine the concentrations after the ! surface source is applied and diapycnal advection and diffusion occurs. if (present(evap_CFL_limit) .and. present(minimum_forcing_depth)) then do k=1,nz ;do j=js,je ; do i=is,ie h_work(i,j,k) = h_old(i,j,k) enddo ; enddo ; enddo - call applyTracerBoundaryFluxesInOut(G, GV, CS%CFC11, dt, fluxes, h_work, & + call applyTracerBoundaryFluxesInOut(G, GV, CS%CFC_data(1)%conc, dt, fluxes, h_work, & evap_CFL_limit, minimum_forcing_depth) - call tracer_vertdiff(h_work, ea, eb, dt, CS%CFC11, G, GV, sfc_flux=fluxes%cfc11_flux) + call tracer_vertdiff(h_work, ea, eb, dt, CS%CFC_data(1)%conc, G, GV, sfc_flux=fluxes%cfc11_flux) do k=1,nz ;do j=js,je ; do i=is,ie h_work(i,j,k) = h_old(i,j,k) enddo ; enddo ; enddo - call applyTracerBoundaryFluxesInOut(G, GV, CS%CFC12, dt, fluxes, h_work, & + call applyTracerBoundaryFluxesInOut(G, GV, CS%CFC_data(2)%conc, dt, fluxes, h_work, & evap_CFL_limit, minimum_forcing_depth) - call tracer_vertdiff(h_work, ea, eb, dt, CS%CFC12, G, GV, sfc_flux=fluxes%cfc12_flux) + call tracer_vertdiff(h_work, ea, eb, dt, CS%CFC_data(2)%conc, G, GV, sfc_flux=fluxes%cfc12_flux) else - call tracer_vertdiff(h_old, ea, eb, dt, CS%CFC11, G, GV, sfc_flux=fluxes%cfc11_flux) - call tracer_vertdiff(h_old, ea, eb, dt, CS%CFC12, G, GV, sfc_flux=fluxes%cfc12_flux) + call tracer_vertdiff(h_old, ea, eb, dt, CS%CFC_data(1)%conc, G, GV, sfc_flux=fluxes%cfc11_flux) + call tracer_vertdiff(h_old, ea, eb, dt, CS%CFC_data(2)%conc, G, GV, sfc_flux=fluxes%cfc12_flux) endif ! If needed, write out any desired diagnostics from tracer sources & sinks here. - if (CS%id_cfc11_cmor > 0) call post_data(CS%id_cfc11_cmor, (GV%Rho0*US%R_to_kg_m3)*CS%CFC11, CS%diag) - if (CS%id_cfc12_cmor > 0) call post_data(CS%id_cfc12_cmor, (GV%Rho0*US%R_to_kg_m3)*CS%CFC12, CS%diag) + if (CS%CFC_data(1)%id_cmor > 0) call post_data(CS%CFC_data(1)%id_cmor, & + (GV%Rho0*US%R_to_kg_m3)*CS%CFC_data(1)%conc, & + CS%diag) + if (CS%CFC_data(2)%id_cmor > 0) call post_data(CS%CFC_data(2)%id_cmor, & + (GV%Rho0*US%R_to_kg_m3)*CS%CFC_data(2)%conc, & + CS%diag) end subroutine CFC_cap_column_physics + !> Calculates the mass-weighted integral of all tracer stocks, !! returning the number of stocks it has calculated. If the stock_index !! is present, only the stock corresponding to that coded index is returned. @@ -355,6 +377,11 @@ function CFC_cap_stock(h, stocks, G, GV, CS, names, units, stock_index) !! stock being sought. integer :: CFC_cap_stock !< The number of stocks calculated here. + ! Local variables + real :: stock_scale ! The dimensional scaling factor to convert stocks to kg [kg H-1 L-2 ~> kg m-3 or 1] + real :: mass ! The cell volume or mass [H L2 ~> m3 or kg] + integer :: i, j, k, is, ie, js, je, nz, m + is = G%isc ; ie = G%iec ; js = G%jsc ; je = G%jec ; nz = GV%ke CFC_cap_stock = 0 if (.not.associated(CS)) return @@ -366,12 +393,11 @@ function CFC_cap_stock(h, stocks, G, GV, CS, names, units, stock_index) return endif ; endif - call query_vardesc(CS%CFC11_desc, name=names(1), units=units(1), caller="CFC_cap_stock") - call query_vardesc(CS%CFC12_desc, name=names(2), units=units(2), caller="CFC_cap_stock") - units(1) = trim(units(1))//" kg" ; units(2) = trim(units(2))//" kg" - - stocks(1) = global_mass_int_EFP(h, G, GV, CS%CFC11, on_PE_only=.true.) - stocks(2) = global_mass_int_EFP(h, G, GV, CS%CFC12, on_PE_only=.true.) + do m=1,2 + call query_vardesc(CS%CFC_data(m)%desc, name=names(m), units=units(m), caller="CFC_cap_stock") + units(m) = trim(units(m))//" kg" + stocks(m) = global_mass_int_EFP(h, G, GV, CS%CFC_data(m)%conc, on_PE_only=.true.) + enddo CFC_cap_stock = 2 @@ -393,8 +419,8 @@ subroutine CFC_cap_surface_state(sfc_state, G, CS) if (.not.associated(CS)) return do j=js,je ; do i=is,ie - sfc_state%sfc_cfc11(i,j) = CS%CFC11(i,j,1) - sfc_state%sfc_cfc12(i,j) = CS%CFC12(i,j,1) + sfc_state%sfc_cfc11(i,j) = CS%CFC_data(1)%conc(i,j,1) + sfc_state%sfc_cfc12(i,j) = CS%CFC_data(2)%conc(i,j,1) enddo ; enddo end subroutine CFC_cap_surface_state @@ -575,9 +601,13 @@ subroutine CFC_cap_end(CS) type(CFC_cap_CS), pointer :: CS !< The control structure returned by a !! previous call to register_CFC_cap. + ! local variables + integer :: m + if (associated(CS)) then - if (associated(CS%CFC11)) deallocate(CS%CFC11) - if (associated(CS%CFC12)) deallocate(CS%CFC12) + do m=1,2 + if (associated(CS%CFC_data(m)%conc)) deallocate(CS%CFC_data(m)%conc) + enddo deallocate(CS) endif diff --git a/src/tracer/MOM_tracer_flow_control.F90 b/src/tracer/MOM_tracer_flow_control.F90 index 94e4b669ea..d1c105fcd5 100644 --- a/src/tracer/MOM_tracer_flow_control.F90 +++ b/src/tracer/MOM_tracer_flow_control.F90 @@ -11,6 +11,7 @@ module MOM_tracer_flow_control use MOM_get_input, only : Get_MOM_input use MOM_grid, only : ocean_grid_type use MOM_hor_index, only : hor_index_type +use MOM_CVMix_KPP, only : KPP_CS use MOM_open_boundary, only : ocean_OBC_type use MOM_restart, only : MOM_restart_CS use MOM_sponge, only : sponge_CS @@ -403,7 +404,7 @@ end subroutine call_tracer_set_forcing !> This subroutine calls all registered tracer column physics subroutines. subroutine call_tracer_column_fns(h_old, h_new, ea, eb, fluxes, Hml, dt, G, GV, US, tv, optics, CS, & - debug, evap_CFL_limit, minimum_forcing_depth) + debug, KPP_CSp, nonLocalTrans, evap_CFL_limit, minimum_forcing_depth) type(ocean_grid_type), intent(in) :: G !< The ocean's grid structure. type(verticalGrid_type), intent(in) :: GV !< The ocean's vertical grid structure. real, dimension(SZI_(G),SZJ_(G),SZK_(GV)), intent(in) :: h_old !< Layer thickness before entrainment @@ -431,6 +432,8 @@ subroutine call_tracer_column_fns(h_old, h_new, ea, eb, fluxes, Hml, dt, G, GV, !! a previous call to !! call_tracer_register. logical, intent(in) :: debug !< If true calculate checksums + type(KPP_CS), optional, pointer :: KPP_CSp !< KPP control structure + real, optional, intent(in) :: nonLocalTrans(:,:,:) !< Non-local transport [nondim] real, optional, intent(in) :: evap_CFL_limit !< Limit on the fraction of !! the water that can be fluxed out !! of the top layer in a timestep [nondim] @@ -490,6 +493,8 @@ subroutine call_tracer_column_fns(h_old, h_new, ea, eb, fluxes, Hml, dt, G, GV, if (CS%use_CFC_cap) & call CFC_cap_column_physics(h_old, h_new, ea, eb, fluxes, dt, & G, GV, US, CS%CFC_cap_CSp, & + KPP_CSp=KPP_CSp, & + nonLocalTrans=nonLocalTrans, & evap_CFL_limit=evap_CFL_limit, & minimum_forcing_depth=minimum_forcing_depth) if (CS%use_MOM_generic_tracer) then @@ -503,7 +508,10 @@ subroutine call_tracer_column_fns(h_old, h_new, ea, eb, fluxes, Hml, dt, G, GV, endif if (CS%use_pseudo_salt_tracer) & call pseudo_salt_tracer_column_physics(h_old, h_new, ea, eb, fluxes, dt, & - G, GV, US, CS%pseudo_salt_tracer_CSp, tv, debug, & + G, GV, US, CS%pseudo_salt_tracer_CSp, tv, & + debug, & + KPP_CSp=KPP_CSp, & + nonLocalTrans=nonLocalTrans, & evap_CFL_limit=evap_CFL_limit, & minimum_forcing_depth=minimum_forcing_depth) if (CS%use_boundary_impulse_tracer) & @@ -551,7 +559,9 @@ subroutine call_tracer_column_fns(h_old, h_new, ea, eb, fluxes, Hml, dt, G, GV, G, GV, US, CS%OCMIP2_CFC_CSp) if (CS%use_CFC_cap) & call CFC_cap_column_physics(h_old, h_new, ea, eb, fluxes, dt, & - G, GV, US, CS%CFC_cap_CSp) + G, GV, US, CS%CFC_cap_CSp, & + KPP_CSp=KPP_CSp, & + nonLocalTrans=nonLocalTrans) if (CS%use_MOM_generic_tracer) then if (US%QRZ_T_to_W_m2 /= 1.0) call MOM_error(FATAL, "MOM_generic_tracer_column_physics "//& "has not been written to permit dimensionsal rescaling. Set all 4 of the "//& @@ -561,7 +571,10 @@ subroutine call_tracer_column_fns(h_old, h_new, ea, eb, fluxes, Hml, dt, G, GV, endif if (CS%use_pseudo_salt_tracer) & call pseudo_salt_tracer_column_physics(h_old, h_new, ea, eb, fluxes, dt, & - G, GV, US, CS%pseudo_salt_tracer_CSp, tv, debug) + G, GV, US, CS%pseudo_salt_tracer_CSp, & + tv, debug, & + KPP_CSp=KPP_CSp, & + nonLocalTrans=nonLocalTrans) if (CS%use_boundary_impulse_tracer) & call boundary_impulse_tracer_column_physics(h_old, h_new, ea, eb, fluxes, dt, & G, GV, US, CS%boundary_impulse_tracer_CSp, tv, debug) diff --git a/src/tracer/MOM_tracer_registry.F90 b/src/tracer/MOM_tracer_registry.F90 index 821ac6a3cd..e87ce64d4a 100644 --- a/src/tracer/MOM_tracer_registry.F90 +++ b/src/tracer/MOM_tracer_registry.F90 @@ -1,7 +1,7 @@ -!> This module contains the tracer_registry_type and the subroutines -!! that handle registration of tracers and related subroutines. -!! The primary subroutine, register_tracer, is called to indicate the -!! tracers advected and diffused. +!> This module contains subroutines that handle registration of tracers +!! and related subroutines. The primary subroutine, register_tracer, is +!! called to indicate the tracers advected and diffused. +!! It also makes public the types defined in MOM_tracer_types. module MOM_tracer_registry ! This file is part of MOM6. See LICENSE.md for the license. @@ -22,7 +22,7 @@ module MOM_tracer_registry use MOM_time_manager, only : time_type use MOM_unit_scaling, only : unit_scale_type use MOM_verticalGrid, only : verticalGrid_type - +use MOM_tracer_types, only : tracer_type, tracer_registry_type implicit none ; private #include @@ -34,132 +34,19 @@ module MOM_tracer_registry public tracer_registry_init, lock_tracer_registry, tracer_registry_end public tracer_name_lookup -!> The tracer type -type, public :: tracer_type - - real, dimension(:,:,:), pointer :: t => NULL() !< tracer concentration array [conc] -! real :: OBC_inflow_conc= 0.0 !< tracer concentration for generic inflows [conc] -! real, dimension(:,:,:), pointer :: OBC_in_u => NULL() !< structured values for flow into the domain -! !! specified in OBCs through u-face of cell -! real, dimension(:,:,:), pointer :: OBC_in_v => NULL() !< structured values for flow into the domain -! !! specified in OBCs through v-face of cell - - real, dimension(:,:,:), pointer :: ad_x => NULL() !< diagnostic array for x-advective tracer flux - !! [conc H L2 T-1 ~> conc m3 s-1 or conc kg s-1] - real, dimension(:,:,:), pointer :: ad_y => NULL() !< diagnostic array for y-advective tracer flux - !! [conc H L2 T-1 ~> conc m3 s-1 or conc kg s-1] - real, dimension(:,:), pointer :: ad2d_x => NULL() !< diagnostic vertical sum x-advective tracer flux - !! [conc H L2 T-1 ~> conc m3 s-1 or conc kg s-1] - real, dimension(:,:), pointer :: ad2d_y => NULL() !< diagnostic vertical sum y-advective tracer flux - !! [conc H L2 T-1 ~> conc m3 s-1 or conc kg s-1] - - real, dimension(:,:,:), pointer :: df_x => NULL() !< diagnostic array for x-diffusive tracer flux - !! [conc H L2 T-1 ~> conc m3 s-1 or conc kg s-1] - real, dimension(:,:,:), pointer :: df_y => NULL() !< diagnostic array for y-diffusive tracer flux - !! [conc H L2 T-1 ~> conc m3 s-1 or conc kg s-1] - real, dimension(:,:,:), pointer :: lbd_dfx => NULL() !< diagnostic array for x-diffusive tracer flux - !! [conc H L2 T-1 ~> conc m3 s-1 or conc kg s-1] - real, dimension(:,:,:), pointer :: lbd_dfy => NULL() !< diagnostic array for y-diffusive tracer flux - !! [conc H L2 T-1 ~> conc m3 s-1 or conc kg s-1] - real, dimension(:,:), pointer :: lbd_dfx_2d => NULL() !< diagnostic array for x-diffusive tracer flux - !! [conc H L2 T-1 ~> conc m3 s-1 or conc kg s-1] - real, dimension(:,:), pointer :: lbd_dfy_2d => NULL() !< diagnostic array for y-diffusive tracer flux - !! [conc H L2 T-1 ~> conc m3 s-1 or conc kg s-1] - !### These two arrays may be allocated but are never used. - real, dimension(:,:), pointer :: lbd_bulk_df_x => NULL() !< diagnostic array for x-diffusive tracer flux - !! [conc H L2 T-1 ~> conc m3 s-1 or conc kg s-1] - real, dimension(:,:), pointer :: lbd_bulk_df_y => NULL() !< diagnostic array for y-diffusive tracer flux - !! [conc H L2 T-1 ~> conc m3 s-1 or conc kg s-1] - real, dimension(:,:), pointer :: df2d_x => NULL() !< diagnostic vertical sum x-diffusive flux - !! [conc H L2 T-1 ~> conc m3 s-1 or conc kg s-1] - real, dimension(:,:), pointer :: df2d_y => NULL() !< diagnostic vertical sum y-diffusive flux - !! [conc H L2 T-1 ~> conc m3 s-1 or conc kg s-1] -! real, dimension(:,:), pointer :: df2d_conc_x => NULL() !< diagnostic vertical sum x-diffusive content flux -! !! [conc H L2 T-1 ~> conc m3 s-1 or conc kg s-1] -! real, dimension(:,:), pointer :: df2d_conc_y => NULL() !< diagnostic vertical sum y-diffusive content flux -! !! [conc H L2 T-1 ~> conc m3 s-1 or conc kg s-1] - - real, dimension(:,:,:), pointer :: advection_xy => NULL() !< convergence of lateral advective tracer fluxes - !! [conc H T-1 ~> conc m s-1 or conc kg m-2 s-1] -! real, dimension(:,:,:), pointer :: diff_cont_xy => NULL() !< convergence of lateral diffusive tracer fluxes -! !! [conc H T-1 ~> conc m s-1 or conc kg m-2 s-1] -! real, dimension(:,:,:), pointer :: diff_conc_xy => NULL() !< convergence of lateral diffusive tracer fluxes -! !! expressed as a change in concentration -! !! [conc T-1 ~> conc s-1] - real, dimension(:,:,:), pointer :: t_prev => NULL() !< tracer concentration array at a previous - !! timestep used for diagnostics [conc] - real, dimension(:,:,:), pointer :: Trxh_prev => NULL() !< layer integrated tracer concentration array - !! at a previous timestep used for diagnostics - !! [conc H ~> conc m or conc kg m-2] - - character(len=32) :: name !< tracer name used for diagnostics and error messages - character(len=64) :: units !< Physical dimensions of the tracer concentration - character(len=240) :: longname !< Long name of the variable -! type(vardesc), pointer :: vd => NULL() !< metadata describing the tracer - logical :: registry_diags = .false. !< If true, use the registry to set up the - !! diagnostics associated with this tracer. - character(len=64) :: cmor_name !< CMOR name of this tracer - character(len=64) :: cmor_units !< CMOR physical dimensions of the tracer - character(len=240) :: cmor_longname !< CMOR long name of the tracer - character(len=32) :: flux_nameroot = "" !< Short tracer name snippet used construct the - !! names of flux diagnostics. - character(len=64) :: flux_longname = "" !< A word or phrase used construct the long - !! names of flux diagnostics. - real :: flux_scale = 1.0 !< A scaling factor used to convert the fluxes - !! of this tracer to its desired units, - !! including a factor compensating for H scaling. - character(len=48) :: flux_units = "" !< The units for fluxes of this variable. - character(len=48) :: conv_units = "" !< The units for the flux convergence of this tracer. - real :: conv_scale = 1.0 !< A scaling factor used to convert the flux - !! convergence of this tracer to its desired units, - !! including a factor compensating for H scaling. - character(len=48) :: cmor_tendprefix = "" !< The CMOR variable prefix for tendencies of this - !! tracer, required because CMOR does not follow any - !! discernable pattern for these names. - integer :: ind_tr_squared = -1 !< The tracer registry index for the square of this tracer - - !### THESE CAPABILITIES HAVE NOT YET BEEN IMPLEMENTED. - ! logical :: advect_tr = .true. !< If true, this tracer should be advected - ! logical :: hordiff_tr = .true. !< If true, this tracer should experience epineutral diffusion - logical :: remap_tr = .true. !< If true, this tracer should be vertically remapped - - integer :: diag_form = 1 !< An integer indicating which template is to be used to label diagnostics. - !>@{ Diagnostic IDs - integer :: id_tr = -1, id_tr_post_horzn = -1 - integer :: id_adx = -1, id_ady = -1, id_dfx = -1, id_dfy = -1 - integer :: id_lbd_bulk_dfx = -1, id_lbd_bulk_dfy = -1, id_lbd_dfx = -1, id_lbd_dfy = -1 - integer :: id_lbd_dfx_2d = -1 , id_lbd_dfy_2d = -1 - integer :: id_adx_2d = -1, id_ady_2d = -1, id_dfx_2d = -1, id_dfy_2d = -1 - integer :: id_adv_xy = -1, id_adv_xy_2d = -1 - integer :: id_dfxy_cont = -1, id_dfxy_cont_2d = -1, id_dfxy_conc = -1 - integer :: id_lbdxy_cont = -1, id_lbdxy_cont_2d = -1, id_lbdxy_conc = -1 - integer :: id_remap_conc = -1, id_remap_cont = -1, id_remap_cont_2d = -1 - integer :: id_tendency = -1, id_trxh_tendency = -1, id_trxh_tendency_2d = -1 - integer :: id_tr_vardec = -1 - !>@} -end type tracer_type - -!> Type to carry basic tracer information -type, public :: tracer_registry_type - integer :: ntr = 0 !< number of registered tracers - type(tracer_type) :: Tr(MAX_FIELDS_) !< array of registered tracers -! type(diag_ctrl), pointer :: diag !< structure to regulate timing of diagnostics - logical :: locked = .false. !< New tracers may be registered if locked=.false. - !! When locked=.true., no more tracers can be registered, - !! at which point common diagnostics can be set up - !! for the registered tracers -end type tracer_registry_type +! These types come from MOM_tracer_types +public tracer_type, tracer_registry_type contains !> This subroutine registers a tracer to be advected and laterally diffused. subroutine register_tracer(tr_ptr, Reg, param_file, HI, GV, name, longname, units, & - cmor_name, cmor_units, cmor_longname, tr_desc, & - OBC_inflow, OBC_in_u, OBC_in_v, ad_x, ad_y, df_x, df_y, & - ad_2d_x, ad_2d_y, df_2d_x, df_2d_y, advection_xy, registry_diags, & + cmor_name, cmor_units, cmor_longname, net_surfflux_name, NLT_budget_name, & + net_surfflux_longname, tr_desc, OBC_inflow, OBC_in_u, OBC_in_v, ad_x, ad_y, & + df_x, df_y, ad_2d_x, ad_2d_y, df_2d_x, df_2d_y, advection_xy, registry_diags, & flux_nameroot, flux_longname, flux_units, flux_scale, & convergence_units, convergence_scale, cmor_tendprefix, diag_form, & - restart_CS, mandatory) + restart_CS, mandatory, Tr_out) type(hor_index_type), intent(in) :: HI !< horizontal index type type(verticalGrid_type), intent(in) :: GV !< ocean vertical grid structure type(tracer_registry_type), pointer :: Reg !< pointer to the tracer registry @@ -172,6 +59,9 @@ subroutine register_tracer(tr_ptr, Reg, param_file, HI, GV, name, longname, unit character(len=*), optional, intent(in) :: cmor_name !< CMOR name character(len=*), optional, intent(in) :: cmor_units !< CMOR physical dimensions of variable character(len=*), optional, intent(in) :: cmor_longname !< CMOR long name + character(len=*), optional, intent(in) :: net_surfflux_name !< Name for net_surfflux diag + character(len=*), optional, intent(in) :: NLT_budget_name !< Name for NLT_budget diag + character(len=*), optional, intent(in) :: net_surfflux_longname !< Long name for net_surfflux diag type(vardesc), optional, intent(in) :: tr_desc !< A structure with metadata about the tracer real, optional, intent(in) :: OBC_inflow !< the tracer for all inflows via OBC for which OBC_in_u @@ -221,6 +111,7 @@ subroutine register_tracer(tr_ptr, Reg, param_file, HI, GV, name, longname, unit type(MOM_restart_CS), optional, intent(inout) :: restart_CS !< MOM restart control struct logical, optional, intent(in) :: mandatory !< If true, this tracer must be read !! from a restart file. + type(tracer_type), optional, pointer :: Tr_out !< If present, returns pointer into registry logical :: mand type(tracer_type), pointer :: Tr=>NULL() @@ -236,6 +127,7 @@ subroutine register_tracer(tr_ptr, Reg, param_file, HI, GV, name, longname, unit Reg%ntr = Reg%ntr + 1 Tr => Reg%Tr(Reg%ntr) + if (present(Tr_out)) Tr_out => Reg%Tr(Reg%ntr) if (present(name)) then Tr%name = name @@ -277,6 +169,22 @@ subroutine register_tracer(tr_ptr, Reg, param_file, HI, GV, name, longname, unit if (len_trim(flux_longname) > 0) Tr%flux_longname = flux_longname endif + Tr%net_surfflux_name = "KPP_net"//trim(Tr%name) + if (present(net_surfflux_name)) then + Tr%net_surfflux_name = net_surfflux_name + endif + + Tr%NLT_budget_name = 'KPP_NLT_'//trim(Tr%flux_nameroot)//'_budget' + if (present(NLT_budget_name)) then + Tr%NLT_budget_name = NLT_budget_name + endif + + Tr%net_surfflux_longname = 'Effective net surface '//trim(lowercase(Tr%flux_longname))//& + ' flux, as used by [CVMix] KPP' + if (present(net_surfflux_longname)) then + Tr%net_surfflux_longname = net_surfflux_longname + endif + Tr%flux_units = "" if (present(flux_units)) Tr%flux_units = flux_units @@ -340,7 +248,7 @@ end subroutine lock_tracer_registry !> register_tracer_diagnostics does a set of register_diag_field calls for any previously !! registered in a tracer registry with a value of registry_diags set to .true. -subroutine register_tracer_diagnostics(Reg, h, Time, diag, G, GV, US, use_ALE) +subroutine register_tracer_diagnostics(Reg, h, Time, diag, G, GV, US, use_ALE, use_KPP) type(ocean_grid_type), intent(in) :: G !< The ocean's grid structure type(verticalGrid_type), intent(in) :: GV !< The ocean's vertical grid structure type(unit_scale_type), intent(in) :: US !< A dimensional unit scaling type @@ -351,22 +259,25 @@ subroutine register_tracer_diagnostics(Reg, h, Time, diag, G, GV, US, use_ALE) type(diag_ctrl), intent(in) :: diag !< structure to regulate diagnostic output logical, intent(in) :: use_ALE !< If true active diagnostics that only !! apply to ALE configurations - - character(len=24) :: name ! A variable's name in a NetCDF file. - character(len=24) :: shortnm ! A shortened version of a variable's name for - ! creating additional diagnostics. - character(len=72) :: longname ! The long name of that tracer variable. - character(len=72) :: flux_longname ! The tracer name in the long names of fluxes. - character(len=48) :: units ! The dimensions of the tracer. - character(len=48) :: flux_units ! The units for fluxes, either - ! [units] m3 s-1 or [units] kg s-1. - character(len=48) :: conv_units ! The units for flux convergences, either - ! [units] m2 s-1 or [units] kg s-1. - character(len=48) :: unit2 ! The dimensions of the tracer squared + logical, intent(in) :: use_KPP !< If true active diagnostics that only + !! apply to CVMix KPP mixings + + character(len=24) :: name ! A variable's name in a NetCDF file. + character(len=24) :: shortnm ! A shortened version of a variable's name for + ! creating additional diagnostics. + character(len=72) :: longname ! The long name of that tracer variable. + character(len=72) :: flux_longname ! The tracer name in the long names of fluxes. + character(len=48) :: units ! The dimensions of the tracer. + character(len=48) :: flux_units ! The units for fluxes, either + ! [units] m3 s-1 or [units] kg s-1. + character(len=48) :: conv_units ! The units for flux convergences, either + ! [units] m2 s-1 or [units] kg s-1. + character(len=48) :: unit2 ! The dimensions of the tracer squared character(len=72) :: cmorname ! The CMOR name of this tracer. character(len=120) :: cmor_longname ! The CMOR long name of that variable. character(len=120) :: var_lname ! A temporary longname for a diagnostic. character(len=120) :: cmor_var_lname ! The temporary CMOR long name for a diagnostic + real :: conversion ! Temporary term while we address a bug type(tracer_type), pointer :: Tr=>NULL() integer :: i, j, k, is, ie, js, je, nz, m, m2, nTr_in integer :: isd, ied, jsd, jed, IsdB, IedB, JsdB, JedB @@ -660,6 +571,30 @@ subroutine register_tracer_diagnostics(Reg, h, Time, diag, G, GV, US, use_ALE) endif endif + ! KPP nonlocal term diagnostics + if (use_KPP) then + Tr%id_net_surfflux = register_diag_field('ocean_model', Tr%net_surfflux_name, diag%axesT1, Time, & + Tr%net_surfflux_longname, trim(units)//' m s-1', conversion=GV%H_to_m*US%s_to_T) + Tr%id_NLT_tendency = register_diag_field('ocean_model', "KPP_NLT_d"//trim(shortnm)//"dt", & + diag%axesTL, Time, & + trim(longname)//' tendency due to non-local transport of '//trim(lowercase(flux_longname))//& + ', as calculated by [CVMix] KPP', trim(units)//' s-1', conversion=US%s_to_T) + if (Tr%conv_scale == 0.001*GV%H_to_kg_m2) then + conversion = GV%H_to_kg_m2 + else + conversion = Tr%conv_scale + end if + ! We actually want conversion=Tr%conv_scale for all tracers, but introducing the local variable + ! 'conversion' and setting it to GV%H_to_kg_m2 instead of 0.001*GV%H_to_kg_m2 for salt tracers + ! keeps changes introduced by this refactoring limited to round-off level; as it turns out, + ! there is a bug in the code and the NLT budget term for salinity is off by a factor of 10^3 + ! so introducing the 0.001 here will fix that bug. + Tr%id_NLT_budget = register_diag_field('ocean_model', Tr%NLT_budget_name, & + diag%axesTL, Time, & + trim(flux_longname)//' content change due to non-local transport, as calculated by [CVMix] KPP', & + conv_units, conversion=conversion*US%s_to_T, v_extensive=.true.) + endif + endif ; enddo end subroutine register_tracer_diagnostics diff --git a/src/tracer/MOM_tracer_types.F90 b/src/tracer/MOM_tracer_types.F90 new file mode 100644 index 0000000000..4a474e9301 --- /dev/null +++ b/src/tracer/MOM_tracer_types.F90 @@ -0,0 +1,130 @@ +!> This module contains the tracer_type and tracer_registry_type +module MOM_tracer_types + +implicit none ; private + +#include + +!> The tracer type +type, public :: tracer_type + + real, dimension(:,:,:), pointer :: t => NULL() !< tracer concentration array [conc] +! real :: OBC_inflow_conc= 0.0 !< tracer concentration for generic inflows [conc] +! real, dimension(:,:,:), pointer :: OBC_in_u => NULL() !< structured values for flow into the domain +! !! specified in OBCs through u-face of cell +! real, dimension(:,:,:), pointer :: OBC_in_v => NULL() !< structured values for flow into the domain +! !! specified in OBCs through v-face of cell + + real, dimension(:,:,:), pointer :: ad_x => NULL() !< diagnostic array for x-advective tracer flux + !! [conc H L2 T-1 ~> conc m3 s-1 or conc kg s-1] + real, dimension(:,:,:), pointer :: ad_y => NULL() !< diagnostic array for y-advective tracer flux + !! [conc H L2 T-1 ~> conc m3 s-1 or conc kg s-1] + real, dimension(:,:), pointer :: ad2d_x => NULL() !< diagnostic vertical sum x-advective tracer flux + !! [conc H L2 T-1 ~> conc m3 s-1 or conc kg s-1] + real, dimension(:,:), pointer :: ad2d_y => NULL() !< diagnostic vertical sum y-advective tracer flux + !! [conc H L2 T-1 ~> conc m3 s-1 or conc kg s-1] + + real, dimension(:,:,:), pointer :: df_x => NULL() !< diagnostic array for x-diffusive tracer flux + !! [conc H L2 T-1 ~> conc m3 s-1 or conc kg s-1] + real, dimension(:,:,:), pointer :: df_y => NULL() !< diagnostic array for y-diffusive tracer flux + !! [conc H L2 T-1 ~> conc m3 s-1 or conc kg s-1] + real, dimension(:,:,:), pointer :: lbd_dfx => NULL() !< diagnostic array for x-diffusive tracer flux + !! [conc H L2 T-1 ~> conc m3 s-1 or conc kg s-1] + real, dimension(:,:,:), pointer :: lbd_dfy => NULL() !< diagnostic array for y-diffusive tracer flux + !! [conc H L2 T-1 ~> conc m3 s-1 or conc kg s-1] + real, dimension(:,:), pointer :: lbd_dfx_2d => NULL() !< diagnostic array for x-diffusive tracer flux + !! [conc H L2 T-1 ~> conc m3 s-1 or conc kg s-1] + real, dimension(:,:), pointer :: lbd_dfy_2d => NULL() !< diagnostic array for y-diffusive tracer flux + !! [conc H L2 T-1 ~> conc m3 s-1 or conc kg s-1] + !### These two arrays may be allocated but are never used. + real, dimension(:,:), pointer :: lbd_bulk_df_x => NULL() !< diagnostic array for x-diffusive tracer flux + !! [conc H L2 T-1 ~> conc m3 s-1 or conc kg s-1] + real, dimension(:,:), pointer :: lbd_bulk_df_y => NULL() !< diagnostic array for y-diffusive tracer flux + !! [conc H L2 T-1 ~> conc m3 s-1 or conc kg s-1] + real, dimension(:,:), pointer :: df2d_x => NULL() !< diagnostic vertical sum x-diffusive flux + !! [conc H L2 T-1 ~> conc m3 s-1 or conc kg s-1] + real, dimension(:,:), pointer :: df2d_y => NULL() !< diagnostic vertical sum y-diffusive flux + !! [conc H L2 T-1 ~> conc m3 s-1 or conc kg s-1] +! real, dimension(:,:), pointer :: df2d_conc_x => NULL() !< diagnostic vertical sum x-diffusive content flux +! !! [conc H L2 T-1 ~> conc m3 s-1 or conc kg s-1] +! real, dimension(:,:), pointer :: df2d_conc_y => NULL() !< diagnostic vertical sum y-diffusive content flux +! !! [conc H L2 T-1 ~> conc m3 s-1 or conc kg s-1] + + real, dimension(:,:,:), pointer :: advection_xy => NULL() !< convergence of lateral advective tracer fluxes + !! [conc H T-1 ~> conc m s-1 or conc kg m-2 s-1] +! real, dimension(:,:,:), pointer :: diff_cont_xy => NULL() !< convergence of lateral diffusive tracer fluxes +! !! [conc H T-1 ~> conc m s-1 or conc kg m-2 s-1] +! real, dimension(:,:,:), pointer :: diff_conc_xy => NULL() !< convergence of lateral diffusive tracer fluxes +! !! expressed as a change in concentration + !! [conc T-1 ~> conc s-1] + real, dimension(:,:,:), pointer :: t_prev => NULL() !< tracer concentration array at a previous + !! timestep used for diagnostics [conc] + real, dimension(:,:,:), pointer :: Trxh_prev => NULL() !< layer integrated tracer concentration array + !! at a previous timestep used for diagnostics + !! [conc H ~> conc m or conc kg m-2] + + character(len=32) :: name !< tracer name used for diagnostics and error messages + character(len=64) :: units !< Physical dimensions of the tracer concentration + character(len=240) :: longname !< Long name of the variable +! type(vardesc), pointer :: vd => NULL() !< metadata describing the tracer + logical :: registry_diags = .false. !< If true, use the registry to set up the + !! diagnostics associated with this tracer. + character(len=64) :: cmor_name !< CMOR name of this tracer + character(len=64) :: cmor_units !< CMOR physical dimensions of the tracer + character(len=240) :: cmor_longname !< CMOR long name of the tracer + character(len=32) :: flux_nameroot = "" !< Short tracer name snippet used construct the + !! names of flux diagnostics. + character(len=64) :: flux_longname = "" !< A word or phrase used construct the long + !! names of flux diagnostics. + real :: flux_scale = 1.0 !< A scaling factor used to convert the fluxes + !! of this tracer to its desired units, + !! including a factor compensating for H scaling. + character(len=48) :: flux_units = "" !< The units for fluxes of this variable. + character(len=48) :: conv_units = "" !< The units for the flux convergence of this tracer. + real :: conv_scale = 1.0 !< A scaling factor used to convert the flux + !! convergence of this tracer to its desired units, + !! including a factor compensating for H scaling. + character(len=48) :: cmor_tendprefix = "" !< The CMOR variable prefix for tendencies of this + !! tracer, required because CMOR does not follow any + !! discernable pattern for these names. + character(len=48) :: net_surfflux_name = "" !< Name to use for net_surfflux KPP diagnostic + character(len=48) :: NLT_budget_name = "" !< Name to use for NLT_budget KPP diagnostic + character(len=128) :: net_surfflux_longname = "" !< Long name to use for net_surfflux KPP diagnostic + integer :: ind_tr_squared = -1 !< The tracer registry index for the square of this tracer + + !### THESE CAPABILITIES HAVE NOT YET BEEN IMPLEMENTED. + ! logical :: advect_tr = .true. !< If true, this tracer should be advected + ! logical :: hordiff_tr = .true. !< If true, this tracer should experience epineutral diffusion + ! logical :: kpp_nonlocal_tr = .true. !< if true, apply KPP nonlocal transport to this tracer before diffusion + logical :: remap_tr = .true. !< If true, this tracer should be vertically remapped + + integer :: diag_form = 1 !< An integer indicating which template is to be used to label diagnostics. + !>@{ Diagnostic IDs + integer :: id_tr = -1, id_tr_post_horzn = -1 + integer :: id_adx = -1, id_ady = -1, id_dfx = -1, id_dfy = -1 + integer :: id_lbd_bulk_dfx = -1, id_lbd_bulk_dfy = -1, id_lbd_dfx = -1, id_lbd_dfy = -1 + integer :: id_lbd_dfx_2d = -1 , id_lbd_dfy_2d = -1 + integer :: id_adx_2d = -1, id_ady_2d = -1, id_dfx_2d = -1, id_dfy_2d = -1 + integer :: id_adv_xy = -1, id_adv_xy_2d = -1 + integer :: id_dfxy_cont = -1, id_dfxy_cont_2d = -1, id_dfxy_conc = -1 + integer :: id_lbdxy_cont = -1, id_lbdxy_cont_2d = -1, id_lbdxy_conc = -1 + integer :: id_remap_conc = -1, id_remap_cont = -1, id_remap_cont_2d = -1 + integer :: id_tendency = -1, id_trxh_tendency = -1, id_trxh_tendency_2d = -1 + integer :: id_tr_vardec = -1 + integer :: id_net_surfflux = -1, id_NLT_tendency = -1, id_NLT_budget = -1 + !>@} +end type tracer_type + +!> Type to carry basic tracer information +type, public :: tracer_registry_type + integer :: ntr = 0 !< number of registered tracers + type(tracer_type) :: Tr(MAX_FIELDS_) !< array of registered tracers +! type(diag_ctrl), pointer :: diag !< structure to regulate timing of diagnostics + logical :: locked = .false. !< New tracers may be registered if locked=.false. + !! When locked=.true., no more tracers can be registered, + !! at which point common diagnostics can be set up + !! for the registered tracers +end type tracer_registry_type + + +end module MOM_tracer_types diff --git a/src/tracer/pseudo_salt_tracer.F90 b/src/tracer/pseudo_salt_tracer.F90 index 6c22daa150..fe6b49d059 100644 --- a/src/tracer/pseudo_salt_tracer.F90 +++ b/src/tracer/pseudo_salt_tracer.F90 @@ -11,6 +11,7 @@ module pseudo_salt_tracer use MOM_file_parser, only : get_param, log_param, log_version, param_file_type use MOM_forcing_type, only : forcing use MOM_grid, only : ocean_grid_type +use MOM_CVMix_KPP, only : KPP_NonLocalTransport, KPP_CS use MOM_hor_index, only : hor_index_type use MOM_io, only : vardesc, var_desc, query_vardesc use MOM_open_boundary, only : ocean_OBC_type @@ -18,7 +19,7 @@ module pseudo_salt_tracer use MOM_spatial_means, only : global_mass_int_EFP use MOM_sponge, only : set_up_sponge_field, sponge_CS use MOM_time_manager, only : time_type -use MOM_tracer_registry, only : register_tracer, tracer_registry_type +use MOM_tracer_registry, only : register_tracer, tracer_registry_type, tracer_type use MOM_tracer_diabatic, only : tracer_vertdiff, applyTracerBoundaryFluxesInOut use MOM_tracer_Z_init, only : tracer_Z_init use MOM_unit_scaling, only : unit_scale_type @@ -35,6 +36,7 @@ module pseudo_salt_tracer !> The control structure for the pseudo-salt tracer type, public :: pseudo_salt_tracer_CS ; private + type(tracer_type), pointer :: tr_ptr !< pointer to tracer inside Tr_reg type(time_type), pointer :: Time => NULL() !< A pointer to the ocean model's clock. type(tracer_registry_type), pointer :: tr_Reg => NULL() !< A pointer to the MOM tracer registry real, pointer :: ps(:,:,:) => NULL() !< The array of pseudo-salt tracer used in this @@ -98,7 +100,7 @@ function register_pseudo_salt_tracer(HI, GV, param_file, CS, tr_Reg, restart_CS) call register_tracer(tr_ptr, tr_Reg, param_file, HI, GV, name="pseudo_salt", & longname="Pseudo salt passive tracer", units="psu", & registry_diags=.true., restart_CS=restart_CS, & - mandatory=.not.CS%pseudo_salt_may_reinit) + mandatory=.not.CS%pseudo_salt_may_reinit, Tr_out=CS%tr_ptr) CS%tr_Reg => tr_Reg CS%restart_CSp => restart_CS @@ -159,7 +161,7 @@ end subroutine initialize_pseudo_salt_tracer !> Apply sources, sinks and diapycnal diffusion to the tracers in this package. subroutine pseudo_salt_tracer_column_physics(h_old, h_new, ea, eb, fluxes, dt, G, GV, US, CS, tv, debug, & - evap_CFL_limit, minimum_forcing_depth) + KPP_CSp, nonLocalTrans, evap_CFL_limit, minimum_forcing_depth) type(ocean_grid_type), intent(in) :: G !< The ocean's grid structure type(verticalGrid_type), intent(in) :: GV !< The ocean's vertical grid structure real, dimension(SZI_(G),SZJ_(G),SZK_(GV)), & @@ -180,6 +182,8 @@ subroutine pseudo_salt_tracer_column_physics(h_old, h_new, ea, eb, fluxes, dt, G !! call to register_pseudo_salt_tracer type(thermo_var_ptrs), intent(in) :: tv !< A structure pointing to various thermodynamic variables logical, intent(in) :: debug !< If true calculate checksums + type(KPP_CS), optional, pointer :: KPP_CSp !< KPP control structure + real, optional, intent(in) :: nonLocalTrans(:,:,:) !< Non-local transport [nondim] real, optional, intent(in) :: evap_CFL_limit !< Limit on the fraction of the water that can !! be fluxed out of the top layer in a timestep [nondim] real, optional, intent(in) :: minimum_forcing_depth !< The smallest depth over which @@ -212,6 +216,14 @@ subroutine pseudo_salt_tracer_column_physics(h_old, h_new, ea, eb, fluxes, dt, G call hchksum(CS%ps,"pseudo_salt pre pseudo-salt vertdiff", G%HI) endif + ! Compute KPP nonlocal term if necessary + if (present(KPP_CSp)) then + if (associated(KPP_CSp) .and. present(nonLocalTrans)) & + call KPP_NonLocalTransport(KPP_CSp, G, GV, h_old, nonLocalTrans, fluxes%KPP_salt_flux(:,:), & + dt, CS%diag, CS%tr_ptr, CS%ps(:,:,:)) + endif + + ! This uses applyTracerBoundaryFluxesInOut, usually in ALE mode if (present(evap_CFL_limit) .and. present(minimum_forcing_depth)) then ! This option uses applyTracerBoundaryFluxesInOut, usually in ALE mode diff --git a/src/user/MOM_wave_interface.F90 b/src/user/MOM_wave_interface.F90 index ab8a693ba4..e6734b2ac7 100644 --- a/src/user/MOM_wave_interface.F90 +++ b/src/user/MOM_wave_interface.F90 @@ -12,12 +12,15 @@ module MOM_wave_interface use MOM_file_parser, only : get_param, log_version, param_file_type use MOM_forcing_type, only : mech_forcing use MOM_grid, only : ocean_grid_type +use MOM_hor_index, only : hor_index_type use MOM_io, only : file_exists, get_var_sizes, read_variable +use MOM_io, only : vardesc, var_desc use MOM_safe_alloc, only : safe_alloc_ptr use MOM_time_manager, only : time_type, operator(+), operator(/) use MOM_unit_scaling, only : unit_scale_type use MOM_variables, only : thermo_var_ptrs, surface use MOM_verticalgrid, only : verticalGrid_type +use MOM_restart, only : register_restart_field, MOM_restart_CS, query_initialized implicit none ; private @@ -31,6 +34,7 @@ module MOM_wave_interface ! called in step_mom. public get_Langmuir_Number ! Public interface to compute Langmuir number called from ! ePBL or KPP routines. +public Stokes_PGF ! Public interface to compute Stokes-shear induced pressure gradient force anomaly public StokesMixing ! NOT READY - Public interface to add down-Stokes gradient ! momentum mixing (e.g. the approach of Harcourt 2013/2015) public CoriolisStokes ! NOT READY - Public interface to add Coriolis-Stokes acceleration @@ -40,6 +44,7 @@ module MOM_wave_interface ! CL2 effects. public Waves_end ! public interface to deallocate and free wave related memory. public get_wave_method ! public interface to obtain the wave method string +public waves_register_restarts ! public interface to register wave restart fields ! A note on unit descriptions in comments: MOM6 uses units that can be rescaled for dimensional ! consistency testing. These are noted in comments with units like Z, H, L, and T, along with @@ -50,7 +55,15 @@ module MOM_wave_interface type, public :: wave_parameters_CS ; private ! Main surface wave options and publicly visible variables - logical, public :: UseWaves = .false. !< Flag to enable surface gravity wave feature + logical, public :: UseWaves = .false. !< Flag to enable surface gravity wave feature + logical, public :: Stokes_VF = .false. !< True if Stokes vortex force is used + logical, public :: Passive_Stokes_VF = .false. !< Computes Stokes VF, but doesn't affect dynamics + logical, public :: Stokes_PGF = .false. !< True if Stokes shear pressure Gradient force is used + logical, public :: Passive_Stokes_PGF = .false. !< Keeps Stokes_PGF on, but doesn't affect dynamics + logical, public :: Stokes_DDT = .false. !< Developmental: + !! True if Stokes d/dt is used + logical, public :: Passive_Stokes_DDT = .false. !< Keeps Stokes_DDT on, but doesn't affect dynamics + real, allocatable, dimension(:,:,:), public :: & Us_x !< 3d zonal Stokes drift profile [L T-1 ~> m s-1] !! Horizontal -> U points @@ -59,6 +72,30 @@ module MOM_wave_interface Us_y !< 3d meridional Stokes drift profile [L T-1 ~> m s-1] !! Horizontal -> V points !! Vertical -> Mid-points + real, allocatable, dimension(:,:,:), public :: & + ddt_Us_x !< 3d time tendency of zonal Stokes drift profile [m s-1] + !! Horizontal -> U points + !! Vertical -> Mid-points + real, allocatable, dimension(:,:,:), public :: & + ddt_Us_y !< 3d time tendency of meridional Stokes drift profile [m s-1] + !! Horizontal -> V points + !! Vertical -> Mid-points + real, allocatable, dimension(:,:,:), public :: & + Us_x_from_ddt !< Check of 3d zonal Stokes drift profile [m s-1] + !! Horizontal -> U points + !! Vertical -> Mid-points + real, allocatable, dimension(:,:,:), public :: & + Us_y_from_ddt !< Check of 3d meridional Stokes drift profile [m s-1] + !! Horizontal -> V points + !! Vertical -> Mid-points + real, allocatable, dimension(:,:,:), public :: & + Us_x_prev !< 3d zonal Stokes drift profile, previous dynamics call [m s-1] + !! Horizontal -> U points + !! Vertical -> Mid-points + real, allocatable, dimension(:,:,:), public :: & + Us_y_prev !< 3d meridional Stokes drift profile, previous dynamics call [m s-1] + !! Horizontal -> V points + !! Vertical -> Mid-points real, allocatable, dimension(:,:,:), public :: & KvS !< Viscosity for Stokes Drift shear [Z2 T-1 ~> m2 s-1] @@ -165,8 +202,12 @@ module MOM_wave_interface !! timing of diagnostic output. !>@{ Diagnostic handles + integer, public :: id_PFu_Stokes = -1 , id_PFv_Stokes = -1 + integer, public :: id_3dstokes_x_from_ddt = -1 , id_3dstokes_y_from_ddt = -1 + integer :: id_P_deltaStokes_L = -1, id_P_deltaStokes_i = -1 integer :: id_surfacestokes_x = -1 , id_surfacestokes_y = -1 integer :: id_3dstokes_x = -1 , id_3dstokes_y = -1 + integer :: id_ddt_3dstokes_x = -1 , id_ddt_3dstokes_y = -1 integer :: id_La_turb = -1 !>@} @@ -191,7 +232,7 @@ module MOM_wave_interface contains !> Initializes parameters related to MOM_wave_interface -subroutine MOM_wave_interface_init(time, G, GV, US, param_file, CS, diag ) +subroutine MOM_wave_interface_init(time, G, GV, US, param_file, CS, diag, restart_CSp) type(time_type), target, intent(in) :: Time !< Model time type(ocean_grid_type), intent(inout) :: G !< Grid structure type(verticalGrid_type), intent(in) :: GV !< Vertical grid structure @@ -199,6 +240,7 @@ subroutine MOM_wave_interface_init(time, G, GV, US, param_file, CS, diag ) type(param_file_type), intent(in) :: param_file !< Input parameter structure type(wave_parameters_CS), pointer :: CS !< Wave parameter control structure type(diag_ctrl), target, intent(inout) :: diag !< Diagnostic Pointer + type(MOM_restart_CS), optional, pointer:: restart_CSp!< Restart control structure ! Local variables character(len=40) :: mdl = "MOM_wave_interface" !< This module's name. @@ -212,8 +254,8 @@ subroutine MOM_wave_interface_init(time, G, GV, US, param_file, CS, diag ) logical :: StatisticalWaves ! Dummy Check - if (associated(CS)) then - call MOM_error(FATAL, "wave_interface_init called with an associated control structure.") + if (.not. associated(CS)) then + call MOM_error(FATAL, "wave_interface_init called without an associated control structure.") return endif @@ -226,9 +268,6 @@ subroutine MOM_wave_interface_init(time, G, GV, US, param_file, CS, diag ) if (.not.(use_waves .or. StatisticalWaves)) return - ! Allocate CS and set pointers - allocate(CS) - CS%UseWaves = use_waves CS%diag => diag CS%Time => Time @@ -277,6 +316,25 @@ subroutine MOM_wave_interface_init(time, G, GV, US, param_file, CS, diag ) call MOM_error(FATAL, "Should you be enabling Coriolis-Stokes? Code not ready.") endif + call get_param(param_file, mdl, "STOKES_VF", CS%Stokes_VF, & + "Flag to use Stokes vortex force", units="", & + Default=.false.) + call get_param(param_file, mdl, "PASSIVE_STOKES_VF", CS%Passive_Stokes_VF, & + "Flag to make Stokes vortex force diagnostic only.", units="", & + Default=.false.) + call get_param(param_file, mdl, "STOKES_PGF", CS%Stokes_PGF, & + "Flag to use Stokes-induced pressure gradient anomaly", units="", & + Default=.false.) + call get_param(param_file, mdl, "PASSIVE_STOKES_PGF", CS%Passive_Stokes_PGF, & + "Flag to make Stokes-induced pressure gradient anomaly diagnostic only.", units="", & + Default=.false.) + call get_param(param_file, mdl, "STOKES_DDT", CS%Stokes_DDT, & + "Flag to use Stokes d/dt", units="", & + Default=.false.) + call get_param(param_file, mdl, "PASSIVE_STOKES_DDT", CS%Passive_Stokes_DDT, & + "Flag to make Stokes d/dt diagnostic only", units="", & + Default=.false.) + ! Get Wave Method and write to integer WaveMethod call get_param(param_file,mdl,"WAVE_METHOD",TMPSTRING1, & "Choice of wave method, valid options include: \n"// & @@ -333,7 +391,7 @@ subroutine MOM_wave_interface_init(time, G, GV, US, param_file, CS, diag ) "or the model will fail.", default=1) allocate( CS%WaveNum_Cen(CS%NumBands), source=0.0 ) allocate( CS%STKx0(G%isdB:G%iedB,G%jsd:G%jed,CS%NumBands), source=0.0 ) - allocate( CS%STKy0(G%isdB:G%iedB,G%jsd:G%jed,CS%NumBands), source=0.0 ) + allocate( CS%STKy0(G%isd:G%ied,G%jsdB:G%jedB,CS%NumBands), source=0.0 ) CS%PartitionMode = 0 call get_param(param_file, mdl, "SURFBAND_WAVENUMBERS", CS%WaveNum_Cen, & "Central wavenumbers for surface Stokes drift bands.", & @@ -397,8 +455,16 @@ subroutine MOM_wave_interface_init(time, G, GV, US, param_file, CS, diag ) ! Allocate and initialize ! a. Stokes driftProfiles - allocate(CS%Us_x(G%isdB:G%IedB,G%jsd:G%jed,GV%ke), source=0.0) - allocate(CS%Us_y(G%isd:G%Ied,G%jsdB:G%jedB,GV%ke), source=0.0) + allocate(CS%Us_x(G%isdB:G%IedB,G%jsd:G%jed,G%ke), source=0.0) + allocate(CS%Us_y(G%isd:G%Ied,G%jsdB:G%jedB,G%ke), source=0.0) + if (CS%Stokes_DDT) then + !allocate(CS%Us_x_prev(G%isdB:G%IedB,G%jsd:G%jed,G%ke), source=0.0) + !allocate(CS%Us_y_prev(G%isd:G%Ied,G%jsdB:G%jedB,G%ke), source=0.0) + allocate(CS%ddt_Us_x(G%isdB:G%IedB,G%jsd:G%jed,G%ke), source=0.0) + allocate(CS%ddt_Us_y(G%isd:G%Ied,G%jsdB:G%jedB,G%ke), source=0.0) + allocate(CS%Us_x_from_ddt(G%isdB:G%IedB,G%jsd:G%jed,G%ke), source=0.0) + allocate(CS%Us_y_from_ddt(G%isd:G%Ied,G%jsdB:G%jedB,G%ke), source=0.0) + endif ! b. Surface Values allocate(CS%US0_x(G%isdB:G%iedB,G%jsd:G%jed), source=0.0) allocate(CS%US0_y(G%isd:G%ied,G%jsdB:G%jedB), source=0.0) @@ -419,6 +485,26 @@ subroutine MOM_wave_interface_init(time, G, GV, US, param_file, CS, diag ) CS%diag%axesCvL,Time,'3d Stokes drift (y)', 'm s-1', conversion=US%L_T_to_m_s) CS%id_3dstokes_x = register_diag_field('ocean_model','3d_stokes_x', & CS%diag%axesCuL,Time,'3d Stokes drift (x)', 'm s-1', conversion=US%L_T_to_m_s) + if (CS%Stokes_DDT) then + CS%id_ddt_3dstokes_y = register_diag_field('ocean_model','dvdt_Stokes', & + CS%diag%axesCvL,Time,'d/dt Stokes drift (meridional)','m s-2') + CS%id_ddt_3dstokes_x = register_diag_field('ocean_model','dudt_Stokes', & + CS%diag%axesCuL,Time,'d/dt Stokes drift (zonal)','m s-2') + CS%id_3dstokes_y_from_ddt = register_diag_field('ocean_model','3d_stokes_y_from_ddt', & + CS%diag%axesCvL,Time,'3d Stokes drift from ddt (y)', 'm s-1', conversion=US%L_T_to_m_s) + CS%id_3dstokes_x_from_ddt = register_diag_field('ocean_model','3d_stokes_x_from_ddt', & + CS%diag%axesCuL,Time,'3d Stokes drift from ddt (x)', 'm s-1', conversion=US%L_T_to_m_s) + endif + CS%id_PFv_Stokes = register_diag_field('ocean_model','PFv_Stokes', & + CS%diag%axesCvL,Time,'PF from Stokes drift (meridional)','m s-2',conversion=US%L_T2_to_m_s2) + CS%id_PFu_Stokes = register_diag_field('ocean_model','PFu_Stokes', & + CS%diag%axesCuL,Time,'PF from Stokes drift (zonal)','m s-2',conversion=US%L_T2_to_m_s2) + CS%id_P_deltaStokes_i = register_diag_field('ocean_model','P_deltaStokes_i', & + CS%diag%axesTi,Time,'Interfacial pressure anomaly from Stokes drift used in PFu_Stokes',& + 'm2 s-2',conversion=US%L_T_to_m_s**2) + CS%id_P_deltaStokes_L = register_diag_field('ocean_model','P_deltaStokes_L', & + CS%diag%axesTL,Time,'Layer averaged pressure anomaly from Stokes drift used in PFu_Stokes',& + 'm2 s-2',conversion=US%L_T_to_m_s**2) CS%id_La_turb = register_diag_field('ocean_model','La_turbulent', & CS%diag%axesT1,Time,'Surface (turbulent) Langmuir number','nondim') @@ -449,26 +535,28 @@ subroutine query_wave_properties(CS, NumBands, WaveNumbers, US) end subroutine query_wave_properties !> Subroutine that handles updating of surface wave/Stokes drift related properties -subroutine Update_Surface_Waves(G, GV, US, Day, dt, CS, forces) +subroutine Update_Surface_Waves(G, GV, US, Time_present, dt, CS, forces) type(wave_parameters_CS), pointer :: CS !< Wave parameter Control structure type(ocean_grid_type), intent(inout) :: G !< Grid structure type(verticalGrid_type), intent(in) :: GV !< Vertical grid structure type(unit_scale_type), intent(in) :: US !< A dimensional unit scaling type - type(time_type), intent(in) :: Day !< Current model time - type(time_type), intent(in) :: dt !< Timestep as a time-type + type(time_type), intent(in) :: Time_present !< Model Time + type(time_type), intent(in) :: dt !< Time increment as a time-type type(mech_forcing), intent(in), optional :: forces !< MOM_forcing_type ! Local variables + type(time_type) :: Stokes_Time integer :: ii, jj, b - type(time_type) :: Day_Center - - ! Computing central time of time step - Day_Center = Day + DT/2 if (CS%WaveMethod == TESTPROF) then ! Do nothing elseif (CS%WaveMethod == SURFBANDS) then if (CS%DataSource == DATAOVR) then - call Surface_Bands_by_data_override(day_center, G, GV, US, CS) + ! Updating Stokes drift time to center of time increment. + ! This choice makes sense for the thermodynamics, but for the + ! dynamics it may be more useful to update to the end of the + ! time increment. + Stokes_Time = Time_present + dt/2 + call Surface_Bands_by_data_override(Stokes_Time, G, GV, US, CS) elseif (CS%DataSource == COUPLER) then if (.not.present(FORCES)) then call MOM_error(FATAL,"The option SURFBAND = COUPLER can not be used with "//& @@ -517,7 +605,7 @@ end subroutine Update_Surface_Waves !> Constructs the Stokes Drift profile on the model grid based on !! desired coupling options -subroutine Update_Stokes_Drift(G, GV, US, CS, h, ustar) +subroutine Update_Stokes_Drift(G, GV, US, CS, h, ustar, dt, dynamics_step) type(wave_parameters_CS), pointer :: CS !< Wave parameter Control structure type(ocean_grid_type), intent(inout) :: G !< Grid structure type(verticalGrid_type), intent(in) :: GV !< Vertical grid structure @@ -526,6 +614,9 @@ subroutine Update_Stokes_Drift(G, GV, US, CS, h, ustar) intent(in) :: h !< Thickness [H ~> m or kg m-2] real, dimension(SZI_(G),SZJ_(G)), & intent(in) :: ustar !< Wind friction velocity [Z T-1 ~> m s-1]. + real, intent(in) :: dt !< Time-step for computing Stokes-tendency + logical, intent(in) :: dynamics_step !< True if this call is on a dynamics step + ! Local Variables real :: Top, MidPoint, Bottom ! Positions within the layer [Z ~> m] real :: one_cm ! One centimeter in the units of wavelengths [Z ~> m] @@ -538,17 +629,25 @@ subroutine Update_Stokes_Drift(G, GV, US, CS, h, ustar) real :: PI ! 3.1415926535... real :: La ! The local Langmuir number [nondim] integer :: ii, jj, kk, b, iim1, jjm1 + real :: idt ! 1 divided by the time step + + if (CS%WaveMethod==EFACTOR) return one_cm = 0.01*US%m_to_Z min_level_thick_avg = 1.e-3*US%m_to_Z + idt = 1.0/dt + + if (allocated(CS%US_x) .and. allocated(CS%US_y)) then + call pass_vector(CS%US_x(:,:,:),CS%US_y(:,:,:), G%Domain) + endif ! 1. If Test Profile Option is chosen ! Computing mid-point value from surface value and decay wavelength if (CS%WaveMethod==TESTPROF) then PI = 4.0*atan(1.0) DecayScale = 4.*PI / CS%TP_WVL !4pi - do jj = G%jsd,G%jed - do II = G%isdB,G%iedB + do jj = G%jsc,G%jec + do II = G%iscB,G%iecB IIm1 = max(1,II-1) Bottom = 0.0 MidPoint = 0.0 @@ -560,8 +659,8 @@ subroutine Update_Stokes_Drift(G, GV, US, CS, h, ustar) enddo enddo enddo - do JJ = G%jsdB,G%jedB - do ii = G%isd,G%ied + do JJ = G%jscB,G%jecB + do ii = G%isc,G%iec JJm1 = max(1,JJ-1) Bottom = 0.0 MidPoint = 0.0 @@ -573,6 +672,7 @@ subroutine Update_Stokes_Drift(G, GV, US, CS, h, ustar) enddo enddo enddo + call pass_vector(CS%Us_x(:,:,:),CS%Us_y(:,:,:), G%Domain, To_All) ! 2. If Surface Bands is chosen ! In wavenumber mode compute integral for layer averaged Stokes drift. ! In frequency mode compuate value at midpoint. @@ -582,23 +682,12 @@ subroutine Update_Stokes_Drift(G, GV, US, CS, h, ustar) CS%Us0_x(:,:) = 0.0 CS%Us0_y(:,:) = 0.0 ! Computing X direction Stokes drift - do jj = G%jsd,G%jed - do II = G%isdB,G%iedB + do jj = G%jsc,G%jec + do II = G%iscB,G%iecB ! 1. First compute the surface Stokes drift - ! by integrating over the partitions. + ! by summing over the partitions. do b = 1,CS%NumBands - if (CS%PartitionMode==0) then - ! In wavenumber we are averaging over (small) level - CMN_FAC = (1.0-exp(-one_cm*2.*CS%WaveNum_Cen(b))) / & - (one_cm*2.*CS%WaveNum_Cen(b)) - !### For accuracy and numerical stability rewrite this as: - ! CMN_FAC = one_minus_exp_x(2.*CS%WaveNum_Cen(b)*one_cm) - ! or maybe just take the limit of vanishing thickness, CMN_FAC = 1.0 - elseif (CS%PartitionMode==1) then - ! In frequency we are not averaging over level and taking top - CMN_FAC = 1.0 - endif - CS%US0_x(II,jj) = CS%US0_x(II,jj) + CS%STKx0(II,jj,b)*CMN_FAC + CS%US0_x(II,jj) = CS%US0_x(II,jj) + CS%STKx0(II,jj,b) enddo ! 2. Second compute the level averaged Stokes drift bottom = 0.0 @@ -646,22 +735,11 @@ subroutine Update_Stokes_Drift(G, GV, US, CS, h, ustar) enddo enddo ! Computing Y direction Stokes drift - do JJ = G%jsdB,G%jedB - do ii = G%isd,G%ied - ! Compute the surface values. + do JJ = G%jscB,G%jecB + do ii = G%isc,G%iec + ! Set the surface value to that at z=0 do b = 1,CS%NumBands - if (CS%PartitionMode==0) then - ! In wavenumber we are averaging over (small) level - CMN_FAC = (1.0-exp(-one_cm*2.*CS%WaveNum_Cen(b))) / & - (one_cm*2.*CS%WaveNum_Cen(b)) - !### For accuracy and numerical stability rewrite this as: - ! CMN_FAC = one_minus_exp_x(2.*CS%WaveNum_Cen(b)*one_cm) - ! or maybe just take the limit of vanishing thickness, CMN_FAC = 1.0 - elseif (CS%PartitionMode==1) then - ! In frequency we are not averaging over level and taking top - CMN_FAC = 1.0 - endif - CS%US0_y(ii,JJ) = CS%US0_y(ii,JJ) + CS%STKy0(ii,JJ,b)*CMN_FAC + CS%US0_y(ii,JJ) = CS%US0_y(ii,JJ) + CS%STKy0(ii,JJ,b) enddo ! Compute the level averages. bottom = 0.0 @@ -708,10 +786,12 @@ subroutine Update_Stokes_Drift(G, GV, US, CS, h, ustar) enddo enddo enddo + call pass_vector(CS%Us_x(:,:,:),CS%Us_y(:,:,:), G%Domain, To_All) + call pass_vector(CS%Us0_x(:,:),CS%Us0_y(:,:), G%Domain) elseif (CS%WaveMethod == DHH85) then if (.not.(CS%StaticWaves .and. CS%DHH85_is_set)) then - do jj = G%jsd,G%jed - do II = G%isdB,G%iedB + do jj = G%jsc,G%jec + do II = G%iscB,G%iecB bottom = 0.0 do kk = 1,GV%ke Top = Bottom @@ -728,8 +808,8 @@ subroutine Update_Stokes_Drift(G, GV, US, CS, h, ustar) enddo enddo enddo - do JJ = G%jsdB,G%jedB - do ii = G%isd,G%ied + do JJ = G%jscB,G%jecB + do ii = G%isc,G%iec Bottom = 0.0 do kk=1, GV%ke Top = Bottom @@ -752,8 +832,7 @@ subroutine Update_Stokes_Drift(G, GV, US, CS, h, ustar) enddo CS%DHH85_is_set = .true. endif - elseif (CS%WaveMethod==EFACTOR) then - return ! pass + call pass_vector(CS%Us_x(:,:,:),CS%Us_y(:,:,:), G%Domain) else! Keep this else, fallback to 0 Stokes drift do kk= 1,GV%ke do jj = G%jsd,G%jed @@ -781,6 +860,15 @@ subroutine Update_Stokes_Drift(G, GV, US, CS, h, ustar) enddo enddo + ! Finding tendency of Stokes drift over the time step to apply + ! as an acceleration to the models current. + if ( dynamics_step .and. CS%Stokes_DDT ) then + CS%ddt_us_x(:,:,:) = (CS%US_x(:,:,:) - CS%US_x_prev(:,:,:)) * idt + CS%ddt_us_y(:,:,:) = (CS%US_y(:,:,:) - CS%US_y_prev(:,:,:)) * idt + CS%US_x_prev(:,:,:) = CS%US_x(:,:,:) + CS%US_y_prev(:,:,:) = CS%US_y(:,:,:) + endif + ! Output any desired quantities if (CS%id_surfacestokes_y>0) & call post_data(CS%id_surfacestokes_y, CS%us0_y, CS%diag) @@ -790,6 +878,16 @@ subroutine Update_Stokes_Drift(G, GV, US, CS, h, ustar) call post_data(CS%id_3dstokes_y, CS%us_y, CS%diag) if (CS%id_3dstokes_x>0) & call post_data(CS%id_3dstokes_x, CS%us_x, CS%diag) + if (CS%Stokes_DDT) then + if (CS%id_ddt_3dstokes_x>0) & + call post_data(CS%id_ddt_3dstokes_x, CS%ddt_us_x, CS%diag) + if (CS%id_ddt_3dstokes_y>0) & + call post_data(CS%id_ddt_3dstokes_y, CS%ddt_us_y, CS%diag) + if (CS%id_3dstokes_x_from_ddt>0) & + call post_data(CS%id_3dstokes_x_from_ddt, CS%us_x_from_ddt, CS%diag) + if (CS%id_3dstokes_y_from_ddt>0) & + call post_data(CS%id_3dstokes_y_from_ddt, CS%us_y_from_ddt, CS%diag) + endif if (CS%id_La_turb>0) & call post_data(CS%id_La_turb, CS%La_turb, CS%diag) @@ -809,8 +907,8 @@ end function one_minus_exp_x !> A subroutine to fill the Stokes drift from a NetCDF file !! using the data_override procedures. -subroutine Surface_Bands_by_data_override(day_center, G, GV, US, CS) - type(time_type), intent(in) :: day_center !< Center of timestep +subroutine Surface_Bands_by_data_override(Time, G, GV, US, CS) + type(time_type), intent(in) :: Time !< Time to get Stokes drift bands type(wave_parameters_CS), pointer :: CS !< Wave structure type(ocean_grid_type), intent(inout) :: G !< Grid structure type(verticalGrid_type), intent(in) :: GV !< Vertical grid structure @@ -869,7 +967,7 @@ subroutine Surface_Bands_by_data_override(day_center, G, GV, US, CS) PI = 4.0*atan(1.0) call read_variable(CS%SurfBandFileName, dim_name(1), CS%Freq_Cen, scale=2.*PI*US%T_to_s) - do B = 1,CS%NumBands + do b = 1,CS%NumBands CS%WaveNum_Cen(b) = CS%Freq_Cen(b)**2 / CS%g_Earth enddo endif @@ -887,10 +985,10 @@ subroutine Surface_Bands_by_data_override(day_center, G, GV, US, CS) temp_y(:,:) = 0.0 varname = ' ' write(varname, "(A3,I0)") 'Usx', b - call data_override('OCN', trim(varname), temp_x, day_center) + call data_override('OCN', trim(varname), temp_x, Time) varname = ' ' write(varname, "(A3,I0)") 'Usy', b - call data_override('OCN', trim(varname), temp_y, day_center) + call data_override('OCN', trim(varname), temp_y, Time) ! Update halo on h-grid call pass_vector(temp_x, temp_y, G%Domain, To_All, AGRID) ! Filter land values @@ -1454,6 +1552,266 @@ subroutine CoriolisStokes(G, GV, dt, h, u, v, Waves) enddo end subroutine CoriolisStokes +!> Computes tendency due to Stokes pressure gradient force anomaly +!! including analytical integration of Stokes shear using multiple-exponential decay +!! Stokes drift profile and vertical integration of the resulting pressure +!! anomaly to the total pressure gradient force +subroutine Stokes_PGF(G, GV, h, u, v, PFu_Stokes, PFv_Stokes, CS ) + type(ocean_grid_type), & + intent(in) :: G !< Ocean grid + type(verticalGrid_type), & + intent(in) :: GV !< Ocean vertical grid + real, dimension(SZI_(G),SZJ_(G),SZK_(G)),& + intent(in) :: h !< Layer thicknesses [H ~> m or kg m-2] + real, dimension(SZIB_(G),SZJ_(G),SZK_(G)), & + intent(in) :: u !< Lagrangian Velocity i-component [m s-1] + real, dimension(SZI_(G),SZJB_(G),SZK_(G)), & + intent(in) :: v !< Lagrangian Velocity j-component [m s-1] + real, dimension(SZIB_(G),SZJ_(G),SZK_(G)), & + intent(out) :: PFu_Stokes !< PGF Stokes-shear i-component [L T-2] + real, dimension(SZI_(G),SZJB_(G),SZK_(G)), & + intent(out) :: PFv_Stokes !< PGF Stokes-shear j-component [m s-1] + type(Wave_parameters_CS), & + pointer :: CS !< Surface wave related control structure. + + ! Local variables + real, dimension(SZI_(G),SZJ_(G),SZK_(G)) :: P_deltaStokes_L ! The stokes induced Pressure anomaly, layer averaged + real, dimension(SZI_(G),SZJ_(G),SZK_(G)+1) :: P_deltaStokes_i ! The stokes induced Pressure anomaly at interfaces + real :: P_Stokes_l, P_Stokes_r ! Stokes-induced pressure anomaly over layer (left/right of point) [L2 T-2 ~> m2 s-2] + real :: P_Stokes_l0, P_Stokes_r0 ! Stokes-induced pressure anomaly at interface + ! (left/right of point) [L2 T-2 ~> m2 s-2] + real :: dP_Stokes_l_dz, dP_Stokes_r_dz ! Contribution of layer to integrated Stokes pressure anomaly for summation + ! (left/right of point) [L3 T-2 ~> m3 s-2] + real :: dP_Stokes_l, dP_Stokes_r ! Net increment of Stokes pressure anomaly across layer for summation + ! (left/right of point) [L2 T-2 ~> m2 s-2] + real :: uE_l, uE_r, vE_l, vE_r ! Eulerian velocity components (left/right of point) [L T-1 ~> m s-1] + real :: uS0_l, uS0_r, vS0_l, vS0_r ! Surface Stokes velocity components (left/right of point) [L T-1 ~> m s-1] + real :: zi_l(SZK_(G)+1), zi_r(SZK_(G)+1) ! The height of the edges of the cells (left/right of point) [Z ~> m]. + real :: idz_l(SZK_(G)), idz_r(SZK_(G)) ! The inverse thickness of the cells (left/right of point) [Z-1 ~> m-1] + real :: h_l, h_r ! The thickness of the cell (left/right of point) [Z ~> m]. + real :: dexp2kzL,dexp4kzL,dexp2kzR,dexp4kzR ! Analytical evaluation of multi-exponential decay contribution + ! to Stokes pressure anomalies. + real :: TwoK, FourK, iTwoK, iFourK ! Wavenumber multipliers/inverses + + integer :: i,j,k,l + + !--------------------------------------------------------------- + ! Compute the Stokes contribution to the pressure gradient force + !--------------------------------------------------------------- + ! Notes on the algorithm/code: + ! This code requires computing velocities at bounding h points + ! of the u/v points to get the pressure-gradient. In this + ! implementation there are several redundant calculations as the + ! left/right points are computed at each cell while integrating + ! in the vertical, requiring about twice the calculations. The + ! velocities at the tracer points could be precomputed and + ! stored, but this would require more memory and cycling through + ! large 3d arrays while computing the pressures. This could be + ! explored as a way to speed up this code. + !--------------------------------------------------------------- + + PFu_Stokes(:,:,:) = 0.0 + PFv_Stokes(:,:,:) = 0.0 + if (CS%id_P_deltaStokes_i > 0) P_deltaStokes_i(:,:,:) = 0.0 + if (CS%id_P_deltaStokes_L > 0) P_deltaStokes_L(:,:,:) = 0.0 + + ! First compute PGFu. The Stokes-induced pressure anomaly diagnostic is stored from this calculation. + ! > Seeking PGFx at (I,j), meanining we need to compute pressure at h-points (i,j) and (i+1,j). + ! UL(i,j) -> found as average of I-1 & I on j + ! UR(i+1,j) -> found as average of I & I+1 on j + ! VL(i,j) -> found on i as average of J-1 & J + ! VR(i+1,j) -> found on i+1 as average of J-1 & J + ! + do j = G%jsc, G%jec ; do I = G%iscB, G%iecB + if (G%mask2dCu(I,j)>0.5) then + P_Stokes_l0 = 0.0 + P_Stokes_r0 = 0.0 + ! We don't need to precompute the grid in physical space arrays and could have done this during + ! the next loop, but this gives flexibility if the loop directions (integrations) are performed + ! upwards instead of downwards (it seems downwards is the better approach). + zi_l(1) = 0.0 + zi_r(1) = 0.0 + do k = 1, G%ke + h_l = h(i,j,k)*GV%H_to_Z + h_r = h(i+1,j,k)*GV%H_to_Z + zi_l(k+1) = zi_l(k) - h_l + zi_r(k+1) = zi_r(k) - h_r + Idz_l(k) = 1./max(0.1,h_l) + Idz_r(k) = 1./max(0.1,h_r) + enddo + do k = 1,G%ke + ! Computing (left/right) Eulerian velocities assuming the velocity passed to this routine is the + ! Lagrangian velocity. This requires the wave acceleration terms to be activated together. + uE_l = 0.5*((u(I-1,j,k)-CS%Us_x(I-1,j,k))*G%mask2dCu(I-1,j) + & + (u(I,j,k)-CS%Us_x(I-1,j,k))*G%mask2dCu(I,j)) + uE_r = 0.5*((u(I,j,k)-CS%Us_x(I,j,k))*G%mask2dCu(I,j) + & + (u(I+1,j,k)-CS%Us_x(I+1,j,k))*G%mask2dCu(I+1,j)) + vE_l = 0.5*((v(i,J-1,k)-CS%Us_y(i,J-1,k))*G%mask2dCv(i,J-1) + & + (v(i,J,k)-CS%Us_y(i,J,k))*G%mask2dCv(i,J)) + vE_r = 0.5*((v(i+1,J-1,k)-CS%Us_y(i+1,J-1,k))*G%mask2dCv(i+1,J-1) + & + (v(i+1,J,k)-CS%Us_y(i+1,J,k))*G%mask2dCv(i+1,J)) + + dP_Stokes_l_dz = 0.0 + dP_Stokes_r_dz = 0.0 + dP_Stokes_l = 0.0 + dP_Stokes_r = 0.0 + + do l = 1, CS%numbands + + ! Computing (left/right) surface Stokes drift velocities at wavenumber band + uS0_l = 0.5*(CS%Stkx0(I-1,j,l)*G%mask2dCu(I-1,j) + & + CS%Stkx0(I,j,l)*G%mask2dCu(I,j)) + uS0_r = 0.5*(CS%Stkx0(I,j,l)*G%mask2dCu(I,j) + & + CS%Stkx0(I+1,j,l)*G%mask2dCu(I+1,j)) + vS0_l = 0.5*(CS%Stky0(i,J-1,l)*G%mask2dCv(i,J-1) + & + CS%Stky0(i,J,l)*G%mask2dCv(i,J)) + vS0_r = 0.5*(CS%Stky0(i+1,J-1,l)*G%mask2dCv(i+1,J-1) + & + CS%Stky0(i+1,J,l)*G%mask2dCv(i+1,J)) + + ! Wavenumber terms that are useful to simplify the pressure calculations + TwoK = 2.*CS%WaveNum_Cen(l) + FourK = 2.*TwoK + iTwoK = 1./TwoK + iFourK = 1./(FourK) + dexp2kzL = exp(TwoK*zi_l(k))-exp(TwoK*zi_l(k+1)) + dexp2kzR = exp(TwoK*zi_r(k))-exp(TwoK*zi_r(k+1)) + dexp4kzL = exp(FourK*zi_l(k))-exp(FourK*zi_l(k+1)) + dexp4kzR = exp(FourK*zi_r(k))-exp(FourK*zi_r(k+1)) + + ! Compute Pressure at interface and integrated over layer on left/right bounding points. + ! These are summed over wavenumber bands. + if (G%mask2dT(i,j)>0.5) then + dP_Stokes_l_dz = dP_Stokes_l_dz + & + ((uE_l*uS0_l+vE_l*vS0_l)*iTwoK*dexp2kzL + 0.5*(uS0_l*uS0_l+vS0_l*vS0_l)*iFourK*dexp4kzL) + dP_Stokes_l = dP_Stokes_l + (uE_l*uS0_l+vE_l*vS0_l)*dexp2kzL + 0.5*(uS0_l*uS0_l+vS0_l*vS0_l)*dexp4kzL + endif + if (G%mask2dT(i+1,j)>0.5) then + dP_Stokes_r_dz = dP_Stokes_r_dz + & + ((uE_r*uS0_r+vE_r*vS0_r)*iTwoK*dexp2kzR + 0.5*(uS0_l*uS0_l+vS0_l*vS0_l)*iFourK*dexp4kzR) + dP_Stokes_r = dP_Stokes_r + (uE_r*uS0_r+vE_r*vS0_r)*dexp2kzR + 0.5*(uS0_l*uS0_l+vS0_l*vS0_l)*dexp4kzR + endif + enddo + + ! Summing PF over bands + ! > Increment the Layer averaged pressure + P_Stokes_l = P_Stokes_l0 + dP_Stokes_l_dz*Idz_l(k) + P_Stokes_r = P_Stokes_r0 + dP_Stokes_r_dz*Idz_r(k) + ! > Increment the Interface pressure + P_Stokes_l0 = P_Stokes_l0 + dP_Stokes_l + P_Stokes_r0 = P_Stokes_r0 + dP_Stokes_r + + ! Pressure force anomaly is finite difference across the cell. + PFu_Stokes(I,j,k) = (P_Stokes_r - P_Stokes_l)*G%IdxCu(I,j) + + ! Choose to output the pressure delta on the h-points from the PFu calculation. + if (G%mask2dT(i,j)>0.5 .and. CS%id_P_deltaStokes_L > 0) P_deltaStokes_L(i,j,k) = P_Stokes_l + if (G%mask2dT(i,j)>0.5 .and. CS%id_P_deltaStokes_i > 0) P_deltaStokes_i(i,j,k+1) = P_Stokes_l0 + + enddo + endif + enddo ; enddo + + ! Next compute PGFv. The Stokes-induced pressure anomaly diagnostic is stored from this calculation. + ! > Seeking PGFy at (i,J), meanining we need to compute pressure at h-points (i,j) and (i,j+1). + ! UL(i,j) -> found as average of I-1 & I on j + ! UR(i,j+1) -> found as average of I-1 & I on j+1 + ! VL(i,j) -> found on i as average of J-1 & J + ! VR(i,j+1) -> found on i as average of J & J+1 + ! + do J = G%jscB, G%jecB ; do i = G%isc, G%iec + if (G%mask2dCv(i,J)>0.5) then + P_Stokes_l0 = 0.0 + P_Stokes_r0 = 0.0 + zi_l(1) = 0.0 + zi_r(1) = 0.0 + do k = 1, G%ke + h_l = h(i,j,k)*GV%H_to_Z + h_r = h(i,j+1,k)*GV%H_to_Z + zi_l(k+1) = zi_l(k) - h_l + zi_r(k+1) = zi_r(k) - h_r + Idz_l(k) = 1./max(0.1,h_l) + Idz_r(k) = 1./max(0.1,h_r) + enddo + do k = 1,G%ke + ! Computing (left/right) Eulerian velocities assuming the velocity passed to this routine is the + ! Lagrangian velocity. This requires the wave acceleration terms to be activated together. + uE_l = 0.5*((u(I-1,j,k)-CS%Us_x(I-1,j,k))*G%mask2dCu(I-1,j) + & + (u(I,j,k)-CS%Us_x(I,j,k))*G%mask2dCu(I,j)) + uE_r = 0.5*((u(I-1,j+1,k)-CS%Us_x(I-1,j+1,k))*G%mask2dCu(I-1,j+1) + & + (u(I,j+1,k)-CS%Us_x(I,j+1,k))*G%mask2dCu(I,j+1)) + vE_l = 0.5*((v(i,J-1,k)-CS%Us_y(i,J-1,k))*G%mask2dCv(i,J-1) + & + (v(i,J,k)-CS%Us_y(i,J,k))*G%mask2dCv(i,J)) + vE_r = 0.5*((v(i,J,k)-CS%Us_y(i,J,k))*G%mask2dCv(i,J) + & + (v(i,J+1,k)-CS%Us_y(i,J+1,k))*G%mask2dCv(i,J+1)) + + dP_Stokes_l_dz = 0.0 + dP_Stokes_r_dz = 0.0 + dP_Stokes_l = 0.0 + dP_Stokes_r = 0.0 + + do l = 1, CS%numbands + + ! Computing (left/right) surface Stokes drift velocities at wavenumber band + uS0_l = 0.5*(CS%Stkx0(I-1,j,l)*G%mask2dCu(I-1,j) + & + CS%Stkx0(I,j,l)*G%mask2dCu(I,j)) + uS0_r = 0.5*(CS%Stkx0(I-1,j+1,l)*G%mask2dCu(I-1,j+1) + & + CS%Stkx0(I,j+1,l)*G%mask2dCu(I,j+1)) + vS0_l = 0.5*(CS%Stky0(i,J-1,l)*G%mask2dCv(i,J-1) + & + CS%Stky0(i,J,l)*G%mask2dCv(i,J)) + vS0_r = 0.5*(CS%Stky0(i,J,l)*G%mask2dCv(i,J) + & + CS%Stky0(i,J+1,l)*G%mask2dCv(i,J+1)) + + ! Wavenumber terms that are useful to simplify the pressure calculations + TwoK = 2.*CS%WaveNum_Cen(l) + FourK = 2.*TwoK + iTwoK = 1./TwoK + iFourK = 1./(FourK) + dexp2kzL = exp(TwoK*zi_l(k))-exp(TwoK*zi_l(k+1)) + dexp2kzR = exp(TwoK*zi_r(k))-exp(TwoK*zi_r(k+1)) + dexp4kzL = exp(FourK*zi_l(k))-exp(FourK*zi_l(k+1)) + dexp4kzR = exp(FourK*zi_r(k))-exp(FourK*zi_r(k+1)) + + ! Compute Pressure at interface and integrated over layer on left/right bounding points. + ! These are summed over wavenumber bands. + if (G%mask2dT(i,j)>0.5) then + dP_Stokes_l_dz = dP_Stokes_l_dz + & + ((uE_l*uS0_l+vE_l*vS0_l)*iTwoK*dexp2kzL + 0.5*(uS0_l*uS0_l+vS0_l*vS0_l)*iFourK*dexp4kzL) + dP_Stokes_l = dP_Stokes_l + (uE_l*uS0_l+vE_l*vS0_l)*dexp2kzL + 0.5*(uS0_l*uS0_l+vS0_l*vS0_l)*dexp4kzL + endif + if (G%mask2dT(i,j+1)>0.5) then + dP_Stokes_r_dz = dP_Stokes_r_dz + & + ((uE_r*uS0_r+vE_r*vS0_r)*iTwoK*dexp2kzR + 0.5*(uS0_l*uS0_l+vS0_l*vS0_l)*iFourK*dexp4kzR) + dP_Stokes_r = dP_Stokes_r + (uE_r*uS0_r+vE_r*vS0_r)*dexp2kzR + 0.5*(uS0_l*uS0_l+vS0_l*vS0_l)*dexp4kzR + endif + enddo + + ! Summing PF over bands + ! > Increment the Layer averaged pressure + P_Stokes_l = P_Stokes_l0 + dP_Stokes_l_dz*Idz_l(k) + P_Stokes_r = P_Stokes_r0 + dP_Stokes_r_dz*Idz_r(k) + ! > Increment the Interface pressure + P_Stokes_l0 = P_Stokes_l0 + dP_Stokes_l + P_Stokes_r0 = P_Stokes_r0 + dP_Stokes_r + + ! Pressure force anomaly is finite difference across the cell. + PFv_Stokes(I,j,k) = (P_Stokes_r - P_Stokes_l)*G%IdyCv(i,J) + + enddo + endif + enddo ; enddo + + if (CS%id_PFv_Stokes>0) & + call post_data(CS%id_PFv_Stokes, PFv_Stokes, CS%diag) + if (CS%id_PFu_Stokes>0) & + call post_data(CS%id_PFu_Stokes, PFu_Stokes, CS%diag) + if (CS%id_P_deltaStokes_L>0) & + call post_data(CS%id_P_deltaStokes_L, P_deltaStokes_L, CS%diag) + if (CS%id_P_deltaStokes_i>0) & + call post_data(CS%id_P_deltaStokes_i, P_deltaStokes_i, CS%diag) + +end subroutine Stokes_PGF + + !> Computes wind speed from ustar_air based on COARE 3.5 Cd relationship !! Probably doesn't belong in this module, but it is used here to estimate !! wind speed for wind-wave relationships. Should be a fine way to estimate @@ -1530,6 +1888,53 @@ subroutine Waves_end(CS) end subroutine Waves_end +!> Register wave restart fields. To be called before MOM_wave_interface_init +subroutine waves_register_restarts(CS, HI, GV, param_file, restart_CSp) + type(wave_parameters_CS), pointer :: CS !< Wave parameter Control structure + type(hor_index_type), intent(inout) :: HI !< Grid structure + type(verticalGrid_type), intent(in) :: GV !< Vertical grid structure + type(param_file_type), intent(in) :: param_file !< Input parameter structure + type(MOM_restart_CS), pointer :: restart_CSp !< Restart structure, data intent(inout) + ! Local variables + type(vardesc) :: vd(2) + logical :: use_waves + logical :: StatisticalWaves + logical :: time_tendency_term + character(len=40) :: mdl = "MOM_wave_interface" !< This module's name. + + if (associated(CS)) then + call MOM_error(FATAL, "waves_register_restarts: Called with initialized waves control structure") + endif + allocate(CS) + + call get_param(param_file, mdl, "USE_WAVES", use_waves, & + "If true, enables surface wave modules.", do_not_log=.true., default=.false.) + + ! Check if using LA_LI2016 + call get_param(param_file,mdl,"USE_LA_LI2016",StatisticalWaves, & + do_not_log=.true.,default=.false.) + + if (.not.(use_waves .or. StatisticalWaves)) return + + call get_param(param_file,mdl,"STOKES_DDT",time_tendency_term, do_not_log=.true., default=.false.) + + if (time_tendency_term) then + ! Allocate wave fields needed for restart file + allocate(CS%Us_x_prev(HI%isdB:HI%IedB,HI%jsd:HI%jed,GV%ke)) + CS%Us_x_prev(:,:,:) = 0.0 + allocate(CS%Us_y_prev(HI%isd:HI%Ied,HI%jsdB:HI%jedB,GV%ke)) + CS%Us_y_prev(:,:,:) = 0.0 + ! Register to restart + vd(1) = var_desc("Us_x_prev", "m s-1", "3d zonal Stokes drift profile",& + hor_grid='u',z_grid='L') + vd(2) = var_desc("Us_y_prev", "m s-1", "3d meridional Stokes drift profile",& + hor_grid='v',z_grid='L') + call register_restart_field(CS%US_x_prev(:,:,:), vd(1), .false., restart_CSp) + call register_restart_field(CS%US_y_prev(:,:,:), vd(2), .false., restart_CSp) + endif + +end subroutine waves_register_restarts + !> \namespace mom_wave_interface !! !! \author Brandon Reichl, 2018.