-
Notifications
You must be signed in to change notification settings - Fork 92
/
EDPhysiologyMod.F90
2590 lines (2014 loc) · 112 KB
/
EDPhysiologyMod.F90
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
module EDPhysiologyMod
#include "shr_assert.h"
! ============================================================================
! Miscellaneous physiology routines from ED.
! ============================================================================
use FatesGlobals, only : fates_log
use FatesInterfaceTypesMod, only : hlm_days_per_year
use FatesInterfaceTypesMod, only : hlm_model_day
use FatesInterfaceTypesMod, only : hlm_freq_day
use FatesInterfaceTypesMod, only : hlm_day_of_year
use FatesInterfaceTypesMod, only : numpft
use FatesInterfaceTypesMod, only : nleafage
use FatesInterfaceTypesMod, only : hlm_use_planthydro
use FatesInterfaceTypesMod, only : hlm_parteh_mode
use FatesInterfaceTypesMod, only : hlm_use_fixed_biogeog
use FatesInterfaceTypesMod, only : hlm_use_nocomp
use FatesInterfaceTypesMod, only : hlm_nitrogen_spec
use FatesInterfaceTypesMod, only : hlm_phosphorus_spec
use FatesConstantsMod, only : r8 => fates_r8
use FatesConstantsMod, only : nearzero
use EDPftvarcon , only : EDPftvarcon_inst
use PRTParametersMod , only : prt_params
use EDPftvarcon , only : GetDecompyFrac
use FatesInterfaceTypesMod, only : bc_in_type
use FatesInterfaceTypesMod, only : bc_out_type
use EDCohortDynamicsMod , only : zero_cohort
use EDCohortDynamicsMod , only : create_cohort, sort_cohorts
use EDCohortDynamicsMod , only : InitPRTObject
use FatesAllometryMod , only : tree_lai
use FatesAllometryMod , only : tree_sai
use FatesAllometryMod , only : leafc_from_treelai
use FatesAllometryMod , only : decay_coeff_kn
use FatesLitterMod , only : litter_type
use EDTypesMod , only : site_massbal_type
use EDTypesMod , only : numlevsoil_max
use EDTypesMod , only : numWaterMem
use EDTypesMod , only : dl_sf, dinc_vai, dlower_vai, area_inv
use EDTypesMod , only : AREA
use FatesLitterMod , only : ncwd
use FatesLitterMod , only : ndcmpy
use FatesLitterMod , only : ilabile
use FatesLitterMod , only : ilignin
use FatesLitterMod , only : icellulose
use EDTypesMod , only : AREA,AREA_INV
use EDTypesMod , only : nlevleaf
use EDTypesMod , only : num_vegtemp_mem
use EDTypesMod , only : maxpft
use EDTypesMod , only : ed_site_type, ed_patch_type, ed_cohort_type
use EDTypesMod , only : leaves_on
use EDTypesMod , only : leaves_off
use EDTypesMod , only : min_n_safemath
use PRTGenericMod , only : num_elements
use PRTGenericMod , only : element_list
use PRTGenericMod , only : element_pos
use EDTypesMod , only : site_fluxdiags_type
use EDTypesMod , only : phen_cstat_nevercold
use EDTypesMod , only : phen_cstat_iscold
use EDTypesMod , only : phen_cstat_notcold
use EDTypesMod , only : phen_dstat_timeoff
use EDTypesMod , only : phen_dstat_moistoff
use EDTypesMod , only : phen_dstat_moiston
use EDTypesMod , only : phen_dstat_timeon
use EDTypesMod , only : init_recruit_trim
use shr_log_mod , only : errMsg => shr_log_errMsg
use FatesGlobals , only : fates_log
use FatesGlobals , only : endrun => fates_endrun
use EDParamsMod , only : fates_mortality_disturbance_fraction
use EDParamsMod , only : q10_mr
use EDParamsMod , only : q10_froz
use EDParamsMod , only : logging_export_frac
use FatesPlantHydraulicsMod , only : AccumulateMortalityWaterStorage
use FatesConstantsMod , only : itrue,ifalse
use FatesConstantsMod , only : calloc_abs_error
use FatesConstantsMod , only : years_per_day
use FatesAllometryMod , only : h_allom
use FatesAllometryMod , only : h2d_allom
use FatesAllometryMod , only : bagw_allom
use FatesAllometryMod , only : bsap_allom
use FatesAllometryMod , only : bleaf
use FatesAllometryMod , only : bfineroot
use FatesAllometryMod , only : bdead_allom
use FatesAllometryMod , only : bstore_allom
use FatesAllometryMod , only : bbgw_allom
use FatesAllometryMod , only : carea_allom
use FatesAllometryMod , only : CheckIntegratedAllometries
use FatesAllometryMod, only : set_root_fraction
use PRTGenericMod, only : prt_carbon_allom_hyp
use PRTGenericMod, only : prt_cnp_flex_allom_hyp
use PRTGenericMod, only : prt_vartypes
use PRTGenericMod, only : leaf_organ
use PRTGenericMod, only : sapw_organ, struct_organ
use PRTGenericMod, only : all_carbon_elements
use PRTGenericMod, only : carbon12_element
use PRTGenericMod, only : nitrogen_element
use PRTGenericMod, only : phosphorus_element
use PRTGenericMod, only : leaf_organ
use PRTGenericMod, only : fnrt_organ
use PRTGenericMod, only : sapw_organ
use PRTGenericMod, only : store_organ
use PRTGenericMod, only : repro_organ
use PRTGenericMod, only : struct_organ
use PRTGenericMod, only : SetState
use PRTLossFluxesMod, only : PRTPhenologyFlush
use PRTLossFluxesMod, only : PRTDeciduousTurnover
use PRTLossFluxesMod, only : PRTReproRelease
use PRTGenericMod, only : StorageNutrientTarget
implicit none
private
public :: trim_canopy
public :: phenology
public :: satellite_phenology
public :: assign_cohort_SP_properties
public :: recruitment
public :: ZeroLitterFluxes
public :: ZeroAllocationRates
public :: PreDisturbanceLitterFluxes
public :: PreDisturbanceIntegrateLitter
public :: SeedIn
logical, parameter :: debug = .false. ! local debug flag
character(len=*), parameter, private :: sourcefile = &
__FILE__
integer, parameter :: dleafon_drycheck = 100 ! Drought deciduous leaves max days on check parameter
! ============================================================================
contains
subroutine ZeroLitterFluxes( currentSite )
! This routine loops through all patches in a site
! and zero's the flux terms for the litter pools.
! This is typically called at the beginning of the dynamics
! call sequence.
! !ARGUMENTS
type(ed_site_type), intent(inout), target :: currentSite
type(ed_patch_type), pointer :: currentPatch
integer :: el
currentPatch => currentSite%youngest_patch
do while(associated(currentPatch))
do el=1,num_elements
call currentPatch%litter(el)%ZeroFlux()
end do
currentPatch => currentPatch%older
end do
return
end subroutine ZeroLitterFluxes
! =====================================================================================
subroutine ZeroAllocationRates( currentSite )
! !ARGUMENTS
type(ed_site_type), intent(inout), target :: currentSite
type(ed_patch_type), pointer :: currentPatch
type(ed_cohort_type), pointer :: currentCohort
currentPatch => currentSite%youngest_patch
do while(associated(currentPatch))
currentCohort => currentPatch%tallest
do while (associated(currentCohort))
! This sets turnover and growth rates to zero
call currentCohort%prt%ZeroRates()
currentCohort => currentCohort%shorter
enddo
currentPatch => currentPatch%older
end do
return
end subroutine ZeroAllocationRates
! ============================================================================
subroutine PreDisturbanceLitterFluxes( currentSite, currentPatch, bc_in )
! -----------------------------------------------------------------------------------
!
! This subroutine calculates all of the different litter input and output fluxes
! associated with seed turnover, seed influx, litterfall from live and
! dead plants, germination, and fragmentation.
!
! At this time we do not have explicit herbivory, and burning losses to litter
! are handled elsewhere.
!
! Note: The processes conducted here DO NOT handle litter fluxes associated
! with disturbance. Those fluxes are handled elsewhere (EDPatchDynamcisMod)
! because the fluxes are potentially cross patch, and also dealing
! patch areas that are changing.
!
! -----------------------------------------------------------------------------------
! !ARGUMENTS
type(ed_site_type), intent(inout) :: currentSite
type(ed_patch_type), intent(inout) :: currentPatch
type(bc_in_type), intent(in) :: bc_in
!
! !LOCAL VARIABLES:
type(site_massbal_type), pointer :: site_mass
type(litter_type), pointer :: litt ! Points to the litter object for
! the different element types
integer :: el ! Litter element loop index
integer :: nlev_eff_decomp ! Number of active layers over which
! fragmentation fluxes are transfered
!------------------------------------------------------------------------------------
! Calculate the fragmentation rates
call fragmentation_scaler(currentPatch, bc_in)
do el = 1, num_elements
litt => currentPatch%litter(el)
! Calculate loss rate of viable seeds to litter
call SeedDecay(litt)
! Calculate seed germination rate, the status flags prevent
! germination from occuring when the site is in a drought
! (for drought deciduous) or too cold (for cold deciduous)
call SeedGermination(litt, currentSite%cstatus, currentSite%dstatus)
! Send fluxes from newly created litter into the litter pools
! This litter flux is from non-disturbance inducing mortality, as well
! as litter fluxes from live trees
call CWDInput(currentSite, currentPatch, litt,bc_in)
! Only calculate fragmentation flux over layers that are active
! (RGK-Mar2019) SHOULD WE MAX THIS AT 1? DONT HAVE TO
nlev_eff_decomp = max(bc_in%max_rooting_depth_index_col, 1)
call CWDOut(litt,currentPatch%fragmentation_scaler,nlev_eff_decomp)
site_mass => currentSite%mass_balance(el)
! Fragmentation flux to soil decomposition model [kg/site/day]
site_mass%frag_out = site_mass%frag_out + currentPatch%area * &
( sum(litt%ag_cwd_frag) + sum(litt%bg_cwd_frag) + &
sum(litt%leaf_fines_frag) + sum(litt%root_fines_frag) + &
sum(litt%seed_decay) + sum(litt%seed_germ_decay))
end do
return
end subroutine PreDisturbanceLitterFluxes
! =====================================================================================
subroutine PreDisturbanceIntegrateLitter(currentPatch)
! -----------------------------------------------------------------------------------
!
! This step applies the litter fluxes to the prognostic state variables.
! This procedure is called in response to fluxes generated from:
! 1) seed rain,
! 2) non-disturbance generating turnover
! 3) litter fall from living plants
! 4) fragmentation
!
! This routine does NOT accomodate the litter fluxes associated with
! disturbance generation. That will happen after this call.
! Fluxes associated with FIRE also happen after this step.
!
! All states are in units kg/m2
! All fluxes are in units kg/m2/day
! The integration step is 1 day, thus time is implied
!
! -----------------------------------------------------------------------------------
! Arguments
type(ed_patch_type),intent(inout),target :: currentPatch
! Locals
type(litter_type), pointer :: litt
integer :: el ! Loop counter for litter element type
integer :: pft ! pft loop counter
integer :: c ! CWD loop counter
integer :: nlevsoil ! number of soil layers
integer :: ilyr ! soil layer loop counter
integer :: dcmpy ! decomposability index
do el = 1, num_elements
litt => currentPatch%litter(el)
! Update the bank of viable seeds
! -----------------------------------------------------------------------------------
do pft = 1,numpft
litt%seed(pft) = litt%seed(pft) + &
litt%seed_in_local(pft) + &
litt%seed_in_extern(pft) - &
litt%seed_decay(pft) - &
litt%seed_germ_in(pft)
! Note that the recruitment scheme will use seed_germ
! for its construction costs.
litt%seed_germ(pft) = litt%seed_germ(pft) + &
litt%seed_germ_in(pft) - &
litt%seed_germ_decay(pft)
enddo
! Update the Coarse Woody Debris pools (above and below)
! -----------------------------------------------------------------------------------
nlevsoil = size(litt%bg_cwd,dim=2)
do c = 1,ncwd
litt%ag_cwd(c) = litt%ag_cwd(c) + litt%ag_cwd_in(c) - litt%ag_cwd_frag(c)
do ilyr=1,nlevsoil
litt%bg_cwd(c,ilyr) = litt%bg_cwd(c,ilyr) &
+ litt%bg_cwd_in(c,ilyr) &
- litt%bg_cwd_frag(c,ilyr)
enddo
end do
! Update the fine litter pools from leaves and fine-roots
! -----------------------------------------------------------------------------------
do dcmpy = 1,ndcmpy
litt%leaf_fines(dcmpy) = litt%leaf_fines(dcmpy) &
+ litt%leaf_fines_in(dcmpy) &
- litt%leaf_fines_frag(dcmpy)
do ilyr=1,nlevsoil
litt%root_fines(dcmpy,ilyr) = litt%root_fines(dcmpy,ilyr) &
+ litt%root_fines_in(dcmpy,ilyr) &
- litt%root_fines_frag(dcmpy,ilyr)
enddo
end do
end do ! litter element loop
return
end subroutine PreDisturbanceIntegrateLitter
! ============================================================================
subroutine trim_canopy( currentSite )
!
! !DESCRIPTION:
! Canopy trimming / leaf optimisation. Removes leaves in negative annual carbon balance.
!
! !USES:
! !ARGUMENTS
type (ed_site_type),intent(inout), target :: currentSite
!
! !LOCAL VARIABLES:
type (ed_cohort_type) , pointer :: currentCohort
type (ed_patch_type) , pointer :: currentPatch
integer :: z ! leaf layer
integer :: ipft ! pft index
logical :: trimmed ! was this layer trimmed in this year? If not expand the canopy.
real(r8) :: tar_bl ! target leaf biomass (leaves flushed, trimmed)
real(r8) :: tar_bfr ! target fine-root biomass (leaves flushed, trimmed)
real(r8) :: bfr_per_bleaf ! ratio of fine root per leaf biomass
real(r8) :: sla_levleaf ! sla at leaf level z
real(r8) :: nscaler_levleaf ! nscaler value at leaf level z
integer :: cl ! canopy layer index
real(r8) :: kn ! nitrogen decay coefficient
real(r8) :: sla_max ! Observational constraint on how large sla (m2/gC) can become
real(r8) :: leaf_c ! leaf carbon [kg]
real(r8) :: sapw_c ! sapwood carbon [kg]
real(r8) :: store_c ! storage carbon [kg]
real(r8) :: struct_c ! structure carbon [kg]
real(r8) :: leaf_inc ! LAI-only portion of the vegetation increment of dinc_vai
real(r8) :: lai_canopy_above ! the LAI in the canopy layers above the layer of interest
real(r8) :: lai_layers_above ! the LAI in the leaf layers, within the current canopy,
! above the leaf layer of interest
real(r8) :: lai_current ! the LAI in the current leaf layer
real(r8) :: cumulative_lai ! whole canopy cumulative LAI, top down, to the leaf layer of interest
real(r8) :: cumulative_lai_cohort ! cumulative LAI within the current cohort only
! Temporary diagnostic ouptut
integer :: ipatch
integer :: icohort
! LAPACK linear least squares fit variables
! The standard equation for a linear fit, y = mx + b, is converted to a linear system, AX=B and has
! the form: [n sum(x); sum(x) sum(x^2)] * [b; m] = [sum(y); sum(x*y)] where
! n is the number of leaf layers
! x is yearly_net_uptake minus the leaf cost aka the net-net uptake
! y is the cumulative lai for the current cohort
! b is the y-intercept i.e. the cumulative lai that has zero net-net uptake
! m is the slope of the linear fit
integer :: nll = 3 ! Number of leaf layers to fit a regression to for calculating the optimum lai
character(1) :: trans = 'N' ! Input matrix is not transposed
integer, parameter :: m = 2, n = 2 ! Number of rows and columns, respectively, in matrix A
integer, parameter :: nrhs = 1 ! Number of columns in matrix B and X
integer, parameter :: workmax = 100 ! Maximum iterations to minimize work
integer :: lda = m, ldb = n ! Leading dimension of A and B, respectively
integer :: lwork ! Dimension of work array
integer :: info ! Procedure diagnostic ouput
real(r8) :: nnu_clai_a(m,n) ! LHS of linear least squares fit, A matrix
real(r8) :: nnu_clai_b(m,nrhs) ! RHS of linear least squares fit, B matrix
real(r8) :: work(workmax) ! work array
real(r8) :: initial_trim ! Initial trim
real(r8) :: optimum_trim ! Optimum trim value
real(r8) :: initial_leafmem ! Initial leafmemory
real(r8) :: optimum_leafmem ! Optimum leafmemory
!----------------------------------------------------------------------
ipatch = 1 ! Start counting patches
currentPatch => currentSite%youngest_patch
do while(associated(currentPatch))
! Add debug diagnstic output to determine which patch
if (debug) then
write(fates_log(),*) 'Current patch:', ipatch
write(fates_log(),*) 'Current patch cohorts:', currentPatch%countcohorts
endif
icohort = 1
currentCohort => currentPatch%tallest
do while (associated(currentCohort))
! Save off the incoming trim and leafmemory
initial_trim = currentCohort%canopy_trim
initial_leafmem = currentCohort%leafmemory
! Add debug diagnstic output to determine which cohort
if (debug) then
write(fates_log(),*) 'Current cohort:', icohort
write(fates_log(),*) 'Starting canopy trim:', initial_trim
write(fates_log(),*) 'Starting leafmemory:', currentCohort%leafmemory
endif
trimmed = .false.
ipft = currentCohort%pft
call carea_allom(currentCohort%dbh,currentCohort%n,currentSite%spread,currentCohort%pft,currentCohort%c_area)
leaf_c = currentCohort%prt%GetState(leaf_organ, all_carbon_elements)
currentCohort%treelai = tree_lai(leaf_c, currentCohort%pft, currentCohort%c_area, &
currentCohort%n, currentCohort%canopy_layer, &
currentPatch%canopy_layer_tlai,currentCohort%vcmax25top )
! We don't need check on sp mode here since we don't trim_canopy with sp mode
currentCohort%treesai = tree_sai(currentCohort%pft, currentCohort%dbh, currentCohort%canopy_trim, &
currentCohort%c_area, currentCohort%n, currentCohort%canopy_layer, &
currentPatch%canopy_layer_tlai, currentCohort%treelai, &
currentCohort%vcmax25top,0 )
currentCohort%nv = count((currentCohort%treelai+currentCohort%treesai) .gt. dlower_vai(:)) + 1
if (currentCohort%nv > nlevleaf)then
write(fates_log(),*) 'nv > nlevleaf',currentCohort%nv, &
currentCohort%treelai,currentCohort%treesai, &
currentCohort%c_area,currentCohort%n,leaf_c
call endrun(msg=errMsg(sourcefile, __LINE__))
endif
call bleaf(currentcohort%dbh,ipft,currentcohort%canopy_trim,tar_bl)
if ( int(prt_params%allom_fmode(ipft)) .eq. 1 ) then
! only query fine root biomass if using a fine root allometric model that takes leaf trim into account
call bfineroot(currentcohort%dbh,ipft,currentcohort%canopy_trim,tar_bfr)
bfr_per_bleaf = tar_bfr/tar_bl
endif
! Identify current canopy layer (cl)
cl = currentCohort%canopy_layer
! PFT-level maximum SLA value, even if under a thick canopy (same units as slatop)
sla_max = prt_params%slamax(ipft)
! Initialize nnu_clai_a
nnu_clai_a(:,:) = 0._r8
nnu_clai_b(:,:) = 0._r8
!Leaf cost vs netuptake for each leaf layer.
do z = 1, currentCohort%nv
! Calculate the cumulative total vegetation area index (no snow occlusion, stems and leaves)
leaf_inc = dinc_vai(z) * &
currentCohort%treelai/(currentCohort%treelai+currentCohort%treesai)
! Now calculate the cumulative top-down lai of the current layer's midpoint within the current cohort
lai_layers_above = (dlower_vai(z) - dinc_vai(z)) * &
currentCohort%treelai/(currentCohort%treelai+currentCohort%treesai)
lai_current = min(leaf_inc, currentCohort%treelai - lai_layers_above)
cumulative_lai_cohort = lai_layers_above + 0.5*lai_current
! Now add in the lai above the current cohort for calculating the sla leaf level
lai_canopy_above = sum(currentPatch%canopy_layer_tlai(1:cl-1))
cumulative_lai = lai_canopy_above + cumulative_lai_cohort
! There was activity this year in this leaf layer. This should only occur for bottom most leaf layer
if (currentCohort%year_net_uptake(z) /= 999._r8)then
! Calculate sla_levleaf following the sla profile with overlying leaf area
! Scale for leaf nitrogen profile
kn = decay_coeff_kn(ipft,currentCohort%vcmax25top)
! Nscaler value at leaf level z
nscaler_levleaf = exp(-kn * cumulative_lai)
! Sla value at leaf level z after nitrogen profile scaling (m2/gC)
sla_levleaf = prt_params%slatop(ipft)/nscaler_levleaf
if(sla_levleaf > sla_max)then
sla_levleaf = sla_max
end if
!Leaf Cost kgC/m2/year-1
!decidous costs.
if (prt_params%season_decid(ipft) == itrue .or. &
prt_params%stress_decid(ipft) == itrue )then
! Leaf cost at leaf level z accounting for sla profile (kgC/m2)
currentCohort%leaf_cost = 1._r8/(sla_levleaf*1000.0_r8)
if ( int(prt_params%allom_fmode(ipft)) .eq. 1 ) then
! if using trimmed leaf for fine root biomass allometry, add the cost of the root increment
! to the leaf increment; otherwise do not.
currentCohort%leaf_cost = currentCohort%leaf_cost + &
1.0_r8/(sla_levleaf*1000.0_r8) * &
bfr_per_bleaf / prt_params%root_long(ipft)
endif
currentCohort%leaf_cost = currentCohort%leaf_cost * &
(prt_params%grperc(ipft) + 1._r8)
else !evergreen costs
! Leaf cost at leaf level z accounting for sla profile
currentCohort%leaf_cost = 1.0_r8/(sla_levleaf* &
sum(prt_params%leaf_long(ipft,:))*1000.0_r8) !convert from sla in m2g-1 to m2kg-1
if ( int(prt_params%allom_fmode(ipft)) .eq. 1 ) then
! if using trimmed leaf for fine root biomass allometry, add the cost of the root increment
! to the leaf increment; otherwise do not.
currentCohort%leaf_cost = currentCohort%leaf_cost + &
1.0_r8/(sla_levleaf*1000.0_r8) * &
bfr_per_bleaf / prt_params%root_long(ipft)
endif
currentCohort%leaf_cost = currentCohort%leaf_cost * &
(prt_params%grperc(ipft) + 1._r8)
endif
! Construct the arrays for a least square fit of the net_net_uptake versus the cumulative lai
! if at least nll leaf layers are present in the current cohort and only for the bottom nll
! leaf layers.
if (currentCohort%nv > nll .and. currentCohort%nv - z < nll) then
! Build the A matrix for the LHS of the linear system. A = [n sum(x); sum(x) sum(x^2)]
! where n = nll and x = yearly_net_uptake-leafcost
nnu_clai_a(1,1) = nnu_clai_a(1,1) + 1 ! Increment for each layer used
nnu_clai_a(1,2) = nnu_clai_a(1,2) + currentCohort%year_net_uptake(z) - currentCohort%leaf_cost
nnu_clai_a(2,1) = nnu_clai_a(1,2)
nnu_clai_a(2,2) = nnu_clai_a(2,2) + (currentCohort%year_net_uptake(z) - currentCohort%leaf_cost)**2
! Build the B matrix for the RHS of the linear system. B = [sum(y); sum(x*y)]
! where x = yearly_net_uptake-leafcost and y = cumulative_lai_cohort
nnu_clai_b(1,1) = nnu_clai_b(1,1) + cumulative_lai_cohort
nnu_clai_b(2,1) = nnu_clai_b(2,1) + (cumulative_lai_cohort * &
(currentCohort%year_net_uptake(z) - currentCohort%leaf_cost))
end if
! Check leaf cost against the yearly net uptake for that cohort leaf layer
if (currentCohort%year_net_uptake(z) < currentCohort%leaf_cost) then
! Make sure the cohort trim fraction is great than the pft trim limit
if (currentCohort%canopy_trim > EDPftvarcon_inst%trim_limit(ipft)) then
! keep trimming until none of the canopy is in negative carbon balance.
if (currentCohort%hite > EDPftvarcon_inst%hgt_min(ipft)) then
currentCohort%canopy_trim = currentCohort%canopy_trim - &
EDPftvarcon_inst%trim_inc(ipft)
if (prt_params%evergreen(ipft) /= 1)then
currentCohort%leafmemory = currentCohort%leafmemory * &
(1.0_r8 - EDPftvarcon_inst%trim_inc(ipft))
endif
trimmed = .true.
endif ! hite check
endif ! trim limit check
endif ! net uptake check
endif ! leaf activity check
enddo ! z, leaf layer loop
! Compute the optimal cumulative lai based on the cohort net-net uptake profile if at least 2 leaf layers
if (nnu_clai_a(1,1) > 1) then
! Compute the optimum size of the work array
lwork = -1 ! Ask sgels to compute optimal number of entries for work
call dgels(trans, m, n, nrhs, nnu_clai_a, lda, nnu_clai_b, ldb, work, lwork, info)
lwork = int(work(1)) ! Pick the optimum. TBD, can work(1) come back with greater than work size?
! Compute the minimum of 2-norm of of the least squares fit to solve for X
! Note that dgels returns the solution by overwriting the nnu_clai_b array.
! The result has the form: X = [b; m]
! where b = y-intercept (i.e. the cohort lai that has zero yearly net-net uptake)
! and m is the slope of the linear fit
call dgels(trans, m, n, nrhs, nnu_clai_a, lda, nnu_clai_b, ldb, work, lwork, info)
if (info < 0) then
write(fates_log(),*) 'LLSF optimium LAI calculation returned illegal value'
call endrun(msg=errMsg(sourcefile, __LINE__))
endif
if (debug) then
write(fates_log(),*) 'LLSF optimium LAI (intercept,slope):', nnu_clai_b
write(fates_log(),*) 'LLSF optimium LAI:', nnu_clai_b(1,1)
write(fates_log(),*) 'LLSF optimium LAI info:', info
write(fates_log(),*) 'LAI fraction (optimum_lai/cumulative_lai):', nnu_clai_b(1,1) / cumulative_lai_cohort
endif
! Calculate the optimum trim based on the initial canopy trim value
if (cumulative_lai_cohort > 0._r8) then ! Sometime cumulative_lai comes in at 0.0?
!
optimum_trim = (nnu_clai_b(1,1) / cumulative_lai_cohort) * initial_trim
optimum_leafmem = (nnu_clai_b(1,1) / cumulative_lai_cohort) * initial_leafmem
! Determine if the optimum trim value makes sense. The smallest cohorts tend to have unrealistic fits.
if (optimum_trim > 0. .and. optimum_trim < 1.) then
currentCohort%canopy_trim = optimum_trim
! If the cohort pft is not evergreen we reduce the leafmemory as well
if (prt_params%evergreen(ipft) /= 1) then
currentCohort%leafmemory = optimum_leafmem
endif
trimmed = .true.
endif
endif
endif
! Reset activity for the cohort for the start of the next year
currentCohort%year_net_uptake(:) = 999.0_r8
! Add to trim fraction if cohort not trimmed at all
if ( (.not.trimmed) .and.currentCohort%canopy_trim < 1.0_r8)then
currentCohort%canopy_trim = currentCohort%canopy_trim + EDPftvarcon_inst%trim_inc(ipft)
endif
if ( debug ) then
write(fates_log(),*) 'trimming:',currentCohort%canopy_trim
endif
! currentCohort%canopy_trim = 1.0_r8 !FIX(RF,032414) this turns off ctrim for now.
currentCohort => currentCohort%shorter
icohort = icohort + 1
enddo
currentPatch => currentPatch%older
ipatch = ipatch + 1
enddo
end subroutine trim_canopy
! ============================================================================
subroutine phenology( currentSite, bc_in )
!
! !DESCRIPTION:
! Phenology.
!
! !USES:
use FatesConstantsMod, only : tfrz => t_water_freeze_k_1atm
use EDParamsMod, only : ED_val_phen_drought_threshold, ED_val_phen_doff_time
use EDParamsMod, only : ED_val_phen_a, ED_val_phen_b, ED_val_phen_c, ED_val_phen_chiltemp
use EDParamsMod, only : ED_val_phen_mindayson, ED_val_phen_ncolddayslim, ED_val_phen_coldtemp
!
! !ARGUMENTS:
type(ed_site_type), intent(inout), target :: currentSite
type(bc_in_type), intent(in) :: bc_in
!
! !LOCAL VARIABLES:
type(ed_patch_type),pointer :: cpatch
integer :: model_day_int ! integer model day 1 - inf
integer :: ncolddays ! no days underneath the threshold for leaf drop
integer :: i_wmem ! Loop counter for water mem days
integer :: i_tmem ! Loop counter for veg temp mem days
integer :: dayssincedleafon ! Days since drought-decid leaf-on started
integer :: dayssincedleafoff ! Days since drought-decid leaf-off started
integer :: dayssincecleafon ! Days since cold-decid leaf-on started
integer :: dayssincecleafoff ! Days since cold-decid leaf-off started
real(r8) :: mean_10day_liqvol ! mean liquid volume (m3/m3) over last 10 days
real(r8) :: leaf_c ! leaf carbon [kg]
real(r8) :: fnrt_c ! fineroot carbon [kg]
real(r8) :: sapw_c ! sapwood carbon [kg]
real(r8) :: store_c ! storage carbon [kg]
real(r8) :: struct_c ! structure carbon [kg]
real(r8) :: gdd_threshold ! GDD accumulation function,
integer :: ilayer_swater ! Layer index for soil water
! which also depends on chilling days.
integer :: ncdstart ! beginning of counting period for chilling degree days.
integer :: gddstart ! beginning of counting period for growing degree days.
real(r8) :: temp_in_C ! daily averaged temperature in celcius
integer, parameter :: canopy_leaf_lifespan = 365 ! Maximum lifespan of drought decid leaves
integer, parameter :: min_daysoff_dforcedflush = 30 ! THis is the number of days that must had elapsed
! since leaves had dropped, in order to forcably
! flush leaves again. This does not impact flushing
! due to real moisture constraints, and will prevent
! drought deciduous in perennially wet environments
! that have been forced to drop their leaves, from
! flushing them back immediately.
real(r8),parameter :: dphen_soil_depth = 0.1 ! Use liquid soil water that is
! closest to this depth [m]
! This is the integer model day. The first day of the simulation is 1, and it
! continues monotonically, indefinitely
! Advance it. (this should be a global, no reason
! for site level, but we don't have global scalars in the
! restart file)
currentSite%phen_model_date = currentSite%phen_model_date + 1
model_day_int = currentSite%phen_model_date
! Use the following layer index to calculate drought conditions
ilayer_swater = minloc(abs(bc_in%z_sisl(:)-dphen_soil_depth),dim=1)
! Parameter of drought decid leaf loss in mm in top layer...FIX(RF,032414)
! - this is arbitrary and poorly understood. Needs work. ED_
!Parameters: defaults from Botta et al. 2000 GCB,6 709-725
!Parameters, default from from SDGVM model of senesence
temp_in_C = 0._r8
cpatch => CurrentSite%oldest_patch
do while(associated(cpatch))
temp_in_C = temp_in_C + cpatch%tveg24%GetMean()*cpatch%area
cpatch => cpatch%younger
end do
temp_in_C = temp_in_C * area_inv - tfrz
!-----------------Cold Phenology--------------------!
!Zero growing degree and chilling day counters
if (currentSite%lat > 0)then
ncdstart = 270 !Northern Hemisphere begining November
gddstart = 1 !Northern Hemisphere begining January
else
ncdstart = 120 !Southern Hemisphere beginning May
gddstart = 181 !Northern Hemisphere begining July
endif
! Count the number of chilling days over a seasonal window.
! For comparing against GDD, we start calculating chilling
! in the late autumn.
! This value is used to determine the GDD exceedance threshold
if (hlm_day_of_year == ncdstart)then
currentSite%nchilldays = 0
endif
!Accumulate growing/chilling days after start of counting period
if (temp_in_C < ED_val_phen_chiltemp)then
currentSite%nchilldays = currentSite%nchilldays + 1
endif
!GDD accumulation function, which also depends on chilling days.
! -68 + 638 * (-0.001 * ncd)
gdd_threshold = ED_val_phen_a + ED_val_phen_b*exp(ED_val_phen_c*real(currentSite%nchilldays,r8))
!Accumulate temperature of last 10 days.
currentSite%vegtemp_memory(2:num_vegtemp_mem) = currentSite%vegtemp_memory(1:num_vegtemp_mem-1)
currentSite%vegtemp_memory(1) = temp_in_C
!count number of days for leaves off
ncolddays = 0
do i_tmem = 1,num_vegtemp_mem
if (currentSite%vegtemp_memory(i_tmem) < ED_val_phen_coldtemp)then
ncolddays = ncolddays + 1
endif
enddo
! Here is where we do the GDD accumulation calculation
!
! reset GDD on set dates
if (hlm_day_of_year == gddstart)then
currentSite%grow_deg_days = 0._r8
endif
!
! accumulate the GDD using daily mean temperatures
! Don't accumulate GDD during the growing season (that wouldn't make sense)
if (temp_in_C .gt. 0._r8 .and. currentSite%cstatus == phen_cstat_iscold) then
currentSite%grow_deg_days = currentSite%grow_deg_days + temp_in_C
endif
!this logic is to prevent GDD accumulating after the leaves have fallen and before the
! beginnning of the accumulation period, to prevend erroneous autumn leaf flushing.
if(model_day_int>365)then !only do this after the first year to prevent odd behaviour
if(currentSite%lat .gt. 0.0_r8)then !Northern Hemisphere
! In the north, don't accumulate when we are past the leaf fall date.
! Accumulation starts on day 1 of year in NH.
! The 180 is to prevent going into an 'always off' state after initialization
if( model_day_int .gt. currentSite%cleafoffdate.and.hlm_day_of_year.gt.180)then !
currentSite%grow_deg_days = 0._r8
endif
else !Southern Hemisphere
! In the South, don't accumulate after the leaf off date, and before the start of
! the accumulation phase (day 181).
if(model_day_int .gt. currentSite%cleafoffdate.and.hlm_day_of_year.lt.gddstart) then!
currentSite%grow_deg_days = 0._r8
endif
endif
endif !year1
! Calculate the number of days since the leaves last came on
! and off. If this is the beginning of the simulation, that day might
! not had occured yet, so set it to last year to get things rolling
if (model_day_int < currentSite%cleafoffdate) then
dayssincecleafoff = model_day_int - (currentSite%cleafoffdate - 365)
else
dayssincecleafoff = model_day_int - currentSite%cleafoffdate
end if
if (model_day_int < currentSite%cleafondate) then
dayssincecleafon = model_day_int - (currentSite%cleafondate - 365)
else
dayssincecleafon = model_day_int - currentSite%cleafondate
end if
!LEAF ON: COLD DECIDUOUS. Needs to
!1) have exceeded the growing degree day threshold
!2) The leaves should not be on already
!3) There should have been at least one chilling day in the counting period.
! this prevents tropical or warm climate plants that are "cold-deciduous"
! from ever re-flushing after they have reached their maximum age (thus
! preventing them from competing
if ( (currentSite%cstatus == phen_cstat_iscold .or. &
currentSite%cstatus == phen_cstat_nevercold) .and. &
(currentSite%grow_deg_days > gdd_threshold) .and. &
(dayssincecleafoff > ED_val_phen_mindayson) .and. &
(currentSite%nchilldays >= 1)) then
currentSite%cstatus = phen_cstat_notcold ! Set to not-cold status (leaves can come on)
currentSite%cleafondate = model_day_int
dayssincecleafon = 0
currentSite%grow_deg_days = 0._r8 ! zero GDD for the rest of the year until counting season begins.
if ( debug ) write(fates_log(),*) 'leaves on'
endif !GDD
!LEAF OFF: COLD THRESHOLD
!Needs to:
!1) have exceeded the number of cold days threshold
!2) have exceeded the minimum leafon time.
!3) The leaves should not be off already
!4) The day of simulation should be larger than the counting period.
if ( (currentSite%cstatus == phen_cstat_notcold) .and. &
(model_day_int > num_vegtemp_mem) .and. &
(ncolddays > ED_val_phen_ncolddayslim) .and. &
(dayssincecleafon > ED_val_phen_mindayson) )then
currentSite%grow_deg_days = 0._r8 ! The equations for Botta et al
! are for calculations of
! first flush, but if we dont
! clear this value, it will cause
! leaves to flush later in the year
currentSite%cstatus = phen_cstat_iscold ! alter status of site to 'leaves off'
currentSite%cleafoffdate = model_day_int ! record leaf off date
if ( debug ) write(fates_log(),*) 'leaves off'
endif
! LEAF OFF: COLD LIFESPAN THRESHOLD
! NOTE: Some areas of the planet will never generate a cold day
! and thus %nchilldays will never go from zero to 1. The following logic
! when coupled with this fact will essentially prevent cold-deciduous
! plants from re-emerging in areas without at least some cold days
if( (currentSite%cstatus == phen_cstat_notcold) .and. &
(dayssincecleafoff > 400)) then ! remove leaves after a whole year
! when there is no 'off' period.
currentSite%grow_deg_days = 0._r8
currentSite%cstatus = phen_cstat_nevercold ! alter status of site to imply that this
! site is never really cold enough
! for cold deciduous
currentSite%cleafoffdate = model_day_int ! record leaf off date
if ( debug ) write(fates_log(),*) 'leaves off'
endif
!-----------------Drought Phenology--------------------!
! Principles of drought-deciduos phenology model...
! The 'is_drought' flag is false when leaves are on, and true when leaves area off.
! The following sets those site-level flags, which are acted on in phenology_deciduos.
! A* The leaves live for either the length of time the soil moisture is over the threshold
! or the lifetime of the leaves, whichever is shorter.
! B*: If the soil is only wet for a very short time, then the leaves stay on for 100 days
! C*: The leaves are only permitted to come ON for a 60 day window around when they last came on,
! to prevent 'flickering' on in response to wet season storms
! D*: We don't allow anything to happen in the first ten days to allow the water memory window
! to come into equlibirium.
! E*: If the soil is always wet, the leaves come on at the beginning of the window, and then
! last for their lifespan.
! ISSUES
! 1. It's not clear what water content we should track. Here we are tracking the top layer,
! but we probably should track something like BTRAN, but BTRAN is defined for each PFT,
! and there could potentially be more than one stress-dec PFT.... ?
! 2. In the beginning, the window is set at an arbitrary time of the year, so the leaves
! might come on in the dry season, using up stored reserves
! for the stress-dec plants, and potentially killing them. To get around this,
! we need to read in the 'leaf on' date from some kind of start-up file
! but we would need that to happen for every resolution, etc.
! 3. Will this methodology properly kill off the stress-dec trees where there is no
! water stress? What about where the wet period coincides with the warm period?
! We would just get them overlapping with the cold-dec trees, even though that isn't appropriate
! Why don't the drought deciduous trees grow in the North?
! Is cold decidousness maybe even the same as drought deciduosness there (and so does this
! distinction actually matter??)....
! Accumulate surface water memory of last 10 days.
! Liquid volume in ground layer (m3/m3)
do i_wmem = 1,numWaterMem-1 !shift memory along one
currentSite%water_memory(numWaterMem+1-i_wmem) = currentSite%water_memory(numWaterMem-i_wmem)
enddo
currentSite%water_memory(1) = bc_in%h2o_liqvol_sl(ilayer_swater)
! Calculate the mean water content over the last 10 days (m3/m3)
mean_10day_liqvol = sum(currentSite%water_memory(1:numWaterMem))/real(numWaterMem,r8)
! In drought phenology, we often need to force the leaves to stay
! on or off as moisture fluctuates...
! Calculate days since leaves have come off, but make a provision
! for the first year of simulation, we have to assume a leaf drop
! date to start, so if that is in the future, set it to last year
if (model_day_int < currentSite%dleafoffdate) then
dayssincedleafoff = model_day_int - (currentSite%dleafoffdate-365)
else
dayssincedleafoff = model_day_int - currentSite%dleafoffdate
endif
! the leaves are on. How long have they been on?
if (model_day_int < currentSite%dleafondate) then
dayssincedleafon = model_day_int - (currentSite%dleafondate-365)
else
dayssincedleafon = model_day_int - currentSite%dleafondate
endif
! LEAF ON: DROUGHT DECIDUOUS WETNESS
! Here, we used a window of oppurtunity to determine if we are
! close to the time when then leaves came on last year
! Has it been ...
! a) a year, plus or minus 1 month since we last had leaf-on?
! b) Has there also been at least a nominaly short amount of "leaf-off"
! c) is the model day at least > 10 (let soil water spin-up)
! Note that cold-starts begin in the "leaf-on"
! status
if ( (currentSite%dstatus == phen_dstat_timeoff .or. &
currentSite%dstatus == phen_dstat_moistoff) .and. &
(model_day_int > numWaterMem) .and. &
(dayssincedleafon >= 365-30 .and. dayssincedleafon <= 365+30 ) .and. &
(dayssincedleafoff > ED_val_phen_doff_time) ) then
! If leaves are off, and have been off for at least a few days
! and the time is consistent with the correct
! time window... test if the moisture conditions allow for leaf-on