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