module OzoneMod !----------------------------------------------------------------------- ! !DESCRIPTION: ! Calculates ozone-induced stress. ! ! Note that the ozone calculations need to happen AFTER rssun and rsshade are computed ! by the Photosynthesis routine. However, Photosynthesis also uses the ozone stress ! computed here. Thus, the ozone stress computed in timestep i is applied in timestep ! (i+1), requiring these stresses to be saved on the restart file. ! ! Developed by Danica Lombardozzi. ! ! !USES: #include "shr_assert.h" use shr_kind_mod, only : r8 => shr_kind_r8 use decompMod , only : bounds_type use clm_varcon , only : spval use shr_log_mod , only : errMsg => shr_log_errMsg use OzoneBaseMod, only : ozone_base_type use abortutils , only : endrun implicit none save private ! !PUBLIC TYPES: type, extends(ozone_base_type), public :: ozone_type private ! Private data members real(r8), pointer :: o3uptakesha_patch(:) ! ozone dose, shaded leaves (mmol O3/m^2) real(r8), pointer :: o3uptakesun_patch(:) ! ozone dose, sunlit leaves (mmol O3/m^2) ! NOTE(wjs, 2014-09-29) tlai_old_patch really belongs alongside tlai_patch in ! CanopyStateType. But there are problems with any way I can think to implement ! that: ! ! - Updating tlai_old from a call in clm_driver, just before tlai is updated: This ! is problematic to do correctly because tlai is updated in different places ! depending on whether you're using SP, CN or ED. ! ! - Updating tlai_old within each routine that updates tlai: This feels fragile, ! since it depends on each scheme remembering to do this update at the correct ! time. ! ! - Making tlai a private member of CanopyFluxes, with getter and setter methods. ! Then the setter method would also set tlai_old. This feels like the most robust ! solution, but we don't have any precedent for using getters and setters for data ! arrays. real(r8), pointer :: tlai_old_patch(:) ! tlai from last time step contains ! Public routines procedure, public :: Init procedure, public :: Restart procedure, public :: CalcOzoneStress ! Private routines procedure, private :: InitAllocate procedure, private :: InitHistory procedure, private :: InitCold ! Calculate ozone stress for a single point, for just sunlit or shaded leaves procedure, private, nopass :: CalcOzoneStressOnePoint end type ozone_type interface ozone_type module procedure constructor end interface ozone_type ! !PRIVATE TYPES: ! TODO(wjs, 2014-09-29) This parameter will eventually become a spatially-varying ! value, obtained from ATM real(r8), parameter :: forc_ozone = 100._r8 * 1.e-9_r8 ! ozone partial pressure [mol/mol] ! TODO(wjs, 2014-09-29) The following parameters should eventually be moved to the ! params file. Parameters differentiated on veg type should be put on the params file ! with a pft dimension. ! o3:h2o resistance ratio defined by Sitch et al. 2007 real(r8), parameter :: ko3 = 1.67_r8 ! LAI threshold for LAIs that asymptote and don't reach 0 real(r8), parameter :: lai_thresh = 0.5_r8 ! threshold below which o3flux is set to 0 (nmol m^-2 s^-1) real(r8), parameter :: o3_flux_threshold = 0.8_r8 ! o3 intercepts and slopes for photosynthesis real(r8), parameter :: needleleafPhotoInt = 0.8390_r8 ! units = unitless real(r8), parameter :: needleleafPhotoSlope = 0._r8 ! units = per mmol m^-2 real(r8), parameter :: broadleafPhotoInt = 0.8752_r8 ! units = unitless real(r8), parameter :: broadleafPhotoSlope = 0._r8 ! units = per mmol m^-2 real(r8), parameter :: nonwoodyPhotoInt = 0.8021_r8 ! units = unitless real(r8), parameter :: nonwoodyPhotoSlope = -0.0009_r8 ! units = per mmol m^-2 ! o3 intercepts and slopes for conductance real(r8), parameter :: needleleafCondInt = 0.7823_r8 ! units = unitless real(r8), parameter :: needleleafCondSlope = 0.0048_r8 ! units = per mmol m^-2 real(r8), parameter :: broadleafCondInt = 0.9125_r8 ! units = unitless real(r8), parameter :: broadleafCondSlope = 0._r8 ! units = per mmol m^-2 real(r8), parameter :: nonwoodyCondInt = 0.7511_r8 ! units = unitless real(r8), parameter :: nonwoodyCondSlope = 0._r8 ! units = per mmol m^-2 character(len=*), parameter, private :: sourcefile = & __FILE__ contains ! ======================================================================== ! Infrastructure routines (initialization, restart, etc.) ! ======================================================================== !----------------------------------------------------------------------- function constructor() result(ozone) ! ! !DESCRIPTION: ! Return an instance of ozone_type ! ! !USES: ! ! !ARGUMENTS: type(ozone_type) :: ozone ! function result ! ! !LOCAL VARIABLES: character(len=*), parameter :: subname = 'constructor' !----------------------------------------------------------------------- ! DO NOTHING (simply return a variable of the appropriate type) ! Eventually this should call the Init routine (or replace the Init routine ! entirely). But I think it would be confusing to do that until we switch everything ! to use a constructor rather than the init routine. end function constructor !----------------------------------------------------------------------- subroutine Init(this, bounds) ! ! !DESCRIPTION: ! Initialize ozone data structure ! ! !ARGUMENTS: class(ozone_type), intent(inout) :: this type(bounds_type), intent(in) :: bounds !----------------------------------------------------------------------- call this%InitAllocate(bounds) call this%InitHistory(bounds) call this%InitCold(bounds) end subroutine Init !----------------------------------------------------------------------- subroutine InitAllocate(this, bounds) ! ! !DESCRIPTION: ! Allocate memory for ozone data structure ! ! !USES: use shr_infnan_mod , only : nan => shr_infnan_nan, assignment(=) ! ! !ARGUMENTS: class(ozone_type), intent(inout) :: this type(bounds_type), intent(in) :: bounds ! ! !LOCAL VARIABLES: integer :: begp, endp !----------------------------------------------------------------------- begp = bounds%begp endp = bounds%endp call this%InitAllocateBase(bounds) allocate(this%o3uptakesha_patch(begp:endp)) ; this%o3uptakesha_patch(:) = nan allocate(this%o3uptakesun_patch(begp:endp)) ; this%o3uptakesun_patch(:) = nan allocate(this%tlai_old_patch(begp:endp)) ; this%tlai_old_patch(:) = nan end subroutine InitAllocate !----------------------------------------------------------------------- subroutine InitHistory(this, bounds) ! ! !DESCRIPTION: ! Initialize ozone history variables ! ! !USES: use histFileMod , only : hist_addfld1d ! ! !ARGUMENTS: class(ozone_type), intent(inout) :: this type(bounds_type), intent(in) :: bounds ! ! !LOCAL VARIABLES: integer :: begp, endp character(len=*), parameter :: subname = 'InitHistory' !----------------------------------------------------------------------- begp = bounds%begp endp = bounds%endp this%o3uptakesun_patch(begp:endp) = spval call hist_addfld1d (fname='O3UPTAKESUN', units='mmol/m^2', & avgflag='A', long_name='total ozone flux into sunlit leaves', & ptr_patch=this%o3uptakesun_patch) this%o3uptakesha_patch(begp:endp) = spval call hist_addfld1d (fname='O3UPTAKESHA', units='mmol/m^2', & avgflag='A', long_name='total ozone flux into shaded leaves', & ptr_patch=this%o3uptakesha_patch) end subroutine InitHistory !----------------------------------------------------------------------- subroutine InitCold(this, bounds) ! ! !DESCRIPTION: ! Perform cold-start initialization for ozone ! ! !ARGUMENTS: class(ozone_type), intent(inout) :: this type(bounds_type), intent(in) :: bounds ! ! !LOCAL VARIABLES: integer :: begp, endp character(len=*), parameter :: subname = 'InitCold' !----------------------------------------------------------------------- begp = bounds%begp endp = bounds%endp call this%InitColdBase(bounds) this%o3uptakesha_patch(begp:endp) = 0._r8 this%o3uptakesun_patch(begp:endp) = 0._r8 this%tlai_old_patch(begp:endp) = 0._r8 end subroutine InitCold !----------------------------------------------------------------------- subroutine Restart(this, bounds, ncid, flag) ! ! !DESCRIPTION: ! Handle restart of ozone variables. ! ! !USES: use ncdio_pio , only : file_desc_t, ncd_inqvdlen, ncd_double use restUtilMod ! ! !ARGUMENTS: class(ozone_type) :: this type(bounds_type), intent(in) :: bounds type(file_desc_t), intent(inout) :: ncid ! netcdf id character(len=*) , intent(in) :: flag ! 'read', 'write' or 'define' ! ! !LOCAL VARIABLES: logical :: readvar character(len=*), parameter :: subname = 'Restart' !----------------------------------------------------------------------- call restartvar(ncid=ncid, flag=flag, varname='o3_tlaiold', xtype=ncd_double, & dim1name='pft', & long_name='one-sided leaf area index, from previous timestep, for ozone calculations', units='', & readvar=readvar, interpinic_flag='interp', data=this%tlai_old_patch) call restartvar(ncid=ncid, flag=flag, varname='o3uptakesha', xtype=ncd_double, & dim1name='pft', & long_name='ozone uptake for shaded leaves', units='mmol m^-3', & readvar=readvar, interpinic_flag='interp', data=this%o3uptakesha_patch) call restartvar(ncid=ncid, flag=flag, varname='o3uptakesun', xtype=ncd_double, & dim1name='pft', & long_name='ozone uptake for sunlit leaves', units='mmol m^-3', & readvar=readvar, interpinic_flag='interp', data=this%o3uptakesun_patch) call restartvar(ncid=ncid, flag=flag, varname='o3coefvsun', xtype=ncd_double, & dim1name='pft', & long_name='ozone coefficient for photosynthesis for sunlit leaves', units='unitless', & readvar=readvar, interpinic_flag='interp', data=this%o3coefvsun_patch) call restartvar(ncid=ncid, flag=flag, varname='o3coefgsun', xtype=ncd_double, & dim1name='pft', & long_name='ozone coefficient for stomatal conductance for sunlit leaves', units='unitless', & readvar=readvar, interpinic_flag='interp', data=this%o3coefgsun_patch) call restartvar(ncid=ncid, flag=flag, varname='o3coefvsha', xtype=ncd_double, & dim1name='pft', & long_name='ozone coefficient for photosynthesis for shaded leaves', units='unitless', & readvar=readvar, interpinic_flag='interp', data=this%o3coefvsha_patch) call restartvar(ncid=ncid, flag=flag, varname='o3coefgsha', xtype=ncd_double, & dim1name='pft', & long_name='ozone coefficient for stomatal conductance for shaded leaves', units='unitless', & readvar=readvar, interpinic_flag='interp', data=this%o3coefgsha_patch) end subroutine Restart ! ======================================================================== ! Science routines ! ======================================================================== !----------------------------------------------------------------------- subroutine CalcOzoneStress(this, bounds, num_exposedvegp, filter_exposedvegp, & forc_pbot, forc_th, rssun, rssha, rb, ram, tlai) ! ! !DESCRIPTION: ! Calculate ozone stress. ! ! !USES: use PatchType , only : patch ! ! !ARGUMENTS: class(ozone_type) , intent(inout) :: this type(bounds_type) , intent(in) :: bounds integer , intent(in) :: num_exposedvegp ! number of points in filter_exposedvegp integer , intent(in) :: filter_exposedvegp(:) ! patch filter for non-snow-covered veg real(r8) , intent(in) :: forc_pbot( bounds%begc: ) ! atmospheric pressure (Pa) real(r8) , intent(in) :: forc_th( bounds%begc: ) ! atmospheric potential temperature (K) real(r8) , intent(in) :: rssun( bounds%begp: ) ! leaf stomatal resistance, sunlit leaves (s/m) real(r8) , intent(in) :: rssha( bounds%begp: ) ! leaf stomatal resistance, shaded leaves (s/m) real(r8) , intent(in) :: rb( bounds%begp: ) ! boundary layer resistance (s/m) real(r8) , intent(in) :: ram( bounds%begp: ) ! aerodynamical resistance (s/m) real(r8) , intent(in) :: tlai( bounds%begp: ) ! one-sided leaf area index, no burying by snow ! ! !LOCAL VARIABLES: integer :: fp ! filter index integer :: p ! patch index integer :: c ! column index character(len=*), parameter :: subname = 'CalcOzoneStress' !----------------------------------------------------------------------- ! Enforce expected array sizes SHR_ASSERT_ALL((ubound(forc_pbot) == (/bounds%endc/)), errMsg(sourcefile, __LINE__)) SHR_ASSERT_ALL((ubound(forc_th) == (/bounds%endc/)), errMsg(sourcefile, __LINE__)) SHR_ASSERT_ALL((ubound(rssun) == (/bounds%endp/)), errMsg(sourcefile, __LINE__)) SHR_ASSERT_ALL((ubound(rssha) == (/bounds%endp/)), errMsg(sourcefile, __LINE__)) SHR_ASSERT_ALL((ubound(rb) == (/bounds%endp/)), errMsg(sourcefile, __LINE__)) SHR_ASSERT_ALL((ubound(ram) == (/bounds%endp/)), errMsg(sourcefile, __LINE__)) SHR_ASSERT_ALL((ubound(tlai) == (/bounds%endp/)), errMsg(sourcefile, __LINE__)) associate( & o3coefvsha => this%o3coefvsha_patch , & ! Output: [real(r8) (:)] ozone coef o3coefvsun => this%o3coefvsun_patch , & ! Output: [real(r8) (:)] ozone coef o3coefgsha => this%o3coefgsha_patch , & ! Output: [real(r8) (:)] ozone coef o3coefgsun => this%o3coefgsun_patch , & ! Output: [real(r8) (:)] ozone coef o3uptakesha => this%o3uptakesha_patch , & ! Output: [real(r8) (:)] ozone dose o3uptakesun => this%o3uptakesun_patch , & ! Output: [real(r8) (:)] ozone dose tlai_old => this%tlai_old_patch & ! Output: [real(r8) (:)] tlai from last time step ) do fp = 1, num_exposedvegp p = filter_exposedvegp(fp) c = patch%column(p) ! if (.not.patch%is_fates(p)) then ! When FATES coexists with other vegetation, ! or when it has an ozone compatible module, this ! logic will likely come into play ! Ozone stress for shaded leaves call CalcOzoneStressOnePoint( & forc_ozone=forc_ozone, forc_pbot=forc_pbot(c), forc_th=forc_th(c), & rs=rssha(p), rb=rb(p), ram=ram(p), & tlai=tlai(p), tlai_old=tlai_old(p), pft_type=patch%itype(p), & o3uptake=o3uptakesha(p), o3coefv=o3coefvsha(p), o3coefg=o3coefgsha(p)) ! Ozone stress for sunlit leaves call CalcOzoneStressOnePoint( & forc_ozone=forc_ozone, forc_pbot=forc_pbot(c), forc_th=forc_th(c), & rs=rssun(p), rb=rb(p), ram=ram(p), & tlai=tlai(p), tlai_old=tlai_old(p), pft_type=patch%itype(p), & o3uptake=o3uptakesun(p), o3coefv=o3coefvsun(p), o3coefg=o3coefgsun(p)) tlai_old(p) = tlai(p) ! else ! ! FATES is fundamentlaly incompatible with this type of patch-level ! ! association with plant functional type, so for the time ! ! being, fates patches will just push these values to invalid ! o3uptakesha(p) = spval ! o3coefvsha(p) = spval ! o3coefgsha(p) = spval ! o3uptakesun(p) = spval ! o3coefvsun(p) = spval ! o3coefgsun(p) = spval ! ! end if ! else ! ! FATES is fundamentlaly incompatible with this type of patch-level ! ! association with plant functional type, so for the time ! ! being, fates patches will just push these values to invalid ! o3uptakesha(p) = spval ! o3coefvsha(p) = spval ! o3coefgsha(p) = spval ! o3uptakesun(p) = spval ! o3coefvsun(p) = spval ! o3coefgsun(p) = spval ! ! end if end do end associate end subroutine CalcOzoneStress !----------------------------------------------------------------------- subroutine CalcOzoneStressOnePoint( & forc_ozone, forc_pbot, forc_th, & rs, rb, ram, & tlai, tlai_old, pft_type, & o3uptake, o3coefv, o3coefg) ! ! !DESCRIPTION: ! Calculates ozone stress for a single point, for just sunlit or shaded leaves ! ! !USES: use shr_const_mod , only : SHR_CONST_RGAS use pftconMod , only : pftcon use clm_time_manager , only : get_step_size ! ! !ARGUMENTS: real(r8) , intent(in) :: forc_ozone ! ozone partial pressure (mol/mol) real(r8) , intent(in) :: forc_pbot ! atmospheric pressure (Pa) real(r8) , intent(in) :: forc_th ! atmospheric potential temperature (K) real(r8) , intent(in) :: rs ! leaf stomatal resistance (s/m) real(r8) , intent(in) :: rb ! boundary layer resistance (s/m) real(r8) , intent(in) :: ram ! aerodynamical resistance (s/m) real(r8) , intent(in) :: tlai ! one-sided leaf area index, no burying by snow real(r8) , intent(in) :: tlai_old ! tlai from last time step integer , intent(in) :: pft_type ! vegetation type, for indexing into pftvarcon arrays real(r8) , intent(inout) :: o3uptake ! ozone entering the leaf real(r8) , intent(out) :: o3coefv ! ozone coefficient for photosynthesis (0 - 1) real(r8) , intent(out) :: o3coefg ! ozone coefficient for conductance (0 - 1) ! ! !LOCAL VARIABLES: integer :: dtime ! land model time step (sec) real(r8) :: dtimeh ! time step in hours real(r8) :: o3concnmolm3 ! o3 concentration (nmol/m^3) real(r8) :: o3flux ! instantaneous o3 flux (nmol m^-2 s^-1) real(r8) :: o3fluxcrit ! instantaneous o3 flux beyond threshold (nmol m^-2 s^-1) real(r8) :: o3fluxperdt ! o3 flux per timestep (mmol m^-2) real(r8) :: heal ! o3uptake healing rate based on % of new leaves growing (mmol m^-2) real(r8) :: leafturn ! leaf turnover time / mortality rate (per hour) real(r8) :: decay ! o3uptake decay rate based on leaf lifetime (mmol m^-2) real(r8) :: photoInt ! intercept for photosynthesis real(r8) :: photoSlope ! slope for photosynthesis real(r8) :: condInt ! intercept for conductance real(r8) :: condSlope ! slope for conductance character(len=*), parameter :: subname = 'CalcOzoneStressOnePoint' !----------------------------------------------------------------------- ! convert o3 from mol/mol to nmol m^-3 o3concnmolm3 = forc_ozone * 1.e9_r8 * (forc_pbot/(forc_th*SHR_CONST_RGAS*0.001_r8)) ! calculate instantaneous flux o3flux = o3concnmolm3/ (ko3*rs+ rb + ram) ! apply o3 flux threshold if (o3flux < o3_flux_threshold) then o3fluxcrit = 0._r8 else o3fluxcrit = o3flux - o3_flux_threshold endif dtime = get_step_size() dtimeh = dtime / 3600._r8 ! calculate o3 flux per timestep o3fluxperdt = o3fluxcrit * dtime * 0.000001_r8 if (tlai > lai_thresh) then ! checking if new leaf area was added if (tlai - tlai_old > 0) then ! minimizing o3 damage to new leaves heal = max(0._r8,(((tlai-tlai_old)/tlai)*o3fluxperdt)) else heal = 0._r8 endif if (pftcon%evergreen(pft_type) == 1) then leafturn = 1._r8/(pftcon%leaf_long(pft_type)*365._r8*24._r8) else leafturn = 0._r8 endif ! o3 uptake decay based on leaf lifetime for evergreen plants decay = o3uptake * leafturn * dtimeh !cumulative uptake (mmol m^-2) o3uptake = max(0._r8, o3uptake + o3fluxperdt - decay - heal) else o3uptake = 0._r8 end if if (o3uptake == 0._r8) then ! No o3 damage if no o3 uptake o3coefv = 1._r8 o3coefg = 1._r8 else ! Determine parameter values for this pft ! TODO(wjs, 2014-10-01) Once these parameters are moved into the params file, this ! logic can be removed. if (pft_type>3) then if (pftcon%woody(pft_type)==0) then photoInt = nonwoodyPhotoInt photoSlope = nonwoodyPhotoSlope condInt = nonwoodyCondInt condSlope = nonwoodyCondSlope else photoInt = broadleafPhotoInt photoSlope = broadleafPhotoSlope condInt = broadleafCondInt condSlope = broadleafCondSlope end if else photoInt = needleleafPhotoInt photoSlope = needleleafPhotoSlope condInt = needleleafCondInt condSlope = needleleafCondSlope end if ! Apply parameter values to compute o3 coefficients o3coefv = max(0._r8, min(1._r8, photoInt + photoSlope * o3uptake)) o3coefg = max(0._r8, min(1._r8, condInt + condSlope * o3uptake)) end if end subroutine CalcOzoneStressOnePoint end module OzoneMod