Skip to content

Commit bf6cdae

Browse files
committed
Add moss photosynthesis & respiration.
1 parent 5bcf0e6 commit bf6cdae

File tree

5 files changed

+98
-27
lines changed

5 files changed

+98
-27
lines changed

biogeochem/EDCanopyStructureMod.F90

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ module EDCanopyStructureMod
1212
use FatesConstantsMod , only : rsnbl_math_prec
1313
use FatesConstantsMod , only : nocomp_bareground
1414
use FatesConstantsMod, only : i_term_mort_type_canlev
15+
use FatesConstantsMod, only : m2_per_ha
1516
use FatesGlobals , only : fates_log
1617
use EDPftvarcon , only : EDPftvarcon_inst
1718
use PRTParametersMod , only : prt_params
@@ -2232,7 +2233,9 @@ end subroutine UpdatePatchLAI
22322233
subroutine UpdateCohortLAI(currentCohort, canopy_layer_tlai, total_canopy_area)
22332234

22342235
! Update LAI and related variables for a given cohort
2235-
2236+
2237+
use FatesMossMod, only : SLA_M2LEAF_PER_KGMOSS
2238+
22362239
! Arguments
22372240
type(fates_cohort_type),intent(inout), target :: currentCohort
22382241
real(r8), intent(in) :: canopy_layer_tlai(nclmax) ! total leaf area index of each canopy layer
@@ -2244,20 +2247,26 @@ subroutine UpdateCohortLAI(currentCohort, canopy_layer_tlai, total_canopy_area)
22442247

22452248
! Obtain the leaf carbon
22462249
leaf_c = currentCohort%prt%GetState(leaf_organ,carbon12_element)
2247-
2248-
! Note that tree_lai has an internal check on the canopy location
2249-
call tree_lai_sai(leaf_c, currentCohort%pft, currentCohort%c_area, currentCohort%n, &
2250-
currentCohort%canopy_layer, canopy_layer_tlai, currentCohort%vcmax25top, currentCohort%dbh, currentCohort%crowndamage, &
2251-
currentCohort%canopy_trim, currentCohort%efstem_coh, 4, currentCohort%treelai, treesai )
2252-
2253-
! Do not update stem area index of SP vegetation
2254-
if (hlm_use_sp .eq. ifalse) then
2255-
currentCohort%treesai = treesai
2256-
end if
22572250

2258-
! Number of actual vegetation layers in this cohort's crown
2259-
currentCohort%nv = GetNVegLayers(currentCohort%treelai+currentCohort%treesai)
2251+
if (prt_params%moss(currentCohort%pft) == itrue) then
2252+
currentCohort%treelai = leaf_c * currentCohort%n / m2_per_ha * SLA_M2LEAF_PER_KGMOSS
2253+
currentCohort%treesai = 0._r8
2254+
currentCohort%nv = 1
2255+
else
2256+
2257+
! Note that tree_lai has an internal check on the canopy location
2258+
call tree_lai_sai(leaf_c, currentCohort%pft, currentCohort%c_area, currentCohort%n, &
2259+
currentCohort%canopy_layer, canopy_layer_tlai, currentCohort%vcmax25top, currentCohort%dbh, currentCohort%crowndamage, &
2260+
currentCohort%canopy_trim, currentCohort%efstem_coh, 4, currentCohort%treelai, treesai )
2261+
2262+
! Do not update stem area index of SP vegetation
2263+
if (hlm_use_sp .eq. ifalse) then
2264+
currentCohort%treesai = treesai
2265+
end if
22602266

2267+
! Number of actual vegetation layers in this cohort's crown
2268+
currentCohort%nv = GetNVegLayers(currentCohort%treelai+currentCohort%treesai)
2269+
end if
22612270
end subroutine UpdateCohortLAI
22622271

22632272
! ===============================================================================================

biogeochem/EDPhysiologyMod.F90

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -702,7 +702,12 @@ subroutine trim_canopy( currentSite )
702702
currentCohort%dbh, currentCohort%crowndamage, currentCohort%canopy_trim, &
703703
currentCohort%efstem_coh, 0, currentCohort%treelai, currentCohort%treesai )
704704

705-
currentCohort%nv = GetNVegLayers(currentCohort%treelai+currentCohort%treesai)
705+
! TODO: Would be safer to add this handling to GetNVegLayers()
706+
if (prt_params%moss(ipft) == itrue) then
707+
currentCohort%nv = 1
708+
else
709+
currentCohort%nv = GetNVegLayers(currentCohort%treelai+currentCohort%treesai)
710+
end if
706711

707712
leaf_veg_frac = currentCohort%treelai/(currentCohort%treelai+currentCohort%treesai)
708713

@@ -1885,6 +1890,11 @@ subroutine satellite_phenology(currentSite, bc_in)
18851890
call endrun(msg=errMsg(sourcefile, __LINE__))
18861891
end if
18871892

1893+
if (prt_params%moss(fates_pft) == itrue) then
1894+
write(fates_log(),*) 'Moss in SP mode'
1895+
call endrun(msg=errMsg(sourcefile, __LINE__))
1896+
end if
1897+
18881898
! Call routine to invert SP drivers into cohort properites.
18891899
call assign_cohort_SP_properties(currentCohort, currentSite%sp_htop(fates_pft), currentSite%sp_tlai(fates_pft) , currentSite%sp_tsai(fates_pft),currentPatch%area,ifalse,leaf_c)
18901900

biogeophys/FatesMossMod.F90

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ module FatesMossMod
1111
!
1212
! ==============================================================================================
1313

14-
use FatesConstantsMod , only: r8 => fates_r8
14+
use FatesConstantsMod , only: r8 => fates_r8, nearzero
15+
use FatesConstantsMod , only: umolC_to_kgC
16+
use FatesConstantsMod , only: days_per_sec, days_per_year
1517
use FatesGlobals, only : endrun => fates_endrun
1618
use FatesGlobals, only : fates_log
1719

@@ -133,8 +135,8 @@ subroutine moss_biomass_change_kg_per_m2(q_kg_per_kg_moss_in, b_kg_per_kg_moss_i
133135
end subroutine moss_biomass_change_kg_per_m2
134136

135137
!------------------------------------------------------------------------------
136-
subroutine moss(alff, canopy_lai, decid_litter, moss_biom_kg_per_m2plot_inout, moss_to_litter_flux_kg_per_m2plot, moss_to_atmos_flux_kg_per_m2plot, &
137-
livemoss_depth_m)
138+
subroutine moss(alff, canopy_lai, decid_litter, dtime, moss_biom_kg_per_m2plot_inout, moss_to_litter_flux_kg_per_m2plot, moss_to_atmos_flux_kg_per_m2plot, &
139+
livemoss_depth_m, psn_z, anet_av_z, lmr_z)
138140
!
139141
! Calculates annual moss growth and mortality
140142
! Adapted from Bonan and Korzukhin 1989 Vegetatio 84:31-44
@@ -145,12 +147,19 @@ subroutine moss(alff, canopy_lai, decid_litter, moss_biom_kg_per_m2plot_inout, m
145147
real(r8), intent(in) :: alff ! Available light on the forest floor (0-1)
146148
real(r8), intent(in) :: canopy_lai ! Leaf area index of canopy (i.e., excluding moss) (m2 leaves / m2 plot)
147149
real(r8), intent(in) :: decid_litter ! Fresh deciduous leaf litter (kg/m2)
150+
real(r8), intent(in) :: dtime ! Time step length (s/timestep)
148151
real(r8), intent(inout) :: moss_biom_kg_per_m2plot_inout ! Moss biomass (kg/m2)
149152
real(r8), intent(out) :: moss_to_litter_flux_kg_per_m2plot ! Flux from moss to litter (kg/m2)
150153
real(r8), intent(out) :: moss_to_atmos_flux_kg_per_m2plot ! Flux from moss to atmosphere (kg/m2)
151154
real(r8), intent(out) :: livemoss_depth_m ! Depth (m) of live moss layer
155+
! Outputs for FatesPlantRespPhotosynthDrive()
156+
real(r8), intent(out) :: psn_z ! GPP [umolC/m2leaf/s]
157+
real(r8), intent(out) :: anet_av_z ! "net leaf photosynthesis" [umol C/m2leaf/timestep]
158+
real(r8), intent(out) :: lmr_z ! leaf maintenance (dark) respiration [umolC/m2leaf/s]
152159

153160
! Local variables
161+
real(r8) :: per_year_to_per_sec
162+
real(r8) :: per_year_to_per_timestep
154163
real(r8) :: moss_biom_kg_per_plot_before ! Moss biomass (kg per plot) before this timestep
155164
real(r8) :: moss_biom_kg_per_m2plot_before ! Moss biomass (kg/m2) before this timestep
156165
real(r8) :: moss_biom_kg_per_m2plot_after ! Moss biomass (kg/m2) after this timestep
@@ -204,7 +213,16 @@ subroutine moss(alff, canopy_lai, decid_litter, moss_biom_kg_per_m2plot_inout, m
204213
! TODO: Change these to what FATES needs
205214
moss_biom_kg_per_m2plot_inout = moss_biom_kg_per_m2plot_after
206215

207-
216+
! Outputs for FatesPlantRespPhotosynthDrive()
217+
per_year_to_per_sec = days_per_sec / days_per_year
218+
per_year_to_per_timestep = per_year_to_per_sec * dtime
219+
anet_av_z = assim_kg_per_m2leaf / umolC_to_kgC * per_year_to_per_timestep ! "net leaf photosynthesis" [umol C/m2leaf/timestep]
220+
psn_z = anet_av_z * dtime ! GPP [umolC/m2leaf/s]. Moss model doesn't calculate, so assume GPP = NPP.
221+
if (moss_biom_kg_per_m2plot_before < nearzero) then
222+
lmr_z = 0._r8
223+
else
224+
lmr_z = moss_resp / umolC_to_kgC / moss_biom_kg_per_m2plot_before / SLA_M2LEAF_PER_KGMOSS * per_year_to_per_sec ! leaf maintenance (dark) respiration [umolC/m2leaf/s]
225+
end if
208226

209227
end subroutine moss
210228

biogeophys/FatesPlantRespPhotosynthMod.F90

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ module FATESPlantRespPhotosynthMod
2424
use FatesGlobals, only : fates_log
2525
use FatesGlobals, only : FatesWarn,N2S,A2S,I2S
2626
use FatesConstantsMod, only : r8 => fates_r8
27-
use FatesConstantsMod, only : itrue
27+
use FatesConstantsMod, only : itrue, ifalse
2828
use FatesConstantsMod, only : nearzero
2929
use FatesConstantsMod, only : fates_unset_r8
3030
use FatesConstantsMod, only : tfrz => t_water_freeze_k_1atm
@@ -114,13 +114,15 @@ subroutine FatesPlantRespPhotosynthDrive (nsites, sites,bc_in,bc_out,dtime)
114114
use EDCanopyStructureMod, only : calc_areaindex
115115
use FatesConstantsMod, only : umolC_to_kgC
116116
use FatesConstantsMod, only : umol_per_mmol
117+
use FatesConstantsMod, only : m2_per_ha
117118

118119
use FatesParameterDerivedMod, only : param_derived
119120
use FatesAllometryMod, only : bleaf, bstore_allom
120121
use FatesAllometryMod, only : storage_fraction_of_target
121122
use FatesAllometryMod, only : set_root_fraction
122123
use DamageMainMod, only : GetCrownReduction
123124
use FatesInterfaceTypesMod, only : hlm_use_tree_damage
125+
use FatesMossMod, only : moss
124126

125127
! ARGUMENTS:
126128
! -----------------------------------------------------------------------------------
@@ -209,7 +211,7 @@ subroutine FatesPlantRespPhotosynthDrive (nsites, sites,bc_in,bc_out,dtime)
209211
real(r8) :: r_stomata ! Mean stomatal resistance across all leaves in the patch [s/m]
210212
real(r8) :: maintresp_reduction_factor ! factor by which to reduce maintenance
211213
! respiration when storage pools are low
212-
real(r8) :: b_leaf ! leaf biomass kgC
214+
real(r8) :: b_leaf ! leaf biomass kgC/m2 plot
213215
real(r8) :: frac ! storage pool as a fraction of target leaf biomass
214216
! over each cohort x layer.
215217
real(r8) :: cohort_eleaf_area ! This is the effective leaf area [m2] reported by each cohort
@@ -288,7 +290,14 @@ subroutine FatesPlantRespPhotosynthDrive (nsites, sites,bc_in,bc_out,dtime)
288290
integer :: NCL_p ! number of canopy layers in patch
289291
integer :: iage ! loop counter for leaf age classes
290292
integer :: solve_iter ! number of iterations required for photosynthesis solve
291-
293+
294+
! Variables for moss: Call of moss()
295+
real(r8) :: alff = 1._r8 ! available light on forest floor. TODO: Change from 1?
296+
real(r8) :: decid_litter = 0._r8 ! Fresh deciduous leaf litter (kg/m2). TODO: Change from 0.
297+
real(r8) :: dummy_moss_to_litter_flux_kg_per_m2plot
298+
real(r8) :: dummy_moss_to_atmos_flux_kg_per_m2plot
299+
real(r8) :: dummy_livemoss_depth_m
300+
292301
! Parameters
293302
! Absolute convergence tolerance on solving intracellular CO2 concentration [Pa]
294303

@@ -532,8 +541,17 @@ subroutine FatesPlantRespPhotosynthDrive (nsites, sites,bc_in,bc_out,dtime)
532541
! vascular plants isn't applied anywhere in this subroutine. This will make it so that
533542
! you can use ScaleLeafLayerFluxToCohort and its leaf area scaling with no adjustments.
534543

535-
write (fates_log(),*) 'Moss not yet implemented in FatesPlantRespPhotosynthDrive'
536-
call endrun(msg=errMsg(sourcefile, __LINE__))
544+
! Moss has no stomatal resistance because it has no stomata
545+
rs_z(iv,ft,cl) = 0._r8
546+
547+
! Get leaf biomass
548+
leaf_c = currentCohort%prt%GetState(leaf_organ, carbon12_element)
549+
! Convert kg/plant to kg/m2 plot
550+
b_leaf = leaf_c * currentCohort%n / m2_per_ha
551+
552+
call moss(alff, lai_canopy_above, decid_litter, dtime, b_leaf, &
553+
dummy_moss_to_litter_flux_kg_per_m2plot, dummy_moss_to_atmos_flux_kg_per_m2plot, &
554+
dummy_livemoss_depth_m, psn_z(iv,ft,cl), anet_av_z(iv,ft,cl), lmr_z(iv,ft,cl))
537555
else
538556

539557
if (hlm_use_planthydro.eq.itrue ) then

testing/unit_testing/moss_test/test_Moss.pf

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ module test_Moss
1616
end type TestMoss
1717

1818
real(r8), parameter :: tol = 1.e-13_r8
19+
real(r8), parameter :: dtime = 60 * 30 ! model timestep (s). Doesn't actually matter when testing
1920

2021
contains
2122

@@ -186,7 +187,7 @@ module test_Moss
186187

187188
@Test
188189
subroutine ZeroMoss(this)
189-
! test that all outputs are zero if input moss biomass is zero
190+
! test that most outputs are zero if input moss biomass is zero
190191
class(TestMoss), intent(inout) :: this ! test object
191192
real(r8) :: alff ! Available light on the forest floor (0-1)
192193
real(r8) :: cla ! Cumulative leaf area on forest floor (m2)
@@ -195,19 +196,27 @@ module test_Moss
195196
real(r8) :: moss_litter_flux ! Moss biomass flux to litter (kg/m2)
196197
real(r8) :: moss_atmos_flux ! Moss biomass flux to atmosphere (kg/m2)
197198
real(r8) :: livemoss_depth ! Depth (m) of live moss layer
199+
real(r8) :: psn_z ! GPP [umolC/m2leaf/s]
200+
real(r8) :: anet_av_z ! "net leaf photosynthesis" [umol C/m2leaf/timestep]
201+
real(r8) :: lmr_z ! leaf maintenance (dark) respiration [umolC/m2leaf/s]
198202

199203
! All inputs good for moss except zero biomass
200204
alff = 1.0_r8
201205
cla = 0.0_r8
202206
decLit = 0.0_r8
203207
moss_biom_kg = 0.0_r8
204208

205-
! Outputs should all be zero
206-
call moss(alff, cla, decLit, moss_biom_kg, moss_litter_flux, moss_atmos_flux, livemoss_depth)
209+
! Outputs should all be zero, except for per-area-leaf photosynthesis, which is independent
210+
! of moss biomass.
211+
call moss(alff, cla, decLit, dtime, moss_biom_kg, moss_litter_flux, moss_atmos_flux, &
212+
livemoss_depth, psn_z, anet_av_z, lmr_z)
207213
@assertEqual(0.0_r8, moss_biom_kg)
208214
@assertEqual(0.0_r8, moss_litter_flux)
209215
@assertEqual(0.0_r8, moss_atmos_flux)
210216
@assertEqual(0.0_r8, livemoss_depth)
217+
@assertGreaterThan(anet_av_z, 0._r8)
218+
@assertGreaterThan(psn_z, 0._r8)
219+
@assertEqual(0.0_r8, lmr_z)
211220

212221
end subroutine ZeroMoss
213222

@@ -222,17 +231,24 @@ module test_Moss
222231
real(r8) :: moss_litter_flux ! Moss biomass flux to litter (kg/m2)
223232
real(r8) :: moss_atmos_flux ! Moss biomass flux to atmosphere (kg/m2)
224233
real(r8) :: livemoss_depth ! Depth (m) of live moss layer
234+
real(r8) :: psn_z ! GPP [umolC/m2leaf/s]
235+
real(r8) :: anet_av_z ! "net leaf photosynthesis" [umol C/m2leaf/timestep]
236+
real(r8) :: lmr_z ! leaf maintenance (dark) respiration [umolC/m2leaf/s]
225237

226238
alff = 1.0_r8
227239
cla = 3.0_r8
228240
decLit = 0.1_r8
229241
moss_biom_kg = 0.001_r8
230242

231-
call moss(alff, cla, decLit, moss_biom_kg, moss_litter_flux, moss_atmos_flux, livemoss_depth)
243+
call moss(alff, cla, decLit, dtime, moss_biom_kg, moss_litter_flux, moss_atmos_flux, &
244+
livemoss_depth, psn_z, anet_av_z, lmr_z)
232245
@assertEqual(0.0008908437312040781_r8, moss_biom_kg, tolerance=tol)
233246
@assertEqual(0.0001360000073909760_r8, moss_litter_flux, tolerance=tol)
234247
@assertEqual(0.0001199999973177910_r8, moss_atmos_flux, tolerance=tol)
235248
@assertEqual(0.4949131844885193E-04_r8, livemoss_depth, tolerance=tol)
249+
@assertEqual(698.4576479872762_r8, anet_av_z, tolerance=tol)
250+
@assertEqual(anet_av_z * dtime, psn_z, tolerance=tol)
251+
@assertEqual(0.3170979127499550_r8, lmr_z, tolerance=tol)
236252

237253
end subroutine MossA
238254

0 commit comments

Comments
 (0)