diff --git a/compile.sh b/compile.sh index 66a7c5a3b5bab8e4077652612b289600062d5e8c..22b260f68b85411e2cd6580d3157324367508425 100755 --- a/compile.sh +++ b/compile.sh @@ -14,5 +14,5 @@ gfortran -c -cpp -Wuninitialized srcF/sfx_esm_param.f90 gfortran -c -cpp -Wuninitialized srcF/sfx_esm.f90 gfortran -c -cpp -Wuninitialized srcF/sfx_main.f90 -gfortran -o sfx.exe sfx_main.o sfx_log.o sfx_log_param.o sfx_esm.o sfx_esm_param.o sfx_surface.o sfx_data.o sfx_io.o sfx_common.o sfx_phys_const.o +gfortran -o sfx.exe sfx_main.o sfx_log.o sfx_log_param.o sfx_most.o sfx_most_param.o sfx_sheba.o sfx_sheba_param.o sfx_esm.o sfx_esm_param.o sfx_surface.o sfx_data.o sfx_io.o sfx_common.o sfx_phys_const.o rm *.o *.mod diff --git a/makefile b/makefile index 5a0fd984f23733e423903686cbc14074c232f9d2..c14265ba2925727ed10e4ac59049fb4e6db02d46 100644 --- a/makefile +++ b/makefile @@ -10,7 +10,7 @@ ifeq ($(COMPILER),gnu) FC = gfortran endif -OBJ_F90 = sfx_phys_const.o sfx_common.o sfx_io.o sfx_data.o sfx_surface.o sfx_log_param.o sfx_log.o sfx_esm_param.o sfx_esm.o sfx_main.o +OBJ_F90 = sfx_phys_const.o sfx_common.o sfx_io.o sfx_data.o sfx_surface.o sfx_log_param.o sfx_log.o sfx_most_param.o sfx_most.o sfx_sheba_param.o sfx_sheba.o sfx_esm_param.o sfx_esm.o sfx_main.o OBJ_F = OBJ = $(OBJ_F90) $(OBJ_F) diff --git a/srcF/sfx_main.f90 b/srcF/sfx_main.f90 index 7900d484373c3bea0d99d95adb63d86b2a0791a4..40d791265d0e255808181beee797af9dd7739447 100644 --- a/srcF/sfx_main.f90 +++ b/srcF/sfx_main.f90 @@ -13,6 +13,12 @@ program sfx_main use sfx_log, only: & get_surface_fluxes_vec_log => get_surface_fluxes_vec, & numericsType_log => numericsType + use sfx_most, only: & + get_surface_fluxes_vec_most => get_surface_fluxes_vec, & + numericsType_most => numericsType + use sfx_sheba, only: & + get_surface_fluxes_vec_sheba => get_surface_fluxes_vec, & + numericsType_sheba => numericsType ! -------------------------------------------------------------------------------- ! directives list @@ -33,6 +39,8 @@ program sfx_main character(len = 256) :: model_name integer, parameter :: model_esm = 0 !< ESM model integer, parameter :: model_log = 1 !< LOG simplified model + integer, parameter :: model_most = 2 !< MOST simplified model + integer, parameter :: model_sheba = 3 !< SHEBA simplified model ! input/output data ! -------------------------------------------------------------------------------- @@ -41,8 +49,10 @@ program sfx_main type(sfxDataVecType) :: sfx !< surface fluxes (output) - type(numericsType_esm) :: numerics_esm !< surface flux module (ESM) numerics parameters - type(numericsType_log) :: numerics_log !< surface flux module (LOG) numerics parameters + type(numericsType_esm) :: numerics_esm !< surface flux module (ESM) numerics parameters + type(numericsType_log) :: numerics_log !< surface flux module (LOG) numerics parameters + type(numericsType_most) :: numerics_most !< surface flux module (MOST) numerics parameters + type(numericsType_sheba) :: numerics_sheba !< surface flux module (SHEBA) numerics parameters integer :: num !< number of 'cells' in input @@ -64,13 +74,15 @@ program sfx_main character(len = 128), parameter :: arg_key_nmax = '--nmax' character(len = 128), parameter :: arg_key_help = '--help' - character(len = 128), parameter :: arg_key_esm = 'esm' - character(len = 128), parameter :: arg_key_log = 'log' + character(len = 128), parameter :: arg_key_model_esm = 'esm' + character(len = 128), parameter :: arg_key_model_log = 'log' + character(len = 128), parameter :: arg_key_model_most = 'most' + character(len = 128), parameter :: arg_key_model_sheba = 'sheba' - character(len = 128), parameter :: arg_key_mosaic = 'mosaic' - character(len = 128), parameter :: arg_key_irgason = 'irgason' - character(len = 128), parameter :: arg_key_sheba = 'sheba' - character(len = 128), parameter :: arg_key_user = 'user' + character(len = 128), parameter :: arg_key_dataset_mosaic = 'mosaic' + character(len = 128), parameter :: arg_key_dataset_irgason = 'irgason' + character(len = 128), parameter :: arg_key_dataset_sheba = 'sheba' + character(len = 128), parameter :: arg_key_dataset_user = 'user' integer :: is_output_set integer :: nmax @@ -97,9 +109,9 @@ program sfx_main write(*, *) ' --help ' write(*, *) ' print usage options ' write(*, *) ' --model [key]' - write(*, *) ' key = esm || log' + write(*, *) ' key = esm (default) || log || most || sheba' write(*, *) ' --dataset [key]' - write(*, *) ' key = mosaic || irgason || sheba || user [files]' + write(*, *) ' key = mosaic (default) || irgason || sheba || user [files]' write(*, *) ' files = in-common-file in-file out-file' write(*, *) ' --output [file]' write(*, *) ' set output filename ' @@ -113,10 +125,14 @@ program sfx_main stop end if call get_command_argument(i + 1, arg) - if (trim(arg) == trim(arg_key_esm)) then + if (trim(arg) == trim(arg_key_model_esm)) then model_id = model_esm - else if (trim(arg) == trim(arg_key_log)) then + else if (trim(arg) == trim(arg_key_model_log)) then model_id = model_log + else if (trim(arg) == trim(arg_key_model_most)) then + model_id = model_most + else if (trim(arg) == trim(arg_key_model_sheba)) then + model_id = model_sheba else write(*, *) ' FAILURE! > unknown model [key]: ', trim(arg) stop @@ -128,13 +144,13 @@ program sfx_main stop end if call get_command_argument(i + 1, arg) - if (trim(arg) == trim(arg_key_mosaic)) then + if (trim(arg) == trim(arg_key_dataset_mosaic)) then dataset_id = dataset_MOSAiC - else if (trim(arg) == trim(arg_key_irgason)) then + else if (trim(arg) == trim(arg_key_dataset_irgason)) then dataset_id = dataset_IRGASON - else if (trim(arg) == trim(arg_key_sheba)) then + else if (trim(arg) == trim(arg_key_dataset_sheba)) then dataset_id = dataset_SHEBA - else if (trim(arg) == trim(arg_key_user)) then + else if (trim(arg) == trim(arg_key_dataset_user)) then dataset_id = dataset_USER if (i + 4 > num_args) then write(*, *) ' FAILURE! > incorrect arguments for [user] dataset' @@ -180,6 +196,10 @@ program sfx_main model_name = "ESM" else if (model_id == model_log) then model_name = "LOG" + else if (model_id == model_most) then + model_name = "MOST" + else if (model_id == model_sheba) then + model_name = "SHEBA" else write(*, *) ' FAILURE! > unknown model id: ', model_id stop @@ -270,6 +290,10 @@ program sfx_main call get_surface_fluxes_vec_esm(sfx, meteo, numerics_esm, num) else if (model_id == model_log) then call get_surface_fluxes_vec_log(sfx, meteo, numerics_log, num) + else if (model_id == model_most) then + call get_surface_fluxes_vec_most(sfx, meteo, numerics_most, num) + else if (model_id == model_sheba) then + call get_surface_fluxes_vec_sheba(sfx, meteo, numerics_sheba, num) end if diff --git a/srcF/sfx_most.f90 b/srcF/sfx_most.f90 new file mode 100644 index 0000000000000000000000000000000000000000..799cf43d671596cd1de134899b53282c23ee2124 --- /dev/null +++ b/srcF/sfx_most.f90 @@ -0,0 +1,347 @@ +#include "../includeF/sfx_def.fi" + +module sfx_most + !< @brief MOST surface flux module + + ! modules used + ! -------------------------------------------------------------------------------- +#ifdef SFX_CHECK_NAN + use sfx_common +#endif + use sfx_data + use sfx_surface + use sfx_most_param + ! -------------------------------------------------------------------------------- + + ! directives list + ! -------------------------------------------------------------------------------- + implicit none + private + ! -------------------------------------------------------------------------------- + + ! public interface + ! -------------------------------------------------------------------------------- + public :: get_surface_fluxes + public :: get_surface_fluxes_vec + ! -------------------------------------------------------------------------------- + + ! -------------------------------------------------------------------------------- + type, public :: numericsType + integer :: maxiters_charnock = 10 !< maximum (actual) number of iterations in charnock roughness + end type + ! -------------------------------------------------------------------------------- + +contains + + ! -------------------------------------------------------------------------------- + subroutine get_surface_fluxes_vec(sfx, meteo, numerics, n) + !< @brief surface flux calculation for array data + !< @details contains C/C++ & CUDA interface + ! ---------------------------------------------------------------------------- + type (sfxDataVecType), intent(inout) :: sfx + + type (meteoDataVecType), intent(in) :: meteo + type (numericsType), intent(in) :: numerics + integer, intent(in) :: n + ! ---------------------------------------------------------------------------- + + ! --- local variables + type (meteoDataType) meteo_cell + type (sfxDataType) sfx_cell + integer i + ! ---------------------------------------------------------------------------- + + do i = 1, n + meteo_cell = meteoDataType(& + h = meteo%h(i), & + U = meteo%U(i), dT = meteo%dT(i), Tsemi = meteo%Tsemi(i), dQ = meteo%dQ(i), & + z0_m = meteo%z0_m(i)) + + call get_surface_fluxes(sfx_cell, meteo_cell, numerics) + + call push_sfx_data(sfx, sfx_cell, i) + end do + + end subroutine get_surface_fluxes_vec + ! -------------------------------------------------------------------------------- + + ! -------------------------------------------------------------------------------- + subroutine get_surface_fluxes(sfx, meteo, numerics) + !< @brief surface flux calculation for single cell + !< @details contains C/C++ interface + ! ---------------------------------------------------------------------------- +#ifdef SFX_CHECK_NAN + use ieee_arithmetic +#endif + + type (sfxDataType), intent(out) :: sfx + + type (meteoDataType), intent(in) :: meteo + type (numericsType), intent(in) :: numerics + ! ---------------------------------------------------------------------------- + + ! --- meteo derived datatype name shadowing + ! ---------------------------------------------------------------------------- + real :: h !< constant flux layer height [m] + real :: U !< abs(wind speed) at 'h' [m/s] + real :: dT !< difference between potential temperature at 'h' and at surface [K] + real :: Tsemi !< semi-sum of potential temperature at 'h' and at surface [K] + real :: dQ !< difference between humidity at 'h' and at surface [g/g] + real :: z0_m !< surface aerodynamic roughness (should be < 0 for water bodies surface) + ! ---------------------------------------------------------------------------- + + ! --- local variables + ! ---------------------------------------------------------------------------- + real z0_t !< thermal roughness [m] + real B !< = ln(z0_m / z0_t) [n/d] + real h0_m, h0_t !< = h / z0_m, h / z0_h [n/d] + + real u_dyn0 !< dynamic velocity in neutral conditions [m/s] + real Re !< roughness Reynolds number = u_dyn0 * z0_m / nu [n/d] + + real zeta !< = z/L [n/d] + real Rib !< bulk Richardson number + + real Udyn, Tdyn, Qdyn !< dynamic scales + + real phi_m, phi_h !< stability functions (momentum) & (heat) [n/d] + + real Km !< eddy viscosity coeff. at h [m^2/s] + real Pr_t_inv !< invese Prandt number [n/d] + + real Cm, Ct !< transfer coeff. for (momentum) & (heat) [n/d] + + integer surface_type !< surface type = (ocean || land) + + +#ifdef SFX_CHECK_NAN + real NaN +#endif + ! ---------------------------------------------------------------------------- + +#ifdef SFX_CHECK_NAN + ! --- checking if arguments are finite + if (.not.(is_finite(meteo%U).and.is_finite(meteo%Tsemi).and.is_finite(meteo%dT).and.is_finite(meteo%dQ) & + .and.is_finite(meteo%z0_m).and.is_finite(meteo%h))) then + + NaN = ieee_value(0.0, ieee_quiet_nan) ! setting NaN + sfx = sfxDataType(zeta = NaN, Rib = NaN, & + Re = NaN, B = NaN, z0_m = NaN, z0_t = NaN, & + Rib_conv_lim = NaN, & + Cm = NaN, Ct = NaN, Km = NaN, Pr_t_inv = NaN) + return + end if +#endif + + ! --- shadowing names for clarity + U = meteo%U + Tsemi = meteo%Tsemi + dT = meteo%dT + dQ = meteo%dQ + h = meteo%h + z0_m = meteo%z0_m + + ! --- define surface type + if (z0_m < 0.0) then + surface_type = surface_ocean + else + surface_type = surface_land + end if + + if (surface_type == surface_ocean) then + ! --- define surface roughness [momentum] & dynamic velocity in neutral conditions + call get_charnock_roughness(z0_m, u_dyn0, U, h, numerics%maxiters_charnock) + ! --- define relative height + h0_m = h / z0_m + endif + if (surface_type == surface_land) then + ! --- define relative height + h0_m = h / z0_m + ! --- define dynamic velocity in neutral conditions + u_dyn0 = U * kappa / log(h0_m) + end if + + ! --- define thermal roughness & B = log(z0_m / z0_h) + Re = u_dyn0 * z0_m / nu_air + call get_thermal_roughness(z0_t, B, z0_m, Re, surface_type) + + ! --- define relative height [thermal] + h0_t = h / z0_t + + ! --- define Ri-bulk + Rib = (g / Tsemi) * h * (dT + 0.61e0 * Tsemi * dQ) / U**2 + + ! --- get the fluxes + ! ---------------------------------------------------------------------------- + call get_dynamic_scales(Udyn, Tdyn, Qdyn, zeta, & + U, Tsemi, dT, dQ, h, z0_m, z0_t, (g / Tsemi), 10) + ! ---------------------------------------------------------------------------- + + call get_phi(phi_m, phi_h, zeta) + ! ---------------------------------------------------------------------------- + + ! --- define transfer coeff. (momentum) & (heat) + Cm = 0.0 + if (U > 0.0) then + Cm = Udyn / U + end if + Ct = 0.0 + if (abs(dT) > 0.0) then + Ct = Tdyn / dT + end if + + ! --- define eddy viscosity & inverse Prandtl number + Km = kappa * Cm * U * h / phi_m + Pr_t_inv = phi_m / phi_h + + ! --- setting output + sfx = sfxDataType(zeta = zeta, Rib = Rib, & + Re = Re, B = B, z0_m = z0_m, z0_t = z0_t, & + Rib_conv_lim = 0.0, & + Cm = Cm, Ct = Ct, Km = Km, Pr_t_inv = Pr_t_inv) + + end subroutine get_surface_fluxes + ! -------------------------------------------------------------------------------- + + !< @brief get dynamic scales + ! -------------------------------------------------------------------------------- + subroutine get_dynamic_scales(Udyn, Tdyn, Qdyn, zeta, & + U, Tsemi, dT, dQ, z, z0_m, z0_t, beta, maxiters) + ! ---------------------------------------------------------------------------- + real, intent(out) :: Udyn, Tdyn, Qdyn !< dynamic scales + real, intent(out) :: zeta !< = z/L + + real, intent(in) :: U !< abs(wind speed) at z + real, intent(in) :: Tsemi !< semi-sum of temperature at z and at surface + real, intent(in) :: dT, dQ !< temperature & humidity difference between z and at surface + real, intent(in) :: z !< constant flux layer height + real, intent(in) :: z0_m, z0_t !< roughness parameters + real, intent(in) :: beta !< buoyancy parameter + + integer, intent(in) :: maxiters !< maximum number of iterations + ! ---------------------------------------------------------------------------- + + ! --- local variables + real, parameter :: gamma = 0.61 + + real :: psi_m, psi_h + real :: psi0_m, psi0_h + real :: Linv + integer :: i + ! ---------------------------------------------------------------------------- + + + Udyn = kappa * U / log(z / z0_m) + Tdyn = kappa * dT * Pr_t_0_inv / log(z / z0_t) + Qdyn = kappa * dQ * Pr_t_0_inv / log(z / z0_t) + zeta = 0.0 + + ! --- no wind + if (Udyn < 1e-5) return + + Linv = kappa * beta * (Tdyn + gamma * Qdyn * Tsemi) / (Udyn * Udyn) + zeta = z * Linv + + ! --- near neutral case + if (Linv < 1e-5) return + + do i = 1, maxiters + + call get_psi(psi_m, psi_h, zeta) + call get_psi_mh(psi0_m, psi0_h, z0_m * Linv, z0_t * Linv) + + Udyn = kappa * U / (log(z / z0_m) - (psi_m - psi0_m)) + Tdyn = kappa * dT * Pr_t_0_inv / (log(z / z0_t) - (psi_h - psi0_h)) + Qdyn = kappa * dQ * Pr_t_0_inv / (log(z / z0_t) - (psi_h - psi0_h)) + + if (Udyn < 1e-5) exit + + Linv = kappa * beta * (Tdyn + gamma * Qdyn * Tsemi) / (Udyn * Udyn) + zeta = z * Linv + end do + + end subroutine get_dynamic_scales + ! -------------------------------------------------------------------------------- + + ! stability functions + ! -------------------------------------------------------------------------------- + subroutine get_phi(phi_m, phi_h, zeta) + !< @brief stability functions (momentum) & (heat): neutral case + ! ---------------------------------------------------------------------------- + real, intent(out) :: phi_m, phi_h !< stability functions + + real, intent(in) :: zeta !< = z/L + ! ---------------------------------------------------------------------------- + + + if (zeta >= 0.0) then + phi_m = 1.0 + beta_m * zeta + phi_h = 1.0 + beta_h * zeta + else + phi_m = (1.0 - alpha_m * zeta)**(-0.25) + phi_h = (1.0 - alpha_h * zeta)**(-0.5) + end if + + end subroutine + ! -------------------------------------------------------------------------------- + + ! universal functions + ! -------------------------------------------------------------------------------- + subroutine get_psi(psi_m, psi_h, zeta) + !< @brief universal functions (momentum) & (heat): neutral case + ! ---------------------------------------------------------------------------- + real, intent(out) :: psi_m, psi_h !< universal functions + + real, intent(in) :: zeta !< = z/L + ! ---------------------------------------------------------------------------- + + ! --- local variables + real :: x_m, x_h + ! ---------------------------------------------------------------------------- + + + if (zeta >= 0.0) then + psi_m = -beta_m * zeta; + psi_h = -beta_h * zeta; + else + x_m = (1.0 - alpha_m * zeta)**(0.25) + x_h = (1.0 - alpha_h * zeta)**(0.25) + + psi_m = (4.0 * atan(1.0) / 2.0) + 2.0 * log(0.5 * (1.0 + x_m)) + log(0.5 * (1.0 + x_m * x_m)) - 2.0 * atan(x_m) + psi_h = 2.0 * log(0.5 * (1.0 + x_h * x_h)) + end if + + end subroutine + + + subroutine get_psi_mh(psi_m, psi_h, zeta_m, zeta_h) + !< @brief universal functions (momentum) & (heat): neutral case + ! ---------------------------------------------------------------------------- + real, intent(out) :: psi_m, psi_h !< universal functions + + real, intent(in) :: zeta_m, zeta_h !< = z/L + ! ---------------------------------------------------------------------------- + + ! --- local variables + real :: x_m, x_h + ! ---------------------------------------------------------------------------- + + + if (zeta_m >= 0.0) then + psi_m = -beta_m * zeta_m + else + x_m = (1.0 - alpha_m * zeta_m)**(0.25) + psi_m = (4.0 * atan(1.0) / 2.0) + 2.0 * log(0.5 * (1.0 + x_m)) + log(0.5 * (1.0 + x_m * x_m)) - 2.0 * atan(x_m) + end if + + if (zeta_h >= 0.0) then + psi_h = -beta_h * zeta_h; + else + x_h = (1.0 - alpha_h * zeta_h)**(0.25) + psi_h = 2.0 * log(0.5 * (1.0 + x_h * x_h)) + end if + + end subroutine + ! -------------------------------------------------------------------------------- + +end module sfx_most \ No newline at end of file diff --git a/srcF/sfx_most_param.f90 b/srcF/sfx_most_param.f90 new file mode 100644 index 0000000000000000000000000000000000000000..51b34fd6610b2ea551017e8ae36e29517ac1ae1d --- /dev/null +++ b/srcF/sfx_most_param.f90 @@ -0,0 +1,30 @@ +module sfx_most_param + !< @brief MOST BD71 surface flux model parameters + !< @details all in SI units + + ! modules used + ! -------------------------------------------------------------------------------- + use sfx_phys_const + ! -------------------------------------------------------------------------------- + + ! directives list + ! -------------------------------------------------------------------------------- + implicit none + ! -------------------------------------------------------------------------------- + + + !< von Karman constant [n/d] + real, parameter :: kappa = 0.40 + !< inverse Prandtl (turbulent) number in neutral conditions [n/d] + real, parameter :: Pr_t_0_inv = 1.15 + + + !< stability function coeff. (unstable) + real, parameter :: alpha_m = 16.0 + real, parameter :: alpha_h = 16.0 + + !< stability function coeff. (stable) + real, parameter :: beta_m = 4.7 + real, parameter :: beta_h = beta_m + +end module sfx_most_param diff --git a/srcF/sfx_sheba.f90 b/srcF/sfx_sheba.f90 new file mode 100644 index 0000000000000000000000000000000000000000..26231a763c69ec0c2f216c92278ce71268a12c71 --- /dev/null +++ b/srcF/sfx_sheba.f90 @@ -0,0 +1,379 @@ +#include "../includeF/sfx_def.fi" + +module sfx_sheba + !< @brief SHEBA surface flux module + + ! modules used + ! -------------------------------------------------------------------------------- +#ifdef SFX_CHECK_NAN + use sfx_common +#endif + use sfx_data + use sfx_surface + use sfx_sheba_param + ! -------------------------------------------------------------------------------- + + ! directives list + ! -------------------------------------------------------------------------------- + implicit none + private + ! -------------------------------------------------------------------------------- + + ! public interface + ! -------------------------------------------------------------------------------- + public :: get_surface_fluxes + public :: get_surface_fluxes_vec + ! -------------------------------------------------------------------------------- + + ! -------------------------------------------------------------------------------- + type, public :: numericsType + integer :: maxiters_charnock = 10 !< maximum (actual) number of iterations in charnock roughness + end type + ! -------------------------------------------------------------------------------- + +contains + + ! -------------------------------------------------------------------------------- + subroutine get_surface_fluxes_vec(sfx, meteo, numerics, n) + !< @brief surface flux calculation for array data + !< @details contains C/C++ & CUDA interface + ! ---------------------------------------------------------------------------- + type (sfxDataVecType), intent(inout) :: sfx + + type (meteoDataVecType), intent(in) :: meteo + type (numericsType), intent(in) :: numerics + integer, intent(in) :: n + ! ---------------------------------------------------------------------------- + + ! --- local variables + type (meteoDataType) meteo_cell + type (sfxDataType) sfx_cell + integer i + ! ---------------------------------------------------------------------------- + + do i = 1, n + meteo_cell = meteoDataType(& + h = meteo%h(i), & + U = meteo%U(i), dT = meteo%dT(i), Tsemi = meteo%Tsemi(i), dQ = meteo%dQ(i), & + z0_m = meteo%z0_m(i)) + + call get_surface_fluxes(sfx_cell, meteo_cell, numerics) + + call push_sfx_data(sfx, sfx_cell, i) + end do + + end subroutine get_surface_fluxes_vec + ! -------------------------------------------------------------------------------- + + ! -------------------------------------------------------------------------------- + subroutine get_surface_fluxes(sfx, meteo, numerics) + !< @brief surface flux calculation for single cell + !< @details contains C/C++ interface + ! ---------------------------------------------------------------------------- +#ifdef SFX_CHECK_NAN + use ieee_arithmetic +#endif + + type (sfxDataType), intent(out) :: sfx + + type (meteoDataType), intent(in) :: meteo + type (numericsType), intent(in) :: numerics + ! ---------------------------------------------------------------------------- + + ! --- meteo derived datatype name shadowing + ! ---------------------------------------------------------------------------- + real :: h !< constant flux layer height [m] + real :: U !< abs(wind speed) at 'h' [m/s] + real :: dT !< difference between potential temperature at 'h' and at surface [K] + real :: Tsemi !< semi-sum of potential temperature at 'h' and at surface [K] + real :: dQ !< difference between humidity at 'h' and at surface [g/g] + real :: z0_m !< surface aerodynamic roughness (should be < 0 for water bodies surface) + ! ---------------------------------------------------------------------------- + + ! --- local variables + ! ---------------------------------------------------------------------------- + real z0_t !< thermal roughness [m] + real B !< = ln(z0_m / z0_t) [n/d] + real h0_m, h0_t !< = h / z0_m, h / z0_h [n/d] + + real u_dyn0 !< dynamic velocity in neutral conditions [m/s] + real Re !< roughness Reynolds number = u_dyn0 * z0_m / nu [n/d] + + real zeta !< = z/L [n/d] + real Rib !< bulk Richardson number + + real Udyn, Tdyn, Qdyn !< dynamic scales + + real phi_m, phi_h !< stability functions (momentum) & (heat) [n/d] + + real Km !< eddy viscosity coeff. at h [m^2/s] + real Pr_t_inv !< invese Prandt number [n/d] + + real Cm, Ct !< transfer coeff. for (momentum) & (heat) [n/d] + + integer surface_type !< surface type = (ocean || land) + + +#ifdef SFX_CHECK_NAN + real NaN +#endif + ! ---------------------------------------------------------------------------- + +#ifdef SFX_CHECK_NAN + ! --- checking if arguments are finite + if (.not.(is_finite(meteo%U).and.is_finite(meteo%Tsemi).and.is_finite(meteo%dT).and.is_finite(meteo%dQ) & + .and.is_finite(meteo%z0_m).and.is_finite(meteo%h))) then + + NaN = ieee_value(0.0, ieee_quiet_nan) ! setting NaN + sfx = sfxDataType(zeta = NaN, Rib = NaN, & + Re = NaN, B = NaN, z0_m = NaN, z0_t = NaN, & + Rib_conv_lim = NaN, & + Cm = NaN, Ct = NaN, Km = NaN, Pr_t_inv = NaN) + return + end if +#endif + + ! --- shadowing names for clarity + U = meteo%U + Tsemi = meteo%Tsemi + dT = meteo%dT + dQ = meteo%dQ + h = meteo%h + z0_m = meteo%z0_m + + ! --- define surface type + if (z0_m < 0.0) then + surface_type = surface_ocean + else + surface_type = surface_land + end if + + if (surface_type == surface_ocean) then + ! --- define surface roughness [momentum] & dynamic velocity in neutral conditions + call get_charnock_roughness(z0_m, u_dyn0, U, h, numerics%maxiters_charnock) + ! --- define relative height + h0_m = h / z0_m + endif + if (surface_type == surface_land) then + ! --- define relative height + h0_m = h / z0_m + ! --- define dynamic velocity in neutral conditions + u_dyn0 = U * kappa / log(h0_m) + end if + + ! --- define thermal roughness & B = log(z0_m / z0_h) + Re = u_dyn0 * z0_m / nu_air + call get_thermal_roughness(z0_t, B, z0_m, Re, surface_type) + + ! --- define relative height [thermal] + h0_t = h / z0_t + + ! --- define Ri-bulk + Rib = (g / Tsemi) * h * (dT + 0.61e0 * Tsemi * dQ) / U**2 + + ! --- get the fluxes + ! ---------------------------------------------------------------------------- + call get_dynamic_scales(Udyn, Tdyn, Qdyn, zeta, & + U, Tsemi, dT, dQ, h, z0_m, z0_t, (g / Tsemi), 10) + ! ---------------------------------------------------------------------------- + + call get_phi(phi_m, phi_h, zeta) + ! ---------------------------------------------------------------------------- + + ! --- define transfer coeff. (momentum) & (heat) + Cm = 0.0 + if (U > 0.0) then + Cm = Udyn / U + end if + Ct = 0.0 + if (abs(dT) > 0.0) then + Ct = Tdyn / dT + end if + + ! --- define eddy viscosity & inverse Prandtl number + Km = kappa * Cm * U * h / phi_m + Pr_t_inv = phi_m / phi_h + + ! --- setting output + sfx = sfxDataType(zeta = zeta, Rib = Rib, & + Re = Re, B = B, z0_m = z0_m, z0_t = z0_t, & + Rib_conv_lim = 0.0, & + Cm = Cm, Ct = Ct, Km = Km, Pr_t_inv = Pr_t_inv) + + end subroutine get_surface_fluxes + ! -------------------------------------------------------------------------------- + + !< @brief get dynamic scales + ! -------------------------------------------------------------------------------- + subroutine get_dynamic_scales(Udyn, Tdyn, Qdyn, zeta, & + U, Tsemi, dT, dQ, z, z0_m, z0_t, beta, maxiters) + ! ---------------------------------------------------------------------------- + real, intent(out) :: Udyn, Tdyn, Qdyn !< dynamic scales + real, intent(out) :: zeta !< = z/L + + real, intent(in) :: U !< abs(wind speed) at z + real, intent(in) :: Tsemi !< semi-sum of temperature at z and at surface + real, intent(in) :: dT, dQ !< temperature & humidity difference between z and at surface + real, intent(in) :: z !< constant flux layer height + real, intent(in) :: z0_m, z0_t !< roughness parameters + real, intent(in) :: beta !< buoyancy parameter + + integer, intent(in) :: maxiters !< maximum number of iterations + ! ---------------------------------------------------------------------------- + + ! --- local variables + real, parameter :: gamma = 0.61 + + real :: psi_m, psi_h + real :: psi0_m, psi0_h + real :: Linv + integer :: i + ! ---------------------------------------------------------------------------- + + + Udyn = kappa * U / log(z / z0_m) + Tdyn = kappa * dT * Pr_t_0_inv / log(z / z0_t) + Qdyn = kappa * dQ * Pr_t_0_inv / log(z / z0_t) + zeta = 0.0 + + ! --- no wind + if (Udyn < 1e-5) return + + Linv = kappa * beta * (Tdyn + gamma * Qdyn * Tsemi) / (Udyn * Udyn) + zeta = z * Linv + + ! --- near neutral case + if (Linv < 1e-5) return + + do i = 1, maxiters + + call get_psi(psi_m, psi_h, zeta) + call get_psi_mh(psi0_m, psi0_h, z0_m * Linv, z0_t * Linv) + + Udyn = kappa * U / (log(z / z0_m) - (psi_m - psi0_m)) + Tdyn = kappa * dT * Pr_t_0_inv / (log(z / z0_t) - (psi_h - psi0_h)) + Qdyn = kappa * dQ * Pr_t_0_inv / (log(z / z0_t) - (psi_h - psi0_h)) + + if (Udyn < 1e-5) exit + + Linv = kappa * beta * (Tdyn + gamma * Qdyn * Tsemi) / (Udyn * Udyn) + zeta = z * Linv + end do + + end subroutine get_dynamic_scales + ! -------------------------------------------------------------------------------- + + ! stability functions + ! -------------------------------------------------------------------------------- + subroutine get_phi(phi_m, phi_h, zeta) + !< @brief stability functions (momentum) & (heat): neutral case + ! ---------------------------------------------------------------------------- + real, intent(out) :: phi_m, phi_h !< stability functions + + real, intent(in) :: zeta !< = z/L + ! ---------------------------------------------------------------------------- + + + if (zeta >= 0.0) then + phi_m = 1.0 + (a_m * zeta * (1.0 + zeta)**(1.0 / 3.0)) / (1.0 + b_m * zeta) + phi_h = 1.0 + (a_h * zeta + b_h * zeta * zeta) / (1.0 + c_h * zeta + zeta * zeta) + else + phi_m = (1.0 - alpha_m * zeta)**(-0.25) + phi_h = (1.0 - alpha_h * zeta)**(-0.5) + end if + + end subroutine + ! -------------------------------------------------------------------------------- + + ! universal functions + ! -------------------------------------------------------------------------------- + subroutine get_psi(psi_m, psi_h, zeta) + !< @brief universal functions (momentum) & (heat): neutral case + ! ---------------------------------------------------------------------------- + real, intent(out) :: psi_m, psi_h !< universal functions + + real, intent(in) :: zeta !< = z/L + ! ---------------------------------------------------------------------------- + + ! --- local variables + real :: x_m, x_h + real :: q_m, q_h + ! ---------------------------------------------------------------------------- + + + if (zeta >= 0.0) then + + q_m = ((1.0 - b_m) / b_m)**(1.0 / 3.0) + q_h = sqrt(c_h * c_h - 4.0) + + x_m = (1.0 + zeta)**(1.0 / 3.0) + x_h = zeta + + psi_m = -3.0 * (a_m / b_m) * (x_m - 1.0) + 0.5 * (a_m / b_m) * q_m * (& + 2.0 * log((x_m + q_m) / (1.0 + q_m)) - & + log((x_m * x_m - x_m * q_m + q_m * q_m) / (1.0 - q_m + q_m * q_m)) + & + 2.0 * sqrt(3.0) * (& + atan((2.0 * x_m - q_m) / (sqrt(3.0) * q_m)) - & + atan((2.0 - q_m) / (sqrt(3.0) * q_m)))) + + psi_h = -0.5 * b_h * log(1.0 + c_h * x_h + x_h * x_h) + & + ((-a_h / q_h) + ((b_h * c_h) / (2.0 * q_h))) * (& + log((2.0 * x_h + c_h - q_h) / (2.0 * x_h + c_h + q_h)) - & + log((c_h - q_h) / (c_h + q_h))) + else + x_m = (1.0 - alpha_m * zeta)**(0.25) + x_h = (1.0 - alpha_h * zeta)**(0.25) + + psi_m = (4.0 * atan(1.0) / 2.0) + 2.0 * log(0.5 * (1.0 + x_m)) + log(0.5 * (1.0 + x_m * x_m)) - 2.0 * atan(x_m) + psi_h = 2.0 * log(0.5 * (1.0 + x_h * x_h)) + end if + + end subroutine + + + subroutine get_psi_mh(psi_m, psi_h, zeta_m, zeta_h) + !< @brief universal functions (momentum) & (heat): neutral case + ! ---------------------------------------------------------------------------- + real, intent(out) :: psi_m, psi_h !< universal functions + + real, intent(in) :: zeta_m, zeta_h !< = z/L + ! ---------------------------------------------------------------------------- + + ! --- local variables + real :: x_m, x_h + real :: q_m, q_h + ! ---------------------------------------------------------------------------- + + + if (zeta_m >= 0.0) then + q_m = ((1.0 - b_m) / b_m)**(1.0 / 3.0) + x_m = (1.0 + zeta_m)**(1.0 / 3.0) + + psi_m = -3.0 * (a_m / b_m) * (x_m - 1.0) + 0.5 * (a_m / b_m) * q_m * (& + 2.0 * log((x_m + q_m) / (1.0 + q_m)) - & + log((x_m * x_m - x_m * q_m + q_m * q_m) / (1.0 - q_m + q_m * q_m)) + & + 2.0 * sqrt(3.0) * (& + atan((2.0 * x_m - q_m) / (sqrt(3.0) * q_m)) - & + atan((2.0 - q_m) / (sqrt(3.0) * q_m)))) + else + x_m = (1.0 - alpha_m * zeta_m)**(0.25) + psi_m = (4.0 * atan(1.0) / 2.0) + 2.0 * log(0.5 * (1.0 + x_m)) + log(0.5 * (1.0 + x_m * x_m)) - 2.0 * atan(x_m) + end if + + if (zeta_h >= 0.0) then + q_h = sqrt(c_h * c_h - 4.0) + x_h = zeta_h + + psi_h = -0.5 * b_h * log(1.0 + c_h * x_h + x_h * x_h) + & + ((-a_h / q_h) + ((b_h * c_h) / (2.0 * q_h))) * (& + log((2.0 * x_h + c_h - q_h) / (2.0 * x_h + c_h + q_h)) - & + log((c_h - q_h) / (c_h + q_h))) + else + x_h = (1.0 - alpha_h * zeta_h)**(0.25) + psi_h = 2.0 * log(0.5 * (1.0 + x_h * x_h)) + end if + + end subroutine + ! -------------------------------------------------------------------------------- + +end module sfx_sheba \ No newline at end of file diff --git a/srcF/sfx_sheba_param.f90 b/srcF/sfx_sheba_param.f90 new file mode 100644 index 0000000000000000000000000000000000000000..2b0e0411225bd08739fcb44f429f7d5f36e2fa3b --- /dev/null +++ b/srcF/sfx_sheba_param.f90 @@ -0,0 +1,32 @@ +module sfx_sheba_param + !< @brief SHEBA surface flux model parameters + !< @details all in SI units + + ! modules used + ! -------------------------------------------------------------------------------- + use sfx_phys_const + ! -------------------------------------------------------------------------------- + + ! directives list + ! -------------------------------------------------------------------------------- + implicit none + ! -------------------------------------------------------------------------------- + + + !< von Karman constant [n/d] + real, parameter :: kappa = 0.40 + !< inverse Prandtl (turbulent) number in neutral conditions [n/d] + real, parameter :: Pr_t_0_inv = 1.15 + + !< stability function coeff. (unstable) + real, parameter :: alpha_m = 16.0 + real, parameter :: alpha_h = 16.0 + + !< stability function coeff. (stable) + real, parameter :: a_m = 5.0 + real, parameter :: b_m = a_m / 6.5 + real, parameter :: a_h = 5.0 + real, parameter :: b_h = 5.0 + real, parameter :: c_h = 3.0 + +end module sfx_sheba_param