diff --git a/bld/namelist_files/namelist_defaults_cam.xml b/bld/namelist_files/namelist_defaults_cam.xml index 8744468500..753967e237 100644 --- a/bld/namelist_files/namelist_defaults_cam.xml +++ b/bld/namelist_files/namelist_defaults_cam.xml @@ -1413,7 +1413,7 @@ SPCAM off -NONE +NONE NONE NONE diff --git a/bld/namelist_files/namelist_definition.xml b/bld/namelist_files/namelist_definition.xml index 0fb995559d..aa23b12083 100644 --- a/bld/namelist_files/namelist_definition.xml +++ b/bld/namelist_files/namelist_definition.xml @@ -3277,7 +3277,7 @@ Default: true - Absolute path to the neural net weights used for the YOG deep convection scheme. diff --git a/src/control/runtime_opts.F90 b/src/control/runtime_opts.F90 index 8018c3a1a3..1474ab71d4 100644 --- a/src/control/runtime_opts.F90 +++ b/src/control/runtime_opts.F90 @@ -52,6 +52,7 @@ subroutine read_namelist(nlfilename, single_column, scmlat, scmlon) use rk_stratiform, only: rk_stratiform_readnl use unicon_cam, only: unicon_cam_readnl use zm_conv_intr, only: zm_conv_readnl + use yog_intr, only: yog_readnl use hk_conv, only: hkconv_readnl use uwshcu, only: uwshcu_readnl use pkg_cld_sediment, only: cld_sediment_readnl @@ -144,6 +145,7 @@ subroutine read_namelist(nlfilename, single_column, scmlat, scmlon) call cldfrc2m_readnl(nlfilename) call unicon_cam_readnl(nlfilename) call zm_conv_readnl(nlfilename) + call yog_readnl(nlfilename) call rk_stratiform_readnl(nlfilename) call hkconv_readnl(nlfilename) call uwshcu_readnl(nlfilename) diff --git a/src/physics/cam/SAM_consts.F90 b/src/physics/cam/SAM_consts.F90 new file mode 100644 index 0000000000..e16b192ad6 --- /dev/null +++ b/src/physics/cam/SAM_consts.F90 @@ -0,0 +1,129 @@ +module SAM_consts_mod + !! Physical constants from the SAM Model required for variable conversions + + use netcdf + + implicit none + public + + ! From params.f90 in SAM + ! These are constants used in equations as part of the parameterisation. + ! Since the net is trained with them they should be left as defined here, rather than + ! forced to match the external model + + ! Physical Constants: + + != unit (m / s^2) + real(8), parameter :: ggr = 9.81 + !! Gravity acceleration, m/s2 + + != unit J / kg :: lcond, lsub, lfus + real(8), parameter :: lfus = 0.3336e+06 + !! Latent heat of fusion + real(8), parameter :: lcond = 2.5104e+06 + !! Latent heat of condensation + real(8), parameter :: lsub = 2.8440e+06 + !! Latent heat of sublimation, J/kg + + != unit (J / kg) / K :: cp + real(8), parameter :: cp = 1004. + !! Specific heat of air + + ! unit K :: fac_cond, fac_fus, fac_sub + real(8), parameter :: fac_cond = lcond/cp + !! + real(8), parameter :: fac_fus = lfus/cp + !! + real(8), parameter :: fac_sub = lsub/cp + !! + + ! Temperatures limits for various hydrometeors + != unit K :: tprmin + real(8), parameter :: tprmin = 268.16 + !! Minimum temperature for rain + + != unit K :: tprmax + real(8), parameter :: tprmax = 283.16 + !! Maximum temperature for snow+graupel, K + + != unit K :: tbgmin + real(8), parameter :: tbgmin = 253.16 + !! Minimum temperature for cloud water. + + != unit K :: tbgmin + real(8), parameter :: tbgmax = 273.16 + !! Maximum temperature for cloud ice, K + + != unit K :: tbgmin + real(8), parameter :: tgrmin = 223.16 + !! Maximum temperature for snow, K + + != unit K :: tbgmin + real(8), parameter :: tgrmax = 283.16 + !! Maximum temperature for graupel, K + + ! Misc. microphysics variables + ! != unit 1 / K :: a_pr + real(8), parameter :: a_pr = 1./(tprmax-tprmin) + !! Misc. microphysics variables + + != unit 1 / K :: a_bg + real(8), parameter :: a_bg = 1./(tbgmax-tbgmin) + !! Misc. microphysics variables + + real(8), parameter :: an = 1./(tbgmax-tbgmin) + real(8), parameter :: bn = tbgmin * an + real(8), parameter :: ap = 1./(tprmax-tprmin) + real(8), parameter :: bp = tprmin * ap + real(8), parameter :: fac1 = fac_cond+(1+bp)*fac_fus + real(8), parameter :: fac2 = fac_fus*ap + real(8), parameter :: ag = 1./(tgrmax-tgrmin) + + ! --------------------------- + ! SAM Grid Variables + ! --------------------------- + integer, parameter :: input_ver_dim = 30 + !! Set to 48 in setparm.f90 of SAM. Same as nz_gl, but trained on a run with 30. + + ! Outputs from NN are supplied at lowest 30 half-model levels for sedimentation fluxes, + ! and at 29 levels for fluxes (as flux at bottom boundary is zero). + integer, parameter :: nrf = 30 + !! number of vertical levels the NN uses + integer, parameter :: nrfq = nrf - 1 + !! number of vertical levels the NN uses when boundary condition is set to 0 + + real(8), parameter :: dt_sam = 30.0 + !! SAM timestep in seconds + +!--------------------------------------------------------------------- +! Functions and Subroutines + +contains + + != unit 1 :: omegan + real(8) function omegan(tabs) + != unit K :: tabs + real(8), intent(in) :: tabs + !! Absolute temperature + + omegan = max(0., min(1., (tabs-tbgmin)*a_bg)) + + return + + end function omegan + + + subroutine check(err_status) + !! Check error status after netcdf call and print message for + !! error codes. + + integer, intent(in) :: err_status + !! error status from nf90 function + + if(err_status /= nf90_noerr) then + write(*, *) trim(nf90_strerror(err_status)) + end if + + end subroutine check + +end module SAM_consts_mod diff --git a/src/physics/cam/nn_cf_net.F90 b/src/physics/cam/nn_cf_net.F90 new file mode 100644 index 0000000000..ffb6fb5285 --- /dev/null +++ b/src/physics/cam/nn_cf_net.F90 @@ -0,0 +1,286 @@ +module nn_cf_net_mod + !! neural_net convection emulator + !! Module containing code pertaining only to the Neural Net functionalities of + !! the M2LiNES Convection Flux parameterisation. + +!--------------------------------------------------------------------- +! Libraries to use +use netcdf +implicit none +private + + +!--------------------------------------------------------------------- +! public interfaces +public relu, net_forward, nn_cf_net_init, nn_cf_net_finalize + + +!--------------------------------------------------------------------- +! local/private data + +! Neural Net Parameters +integer :: n_in + !! Combined length of input features +integer :: n_features_out + !! Number of output features (variables) +integer, allocatable, dimension(:) :: feature_out_sizes + !! Vector storing the length of each of the input features +integer :: n_lev + !! number of atmospheric layers (model levels) output is supplied for +! Dimension of each hidden layer +integer :: n_h1 +integer :: n_h2 +integer :: n_h3 +integer :: n_h4 +integer :: n_out + +! Weights at each hidden layer of the NN +real(4), allocatable, dimension(:,:) :: r_w1 +real(4), allocatable, dimension(:,:) :: r_w2 +real(4), allocatable, dimension(:,:) :: r_w3 +real(4), allocatable, dimension(:,:) :: r_w4 +real(4), allocatable, dimension(:,:) :: r_w5 + +! Biases at each hidden layer of the NN +real(4), allocatable, dimension(:) :: r_b1 +real(4), allocatable, dimension(:) :: r_b2 +real(4), allocatable, dimension(:) :: r_b3 +real(4), allocatable, dimension(:) :: r_b4 +real(4), allocatable, dimension(:) :: r_b5 + +! Scale factors for inputs +real(4), allocatable, dimension(:) :: xscale_mean +real(4), allocatable, dimension(:) :: xscale_stnd + +! vectors at each hidden layer of the NN +real(4), allocatable, dimension(:) :: z1 +real(4), allocatable, dimension(:) :: z2 +real(4), allocatable, dimension(:) :: z3 +real(4), allocatable, dimension(:) :: z4 + +! Scale factors for outputs +real(4), allocatable, dimension(:) :: yscale_mean +real(4), allocatable, dimension(:) :: yscale_stnd + + +!--------------------------------------------------------------------- +! Functions and Subroutines + +contains + + !----------------------------------------------------------------- + ! Public Subroutines + + subroutine relu(logits) + !! Applies ReLU to a vector. + + real(4), dimension(:), intent(inout) :: logits + !! vector to which ReLU will be applied + + where (logits .lt. 0.0) logits = 0.0 + + end subroutine relu + + + subroutine net_forward(features, logits) + !! Run forward method of the Neural Net. + + real(4), dimension(:) :: features + !! Vector of input features + real(4), dimension(:), intent(out) :: logits + !! Output vector + integer :: i + !! Loop counter + integer :: out_dim_counter + !! + integer :: out_pos, feature_size, f + !! + + ! Initialise hidden layer values + ! TODO Is this really neccessary...??? + z1 = 0. + z2 = 0. + z3 = 0. + z4 = 0. + + !Normalize features + features = (features - xscale_mean) / xscale_stnd + + z1 = matmul(features, r_w1) + r_b1 + call relu(z1) + + z2 = matmul(z1, r_w2) + r_b2 + call relu(z2) + + z3 = matmul(z2, r_w3) + r_b3 + call relu(z3) + + z4 = matmul(z3, r_w4) + r_b4 + call relu(z4) + + logits = matmul(z4, r_w5) + r_b5 + + ! Apply scaling and normalisation of each output feature in logits + out_pos = 0 + do f = 1, n_features_out + feature_size = feature_out_sizes(f) + logits(out_pos+1:out_pos+feature_size) = (logits(out_pos+1:out_pos+feature_size)*yscale_stnd(f)) + yscale_mean(f) + out_pos = out_pos + feature_size + end do + + end subroutine + + + subroutine nn_cf_net_init(nn_filename, n_inputs, n_outputs, nrf) + !! Initialise the neural net + + integer, intent(out) :: n_inputs, n_outputs + integer, intent(in) :: nrf + !! number of atmospheric layers in each input + + ! This will be the netCDF ID for the file and data variable. + integer :: ncid + integer :: in_dimid, out_dimid, single_dimid + integer :: h1_dimid, h2_dimid, h3_dimid, h4_dimid + integer :: r_w1_varid, r_w2_varid, r_b1_varid, r_b2_varid + integer :: r_w3_varid, r_w4_varid, r_b3_varid, r_b4_varid + integer :: r_w5_varid, r_b5_varid + + integer :: xscale_mean_varid, xscale_stnd_varid + integer :: yscale_mean_varid, yscale_stnd_varid + + character(len=136), intent(in) :: nn_filename + !! NetCDF filename from which to read model weights + + + !-------------allocate arrays and read data------------------- + + ! Open the file. NF90_NOWRITE tells netCDF we want read-only + ! access + ! Get the varid or dimid for each variable or dimension based + ! on its name. + + call check( nf90_open(trim(nn_filename),NF90_NOWRITE,ncid )) + + call check( nf90_inq_dimid(ncid, 'N_in', in_dimid)) + call check( nf90_inquire_dimension(ncid, in_dimid, len=n_in)) + call check( nf90_inq_dimid(ncid, 'N_h1', h1_dimid)) + call check( nf90_inquire_dimension(ncid, h1_dimid, len=n_h1)) + call check( nf90_inq_dimid(ncid, 'N_h2', h2_dimid)) + call check( nf90_inquire_dimension(ncid, h2_dimid, len=n_h2)) + call check( nf90_inq_dimid(ncid, 'N_h3', h3_dimid)) + call check( nf90_inquire_dimension(ncid, h3_dimid, len=n_h3)) + call check( nf90_inq_dimid(ncid, 'N_h4', h4_dimid)) + call check( nf90_inquire_dimension(ncid, h4_dimid, len=n_h4)) + call check( nf90_inq_dimid(ncid, 'N_out', out_dimid)) + call check( nf90_inquire_dimension(ncid, out_dimid, len=n_out)) + call check( nf90_inq_dimid(ncid, 'N_out_dim', out_dimid)) + call check( nf90_inquire_dimension(ncid, out_dimid, len=n_features_out)) + + print *, 'size of features to NN', n_in + print *, 'size of outputs from NN', n_out + + ! Allocate arrays for NN based on sizes read from nc file + allocate(r_w1(n_in,n_h1)) + allocate(r_w2(n_h1,n_h2)) + allocate(r_w3(n_h2,n_h3)) + allocate(r_w4(n_h3,n_h4)) + allocate(r_w5(n_h4,n_out)) + + allocate(r_b1(n_h1)) + allocate(r_b2(n_h2)) + allocate(r_b3(n_h3)) + allocate(r_b4(n_h4)) + allocate(r_b5(n_out)) + + allocate(z1(n_h1)) + allocate(z2(n_h2)) + allocate(z3(n_h3)) + allocate(z4(n_h4)) + + allocate(xscale_mean(n_in)) + allocate(xscale_stnd(n_in)) + + allocate(yscale_mean(n_features_out)) + allocate(yscale_stnd(n_features_out)) + allocate(feature_out_sizes(n_features_out)) + + ! Read NN data in from nc file + call check( nf90_inq_varid(ncid, "w1", r_w1_varid)) + call check( nf90_get_var(ncid, r_w1_varid, r_w1)) + call check( nf90_inq_varid(ncid, "w2", r_w2_varid)) + call check( nf90_get_var(ncid, r_w2_varid, r_w2)) + call check( nf90_inq_varid(ncid, "w3", r_w3_varid)) + call check( nf90_get_var(ncid, r_w3_varid, r_w3)) + call check( nf90_inq_varid(ncid, "w4", r_w4_varid)) + call check( nf90_get_var(ncid, r_w4_varid, r_w4)) + call check( nf90_inq_varid(ncid, "w5", r_w5_varid)) + call check( nf90_get_var(ncid, r_w5_varid, r_w5)) + + call check( nf90_inq_varid(ncid, "b1", r_b1_varid)) + call check( nf90_get_var(ncid, r_b1_varid, r_b1)) + call check( nf90_inq_varid(ncid, "b2", r_b2_varid)) + call check( nf90_get_var(ncid, r_b2_varid, r_b2)) + call check( nf90_inq_varid(ncid, "b3", r_b3_varid)) + call check( nf90_get_var(ncid, r_b3_varid, r_b3)) + call check( nf90_inq_varid(ncid, "b4", r_b4_varid)) + call check( nf90_get_var(ncid, r_b4_varid, r_b4)) + call check( nf90_inq_varid(ncid, "b5", r_b5_varid)) + call check( nf90_get_var(ncid, r_b5_varid, r_b5)) + + call check( nf90_inq_varid(ncid,"fscale_mean", xscale_mean_varid)) + call check( nf90_get_var( ncid, xscale_mean_varid,xscale_mean )) + call check( nf90_inq_varid(ncid,"fscale_stnd", xscale_stnd_varid)) + call check( nf90_get_var( ncid, xscale_stnd_varid,xscale_stnd )) + + call check( nf90_inq_varid(ncid,"oscale_mean", yscale_mean_varid)) + call check( nf90_get_var( ncid, yscale_mean_varid,yscale_mean )) + call check( nf90_inq_varid(ncid,"oscale_stnd", yscale_stnd_varid)) + call check( nf90_get_var( ncid, yscale_stnd_varid,yscale_stnd )) + + ! Close the nc file + call check( nf90_close(ncid)) + + write(*, *) 'Finished reading NN regression file.' + + n_inputs = n_in + n_outputs = n_out + + ! Set sizes of output features based on nrf + n_lev = nrf + + ! Set sizes of the feature groups + feature_out_sizes(:) = n_lev + feature_out_sizes(2:3) = n_lev-1 + + end subroutine nn_cf_net_init + + + subroutine nn_cf_net_finalize() + !! Clean up NN space by deallocating any arrays. + + ! Deallocate arrays + deallocate(r_w1, r_w2, r_w3, r_w4, r_w5) + deallocate(r_b1, r_b2, r_b3, r_b4, r_b5) + deallocate(z1, z2, z3, z4) + deallocate(xscale_mean, xscale_stnd, yscale_mean, yscale_stnd) + deallocate(feature_out_sizes) + + end subroutine nn_cf_net_finalize + + + subroutine check(err_status) + !! Check error status after netcdf call and print message for + !! error codes. + + integer, intent(in) :: err_status + !! error status from nf90 function + + if(err_status /= nf90_noerr) then + write(*, *) trim(nf90_strerror(err_status)) + end if + + end subroutine check + + +end module nn_cf_net_mod diff --git a/src/physics/cam/nn_convection_flux.F90 b/src/physics/cam/nn_convection_flux.F90 new file mode 100644 index 0000000000..0d76e91558 --- /dev/null +++ b/src/physics/cam/nn_convection_flux.F90 @@ -0,0 +1,525 @@ +module nn_convection_flux_mod + !! Code to perform the Convection Flux parameterisation + !! and interface to the nn_cf_net Neural Net + !! Reference: https://doi.org/10.1029/2020GL091363 + !! Also see YOG20: https://doi.org/10.1038/s41467-020-17142-3 + +!--------------------------------------------------------------------- +! Libraries to use +use nn_cf_net_mod, only: nn_cf_net_init, net_forward, nn_cf_net_finalize +use SAM_consts_mod, only: fac_cond, fac_fus, tprmin, a_pr, input_ver_dim, & + nrf, nrfq, dt_sam + +implicit none +private + + +!--------------------------------------------------------------------- +! public interfaces +public nn_convection_flux, nn_convection_flux_init, nn_convection_flux_finalize, & + esati, qsati, qsatw, dtqsatw, dtqsati + + +!--------------------------------------------------------------------- +! local/private data + +! Neural Net parameters used throughout module + +integer :: n_inputs, n_outputs + !! Length of input/output vector to the NN + +logical :: do_init=.true. + !! model initialisation is yet to be performed + + +!--------------------------------------------------------------------- +! Functions and Subroutines + +contains + + !----------------------------------------------------------------- + ! Public Subroutines + + subroutine nn_convection_flux_init(nn_filename) + !! Initialise the NN module + + character(len=136), intent(in) :: nn_filename + !! NetCDF filename from which to read model weights + + ! Initialise the Neural Net from file and get info + call nn_cf_net_init(nn_filename, n_inputs, n_outputs, nrf) + + ! Set init flag as complete + do_init = .false. + + end subroutine nn_convection_flux_init + + + subroutine nn_convection_flux(tabs_i, q_i, y_in, & + tabs, & + t, q, & + rho, adz, dz, dtn, & + precsfc) + !! Interface to the neural net that applies physical constraints and reshaping + !! of variables. + !! Operates on subcycle of timestep dtn to update t, q, precsfc, and prec_xy + + ! ----------------------------------- + ! Input Variables + ! ----------------------------------- + + ! --------------------- + ! Fields from beginning of time step used as NN inputs + ! --------------------- + != unit s :: tabs_i + real(8), intent(in) :: tabs_i(:, :) + !! Temperature + + != unit 1 :: q_i + real(8), intent(in) :: q_i(:, :) + !! Non-precipitating water mixing ratio + + ! --------------------- + ! Other fields from SAM + ! --------------------- + != unit m :: y_in + real(8), intent(in) :: y_in(:) + !! Distance of column from equator (proxy for insolation and sfc albedo) + + != unit K :: tabs + real(8), intent(in) :: tabs(:, :) + !! absolute temperature + + != unit (J / kg) :: t + real(8), intent(inout) :: t(:, :) + !! Liquid Ice static energy (cp*T + g*z − L(qliq + qice) − Lf*qice) + + != unit 1 :: q + real(8), intent(inout) :: q(:, :) + !! total water + + ! --------------------- + ! reference vertical profiles: + ! --------------------- + != unit (kg / m**3) :: rho + real(8), intent(in) :: rho(:) + !! air density at pressure levels + + ! != unit mb :: pres + ! real(8), intent(in) pres(nzm) + ! !! pressure,mb at scalar levels + + != unit 1 :: adz + real(8), intent(in) :: adz(:) + !! ratio of the pressure level grid height spacing [m] to dz (lowest dz spacing) + + ! --------------------- + ! Single value parameters from model/grid + ! --------------------- + != unit m :: dz + real(8), intent(in) :: dz + !! grid spacing in z direction for the lowest grid layer + + != unit s :: dtn + real(8), intent(in) :: dtn + !! current dynamical timestep (can be smaller than dt due to subcycling) + + ! ----------------------------------- + ! Output Variables + ! ----------------------------------- + != unit (J / kg) :: t_delta_adv, t_delta_auto, t_delta_sed + != unit 1 :: q_delta_adv, q_delta_auto, q_delta_sed + real(8), dimension(size(tabs_i, 1), size(tabs_i, 2)) :: t_delta_adv, q_delta_adv, & + t_delta_auto, q_delta_auto, & + t_delta_sed, q_delta_sed + !! delta values of t and q generated by the NN + + != unit kg :: t_rad_rest_tend + real(8), dimension(size(tabs_i, 1), size(tabs_i, 2)) :: t_rad_rest_tend + !! tendency of t generated by the NN + + != unit kg / m**2 :: precsfc + real(8), intent(out), dimension(:) :: precsfc + !! Surface precipitation due to autoconversion and sedimentation + + ! ----------------------------------- + ! Local Variables + ! ----------------------------------- + integer i, k, dim_counter, out_dim_counter + integer nx + !! Number of x points in a subdomain + integer nzm + !! Number of z points in a subdomain - 1 + ! real(8) :: omn + ! !! variable to store result of omegan function + ! !! Note that if using you will need to import omegan() from SAM_consts_mod + + ! Other variables + real(8), dimension(nrf) :: omp, fac + real(8), dimension(size(tabs_i, 2)) :: qsat, irhoadz, irhoadzdz + + ! ----------------------------------- + ! variables for NN + ! ----------------------------------- + real(4), dimension(n_inputs) :: features + !! Vector of input features for the NN + real(4), dimension(n_outputs) :: outputs + !! vector of output features from the NN + ! NN outputs + real(8), dimension(nrf) :: t_flux_adv, q_flux_adv, q_tend_auto, & + q_sed_flux + ! Output variable t_rad_rest_tend is also an output from the NN (defined above) + + nx = size(tabs_i, 1) + nzm = size(tabs_i, 2) + + ! Check that we have initialised all of the variables. + if (do_init) call error_mesg('NN has not yet been initialised using nn_convection_flux_init.') + + ! Define useful variables relating to grid spacing to convert fluxes to tendencies + do k=1,nzm + irhoadz(k) = dtn/(rho(k)*adz(k)) ! Temporary factor for below + irhoadzdz(k) = irhoadz(k)/dz ! 2.0 * dtn / (rho(k)*(z(k+1) - z(k-1))) [(kg.m/s)^-1] + end do + + ! The NN operates on atmospheric columns which have been flattened into 2D + do i=1,nx + ! Initialize variables + features = 0. + dim_counter = 0 + outputs = 0. + t_rad_rest_tend(i,:) = 0. + t_flux_adv = 0. + q_flux_adv = 0. + t_delta_adv(i,:) = 0. + q_delta_adv(i,:) = 0. + q_tend_auto = 0. + t_delta_auto(i,:) = 0. + q_delta_auto(i,:) = 0. + q_sed_flux = 0. + t_delta_sed(i,:) = 0. + q_delta_sed(i,:) = 0. + precsfc(i) = 0. + omp = 0. + fac = 0. + + !----------------------------------------------------- + ! Combine all features into one vector + + ! Add temperature as input feature + features(dim_counter+1:dim_counter + input_ver_dim) = real(tabs_i(i,1:input_ver_dim),4) + dim_counter = dim_counter + input_ver_dim + + ! Add non-precipitating water mixing ratio as input feature using random forest (rf) approach from earlier paper + ! Currently we do not use rf_uses_rh option, but may add it back in later + ! if (rf_uses_rh) then + ! ! If using generalised relative humidity convert non-precip. water content to rel. hum + ! do k=1,nzm + ! omn = omegan(tabs(i,j,k)) + ! qsat(k) = omn * qsatw(tabs(i,j,k),pres(k)) + (1.-omn) * qsati(tabs(i,j,k),pres(k)) + ! end do + ! features(dim_counter+1:dim_counter+input_ver_dim) = real(q_i(i,j,1:input_ver_dim)/qsat(1:input_ver_dim),4) + ! dim_counter = dim_counter + input_ver_dim + ! else + ! ! if using non-precipitating water as water content + features(dim_counter+1:dim_counter+input_ver_dim) = real(q_i(i,1:input_ver_dim),4) + dim_counter = dim_counter + input_ver_dim + ! endif + + ! Add distance to the equator as input feature + ! y is a proxy for insolation and surface albedo as both are only a function of |y| in SAM + features(dim_counter+1) = y_in(i) + dim_counter = dim_counter+1 + + !----------------------------------------------------- + ! Call the forward method of the NN on the input features + ! Scaling and normalisation done as layers in NN + + call net_forward(features, outputs) + + !----------------------------------------------------- + ! Separate physical outputs from NN output vector + + ! Moist Static Energy radiative + rest(microphysical changes) tendency + t_rad_rest_tend(i,1:nrf) = outputs(1:nrf) + out_dim_counter = nrf + + ! Moist Static Energy advective subgrid flux + ! BC: advection surface flux is zero + t_flux_adv(1) = 0.0 + t_flux_adv(2:nrf) = outputs(out_dim_counter+1:out_dim_counter+nrfq) + out_dim_counter = out_dim_counter + nrfq + + ! Total non-precip. water mix. ratio advective flux + ! BC: advection surface flux is zero + q_flux_adv(1) = 0.0 + q_flux_adv(2:nrf) = outputs(out_dim_counter+1:out_dim_counter+nrfq) + out_dim_counter = out_dim_counter + nrfq + + ! Total non-precip. water autoconversion tendency + q_tend_auto(1:nrf) = outputs(out_dim_counter+1:out_dim_counter+nrf) + out_dim_counter = out_dim_counter + nrf + + ! total non-precip. water mix. ratio ice-sedimenting flux + q_sed_flux(1:nrf) = outputs(out_dim_counter+1:out_dim_counter+nrf) + + !----------------------------------------------------- + ! Apply physical constraints and update q and t + + ! Non-precip. water content must be >= 0, so ensure advective fluxes + ! will not reduce it below 0 anywhere + do k=2,nrf + if (q_flux_adv(k).lt.0) then + ! If flux is negative ensure we don't lose more than is already present + if ( q(i,k).lt.-q_flux_adv(k)* irhoadzdz(k)) then + q_flux_adv(k) = -q(i,k)/irhoadzdz(k) + end if + else + ! If flux is positive ensure we don't gain more than is in the box below + if (q(i,k-1).lt.q_flux_adv(k)* irhoadzdz(k)) then + q_flux_adv(k) = q(i,k-1)/irhoadzdz(k) + end if + end if + end do + + ! Convert advective fluxes to deltas + do k=1,nrf-1 + t_delta_adv(i,k) = - (t_flux_adv(k+1) - t_flux_adv(k)) * irhoadzdz(k) + q_delta_adv(i,k) = - (q_flux_adv(k+1) - q_flux_adv(k)) * irhoadzdz(k) + end do + ! Enforce boundary condition at top of column + t_delta_adv(i,nrf) = - (0.0 - t_flux_adv(nrf)) * irhoadzdz(nrf) + q_delta_adv(i,nrf) = - (0.0 - q_flux_adv(nrf)) * irhoadzdz(nrf) + ! q must be >= 0 so ensure delta won't reduce it below zero + do k=1,nrf + if (q(i,k) .lt. -q_delta_adv(i,k)) then + q_delta_adv(i,k) = -q(i,k) + end if + end do + + ! Update q and t with delta values + q(i,1:nrf) = q(i,1:nrf) + q_delta_adv(i,1:nrf) + t(i,1:nrf) = t(i,1:nrf) + t_delta_adv(i,1:nrf) + + ! ensure autoconversion tendency won't reduce q below 0 + do k=1,nrf + omp(k) = max(0.,min(1.,(tabs(i,k)-tprmin)*a_pr)) + fac(k) = (fac_cond + fac_fus * (1.0 - omp(k))) + if (q_tend_auto(k).lt.0) then + q_delta_auto(i,k) = - min(-q_tend_auto(k) * dtn, q(i,k)) + else + q_delta_auto(i,k) = q_tend_auto(k) * dtn + endif + end do + + ! Update with autoconversion q and t deltas (dt = -dq*(latent_heat/cp)) + q(i,1:nrf) = q(i,1:nrf) + q_delta_auto(i,1:nrf) + t_delta_auto(i,1:nrf) = - q_delta_auto(i,1:nrf)*fac(1:nrf) + t(i,1:nrf) = t(i,1:nrf) + t_delta_auto(i,1:nrf) + + ! Ensure sedimenting ice will not reduce q below zero anywhere + do k=2,nrf + if (q_sed_flux(k).lt.0) then + ! If flux is negative ensure we don't lose more than is already present + if ( q(i,k).lt.-q_sed_flux(k)* irhoadzdz(k)) then + q_sed_flux(k) = -q(i,k)/irhoadzdz(k) + end if + else + ! If flux is positive ensure we don't gain more than is in the box below + if (q(i,k-1).lt.q_sed_flux(k)* irhoadzdz(k)) then + q_sed_flux(k) = q(i,k-1)/irhoadzdz(k) + end if + end if + end do + + ! Convert sedimenting fluxes to deltas + do k=1,nrf-1 ! One level less than I actually use + q_delta_sed(i,k) = - (q_sed_flux(k+1) - q_sed_flux(k)) * irhoadzdz(k) + end do + ! Enforce boundary condition at top of column + q_delta_sed(i,nrf) = - (0.0 - q_sed_flux(nrf)) * irhoadzdz(nrf) + ! q must be >= 0 so ensure delta won't reduce it below zero + do k=1,nrf + if (q_delta_sed(i,k).lt.0) then + q_delta_sed(i,k) = min(-q_delta_sed(i,k), q(i,k)) + q_delta_sed(i,k) = -q_delta_sed(i,k) + end if + end do + + ! Update q and t with sed q and t deltas (dt = -dq*(latent_heat/cp)) + q(i,1:nrf) = q(i,1:nrf) + q_delta_sed(i,1:nrf) + t(i,1:nrf) = t(i,1:nrf) - q_delta_sed(i,1:nrf)*(fac_fus+fac_cond) + + ! Radiation is not applied in this scheme for now. + ! ! Apply radiation rest tendency to variables (multiply by dtn to get dt) + ! t(i,1:nrf) = t(i,1:nrf) + t_rad_rest_tend(i,1:nrf)*dtn + + ! Calculate surface precipitation + ! Combination of sedimentation at surface, and autoconversion in the column + ! Apply sedimenting flux at surface to get rho*dq term + precsfc(i) = precsfc(i) - min(q_sed_flux(1), 0D0) * irhoadzdz(1) * rho(1) * adz(1) !! *dtn/dz + ! Loop up column for all autoconverted precipitation + do k=1,nrf + precsfc(i) = precsfc(i) - q_delta_auto(i,k) * rho(k) * adz(k) + end do + precsfc(i) = precsfc(i) * dz + + ! As a final check enforce q must be >= 0.0 + do k = 1,nrf + q(i,k) = max(0.,q(i,k)) + end do + end do + ! End of loop over columns + + end subroutine nn_convection_flux + + + subroutine nn_convection_flux_finalize() + !! Finalize the NN module + + ! Finalize the Neural Net deallocating arrays + call nn_cf_net_finalize() + + end subroutine nn_convection_flux_finalize + + + !----------------------------------------------------------------- + + subroutine error_mesg (message) + character(len=*), intent(in) :: message + !! message to be written to output (character string) + + ! Since masterproc is a SAM variable adjust to print from all proc for now + ! if(masterproc) print*, 'Neural network module: ', message + print*, 'Neural network module: ', message + stop + + end subroutine error_mesg + + + !----------------------------------------------------------------- + ! Need qsatw functions to: + ! - run with rf_uses_rh option (currently unused) + ! - convert variables in CAM interface + ! Ripped from SAM model: + ! https://github.com/yaniyuval/Neural_nework_parameterization/blob/f81f5f695297888f0bd1e0e61524590b4566bf03/sam_code_NN/sat.f90 + + ! Saturation vapor pressure and mixing ratio. + ! Based on Flatau et.al, (JAM, 1992:1507) + + != unit mb :: esatw + real(8) function esatw(t) + implicit none + != unit K :: t + real(8) :: t ! temperature (K) + + != unit :: a0 + != unit :: mb / k :: a1, a2, a3, a4, a5, a6, a7, a8 + real(8) :: a0,a1,a2,a3,a4,a5,a6,a7,a8 + data a0,a1,a2,a3,a4,a5,a6,a7,a8 /& + 6.105851, 0.4440316, 0.1430341e-1, & + 0.2641412e-3, 0.2995057e-5, 0.2031998e-7, & + 0.6936113e-10, 0.2564861e-13,-0.3704404e-15/ + ! 6.11239921, 0.443987641, 0.142986287e-1, & + ! 0.264847430e-3, 0.302950461e-5, 0.206739458e-7, & + ! 0.640689451e-10, -0.952447341e-13,-0.976195544e-15/ + + != unit K :: dt + real(8) :: dt + dt = max(-80.,t-273.16) + esatw = a0 + dt*(a1+dt*(a2+dt*(a3+dt*(a4+dt*(a5+dt*(a6+dt*(a7+a8*dt))))))) + end function esatw + + + != unit 1 :: qsatw + real(8) function qsatw(t,p) + implicit none + != unit K :: t + real(8) :: t ! temperature + + != unit mb :: p, esat + real(8) :: p ! pressure + real(8) :: esat + + esat = esatw(t) + qsatw = 0.622 * esat/max(esat, p-esat) + end function qsatw + + + real(8) function dtesatw(t) + implicit none + real(8) :: t ! temperature (K) + real(8) :: a0,a1,a2,a3,a4,a5,a6,a7,a8 + data a0,a1,a2,a3,a4,a5,a6,a7,a8 /& + 0.443956472, 0.285976452e-1, 0.794747212e-3, & + 0.121167162e-4, 0.103167413e-6, 0.385208005e-9, & + -0.604119582e-12, -0.792933209e-14, -0.599634321e-17/ + real(8) :: dt + dt = max(-80.,t-273.16) + dtesatw = a0 + dt* (a1+dt*(a2+dt*(a3+dt*(a4+dt*(a5+dt*(a6+dt*(a7+a8*dt))))))) + end function dtesatw + + + real(8) function dtqsatw(t,p) + implicit none + real(8) :: t ! temperature (K) + real(8) :: p ! pressure (mb) + dtqsatw=0.622*dtesatw(t)/p + end function dtqsatw + + + real(8) function esati(t) + implicit none + != unit K :: t + real(8) :: t ! temperature + real(8) :: a0,a1,a2,a3,a4,a5,a6,a7,a8 + data a0,a1,a2,a3,a4,a5,a6,a7,a8 /& + 6.11147274, 0.503160820, 0.188439774e-1, & + 0.420895665e-3, 0.615021634e-5,0.602588177e-7, & + 0.385852041e-9, 0.146898966e-11, 0.252751365e-14/ + real(8) :: dt + dt = max(-80.0, t-273.16) + esati = a0 + dt*(a1+dt*(a2+dt*(a3+dt*(a4+dt*(a5+dt*(a6+dt*(a7+a8*dt))))))) + end function esati + + + != unit 1 :: qsati + real(8) function qsati(t,p) + implicit none + != unit t :: K + real(8) :: t ! temperature + + != unit mb :: p + real(8) :: p ! pressure + + != unit mb :: esat + real(8) :: esat + esat = esati(t) + qsati = 0.622 * esat/max(esat,p-esat) + end function qsati + + + real(8) function dtesati(t) + implicit none + real(8) :: t ! temperature (K) + real(8) :: a0,a1,a2,a3,a4,a5,a6,a7,a8 + data a0,a1,a2,a3,a4,a5,a6,a7,a8 / & + 0.503223089, 0.377174432e-1,0.126710138e-2, & + 0.249065913e-4, 0.312668753e-6, 0.255653718e-8, & + 0.132073448e-10, 0.390204672e-13, 0.497275778e-16/ + real(8) :: dt + dt = max(-800. ,t-273.16) + dtesati = a0 + dt*(a1+dt*(a2+dt*(a3+dt*(a4+dt*(a5+dt*(a6+dt*(a7+a8*dt))))))) + return + end function dtesati + + + real(8) function dtqsati(t,p) + implicit none + real(8) :: t ! temperature (K) + real(8) :: p ! pressure (mb) + dtqsati = 0.622 * dtesati(t) / p + end function dtqsati + + +end module nn_convection_flux_mod diff --git a/src/physics/cam/nn_interface_cam.F90 b/src/physics/cam/nn_interface_cam.F90 new file mode 100644 index 0000000000..d66d773613 --- /dev/null +++ b/src/physics/cam/nn_interface_cam.F90 @@ -0,0 +1,739 @@ +module nn_interface_CAM + !! Interface to convection parameterisation for the CAM model + !! Reference: https://doi.org/10.1029/2020GL091363 + !! Also see YOG20: https://doi.org/10.1038/s41467-020-17142-3 + + ! TODO: Check for redundant variables once refactored (icycle, nstatis, precip etc.) + +!--------------------------------------------------------------------- +! Libraries to use +use netcdf +use nn_convection_flux_mod, only: nn_convection_flux, & + nn_convection_flux_init, nn_convection_flux_finalize, & + esati, qsati, qsatw, dtqsatw, dtqsati +use SAM_consts_mod, only: nrf, ggr, cp, tbgmax, tbgmin, tprmax, tprmin, & + fac_cond, fac_sub, fac_fus, & + a_bg, a_pr, an, bn, ap, bp, & + omegan, check + +implicit none +private + + +!--------------------------------------------------------------------- +! public interfaces +public nn_convection_flux_CAM, & + nn_convection_flux_CAM_init, nn_convection_flux_CAM_finalize + +! Make these routines public for purposes of testing, +! otherwise not required outside this module. +public interp_to_sam, interp_to_cam, fetch_sam_data +public SAM_var_conversion, CAM_var_conversion + +!--------------------------------------------------------------------- +! local/private data + +!= unit 1 :: nz_sam +integer :: nz_sam + !! number of vertical values in the SAM sounding profiles + +!= unit m :: z +!= unit hPa :: pres, presi, pres_mid +!= unit kg m-3 :: rho +!= unit 1 :: adz +!= unit K :: gamaz +real(8), allocatable, dimension(:) :: z, pres, presi, pres_mid, rho, adz, gamaz + !! SAM sounding variables + +!= unit m :: dz +real(8) :: dz + !! grid spacing in z direction for the lowest grid layer + +!--------------------------------------------------------------------- +! Functions and Subroutines + +contains + + subroutine fetch_sam_data(pressam, presisam, gamazsam, rhosam, zsam) + !! Temporary subroutine to extract SAM pressure profile + real(8), dimension(48), intent(out) :: pressam, presisam, gamazsam, rhosam, zsam + pressam(:) = pres_mid(:) + presisam(:) = presi(:) + gamazsam(:) = gamaz(:) + rhosam(:) = rho(:) + zsam(:) = z(:) + + end subroutine fetch_sam_data + + + !----------------------------------------------------------------- + ! Public Subroutines + + subroutine nn_convection_flux_CAM_init(nn_filename, sounding_filename) + !! Initialise the NN module + + character(len=136), intent(in) :: nn_filename + !! NetCDF filename from which to read model weights + character(len=136), intent(in) :: sounding_filename + !! NetCDF filename from which to read SAM sounding and grid data + + ! Initialise the Neural Net from file and get info + call nn_convection_flux_init(nn_filename) + + ! Read in the SAM sounding and grid data required + call sam_sounding_init(sounding_filename) + + end subroutine nn_convection_flux_CAM_init + + + subroutine nn_convection_flux_CAM(pres_cam, pres_int_cam, pres_sfc_cam, & + tabs_cam, qv_cam, qc_cam, qi_cam, & + cp_cam, & + dtn, & + nx, nz, & + nstep, nstatis, icycle, & + precsfc, & + dqi, dqv, dqc, ds) + !! Interface to the nn_convection parameterisation for the CAM model + + real(8), intent(in) :: dtn ! Seconds + integer, intent(in) :: nx, nz, nstep, nstatis, icycle + + real(8), intent(in) :: cp_cam + !! specific heat capacity of dry air from CAM [J/kg/K] + real(8), dimension(:,:) :: pres_cam + !! pressure [Pa] from the CAM model + real(8), dimension(:,:) :: pres_int_cam + !! interface pressure [Pa] from the CAM model + real(8), dimension(:) :: pres_sfc_cam + !! surface pressure [Pa] from the CAM model + real(8), dimension(:,:) :: tabs_cam + !! absolute temperature [K] from the CAM model + real(8), dimension(:,:) :: qv_cam, qc_cam, qi_cam + !! moisture content [kg/kg] from the CAM model + real(8), dimension(:) :: precsfc + + ! Outputs on the CAM grid + real(8), dimension(nx, nz), intent(out) :: dqi, dqv, dqc, ds + + real(8) :: y_in(nx) + !! Distance of column from equator (proxy for insolation and sfc albedo) + + real(8), dimension(nx) :: precsfc_i + !! precipitation at surface from one call to parameterisation + + ! Local input variables on the SAM grid + real(8), dimension(nx, nrf) :: q0_sam, tabs0_sam + real(8), dimension(nx, nrf) :: q_sam, t_sam, tabs_sam + real(8), dimension(nx, nrf) :: qi_sam, qv_sam, qc_sam + real(8), dimension(nx, nrf) :: qi0_sam, qv0_sam, qc0_sam + real(8), dimension(nx, nrf) :: dqi_sam, dqv_sam, dqc_sam, ds_sam + real(8), dimension(nx) :: qi_surf, qv_surf, qc_surf, tabs_surf + + integer :: k + + ! Initialise precipitation to 0 if required and at start of cycle if subcycling + if(mod(nstep-1,nstatis).eq.0 .and. icycle.eq.1) then + precsfc(:)=0. + end if + + ! distance to the equator + ! y is a proxy for insolation and surface albedo as both are only a function of |y| in SAM + ! TODO: Set y_in as appropriate for CAM + y_in(:) = 0.0 + + !----------------------------------------------------- + + ! Interpolate CAM variables to the SAM pressure levels + ! TODO Interpolate all variables in one call + ! Set surface values + call extrapolate_to_surface(pres_cam, qi_cam, pres_sfc_cam, qi_surf) + where (qi_surf>=0) + qi_surf = qi_surf + elsewhere + qi_surf = 0 + end where + call extrapolate_to_surface(pres_cam, qc_cam, pres_sfc_cam, qc_surf) + where (qc_surf>=0) + qc_surf = qc_surf + elsewhere + qc_surf = 0 + end where + call extrapolate_to_surface(pres_cam, qv_cam, pres_sfc_cam, qv_surf) + where (qv_surf>=0) + qv_surf = qv_surf + elsewhere + qv_surf = 0 + end where + call extrapolate_to_surface(pres_cam, tabs_cam, pres_sfc_cam, tabs_surf) + + call interp_to_sam(pres_cam, pres_sfc_cam, & + tabs_cam, tabs_sam, tabs_surf) + call interp_to_sam(pres_cam, pres_sfc_cam, & + qi_cam, qi_sam, qi_surf) + call interp_to_sam(pres_cam, pres_sfc_cam, & + qc_cam, qc_sam, qc_surf) + call interp_to_sam(pres_cam, pres_sfc_cam, & + qv_cam, qv_sam, qv_surf) + + !----------------------------------------------------- + + ! Convert CAM Moistures and tabs to SAM q and t + call CAM_var_conversion(qv_sam, qc_sam, qi_sam, q_sam, tabs_sam, t_sam) + + ! Store variables on SAM grid at the start of the timestep + ! for calculating tendencies later + qv0_sam = qv_sam + qc0_sam = qc_sam + qi0_sam = qi_sam + tabs0_sam = tabs_sam + + !----------------------------------------------------- + + tabs0_sam = tabs_sam + q0_sam = q_sam + ! Run the neural net parameterisation + ! Updates q and t with delta values + ! advective, autoconversion (dt = -dq*(latent_heat/cp)), + ! sedimentation (dt = -dq*(latent_heat/cp)), + ! radiation rest tendency (multiply by dtn to get dt) + call nn_convection_flux(tabs0_sam(:,1:nrf), q0_sam(:,1:nrf), y_in, & + tabs_sam(:,1:nrf), & + t_sam(:,1:nrf), q_sam(:,1:nrf), & + rho, adz, dz, dtn, & + precsfc_i) + ! Update precsfc with prec from this timestep + precsfc = precsfc + precsfc_i + + !----------------------------------------------------- + + ! Formulate the output variables to CAM as required. + call SAM_var_conversion(t_sam, q_sam, tabs_sam, qv_sam, qc_sam, qi_sam) + ! Convert precipitation from kg/m^2 to m by dividing by density (1000) + precsfc = precsfc * 1.0D-3 + + !----------------------------------------------------- + + ! Convert back into CAM variable tendencies (diff div by dtn) on SAM grid + ! q into components and tabs to s (mult by cp) + dqv_sam = (qv_sam - qv0_sam) / dtn + dqc_sam = (qc_sam - qc0_sam) / dtn + dqi_sam = (qi_sam - qi0_sam) / dtn + ds_sam = cp_cam * (tabs_sam - tabs0_sam) / dtn + ! Convert precipitation from total in m to date in m / s by div by dtn + precsfc = precsfc / dtn + + !----------------------------------------------------- + + ! Interpolate SAM variables to the CAM pressure levels + ! setting tendencies above the SAM grid to 0.0 + ! TODO Interpolate all variables in one call + call interp_to_cam(pres_cam, pres_int_cam, pres_sfc_cam, dqv_sam, dqv) + call interp_to_cam(pres_cam, pres_int_cam, pres_sfc_cam, dqc_sam, dqc) + call interp_to_cam(pres_cam, pres_int_cam, pres_sfc_cam, dqi_sam, dqi) + call interp_to_cam(pres_cam, pres_int_cam, pres_sfc_cam, ds_sam, ds) + + end subroutine nn_convection_flux_CAM + + + subroutine nn_convection_flux_CAM_finalize() + !! Finalize the module deallocating arrays + + call nn_convection_flux_finalize() + call sam_sounding_finalize() + + end subroutine nn_convection_flux_CAM_finalize + + + !----------------------------------------------------------------- + ! Private Subroutines + + subroutine interp_to_sam(p_cam, p_surf_cam, var_cam, var_sam, var_cam_surface) + !! Interpolate from the CAM pressure grid to the SAM pressure grid. + !! Uses linear interpolation between nearest grid points on domain (CAM) grid. + + !! The CAM and SAM grids have pressures measured at cell centres which is where + !! the variables that we are interpolating are stored. + + !! It can be expected, but not guaranteed, that the domain (CAM) grid is + !! coarser than the target (SAM) grid. + + !! NOTE: When reading this code remember that pressure DECREASES monotonically + !! with altitude, so comparisons are not intuitive: Pa > Pb => a is below b + + != unit hPa :: p_cam, p_surf_cam + real(8), dimension(:,:), intent(in) :: p_cam + real(8), dimension(:), intent(in) :: p_surf_cam + !! pressure [Pa] from the CAM model + != unit 1 :: var_cam, var_sam + real(8), dimension(:,:), intent(in) :: var_cam + !! variable from CAM grid to be interpolated from + real(8), dimension(:), intent(in) :: var_cam_surface + !! Surface value of CAM variable for interpolation/boundary condition + real(8), dimension(:,:), intent(out) :: var_sam + !! variable from SAM grid to be interpolated to + != unit 1 :: p_norm_sam, p_norm_cam + real(8), dimension(nrf) :: p_mid_sam, p_norm_sam + real(8), dimension(:,:), allocatable :: p_norm_cam + !! Normalised pressures (using surface pressure) as a sigma-coordinate + + integer :: i, k, nz_cam, ncol_cam + + integer :: kc_u, kc_l + real(8) :: pc_u, pc_l + !! Pressures of the CAM cell above and below the target SAM cell + + ncol_cam = size(p_cam, 1) + nz_cam = size(p_cam, 2) + allocate(p_norm_cam(ncol_cam, nz_cam)) + + ! Normalise pressures by surface value + ! Note - We work in pressure space but use a 'midpoint' based on altitude from + ! SAM as this is concurrent to the variable value at this location. + p_norm_sam(:) = pres_mid(:) / presi(1) + do k = 1,nz_cam + p_norm_cam(:,k) = p_cam(:,k) / p_surf_cam(:) + end do + + ! Loop over columns and SAM levels and interpolate each column + do k = 1, nrf + do i = 1, ncol_cam + ! Check we are within array + + ! If SAM cell centre is below lowest CAM centre - interpolate to surface value + if (p_norm_sam(k) > p_norm_cam(i, 1)) then +! write(*,*) "Interpolating to surface." + var_sam(i, k) = var_cam_surface(i) & + + (p_norm_sam(k)-1.0) & + * (var_cam(i, 1)-var_cam_surface(i))/(p_norm_cam(i, 1)-1.0) + ! Check CAM grid top cell is above SAM grid top + elseif (p_norm_cam(i, nz_cam) > p_norm_sam(nrf)) then + ! This should not happen as CAM grid extends to higher altitudes than SAM + ! TODO This check is run on every iteration - move outside +! write(*,*) "CAM upper pressure level is lower than that of SAM: Stopping." + stop + else + ! Locate the neighbouring CAM indices to interpolate between + ! TODO - this will be slow - speed up later + kc_u = 1 + pc_u = p_norm_cam(i, kc_u) + do while (pc_u > p_norm_sam(k)) + kc_u = kc_u + 1 + pc_u = p_norm_cam(i, kc_u) + end do + kc_l = kc_u - 1 + pc_l = p_norm_cam(i, kc_l) + ! Redundant following the lines above + ! do while (pc_l < p_norm_sam(k)) + ! kc_l = kc_l - 1 + ! pc_l = p_norm_cam(i, kc_l) + ! end do + + ! interpolate variables - Repeat for as many variables as need interpolating + var_sam(i, k) = var_cam(i, kc_l) & + + (p_norm_sam(k)-pc_l) & + * (var_cam(i, kc_u)-var_cam(i, kc_l))/(pc_u-pc_l) + endif + end do + end do + + ! Clean up + deallocate(p_norm_cam) + + end subroutine interp_to_sam + + subroutine interp_to_cam(p_cam, p_int_cam, p_surf_cam, var_sam, var_cam) + ! TODO This would seriously benefit from a refactor to add the top interface to p_sam_int and p_cam_int + !! Interpolate from the SAM NN pressure grid to the CAM pressure grid. + !! Uses conservative regridding between grids. + + !! Requires dealing in interfaces rather than centres. + !! These start at the surface of both grids + !! and extend to grid-top for CAM, but stop at the base of the top cell + !! (i.e. no grid-top value) for SAM. + + != unit Pa :: p_cam, p_int_cam + real(8), dimension(:,:), intent(in) :: p_cam + !! pressure [Pa] from the CAM model + real(8), dimension(:,:), intent(in) :: p_int_cam + !! interface pressures [Pa] for each column in CAM + real(8), dimension(:), intent(in) :: p_surf_cam + !! surface pressures [Pa] for each column in CAM + != unit 1 :: var_cam, var_sam + real(8), dimension(:,:), intent(out) :: var_cam + !! variable from CAM grid to be interpolated from + real(8), dimension(:,:), intent(in) :: var_sam + !! variable from SAM grid to be interpolated to + != unit 1 :: p_norm_sam, p_norm_cam + real(8), dimension(nrf) :: p_norm_sam + real(8), dimension(nrf+1) :: p_int_norm_sam + real(8), dimension(:,:), allocatable :: p_norm_cam + real(8), dimension(:,:), allocatable :: p_int_norm_cam + + integer :: i, k, c, nz_cam, ncol_cam, ks, ks_u, ks_l + real(8) :: ps_u, ps_l, pc_u, pc_l + real(8) :: p_norm_sam_min + + ncol_cam = size(p_cam, 1) + nz_cam = size(p_cam, 2) + allocate(p_norm_cam(ncol_cam, nz_cam)) + allocate(p_int_norm_cam(ncol_cam, nz_cam+1)) + + ! Normalise pressures by surface value (extending SAM to add top interface) + p_norm_sam(:) = pres_mid(1:nrf) / presi(1) + p_int_norm_sam(1:nrf+1) = presi(1:nrf+1) / presi(1) + p_int_norm_sam(1) = 1.0 + + do k = 1,nz_cam + p_norm_cam(:,k) = p_cam(:,k) / p_surf_cam(:) + p_int_norm_cam(:,k) = p_int_cam(:,k) / p_surf_cam(:) + end do + p_int_norm_cam(:,nz_cam+1) = p_int_cam(:,nz_cam+1) / p_surf_cam(:) + p_int_norm_cam(:,1) = 1.0 + + ! Loop over columns and SAM levels and interpolate each column + ! TODO Revert indices to i,k for speed + do k = 1, nz_cam + do i = 1, ncol_cam + ! Check we are within array overlap: + ! No need to compare bottom end as pressure is normalised to 1.0 for both. + + if (p_int_norm_cam(i, k) < p_int_norm_sam(nrf+1)) then + ! Anything above the SAM NN grid should be set to 0.0 +! write(*,*) "CAM pressure is lower than SAM top: set tend to 0.0." + var_cam(i, k) = 0.0 + + else + ! Get the pressures at the top and bottom of the CAM cell + pc_u = p_int_norm_cam(i, k+1) + pc_l = p_int_norm_cam(i,k) + + ! Locate the SAM indices to interpolate between + ! This will be slow - speed up later + ! ks_u is the SAM block with an upper interface above the CAM block + ks_u = 1 + ps_u = p_int_norm_sam(ks_u+1) + do while ((ps_u > pc_u) .and. (ks_u < nrf)) + ks_u = ks_u + 1 + ps_u = p_int_norm_sam(ks_u+1) + end do + ! ks_l is the SAM block with a lower interface below the CAM block + ks_l = ks_u + ps_l = p_int_norm_sam(ks_l) + do while ((ps_l < pc_l) .and. (ks_l > 1)) + ks_l = ks_l - 1 + ps_l = p_int_norm_sam(ks_l) + end do + + ! Combine all SAM cells that this CAM_CELL(i, k) overlaps + var_cam(i,k) = 0.0 + if (ks_u == ks_l) then + ! CAM cell fully enclosed by SAM cell => match 'density' of SAM cell + var_cam(i,k) = var_cam(i,k) + var_sam(i,ks_u) + else + do c = ks_l, ks_u + if (c == ks_l) then + ! Bottom cell + var_cam(i,k) = var_cam(i,k) + (pc_l-p_int_norm_sam(ks_l+1))*var_sam(i,ks_l) / (pc_l - pc_u) + elseif (c == ks_u) then + ! Top cell + var_cam(i,k) = var_cam(i,k) + (p_int_norm_sam(ks_u)-pc_u)*var_sam(i, ks_u) / (pc_l - pc_u) + else + ! Intermediate cell (SAM Cell fully enclosed by CAM Cell => Absorb all) + var_cam(i,k) = var_cam(i,k) + (p_int_norm_sam(c)-p_int_norm_sam(c+1))*var_sam(i, c) / (pc_l - pc_u) + + endif + + enddo + endif + endif + end do + end do + + ! Clean up + deallocate(p_norm_cam) + deallocate(p_int_norm_cam) + + end subroutine interp_to_cam + + subroutine extrapolate_to_surface(p_cam, var_cam, p_surf_cam, var_surf_cam) + !! Extrapolate variable profile to the surface, required for interpolation. + + != unit Pa :: p_cam + real(8), dimension(:,:), intent(in) :: p_cam + !! pressure [Pa] from the CAM model + real(8), dimension(:), intent(in) :: p_surf_cam + !! surface pressures [Pa] for each column in CAM + != unit 1 :: var_cam + real(8), dimension(:,:), intent(in) :: var_cam + !! variable from CAM grid to be interpolated from + real(8), dimension(:), intent(out) :: var_surf_cam + !! variable from CAM grid to be interpolated from + + integer :: i, k + real(8) :: gdt(size(var_surf_cam)) + + gdt(:) = (var_cam(:,2) - var_cam(:,1)) / (p_cam(:,2) - p_cam(:,1)) + var_surf_cam(:) = var_cam(:,1) + gdt(:) * (p_surf_cam(:) - p_cam(:,1)) + + end subroutine extrapolate_to_surface + + subroutine sam_sounding_init(filename) + !! Read various profiles in from SAM sounding file + + ! This will be the netCDF ID for the file and data variable. + integer :: ncid, k + integer :: z_dimid, dz_dimid + integer :: z_varid, dz_varid, pres_varid, presi_varid, rho_varid, adz_varid + + character(len=136), intent(in) :: filename + !! NetCDF filename from which to read data + + + !-------------allocate arrays and read data------------------- + + ! Open the file. NF90_NOWRITE tells netCDF we want read-only + ! access + ! Get the varid or dimid for each variable or dimension based + ! on its name. + + call check( nf90_open(trim(filename),NF90_NOWRITE,ncid )) + + call check( nf90_inq_dimid(ncid, 'z', z_dimid)) + call check( nf90_inquire_dimension(ncid, z_dimid, len=nz_sam)) + ! call check( nf90_inq_dimid(ncid, 'dz', dz_dimid)) + ! call check( nf90_inquire_dimension(ncid, dz_dimid, len=ndz)) + + ! Note that nz of sounding may be longer than nrf + allocate(z(nz_sam)) + allocate(pres(nz_sam)) + allocate(presi(nz_sam)) + allocate(pres_mid(nz_sam)) + allocate(rho(nz_sam)) + allocate(adz(nz_sam)) + allocate(gamaz(nz_sam)) + + ! Read data in from nc file - convert pressures to hPa + call check( nf90_inq_varid(ncid, "z", z_varid)) + call check( nf90_get_var(ncid, z_varid, z)) + call check( nf90_inq_varid(ncid, "pressure", pres_varid)) + call check( nf90_get_var(ncid, pres_varid, pres)) + pres(:) = pres / 100.0 + call check( nf90_inq_varid(ncid, "interface_pressure", presi_varid)) + call check( nf90_get_var(ncid, presi_varid, presi)) + presi(:) = presi(:) / 100.0 + call check( nf90_inq_varid(ncid, "rho", rho_varid)) + call check( nf90_get_var(ncid, rho_varid, rho)) + call check( nf90_inq_varid(ncid, "adz", adz_varid)) + call check( nf90_get_var(ncid, adz_varid, adz)) + call check( nf90_inq_varid(ncid, "dz", dz_varid)) + call check( nf90_get_var(ncid, dz_varid, dz)) + + ! Close the nc file + call check( nf90_close(ncid)) + + ! Calculate gamaz required elsewhere + do k = 1, nz_sam + gamaz(k) = ggr/cp*z(k) + end do + + ! Calculate the pressure centre of the cell (as opposed to the altitude centre) + do k = 1, nz_sam-1 + pres_mid(k) = (presi(k) + presi(k+1)) / 2.0 + end do + pres_mid(nz_sam) = -9999.0 + + write(*,*) 'Finished reading SAM sounding file.' + + end subroutine sam_sounding_init + + + subroutine sam_sounding_finalize() + !! Deallocate module variables read from sounding + + deallocate(z, pres, presi, pres_mid, rho, adz, gamaz) + + end subroutine sam_sounding_finalize + + + subroutine CAM_var_conversion(qv, qc, qi, q, tabs, t) + !! Convert CAM qv, qc, qi to q to used by SAM parameterisation + !! q is total water qv/c/i is cloud vapor/liquid/ice + !! Convert CAM absolute temperature to moist static energy t used by SAM + !! by inverting lines 49-53 of diagnose.f90 from SAM where + !! qn is cloud water + ice, qp is precipitable water (0 here) + + !! WARNING: This routine uses gamaz(k) which is defined on the SAM grid. + !! Using a grid that does not match the SAM grid for input/output + !! data will produce unphysical values. + + integer :: nx, nz + !! array sizes + integer :: i, k + !! Counters + real(8) :: omn + !! intermediate omn factor used in variable conversion + + ! --------------------- + ! Fields from CAM/SAM + ! --------------------- + != unit 1 :: q, qv, qc, qi + real(8), intent(out) :: q(:, :) + !! Total non-precipitating water mixing ratio as required by SAM NN + real(8), intent(in) :: qv(:, :) + !! Cloud water vapour from CAM + real(8), intent(in) :: qc(:, :) + !! Cloud water from CAM + real(8), intent(in) :: qi(:, :) + !! Cloud ice from CAM + + real(8), intent(out) :: t(:, :) + !! Static energy as required by SAM NN + real(8), intent(in) :: tabs(:, :) + !! Absolute temperature from CAM + + + nx = size(q, 1) + nz = size(q, 2) + + do k = 1, nz + do i = 1, nx + q(i,k) = qv(i,k) + qc(i,k) + qi(i,k) + + ! omp = max(0.,min(1.,(tabs(i,k)-tprmin)*a_pr)) + omn = max(0.,min(1.,(tabs(i,k)-tbgmin)*a_bg)) + t(i,k) = tabs(i,k) & + ! - (fac_cond+(1.-omp)*fac_fus)*qp(i,k) & ! There is no qp in CAM + - (fac_cond+(1.-omn)*fac_fus)*(qc(i,k) + qi(i,k)) & + + gamaz(k) + end do + end do + + end subroutine CAM_var_conversion + + + subroutine SAM_var_conversion(t, q, tabs, qv, qc, qi) + !! Convert SAM t and q to tabs, qv, qc, qi used by CAM + !! t is normalised liquid ice static energy, q is total water + !! tabs is absolute temperature, q is cloud vapor/liquid/ice, + + integer :: nx, nz + !! array sizes + integer :: i, k, niter + !! Counters + + ! --------------------- + ! Fields from SAM/CAM + ! --------------------- + != unit K :: tabs, tabs1 + real(8), intent(inout) :: tabs(:, :) + !! absolute temperature + real(8) :: tabs1 + !! Temporary variable for tabs + + != unit kg/kg :: q, qn, qv, qc, qi + real(8) :: q(:, :) + !! Total non-precipitating water mixing ratio from SAM + real(8), intent(out) :: qv(:, :) + !! Cloud water vapour in CAM + real(8), intent(out) :: qc(:, :) + !! Cloud water (liquid) in CAM + real(8), intent(out) :: qi(:, :) + !! Cloud ice in CAM + real(8) :: qn + !! Cloud liquid + cloud ice + + != K :: t + real(8), intent(in) :: t(:, :) + !! normalised liquid ice static energy + + ! Intermediate variables + real(8) :: qsat, om, omn, dtabs, dqsat, lstarn, dlstarn, fff, dfff + + nx = size(tabs, 1) + nz = size(tabs, 2) + + do k = 1, nz + do i = 1, nx + + ! Enforce q >= 0.0 + q(i,k)=max(0.,q(i,k)) + + ! Initial guess for temperature assuming no cloud water/ice: + tabs(i,k) = t(i,k)-gamaz(k) + tabs1=tabs(i,k) + + ! Warm cloud: + if(tabs1.ge.tbgmax) then + qsat = qsatw(tabs1,pres_mid(k)) + + ! Ice cloud: + elseif(tabs1.le.tbgmin) then + qsat = qsati(tabs1,pres_mid(k)) + + ! Mixed-phase cloud: + else + om = an*tabs1-bn + qsat = om*qsatw(tabs1,pres_mid(k))+(1.-om)*qsati(tabs1,pres_mid(k)) + + endif + + ! Test if condensation is possible and iterate: + if(q(i,k) .gt. qsat) then + niter=0 + dtabs = 100. + do while(abs(dtabs).gt.0.01.and.niter.lt.10) + if(tabs1.ge.tbgmax) then + om=1. + lstarn=fac_cond + dlstarn=0. + qsat=qsatw(tabs1,pres_mid(k)) + dqsat=dtqsatw(tabs1,pres_mid(k)) + else if(tabs1.le.tbgmin) then + om=0. + lstarn=fac_sub + dlstarn=0. + qsat=qsati(tabs1,pres_mid(k)) + dqsat=dtqsati(tabs1,pres_mid(k)) + else + om=an*tabs1-bn + lstarn=fac_cond+(1.-om)*fac_fus + dlstarn=an + qsat=om*qsatw(tabs1,pres_mid(k))+(1.-om)*qsati(tabs1,pres_mid(k)) + dqsat=om*dtqsatw(tabs1,pres_mid(k))+(1.-om)*dtqsati(tabs1,pres_mid(k)) + endif + + fff = tabs(i,k)-tabs1+lstarn*(q(i,k)-qsat) + dfff=dlstarn*(q(i,k)-qsat)-lstarn*dqsat-1. + dtabs=-fff/dfff + niter=niter+1 + tabs1=tabs1+dtabs + end do + + qsat = qsat + dqsat * dtabs + qn = max(0., q(i,k)-qsat) + qv(i,k) = max(0., q(i,k)-qn) + + ! If condensation not possible qn is 0.0 + else + qn = 0. + qv(i,k) = q(i,k) + + endif + + ! Set tabs to iterated tabs after convection + tabs(i,k) = tabs1 + + !! Code for calculating qcc and qii from qn. + !! Assumes dokruegermicro=.false. in SAM. + !! Taken from statistics.f90 + omn = omegan(tabs(i,k)) + qc(i,k) = qn*omn + qi(i,k) = qn*(1.-omn) + + end do + end do + + end subroutine SAM_var_conversion + +end module nn_interface_CAM diff --git a/src/physics/cam/physpkg.F90 b/src/physics/cam/physpkg.F90 index 7721a09682..a141e6d542 100644 --- a/src/physics/cam/physpkg.F90 +++ b/src/physics/cam/physpkg.F90 @@ -747,6 +747,7 @@ subroutine phys_init( phys_state, phys_tend, pbuf2d, cam_out ) use dadadj_cam, only: dadadj_init use cam_abortutils, only: endrun use nudging, only: Nudge_Model, nudging_init + use yog_intr, only: yog_init ! Input/output arguments type(physics_state), pointer :: phys_state(:) @@ -933,6 +934,11 @@ subroutine phys_init( phys_state, phys_tend, pbuf2d, cam_out ) ! Initialize qneg3 and qneg4 call qneg_init() + ! Initialise YOG scheme + if (yog_scheme=='on') then + call yog_init() + end if + end subroutine phys_init ! @@ -1196,6 +1202,7 @@ subroutine phys_final( phys_state, phys_tend, pbuf2d ) use chemistry, only : chem_final use carma_intr, only : carma_final use wv_saturation, only : wv_sat_final + use yog_intr, only: yog_final !----------------------------------------------------------------------- ! ! Purpose: @@ -1216,6 +1223,9 @@ subroutine phys_final( phys_state, phys_tend, pbuf2d ) call chem_final call carma_final call wv_sat_final + if (yog_scheme=='on') then + call yog_final() + end if end subroutine phys_final @@ -1702,6 +1712,7 @@ subroutine tphysbc (ztodt, state, & use subcol, only: subcol_gen, subcol_ptend_avg use subcol_utils, only: subcol_ptend_copy, is_subcol_on use qneg_module, only: qneg3 + use yog_intr, only: yog_tend ! Arguments @@ -1803,6 +1814,8 @@ subroutine tphysbc (ztodt, state, & real(r8) :: zero_tracers(pcols,pcnst) logical :: lq(pcnst) + + real(r8) :: ftem(pcols,pver) ! Temporary workspace for outfld variables !----------------------------------------------------------------------- call t_startf('bc_init') @@ -1810,6 +1823,7 @@ subroutine tphysbc (ztodt, state, & zero = 0._r8 zero_tracers(:,:) = 0._r8 zero_sc(:) = 0._r8 + ftem = 0._r8 lchnk = state%lchnk ncol = state%ncol @@ -1925,6 +1939,7 @@ subroutine tphysbc (ztodt, state, & state, ptend, cam_in%landfrac, pbuf) call physics_update(state, ptend, ztodt, tend) + ! NB: If we want to discard ZM tendencies here we need to call a ptend_dealloc call t_stopf('convect_deep_tend') @@ -1950,6 +1965,18 @@ subroutine tphysbc (ztodt, state, & call check_energy_chng(state, tend, "convect_deep", nstep, ztodt, zero, flx_cnd, snow_dp, zero) snow_dp(:ncol) = snow_dp(:ncol) - rice(:ncol) + ! Yuval O'Gorman scheme + if (yog_scheme=='on') then + call t_startf('yog_nn') + + call yog_tend(ztodt, state, ptend) + + call physics_update(state, ptend, ztodt, tend) + call check_energy_chng(state, tend, "chkengyfix", nstep, ztodt, zero, zero, zero, flx_heat) + + call t_stopf('yog_nn') + end if + ! ! Call Hack (1994) convection scheme to deal with shallow/mid-level convection ! diff --git a/src/physics/cam/yog_intr.F90 b/src/physics/cam/yog_intr.F90 new file mode 100644 index 0000000000..6876a2da82 --- /dev/null +++ b/src/physics/cam/yog_intr.F90 @@ -0,0 +1,233 @@ +module yog_intr +!--------------------------------------------------------------------------------- +! Purpose: +! +! CAM interface to the Yoval-O'Gorman deep convection scheme. +! based off of the Zhang-McFarlane deep convection scheme +! +! Author: J.W. Atkinson +! January 2024 +!--------------------------------------------------------------------------------- + use shr_kind_mod, only: r8=>shr_kind_r8 + use physconst, only: cpair + use ppgrid, only: pver, pcols, pverp, begchunk, endchunk + use cam_abortutils, only: endrun + use physconst, only: pi + use spmd_utils, only: masterproc + use perf_mod + use cam_logfile, only: iulog + use constituents, only: cnst_add + + implicit none + private + save + + ! Public methods + + public ::& + yog_readnl, &! read namelist for YOG scheme/module + yog_init, &! initialize YOG scheme/module + yog_final, &! finalize YOG scheme/module + yog_tend ! return tendencies + + character(len=136) :: yog_nn_weights ! location of weights for the YOG NN, set in namelist + character(len=136) :: SAM_sounding ! location of SAM sounding profile for the YOG NN, set in namelist + +!========================================================================================= +contains +!========================================================================================= + +! There is currently no need for a yog_register as nothing to add to the physics buffer +! subroutine yog_register +! +! !---------------------------------------- +! ! Purpose: register fields with the physics buffer +! !---------------------------------------- +! +! use physics_buffer, only : pbuf_add_field, dtype_r8, dtype_i4 +! +! implicit none +! +! end subroutine yog_register + +!========================================================================================= + +subroutine yog_readnl(nlfile) + + use spmd_utils, only: mpi_character, masterprocid, mpicom + use namelist_utils, only: find_group_name + use units, only: getunit, freeunit + + character(len=*), intent(in) :: nlfile ! filepath for file containing namelist input + + ! Local variables + integer :: unitn, ierr + character(len=*), parameter :: subname = 'yog_readnl' + + namelist /yog_params_nl/ yog_nn_weights, SAM_sounding + !----------------------------------------------------------------------------- + + if (masterproc) then + unitn = getunit() + open( unitn, file=trim(nlfile), status='old' ) + call find_group_name(unitn, 'yog_params_nl', status=ierr) + if (ierr == 0) then + read(unitn, yog_params_nl, iostat=ierr) + if (ierr /= 0) then + call endrun(subname // ':: ERROR reading namelist') + end if + end if + close(unitn) + call freeunit(unitn) + + end if + + ! Broadcast namelist variables + call mpi_bcast(yog_nn_weights, len(yog_nn_weights), mpi_character, masterprocid, mpicom, ierr) + if (ierr /= 0) call endrun("yog_readnl: FATAL: mpi_bcast: yog_nn_weights") + call mpi_bcast(SAM_sounding, len(SAM_sounding), mpi_character, masterprocid, mpicom, ierr) + if (ierr /= 0) call endrun("yog_readnl: FATAL: mpi_bcast: SAM_sounding") + +end subroutine yog_readnl + +!========================================================================================= + +subroutine yog_init() + +!---------------------------------------- +! Purpose: declare output fields, initialize variables needed by convection +!---------------------------------------- + + use cam_history, only: addfld, add_default, horiz_only + use nn_interface_CAM, only: nn_convection_flux_CAM_init + + use spmd_utils, only: masterproc + use phys_control, only: phys_getopts + + implicit none + + integer istat + logical :: history_budget ! output tendencies and state variables for + ! temperature, water vapor, cloud ice and cloud + ! liquid budgets. + integer :: history_budget_histfile_num ! output history file number for budget fields + + ! Register fields with the output buffer + call addfld ('YOGDT ', (/ 'lev' /), 'A', 'K/s','T tendency - Yuval-OGorman moist convection') + call addfld ('YOGDQ ', (/ 'lev' /), 'A', 'kg/kg/s','Q tendency - Yuval-OGorman moist convection') + call addfld ('YOGDICE', (/ 'lev' /), 'A', 'kg/kg/s','Cloud ice tendency - Yuval-OGorman convection') + call addfld ('YOGDLIQ', (/ 'lev' /), 'A', 'kg/kg/s','Cloud liq tendency - Yuval-OGorman convection') + call addfld ('YOGPREC', horiz_only , 'A', 'm/s','Surface preciptation - Yuval-OGorman convection') + if (masterproc) then + write(iulog,*)'YOG output fields added to buffer' + end if + + call phys_getopts( history_budget_out = history_budget, & + history_budget_histfile_num_out = history_budget_histfile_num) + + if ( history_budget ) then + call add_default('YOGDT ', history_budget_histfile_num, ' ') + call add_default('YOGDQ ', history_budget_histfile_num, ' ') + call add_default('YOGDICE', history_budget_histfile_num, ' ') + call add_default('YOGDLIQ', history_budget_histfile_num, ' ') + call add_default('YOGPREC', history_budget_histfile_num, ' ') + end if + + call nn_convection_flux_CAM_init(yog_nn_weights, SAM_sounding) + if (masterproc) then + write(iulog,*)'yog_nn_weights at: ', yog_nn_weights + write(iulog,*)'SAM_sounding at: ', SAM_sounding + write(iulog,*)'YOG scheme initialised' + endif + +end subroutine yog_init + +!========================================================================================= + +subroutine yog_final() + +!---------------------------------------- +! Purpose: finalization of the YOG scheme +!---------------------------------------- + + use nn_interface_CAM, only: nn_convection_flux_CAM_finalize + + implicit none + + call nn_convection_flux_CAM_finalize() + +end subroutine yog_final + +!========================================================================================= + +subroutine yog_tend(ztodt, state, ptend) + +!---------------------------------------- +! Purpose: tendency calculation for YOG scheme +!---------------------------------------- + + use cam_history, only: outfld + use physics_types, only: physics_state, physics_ptend + use physics_types, only: physics_ptend_init + use constituents, only: pcnst, cnst_get_ind + + use nn_interface_CAM, only: nn_convection_flux_CAM + + ! Arguments + + type(physics_state), intent(in) :: state ! Physics state variables + type(physics_ptend), intent(out) :: ptend ! individual parameterization tendencies + + real(r8), intent(in) :: ztodt ! 2 delta t (model time increment) + + ! Local variables + + integer :: i + integer :: nstep + integer :: ixcldice, ixcldliq ! constituent indices for cloud liquid and ice water. + integer :: lchnk ! chunk identifier + integer :: ncol ! number of atmospheric columns + + real(r8) :: ftem(pcols,pver) ! Temporary workspace for outfld variables + + logical :: lq(pcnst) + + ! physics buffer fields + + real(r8), pointer, dimension(:) :: prec ! total precipitation + real(r8), pointer, dimension(:) :: snow ! snow + real(r8), pointer, dimension(:,:) :: cld + + real(r8) :: yog_precsfc(pcols) ! scattered precip flux at each level + + lchnk = state%lchnk + ncol = state%ncol + + call cnst_get_ind('CLDLIQ', ixcldliq) + call cnst_get_ind('CLDICE', ixcldice) + + lq(:) = .true. + call physics_ptend_init(ptend, state%psetcols, 'yogNN', ls=.true., lq=lq(:))! initialize ptend type for YOG + + call nn_convection_flux_CAM(state%pmid(:,pver:1:-1), state%pint(:,pverp:1:-1), state%ps, & + state%t(:,pver:1:-1), state%q(:,pver:1:-1,1), & + state%q(:,pver:1:-1,ixcldliq), state%q(:,pver:1:-1,ixcldice), & + cpair, & + ztodt, & + ncol, pver, & + 1, 1, 1, & + yog_precsfc, & + ptend%q(:,pver:1:-1,ixcldice), ptend%q(:,pver:1:-1,1), & + ptend%q(:,pver:1:-1,ixcldliq), ptend%s(:,pver:1:-1)) + + ftem(:ncol,:pver) = ptend%s(:ncol,:pver)/cpair + call outfld('YOGDT ',ftem ,pcols ,lchnk ) + call outfld('YOGDQ ',ptend%q(1,1,1) ,pcols ,lchnk ) + call outfld('YOGDICE ',ptend%q(1,1,ixcldice) ,pcols ,lchnk ) + call outfld('YOGDLIQ ',ptend%q(1,1,ixcldliq) ,pcols ,lchnk ) + call outfld('YOGPREC ',yog_precsfc ,pcols ,lchnk ) +end subroutine yog_tend + +!========================================================================================= + +end module yog_intr