From b47072f0239ad884f520234708c88f0649c9ee7c Mon Sep 17 00:00:00 2001 From: Adrianna Foster Date: Wed, 20 Mar 2024 10:06:33 -0600 Subject: [PATCH 001/111] get rid of unnused unit_test_shr --- CMakeLists.txt | 3 --- unit_test_shr/CMakeLists.txt | 5 ----- unit_test_shr/unittestUtils.F90 | 33 --------------------------------- 3 files changed, 41 deletions(-) delete mode 100644 unit_test_shr/CMakeLists.txt delete mode 100644 unit_test_shr/unittestUtils.F90 diff --git a/CMakeLists.txt b/CMakeLists.txt index cbfa8c92db..00544cc654 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,9 +19,6 @@ add_subdirectory(${HLM_ROOT}/src/fates/biogeochem fates_biogeochem) add_subdirectory(${HLM_ROOT}/src/fates/fire fates_fire) add_subdirectory(${HLM_ROOT}/src/fates/radiation fates_radiation) -# Add general unit test directories (stubbed out files, etc.) -add_subdirectory(${HLM_ROOT}/src/fates/unit_test_shr) - # Remove shr_mpi_mod from share_sources. # This is needed because we want to use the mock shr_mpi_mod in place of the real one # diff --git a/unit_test_shr/CMakeLists.txt b/unit_test_shr/CMakeLists.txt deleted file mode 100644 index fd79fc097e..0000000000 --- a/unit_test_shr/CMakeLists.txt +++ /dev/null @@ -1,5 +0,0 @@ -list(APPEND fates_sources - unittestUtils.F90 - ) - -sourcelist_to_parent(fates_sources) diff --git a/unit_test_shr/unittestUtils.F90 b/unit_test_shr/unittestUtils.F90 deleted file mode 100644 index ab79cd8bd0..0000000000 --- a/unit_test_shr/unittestUtils.F90 +++ /dev/null @@ -1,33 +0,0 @@ -module unittestUtils - - ! Miscellaneous utilities to aid unit testing - - implicit none - private - - public :: endrun_msg ! Gives the message thrown by shr_abort_abort, given a call to endrun(msg) - -contains - - !----------------------------------------------------------------------- - function endrun_msg(msg) - ! - ! !DESCRIPTION: - ! Gives the message thrown by shr_abort_abort, given a call to fates_endrun(msg) - ! - ! !USES: - ! - ! !ARGUMENTS: - character(len=:), allocatable :: endrun_msg ! function result - character(len=*), intent(in) :: msg - ! - ! !LOCAL VARIABLES: - - character(len=*), parameter :: subname = 'endrun_msg' - !----------------------------------------------------------------------- - - endrun_msg = 'ENDRUN:'//trim(msg) - - end function endrun_msg - -end module unittestUtils From 902f8428cfec0e7f9010fd2915e6cac15b3f8ff8 Mon Sep 17 00:00:00 2001 From: Adrianna Foster Date: Wed, 20 Mar 2024 13:24:22 -0600 Subject: [PATCH 002/111] code cleanup; add updatelivegrass subroutine --- biogeochem/FatesPatchMod.F90 | 33 +++ fire/FatesFuelClassesMod.F90 | 64 ++++++ fire/FatesFuelMod.F90 | 45 ++++ fire/SFMainMod.F90 | 418 +++++++++++++++-------------------- 4 files changed, 323 insertions(+), 237 deletions(-) create mode 100644 fire/FatesFuelClassesMod.F90 create mode 100644 fire/FatesFuelMod.F90 diff --git a/biogeochem/FatesPatchMod.F90 b/biogeochem/FatesPatchMod.F90 index e45f18a8db..d44ebb2a14 100644 --- a/biogeochem/FatesPatchMod.F90 +++ b/biogeochem/FatesPatchMod.F90 @@ -234,6 +234,7 @@ module FatesPatchMod procedure :: InitLitter procedure :: Create procedure :: UpdateTreeGrassArea + procedure :: UpdateLiveGrass procedure :: FreeMemory procedure :: Dump procedure :: CheckVars @@ -653,6 +654,38 @@ end subroutine UpdateTreeGrassArea !=========================================================================== + subroutine UpdateLiveGrass(this) + ! + ! DESCRIPTION: + ! Calculates the sum of live grass biomass [kgC/m2] on a patch + + ! ARGUMENTS: + class(fates_patch_type), intent(inout) :: this ! patch + + ! LOCALS: + real(r8) :: live_grass ! live grass [kgC/m2] + class(fates_cohort_type) :: currentCohort ! cohort type + + live_grass = 0.0_r8 + currentCohort => this%tallest + do while(associated(currentCohort)) + ! for grasses sum all aboveground tissues + if (prt_params%woody(currentCohort%pft) == ifalse) then + live_grass = live_grass + & + (currentCohort%prt%GetState(leaf_organ, carbon12_element) + & + currentCohort%prt%GetState(sapw_organ, carbon12_element) + & + currentCohort%prt%GetState(struct_organ, carbon12_element))* & + currentCohort%n/this%area + endif + currentCohort => currentCohort%shorter + enddo + + this%livegrass = live_grass + + end subroutine UpdateLiveGrass + + !=========================================================================== + subroutine FreeMemory(this, regeneration_model, numpft) ! ! DESCRIPTION: diff --git a/fire/FatesFuelClassesMod.F90 b/fire/FatesFuelClassesMod.F90 new file mode 100644 index 0000000000..7b74a0645a --- /dev/null +++ b/fire/FatesFuelClassesMod.F90 @@ -0,0 +1,64 @@ +module FatesFuelClassesMod + + !use FatesLitterMod, only : ncwd + + implicit none + private + + integer, parameter :: ncwd = 4 + integer, parameter, public :: nfsc = ncwd + 2 + + type :: fuel_classes_type + ! There are six fuel classes: + ! 1) twigs, 2) small branches, 3) large branches, 4) trunks + ! 5) dead leaves, 6) live grass + integer, private :: twigs_i = 1 ! array index for twigs pool + integer, private :: small_branches_i = 2 ! array index for small branches pool + integer, private :: large_branches_i = 3 ! array index for large branches pool + integer, private :: trunks_i = 4 ! array index for trunks pool + integer, private :: dead_leaves_i = 5 ! array index for dead leaves pool + integer, private :: live_grass_i = 6 ! array index for live grass pool + + contains + + procedure :: twigs, small_branches, large_branches, trunks + procedure :: dead_leaves, live_grass + + end type fuel_classes_type + + ! actual type we can pass around + type(fuel_classes_type), public :: fuel_classes + + contains + + integer function twigs(this) + class(fuel_classes_type), intent(in) :: this + twigs = this%twigs_i + end function twigs + + integer function small_branches(this) + class(fuel_classes_type), intent(in) :: this + small_branches = this%small_branches_i + end function small_branches + + integer function large_branches(this) + class(fuel_classes_type), intent(in) :: this + large_branches = this%large_branches_i + end function large_branches + + integer function trunks(this) + class(fuel_classes_type), intent(in) :: this + trunks = this%trunks_i + end function trunks + + integer function dead_leaves(this) + class(fuel_classes_type), intent(in) :: this + dead_leaves = this%dead_leaves_i + end function dead_leaves + + integer function live_grass(this) + class(fuel_classes_type), intent(in) :: this + live_grass = this%live_grass_i + end function live_grass + +end module FatesFuelClassesMod \ No newline at end of file diff --git a/fire/FatesFuelMod.F90 b/fire/FatesFuelMod.F90 new file mode 100644 index 0000000000..4964de19c7 --- /dev/null +++ b/fire/FatesFuelMod.F90 @@ -0,0 +1,45 @@ +module FatesFuelMod + + use FatesFuelClassesMod, only : nfsc + + implicit none + private + + integer, parameter :: r8 = selected_real_kind(12) + + type, public :: fuel_type + real(r8) :: loading(nfsc) ! fuel loading of each fuel class [kg/m2] + real(r8) :: total_loading ! total fuel loading - DOES NOT INCLUDE TRUNKS [kg/m2] + real(r8) :: frac_loading(nfsc) ! fractional loading of each fuel class [0-1] - TRUNKS SET TO 0.0 + real(r8) :: moisture(nfsc) ! fuel moisture of each fuel class [m3/m3] + real(r8) :: average_moisture ! weighted average of fuel moisture across all fuel classes - DOES NOT INCLUDE TRUNKS [m3/m3] + real(r8) :: bulk_density ! weighted average of bulk density across all fuel classes - DOES NOT INCLUDE TRUNKS [kg/m3] + real(r8) :: SAV ! weighted average of surface area to volume ratio across all fuel classes - DOES NOT INCLUDE TRUNKS [/cm] + real(r8) :: MEF ! weighted average of moisture of extinction across all fuel classes - DOES NOT INCLUDE TRUNKS [m3/m3] + + contains + procedure :: Init + end type fuel_type + + contains + + subroutine Init(this) + ! DESCRIPTION: + ! Initialize fuel class + + ! ARGUMENTS: + class(fuel_type) :: this ! fuel class + + ! just zero everything + this%loading(:) = 0.0_r8 + this%total_loading = 0.0_r8 + this%frac_loading(:) = 0.0_r8 + this%moisture(:) = 0.0_r8 + this%average_moisture = 0.0_r8 + this%bulk_density = 0.0_r8 + this%SAV = 0.0_r8 + this%MEF = 0.0_r8 + + end subroutine Init + +end module FatesFuelMod \ No newline at end of file diff --git a/fire/SFMainMod.F90 b/fire/SFMainMod.F90 index 34e9f784c8..bcf5455c54 100644 --- a/fire/SFMainMod.F90 +++ b/fire/SFMainMod.F90 @@ -107,243 +107,187 @@ end subroutine fire_model !--------------------------------------------------------------------------------------- - subroutine UpdateFireWeather(currentSite, bc_in) - ! - ! DESCRIPTION: - ! Updates the site's fire weather index and calculates effective windspeed based on - ! vegetation characteristics - ! Currently we use tree and grass fraction averaged over whole grid (site) to - ! prevent extreme divergence - - use SFParamsMod, only : SF_val_fdi_a, SF_val_fdi_b - use FatesConstantsMod, only : tfrz => t_water_freeze_k_1atm - use FatesConstantsMod, only : sec_per_day, sec_per_min - use EDTypesMod, only : CalculateTreeGrassArea - - ! CONSTANTS: - real(r8), parameter :: wind_atten_treed = 0.4_r8 ! wind attenuation factor for tree fraction - real(r8), parameter :: wind_atten_grass = 0.6_r8 ! wind attenuation factor for grass fraction - - ! ARGUMENTS: - type(ed_site_type), intent(inout), target :: currentSite - type(bc_in_type), intent(in) :: bc_in - - ! LOCALS: - type(fates_patch_type), pointer :: currentPatch ! patch object - real(r8) :: temp_C ! daily averaged temperature [deg C] - real(r8) :: precip ! daily precip [mm/day] - real(r8) :: rh ! daily relative humidity [%] - real(r8) :: wind ! wind speed [m/s] - real(r8) :: tree_fraction ! site-level tree fraction [0-1] - real(r8) :: grass_fraction ! site-level grass fraction [0-1] - real(r8) :: bare_fraction ! site-level bare ground fraction [0-1] - integer :: iofp ! index of oldest the fates patch - - ! NOTE that the boundary conditions of temperature, precipitation and relative humidity - ! are available at the patch level. We are currently using a simplification where the whole site - ! is simply using the values associated with the first patch. - ! which probably won't have much impact, unless we decide to ever calculated fire weather for each patch. - - currentPatch => currentSite%oldest_patch - - ! If the oldest patch is a bareground patch (i.e. nocomp mode is on) use the first vegetated patch - ! for the iofp index (i.e. the next younger patch) - if (currentPatch%nocomp_pft_label == nocomp_bareground) then - currentPatch => currentPatch%younger - endif - - iofp = currentPatch%patchno - temp_C = currentPatch%tveg24%GetMean() - tfrz - precip = bc_in%precip24_pa(iofp)*sec_per_day - rh = bc_in%relhumid24_pa(iofp) - wind = bc_in%wind24_pa(iofp) - - ! convert to m/min - currentSite%wind = wind*sec_per_min - - ! update fire weather index - call currentSite%fireWeather%UpdateIndex(temp_C, precip, rh, wind) - - ! calculate site-level tree, grass, and bare fraction - call CalculateTreeGrassArea(currentSite, tree_fraction, grass_fraction, bare_fraction) - - ! update effective wind speed - call currentSite%fireWeather%UpdateEffectiveWindSpeed(wind*sec_per_min, tree_fraction, & - grass_fraction, bare_fraction) - - end subroutine UpdateFireWeather - - !--------------------------------------------------------------------------------------- - - subroutine charecteristics_of_fuel ( currentSite ) - - use SFParamsMod, only : SF_val_drying_ratio, SF_val_SAV, SF_val_FBD - - type(ed_site_type), intent(in), target :: currentSite - - type(fates_patch_type), pointer :: currentPatch - type(fates_cohort_type), pointer :: currentCohort - type(litter_type), pointer :: litt_c - - real(r8) alpha_FMC(nfsc) ! Relative fuel moisture adjusted per drying ratio - real(r8) fuel_moisture(nfsc) ! Scaled moisture content of small litter fuels. - real(r8) MEF(nfsc) ! Moisture extinction factor of fuels integer n - - fuel_moisture(:) = 0.0_r8 - - currentPatch => currentSite%oldest_patch; - do while(associated(currentPatch)) - - if(currentPatch%nocomp_pft_label .ne. nocomp_bareground)then - - litt_c => currentPatch%litter(element_pos(carbon12_element)) - - ! How much live grass is there? - currentPatch%livegrass = 0.0_r8 - currentCohort => currentPatch%tallest - do while(associated(currentCohort)) - ! for grasses sum all aboveground tissues - if( prt_params%woody(currentCohort%pft) == ifalse)then - - currentPatch%livegrass = currentPatch%livegrass + & - ( currentCohort%prt%GetState(leaf_organ, carbon12_element) + & - currentCohort%prt%GetState(sapw_organ, carbon12_element) + & - currentCohort%prt%GetState(struct_organ, carbon12_element) ) * & - currentCohort%n/currentPatch%area - - endif - currentCohort => currentCohort%shorter - enddo - - ! There are SIX fuel classes - ! 1:4) four CWD_AG pools (twig, s branch, l branch, trunk), 5) dead leaves and 6) live grass - ! NCWD =4 NFSC = 6 - ! tw_sf = 1, lb_sf = 3, tr_sf = 4, dl_sf = 5, lg_sf = 6, - - - if(write_sf == itrue)then - if ( hlm_masterproc == itrue ) write(fates_log(),*) ' leaf_litter1 ',sum(litt_c%leaf_fines(:)) - if ( hlm_masterproc == itrue ) write(fates_log(),*) ' leaf_litter2 ',sum(litt_c%ag_cwd(:)) - if ( hlm_masterproc == itrue ) write(fates_log(),*) ' leaf_litter3 ',currentPatch%livegrass - endif - - currentPatch%sum_fuel = sum(litt_c%leaf_fines(:)) + & - sum(litt_c%ag_cwd(:)) + & - currentPatch%livegrass - if(write_SF == itrue)then - if ( hlm_masterproc == itrue ) write(fates_log(),*) 'sum fuel', currentPatch%sum_fuel,currentPatch%area - endif - ! =============================================== - ! Average moisture, bulk density, surface area-volume and moisture extinction of fuel - ! ================================================ - - if (currentPatch%sum_fuel > 0.0) then - ! Fraction of fuel in litter classes - currentPatch%fuel_frac(dl_sf) = sum(litt_c%leaf_fines(:))/ currentPatch%sum_fuel - currentPatch%fuel_frac(tw_sf:tr_sf) = litt_c%ag_cwd(:) / currentPatch%sum_fuel - - if(write_sf == itrue)then - if ( hlm_masterproc == itrue ) write(fates_log(),*) 'ff2a ', & - lg_sf,currentPatch%livegrass,currentPatch%sum_fuel - endif - - currentPatch%fuel_frac(lg_sf) = currentPatch%livegrass / currentPatch%sum_fuel - - ! MEF (moisure of extinction) depends on compactness of fuel, depth, particle size, wind, slope - ! Eqn here is eqn 27 from Peterson and Ryan (1986) "Modeling Postfire Conifer Mortality for Long-Range Planning" - ! but lots of other approaches in use out there... - ! MEF: pine needles=0.30 (text near EQ 28 Rothermal 1972) - ! Table II-1 NFFL mixed fuels models from Rothermal 1983 Gen. Tech. Rep. INT-143 - ! MEF: short grass=0.12,tall grass=0.25,chaparral=0.20,closed timber litter=0.30,hardwood litter=0.25 - ! Thonicke 2010 SAV values propagated thru P&R86 eqn below gives MEF:tw=0.355, sb=0.44, lb=0.525, tr=0.63, dg=0.248, lg=0.248 - ! Lasslop 2014 Table 1 MEF PFT level:grass=0.2,shrubs=0.3,TropEverGrnTree=0.2,TropDecid Tree=0.3, Extra-trop Tree=0.3 - MEF(1:nfsc) = 0.524_r8 - 0.066_r8 * log(SF_val_SAV(1:nfsc)) - - !--- weighted average of relative moisture content--- - ! Equation 6 in Thonicke et al. 2010. across twig, small branch, large branch, and dead leaves - ! dead leaves and twigs included in 1hr pool per Thonicke (2010) - ! Calculate fuel moisture for trunks to hold value for fuel consumption - alpha_FMC(tw_sf:dl_sf) = SF_val_SAV(tw_sf:dl_sf)/SF_val_drying_ratio - - fuel_moisture(tw_sf:dl_sf) = exp(-1.0_r8 * alpha_FMC(tw_sf:dl_sf) * & - currentSite%fireWeather%fire_weather_index) - - if(write_SF == itrue)then - if ( hlm_masterproc == itrue ) write(fates_log(),*) 'ff3 ',currentPatch%fuel_frac - if ( hlm_masterproc == itrue ) write(fates_log(),*) 'fm ',fuel_moisture - if ( hlm_masterproc == itrue ) write(fates_log(),*) 'csa ',currentSite%fireWeather%fire_weather_index - if ( hlm_masterproc == itrue ) write(fates_log(),*) 'sfv ',alpha_FMC - endif - - ! live grass moisture is a function of SAV and changes via Nesterov Index - ! along the same relationship as the 1 hour fuels (live grass has same SAV as dead grass, - ! but retains more moisture with this calculation.) - fuel_moisture(lg_sf) = exp(-1.0_r8 * ((SF_val_SAV(tw_sf)/SF_val_drying_ratio) * & - currentSite%fireWeather%fire_weather_index)) - - ! Average properties over the first three litter pools (twigs, s branches, l branches) - currentPatch%fuel_bulkd = sum(currentPatch%fuel_frac(tw_sf:lb_sf) * SF_val_FBD(tw_sf:lb_sf)) - currentPatch%fuel_sav = sum(currentPatch%fuel_frac(tw_sf:lb_sf) * SF_val_SAV(tw_sf:lb_sf)) - currentPatch%fuel_mef = sum(currentPatch%fuel_frac(tw_sf:lb_sf) * MEF(tw_sf:lb_sf)) - currentPatch%fuel_eff_moist = sum(currentPatch%fuel_frac(tw_sf:lb_sf) * fuel_moisture(tw_sf:lb_sf)) - if(write_sf == itrue)then - if ( hlm_masterproc == itrue ) write(fates_log(),*) 'ff4 ',currentPatch%fuel_eff_moist - endif - ! Add on properties of dead leaves and live grass pools (5 & 6) - currentPatch%fuel_bulkd = currentPatch%fuel_bulkd + sum(currentPatch%fuel_frac(dl_sf:lg_sf) * SF_val_FBD(dl_sf:lg_sf)) - currentPatch%fuel_sav = currentPatch%fuel_sav + sum(currentPatch%fuel_frac(dl_sf:lg_sf) * SF_val_SAV(dl_sf:lg_sf)) - currentPatch%fuel_mef = currentPatch%fuel_mef + sum(currentPatch%fuel_frac(dl_sf:lg_sf) * MEF(dl_sf:lg_sf)) - currentPatch%fuel_eff_moist = currentPatch%fuel_eff_moist+ sum(currentPatch%fuel_frac(dl_sf:lg_sf) * fuel_moisture(dl_sf:lg_sf)) - - ! Correct averaging for the fact that we are not using the trunks pool for fire ROS and intensity (5) - ! Consumption of fuel in trunk pool does not influence fire ROS or intensity (Pyne 1996) - currentPatch%fuel_bulkd = currentPatch%fuel_bulkd * (1.0_r8/(1.0_r8-currentPatch%fuel_frac(tr_sf))) - currentPatch%fuel_sav = currentPatch%fuel_sav * (1.0_r8/(1.0_r8-currentPatch%fuel_frac(tr_sf))) - currentPatch%fuel_mef = currentPatch%fuel_mef * (1.0_r8/(1.0_r8-currentPatch%fuel_frac(tr_sf))) - currentPatch%fuel_eff_moist = currentPatch%fuel_eff_moist * (1.0_r8/(1.0_r8-currentPatch%fuel_frac(tr_sf))) - - ! Pass litter moisture into the fuel burning routine (all fuels: twigs,s branch,l branch,trunk,dead leaves,live grass) - ! (wo/me term in Thonicke et al. 2010) - currentPatch%litter_moisture(tw_sf:lb_sf) = fuel_moisture(tw_sf:lb_sf)/MEF(tw_sf:lb_sf) - currentPatch%litter_moisture(tr_sf) = fuel_moisture(tr_sf)/MEF(tr_sf) - currentPatch%litter_moisture(dl_sf) = fuel_moisture(dl_sf)/MEF(dl_sf) - currentPatch%litter_moisture(lg_sf) = fuel_moisture(lg_sf)/MEF(lg_sf) - - else - - if(write_SF == itrue)then - - if ( hlm_masterproc == itrue ) write(fates_log(),*) 'no litter fuel at all',currentPatch%patchno, & - currentPatch%sum_fuel,sum(litt_c%ag_cwd(:)),sum(litt_c%leaf_fines(:)) - - endif - currentPatch%fuel_sav = sum(SF_val_SAV(1:nfsc))/(nfsc) ! make average sav to avoid crashing code. - - if ( hlm_masterproc == itrue .and. write_SF == itrue)then - write(fates_log(),*) 'problem with spitfire fuel averaging' - end if - - ! FIX(SPM,032414) refactor...should not have 0 fuel unless everything is burnt - ! off. - currentPatch%fuel_eff_moist = 0.0000000001_r8 - currentPatch%fuel_bulkd = 0.0000000001_r8 - currentPatch%fuel_frac(:) = 0.0000000001_r8 - currentPatch%fuel_mef = 0.0000000001_r8 - currentPatch%sum_fuel = 0.0000000001_r8 - - endif - ! check values. - ! FIX(SPM,032414) refactor... - if(write_SF == itrue.and.currentPatch%fuel_sav <= 0.0_r8.or.currentPatch%fuel_bulkd <= & - 0.0_r8.or.currentPatch%fuel_mef <= 0.0_r8.or.currentPatch%fuel_eff_moist <= 0.0_r8)then - if ( hlm_masterproc == itrue ) write(fates_log(),*) 'problem with spitfire fuel averaging' - endif - endif !nocomp_pft_label check - currentPatch => currentPatch%younger - - enddo !end patch loop - - end subroutine charecteristics_of_fuel + subroutine UpdateFireWeather(currentSite, bc_in) + ! + ! DESCRIPTION: + ! Updates the site's fire weather index and calculates effective windspeed based on + ! vegetation characteristics + ! Currently we use tree and grass fraction averaged over whole grid (site) to + ! prevent extreme divergence + + use SFParamsMod, only : SF_val_fdi_a, SF_val_fdi_b + use FatesConstantsMod, only : tfrz => t_water_freeze_k_1atm + use FatesConstantsMod, only : sec_per_day, sec_per_min + use EDTypesMod, only : CalculateTreeGrassArea + + ! CONSTANTS: + real(r8), parameter :: wind_atten_treed = 0.4_r8 ! wind attenuation factor for tree fraction + real(r8), parameter :: wind_atten_grass = 0.6_r8 ! wind attenuation factor for grass fraction + + ! ARGUMENTS: + type(ed_site_type), intent(inout), target :: currentSite + type(bc_in_type), intent(in) :: bc_in + + ! LOCALS: + type(fates_patch_type), pointer :: currentPatch ! patch object + real(r8) :: temp_C ! daily averaged temperature [deg C] + real(r8) :: precip ! daily precip [mm/day] + real(r8) :: rh ! daily relative humidity [%] + real(r8) :: wind ! wind speed [m/s] + real(r8) :: tree_fraction ! site-level tree fraction [0-1] + real(r8) :: grass_fraction ! site-level grass fraction [0-1] + real(r8) :: bare_fraction ! site-level bare ground fraction [0-1] + integer :: iofp ! index of oldest the fates patch + + ! NOTE that the boundary conditions of temperature, precipitation and relative humidity + ! are available at the patch level. We are currently using a simplification where the whole site + ! is simply using the values associated with the first patch. + ! which probably won't have much impact, unless we decide to ever calculated fire weather for each patch. + + currentPatch => currentSite%oldest_patch + + ! If the oldest patch is a bareground patch (i.e. nocomp mode is on) use the first vegetated patch + ! for the iofp index (i.e. the next younger patch) + if (currentPatch%nocomp_pft_label == nocomp_bareground) then + currentPatch => currentPatch%younger + endif + + iofp = currentPatch%patchno + temp_C = currentPatch%tveg24%GetMean() - tfrz + precip = bc_in%precip24_pa(iofp)*sec_per_day + rh = bc_in%relhumid24_pa(iofp) + wind = bc_in%wind24_pa(iofp) + + ! convert to m/min + currentSite%wind = wind*sec_per_min + + ! update fire weather index + call currentSite%fireWeather%UpdateIndex(temp_C, precip, rh, wind) + + ! calculate site-level tree, grass, and bare fraction + call CalculateTreeGrassArea(currentSite, tree_fraction, grass_fraction, bare_fraction) + + ! update effective wind speed + call currentSite%fireWeather%UpdateEffectiveWindSpeed(wind*sec_per_min, tree_fraction, & + grass_fraction, bare_fraction) + + end subroutine UpdateFireWeather + + !--------------------------------------------------------------------------------------- + + subroutine charecteristics_of_fuel(currentSite) + ! + ! DESCRIPTION: + ! Updates the site's fuel characteristics + + use SFParamsMod, only : SF_val_drying_ratio, SF_val_SAV, SF_val_FBD + + ! AGUMENTS + type(ed_site_type), intent(in), target :: currentSite + + ! LOCALS: + type(fates_patch_type), pointer :: currentPatch + type(litter_type), pointer :: litt_c + real(r8) :: alpha_FMC(nfsc) ! Relative fuel moisture adjusted per drying ratio + real(r8) :: fuel_moisture(nfsc) ! Scaled moisture content of small litter fuels. + real(r8) :: MEF(nfsc) ! Moisture extinction factor of fuels integer n + + fuel_moisture(:) = 0.0_r8 + + currentPatch => currentSite%oldest_patch; + do while(associated(currentPatch)) + + if (currentPatch%nocomp_pft_label /= nocomp_bareground) then + + litt_c => currentPatch%litter(element_pos(carbon12_element)) + + ! how much live grass is there? + call currentPatch%UpdateLiveGrass() + + currentPatch%sum_fuel = sum(litt_c%leaf_fines(:)) + + sum(litt_c%ag_cwd(:)) + currentPatch%livegrass + + ! =============================================== + ! Average moisture, bulk density, surface area-volume and moisture extinction of fuel + ! ================================================ + + if (currentPatch%sum_fuel > 0.0) then + + ! Fraction of fuel in litter classes + currentPatch%fuel_frac(dl_sf) = sum(litt_c%leaf_fines(:))/ currentPatch%sum_fuel + currentPatch%fuel_frac(tw_sf:tr_sf) = litt_c%ag_cwd(:) / currentPatch%sum_fuel + currentPatch%fuel_frac(lg_sf) = currentPatch%livegrass / currentPatch%sum_fuel + + ! MEF (moisure of extinction) depends on compactness of fuel, depth, particle size, wind, slope + ! Eqn here is eqn 27 from Peterson and Ryan (1986) "Modeling Postfire Conifer Mortality for Long-Range Planning" + ! but lots of other approaches in use out there... + ! MEF: pine needles=0.30 (text near EQ 28 Rothermal 1972) + ! Table II-1 NFFL mixed fuels models from Rothermal 1983 Gen. Tech. Rep. INT-143 + ! MEF: short grass=0.12,tall grass=0.25,chaparral=0.20,closed timber litter=0.30,hardwood litter=0.25 + ! Thonicke 2010 SAV values propagated thru P&R86 eqn below gives MEF:tw=0.355, sb=0.44, lb=0.525, tr=0.63, dg=0.248, lg=0.248 + ! Lasslop 2014 Table 1 MEF PFT level:grass=0.2,shrubs=0.3,TropEverGrnTree=0.2,TropDecid Tree=0.3, Extra-trop Tree=0.3 + MEF(1:nfsc) = 0.524_r8 - 0.066_r8 * log(SF_val_SAV(1:nfsc)) + + !--- weighted average of relative moisture content--- + ! Equation 6 in Thonicke et al. 2010. across twig, small branch, large branch, and dead leaves + ! dead leaves and twigs included in 1hr pool per Thonicke (2010) + ! Calculate fuel moisture for trunks to hold value for fuel consumption + alpha_FMC(tw_sf:dl_sf) = SF_val_SAV(tw_sf:dl_sf)/SF_val_drying_ratio + + fuel_moisture(tw_sf:dl_sf) = exp(-1.0_r8 * alpha_FMC(tw_sf:dl_sf) * & + currentSite%fireWeather%fire_weather_index) + + ! live grass moisture is a function of SAV and changes via Nesterov Index + ! along the same relationship as the 1 hour fuels (live grass has same SAV as dead grass, + ! but retains more moisture with this calculation.) + fuel_moisture(lg_sf) = exp(-1.0_r8 * ((SF_val_SAV(tw_sf)/SF_val_drying_ratio) * & + currentSite%fireWeather%fire_weather_index)) + + ! Average properties over the first three litter pools (twigs, s branches, l branches) + currentPatch%fuel_bulkd = sum(currentPatch%fuel_frac(tw_sf:lb_sf) * SF_val_FBD(tw_sf:lb_sf)) + currentPatch%fuel_sav = sum(currentPatch%fuel_frac(tw_sf:lb_sf) * SF_val_SAV(tw_sf:lb_sf)) + currentPatch%fuel_mef = sum(currentPatch%fuel_frac(tw_sf:lb_sf) * MEF(tw_sf:lb_sf)) + currentPatch%fuel_eff_moist = sum(currentPatch%fuel_frac(tw_sf:lb_sf) * fuel_moisture(tw_sf:lb_sf)) + + ! Add on properties of dead leaves and live grass pools (5 & 6) + currentPatch%fuel_bulkd = currentPatch%fuel_bulkd + sum(currentPatch%fuel_frac(dl_sf:lg_sf) * SF_val_FBD(dl_sf:lg_sf)) + currentPatch%fuel_sav = currentPatch%fuel_sav + sum(currentPatch%fuel_frac(dl_sf:lg_sf) * SF_val_SAV(dl_sf:lg_sf)) + currentPatch%fuel_mef = currentPatch%fuel_mef + sum(currentPatch%fuel_frac(dl_sf:lg_sf) * MEF(dl_sf:lg_sf)) + currentPatch%fuel_eff_moist = currentPatch%fuel_eff_moist+ sum(currentPatch%fuel_frac(dl_sf:lg_sf) * fuel_moisture(dl_sf:lg_sf)) + + ! Correct averaging for the fact that we are not using the trunks pool for fire ROS and intensity (5) + ! Consumption of fuel in trunk pool does not influence fire ROS or intensity (Pyne 1996) + currentPatch%fuel_bulkd = currentPatch%fuel_bulkd * (1.0_r8/(1.0_r8-currentPatch%fuel_frac(tr_sf))) + currentPatch%fuel_sav = currentPatch%fuel_sav * (1.0_r8/(1.0_r8-currentPatch%fuel_frac(tr_sf))) + currentPatch%fuel_mef = currentPatch%fuel_mef * (1.0_r8/(1.0_r8-currentPatch%fuel_frac(tr_sf))) + currentPatch%fuel_eff_moist = currentPatch%fuel_eff_moist * (1.0_r8/(1.0_r8-currentPatch%fuel_frac(tr_sf))) + + ! Pass litter moisture into the fuel burning routine (all fuels: twigs,s branch,l branch,trunk,dead leaves,live grass) + ! (wo/me term in Thonicke et al. 2010) + currentPatch%litter_moisture(tw_sf:lb_sf) = fuel_moisture(tw_sf:lb_sf)/MEF(tw_sf:lb_sf) + currentPatch%litter_moisture(tr_sf) = fuel_moisture(tr_sf)/MEF(tr_sf) + currentPatch%litter_moisture(dl_sf) = fuel_moisture(dl_sf)/MEF(dl_sf) + currentPatch%litter_moisture(lg_sf) = fuel_moisture(lg_sf)/MEF(lg_sf) + + else + + currentPatch%fuel_sav = sum(SF_val_SAV(1:nfsc))/(nfsc) ! make average sav to avoid crashing code. + + ! FIX(SPM,032414) refactor...should not have 0 fuel unless everything is burnt + ! off. + currentPatch%fuel_eff_moist = 0.0000000001_r8 + currentPatch%fuel_bulkd = 0.0000000001_r8 + currentPatch%fuel_frac(:) = 0.0000000001_r8 + currentPatch%fuel_mef = 0.0000000001_r8 + currentPatch%sum_fuel = 0.0000000001_r8 + + end if + end if + currentPatch => currentPatch%younger + + end do + + end subroutine charecteristics_of_fuel From f7258c6b49c8b0a4e818438685fc14d034d57941 Mon Sep 17 00:00:00 2001 From: Adrianna Foster Date: Wed, 20 Mar 2024 13:34:27 -0600 Subject: [PATCH 003/111] code cleanup --- fire/SFMainMod.F90 | 72 +++++++++++++++++++++++----------------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/fire/SFMainMod.F90 b/fire/SFMainMod.F90 index bcf5455c54..84b68190ae 100644 --- a/fire/SFMainMod.F90 +++ b/fire/SFMainMod.F90 @@ -71,39 +71,39 @@ module SFMainMod contains - subroutine fire_model(currentSite, bc_in) - ! - ! DESCRIPTION: - ! Runs the daily fire weather model - - ! ARGUMENTS: - type(ed_site_type), intent(inout), target :: currentSite ! site object - type(bc_in_type), intent(in) :: bc_in ! BC in object - - ! LOCALS: - type (fates_patch_type), pointer :: currentPatch ! patch object + subroutine fire_model(currentSite, bc_in) + ! + ! DESCRIPTION: + ! Runs the daily fire weather model - ! zero fire things - currentPatch => currentSite%youngest_patch - do while(associated(currentPatch)) - currentPatch%frac_burnt = 0.0_r8 - currentPatch%fire = 0 - currentPatch => currentPatch%older - end do - - if (hlm_spitfire_mode > hlm_sf_nofire_def) then - call UpdateFireWeather(currentSite, bc_in) - call charecteristics_of_fuel(currentSite) - call rate_of_spread(currentSite) - call ground_fuel_consumption(currentSite) - call area_burnt_intensity(currentSite, bc_in) - call crown_scorching(currentSite) - call crown_damage(currentSite) - call cambial_damage_kill(currentSite) - call post_fire_mortality(currentSite) - end if + ! ARGUMENTS: + type(ed_site_type), intent(inout), target :: currentSite ! site object + type(bc_in_type), intent(in) :: bc_in ! BC in object - end subroutine fire_model + ! LOCALS: + type (fates_patch_type), pointer :: currentPatch ! patch object + + ! zero fire things + currentPatch => currentSite%youngest_patch + do while(associated(currentPatch)) + currentPatch%frac_burnt = 0.0_r8 + currentPatch%fire = 0 + currentPatch => currentPatch%older + end do + + if (hlm_spitfire_mode > hlm_sf_nofire_def) then + call UpdateFireWeather(currentSite, bc_in) + call charecteristics_of_fuel(currentSite) + call rate_of_spread(currentSite) + call ground_fuel_consumption(currentSite) + call area_burnt_intensity(currentSite, bc_in) + call crown_scorching(currentSite) + call crown_damage(currentSite) + call cambial_damage_kill(currentSite) + call post_fire_mortality(currentSite) + end if + + end subroutine fire_model !--------------------------------------------------------------------------------------- @@ -173,7 +173,7 @@ subroutine UpdateFireWeather(currentSite, bc_in) end subroutine UpdateFireWeather - !--------------------------------------------------------------------------------------- + !-------------------------------------------------------------------------------------- subroutine charecteristics_of_fuel(currentSite) ! @@ -190,18 +190,18 @@ subroutine charecteristics_of_fuel(currentSite) type(litter_type), pointer :: litt_c real(r8) :: alpha_FMC(nfsc) ! Relative fuel moisture adjusted per drying ratio real(r8) :: fuel_moisture(nfsc) ! Scaled moisture content of small litter fuels. - real(r8) :: MEF(nfsc) ! Moisture extinction factor of fuels integer n + real(r8) :: MEF(nfsc) ! Moisture extinction factor of fuels fuel_moisture(:) = 0.0_r8 - currentPatch => currentSite%oldest_patch; + currentPatch => currentSite%oldest_patch do while(associated(currentPatch)) if (currentPatch%nocomp_pft_label /= nocomp_bareground) then litt_c => currentPatch%litter(element_pos(carbon12_element)) - ! how much live grass is there? + ! how much live grass is there? [kgC/m2] call currentPatch%UpdateLiveGrass() currentPatch%sum_fuel = sum(litt_c%leaf_fines(:)) + @@ -289,7 +289,7 @@ subroutine charecteristics_of_fuel(currentSite) end subroutine charecteristics_of_fuel - + !-------------------------------------------------------------------------------------- subroutine rate_of_spread ( currentSite ) !*****************************************************************. From 81835a71d08ded5149d0c897a8a677023dfa1ca1 Mon Sep 17 00:00:00 2001 From: Adrianna Foster Date: Mon, 25 Mar 2024 12:55:04 -0600 Subject: [PATCH 004/111] call grass --- biogeochem/FatesPatchMod.F90 | 8 +++++--- fire/SFMainMod.F90 | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/biogeochem/FatesPatchMod.F90 b/biogeochem/FatesPatchMod.F90 index d44ebb2a14..afcd61186a 100644 --- a/biogeochem/FatesPatchMod.F90 +++ b/biogeochem/FatesPatchMod.F90 @@ -6,7 +6,7 @@ module FatesPatchMod use FatesConstantsMod, only : primaryland, secondaryland use FatesConstantsMod, only : n_landuse_cats use FatesConstantsMod, only : TRS_regeneration - use FatesConstantsMod, only : itrue + use FatesConstantsMod, only : itrue, ifalse use FatesGlobals, only : fates_log use FatesGlobals, only : endrun => fates_endrun use FatesUtilsMod, only : check_hlm_list @@ -17,6 +17,8 @@ module FatesPatchMod use FatesLitterMod, only : litter_type use PRTGenericMod, only : num_elements use PRTGenericMod, only : element_list + use PRTGenericMod, only : carbon12_element + use PRTGenericMod, only : struct_organ, leaf_organ, sapw_organ use PRTParametersMod, only : prt_params use FatesConstantsMod, only : nocomp_bareground use EDParamsMod, only : nlevleaf, nclmax, maxpft @@ -663,8 +665,8 @@ subroutine UpdateLiveGrass(this) class(fates_patch_type), intent(inout) :: this ! patch ! LOCALS: - real(r8) :: live_grass ! live grass [kgC/m2] - class(fates_cohort_type) :: currentCohort ! cohort type + real(r8) :: live_grass ! live grass [kgC/m2] + class(fates_cohort_type), pointer :: currentCohort ! cohort type live_grass = 0.0_r8 currentCohort => this%tallest diff --git a/fire/SFMainMod.F90 b/fire/SFMainMod.F90 index 84b68190ae..aa30d432d2 100644 --- a/fire/SFMainMod.F90 +++ b/fire/SFMainMod.F90 @@ -204,7 +204,7 @@ subroutine charecteristics_of_fuel(currentSite) ! how much live grass is there? [kgC/m2] call currentPatch%UpdateLiveGrass() - currentPatch%sum_fuel = sum(litt_c%leaf_fines(:)) + + currentPatch%sum_fuel = sum(litt_c%leaf_fines(:)) + & sum(litt_c%ag_cwd(:)) + currentPatch%livegrass ! =============================================== From f1039872ddda876bd4789727a789f8e77ef5c83f Mon Sep 17 00:00:00 2001 From: Adrianna Foster Date: Tue, 26 Mar 2024 09:23:40 -0600 Subject: [PATCH 005/111] add sum loading --- biogeochem/FatesPatchMod.F90 | 14 ++++++- fire/FatesFuelClassesMod.F90 | 3 +- fire/FatesFuelMod.F90 | 78 +++++++++++++++++++++++++++++++----- fire/SFMainMod.F90 | 24 ++++++----- 4 files changed, 97 insertions(+), 22 deletions(-) diff --git a/biogeochem/FatesPatchMod.F90 b/biogeochem/FatesPatchMod.F90 index afcd61186a..6c13ab2689 100644 --- a/biogeochem/FatesPatchMod.F90 +++ b/biogeochem/FatesPatchMod.F90 @@ -15,6 +15,7 @@ module FatesPatchMod use FatesRunningMeanMod, only : rmean_type, rmean_arr_type use FatesLitterMod, only : nfsc use FatesLitterMod, only : litter_type + use FatesFuelMod, only : fuel_type use PRTGenericMod, only : num_elements use PRTGenericMod, only : element_list use PRTGenericMod, only : carbon12_element @@ -189,6 +190,7 @@ module FatesPatchMod ! LITTER AND COARSE WOODY DEBRIS type(litter_type), pointer :: litter(:) ! litter (leaf,fnrt,CWD and seeds) for different elements + type(fuel_type), pointer :: fuel ! fuel class real(r8), allocatable :: fragmentation_scaler(:) ! scale rate of litter fragmentation based on soil layer [0-1] !--------------------------------------------------------------------------- @@ -594,6 +596,10 @@ subroutine Create(this, age, area, label, nocomp_pft, num_swb, num_pft, & ! initialize litter call this%InitLitter(num_pft, num_levsoil) + ! initialize fuel + allocate(this%fuel) + this%fuel%Init() + this%twostr%scelg => null() ! The radiation module will check if this ! is associated, since it is not, it will then ! initialize and allocate @@ -735,7 +741,13 @@ subroutine FreeMemory(this, regeneration_model, numpft) write(fates_log(),*) 'dealloc008: fail on deallocate(this%litter):'//trim(smsg) call endrun(msg=errMsg(sourcefile, __LINE__)) endif - + + deallocate(this%fuel, stat=istat, errmsg=smsg) + if (istat/=0) then + write(fates_log(),*) 'dealloc009: fail on deallocate patch fuel:'//trim(smsg) + call endrun(msg=errMsg(sourcefile, __LINE__)) + endif + ! deallocate the allocatable arrays deallocate(this%tr_soil_dir, & this%tr_soil_dif, & diff --git a/fire/FatesFuelClassesMod.F90 b/fire/FatesFuelClassesMod.F90 index 7b74a0645a..81d19326a4 100644 --- a/fire/FatesFuelClassesMod.F90 +++ b/fire/FatesFuelClassesMod.F90 @@ -1,11 +1,10 @@ module FatesFuelClassesMod - !use FatesLitterMod, only : ncwd + use FatesLitterMod, only : ncwd implicit none private - integer, parameter :: ncwd = 4 integer, parameter, public :: nfsc = ncwd + 2 type :: fuel_classes_type diff --git a/fire/FatesFuelMod.F90 b/fire/FatesFuelMod.F90 index 4964de19c7..0b717aa12a 100644 --- a/fire/FatesFuelMod.F90 +++ b/fire/FatesFuelMod.F90 @@ -1,6 +1,8 @@ module FatesFuelMod use FatesFuelClassesMod, only : nfsc + use FatesLitterMod, only : litter_type + use FatesConstantsMod, only : nearzero implicit none private @@ -8,17 +10,22 @@ module FatesFuelMod integer, parameter :: r8 = selected_real_kind(12) type, public :: fuel_type - real(r8) :: loading(nfsc) ! fuel loading of each fuel class [kg/m2] - real(r8) :: total_loading ! total fuel loading - DOES NOT INCLUDE TRUNKS [kg/m2] - real(r8) :: frac_loading(nfsc) ! fractional loading of each fuel class [0-1] - TRUNKS SET TO 0.0 + real(r8) :: loading(nfsc) ! fuel loading of each fuel class [kgC/m2] real(r8) :: moisture(nfsc) ! fuel moisture of each fuel class [m3/m3] - real(r8) :: average_moisture ! weighted average of fuel moisture across all fuel classes - DOES NOT INCLUDE TRUNKS [m3/m3] - real(r8) :: bulk_density ! weighted average of bulk density across all fuel classes - DOES NOT INCLUDE TRUNKS [kg/m3] - real(r8) :: SAV ! weighted average of surface area to volume ratio across all fuel classes - DOES NOT INCLUDE TRUNKS [/cm] - real(r8) :: MEF ! weighted average of moisture of extinction across all fuel classes - DOES NOT INCLUDE TRUNKS [m3/m3] + real(r8) :: frac_loading(nfsc) ! fractional loading of non-trunk fuel classes [0-1] + real(r8) :: total_loading ! total fuel loading - DOES NOT INCLUDE TRUNKS [kgC/m2] + real(r8) :: average_moisture ! weighted average of fuel moisture across non-trunk fuel classes [m3/m3] + real(r8) :: bulk_density ! weighted average of bulk density across non-trunk fuel classes [kg/m3] + real(r8) :: SAV ! weighted average of surface area to volume ratio across non-trunk fuel classes [/cm] + real(r8) :: MEF ! weighted average of moisture of extinction across non-trunk fuel classes [m3/m3] contains - procedure :: Init + + procedure :: Init + procedure :: CalculateLoading + procedure :: SumLoading + procedure :: CalculateFractionalLoading + end type fuel_type contains @@ -28,7 +35,7 @@ subroutine Init(this) ! Initialize fuel class ! ARGUMENTS: - class(fuel_type) :: this ! fuel class + class(fuel_type), intent(inout) :: this ! fuel class ! just zero everything this%loading(:) = 0.0_r8 @@ -42,4 +49,57 @@ subroutine Init(this) end subroutine Init + !------------------------------------------------------------------------------------- + + subroutine CalculateLoading(this, litter, live_grass) + ! DESCRIPTION: + ! Calculates loading for each fuel type + + ! ARGUMENTS: + class(fuel_type), intent(inout) :: this ! fuel class + type(litter_type), target, intent(in) :: litter ! litter class + real(r8), intent(in) :: live_grass ! amount of live grass [kgC/m2] + + + this%loading(dl_sf) = sum(litter%leaf_fines(:)) + this%loading(tw_sf:tr_sf) = litter%ag_cwd(:) + this%loading(lg_sf) = live_grass + + end subroutine CalculateLoading + + !------------------------------------------------------------------------------------- + + subroutine SumLoading(this) + ! DESCRIPTION: + ! Sums up the loading + + ! ARGUMENTS: + class(fuel_type), intent(inout) :: this ! fuel class + + this%total_loading = sum(this%loading(:)) + + end subroutine SumLoading + + !------------------------------------------------------------------------------------- + + subroutine CalculateFractionalLoading(this) + ! DESCRIPTION: + ! Calculates fractional loading + + ! ARGUMENTS: + class(fuel_type), intent(inout) :: this ! fuel class + + ! sum up loading just in case + call this%SumLoading() + + if (this%total_loading > nearzero) then + this%frac_loading(:) = this%loading(:)/this%total_loading + else + this%frac_loading(:) = 0.0_r8 + end if + + end subroutine CalculateFractionalLoading + + !------------------------------------------------------------------------------------- + end module FatesFuelMod \ No newline at end of file diff --git a/fire/SFMainMod.F90 b/fire/SFMainMod.F90 index aa30d432d2..3456b4a4b2 100644 --- a/fire/SFMainMod.F90 +++ b/fire/SFMainMod.F90 @@ -182,7 +182,7 @@ subroutine charecteristics_of_fuel(currentSite) use SFParamsMod, only : SF_val_drying_ratio, SF_val_SAV, SF_val_FBD - ! AGUMENTS + ! AGUMENTS: type(ed_site_type), intent(in), target :: currentSite ! LOCALS: @@ -199,19 +199,23 @@ subroutine charecteristics_of_fuel(currentSite) if (currentPatch%nocomp_pft_label /= nocomp_bareground) then + ! calculate live grass [kgC/m2] + call currentPatch%UpdateLiveGrass() + + ! update fuel loading litt_c => currentPatch%litter(element_pos(carbon12_element)) + call currentPatch%fuel%CalculateLoading(litt_c, currentPatch%livegrass) + call currentPatch%fuel%SumLoading() + + currentPatch%sum_fuel = currentPatch%fuel%total_loading - ! how much live grass is there? [kgC/m2] - call currentPatch%UpdateLiveGrass() + !call currentPatch%fuel%CalculateFractionalLoading() - currentPatch%sum_fuel = sum(litt_c%leaf_fines(:)) + & - sum(litt_c%ag_cwd(:)) + currentPatch%livegrass - - ! =============================================== - ! Average moisture, bulk density, surface area-volume and moisture extinction of fuel - ! ================================================ + if (currentPatch%sum_fuel > 0.0) then - if (currentPatch%sum_fuel > 0.0) then + ! currentPatch%fuel_frac(dl_sf) = currentPatch%fuel%frac_loading(dl_sf) + ! currentPatch%fuel_frac(tw_sf:tr_sf) = currentPatch%fuel%frac_loading(tw_sf:tr_sf) + ! currentPatch%fuel_frac(lg_sf) = currentPatch%fuel%frac_loading(lg_sf) ! Fraction of fuel in litter classes currentPatch%fuel_frac(dl_sf) = sum(litt_c%leaf_fines(:))/ currentPatch%sum_fuel From 187b2a453e64d1030661852d3c0be8850295ee93 Mon Sep 17 00:00:00 2001 From: Adrianna Foster Date: Tue, 26 Mar 2024 12:06:50 -0600 Subject: [PATCH 006/111] get rid of sum_fuel --- biogeochem/EDPatchDynamicsMod.F90 | 2 +- biogeochem/FatesLitterMod.F90 | 1 + biogeochem/FatesPatchMod.F90 | 5 +---- fire/FatesFuelMod.F90 | 26 ++++++++++++++-------- fire/SFMainMod.F90 | 36 +++++++++++++++---------------- main/EDInitMod.F90 | 1 - main/FatesHistoryInterfaceMod.F90 | 8 +++---- 7 files changed, 42 insertions(+), 37 deletions(-) diff --git a/biogeochem/EDPatchDynamicsMod.F90 b/biogeochem/EDPatchDynamicsMod.F90 index 998bacc1d1..43894a2c75 100644 --- a/biogeochem/EDPatchDynamicsMod.F90 +++ b/biogeochem/EDPatchDynamicsMod.F90 @@ -2754,7 +2754,7 @@ subroutine fuse_2_patches(csite, dp, rp) rp%fuel_eff_moist = (dp%fuel_eff_moist*dp%area + rp%fuel_eff_moist*rp%area) * inv_sum_area rp%livegrass = (dp%livegrass*dp%area + rp%livegrass*rp%area) * inv_sum_area - rp%sum_fuel = (dp%sum_fuel*dp%area + rp%sum_fuel*rp%area) * inv_sum_area + rp%fuel%total_loading = (dp%fuel%total_loading*dp%area + rp%fuel%total_loading*rp%area) * inv_sum_area rp%fuel_bulkd = (dp%fuel_bulkd*dp%area + rp%fuel_bulkd*rp%area) * inv_sum_area rp%fuel_sav = (dp%fuel_sav*dp%area + rp%fuel_sav*rp%area) * inv_sum_area rp%fuel_mef = (dp%fuel_mef*dp%area + rp%fuel_mef*rp%area) * inv_sum_area diff --git a/biogeochem/FatesLitterMod.F90 b/biogeochem/FatesLitterMod.F90 index 7d55dc9aab..8ad782eb22 100644 --- a/biogeochem/FatesLitterMod.F90 +++ b/biogeochem/FatesLitterMod.F90 @@ -59,6 +59,7 @@ module FatesLitterMod integer, parameter, public :: NFSC = NCWD+2 ! number fuel size classes (4 cwd size classes, leaf litter, and grass) integer, parameter, public :: tw_sf = 1 ! array index of twig pool for spitfire + integer, parameter, public :: sb_sf = 2 ! array index of large branch pool for spitfire integer, parameter, public :: lb_sf = 3 ! array index of large branch pool for spitfire integer, parameter, public :: tr_sf = 4 ! array index of dead trunk pool for spitfire integer, parameter, public :: dl_sf = 5 ! array index of dead leaf pool for spitfire (dead grass and dead leaves) diff --git a/biogeochem/FatesPatchMod.F90 b/biogeochem/FatesPatchMod.F90 index 6c13ab2689..4013643de0 100644 --- a/biogeochem/FatesPatchMod.F90 +++ b/biogeochem/FatesPatchMod.F90 @@ -197,7 +197,6 @@ module FatesPatchMod ! FUELS AND FIRE ! fuel characteristics - real(r8) :: sum_fuel ! total ground fuel related to ROS (omits 1000 hr fuels) [kgC/m2] real(r8) :: fuel_frac(nfsc) ! fraction of each litter class in the ros_fuel [0-1] real(r8) :: livegrass ! total aboveground grass biomass in patch [kgC/m2] real(r8) :: fuel_bulkd ! average fuel bulk density of the ground fuel. [kg/m3] @@ -381,7 +380,6 @@ subroutine NanValues(this) this%fragmentation_scaler(:) = nan ! FUELS AND FIRE - this%sum_fuel = nan this%fuel_frac(:) = nan this%livegrass = nan this%fuel_bulkd = nan @@ -458,7 +456,6 @@ subroutine ZeroValues(this) this%fragmentation_scaler(:) = 0.0_r8 ! FIRE - this%sum_fuel = 0.0_r8 this%fuel_frac(:) = 0.0_r8 this%livegrass = 0.0_r8 this%fuel_bulkd = 0.0_r8 @@ -598,7 +595,7 @@ subroutine Create(this, age, area, label, nocomp_pft, num_swb, num_pft, & ! initialize fuel allocate(this%fuel) - this%fuel%Init() + call this%fuel%Init() this%twostr%scelg => null() ! The radiation module will check if this ! is associated, since it is not, it will then diff --git a/fire/FatesFuelMod.F90 b/fire/FatesFuelMod.F90 index 0b717aa12a..9369d860f4 100644 --- a/fire/FatesFuelMod.F90 +++ b/fire/FatesFuelMod.F90 @@ -3,7 +3,8 @@ module FatesFuelMod use FatesFuelClassesMod, only : nfsc use FatesLitterMod, only : litter_type use FatesConstantsMod, only : nearzero - + use FatesLitterMod, only : dl_sf, tw_sf, sb_sf, lb_sf, tr_sf, lg_sf + implicit none private @@ -51,18 +52,25 @@ end subroutine Init !------------------------------------------------------------------------------------- - subroutine CalculateLoading(this, litter, live_grass) + subroutine CalculateLoading(this, leaf_litter, twig_litter, small_branch_litter, & + large_branch_litter, trunk_litter, live_grass) ! DESCRIPTION: ! Calculates loading for each fuel type ! ARGUMENTS: - class(fuel_type), intent(inout) :: this ! fuel class - type(litter_type), target, intent(in) :: litter ! litter class - real(r8), intent(in) :: live_grass ! amount of live grass [kgC/m2] - - - this%loading(dl_sf) = sum(litter%leaf_fines(:)) - this%loading(tw_sf:tr_sf) = litter%ag_cwd(:) + class(fuel_type), intent(inout) :: this ! fuel class + real(r8), intent(in) :: leaf_litter ! input leaf litter [kgC/m2] + real(r8), intent(in) :: twig_litter ! input twig litter [kgC/m2] + real(r8), intent(in) :: small_branch_litter ! input small branch litter [kgC/m2] + real(r8), intent(in) :: large_branch_litter ! input leaf litter [kgC/m2] + real(r8), intent(in) :: trunk_litter ! input leaf litter [kgC/m2] + real(r8), intent(in) :: live_grass ! input live grass [kgC/m2] + + this%loading(dl_sf) = leaf_litter + this%loading(tw_sf) = twig_litter + this%loading(sb_sf) = small_branch_litter + this%loading(lb_sf) = large_branch_litter + this%loading(tr_sf) = trunk_litter this%loading(lg_sf) = live_grass end subroutine CalculateLoading diff --git a/fire/SFMainMod.F90 b/fire/SFMainMod.F90 index 3456b4a4b2..343c9eb6ce 100644 --- a/fire/SFMainMod.F90 +++ b/fire/SFMainMod.F90 @@ -186,11 +186,11 @@ subroutine charecteristics_of_fuel(currentSite) type(ed_site_type), intent(in), target :: currentSite ! LOCALS: - type(fates_patch_type), pointer :: currentPatch - type(litter_type), pointer :: litt_c - real(r8) :: alpha_FMC(nfsc) ! Relative fuel moisture adjusted per drying ratio - real(r8) :: fuel_moisture(nfsc) ! Scaled moisture content of small litter fuels. - real(r8) :: MEF(nfsc) ! Moisture extinction factor of fuels + type(fates_patch_type), pointer :: currentPatch ! FATES patch + type(litter_type), pointer :: litter ! pointer to patch litter class + real(r8) :: alpha_FMC(nfsc) ! relative fuel moisture adjusted per drying ratio + real(r8) :: fuel_moisture(nfsc) ! scaled moisture content of small litter fuels. + real(r8) :: MEF(nfsc) ! moisture extinction factor of fuels fuel_moisture(:) = 0.0_r8 @@ -203,24 +203,24 @@ subroutine charecteristics_of_fuel(currentSite) call currentPatch%UpdateLiveGrass() ! update fuel loading - litt_c => currentPatch%litter(element_pos(carbon12_element)) - call currentPatch%fuel%CalculateLoading(litt_c, currentPatch%livegrass) + litter => currentPatch%litter(element_pos(carbon12_element)) + call currentPatch%fuel%CalculateLoading(sum(litter%leaf_fines(:)), & + litter%ag_cwd(1), litter%ag_cwd(2), litter%ag_cwd(3), litter%ag_cwd(4), & + currentPatch%livegrass) call currentPatch%fuel%SumLoading() - currentPatch%sum_fuel = currentPatch%fuel%total_loading - !call currentPatch%fuel%CalculateFractionalLoading() - if (currentPatch%sum_fuel > 0.0) then + if (currentPatch%fuel%total_loading> 0.0) then ! currentPatch%fuel_frac(dl_sf) = currentPatch%fuel%frac_loading(dl_sf) ! currentPatch%fuel_frac(tw_sf:tr_sf) = currentPatch%fuel%frac_loading(tw_sf:tr_sf) ! currentPatch%fuel_frac(lg_sf) = currentPatch%fuel%frac_loading(lg_sf) ! Fraction of fuel in litter classes - currentPatch%fuel_frac(dl_sf) = sum(litt_c%leaf_fines(:))/ currentPatch%sum_fuel - currentPatch%fuel_frac(tw_sf:tr_sf) = litt_c%ag_cwd(:) / currentPatch%sum_fuel - currentPatch%fuel_frac(lg_sf) = currentPatch%livegrass / currentPatch%sum_fuel + currentPatch%fuel_frac(dl_sf) = sum(litter%leaf_fines(:))/currentPatch%fuel%total_loading + currentPatch%fuel_frac(tw_sf:tr_sf) = litter%ag_cwd(:)/currentPatch%fuel%total_loading + currentPatch%fuel_frac(lg_sf) = currentPatch%livegrass/currentPatch%fuel%total_loading ! MEF (moisure of extinction) depends on compactness of fuel, depth, particle size, wind, slope ! Eqn here is eqn 27 from Peterson and Ryan (1986) "Modeling Postfire Conifer Mortality for Long-Range Planning" @@ -283,7 +283,7 @@ subroutine charecteristics_of_fuel(currentSite) currentPatch%fuel_bulkd = 0.0000000001_r8 currentPatch%fuel_frac(:) = 0.0000000001_r8 currentPatch%fuel_mef = 0.0000000001_r8 - currentPatch%sum_fuel = 0.0000000001_r8 + currentPatch%fuel%total_loading = 0.0000000001_r8 end if end if @@ -330,7 +330,7 @@ subroutine rate_of_spread ( currentSite ) if(currentPatch%nocomp_pft_label .ne. nocomp_bareground)then ! remove mineral content from net fuel load per Thonicke 2010 for ir calculation - currentPatch%sum_fuel = currentPatch%sum_fuel * (1.0_r8 - SF_val_miner_total) !net of minerals + currentPatch%fuel%total_loading = currentPatch%fuel%total_loading * (1.0_r8 - SF_val_miner_total) !net of minerals ! ----start spreading--- @@ -413,8 +413,8 @@ subroutine rate_of_spread ( currentSite ) (3.52_r8*(mw_weight**3.0_r8)))) ! ir = reaction intenisty in kJ/m2/min - ! currentPatch%sum_fuel converted from kgC/m2 to kgBiomass/m2 for ir calculation - ir = reaction_v_opt*(currentPatch%sum_fuel/0.45_r8)*SF_val_fuel_energy*moist_damp*SF_val_miner_damp + ! currentPatch%fuel%total_loading converted from kgC/m2 to kgBiomass/m2 for ir calculation + ir = reaction_v_opt*(currentPatch%fuel%total_loading/0.45_r8)*SF_val_fuel_energy*moist_damp*SF_val_miner_damp ! write(fates_log(),*) 'ir',gamma_aptr,moist_damp,SF_val_fuel_energy,SF_val_miner_damp @@ -510,7 +510,7 @@ subroutine ground_fuel_consumption ( currentSite ) ! The /10 is to convert from kgC/m2 into gC/cm2, as in the Peterson and Ryan paper #Rosie,Jun 2013 do c = 1,nfsc - tau_b(c) = 39.4_r8 *(currentPatch%fuel_frac(c)*currentPatch%sum_fuel/0.45_r8/10._r8)* & + tau_b(c) = 39.4_r8 *(currentPatch%fuel_frac(c)*currentPatch%fuel%total_loading/0.45_r8/10._r8)* & (1.0_r8-((1.0_r8-currentPatch%burnt_frac_litter(c))**0.5_r8)) enddo tau_b(tr_sf) = 0.0_r8 diff --git a/main/EDInitMod.F90 b/main/EDInitMod.F90 index e6247ecb13..7cc280176e 100644 --- a/main/EDInitMod.F90 +++ b/main/EDInitMod.F90 @@ -816,7 +816,6 @@ subroutine init_patches( nsites, sites, bc_in) currentPatch%litter_moisture(:) = 0._r8 currentPatch%fuel_eff_moist = 0._r8 currentPatch%livegrass = 0._r8 - currentPatch%sum_fuel = 0._r8 currentPatch%fuel_bulkd = 0._r8 currentPatch%fuel_sav = 0._r8 currentPatch%fuel_mef = 0._r8 diff --git a/main/FatesHistoryInterfaceMod.F90 b/main/FatesHistoryInterfaceMod.F90 index 063925c229..0cdac626ca 100644 --- a/main/FatesHistoryInterfaceMod.F90 +++ b/main/FatesHistoryInterfaceMod.F90 @@ -2633,7 +2633,7 @@ subroutine update_history_dyn1(this,nc,nsites,sites,bc_in) hio_fire_fuel_eff_moist_si(io_si) = hio_fire_fuel_eff_moist_si(io_si) + cpatch%fuel_eff_moist * cpatch%area * AREA_INV hio_fire_fuel_sav_si(io_si) = hio_fire_fuel_sav_si(io_si) + cpatch%fuel_sav * cpatch%area * AREA_INV / m_per_cm hio_fire_fuel_mef_si(io_si) = hio_fire_fuel_mef_si(io_si) + cpatch%fuel_mef * cpatch%area * AREA_INV - hio_sum_fuel_si(io_si) = hio_sum_fuel_si(io_si) + cpatch%sum_fuel * cpatch%area * AREA_INV + hio_sum_fuel_si(io_si) = hio_sum_fuel_si(io_si) + cpatch%fuel%total_loading * cpatch%area * AREA_INV hio_fire_intensity_area_product_si(io_si) = hio_fire_intensity_area_product_si(io_si) + & cpatch%FI * cpatch%frac_burnt * cpatch%area * AREA_INV * J_per_kJ @@ -3409,7 +3409,7 @@ subroutine update_history_dyn2(this,nc,nsites,sites,bc_in) ! Fuel sum [kg/m2] hio_fire_sum_fuel_si_age(io_si, cpatch%age_class) = hio_fire_sum_fuel_si_age(io_si, cpatch%age_class) + & - cpatch%sum_fuel * cpatch%area * AREA_INV + cpatch%fuel%total_loading * cpatch%area * AREA_INV @@ -4179,13 +4179,13 @@ subroutine update_history_dyn2(this,nc,nsites,sites,bc_in) i_agefuel = get_agefuel_class_index(cpatch%age,i_fuel) hio_fuel_amount_age_fuel(io_si,i_agefuel) = hio_fuel_amount_age_fuel(io_si,i_agefuel) + & - cpatch%fuel_frac(i_fuel) * cpatch%sum_fuel * cpatch%area * AREA_INV + cpatch%fuel_frac(i_fuel) * cpatch%fuel%total_loading * cpatch%area * AREA_INV hio_litter_moisture_si_fuel(io_si, i_fuel) = hio_litter_moisture_si_fuel(io_si, i_fuel) + & cpatch%litter_moisture(i_fuel) * cpatch%area * AREA_INV hio_fuel_amount_si_fuel(io_si, i_fuel) = hio_fuel_amount_si_fuel(io_si, i_fuel) + & - cpatch%fuel_frac(i_fuel) * cpatch%sum_fuel * cpatch%area * AREA_INV + cpatch%fuel_frac(i_fuel) * cpatch%fuel%total_loading * cpatch%area * AREA_INV hio_burnt_frac_litter_si_fuel(io_si, i_fuel) = hio_burnt_frac_litter_si_fuel(io_si, i_fuel) + & cpatch%burnt_frac_litter(i_fuel) * cpatch%frac_burnt * cpatch%area * AREA_INV From f4f940d13093aea2a343dfdf5dcc06a43478f897 Mon Sep 17 00:00:00 2001 From: adrifoster Date: Thu, 2 May 2024 09:07:22 -0600 Subject: [PATCH 007/111] more updates --- fire/FatesFuelClassesMod.F90 | 6 +++--- fire/FatesFuelMod.F90 | 16 +++++++--------- fire/SFMainMod.F90 | 6 +++--- 3 files changed, 13 insertions(+), 15 deletions(-) diff --git a/fire/FatesFuelClassesMod.F90 b/fire/FatesFuelClassesMod.F90 index 81d19326a4..98f7dc2c56 100644 --- a/fire/FatesFuelClassesMod.F90 +++ b/fire/FatesFuelClassesMod.F90 @@ -9,14 +9,14 @@ module FatesFuelClassesMod type :: fuel_classes_type ! There are six fuel classes: - ! 1) twigs, 2) small branches, 3) large branches, 4) trunks - ! 5) dead leaves, 6) live grass + ! 1) twigs, 2) small branches, 3) large branches + ! 4) dead leaves, 5) live grass, 6) trunks integer, private :: twigs_i = 1 ! array index for twigs pool integer, private :: small_branches_i = 2 ! array index for small branches pool integer, private :: large_branches_i = 3 ! array index for large branches pool - integer, private :: trunks_i = 4 ! array index for trunks pool integer, private :: dead_leaves_i = 5 ! array index for dead leaves pool integer, private :: live_grass_i = 6 ! array index for live grass pool + integer, private :: trunks_i = 4 ! array index for trunks pool contains diff --git a/fire/FatesFuelMod.F90 b/fire/FatesFuelMod.F90 index 9369d860f4..bdb552c013 100644 --- a/fire/FatesFuelMod.F90 +++ b/fire/FatesFuelMod.F90 @@ -1,9 +1,7 @@ module FatesFuelMod - use FatesFuelClassesMod, only : nfsc - use FatesLitterMod, only : litter_type + use FatesFuelClassesMod, only : nfsc, fuel_classes use FatesConstantsMod, only : nearzero - use FatesLitterMod, only : dl_sf, tw_sf, sb_sf, lb_sf, tr_sf, lg_sf implicit none private @@ -66,12 +64,12 @@ subroutine CalculateLoading(this, leaf_litter, twig_litter, small_branch_litter, real(r8), intent(in) :: trunk_litter ! input leaf litter [kgC/m2] real(r8), intent(in) :: live_grass ! input live grass [kgC/m2] - this%loading(dl_sf) = leaf_litter - this%loading(tw_sf) = twig_litter - this%loading(sb_sf) = small_branch_litter - this%loading(lb_sf) = large_branch_litter - this%loading(tr_sf) = trunk_litter - this%loading(lg_sf) = live_grass + this%loading(fuel_classes%dead_leaves) = leaf_litter + this%loading(fuel_classes%twigs) = twig_litter + this%loading(fuel_classes%small_branches) = small_branch_litter + this%loading(fuel_classes%large_branches) = large_branch_litter + this%loading(fuel_classes%live_grass) = live_grass + this%loading(fuel_classes%trunks) = trunk_litter end subroutine CalculateLoading diff --git a/fire/SFMainMod.F90 b/fire/SFMainMod.F90 index 0c91eebde5..d500661555 100644 --- a/fire/SFMainMod.F90 +++ b/fire/SFMainMod.F90 @@ -175,14 +175,14 @@ end subroutine UpdateFireWeather !-------------------------------------------------------------------------------------- - subroutine charecteristics_of_fuel(currentSite) + subroutine characteristics_of_fuel(currentSite) ! ! DESCRIPTION: ! Updates the site's fuel characteristics use SFParamsMod, only : SF_val_drying_ratio, SF_val_SAV, SF_val_FBD - ! AGUMENTS: + ! ARGUMENTS: type(ed_site_type), intent(in), target :: currentSite ! LOCALS: @@ -295,7 +295,7 @@ subroutine charecteristics_of_fuel(currentSite) end do - end subroutine charecteristics_of_fuel + end subroutine characteristics_of_fuel !-------------------------------------------------------------------------------------- From 7cce284b3d00fd10e9bfec407b370d52e897dad2 Mon Sep 17 00:00:00 2001 From: adrifoster Date: Thu, 2 May 2024 17:10:32 -0600 Subject: [PATCH 008/111] remove old cmakelists --- functional_unit_testing/CMakeLists.txt | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 functional_unit_testing/CMakeLists.txt diff --git a/functional_unit_testing/CMakeLists.txt b/functional_unit_testing/CMakeLists.txt deleted file mode 100644 index 1ab61abfc2..0000000000 --- a/functional_unit_testing/CMakeLists.txt +++ /dev/null @@ -1,2 +0,0 @@ -add_subdirectory(allometry) -add_subdirectory(math_utils) \ No newline at end of file From 7396655b095ef2e00995aeaf4e90e4b417541c1b Mon Sep 17 00:00:00 2001 From: adrifoster Date: Thu, 2 May 2024 17:10:46 -0600 Subject: [PATCH 009/111] add fire test dir --- unit_testing/fire/SyntheticFuelClass.F90 | 175 +++++++++++++++++++++++ 1 file changed, 175 insertions(+) create mode 100644 unit_testing/fire/SyntheticFuelClass.F90 diff --git a/unit_testing/fire/SyntheticFuelClass.F90 b/unit_testing/fire/SyntheticFuelClass.F90 new file mode 100644 index 0000000000..17cf4034f9 --- /dev/null +++ b/unit_testing/fire/SyntheticFuelClass.F90 @@ -0,0 +1,175 @@ +module SyntheticFuelTypes + + !use FatesConstantsMod, only : r8 => fates_r8 + + implicit none + private + + integer, parameter :: chunk_size = 10 + integer, parameter :: r8 = selected_real_kind(12) + + ! holds data for fake fuel models that can be used for functional + ! testing of the FATES fire model + ! these are taken from the fire behavior fuel models in Scott & Burgan 2005 + type, public :: synthetic_fuel_type + + integer :: fuel_model_index ! fuel model index + character(len=2) :: carrier ! carrier ('GR', 'GS', etc.) + character(len=5) :: fuel_model_code ! carrier plus fuel model + character(len=100) :: fuel_model_name ! long name of fuel model + real(r8) :: wind_adj_factor ! wind adjustment factor + real(r8) :: hr1_loading ! fuel loading for 1 hour fuels + real(r8) :: hr10_loading ! fuel loading for 10 hour fuels + real(r8) :: hr100_loading ! fuel loading for 100 hour fuels + real(r8) :: live_herb_loading ! fuel loading for live herbacious fuels + real(r8) :: live_woody_loading ! fuel loading for live woody fuels + real(r8) :: fuel_depth ! fuel bed depth + contains + + procedure :: InitFuelModel + + end type synthetic_fuel_type + + ! -------------------------------------------------------------------------------------- + + ! a class to just hold an array of these fuel models + type, public :: fuel_types_array_class + + type(synthetic_fuel_type), allocatable :: fuel_types(:) ! array of fuel models + integer :: num_fuel_types ! number of total fuel models + + contains + + procedure :: AddFuelModel + procedure :: GetFuelModels + + end type fuel_types_array_class + + ! -------------------------------------------------------------------------------------- + + contains + + subroutine InitFuelModel(this, fuel_model_index, carrier, fuel_model_name, & + wind_adj_factor, hr1_loading, hr10_loading, hr100_loading, live_herb_loading, & + live_woody_loading, fuel_depth) + ! + ! DESCRIPTION: + ! Initializes the fuel model with input characteristics + ! Also converts units as needed + ! + + ! ARGUMENTS: + class(synthetic_fuel_type), intent(inout) :: this + integer, intent(in) :: fuel_model_index ! fuel model index + character(len=2), intent(in) :: carrier ! main carrier + character(len=*), intent(in) :: fuel_model_name ! fuel model long name + real(r8), intent(in) :: wind_adj_factor ! wind adjustment factor + real(r8), intent(in) :: hr1_loading ! loading for 1-hr fuels [tons/acre] + real(r8), intent(in) :: hr10_loading ! loading for 10-hr fuels [tons/acre] + real(r8), intent(in) :: hr100_loading ! loading for 100-hr fuels [tons/acre] + real(r8), intent(in) :: live_herb_loading ! loading for live herbacious fuels [tons/acre] + real(r8), intent(in) :: live_woody_loading ! loading for live woody fuels [tons/acre] + real(r8), intent(in) :: fuel_depth ! fuel bed depth [ft] + + this%fuel_model_index = fuel_model_index + this%carrier = carrier + this%fuel_model_name = fuel_model_name + this%wind_adj_factor = wind_adj_factor + this%hr1_loading = hr1_loading + this%hr10_loading = hr10_loading + this%hr100_loading = hr100_loading + this%live_herb_loading = live_herb_loading + this%live_woody_loading = live_woody_loading + this%fuel_depth = fuel_depth + + end subroutine InitFuelModel + + ! -------------------------------------------------------------------------------------- + + subroutine AddFuelModel(this, fuel_model_index, carrier, fuel_model_name, & + wind_adj_factor, hr1_loading, hr10_loading, hr100_loading, live_herb_loading, & + live_woody_loading, fuel_depth) + ! + ! DESCRIPTION: + ! Adds a fuel model to the dynamic array + ! + + ! ARGUMENTS: + class(fuel_types_array_class), intent(inout) :: this ! array of fuel models + integer, intent(in) :: fuel_model_index ! fuel model index + character(len=2), intent(in) :: carrier ! main carrier + character(len=*), intent(in) :: fuel_model_name ! fuel model long name + real(r8), intent(in) :: wind_adj_factor ! wind adjustment factor + real(r8), intent(in) :: hr1_loading ! loading for 1-hr fuels [tons/acre] + real(r8), intent(in) :: hr10_loading ! loading for 10-hr fuels [tons/acre] + real(r8), intent(in) :: hr100_loading ! loading for 100-hr fuels [tons/acre] + real(r8), intent(in) :: live_herb_loading ! loading for live herbacious fuels [tons/acre] + real(r8), intent(in) :: live_woody_loading ! loading for live woody fuels [tons/acre] + real(r8), intent(in) :: fuel_depth ! fuel bed depth [ft] + + ! LOCALS: + type(synthetic_fuel_type) :: fuel_model ! fuel model + type(synthetic_fuel_type), allocatable :: temporary_array(:) ! temporary array to hold data while re-allocating + + ! first make sure we have enough space in the array + if (allocated(this%fuel_types)) then + ! already allocated to some size + if (this%num_fuel_types == size(this%fuel_types)) then + ! need to add more space + allocate(temporary_array(size(this%fuel_types) + chunk_size)) + temporary_array(1:size(this%fuel_types)) = this%fuel_types + call move_alloc(temporary_array, this%fuel_types) + end if + + this%num_fuel_types = this%num_fuel_types + 1 + + else + ! first element in array + allocate(this%fuel_types(chunk_size)) + this%num_fuel_types = 1 + end if + + call fuel_model%InitFuelModel(fuel_model_index, carrier, fuel_model_name, & + wind_adj_factor, hr1_loading, hr10_loading, hr100_loading, live_herb_loading, & + live_woody_loading, fuel_depth) + + this%fuel_types(this%num_fuel_types) = fuel_model + + end subroutine AddFuelModel + + ! -------------------------------------------------------------------------------------- + + subroutine GetFuelModels(this) + ! + ! DESCRIPTION: + ! Returns an array of hard-coded fuel models + ! these are taken from the fire behavior fuel models in Scott & Burgan 2005 + ! + + ! ARGUMENTS: + class(fuel_types_array_class), intent(inout) :: this ! array of fuel models + + ! initialize to 0 + this%num_fuel_types = 0 + + call this%AddFuelModel(fuel_model_index=1, carrier='GR', fuel_model_name='short grass', & + wind_adj_factor=0.36_r8, hr1_loading=0.7_r8, hr10_loading=0.0_r8, hr100_loading=0.0_r8, & + live_herb_loading=0.0_r8, live_woody_loading=0.0_r8, fuel_depth=1.0_r8) + + call this%AddFuelModel(fuel_model_index=2, carrier='GR', fuel_model_name='timber and grass understory', & + wind_adj_factor=0.36_r8, hr1_loading=2.0_r8, hr10_loading=1.0_r8, hr100_loading=0.5_r8, & + live_herb_loading=0.5_r8, live_woody_loading=0.0_r8, fuel_depth=1.0_r8) + + call this%AddFuelModel(fuel_model_index=101, carrier='GR', fuel_model_name='short, sparse dry climate grass', & + wind_adj_factor=0.31_r8, hr1_loading=0.1_r8, hr10_loading=0.0_r8, hr100_loading=0.0_r8, & + live_herb_loading=0.3_r8, live_woody_loading=0.0_r8, fuel_depth=0.4_r8) + + call this%AddFuelModel(fuel_model_index=102, carrier='GR', fuel_model_name='low load dry climate grass', & + wind_adj_factor=0.36_r8, hr1_loading=0.1_r8, hr10_loading=0.0_r8, hr100_loading=0.0_r8, & + live_herb_loading=1.0_r8, live_woody_loading=0.0_r8, fuel_depth=1.0_r8) + + end subroutine GetFuelModels + + ! -------------------------------------------------------------------------------------- + +end module SyntheticFuelTypes \ No newline at end of file From 861f84cea5a9bf18d2aa262e489d12709f13adb3 Mon Sep 17 00:00:00 2001 From: adrifoster Date: Thu, 2 May 2024 17:17:46 -0600 Subject: [PATCH 010/111] adding test file --- FatesTestFuel.F90 | 6 ++++++ unit_testing/fire/SyntheticFuelClass.F90 | 3 --- 2 files changed, 6 insertions(+), 3 deletions(-) create mode 100644 FatesTestFuel.F90 diff --git a/FatesTestFuel.F90 b/FatesTestFuel.F90 new file mode 100644 index 0000000000..10cf56f151 --- /dev/null +++ b/FatesTestFuel.F90 @@ -0,0 +1,6 @@ +program FatesTestFuel + +implicit none + + +end program FatesTestFuel \ No newline at end of file diff --git a/unit_testing/fire/SyntheticFuelClass.F90 b/unit_testing/fire/SyntheticFuelClass.F90 index 17cf4034f9..48c86420b7 100644 --- a/unit_testing/fire/SyntheticFuelClass.F90 +++ b/unit_testing/fire/SyntheticFuelClass.F90 @@ -149,9 +149,6 @@ subroutine GetFuelModels(this) ! ARGUMENTS: class(fuel_types_array_class), intent(inout) :: this ! array of fuel models - ! initialize to 0 - this%num_fuel_types = 0 - call this%AddFuelModel(fuel_model_index=1, carrier='GR', fuel_model_name='short grass', & wind_adj_factor=0.36_r8, hr1_loading=0.7_r8, hr10_loading=0.0_r8, hr100_loading=0.0_r8, & live_herb_loading=0.0_r8, live_woody_loading=0.0_r8, fuel_depth=1.0_r8) From 0b66d3f2dc6303b458290542ac90fe39f74122ce Mon Sep 17 00:00:00 2001 From: adrifoster Date: Thu, 2 May 2024 21:23:29 -0600 Subject: [PATCH 011/111] adding fire tests --- CMakeLists.txt | 1 + FatesTestFuel.F90 | 6 - fire/CMakeLists.txt | 2 + fire/FatesFuelMod.F90 | 12 +- unit_testing/fire/CMakeLists.txt | 26 ++++ unit_testing/fire/FatesTestFireMod.F90 | 199 +++++++++++++++++++++++++ unit_testing/fire/FatesTestFuel.F90 | 9 ++ unit_testing/run_fates_tests.py | 9 ++ 8 files changed, 252 insertions(+), 12 deletions(-) delete mode 100644 FatesTestFuel.F90 create mode 100644 unit_testing/fire/CMakeLists.txt create mode 100644 unit_testing/fire/FatesTestFireMod.F90 create mode 100644 unit_testing/fire/FatesTestFuel.F90 diff --git a/CMakeLists.txt b/CMakeLists.txt index d8d9d0a395..1828761fb3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -93,4 +93,5 @@ link_directories(${CMAKE_CURRENT_BINARY_DIR}) # done before adding the tests themselves. add_subdirectory(${HLM_ROOT}/src/fates/unit_testing/allometry fates_allom_test) add_subdirectory(${HLM_ROOT}/src/fates/unit_testing/math_utils fates_math_test) +add_subdirectory(${HLM_ROOT}/src/fates/unit_testing/fire fates_fuel_test) add_subdirectory(${HLM_ROOT}/src/fates/fire/test/fire_weather_test fates_fireweather_test) diff --git a/FatesTestFuel.F90 b/FatesTestFuel.F90 deleted file mode 100644 index 10cf56f151..0000000000 --- a/FatesTestFuel.F90 +++ /dev/null @@ -1,6 +0,0 @@ -program FatesTestFuel - -implicit none - - -end program FatesTestFuel \ No newline at end of file diff --git a/fire/CMakeLists.txt b/fire/CMakeLists.txt index 341065c3c3..f3b0f84ca5 100644 --- a/fire/CMakeLists.txt +++ b/fire/CMakeLists.txt @@ -3,6 +3,8 @@ list(APPEND fates_sources SFParamsMod.F90 SFFireWeatherMod.F90 SFNesterovMod.F90 + FatesFuelMod.F90 + FatesFuelClassesMod.F90 ) sourcelist_to_parent(fates_sources) diff --git a/fire/FatesFuelMod.F90 b/fire/FatesFuelMod.F90 index bdb552c013..0cf803b366 100644 --- a/fire/FatesFuelMod.F90 +++ b/fire/FatesFuelMod.F90 @@ -64,12 +64,12 @@ subroutine CalculateLoading(this, leaf_litter, twig_litter, small_branch_litter, real(r8), intent(in) :: trunk_litter ! input leaf litter [kgC/m2] real(r8), intent(in) :: live_grass ! input live grass [kgC/m2] - this%loading(fuel_classes%dead_leaves) = leaf_litter - this%loading(fuel_classes%twigs) = twig_litter - this%loading(fuel_classes%small_branches) = small_branch_litter - this%loading(fuel_classes%large_branches) = large_branch_litter - this%loading(fuel_classes%live_grass) = live_grass - this%loading(fuel_classes%trunks) = trunk_litter + this%loading(fuel_classes%dead_leaves()) = leaf_litter + this%loading(fuel_classes%twigs()) = twig_litter + this%loading(fuel_classes%small_branches()) = small_branch_litter + this%loading(fuel_classes%large_branches()) = large_branch_litter + this%loading(fuel_classes%live_grass()) = live_grass + this%loading(fuel_classes%trunks()) = trunk_litter end subroutine CalculateLoading diff --git a/unit_testing/fire/CMakeLists.txt b/unit_testing/fire/CMakeLists.txt new file mode 100644 index 0000000000..add38c83af --- /dev/null +++ b/unit_testing/fire/CMakeLists.txt @@ -0,0 +1,26 @@ +set(fire_test_sources + FatesTestFuel.F90 + FatesTestFireMod.F90) + +set(NETCDF_C_DIR ${NETCDF_C_PATH}) +set(NETCDF_FORTRAN_DIR ${NETCDF_F_PATH}) + +FIND_PATH(NETCDFC_FOUND libnetcdf.a ${NETCDF_C_DIR}/lib) +FIND_PATH(NETCDFF_FOUND libnetcdff.a ${NETCDF_FORTRAN_DIR}/lib) + + +include_directories(${NETCDF_C_DIR}/include + ${NETCDF_FORTRAN_DIR}/include) + +link_directories(${NETCDF_C_DIR}/lib + ${NETCDF_FORTRAN_DIR}/lib + ${PFUNIT_TOP_DIR}/lib) + +add_executable(FATES_fuel_exe ${fire_test_sources}) + +target_link_libraries(FATES_fuel_exe + netcdf + netcdff + fates + csm_share + funit) \ No newline at end of file diff --git a/unit_testing/fire/FatesTestFireMod.F90 b/unit_testing/fire/FatesTestFireMod.F90 new file mode 100644 index 0000000000..3e09578a79 --- /dev/null +++ b/unit_testing/fire/FatesTestFireMod.F90 @@ -0,0 +1,199 @@ +module FatesTestFireMod + ! + ! DESCRIPTION: + ! Module to support testing the FATES SPIFTIRE model + ! + + use FatesConstantsMod, only : r8 => fates_r8 + use EDTypesMod, only : ed_site_type + use FatesPatchMod, only : fates_patch_type + use SFNesterovMod, only : nesterov_index + + implicit none + private + + public :: SetUpSite + + contains + + !===================================================================================== + + subroutine SetUpSite(site) + ! + ! DESCRIPTION: + ! Sets up site, patch, litter, and fuel + ! This only sets up the stuff we actually need for these subroutines + ! + + ! ARGUMENTS: + type(ed_site_type), target :: site ! site object + + ! LOCALS: + type(fates_patch_type), pointer :: patch + + ! set up fire weather class + allocate(nesterov_index :: site%fireWeather) + call site%fireWeather%Init() + + ! set up one patch + allocate(patch) + call patch%Init(2, 1) + + patch%patchno = 1 + patch%younger => null() + patch%older => null() + + site%youngest_patch => patch + site%oldest_patch => patch + + end subroutine SetUpSite + + !===================================================================================== + + ! subroutine ReadDatmData(nc_file, temp_degC, precip, rh, wind) + ! ! + ! ! DESCRIPTION: + ! ! Reads and returns DATM data + ! ! + + ! ! ARGUMENTS: + ! character(len=*), intent(in) :: nc_file ! netcdf file with DATM data + ! real(r8), allocatable, intent(out) :: temp_degC(:) ! daily air temperature [degC] + ! real(r8), allocatable, intent(out) :: precip(:) ! daily precipitation [mm] + ! real(r8), allocatable, intent(out) :: rh(:) ! daily relative humidity [%] + ! real(r8), allocatable, intent(out) :: wind(:) ! daily wind speed [m/s] + + ! ! LOCALS: + ! integer :: ncid ! netcdf file unit number + + ! ! open file + ! call OpenNCFile(trim(nc_file), ncid, 'read') + + ! ! read in data + ! call GetVar(ncid, 'temp_degC', temp_degC) + ! call GetVar(ncid, 'precip', precip) + ! call GetVar(ncid, 'RH', rh) + ! call GetVar(ncid, 'wind', wind) + + ! ! close file + ! call CloseNCFile(ncid) + + ! end subroutine ReadDatmData + + !===================================================================================== + + ! subroutine WriteFireData(out_file, nsteps, time_counter, temp_degC, precip, rh, NI, & + ! loading, moisture, av_moisture, ros) + ! ! + ! ! DESCRIPTION: + ! ! writes out data from the unit test + ! ! + + ! ! ARGUMENTS: + ! character(len=*), intent(in) :: out_file + ! integer, intent(in) :: nsteps + ! integer, intent(in) :: time_counter(:) + ! real(r8), intent(in) :: temp_degC(:) + ! real(r8), intent(in) :: precip(:) + ! real(r8), intent(in) :: rh(:) + ! real(r8), intent(in) :: NI(:) + ! real(r8), intent(in) :: loading(:) + ! real(r8), intent(in) :: moisture(:,:) + ! real(r8), intent(in) :: av_moisture(:) + ! real(r8), intent(in) :: ros(:) + + ! ! LOCALS: + ! integer :: ncid ! netcdf id + ! character(len=8) :: dim_names(2) ! dimension names + ! integer :: dimIDs(2) ! dimension IDs + ! integer :: timeID, litterID + ! integer :: tempID, precipID, rhID, NIID, loadingID, moistureID + ! integer :: av_moistureID, rosID + + ! ! dimension names + ! dim_names = [character(len=12) :: 'time', 'litter_class'] + + ! ! open file + ! call OpenNCFile(trim(out_file), ncid, 'readwrite') + + ! ! register dimensions + ! call RegisterNCDims(ncid, dim_names, (/nsteps, nfsc/), 2, dimIDs) + + ! ! register time + ! call RegisterVar1D(ncid, 'time', dimIDs(1), type_int, & + ! [character(len=20) :: 'time_origin', 'units', 'calendar', 'long_name'], & + ! [character(len=150) :: '2018-01-01 00:00:00', 'days since 2018-01-01 00:00:00', & + ! 'gregorian', 'time'], & + ! 4, timeID) + + ! ! register litter class + ! call RegisterVar1D(ncid, 'litter_class', dimIDs(2), type_int, & + ! [character(len=20) :: 'units', 'long_name'], & + ! [character(len=150) :: '', 'litter class'], 2, litterID) + + ! ! register temperature + ! call RegisterVar1D(ncid, 'temp_degC', dimIDs(1), type_double, & + ! [character(len=20) :: 'coordinates', 'units', 'long_name'], & + ! [character(len=150) :: 'time', 'degrees C', 'air temperature'], & + ! 3, tempID) + + ! ! register precipitation + ! call RegisterVar1D(ncid, 'precip', dimIDs(1), type_double, & + ! [character(len=20) :: 'coordinates', 'units', 'long_name'], & + ! [character(len=150) :: 'time', 'mm', 'precipitation'], & + ! 3, precipID) + + ! ! register relative humidity + ! call RegisterVar1D(ncid, 'RH', dimIDs(1), type_double, & + ! [character(len=20) :: 'coordinates', 'units', 'long_name'], & + ! [character(len=150) :: 'time', '%', 'relative humidity'], & + ! 3, rhID) + + ! ! register Nesterov Index + ! call RegisterVar1D(ncid, 'NI', dimIDs(1), type_double, & + ! [character(len=20) :: 'coordinates', 'units', 'long_name'], & + ! [character(len=150) :: 'time', '', 'Nesterov Index'], & + ! 3, NIID) + + ! ! register loading + ! call RegisterVar1D(ncid, 'loading', dimIDs(1), type_double, & + ! [character(len=20) :: 'coordinates', 'units', 'long_name'], & + ! [character(len=150) :: 'time', 'kg m-2', 'fuel loading'], & + ! 3, loadingID) + + ! ! register moisture + ! call RegisterVar2D(ncid, 'fuel_moisture', dimIDs(1:2), type_double, & + ! [character(len=20) :: 'coordinates', 'units', 'long_name'], & + ! [character(len=150) :: 'time litter_class', 'm3 m-3', 'fuel moisture'], & + ! 3, moistureID) + + ! ! register average moisture + ! call RegisterVar1D(ncid, 'average_moisture', dimIDs(1), type_double, & + ! [character(len=20) :: 'coordinates', 'units', 'long_name'], & + ! [character(len=150) :: 'time', 'm3 m-3', 'average fuel moisture'], & + ! 3, av_moistureID) + + ! ! register ROS + ! call RegisterVar1D(ncid, 'ros', dimIDs(1), type_double, & + ! [character(len=20) :: 'coordinates', 'units', 'long_name'], & + ! [character(len=150) :: 'time', 'm min-1', 'rate of forward spread'], & + ! 3, rosID) + + ! call Check(NF90_ENDDEF(ncid)) + + ! call Check(NF90_PUT_VAR(ncid, timeID, time_counter)) + ! call Check(NF90_PUT_VAR(ncid, litterID, (/tw_sf, sb_sf, lb_sf, tr_sf, dl_sf, lg_sf/))) + ! call Check(NF90_PUT_VAR(ncid, tempID, temp_degC(:))) + ! call Check(NF90_PUT_VAR(ncid, precipID, precip(:))) + ! call Check(NF90_PUT_VAR(ncid, rhID, rh(:))) + ! call Check(NF90_PUT_VAR(ncid, NIID, NI(:))) + ! call Check(NF90_PUT_VAR(ncid, loadingID, loading(:))) + ! call Check(NF90_PUT_VAR(ncid, moistureID, moisture(:,:))) + ! call Check(NF90_PUT_VAR(ncid, av_moistureID, av_moisture(:))) + ! call Check(NF90_PUT_VAR(ncid, rosID, ros(:))) + + ! call CloseNCFile(ncid) + + ! end subroutine WriteFireData + +end module FatesTestFireMod \ No newline at end of file diff --git a/unit_testing/fire/FatesTestFuel.F90 b/unit_testing/fire/FatesTestFuel.F90 new file mode 100644 index 0000000000..430873c904 --- /dev/null +++ b/unit_testing/fire/FatesTestFuel.F90 @@ -0,0 +1,9 @@ +program FatesTestFuel + + use FatesTestFireMod, only : SetUpSite + +implicit none + +print *, 'hello fates fire' + +end program FatesTestFuel \ No newline at end of file diff --git a/unit_testing/run_fates_tests.py b/unit_testing/run_fates_tests.py index 9c260276ff..3ce90c1e89 100755 --- a/unit_testing/run_fates_tests.py +++ b/unit_testing/run_fates_tests.py @@ -68,6 +68,15 @@ "use_param_file": False, "other_args": [], "plotting_function": plot_quadratic_dat, + }, + "fuel": { + "test_dir": "fates_fuel_test", + "test_exe": "FATES_fuel_exe", + "out_file": None, + "has_unit_test": False, + "use_param_file": False, + "other_args": [], + "plotting_function": None } } From 19f52378b124fc543d62514b10c0b3d2ac5eb901 Mon Sep 17 00:00:00 2001 From: adrifoster Date: Fri, 3 May 2024 09:02:10 -0600 Subject: [PATCH 012/111] testing --- CMakeLists.txt | 6 +++--- unit_testing/allometry/FatesTestAllometry.F90 | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1828761fb3..80ace205f2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -92,6 +92,6 @@ link_directories(${CMAKE_CURRENT_BINARY_DIR}) # carefully: for example, include_directories and link_directories needs to be # done before adding the tests themselves. add_subdirectory(${HLM_ROOT}/src/fates/unit_testing/allometry fates_allom_test) -add_subdirectory(${HLM_ROOT}/src/fates/unit_testing/math_utils fates_math_test) -add_subdirectory(${HLM_ROOT}/src/fates/unit_testing/fire fates_fuel_test) -add_subdirectory(${HLM_ROOT}/src/fates/fire/test/fire_weather_test fates_fireweather_test) +#add_subdirectory(${HLM_ROOT}/src/fates/unit_testing/math_utils fates_math_test) +#add_subdirectory(${HLM_ROOT}/src/fates/unit_testing/fire fates_fuel_test) +#add_subdirectory(${HLM_ROOT}/src/fates/fire/test/fire_weather_test fates_fireweather_test) diff --git a/unit_testing/allometry/FatesTestAllometry.F90 b/unit_testing/allometry/FatesTestAllometry.F90 index 91ee4df2cf..857ceff37f 100644 --- a/unit_testing/allometry/FatesTestAllometry.F90 +++ b/unit_testing/allometry/FatesTestAllometry.F90 @@ -86,11 +86,12 @@ end subroutine WriteAllometryData allocate(character(arglen) :: param_file) call get_command_argument(1, value=param_file) endif - + ! read in parameter file call param_reader%Init(param_file) call param_reader%RetrieveParameters() + ! determine sizes of arrays numpft = size(prt_params%wood_density, dim=1) numdbh = int((max_dbh - min_dbh)/dbh_inc + 1) From e4be4c4fb8b62983619eeda813fa11aa179d18ab Mon Sep 17 00:00:00 2001 From: adrifoster Date: Mon, 6 May 2024 15:30:49 -0600 Subject: [PATCH 013/111] update fuel characteristics subroutine --- CMakeLists.txt | 6 +- biogeochem/EDPatchDynamicsMod.F90 | 2 +- biogeochem/FatesLitterMod.F90 | 13 - biogeochem/FatesPatchMod.F90 | 24 +- fire/FatesFuelClassesMod.F90 | 9 +- fire/FatesFuelMod.F90 | 209 +++++++++-- fire/SFFireWeatherMod.F90 | 3 +- fire/SFMainMod.F90 | 207 +++-------- fire/SFNesterovMod.F90 | 5 +- fire/SFParamsMod.F90 | 25 +- main/EDInitMod.F90 | 1 - main/EDTypesMod.F90 | 2 +- main/FatesHistoryInterfaceMod.F90 | 4 +- parameter_files/fates_params_default.cdl | 18 +- unit_testing/allometry/FatesTestAllometry.F90 | 16 +- unit_testing/allometry/allometry_plotting.py | 41 +- unit_testing/fire/CMakeLists.txt | 3 +- unit_testing/fire/FatesTestFireMod.F90 | 350 ++++++++++-------- unit_testing/fire/FatesTestFuel.F90 | 96 ++++- ...icFuelClass.F90 => SyntheticFuelTypes.F90} | 75 +++- unit_testing/fire/fuel_plotting.py | 60 +++ unit_testing/run_fates_tests.py | 9 +- unit_testing/unit_test_shr/CMakeLists.txt | 1 + .../unit_test_shr/FatesArgumentUtils.F90 | 36 ++ unit_testing/utils.py | 40 ++ 25 files changed, 755 insertions(+), 500 deletions(-) rename unit_testing/fire/{SyntheticFuelClass.F90 => SyntheticFuelTypes.F90} (74%) create mode 100644 unit_testing/fire/fuel_plotting.py create mode 100644 unit_testing/unit_test_shr/FatesArgumentUtils.F90 diff --git a/CMakeLists.txt b/CMakeLists.txt index 80ace205f2..1828761fb3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -92,6 +92,6 @@ link_directories(${CMAKE_CURRENT_BINARY_DIR}) # carefully: for example, include_directories and link_directories needs to be # done before adding the tests themselves. add_subdirectory(${HLM_ROOT}/src/fates/unit_testing/allometry fates_allom_test) -#add_subdirectory(${HLM_ROOT}/src/fates/unit_testing/math_utils fates_math_test) -#add_subdirectory(${HLM_ROOT}/src/fates/unit_testing/fire fates_fuel_test) -#add_subdirectory(${HLM_ROOT}/src/fates/fire/test/fire_weather_test fates_fireweather_test) +add_subdirectory(${HLM_ROOT}/src/fates/unit_testing/math_utils fates_math_test) +add_subdirectory(${HLM_ROOT}/src/fates/unit_testing/fire fates_fuel_test) +add_subdirectory(${HLM_ROOT}/src/fates/fire/test/fire_weather_test fates_fireweather_test) diff --git a/biogeochem/EDPatchDynamicsMod.F90 b/biogeochem/EDPatchDynamicsMod.F90 index 43894a2c75..1860980e76 100644 --- a/biogeochem/EDPatchDynamicsMod.F90 +++ b/biogeochem/EDPatchDynamicsMod.F90 @@ -2755,12 +2755,12 @@ subroutine fuse_2_patches(csite, dp, rp) rp%fuel_eff_moist = (dp%fuel_eff_moist*dp%area + rp%fuel_eff_moist*rp%area) * inv_sum_area rp%livegrass = (dp%livegrass*dp%area + rp%livegrass*rp%area) * inv_sum_area rp%fuel%total_loading = (dp%fuel%total_loading*dp%area + rp%fuel%total_loading*rp%area) * inv_sum_area + rp%fuel%frac_loading = (dp%fuel%frac_loading(:)*dp%area + rp%fuel%frac_loading(:)*rp%area) * inv_sum_area rp%fuel_bulkd = (dp%fuel_bulkd*dp%area + rp%fuel_bulkd*rp%area) * inv_sum_area rp%fuel_sav = (dp%fuel_sav*dp%area + rp%fuel_sav*rp%area) * inv_sum_area rp%fuel_mef = (dp%fuel_mef*dp%area + rp%fuel_mef*rp%area) * inv_sum_area rp%ros_front = (dp%ros_front*dp%area + rp%ros_front*rp%area) * inv_sum_area rp%tau_l = (dp%tau_l*dp%area + rp%tau_l*rp%area) * inv_sum_area - rp%fuel_frac(:) = (dp%fuel_frac(:)*dp%area + rp%fuel_frac(:)*rp%area) * inv_sum_area rp%tfc_ros = (dp%tfc_ros*dp%area + rp%tfc_ros*rp%area) * inv_sum_area rp%fi = (dp%fi*dp%area + rp%fi*rp%area) * inv_sum_area rp%fd = (dp%fd*dp%area + rp%fd*rp%area) * inv_sum_area diff --git a/biogeochem/FatesLitterMod.F90 b/biogeochem/FatesLitterMod.F90 index 8ad782eb22..9a240d45e5 100644 --- a/biogeochem/FatesLitterMod.F90 +++ b/biogeochem/FatesLitterMod.F90 @@ -54,19 +54,6 @@ module FatesLitterMod integer, public, parameter :: ilabile = 1 ! Array index for labile portion integer, public, parameter :: icellulose = 2 ! Array index for cellulose portion integer, public, parameter :: ilignin = 3 ! Array index for the lignin portion - - ! SPITFIRE - - integer, parameter, public :: NFSC = NCWD+2 ! number fuel size classes (4 cwd size classes, leaf litter, and grass) - integer, parameter, public :: tw_sf = 1 ! array index of twig pool for spitfire - integer, parameter, public :: sb_sf = 2 ! array index of large branch pool for spitfire - integer, parameter, public :: lb_sf = 3 ! array index of large branch pool for spitfire - integer, parameter, public :: tr_sf = 4 ! array index of dead trunk pool for spitfire - integer, parameter, public :: dl_sf = 5 ! array index of dead leaf pool for spitfire (dead grass and dead leaves) - integer, parameter, public :: lg_sf = 6 ! array index of live grass pool for spitfire - - - type, public :: litter_type ! This object is allocated for each element (C, N, P, etc) that we wish to track. diff --git a/biogeochem/FatesPatchMod.F90 b/biogeochem/FatesPatchMod.F90 index 4013643de0..f224fbd248 100644 --- a/biogeochem/FatesPatchMod.F90 +++ b/biogeochem/FatesPatchMod.F90 @@ -13,7 +13,7 @@ module FatesPatchMod use FatesUtilsMod, only : check_var_real use FatesCohortMod, only : fates_cohort_type use FatesRunningMeanMod, only : rmean_type, rmean_arr_type - use FatesLitterMod, only : nfsc + use FatesFuelClassesMod, only : nfsc use FatesLitterMod, only : litter_type use FatesFuelMod, only : fuel_type use PRTGenericMod, only : num_elements @@ -197,17 +197,7 @@ module FatesPatchMod ! FUELS AND FIRE ! fuel characteristics - real(r8) :: fuel_frac(nfsc) ! fraction of each litter class in the ros_fuel [0-1] real(r8) :: livegrass ! total aboveground grass biomass in patch [kgC/m2] - real(r8) :: fuel_bulkd ! average fuel bulk density of the ground fuel. [kg/m3] - ! (incl. live grasses, omits 1000hr fuels) - real(r8) :: fuel_sav ! average surface area to volume ratio of the ground fuel [cm-1] - ! (incl. live grasses, omits 1000hr fuels) - real(r8) :: fuel_mef ! average moisture of extinction factor - ! of the ground fuel (incl. live grasses, omits 1000hr fuels) - real(r8) :: fuel_eff_moist ! effective avearage fuel moisture content of the ground fuel - ! (incl. live grasses. omits 1000hr fuels) - real(r8) :: litter_moisture(nfsc) ! moisture of litter [m3/m3] ! fire spread real(r8) :: ros_front ! rate of forward spread of fire [m/min] @@ -380,13 +370,7 @@ subroutine NanValues(this) this%fragmentation_scaler(:) = nan ! FUELS AND FIRE - this%fuel_frac(:) = nan this%livegrass = nan - this%fuel_bulkd = nan - this%fuel_sav = nan - this%fuel_mef = nan - this%fuel_eff_moist = nan - this%litter_moisture(:) = nan this%ros_front = nan this%ros_back = nan this%tau_l = nan @@ -456,13 +440,7 @@ subroutine ZeroValues(this) this%fragmentation_scaler(:) = 0.0_r8 ! FIRE - this%fuel_frac(:) = 0.0_r8 this%livegrass = 0.0_r8 - this%fuel_bulkd = 0.0_r8 - this%fuel_sav = 0.0_r8 - this%fuel_mef = 0.0_r8 - this%fuel_eff_moist = 0.0_r8 - this%litter_moisture(:) = 0.0_r8 this%ros_front = 0.0_r8 this%ros_back = 0.0_r8 this%tau_l = 0.0_r8 diff --git a/fire/FatesFuelClassesMod.F90 b/fire/FatesFuelClassesMod.F90 index 98f7dc2c56..b263e0cf47 100644 --- a/fire/FatesFuelClassesMod.F90 +++ b/fire/FatesFuelClassesMod.F90 @@ -5,7 +5,8 @@ module FatesFuelClassesMod implicit none private - integer, parameter, public :: nfsc = ncwd + 2 + integer, parameter, public :: nfsc = ncwd + 2 ! number of total fuel classes + integer, parameter, public :: nfsc_notrunks = ncwd + 1 ! number of fuel classes without trunks type :: fuel_classes_type ! There are six fuel classes: @@ -14,9 +15,9 @@ module FatesFuelClassesMod integer, private :: twigs_i = 1 ! array index for twigs pool integer, private :: small_branches_i = 2 ! array index for small branches pool integer, private :: large_branches_i = 3 ! array index for large branches pool - integer, private :: dead_leaves_i = 5 ! array index for dead leaves pool - integer, private :: live_grass_i = 6 ! array index for live grass pool - integer, private :: trunks_i = 4 ! array index for trunks pool + integer, private :: dead_leaves_i = 4 ! array index for dead leaves pool + integer, private :: live_grass_i = 5 ! array index for live grass pool + integer, private :: trunks_i = 6 ! array index for trunks pool contains diff --git a/fire/FatesFuelMod.F90 b/fire/FatesFuelMod.F90 index 0cf803b366..50ea2fc88a 100644 --- a/fire/FatesFuelMod.F90 +++ b/fire/FatesFuelMod.F90 @@ -1,22 +1,27 @@ module FatesFuelMod - use FatesFuelClassesMod, only : nfsc, fuel_classes + use FatesFuelClassesMod, only : nfsc, nfsc_notrunks, fuel_classes + use FatesConstantsMod, only : r8 => fates_r8 use FatesConstantsMod, only : nearzero + use SFNesterovMod, only : nesterov_index + use SFFireWeatherMod, only : fire_weather + use FatesGlobals, only : fates_log + use FatesGlobals, only : endrun => fates_endrun + use shr_log_mod, only : errMsg => shr_log_errMsg implicit none private - integer, parameter :: r8 = selected_real_kind(12) - type, public :: fuel_type - real(r8) :: loading(nfsc) ! fuel loading of each fuel class [kgC/m2] - real(r8) :: moisture(nfsc) ! fuel moisture of each fuel class [m3/m3] - real(r8) :: frac_loading(nfsc) ! fractional loading of non-trunk fuel classes [0-1] - real(r8) :: total_loading ! total fuel loading - DOES NOT INCLUDE TRUNKS [kgC/m2] - real(r8) :: average_moisture ! weighted average of fuel moisture across non-trunk fuel classes [m3/m3] - real(r8) :: bulk_density ! weighted average of bulk density across non-trunk fuel classes [kg/m3] - real(r8) :: SAV ! weighted average of surface area to volume ratio across non-trunk fuel classes [/cm] - real(r8) :: MEF ! weighted average of moisture of extinction across non-trunk fuel classes [m3/m3] + + real(r8) :: loading(nfsc_notrunks) ! fuel loading of non-trunks fuel class [kgC/m2] + real(r8) :: effective_moisture(nfsc) ! fuel effective moisture all fuel class [m3/m3] + real(r8) :: frac_loading(nfsc_notrunks) ! fractional loading of non-trunk fuel classes [0-1] + real(r8) :: total_loading ! total fuel loading - DOES NOT INCLUDE TRUNKS [kgC/m2] + real(r8) :: average_moisture ! weighted average of fuel moisture across non-trunk fuel classes [m3/m3] + real(r8) :: bulk_density ! weighted average of bulk density across non-trunk fuel classes [kg/m3] + real(r8) :: SAV ! weighted average of surface area to volume ratio across non-trunk fuel classes [/cm] + real(r8) :: MEF ! weighted average of moisture of extinction across non-trunk fuel classes [m3/m3] contains @@ -24,9 +29,12 @@ module FatesFuelMod procedure :: CalculateLoading procedure :: SumLoading procedure :: CalculateFractionalLoading + procedure :: UpdateFuelMoisture + procedure :: AverageBulkDensity + procedure :: AverageSAV end type fuel_type - + contains subroutine Init(this) @@ -34,17 +42,17 @@ subroutine Init(this) ! Initialize fuel class ! ARGUMENTS: - class(fuel_type), intent(inout) :: this ! fuel class - + class(fuel_type), intent(inout) :: this ! fuel class + ! just zero everything - this%loading(:) = 0.0_r8 - this%total_loading = 0.0_r8 - this%frac_loading(:) = 0.0_r8 - this%moisture(:) = 0.0_r8 + this%loading(1:nfsc_notrunks) = 0.0_r8 + this%frac_loading(1:nfsc_notrunks) = 0.0_r8 + this%effective_moisture(1:nfsc) = 0.0_r8 + this%total_loading = 0.0_r8 this%average_moisture = 0.0_r8 - this%bulk_density = 0.0_r8 - this%SAV = 0.0_r8 - this%MEF = 0.0_r8 + this%bulk_density = 0.0_r8 + this%SAV = 0.0_r8 + this%MEF = 0.0_r8 end subroutine Init @@ -69,7 +77,6 @@ subroutine CalculateLoading(this, leaf_litter, twig_litter, small_branch_litter, this%loading(fuel_classes%small_branches()) = small_branch_litter this%loading(fuel_classes%large_branches()) = large_branch_litter this%loading(fuel_classes%live_grass()) = live_grass - this%loading(fuel_classes%trunks()) = trunk_litter end subroutine CalculateLoading @@ -81,16 +88,16 @@ subroutine SumLoading(this) ! ARGUMENTS: class(fuel_type), intent(inout) :: this ! fuel class - - this%total_loading = sum(this%loading(:)) + + this%total_loading = sum(this%loading(1:nfsc_notrunks)) end subroutine SumLoading !------------------------------------------------------------------------------------- - + subroutine CalculateFractionalLoading(this) ! DESCRIPTION: - ! Calculates fractional loading + ! Calculates fractional loading for fuel ! ARGUMENTS: class(fuel_type), intent(inout) :: this ! fuel class @@ -99,13 +106,159 @@ subroutine CalculateFractionalLoading(this) call this%SumLoading() if (this%total_loading > nearzero) then - this%frac_loading(:) = this%loading(:)/this%total_loading + this%frac_loading(1:nfsc_notrunks) = this%loading(1:nfsc_notrunks)/this%total_loading else - this%frac_loading(:) = 0.0_r8 + this%frac_loading(1:nfsc_notrunks) = 0.0_r8 end if end subroutine CalculateFractionalLoading !------------------------------------------------------------------------------------- + + subroutine UpdateFuelMoisture(this, sav_fuel, drying_ratio, fireWeatherClass) + ! DESCRIPTION: + ! Updates fuel moisture depending on what fire weather class is in use + + ! ARGUMENTS: + class(fuel_type), intent(inout) :: this ! fuel class + real(r8), intent(in) :: sav_fuel(nfsc) ! surface area to volume ratio of all fuel types [/cm] + real(r8), intent(in) :: drying_ratio ! drying ratio + class(fire_weather), intent(in) :: fireWeatherClass ! fireWeatherClass + + real(r8) :: moisture(nfsc) ! fuel moisture [m3/m3] + real(r8) :: moisture_of_extinction(nfsc) ! fuel moisture of extinction [m3/m3] + integer :: i ! looping index + + if (this%total_loading > nearzero) then + ! calculate fuel moisture [m3/m3] for each fuel class depending on what + ! fire weather class is in use + select type (fireWeatherClass) + class is (nesterov_index) + call CalculateFuelMoistureNesterov(sav_fuel, drying_ratio, & + fireWeatherClass%fire_weather_index, moisture) + class default + write(fates_log(), *) 'Unknown fire weather class selected.' + write(fates_log(), *) 'Choose a different fire weather class or upate this subroutine.' + call endrun(msg=errMsg( __FILE__, __LINE__)) + end select + + ! calculate moisture of extinction and fuel effective moisture + do i = 1, nfsc + moisture_of_extinction(i) = MoistureOfExtinction(sav_fuel(i)) + this%effective_moisture(i) = moisture(i)/moisture_of_extinction(i) + end do + + ! average fuel moisture across all fuel types except trunks [m3/m3] + this%average_moisture = sum(this%frac_loading(1:nfsc_notrunks)*moisture(1:nfsc_notrunks)) + + ! calculate average moisture of extinction across all fuel types except trunks + this%MEF = sum(this%frac_loading(1:nfsc_notrunks)*moisture_of_extinction(1:nfsc_notrunks)) + else + this%effective_moisture(1:nfsc) = 0.0_r8 + this%average_moisture = 0.0_r8 + this%MEF = 0.0_r8 + end if + + end subroutine UpdateFuelMoisture + + !------------------------------------------------------------------------------------- + + subroutine CalculateFuelMoistureNesterov(sav_fuel, drying_ratio, NI, moisture) + ! + ! DESCRIPTION: + ! Updates fuel moisture + ! ARGUMENTS: + real(r8), intent(in) :: sav_fuel(nfsc) ! surface area to volume ratio of all fuel types [/cm] + real(r8), intent(in) :: drying_ratio ! drying ratio + real(r8), intent(in) :: NI ! Nesterov Index + real(r8), intent(out) :: moisture(nfsc) ! moisture of litter [m3/m3] + + ! LOCALS + integer :: i ! looping index + real(r8) :: alpha_FMC ! intermediate variable for calculating fuel moisture + + do i = 1, nfsc + if (i == fuel_classes%live_grass()) then + ! live grass moisture is a function of SAV and changes via Nesterov Index + ! along the same relationship as the 1 hour fuels + ! live grass has same SAV as dead grass, but retains more moisture with this calculation + alpha_FMC = sav_fuel(fuel_classes%twigs())/drying_ratio + else + alpha_FMC = sav_fuel(i)/drying_ratio + end if + ! Equation + moisture(i) = exp(-1.0_r8*alpha_FMC*NI) + end do + + end subroutine CalculateFuelMoistureNesterov + + !------------------------------------------------------------------------------------- + + real(r8) function MoistureOfExtinction(sav) + ! + ! DESCRIPTION: + ! Calculates moisture of extinction based on input surface area to volume ratio + + ! MEF (moisure of extinction) depends on compactness of fuel, depth, particle size, wind, slope + ! Eqn here is eqn 27 from Peterson and Ryan (1986) "Modeling Postfire Conifer Mortality for Long-Range Planning" + ! MEF: pine needles=0.30 (text near EQ 28 Rothermal 1972) + ! Table II-1 NFFL mixed fuels models from Rothermal 1983 Gen. Tech. Rep. INT-143 + ! MEF: short grass=0.12,tall grass=0.25,chaparral=0.20,closed timber litter=0.30,hardwood litter=0.25 + ! Thonicke 2010 SAV values propagated thru P&R86 eqn below gives MEF:tw=0.355, sb=0.44, lb=0.525, tr=0.63, dg=0.248, lg=0.248 + ! Lasslop 2014 Table 1 MEF PFT level:grass=0.2,shrubs=0.3,TropEverGrnTree=0.2,TropDecid Tree=0.3, Extra-trop Tree=0.3 + + ! ARGUMENTS: + real(r8), intent(in) :: sav ! fuel surface area to volume ratio [/cm] + + ! CONSTANTS: + real(r8), parameter :: MEF_a = 0.524_r8 + real(r8), parameter :: MEF_b = 0.066_r8 + + if (sav <= nearzero) then + MoistureOfExtinction = 0.0_r8 + else + MoistureOfExtinction = MEF_a - MEF_b*log(sav) + end if + + end function MoistureOfExtinction + + !------------------------------------------------------------------------------------- + + subroutine AverageBulkDensity(this, bulk_density) + ! DESCRIPTION: + ! Calculates average bulk density (not including trunks) + + ! ARGUMENTS: + class(fuel_type), intent(inout) :: this ! fuel class + real(r8), intent(in) :: bulk_density(nfsc) ! bulk density of all fuel types [kg/m2] + + if (this%total_loading > nearzero) then + this%bulk_density = sum(this%frac_loading(1:nfsc_notrunks)*bulk_density(1:nfsc_notrunks)) + else + this%bulk_density = 0.0_r8 + end if + + end subroutine AverageBulkDensity + + !------------------------------------------------------------------------------------- + + subroutine AverageSAV(this, sav_fuel) + ! DESCRIPTION: + ! Calculates average surface area to volume ratio (not including trunks) + + ! ARGUMENTS: + class(fuel_type), intent(inout) :: this ! fuel class + real(r8), intent(in) :: sav_fuel(nfsc) ! surface area to volume ratio of all fuel types [/cm] + + if (this%total_loading > nearzero) then + this%SAV = sum(this%frac_loading(1:nfsc_notrunks)*sav_fuel(1:nfsc_notrunks)) + else + this%SAV = 0.0_r8 + end if + + end subroutine AverageSAV + + !------------------------------------------------------------------------------------- + end module FatesFuelMod \ No newline at end of file diff --git a/fire/SFFireWeatherMod.F90 b/fire/SFFireWeatherMod.F90 index 11b8602fa4..3191b460b1 100644 --- a/fire/SFFireWeatherMod.F90 +++ b/fire/SFFireWeatherMod.F90 @@ -40,7 +40,8 @@ subroutine update_fire_weather(this, temp_C, precip, rh, wind) real(r8), intent(in) :: wind end subroutine update_fire_weather - end interface + + end interface contains diff --git a/fire/SFMainMod.F90 b/fire/SFMainMod.F90 index 21e9083e89..5c535a6e9f 100644 --- a/fire/SFMainMod.F90 +++ b/fire/SFMainMod.F90 @@ -5,50 +5,39 @@ module SFMainMod ! Code originally developed by Allan Spessa & Rosie Fisher as part of the NERC-QUEST project. ! ============================================================================ - use FatesConstantsMod , only : r8 => fates_r8 - use FatesConstantsMod , only : itrue, ifalse - use FatesConstantsMod , only : pi_const - use FatesConstantsMod , only : nocomp_bareground - use FatesInterfaceTypesMod, only : hlm_masterproc ! 1= master process, 0=not master process - use FatesGlobals , only : fates_log + use FatesConstantsMod, only : r8 => fates_r8 + use FatesConstantsMod, only : itrue, ifalse + use FatesConstantsMod, only : pi_const + use FatesConstantsMod, only : nocomp_bareground + use FatesGlobals, only : fates_log + use FatesInterfaceTypesMod, only : hlm_masterproc use FatesInterfaceTypesMod, only : hlm_spitfire_mode use FatesInterfaceTypesMod, only : hlm_sf_nofire_def use FatesInterfaceTypesMod, only : hlm_sf_scalar_lightning_def use FatesInterfaceTypesMod, only : hlm_sf_successful_ignitions_def use FatesInterfaceTypesMod, only : hlm_sf_anthro_ignitions_def use FatesInterfaceTypesMod, only : bc_in_type - - use EDPftvarcon , only : EDPftvarcon_inst - use PRTParametersMod , only : prt_params - - use PRTGenericMod , only : element_pos - use EDtypesMod , only : ed_site_type - use FatesPatchMod , only : fates_patch_type - use FatesCohortMod , only : fates_cohort_type - use EDtypesMod , only : AREA - use FatesLitterMod , only : DL_SF - use FatesLitterMod , only : TW_SF - use FatesLitterMod , only : LB_SF - use FatesLitterMod , only : LG_SF - use FatesLitterMod , only : ncwd - use FatesLitterMod , only : NFSC - use FatesLitterMod , only : TR_SF - use FatesLitterMod , only : litter_type - + use EDPftvarcon, only : EDPftvarcon_inst + use PRTParametersMod, only : prt_params + use PRTGenericMod, only : element_pos + use EDtypesMod, only : ed_site_type + use FatesPatchMod, only : fates_patch_type + use FatesCohortMod, only : fates_cohort_type + use EDtypesMod, only : AREA + use FatesLitterMod, only : litter_type + use FatesFuelClassesMod, only : nfsc use PRTGenericMod, only : leaf_organ use PRTGenericMod, only : carbon12_element - use PRTGenericMod, only : leaf_organ use PRTGenericMod, only : sapw_organ use PRTGenericMod, only : struct_organ use FatesInterfaceTypesMod, only : numpft use FatesAllometryMod, only : CrownDepth - use FatesConstantsMod, only : nearzero implicit none private public :: fire_model - public :: charecteristics_of_fuel + public :: UpdateFuelCharacteristics public :: rate_of_spread public :: ground_fuel_consumption public :: area_burnt_intensity @@ -57,9 +46,6 @@ module SFMainMod public :: cambial_damage_kill public :: post_fire_mortality - ! The following parameter represents one of the values of hlm_spitfire_mode - ! and more of these appear in subroutine area_burnt_intensity below - ! NB. The same parameters are set in /src/biogeochem/CNFireFactoryMod integer :: write_SF = ifalse ! for debugging logical :: debug = .false. ! for debugging @@ -89,7 +75,7 @@ subroutine fire_model(currentSite, bc_in) if (hlm_spitfire_mode > hlm_sf_nofire_def) then call UpdateFireWeather(currentSite, bc_in) - call charecteristics_of_fuel(currentSite) + call UpdateFuelCharacteristics(currentSite) call rate_of_spread(currentSite) call ground_fuel_consumption(currentSite) call area_burnt_intensity(currentSite, bc_in) @@ -108,6 +94,7 @@ subroutine UpdateFireWeather(currentSite, bc_in) ! DESCRIPTION: ! Updates the site's fire weather index and calculates effective windspeed based on ! vegetation characteristics + ! ! Currently we use tree and grass fraction averaged over whole grid (site) to ! prevent extreme divergence @@ -171,24 +158,19 @@ end subroutine UpdateFireWeather !-------------------------------------------------------------------------------------- - subroutine characteristics_of_fuel(currentSite) + subroutine UpdateFuelCharacteristics(currentSite) ! ! DESCRIPTION: - ! Updates the site's fuel characteristics + ! Updates fuel characteristics on each patch of the site use SFParamsMod, only : SF_val_drying_ratio, SF_val_SAV, SF_val_FBD ! ARGUMENTS: - type(ed_site_type), intent(in), target :: currentSite + type(ed_site_type), intent(in), target :: currentSite ! site object ! LOCALS: - type(fates_patch_type), pointer :: currentPatch ! FATES patch - type(litter_type), pointer :: litter ! pointer to patch litter class - real(r8) :: alpha_FMC(nfsc) ! relative fuel moisture adjusted per drying ratio - real(r8) :: fuel_moisture(nfsc) ! scaled moisture content of small litter fuels. - real(r8) :: MEF(nfsc) ! moisture extinction factor of fuels - - fuel_moisture(:) = 0.0_r8 + type(fates_patch_type), pointer :: currentPatch ! FATES patch + type(litter_type), pointer :: litter ! pointer to patch litter class currentPatch => currentSite%oldest_patch do while(associated(currentPatch)) @@ -200,7 +182,6 @@ subroutine characteristics_of_fuel(currentSite) ! update fuel loading [kgC/m2] litter => currentPatch%litter(element_pos(carbon12_element)) - call currentPatch%fuel%CalculateLoading(sum(litter%leaf_fines(:)), & litter%ag_cwd(1), litter%ag_cwd(2), litter%ag_cwd(3), litter%ag_cwd(4), & currentPatch%livegrass) @@ -209,111 +190,24 @@ subroutine characteristics_of_fuel(currentSite) call currentPatch%fuel%SumLoading() call currentPatch%fuel%CalculateFractionalLoading() - if (currentPatch%fuel%total_loading> 0.0) then + ! calculate fuel moisture [m3/m3] + call currentPatch%fuel%UpdateFuelMoisture(SF_val_SAV, SF_val_drying_ratio, & + currentSite%fireWeather) + + ! calculate geometric properties + call currentPatch%fuel%AverageBulkDensity(SF_val_FBD) + call currentPatch%fuel%AverageSAV(SF_val_SAV) - ! currentPatch%fuel_frac(dl_sf) = currentPatch%fuel%frac_loading(dl_sf) - ! currentPatch%fuel_frac(tw_sf:tr_sf) = currentPatch%fuel%frac_loading(tw_sf:tr_sf) - ! currentPatch%fuel_frac(lg_sf) = currentPatch%fuel%frac_loading(lg_sf) - - ! Fraction of fuel in litter classes - currentPatch%fuel_frac(dl_sf) = sum(litter%leaf_fines(:))/currentPatch%fuel%total_loading - currentPatch%fuel_frac(tw_sf:tr_sf) = litter%ag_cwd(:)/currentPatch%fuel%total_loading - currentPatch%fuel_frac(lg_sf) = currentPatch%livegrass/currentPatch%fuel%total_loading - - ! MEF (moisure of extinction) depends on compactness of fuel, depth, particle size, wind, slope - ! Eqn here is eqn 27 from Peterson and Ryan (1986) "Modeling Postfire Conifer Mortality for Long-Range Planning" - ! but lots of other approaches in use out there... - ! MEF: pine needles=0.30 (text near EQ 28 Rothermal 1972) - ! Table II-1 NFFL mixed fuels models from Rothermal 1983 Gen. Tech. Rep. INT-143 - ! MEF: short grass=0.12,tall grass=0.25,chaparral=0.20,closed timber litter=0.30,hardwood litter=0.25 - ! Thonicke 2010 SAV values propagated thru P&R86 eqn below gives MEF:tw=0.355, sb=0.44, lb=0.525, tr=0.63, dg=0.248, lg=0.248 - ! Lasslop 2014 Table 1 MEF PFT level:grass=0.2,shrubs=0.3,TropEverGrnTree=0.2,TropDecid Tree=0.3, Extra-trop Tree=0.3 - MEF(1:nfsc) = 0.524_r8 - 0.066_r8 * log(SF_val_SAV(1:nfsc)) - - ! Correct averaging for the fact that we are not using the trunks pool for fire ROS and intensity (5) - ! Consumption of fuel in trunk pool does not influence fire ROS or intensity (Pyne 1996) - if ( (1.0_r8-currentPatch%fuel_frac(tr_sf)) .gt. nearzero ) then - currentPatch%fuel_bulkd = currentPatch%fuel_bulkd * (1.0_r8/(1.0_r8-currentPatch%fuel_frac(tr_sf))) - currentPatch%fuel_sav = currentPatch%fuel_sav * (1.0_r8/(1.0_r8-currentPatch%fuel_frac(tr_sf))) - currentPatch%fuel_mef = currentPatch%fuel_mef * (1.0_r8/(1.0_r8-currentPatch%fuel_frac(tr_sf))) - currentPatch%fuel_eff_moist = currentPatch%fuel_eff_moist * (1.0_r8/(1.0_r8-currentPatch%fuel_frac(tr_sf))) - else - ! somehow the fuel is all trunk. put dummy values from large branches so as not to break things later in code. - currentPatch%fuel_bulkd = SF_val_FBD(lb_sf) - currentPatch%fuel_sav = SF_val_SAV(lb_sf) - currentPatch%fuel_mef = MEF(lb_sf) - currentPatch%fuel_eff_moist = fuel_moisture(lb_sf) - endif - - ! Pass litter moisture into the fuel burning routine (all fuels: twigs,s branch,l branch,trunk,dead leaves,live grass) - ! (wo/me term in Thonicke et al. 2010) - currentPatch%litter_moisture(tw_sf:lb_sf) = fuel_moisture(tw_sf:lb_sf)/MEF(tw_sf:lb_sf) - currentPatch%litter_moisture(tr_sf) = fuel_moisture(tr_sf)/MEF(tr_sf) - currentPatch%litter_moisture(dl_sf) = fuel_moisture(dl_sf)/MEF(dl_sf) - currentPatch%litter_moisture(lg_sf) = fuel_moisture(lg_sf)/MEF(lg_sf) - - fuel_moisture(tw_sf:dl_sf) = exp(-1.0_r8 * alpha_FMC(tw_sf:dl_sf) * & - currentSite%fireWeather%fire_weather_index) - - ! live grass moisture is a function of SAV and changes via Nesterov Index - ! along the same relationship as the 1 hour fuels (live grass has same SAV as dead grass, - ! but retains more moisture with this calculation.) - fuel_moisture(lg_sf) = exp(-1.0_r8 * ((SF_val_SAV(tw_sf)/SF_val_drying_ratio) * & - currentSite%fireWeather%fire_weather_index)) - - ! Average properties over the first three litter pools (twigs, s branches, l branches) - currentPatch%fuel_bulkd = sum(currentPatch%fuel_frac(tw_sf:lb_sf) * SF_val_FBD(tw_sf:lb_sf)) - currentPatch%fuel_sav = sum(currentPatch%fuel_frac(tw_sf:lb_sf) * SF_val_SAV(tw_sf:lb_sf)) - currentPatch%fuel_mef = sum(currentPatch%fuel_frac(tw_sf:lb_sf) * MEF(tw_sf:lb_sf)) - currentPatch%fuel_eff_moist = sum(currentPatch%fuel_frac(tw_sf:lb_sf) * fuel_moisture(tw_sf:lb_sf)) - - ! Add on properties of dead leaves and live grass pools (5 & 6) - currentPatch%fuel_bulkd = currentPatch%fuel_bulkd + sum(currentPatch%fuel_frac(dl_sf:lg_sf) * SF_val_FBD(dl_sf:lg_sf)) - currentPatch%fuel_sav = currentPatch%fuel_sav + sum(currentPatch%fuel_frac(dl_sf:lg_sf) * SF_val_SAV(dl_sf:lg_sf)) - currentPatch%fuel_mef = currentPatch%fuel_mef + sum(currentPatch%fuel_frac(dl_sf:lg_sf) * MEF(dl_sf:lg_sf)) - currentPatch%fuel_eff_moist = currentPatch%fuel_eff_moist+ sum(currentPatch%fuel_frac(dl_sf:lg_sf) * fuel_moisture(dl_sf:lg_sf)) - - ! Correct averaging for the fact that we are not using the trunks pool for fire ROS and intensity (5) - ! Consumption of fuel in trunk pool does not influence fire ROS or intensity (Pyne 1996) - currentPatch%fuel_bulkd = currentPatch%fuel_bulkd * (1.0_r8/(1.0_r8-currentPatch%fuel_frac(tr_sf))) - currentPatch%fuel_sav = currentPatch%fuel_sav * (1.0_r8/(1.0_r8-currentPatch%fuel_frac(tr_sf))) - currentPatch%fuel_mef = currentPatch%fuel_mef * (1.0_r8/(1.0_r8-currentPatch%fuel_frac(tr_sf))) - currentPatch%fuel_eff_moist = currentPatch%fuel_eff_moist * (1.0_r8/(1.0_r8-currentPatch%fuel_frac(tr_sf))) - - ! Pass litter moisture into the fuel burning routine (all fuels: twigs,s branch,l branch,trunk,dead leaves,live grass) - ! (wo/me term in Thonicke et al. 2010) - currentPatch%litter_moisture(tw_sf:lb_sf) = fuel_moisture(tw_sf:lb_sf)/MEF(tw_sf:lb_sf) - currentPatch%litter_moisture(tr_sf) = fuel_moisture(tr_sf)/MEF(tr_sf) - currentPatch%litter_moisture(dl_sf) = fuel_moisture(dl_sf)/MEF(dl_sf) - currentPatch%litter_moisture(lg_sf) = fuel_moisture(lg_sf)/MEF(lg_sf) - - ! remove trunks from patch%sum_fuel because they should not be included in fire equations - ! NOTE: ACF will update this soon to be more clean/bug-proof - currentPatch%sum_fuel = currentPatch%sum_fuel - litter%ag_cwd(tr_sf) - - else - - currentPatch%fuel_sav = sum(SF_val_SAV(1:nfsc))/(nfsc) ! make average sav to avoid crashing code. - - ! FIX(SPM,032414) refactor...should not have 0 fuel unless everything is burnt - ! off. - currentPatch%fuel_eff_moist = 0.0000000001_r8 - currentPatch%fuel_bulkd = 0.0000000001_r8 - currentPatch%fuel_frac(:) = 0.0000000001_r8 - currentPatch%fuel_mef = 0.0000000001_r8 - currentPatch%fuel%total_loading = 0.0000000001_r8 - - end if end if currentPatch => currentPatch%younger end do - end subroutine characteristics_of_fuel + end subroutine UpdateFuelCharacteristics !-------------------------------------------------------------------------------------- - subroutine rate_of_spread ( currentSite ) + subroutine rate_of_spread (currentSite) !*****************************************************************. !Routine called daily from within ED within a site loop. !Returns the updated currentPatch%ROS_front value for each patch. @@ -353,42 +247,42 @@ subroutine rate_of_spread ( currentSite ) ! ----start spreading--- if ( hlm_masterproc == itrue .and.debug) write(fates_log(),*) & - 'SF - currentPatch%fuel_bulkd ',currentPatch%fuel_bulkd + 'SF - currentPatch%fuel%bulk_density ',currentPatch%currentPatch%fuel%bulk_density if ( hlm_masterproc == itrue .and.debug) write(fates_log(),*) & 'SF - SF_val_part_dens ',SF_val_part_dens ! beta = packing ratio (unitless) ! fraction of fuel array volume occupied by fuel or compactness of fuel bed - beta = currentPatch%fuel_bulkd / SF_val_part_dens + beta = currentPatch%fuel%bulk_density/SF_val_part_dens ! Equation A6 in Thonicke et al. 2010 ! packing ratio (unitless) - beta_op = 0.200395_r8 *(currentPatch%fuel_sav**(-0.8189_r8)) + beta_op = 0.200395_r8 *(currentPatch%fuel%SAV**(-0.8189_r8)) if ( hlm_masterproc == itrue .and.debug) write(fates_log(),*) 'SF - beta ',beta if ( hlm_masterproc == itrue .and.debug) write(fates_log(),*) 'SF - beta_op ',beta_op beta_ratio = beta/beta_op !unitless if(write_sf == itrue)then - if ( hlm_masterproc == itrue ) write(fates_log(),*) 'esf ',currentPatch%fuel_eff_moist + if ( hlm_masterproc == itrue ) write(fates_log(),*) 'esf ',currentPatch%fuel%average_moisture endif ! ---heat of pre-ignition--- ! Equation A4 in Thonicke et al. 2010 - ! Rothermal EQ12= 250 Btu/lb + 1116 Btu/lb * fuel_eff_moist + ! Rothermal EQ12= 250 Btu/lb + 1116 Btu/lb * average_moisture ! conversion of Rothermal (1972) EQ12 in BTU/lb to current kJ/kg ! q_ig in kJ/kg - q_ig = q_dry +2594.0_r8 * currentPatch%fuel_eff_moist + q_ig = q_dry +2594.0_r8 * currentPatch%fuel%average_moisture ! ---effective heating number--- ! Equation A3 in Thonicke et al. 2010. - eps = exp(-4.528_r8 / currentPatch%fuel_sav) + eps = exp(-4.528_r8 / currentPatch%fuel%SAV) ! Equation A7 in Thonicke et al. 2010 per eqn 49 from Rothermel 1972 - b = 0.15988_r8 * (currentPatch%fuel_sav**0.54_r8) + b = 0.15988_r8 * (currentPatch%fuel%SAV**0.54_r8) ! Equation A8 in Thonicke et al. 2010 per eqn 48 from Rothermel 1972 - c = 7.47_r8 * (exp(-0.8711_r8 * (currentPatch%fuel_sav**0.55_r8))) + c = 7.47_r8 * (exp(-0.8711_r8 * (currentPatch%fuel%SAV**0.55_r8))) ! Equation A9 in Thonicke et al. 2010. (appears to have typo, using coefficient eqn.50 Rothermel 1972) - e = 0.715_r8 * (exp(-0.01094_r8 * currentPatch%fuel_sav)) + e = 0.715_r8 * (exp(-0.01094_r8 * currentPatch%fuel%SAV)) if (debug) then if ( hlm_masterproc == itrue .and.debug) write(fates_log(),*) 'SF - c ',c @@ -406,24 +300,24 @@ subroutine rate_of_spread ( currentSite ) ! ---propagating flux---- ! Equation A2 in Thonicke et al.2010 and Eq. 42 Rothermal 1972 ! xi (unitless) - xi = (exp((0.792_r8 + 3.7597_r8 * (currentPatch%fuel_sav**0.5_r8)) * (beta+0.1_r8))) / & - (192_r8+7.9095_r8 * currentPatch%fuel_sav) + xi = (exp((0.792_r8 + 3.7597_r8 * (currentPatch%fuel%SAV**0.5_r8)) * (beta+0.1_r8))) / & + (192_r8+7.9095_r8 * currentPatch%fuel%SAV) ! ---reaction intensity---- ! Equation in table A1 Thonicke et al. 2010. - a = 8.9033_r8 * (currentPatch%fuel_sav**(-0.7913_r8)) + a = 8.9033_r8 * (currentPatch%fuel%SAV**(-0.7913_r8)) a_beta = exp(a*(1.0_r8-beta_ratio)) !dummy variable for reaction_v_opt equation ! Equation in table A1 Thonicke et al. 2010. ! reaction_v_max and reaction_v_opt = reaction velocity in units of per min ! reaction_v_max = Equation 36 in Rothermal 1972 and Fig 12 - reaction_v_max = 1.0_r8 / (0.0591_r8 + 2.926_r8* (currentPatch%fuel_sav**(-1.5_r8))) + reaction_v_max = 1.0_r8 / (0.0591_r8 + 2.926_r8* (currentPatch%fuel%SAV**(-1.5_r8))) ! reaction_v_opt = Equation 38 in Rothermal 1972 and Fig 11 reaction_v_opt = reaction_v_max*(beta_ratio**a)*a_beta ! mw_weight = relative fuel moisture/fuel moisture of extinction ! average values for litter pools (dead leaves, twigs, small and large branches) plus grass - mw_weight = currentPatch%fuel_eff_moist/currentPatch%fuel_mef + mw_weight = currentPatch%currentPatch%fuel%average_moisture/currentPatch%fuel_mef ! Equation in table A1 Thonicke et al. 2010. ! moist_damp is unitless @@ -436,12 +330,11 @@ subroutine rate_of_spread ( currentSite ) ! write(fates_log(),*) 'ir',gamma_aptr,moist_damp,SF_val_fuel_energy,SF_val_miner_damp - if (((currentPatch%fuel_bulkd) <= 0.0_r8).or.(eps <= 0.0_r8).or.(q_ig <= 0.0_r8)) then + if (((currentPatch%fuel%bulk_density) <= 0.0_r8).or.(eps <= 0.0_r8).or.(q_ig <= 0.0_r8)) then currentPatch%ROS_front = 0.0_r8 else ! Equation 9. Thonicke et al. 2010. ! forward ROS in m/min - currentPatch%ROS_front = (ir*xi*(1.0_r8+phi_wind)) / (currentPatch%fuel_bulkd*eps*q_ig) - ! write(fates_log(),*) 'ros calcs',currentPatch%fuel_bulkd,ir,xi,eps,q_ig + currentPatch%ROS_front = (ir*xi*(1.0_r8+phi_wind)) / (currentPatch%fuel%bulk_density*eps*q_ig) endif ! Equation 10 in Thonicke et al. 2010 ! backward ROS from Can FBP System (1992) in m/min @@ -528,7 +421,7 @@ subroutine ground_fuel_consumption ( currentSite ) ! The /10 is to convert from kgC/m2 into gC/cm2, as in the Peterson and Ryan paper #Rosie,Jun 2013 do c = 1,nfsc - tau_b(c) = 39.4_r8 *(currentPatch%fuel_frac(c)*currentPatch%fuel%total_loading/0.45_r8/10._r8)* & + tau_b(c) = 39.4_r8 *(currentPatch%currentPatch%fuel%frac_loading(c)*currentPatch%fuel%total_loading/0.45_r8/10._r8)* & (1.0_r8-((1.0_r8-currentPatch%burnt_frac_litter(c))**0.5_r8)) enddo tau_b(tr_sf) = 0.0_r8 diff --git a/fire/SFNesterovMod.F90 b/fire/SFNesterovMod.F90 index 392aad5b03..ab0338933c 100644 --- a/fire/SFNesterovMod.F90 +++ b/fire/SFNesterovMod.F90 @@ -15,7 +15,7 @@ module SFNesterovMod procedure, public :: Init => init_nesterov_fire_weather procedure, public :: UpdateIndex => update_nesterov_index - + end type nesterov_index real(r8), parameter :: min_precip_thresh = 3.0_r8 ! threshold for precipitation above which to zero NI [mm/day] @@ -103,5 +103,6 @@ real(r8) function dewpoint(temp_C, rh) dewpoint = (dewpoint_b*yipsolon)/(dewpoint_a - yipsolon) end function dewpoint - + + !------------------------------------------------------------------------------------- end module SFNesterovMod \ No newline at end of file diff --git a/fire/SFParamsMod.F90 b/fire/SFParamsMod.F90 index c2dbc3fcd6..c51b14cdb8 100644 --- a/fire/SFParamsMod.F90 +++ b/fire/SFParamsMod.F90 @@ -2,17 +2,17 @@ module SFParamsMod ! ! module that deals with reading the SF parameter file ! - use FatesConstantsMod , only: r8 => fates_r8 - use FatesConstantsMod , only: fates_check_param_set - use FatesLitterMod , only: NFSC - use FatesLitterMod , only: ncwd + use FatesConstantsMod, only : r8 => fates_r8 + use FatesConstantsMod, only : fates_check_param_set + use FatesFuelClassesMod, only : nfsc + use FatesLitterMod, only : ncwd use FatesParametersInterface, only : param_string_length - use FatesGlobals, only : fates_log - use FatesGlobals, only : endrun => fates_endrun - use shr_log_mod , only : errMsg => shr_log_errMsg + use FatesGlobals, only : fates_log + use FatesGlobals, only : endrun => fates_endrun + use shr_log_mod, only : errMsg => shr_log_errMsg implicit none - private ! Modules are private by default + private save ! @@ -58,18 +58,13 @@ module SFParamsMod character(len=param_string_length),parameter :: SF_name_mid_moisture_Coeff = "fates_fire_mid_moisture_Coeff" character(len=param_string_length),parameter :: SF_name_mid_moisture_Slope = "fates_fire_mid_moisture_Slope" - character(len=*), parameter, private :: sourcefile = & - __FILE__ - - - real(r8), parameter,private :: min_fire_threshold = 0.0001_r8 ! The minimum reasonable fire intensity threshold [kW/m] - + character(len=*), parameter, private :: sourcefile = __FILE__ + real(r8), parameter, private :: min_fire_threshold = 0.0001_r8 ! The minimum reasonable fire intensity threshold [kW/m] public :: SpitFireRegisterParams public :: SpitFireReceiveParams public :: SpitFireCheckParams - contains ! ===================================================================================== diff --git a/main/EDInitMod.F90 b/main/EDInitMod.F90 index 7cc280176e..418bd3b3ea 100644 --- a/main/EDInitMod.F90 +++ b/main/EDInitMod.F90 @@ -821,7 +821,6 @@ subroutine init_patches( nsites, sites, bc_in) currentPatch%fuel_mef = 0._r8 currentPatch%ros_front = 0._r8 currentPatch%tau_l = 0._r8 - currentPatch%fuel_frac(:) = 0._r8 currentPatch%tfc_ros = 0._r8 currentPatch%fi = 0._r8 currentPatch%fire = 0 diff --git a/main/EDTypesMod.F90 b/main/EDTypesMod.F90 index dadfc88799..9c07120e99 100644 --- a/main/EDTypesMod.F90 +++ b/main/EDTypesMod.F90 @@ -18,7 +18,7 @@ module EDTypesMod use PRTGenericMod, only : num_element_types use PRTGenericMod, only : carbon12_element use FatesLitterMod, only : litter_type - use FatesLitterMod, only : ncwd, NFSC + use FatesLitterMod, only : ncwd use FatesConstantsMod, only : days_per_year use FatesRunningMeanMod, only : rmean_type,rmean_arr_type use FatesConstantsMod, only : fates_unset_r8 diff --git a/main/FatesHistoryInterfaceMod.F90 b/main/FatesHistoryInterfaceMod.F90 index 66b9ee631b..e32c59122f 100644 --- a/main/FatesHistoryInterfaceMod.F90 +++ b/main/FatesHistoryInterfaceMod.F90 @@ -4189,13 +4189,13 @@ subroutine update_history_dyn2(this,nc,nsites,sites,bc_in) i_agefuel = get_agefuel_class_index(cpatch%age,i_fuel) hio_fuel_amount_age_fuel(io_si,i_agefuel) = hio_fuel_amount_age_fuel(io_si,i_agefuel) + & - cpatch%fuel_frac(i_fuel) * cpatch%fuel%total_loading * cpatch%area * AREA_INV + cpatch%fuel%frac_loading(i_fuel) * cpatch%fuel%total_loading * cpatch%area * AREA_INV hio_litter_moisture_si_fuel(io_si, i_fuel) = hio_litter_moisture_si_fuel(io_si, i_fuel) + & cpatch%litter_moisture(i_fuel) * cpatch%area * AREA_INV hio_fuel_amount_si_fuel(io_si, i_fuel) = hio_fuel_amount_si_fuel(io_si, i_fuel) + & - cpatch%fuel_frac(i_fuel) * cpatch%fuel%total_loading * cpatch%area * AREA_INV + cpatch%fuel%frac_loading(i_fuel) * cpatch%fuel%total_loading * cpatch%area * AREA_INV hio_burnt_frac_litter_si_fuel(io_si, i_fuel) = hio_burnt_frac_litter_si_fuel(io_si, i_fuel) + & cpatch%burnt_frac_litter(i_fuel) * cpatch%frac_burnt * cpatch%area * AREA_INV diff --git a/parameter_files/fates_params_default.cdl b/parameter_files/fates_params_default.cdl index 2a909ee340..36ef5220b0 100644 --- a/parameter_files/fates_params_default.cdl +++ b/parameter_files/fates_params_default.cdl @@ -1612,23 +1612,23 @@ data: 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 ; - fates_fire_FBD = 15.4, 16.8, 19.6, 999, 4, 4 ; + fates_fire_FBD = 15.4, 16.8, 19.6, 4, 4, 999 ; - fates_fire_low_moisture_Coeff = 1.12, 1.09, 0.98, 0.8, 1.15, 1.15 ; + fates_fire_low_moisture_Coeff = 1.12, 1.09, 0.98, 1.15, 1.15, 0.8 ; - fates_fire_low_moisture_Slope = 0.62, 0.72, 0.85, 0.8, 0.62, 0.62 ; + fates_fire_low_moisture_Slope = 0.62, 0.72, 0.85, 0.62, 0.62, 0.8 ; - fates_fire_mid_moisture = 0.72, 0.51, 0.38, 1, 0.8, 0.8 ; + fates_fire_mid_moisture = 0.72, 0.51, 0.38, 0.8, 0.8, 1 ; - fates_fire_mid_moisture_Coeff = 2.35, 1.47, 1.06, 0.8, 3.2, 3.2 ; + fates_fire_mid_moisture_Coeff = 2.35, 1.47, 1.06, 3.2, 3.2, 0.8 ; - fates_fire_mid_moisture_Slope = 2.35, 1.47, 1.06, 0.8, 3.2, 3.2 ; + fates_fire_mid_moisture_Slope = 2.35, 1.47, 1.06, 3.2, 3.2, 0.8 ; - fates_fire_min_moisture = 0.18, 0.12, 0, 0, 0.24, 0.24 ; + fates_fire_min_moisture = 0.18, 0.12, 0, 0.24, 0.24, 0 ; - fates_fire_SAV = 13, 3.58, 0.98, 0.2, 66, 66 ; + fates_fire_SAV = 13, 3.58, 0.98, 66, 66, 0.2 ; - fates_frag_maxdecomp = 0.52, 0.383, 0.383, 0.19, 1, 999 ; + fates_frag_maxdecomp = 0.52, 0.383, 0.383, 1, 999, 0.19 ; fates_frag_cwd_frac = 0.045, 0.075, 0.21, 0.67 ; diff --git a/unit_testing/allometry/FatesTestAllometry.F90 b/unit_testing/allometry/FatesTestAllometry.F90 index 857ceff37f..d4e9d28e7a 100644 --- a/unit_testing/allometry/FatesTestAllometry.F90 +++ b/unit_testing/allometry/FatesTestAllometry.F90 @@ -6,6 +6,7 @@ program FatesTestAllometry use FatesAllometryMod, only : bfineroot, bstore_allom, bdead_allom use PRTParametersMod, only : prt_params use FatesUnitTestParamReaderMod, only : fates_unit_test_param_reader + use FatesArgumentUtils, only : command_line_arg implicit none @@ -13,10 +14,8 @@ program FatesTestAllometry type(fates_unit_test_param_reader) :: param_reader ! param reader instance character(len=:), allocatable :: param_file ! input parameter file integer :: numpft ! number of pfts (from parameter file) - integer :: arglen ! length of command line argument integer :: i, j ! looping indices integer :: numdbh ! size of dbh array - integer :: nargs ! number of command line arguments real(r8), allocatable :: dbh(:) ! diameter at breast height [cm] real(r8), allocatable :: height(:, :) ! height [m] real(r8), allocatable :: bagw(:, :) ! aboveground woody biomass [kgC] @@ -76,22 +75,13 @@ end subroutine WriteAllometryData end interface - ! get parameter file from command-line argument - nargs = command_argument_count() - if (nargs /= 1) then - write(*, '(a, i2, a)') "Incorrect number of arguments: ", nargs, ". Should be 1." - stop - else - call get_command_argument(1, length=arglen) - allocate(character(arglen) :: param_file) - call get_command_argument(1, value=param_file) - endif + ! read in parameter file name from command line + param_file = command_line_arg(1) ! read in parameter file call param_reader%Init(param_file) call param_reader%RetrieveParameters() - ! determine sizes of arrays numpft = size(prt_params%wood_density, dim=1) numdbh = int((max_dbh - min_dbh)/dbh_inc + 1) diff --git a/unit_testing/allometry/allometry_plotting.py b/unit_testing/allometry/allometry_plotting.py index caa6cc1069..bf08386b02 100644 --- a/unit_testing/allometry/allometry_plotting.py +++ b/unit_testing/allometry/allometry_plotting.py @@ -7,46 +7,7 @@ import xarray as xr import matplotlib import matplotlib.pyplot as plt -from utils import get_color_palette, round_up - -def blank_plot(x_max, x_min, y_max, y_min, draw_horizontal_lines=False): - """Generate a blank plot with set attributes - - Args: - x_max (float): maximum x value - x_min (float): minimum x value - y_max (float): maximum y value - y_min (float): minimum y value - draw_horizontal_lines (bool, optional): whether or not to draw horizontal - lines across plot. Defaults to False. - """ - - plt.figure(figsize=(7, 5)) - axis = plt.subplot(111) - axis.spines["top"].set_visible(False) - axis.spines["bottom"].set_visible(False) - axis.spines["right"].set_visible(False) - axis.spines["left"].set_visible(False) - - axis.get_xaxis().tick_bottom() - axis.get_yaxis().tick_left() - - plt.xlim(0.0, x_max) - plt.ylim(0.0, y_max) - - plt.yticks(fontsize=10) - plt.xticks(fontsize=10) - - if draw_horizontal_lines: - inc = (int(y_max) - y_min)/20 - for i in range(0, 20): - plt.plot(range(math.floor(x_min), math.ceil(x_max)), - [0.0 + i*inc] * len(range(math.floor(x_min), math.ceil(x_max))), - "--", lw=0.5, color="black", alpha=0.3) - - plt.tick_params(bottom=False, top=False, left=False, right=False) - - return plt +from utils import get_color_palette, round_up, blank_plot def plot_allometry_var(data, varname, units, save_fig, plot_dir=None): """Plot an allometry variable diff --git a/unit_testing/fire/CMakeLists.txt b/unit_testing/fire/CMakeLists.txt index add38c83af..b6f0dd3f2f 100644 --- a/unit_testing/fire/CMakeLists.txt +++ b/unit_testing/fire/CMakeLists.txt @@ -1,6 +1,7 @@ set(fire_test_sources FatesTestFuel.F90 - FatesTestFireMod.F90) + FatesTestFireMod.F90 + SyntheticFuelTypes.F90) set(NETCDF_C_DIR ${NETCDF_C_PATH}) set(NETCDF_FORTRAN_DIR ${NETCDF_F_PATH}) diff --git a/unit_testing/fire/FatesTestFireMod.F90 b/unit_testing/fire/FatesTestFireMod.F90 index 3e09578a79..03316f09be 100644 --- a/unit_testing/fire/FatesTestFireMod.F90 +++ b/unit_testing/fire/FatesTestFireMod.F90 @@ -4,196 +4,226 @@ module FatesTestFireMod ! Module to support testing the FATES SPIFTIRE model ! - use FatesConstantsMod, only : r8 => fates_r8 - use EDTypesMod, only : ed_site_type - use FatesPatchMod, only : fates_patch_type - use SFNesterovMod, only : nesterov_index + use FatesConstantsMod, only : r8 => fates_r8 + use EDTypesMod, only : ed_site_type + use FatesPatchMod, only : fates_patch_type + use SFNesterovMod, only : nesterov_index + use FatesUnitTestIOMod, only : OpenNCFile, GetVar, CloseNCFile, RegisterNCDims + use FatesUnitTestIOMod, only : RegisterVar, EndNCDef, WriteVar + use FatesUnitTestIOMod, only : type_double, type_int + use FatesFuelClassesMod, only : nfsc, nfsc_notrunks + use SyntheticFuelTypes, only : fuel_types_array_class + use SFParamsMod, only : SF_val_CWD_frac + use FatesFuelMod, only : fuel_type implicit none private - public :: SetUpSite + public :: SetUpFuel, ReadDatmData, WriteFireData contains !===================================================================================== - subroutine SetUpSite(site) + subroutine SetUpFuel(fuel, fuel_models, fuel_model_index) ! ! DESCRIPTION: - ! Sets up site, patch, litter, and fuel - ! This only sets up the stuff we actually need for these subroutines + ! Sets up fuel loading ! ! ARGUMENTS: - type(ed_site_type), target :: site ! site object - + type(fuel_type), intent(inout) :: fuel ! fuel object + type(fuel_types_array_class), intent(in) :: fuel_models ! array of fuel models + integer, intent(in) :: fuel_model_index ! fuel model index + ! LOCALS: - type(fates_patch_type), pointer :: patch + integer :: i ! position of fuel model in array + real(r8) :: leaf_litter ! leaf litter [kg/m2] + real(r8) :: twig_litter ! twig litter [kg/m2] + real(r8) :: small_branch_litter ! small branch litter [kg/m2] + real(r8) :: large_branch_litter ! large branch litter [kg/m2] + real(r8) :: grass_litter ! grass litter [kg/m2] + + + ! get fuel model position in array + i = fuel_models%FuelModelPosition(fuel_model_index) + + ! fuel model data + leaf_litter = fuel_models%fuel_types(i)%hr1_loading + twig_litter = fuel_models%fuel_types(i)%hr10_loading + + ! small vs. large branches based on input parameter file + small_branch_litter = fuel_models%fuel_types(i)%hr100_loading*SF_val_CWD_frac(2)/ & + (SF_val_CWD_frac(2) + SF_val_CWD_frac(3)) + large_branch_litter = fuel_models%fuel_types(i)%hr100_loading*SF_val_CWD_frac(3)/ & + (SF_val_CWD_frac(2) + SF_val_CWD_frac(3)) + + grass_litter = fuel_models%fuel_types(i)%live_herb_loading + + call fuel%CalculateLoading(leaf_litter, twig_litter, small_branch_litter, & + large_branch_litter, 0.0_r8, grass_litter) - ! set up fire weather class - allocate(nesterov_index :: site%fireWeather) - call site%fireWeather%Init() + end subroutine SetUpFuel + + !===================================================================================== + + subroutine ReadDatmData(nc_file, temp_degC, precip, rh, wind) + ! + ! DESCRIPTION: + ! Reads and returns DATM data + ! + + ! ARGUMENTS: + character(len=*), intent(in) :: nc_file ! netcdf file with DATM data + real(r8), allocatable, intent(out) :: temp_degC(:) ! daily air temperature [degC] + real(r8), allocatable, intent(out) :: precip(:) ! daily precipitation [mm] + real(r8), allocatable, intent(out) :: rh(:) ! daily relative humidity [%] + real(r8), allocatable, intent(out) :: wind(:) ! daily wind speed [m/s] - ! set up one patch - allocate(patch) - call patch%Init(2, 1) + ! LOCALS: + integer :: ncid ! netcdf file unit number - patch%patchno = 1 - patch%younger => null() - patch%older => null() + ! open file + call OpenNCFile(trim(nc_file), ncid, 'read') + + ! read in data + call GetVar(ncid, 'temp_degC', temp_degC) + call GetVar(ncid, 'precip', precip) + call GetVar(ncid, 'RH', rh) + call GetVar(ncid, 'wind', wind) - site%youngest_patch => patch - site%oldest_patch => patch + ! close file + call CloseNCFile(ncid) - end subroutine SetUpSite + end subroutine ReadDatmData !===================================================================================== - ! subroutine ReadDatmData(nc_file, temp_degC, precip, rh, wind) - ! ! - ! ! DESCRIPTION: - ! ! Reads and returns DATM data - ! ! + subroutine WriteFireData(out_file, nsteps, nfuelmods, temp_degC, precip, rh, NI, & + loading, frac_loading, total_loading, fuel_models) + ! + ! DESCRIPTION: + ! writes out data from the unit test + ! - ! ! ARGUMENTS: - ! character(len=*), intent(in) :: nc_file ! netcdf file with DATM data - ! real(r8), allocatable, intent(out) :: temp_degC(:) ! daily air temperature [degC] - ! real(r8), allocatable, intent(out) :: precip(:) ! daily precipitation [mm] - ! real(r8), allocatable, intent(out) :: rh(:) ! daily relative humidity [%] - ! real(r8), allocatable, intent(out) :: wind(:) ! daily wind speed [m/s] - - ! ! LOCALS: - ! integer :: ncid ! netcdf file unit number - - ! ! open file - ! call OpenNCFile(trim(nc_file), ncid, 'read') + ! ARGUMENTS: + character(len=*), intent(in) :: out_file + integer, intent(in) :: nsteps + integer, intent(in) :: nfuelmods + real(r8), intent(in) :: temp_degC(:) + real(r8), intent(in) :: precip(:) + real(r8), intent(in) :: rh(:) + real(r8), intent(in) :: NI(:) + real(r8), intent(in) :: loading(:,:) + real(r8), intent(in) :: frac_loading(:,:) + real(r8), intent(in) :: total_loading(:) + integer, intent(in) :: fuel_models(:) + + ! LOCALS: + integer, allocatable :: time_index(:) ! array of time index + integer :: ncid ! netcdf id + integer :: i ! looping index + character(len=20) :: dim_names(3) ! dimension names + integer :: dimIDs(3) ! dimension IDs + integer :: timeID, litterID, modID + integer :: tempID, precipID + integer :: rhID, NIID, loadingID + integer :: frac_loadingID, tot_loadingID - ! ! read in data - ! call GetVar(ncid, 'temp_degC', temp_degC) - ! call GetVar(ncid, 'precip', precip) - ! call GetVar(ncid, 'RH', rh) - ! call GetVar(ncid, 'wind', wind) + ! create pft indices + allocate(time_index(nsteps)) + do i = 1, nsteps + time_index(i) = i + end do - ! ! close file - ! call CloseNCFile(ncid) + ! dimension names + dim_names = [character(len=20) :: 'time', 'litter_class', 'fuel_model'] - ! end subroutine ReadDatmData + ! open file + call OpenNCFile(trim(out_file), ncid, 'readwrite') - !===================================================================================== + ! register dimensions + call RegisterNCDims(ncid, dim_names, (/nsteps, nfsc_notrunks, nfuelmods/), 3, dimIDs) - ! subroutine WriteFireData(out_file, nsteps, time_counter, temp_degC, precip, rh, NI, & - ! loading, moisture, av_moisture, ros) - ! ! - ! ! DESCRIPTION: - ! ! writes out data from the unit test - ! ! - - ! ! ARGUMENTS: - ! character(len=*), intent(in) :: out_file - ! integer, intent(in) :: nsteps - ! integer, intent(in) :: time_counter(:) - ! real(r8), intent(in) :: temp_degC(:) - ! real(r8), intent(in) :: precip(:) - ! real(r8), intent(in) :: rh(:) - ! real(r8), intent(in) :: NI(:) - ! real(r8), intent(in) :: loading(:) - ! real(r8), intent(in) :: moisture(:,:) - ! real(r8), intent(in) :: av_moisture(:) - ! real(r8), intent(in) :: ros(:) - - ! ! LOCALS: - ! integer :: ncid ! netcdf id - ! character(len=8) :: dim_names(2) ! dimension names - ! integer :: dimIDs(2) ! dimension IDs - ! integer :: timeID, litterID - ! integer :: tempID, precipID, rhID, NIID, loadingID, moistureID - ! integer :: av_moistureID, rosID - - ! ! dimension names - ! dim_names = [character(len=12) :: 'time', 'litter_class'] - - ! ! open file - ! call OpenNCFile(trim(out_file), ncid, 'readwrite') - - ! ! register dimensions - ! call RegisterNCDims(ncid, dim_names, (/nsteps, nfsc/), 2, dimIDs) - - ! ! register time - ! call RegisterVar1D(ncid, 'time', dimIDs(1), type_int, & - ! [character(len=20) :: 'time_origin', 'units', 'calendar', 'long_name'], & - ! [character(len=150) :: '2018-01-01 00:00:00', 'days since 2018-01-01 00:00:00', & - ! 'gregorian', 'time'], & - ! 4, timeID) - - ! ! register litter class - ! call RegisterVar1D(ncid, 'litter_class', dimIDs(2), type_int, & - ! [character(len=20) :: 'units', 'long_name'], & - ! [character(len=150) :: '', 'litter class'], 2, litterID) - - ! ! register temperature - ! call RegisterVar1D(ncid, 'temp_degC', dimIDs(1), type_double, & - ! [character(len=20) :: 'coordinates', 'units', 'long_name'], & - ! [character(len=150) :: 'time', 'degrees C', 'air temperature'], & - ! 3, tempID) - - ! ! register precipitation - ! call RegisterVar1D(ncid, 'precip', dimIDs(1), type_double, & - ! [character(len=20) :: 'coordinates', 'units', 'long_name'], & - ! [character(len=150) :: 'time', 'mm', 'precipitation'], & - ! 3, precipID) - - ! ! register relative humidity - ! call RegisterVar1D(ncid, 'RH', dimIDs(1), type_double, & - ! [character(len=20) :: 'coordinates', 'units', 'long_name'], & - ! [character(len=150) :: 'time', '%', 'relative humidity'], & - ! 3, rhID) - - ! ! register Nesterov Index - ! call RegisterVar1D(ncid, 'NI', dimIDs(1), type_double, & - ! [character(len=20) :: 'coordinates', 'units', 'long_name'], & - ! [character(len=150) :: 'time', '', 'Nesterov Index'], & - ! 3, NIID) - - ! ! register loading - ! call RegisterVar1D(ncid, 'loading', dimIDs(1), type_double, & - ! [character(len=20) :: 'coordinates', 'units', 'long_name'], & - ! [character(len=150) :: 'time', 'kg m-2', 'fuel loading'], & - ! 3, loadingID) - - ! ! register moisture - ! call RegisterVar2D(ncid, 'fuel_moisture', dimIDs(1:2), type_double, & - ! [character(len=20) :: 'coordinates', 'units', 'long_name'], & - ! [character(len=150) :: 'time litter_class', 'm3 m-3', 'fuel moisture'], & - ! 3, moistureID) - - ! ! register average moisture - ! call RegisterVar1D(ncid, 'average_moisture', dimIDs(1), type_double, & - ! [character(len=20) :: 'coordinates', 'units', 'long_name'], & - ! [character(len=150) :: 'time', 'm3 m-3', 'average fuel moisture'], & - ! 3, av_moistureID) - - ! ! register ROS - ! call RegisterVar1D(ncid, 'ros', dimIDs(1), type_double, & - ! [character(len=20) :: 'coordinates', 'units', 'long_name'], & - ! [character(len=150) :: 'time', 'm min-1', 'rate of forward spread'], & - ! 3, rosID) - - ! call Check(NF90_ENDDEF(ncid)) - - ! call Check(NF90_PUT_VAR(ncid, timeID, time_counter)) - ! call Check(NF90_PUT_VAR(ncid, litterID, (/tw_sf, sb_sf, lb_sf, tr_sf, dl_sf, lg_sf/))) - ! call Check(NF90_PUT_VAR(ncid, tempID, temp_degC(:))) - ! call Check(NF90_PUT_VAR(ncid, precipID, precip(:))) - ! call Check(NF90_PUT_VAR(ncid, rhID, rh(:))) - ! call Check(NF90_PUT_VAR(ncid, NIID, NI(:))) - ! call Check(NF90_PUT_VAR(ncid, loadingID, loading(:))) - ! call Check(NF90_PUT_VAR(ncid, moistureID, moisture(:,:))) - ! call Check(NF90_PUT_VAR(ncid, av_moistureID, av_moisture(:))) - ! call Check(NF90_PUT_VAR(ncid, rosID, ros(:))) + ! first register dimension variables + + ! register time + call RegisterVar(ncid, 'time', dimIDs(1:1), type_int, & + [character(len=20) :: 'time_origin', 'units', 'calendar', 'long_name'], & + [character(len=150) :: '2018-01-01 00:00:00', 'days since 2018-01-01 00:00:00', & + 'gregorian', 'time'], & + 4, timeID) + + ! register litter class + call RegisterVar(ncid, 'litter_class', dimIDs(2:2), type_int, & + [character(len=20) :: 'units', 'long_name'], & + [character(len=150) :: '', 'fuel class'], 2, litterID) + + ! register fuel models + call RegisterVar(ncid, 'fuel_model', dimIDs(3:3), type_int, & + [character(len=20) :: 'units', 'long_name'], & + [character(len=150) :: '', 'fuel model index'], 2, modID) + + ! then register actual variables + + ! register temperature + call RegisterVar(ncid, 'temp_degC', dimIDs(1:1), type_double, & + [character(len=20) :: 'coordinates', 'units', 'long_name'], & + [character(len=150) :: 'time', 'degrees C', 'air temperature'], & + 3, tempID) + + ! register precipitation + call RegisterVar(ncid, 'precip', dimIDs(1:1), type_double, & + [character(len=20) :: 'coordinates', 'units', 'long_name'], & + [character(len=150) :: 'time', 'mm', 'precipitation'], & + 3, precipID) + + ! register relative humidity + call RegisterVar(ncid, 'RH', dimIDs(1:1), type_double, & + [character(len=20) :: 'coordinates', 'units', 'long_name'], & + [character(len=150) :: 'time', '%', 'relative humidity'], & + 3, rhID) + + ! register Nesterov Index + call RegisterVar(ncid, 'NI', dimIDs(1:1), type_double, & + [character(len=20) :: 'coordinates', 'units', 'long_name'], & + [character(len=150) :: 'time', '', 'Nesterov Index'], & + 3, NIID) + + ! register fuel loading + call RegisterVar(ncid, 'fuel_loading', dimIDs(2:3), type_double, & + [character(len=20) :: 'coordinates', 'units', 'long_name'], & + [character(len=150) :: 'litter_class fuel_model', 'kgC m-2', 'fuel loading'], & + 3, loadingID) + + ! register fractional fuel loading + call RegisterVar(ncid, 'frac_loading', dimIDs(2:3), type_double, & + [character(len=20) :: 'coordinates', 'units', 'long_name'], & + [character(len=150) :: 'litter_class fuel_model', '', 'fractional loading'], & + 3, frac_loadingID) + + ! register total fuel loading + call RegisterVar(ncid, 'total_loading', dimIDs(3:3), type_double, & + [character(len=20) :: 'coordinates', 'units', 'long_name'], & + [character(len=150) :: 'fuel_model', 'kgC m-2', 'total loading'], & + 3, tot_loadingID) + + ! finish defining variables + call EndNCDef(ncid) + + call WriteVar(ncid, timeID, time_index) + call WriteVar(ncid, litterID, (/1, 2, 3, 4, 5/)) + call WriteVar(ncid, modID, fuel_models) + call WriteVar(ncid, tempID, temp_degC(:)) + call WriteVar(ncid, precipID, precip(:)) + call WriteVar(ncid, rhID, rh(:)) + call WriteVar(ncid, NIID, NI(:)) + call WriteVar(ncid, loadingID, loading(:,:)) + call WriteVar(ncid, frac_loadingID, frac_loading(:,:)) + call WriteVar(ncid, tot_loadingID, total_loading(:)) - ! call CloseNCFile(ncid) + call CloseNCFile(ncid) - ! end subroutine WriteFireData + end subroutine WriteFireData end module FatesTestFireMod \ No newline at end of file diff --git a/unit_testing/fire/FatesTestFuel.F90 b/unit_testing/fire/FatesTestFuel.F90 index 430873c904..745e5db2e3 100644 --- a/unit_testing/fire/FatesTestFuel.F90 +++ b/unit_testing/fire/FatesTestFuel.F90 @@ -1,9 +1,95 @@ program FatesTestFuel - use FatesTestFireMod, only : SetUpSite + use FatesConstantsMod, only : r8 => fates_r8 + use EDTypesMod, only : ed_site_type + use FatesTestFireMod, only : SetUpFuel, ReadDatmData, WriteFireData + use FatesArgumentUtils, only : command_line_arg + use FatesUnitTestParamReaderMod, only : fates_unit_test_param_reader + use SyntheticFuelTypes, only : fuel_types_array_class + use SFFireWeatherMod, only : fire_weather + use SFNesterovMod, only : nesterov_index + use FatesFuelMod, only : fuel_type + use FatesFuelClassesMod, only : nfsc, nfsc_notrunks + + implicit none + + ! LOCALS: + type(fates_unit_test_param_reader) :: param_reader ! param reader instance + type(fuel_types_array_class) :: fuel_types_array ! array of fuel models + class(fire_weather), pointer :: fireWeather ! fire weather object + type(fuel_type), allocatable :: fuel(:) ! fuel objects + character(len=:), allocatable :: param_file ! input parameter file + character(len=:), allocatable :: datm_file ! input DATM driver file + real(r8), allocatable :: temp_degC(:) ! daily air temperature [degC] + real(r8), allocatable :: precip(:) ! daily precipitation [mm] + real(r8), allocatable :: rh(:) ! daily relative humidity [%] + real(r8), allocatable :: wind(:) ! daily wind speed [m/s] + real(r8), allocatable :: NI(:) ! Nesterov index + real(r8), allocatable :: fuel_loading(:,:) ! fuel loading [kgC/m2] + real(r8), allocatable :: total_loading(:) ! total fuel loading [kgC/m2] + real(r8), allocatable :: frac_loading(:,:) ! fractional fuel loading [0-1] + integer :: i ! looping index + integer :: num_fuel_models ! number of fuel models to test + + ! CONSTANTS: + integer, parameter :: n_days = 365 ! number of days to run simulation + character(len=*), parameter :: out_file = 'fuel_out.nc' ! output file + + ! fuel models to test + integer, parameter, dimension(3) :: fuel_models = (/102, 183, 164/) + + ! number of fuel models to test + num_fuel_models = size(fuel_models) + + ! allocate arrays + allocate(temp_degC(n_days)) + allocate(precip(n_days)) + allocate(rh(n_days)) + allocate(wind(n_days)) + allocate(NI(n_days)) + allocate(fuel_loading(nfsc_notrunks, num_fuel_models)) + allocate(frac_loading(nfsc_notrunks, num_fuel_models)) + allocate(total_loading(num_fuel_models)) + + ! read in parameter file name and DATM file from command line + param_file = command_line_arg(1) + datm_file = command_line_arg(2) + + ! read in parameter file + call param_reader%Init(param_file) + call param_reader%RetrieveParameters() + + ! read in DATM data + call ReadDatmData(datm_file, temp_degC, precip, rh, wind) + + ! set up fire weather class + allocate(nesterov_index :: fireWeather) + call fireWeather%Init() + + ! set up fuel objects and calculate loading + allocate(fuel(num_fuel_models)) + call fuel_types_array%GetFuelModels() + do i = 1, num_fuel_models + + ! uses data from fuel_models to initialize fuel + call SetUpFuel(fuel(i), fuel_types_array, fuel_models(i)) + + ! sum up fuel and calculate loading + call fuel(i)%SumLoading() + call fuel(i)%CalculateFractionalLoading() + fuel_loading(:,i) = fuel(i)%loading(:) + total_loading(i) = fuel(i)%total_loading + frac_loading(:,i) = fuel(i)%frac_loading(:) + end do + + ! run on time steps + do i = 1, n_days + call fireWeather%UpdateIndex(temp_degC(i), precip(i), rh(i), wind(i)) + NI(i) = fireWeather%fire_weather_index + end do + + ! write out data + call WriteFireData(out_file, n_days, num_fuel_models, temp_degC, precip, rh, NI, & + fuel_loading, frac_loading, total_loading, fuel_models) -implicit none - -print *, 'hello fates fire' - end program FatesTestFuel \ No newline at end of file diff --git a/unit_testing/fire/SyntheticFuelClass.F90 b/unit_testing/fire/SyntheticFuelTypes.F90 similarity index 74% rename from unit_testing/fire/SyntheticFuelClass.F90 rename to unit_testing/fire/SyntheticFuelTypes.F90 index 48c86420b7..7e62209820 100644 --- a/unit_testing/fire/SyntheticFuelClass.F90 +++ b/unit_testing/fire/SyntheticFuelTypes.F90 @@ -1,12 +1,14 @@ module SyntheticFuelTypes - !use FatesConstantsMod, only : r8 => fates_r8 + use FatesConstantsMod, only : r8 => fates_r8 implicit none private - integer, parameter :: chunk_size = 10 - integer, parameter :: r8 = selected_real_kind(12) + integer, parameter :: chunk_size = 10 + real(r8), parameter :: ustons_to_kg = 907.185_r8 + real(r8), parameter :: acres_to_m2 = 4046.86_r8 + real(r8), parameter :: ft_to_m = 0.3048_r8 ! holds data for fake fuel models that can be used for functional ! testing of the FATES fire model @@ -18,12 +20,12 @@ module SyntheticFuelTypes character(len=5) :: fuel_model_code ! carrier plus fuel model character(len=100) :: fuel_model_name ! long name of fuel model real(r8) :: wind_adj_factor ! wind adjustment factor - real(r8) :: hr1_loading ! fuel loading for 1 hour fuels - real(r8) :: hr10_loading ! fuel loading for 10 hour fuels - real(r8) :: hr100_loading ! fuel loading for 100 hour fuels - real(r8) :: live_herb_loading ! fuel loading for live herbacious fuels - real(r8) :: live_woody_loading ! fuel loading for live woody fuels - real(r8) :: fuel_depth ! fuel bed depth + real(r8) :: hr1_loading ! fuel loading for 1 hour fuels [kg/m2] + real(r8) :: hr10_loading ! fuel loading for 10 hour fuels [kg/m2] + real(r8) :: hr100_loading ! fuel loading for 100 hour fuels [kg/m2] + real(r8) :: live_herb_loading ! fuel loading for live herbacious fuels [kg/m2] + real(r8) :: live_woody_loading ! fuel loading for live woody fuels [kg/m2] + real(r8) :: fuel_depth ! fuel bed depth [m] contains procedure :: InitFuelModel @@ -42,6 +44,7 @@ module SyntheticFuelTypes procedure :: AddFuelModel procedure :: GetFuelModels + procedure :: FuelModelPosition end type fuel_types_array_class @@ -57,6 +60,8 @@ subroutine InitFuelModel(this, fuel_model_index, carrier, fuel_model_name, ! Initializes the fuel model with input characteristics ! Also converts units as needed ! + ! NOTE THE UNITS ON INPUTS + ! ! ARGUMENTS: class(synthetic_fuel_type), intent(inout) :: this @@ -75,12 +80,12 @@ subroutine InitFuelModel(this, fuel_model_index, carrier, fuel_model_name, this%carrier = carrier this%fuel_model_name = fuel_model_name this%wind_adj_factor = wind_adj_factor - this%hr1_loading = hr1_loading - this%hr10_loading = hr10_loading - this%hr100_loading = hr100_loading - this%live_herb_loading = live_herb_loading - this%live_woody_loading = live_woody_loading - this%fuel_depth = fuel_depth + this%hr1_loading = hr1_loading*ustons_to_kg/acres_to_m2*0.45_r8 ! convert to kgC/m2 + this%hr10_loading = hr10_loading*ustons_to_kg/acres_to_m2*0.45_r8 ! convert to kgC/m2 + this%hr100_loading = hr100_loading*ustons_to_kg/acres_to_m2*0.45_r8 ! convert to kgC/m2 + this%live_herb_loading = live_herb_loading*ustons_to_kg/acres_to_m2*0.45_r8 ! convert to kgC/m2 + this%live_woody_loading = live_woody_loading*ustons_to_kg/acres_to_m2*0.45_r8 ! convert to kgC/m2 + this%fuel_depth = fuel_depth*ft_to_m ! convert to m end subroutine InitFuelModel @@ -93,6 +98,8 @@ subroutine AddFuelModel(this, fuel_model_index, carrier, fuel_model_name, ! DESCRIPTION: ! Adds a fuel model to the dynamic array ! + ! NOTE THE UNITS ON INPUTS + ! ! ARGUMENTS: class(fuel_types_array_class), intent(inout) :: this ! array of fuel models @@ -139,6 +146,32 @@ end subroutine AddFuelModel ! -------------------------------------------------------------------------------------- + integer function FuelModelPosition(this, fuel_model_index) + ! + ! DESCRIPTION: + ! Returns the index of a desired fuel model + ! + + ! ARGUMENTS: + class(fuel_types_array_class), intent(in) :: this ! array of fuel models + integer, intent(in) :: fuel_model_index ! desired fuel model index + + ! LOCALS: + integer :: i ! looping index + + do i = 1, this%num_fuel_types + if (this%fuel_types(i)%fuel_model_index == fuel_model_index) then + FuelModelPosition = i + return + end if + end do + write(*, '(a, i2, a)') "Cannot find the fuel model index ", fuel_model_index, "." + stop + + end function FuelModelPosition + + ! -------------------------------------------------------------------------------------- + subroutine GetFuelModels(this) ! ! DESCRIPTION: @@ -161,10 +194,18 @@ subroutine GetFuelModels(this) wind_adj_factor=0.31_r8, hr1_loading=0.1_r8, hr10_loading=0.0_r8, hr100_loading=0.0_r8, & live_herb_loading=0.3_r8, live_woody_loading=0.0_r8, fuel_depth=0.4_r8) - call this%AddFuelModel(fuel_model_index=102, carrier='GR', fuel_model_name='low load dry climate grass', & - wind_adj_factor=0.36_r8, hr1_loading=0.1_r8, hr10_loading=0.0_r8, hr100_loading=0.0_r8, & + call this%AddFuelModel(fuel_model_index=102, carrier='GR', fuel_model_name='low load dry climate grass', & + wind_adj_factor=0.36_r8, hr1_loading=0.1_r8, hr10_loading=0.0_r8, hr100_loading=0.0_r8, & live_herb_loading=1.0_r8, live_woody_loading=0.0_r8, fuel_depth=1.0_r8) + call this%AddFuelModel(fuel_model_index=183, carrier='TL', fuel_model_name='moderate load conifer litter', & + wind_adj_factor=0.29_r8, hr1_loading=0.5_r8, hr10_loading=2.2_r8, hr100_loading=2.8_r8, & + live_herb_loading=0.0_r8, live_woody_loading=0.0_r8, fuel_depth=0.3_r8) + + call this%AddFuelModel(fuel_model_index=164, carrier='TU', fuel_model_name='dwarf conifer with understory', & + wind_adj_factor=0.32_r8, hr1_loading=4.5_r8, hr10_loading=0.0_r8, hr100_loading=0.0_r8, & + live_herb_loading=0.0_r8, live_woody_loading=2.0_r8, fuel_depth=0.5_r8) + end subroutine GetFuelModels ! -------------------------------------------------------------------------------------- diff --git a/unit_testing/fire/fuel_plotting.py b/unit_testing/fire/fuel_plotting.py new file mode 100644 index 0000000000..7476e3f190 --- /dev/null +++ b/unit_testing/fire/fuel_plotting.py @@ -0,0 +1,60 @@ +"""Utility functions for fuel functional unit tests +""" +import os +import math +import pandas as pd +import numpy as np +import xarray as xr +import matplotlib +import matplotlib.pyplot as plt + +def plot_fuel_dat(run_dir, out_file, save_figs, plot_dir): + """Plot output associated with fuel tests + + Args: + run_dir (str): run directory + out_file (str): output file + save_figs (bool): whether or not to save the figures + plot_dir (str): plot directory + """ + + fuel_dat = xr.open_dataset(os.path.join(run_dir, out_file)) + + plot_NI_dat(fuel_dat, save_figs, plot_dir) + plot_barchart(fuel_dat, 'fuel_loading', 'Fuel loading', 'kgC m$^{-2}$', save_figs, plot_dir) + plot_barchart(fuel_dat, 'frac_loading', 'Fractional fuel loading', '0-1', save_figs, plot_dir) + +def plot_barchart(fuel_dat, var, varname, units, save_figs, plot_dir): + litter_classes = ['twigs', 'small branches', 'large branches', 'dead leaves', 'live grass'] + + fuel_models = ['Fuel model ' + str(m) for m in np.unique(fuel_dat.fuel_model)] + data_dict = {'{}'.format(lc): fuel_dat.isel(litter_class=i)[var].values for i, lc in enumerate(litter_classes)} + + fig, ax = plt.subplots() + bottom = np.zeros(len(fuel_models)) + for litter_class, dat in data_dict.items(): + p = ax.bar(fuel_models, dat, 0.5, label=litter_class, bottom=bottom) + bottom += dat + box = ax.get_position() + ax.set_position([box.x0, box.y0, box.width * 0.75, box.height]) + plt.ylabel(f'{varname} ({units})', fontsize=11) + plt.legend(loc='center left', bbox_to_anchor=(1, 0.5)) + + +def plot_NI_dat(fuel_dat, save_figs, plot_dir): + """Plot output for Nesterov index + + Args: + fuel_dat (Xarray Dataset): output fuel data + save_figs (bool): whether or not to save the figures + plot_dir (str): plot directory + """ + + plt.figure() + fuel_dat.NI.plot() + plt.xlabel('Time', fontsize=11) + plt.ylabel('Nesterov Index', fontsize=11) + + if save_figs: + fig_name = os.path.join(plot_dir, "Nesterov_plot.png") + plt.savefig(fig_name) \ No newline at end of file diff --git a/unit_testing/run_fates_tests.py b/unit_testing/run_fates_tests.py index 3ce90c1e89..3bb6d94fdb 100755 --- a/unit_testing/run_fates_tests.py +++ b/unit_testing/run_fates_tests.py @@ -34,6 +34,7 @@ from utils import copy_file, create_nc_file from allometry.allometry_plotting import plot_allometry_dat from math_utils.math_plotting import plot_quadratic_dat +from fire.fuel_plotting import plot_fuel_dat add_cime_lib_to_path() @@ -72,11 +73,11 @@ "fuel": { "test_dir": "fates_fuel_test", "test_exe": "FATES_fuel_exe", - "out_file": None, + "out_file": 'fuel_out.nc', "has_unit_test": False, - "use_param_file": False, - "other_args": [], - "plotting_function": None + "use_param_file": True, + "other_args": ['/Users/afoster/Documents/ncar/CTSM/src/fates/BONA_datm.nc'], + "plotting_function": plot_fuel_dat } } diff --git a/unit_testing/unit_test_shr/CMakeLists.txt b/unit_testing/unit_test_shr/CMakeLists.txt index 8cf4eb69c0..c295bdaf64 100644 --- a/unit_testing/unit_test_shr/CMakeLists.txt +++ b/unit_testing/unit_test_shr/CMakeLists.txt @@ -1,6 +1,7 @@ list(APPEND fates_sources FatesUnitTestParamReaderMod.F90 FatesUnitTestIOMod.F90 + FatesArgumentUtils.F90 ) sourcelist_to_parent(fates_sources) \ No newline at end of file diff --git a/unit_testing/unit_test_shr/FatesArgumentUtils.F90 b/unit_testing/unit_test_shr/FatesArgumentUtils.F90 new file mode 100644 index 0000000000..18c3ee6103 --- /dev/null +++ b/unit_testing/unit_test_shr/FatesArgumentUtils.F90 @@ -0,0 +1,36 @@ +module FatesArgumentUtils + + implicit none + private + + public :: command_line_arg + + contains + + function command_line_arg(arg_position) + ! DESCRIPTION: + ! Retrieves a single argument from the command line at a given position + + ! ARGUMENTS: + integer, intent(in) :: arg_position ! argument position + character(len=:), allocatable :: command_line_arg ! command argument + + ! LOCALS: + integer :: n_args ! number of arguments received + integer :: arglen ! argument length + + ! get total number of arguments + n_args = command_argument_count() + + if (n_args < arg_position) then + write(*, '(a, i2, a, i2)') "Incorrect number of arguments: ", n_args, ". Should be at least", arg_position, "." + stop + else + call get_command_argument(arg_position, length=arglen) + allocate(character(arglen) :: command_line_arg) + call get_command_argument(arg_position, value=command_line_arg) + endif + + end function command_line_arg + +end module FatesArgumentUtils \ No newline at end of file diff --git a/unit_testing/utils.py b/unit_testing/utils.py index aa6079757d..bc05353362 100644 --- a/unit_testing/utils.py +++ b/unit_testing/utils.py @@ -3,6 +3,7 @@ import math import os +import matplotlib.pyplot as plt from path_utils import add_cime_lib_to_path add_cime_lib_to_path() @@ -92,3 +93,42 @@ def get_color_palette(number): colors = [(red/255.0, green/255.0, blue/255.0) for red, green, blue in colors] return colors[:number] + +def blank_plot(x_max, x_min, y_max, y_min, draw_horizontal_lines=False): + """Generate a blank plot with set attributes + + Args: + x_max (float): maximum x value + x_min (float): minimum x value + y_max (float): maximum y value + y_min (float): minimum y value + draw_horizontal_lines (bool, optional): whether or not to draw horizontal + lines across plot. Defaults to False. + """ + + plt.figure(figsize=(7, 5)) + axis = plt.subplot(111) + axis.spines["top"].set_visible(False) + axis.spines["bottom"].set_visible(False) + axis.spines["right"].set_visible(False) + axis.spines["left"].set_visible(False) + + axis.get_xaxis().tick_bottom() + axis.get_yaxis().tick_left() + + plt.xlim(0.0, x_max) + plt.ylim(0.0, y_max) + + plt.yticks(fontsize=10) + plt.xticks(fontsize=10) + + if draw_horizontal_lines: + inc = (int(y_max) - y_min)/20 + for i in range(0, 20): + plt.plot(range(math.floor(x_min), math.ceil(x_max)), + [0.0 + i*inc] * len(range(math.floor(x_min), math.ceil(x_max))), + "--", lw=0.5, color="black", alpha=0.3) + + plt.tick_params(bottom=False, top=False, left=False, right=False) + + return plt From 7c84992ab9e22f56df70ff6f3ec64f7298f634a0 Mon Sep 17 00:00:00 2001 From: adrifoster Date: Mon, 6 May 2024 16:16:30 -0600 Subject: [PATCH 014/111] add files --- testing/CMakeLists.txt | 3 +- .../functional_testing/fire/CMakeLists.txt | 27 +++ .../fire/FatesTestFireMod.F90 | 229 ++++++++++++++++++ .../functional_testing/fire/FatesTestFuel.F90 | 95 ++++++++ .../fire/SyntheticFuelTypes.F90 | 213 ++++++++++++++++ .../functional_testing/fire/fuel_plotting.py | 60 +++++ testing/run_fates_tests.py | 1 + 7 files changed, 627 insertions(+), 1 deletion(-) create mode 100644 testing/functional_testing/fire/CMakeLists.txt create mode 100644 testing/functional_testing/fire/FatesTestFireMod.F90 create mode 100644 testing/functional_testing/fire/FatesTestFuel.F90 create mode 100644 testing/functional_testing/fire/SyntheticFuelTypes.F90 create mode 100644 testing/functional_testing/fire/fuel_plotting.py diff --git a/testing/CMakeLists.txt b/testing/CMakeLists.txt index ac3ac02d18..368639f23a 100644 --- a/testing/CMakeLists.txt +++ b/testing/CMakeLists.txt @@ -1,3 +1,4 @@ # This is where you add specific test directories add_subdirectory(functional_testing/allometry fates_allom_test) -add_subdirectory(functional_testing/math_utils fates_math_test) \ No newline at end of file +add_subdirectory(functional_testing/math_utils fates_math_test) +add_subdirectory(functional_testing/fire fates_fuel_test) \ No newline at end of file diff --git a/testing/functional_testing/fire/CMakeLists.txt b/testing/functional_testing/fire/CMakeLists.txt new file mode 100644 index 0000000000..b6f0dd3f2f --- /dev/null +++ b/testing/functional_testing/fire/CMakeLists.txt @@ -0,0 +1,27 @@ +set(fire_test_sources + FatesTestFuel.F90 + FatesTestFireMod.F90 + SyntheticFuelTypes.F90) + +set(NETCDF_C_DIR ${NETCDF_C_PATH}) +set(NETCDF_FORTRAN_DIR ${NETCDF_F_PATH}) + +FIND_PATH(NETCDFC_FOUND libnetcdf.a ${NETCDF_C_DIR}/lib) +FIND_PATH(NETCDFF_FOUND libnetcdff.a ${NETCDF_FORTRAN_DIR}/lib) + + +include_directories(${NETCDF_C_DIR}/include + ${NETCDF_FORTRAN_DIR}/include) + +link_directories(${NETCDF_C_DIR}/lib + ${NETCDF_FORTRAN_DIR}/lib + ${PFUNIT_TOP_DIR}/lib) + +add_executable(FATES_fuel_exe ${fire_test_sources}) + +target_link_libraries(FATES_fuel_exe + netcdf + netcdff + fates + csm_share + funit) \ No newline at end of file diff --git a/testing/functional_testing/fire/FatesTestFireMod.F90 b/testing/functional_testing/fire/FatesTestFireMod.F90 new file mode 100644 index 0000000000..03316f09be --- /dev/null +++ b/testing/functional_testing/fire/FatesTestFireMod.F90 @@ -0,0 +1,229 @@ +module FatesTestFireMod + ! + ! DESCRIPTION: + ! Module to support testing the FATES SPIFTIRE model + ! + + use FatesConstantsMod, only : r8 => fates_r8 + use EDTypesMod, only : ed_site_type + use FatesPatchMod, only : fates_patch_type + use SFNesterovMod, only : nesterov_index + use FatesUnitTestIOMod, only : OpenNCFile, GetVar, CloseNCFile, RegisterNCDims + use FatesUnitTestIOMod, only : RegisterVar, EndNCDef, WriteVar + use FatesUnitTestIOMod, only : type_double, type_int + use FatesFuelClassesMod, only : nfsc, nfsc_notrunks + use SyntheticFuelTypes, only : fuel_types_array_class + use SFParamsMod, only : SF_val_CWD_frac + use FatesFuelMod, only : fuel_type + + implicit none + private + + public :: SetUpFuel, ReadDatmData, WriteFireData + + contains + + !===================================================================================== + + subroutine SetUpFuel(fuel, fuel_models, fuel_model_index) + ! + ! DESCRIPTION: + ! Sets up fuel loading + ! + + ! ARGUMENTS: + type(fuel_type), intent(inout) :: fuel ! fuel object + type(fuel_types_array_class), intent(in) :: fuel_models ! array of fuel models + integer, intent(in) :: fuel_model_index ! fuel model index + + ! LOCALS: + integer :: i ! position of fuel model in array + real(r8) :: leaf_litter ! leaf litter [kg/m2] + real(r8) :: twig_litter ! twig litter [kg/m2] + real(r8) :: small_branch_litter ! small branch litter [kg/m2] + real(r8) :: large_branch_litter ! large branch litter [kg/m2] + real(r8) :: grass_litter ! grass litter [kg/m2] + + + ! get fuel model position in array + i = fuel_models%FuelModelPosition(fuel_model_index) + + ! fuel model data + leaf_litter = fuel_models%fuel_types(i)%hr1_loading + twig_litter = fuel_models%fuel_types(i)%hr10_loading + + ! small vs. large branches based on input parameter file + small_branch_litter = fuel_models%fuel_types(i)%hr100_loading*SF_val_CWD_frac(2)/ & + (SF_val_CWD_frac(2) + SF_val_CWD_frac(3)) + large_branch_litter = fuel_models%fuel_types(i)%hr100_loading*SF_val_CWD_frac(3)/ & + (SF_val_CWD_frac(2) + SF_val_CWD_frac(3)) + + grass_litter = fuel_models%fuel_types(i)%live_herb_loading + + call fuel%CalculateLoading(leaf_litter, twig_litter, small_branch_litter, & + large_branch_litter, 0.0_r8, grass_litter) + + end subroutine SetUpFuel + + !===================================================================================== + + subroutine ReadDatmData(nc_file, temp_degC, precip, rh, wind) + ! + ! DESCRIPTION: + ! Reads and returns DATM data + ! + + ! ARGUMENTS: + character(len=*), intent(in) :: nc_file ! netcdf file with DATM data + real(r8), allocatable, intent(out) :: temp_degC(:) ! daily air temperature [degC] + real(r8), allocatable, intent(out) :: precip(:) ! daily precipitation [mm] + real(r8), allocatable, intent(out) :: rh(:) ! daily relative humidity [%] + real(r8), allocatable, intent(out) :: wind(:) ! daily wind speed [m/s] + + ! LOCALS: + integer :: ncid ! netcdf file unit number + + ! open file + call OpenNCFile(trim(nc_file), ncid, 'read') + + ! read in data + call GetVar(ncid, 'temp_degC', temp_degC) + call GetVar(ncid, 'precip', precip) + call GetVar(ncid, 'RH', rh) + call GetVar(ncid, 'wind', wind) + + ! close file + call CloseNCFile(ncid) + + end subroutine ReadDatmData + + !===================================================================================== + + subroutine WriteFireData(out_file, nsteps, nfuelmods, temp_degC, precip, rh, NI, & + loading, frac_loading, total_loading, fuel_models) + ! + ! DESCRIPTION: + ! writes out data from the unit test + ! + + ! ARGUMENTS: + character(len=*), intent(in) :: out_file + integer, intent(in) :: nsteps + integer, intent(in) :: nfuelmods + real(r8), intent(in) :: temp_degC(:) + real(r8), intent(in) :: precip(:) + real(r8), intent(in) :: rh(:) + real(r8), intent(in) :: NI(:) + real(r8), intent(in) :: loading(:,:) + real(r8), intent(in) :: frac_loading(:,:) + real(r8), intent(in) :: total_loading(:) + integer, intent(in) :: fuel_models(:) + + ! LOCALS: + integer, allocatable :: time_index(:) ! array of time index + integer :: ncid ! netcdf id + integer :: i ! looping index + character(len=20) :: dim_names(3) ! dimension names + integer :: dimIDs(3) ! dimension IDs + integer :: timeID, litterID, modID + integer :: tempID, precipID + integer :: rhID, NIID, loadingID + integer :: frac_loadingID, tot_loadingID + + ! create pft indices + allocate(time_index(nsteps)) + do i = 1, nsteps + time_index(i) = i + end do + + ! dimension names + dim_names = [character(len=20) :: 'time', 'litter_class', 'fuel_model'] + + ! open file + call OpenNCFile(trim(out_file), ncid, 'readwrite') + + ! register dimensions + call RegisterNCDims(ncid, dim_names, (/nsteps, nfsc_notrunks, nfuelmods/), 3, dimIDs) + + ! first register dimension variables + + ! register time + call RegisterVar(ncid, 'time', dimIDs(1:1), type_int, & + [character(len=20) :: 'time_origin', 'units', 'calendar', 'long_name'], & + [character(len=150) :: '2018-01-01 00:00:00', 'days since 2018-01-01 00:00:00', & + 'gregorian', 'time'], & + 4, timeID) + + ! register litter class + call RegisterVar(ncid, 'litter_class', dimIDs(2:2), type_int, & + [character(len=20) :: 'units', 'long_name'], & + [character(len=150) :: '', 'fuel class'], 2, litterID) + + ! register fuel models + call RegisterVar(ncid, 'fuel_model', dimIDs(3:3), type_int, & + [character(len=20) :: 'units', 'long_name'], & + [character(len=150) :: '', 'fuel model index'], 2, modID) + + ! then register actual variables + + ! register temperature + call RegisterVar(ncid, 'temp_degC', dimIDs(1:1), type_double, & + [character(len=20) :: 'coordinates', 'units', 'long_name'], & + [character(len=150) :: 'time', 'degrees C', 'air temperature'], & + 3, tempID) + + ! register precipitation + call RegisterVar(ncid, 'precip', dimIDs(1:1), type_double, & + [character(len=20) :: 'coordinates', 'units', 'long_name'], & + [character(len=150) :: 'time', 'mm', 'precipitation'], & + 3, precipID) + + ! register relative humidity + call RegisterVar(ncid, 'RH', dimIDs(1:1), type_double, & + [character(len=20) :: 'coordinates', 'units', 'long_name'], & + [character(len=150) :: 'time', '%', 'relative humidity'], & + 3, rhID) + + ! register Nesterov Index + call RegisterVar(ncid, 'NI', dimIDs(1:1), type_double, & + [character(len=20) :: 'coordinates', 'units', 'long_name'], & + [character(len=150) :: 'time', '', 'Nesterov Index'], & + 3, NIID) + + ! register fuel loading + call RegisterVar(ncid, 'fuel_loading', dimIDs(2:3), type_double, & + [character(len=20) :: 'coordinates', 'units', 'long_name'], & + [character(len=150) :: 'litter_class fuel_model', 'kgC m-2', 'fuel loading'], & + 3, loadingID) + + ! register fractional fuel loading + call RegisterVar(ncid, 'frac_loading', dimIDs(2:3), type_double, & + [character(len=20) :: 'coordinates', 'units', 'long_name'], & + [character(len=150) :: 'litter_class fuel_model', '', 'fractional loading'], & + 3, frac_loadingID) + + ! register total fuel loading + call RegisterVar(ncid, 'total_loading', dimIDs(3:3), type_double, & + [character(len=20) :: 'coordinates', 'units', 'long_name'], & + [character(len=150) :: 'fuel_model', 'kgC m-2', 'total loading'], & + 3, tot_loadingID) + + ! finish defining variables + call EndNCDef(ncid) + + call WriteVar(ncid, timeID, time_index) + call WriteVar(ncid, litterID, (/1, 2, 3, 4, 5/)) + call WriteVar(ncid, modID, fuel_models) + call WriteVar(ncid, tempID, temp_degC(:)) + call WriteVar(ncid, precipID, precip(:)) + call WriteVar(ncid, rhID, rh(:)) + call WriteVar(ncid, NIID, NI(:)) + call WriteVar(ncid, loadingID, loading(:,:)) + call WriteVar(ncid, frac_loadingID, frac_loading(:,:)) + call WriteVar(ncid, tot_loadingID, total_loading(:)) + + call CloseNCFile(ncid) + + end subroutine WriteFireData + +end module FatesTestFireMod \ No newline at end of file diff --git a/testing/functional_testing/fire/FatesTestFuel.F90 b/testing/functional_testing/fire/FatesTestFuel.F90 new file mode 100644 index 0000000000..745e5db2e3 --- /dev/null +++ b/testing/functional_testing/fire/FatesTestFuel.F90 @@ -0,0 +1,95 @@ +program FatesTestFuel + + use FatesConstantsMod, only : r8 => fates_r8 + use EDTypesMod, only : ed_site_type + use FatesTestFireMod, only : SetUpFuel, ReadDatmData, WriteFireData + use FatesArgumentUtils, only : command_line_arg + use FatesUnitTestParamReaderMod, only : fates_unit_test_param_reader + use SyntheticFuelTypes, only : fuel_types_array_class + use SFFireWeatherMod, only : fire_weather + use SFNesterovMod, only : nesterov_index + use FatesFuelMod, only : fuel_type + use FatesFuelClassesMod, only : nfsc, nfsc_notrunks + + implicit none + + ! LOCALS: + type(fates_unit_test_param_reader) :: param_reader ! param reader instance + type(fuel_types_array_class) :: fuel_types_array ! array of fuel models + class(fire_weather), pointer :: fireWeather ! fire weather object + type(fuel_type), allocatable :: fuel(:) ! fuel objects + character(len=:), allocatable :: param_file ! input parameter file + character(len=:), allocatable :: datm_file ! input DATM driver file + real(r8), allocatable :: temp_degC(:) ! daily air temperature [degC] + real(r8), allocatable :: precip(:) ! daily precipitation [mm] + real(r8), allocatable :: rh(:) ! daily relative humidity [%] + real(r8), allocatable :: wind(:) ! daily wind speed [m/s] + real(r8), allocatable :: NI(:) ! Nesterov index + real(r8), allocatable :: fuel_loading(:,:) ! fuel loading [kgC/m2] + real(r8), allocatable :: total_loading(:) ! total fuel loading [kgC/m2] + real(r8), allocatable :: frac_loading(:,:) ! fractional fuel loading [0-1] + integer :: i ! looping index + integer :: num_fuel_models ! number of fuel models to test + + ! CONSTANTS: + integer, parameter :: n_days = 365 ! number of days to run simulation + character(len=*), parameter :: out_file = 'fuel_out.nc' ! output file + + ! fuel models to test + integer, parameter, dimension(3) :: fuel_models = (/102, 183, 164/) + + ! number of fuel models to test + num_fuel_models = size(fuel_models) + + ! allocate arrays + allocate(temp_degC(n_days)) + allocate(precip(n_days)) + allocate(rh(n_days)) + allocate(wind(n_days)) + allocate(NI(n_days)) + allocate(fuel_loading(nfsc_notrunks, num_fuel_models)) + allocate(frac_loading(nfsc_notrunks, num_fuel_models)) + allocate(total_loading(num_fuel_models)) + + ! read in parameter file name and DATM file from command line + param_file = command_line_arg(1) + datm_file = command_line_arg(2) + + ! read in parameter file + call param_reader%Init(param_file) + call param_reader%RetrieveParameters() + + ! read in DATM data + call ReadDatmData(datm_file, temp_degC, precip, rh, wind) + + ! set up fire weather class + allocate(nesterov_index :: fireWeather) + call fireWeather%Init() + + ! set up fuel objects and calculate loading + allocate(fuel(num_fuel_models)) + call fuel_types_array%GetFuelModels() + do i = 1, num_fuel_models + + ! uses data from fuel_models to initialize fuel + call SetUpFuel(fuel(i), fuel_types_array, fuel_models(i)) + + ! sum up fuel and calculate loading + call fuel(i)%SumLoading() + call fuel(i)%CalculateFractionalLoading() + fuel_loading(:,i) = fuel(i)%loading(:) + total_loading(i) = fuel(i)%total_loading + frac_loading(:,i) = fuel(i)%frac_loading(:) + end do + + ! run on time steps + do i = 1, n_days + call fireWeather%UpdateIndex(temp_degC(i), precip(i), rh(i), wind(i)) + NI(i) = fireWeather%fire_weather_index + end do + + ! write out data + call WriteFireData(out_file, n_days, num_fuel_models, temp_degC, precip, rh, NI, & + fuel_loading, frac_loading, total_loading, fuel_models) + +end program FatesTestFuel \ No newline at end of file diff --git a/testing/functional_testing/fire/SyntheticFuelTypes.F90 b/testing/functional_testing/fire/SyntheticFuelTypes.F90 new file mode 100644 index 0000000000..7e62209820 --- /dev/null +++ b/testing/functional_testing/fire/SyntheticFuelTypes.F90 @@ -0,0 +1,213 @@ +module SyntheticFuelTypes + + use FatesConstantsMod, only : r8 => fates_r8 + + implicit none + private + + integer, parameter :: chunk_size = 10 + real(r8), parameter :: ustons_to_kg = 907.185_r8 + real(r8), parameter :: acres_to_m2 = 4046.86_r8 + real(r8), parameter :: ft_to_m = 0.3048_r8 + + ! holds data for fake fuel models that can be used for functional + ! testing of the FATES fire model + ! these are taken from the fire behavior fuel models in Scott & Burgan 2005 + type, public :: synthetic_fuel_type + + integer :: fuel_model_index ! fuel model index + character(len=2) :: carrier ! carrier ('GR', 'GS', etc.) + character(len=5) :: fuel_model_code ! carrier plus fuel model + character(len=100) :: fuel_model_name ! long name of fuel model + real(r8) :: wind_adj_factor ! wind adjustment factor + real(r8) :: hr1_loading ! fuel loading for 1 hour fuels [kg/m2] + real(r8) :: hr10_loading ! fuel loading for 10 hour fuels [kg/m2] + real(r8) :: hr100_loading ! fuel loading for 100 hour fuels [kg/m2] + real(r8) :: live_herb_loading ! fuel loading for live herbacious fuels [kg/m2] + real(r8) :: live_woody_loading ! fuel loading for live woody fuels [kg/m2] + real(r8) :: fuel_depth ! fuel bed depth [m] + contains + + procedure :: InitFuelModel + + end type synthetic_fuel_type + + ! -------------------------------------------------------------------------------------- + + ! a class to just hold an array of these fuel models + type, public :: fuel_types_array_class + + type(synthetic_fuel_type), allocatable :: fuel_types(:) ! array of fuel models + integer :: num_fuel_types ! number of total fuel models + + contains + + procedure :: AddFuelModel + procedure :: GetFuelModels + procedure :: FuelModelPosition + + end type fuel_types_array_class + + ! -------------------------------------------------------------------------------------- + + contains + + subroutine InitFuelModel(this, fuel_model_index, carrier, fuel_model_name, & + wind_adj_factor, hr1_loading, hr10_loading, hr100_loading, live_herb_loading, & + live_woody_loading, fuel_depth) + ! + ! DESCRIPTION: + ! Initializes the fuel model with input characteristics + ! Also converts units as needed + ! + ! NOTE THE UNITS ON INPUTS + ! + + ! ARGUMENTS: + class(synthetic_fuel_type), intent(inout) :: this + integer, intent(in) :: fuel_model_index ! fuel model index + character(len=2), intent(in) :: carrier ! main carrier + character(len=*), intent(in) :: fuel_model_name ! fuel model long name + real(r8), intent(in) :: wind_adj_factor ! wind adjustment factor + real(r8), intent(in) :: hr1_loading ! loading for 1-hr fuels [tons/acre] + real(r8), intent(in) :: hr10_loading ! loading for 10-hr fuels [tons/acre] + real(r8), intent(in) :: hr100_loading ! loading for 100-hr fuels [tons/acre] + real(r8), intent(in) :: live_herb_loading ! loading for live herbacious fuels [tons/acre] + real(r8), intent(in) :: live_woody_loading ! loading for live woody fuels [tons/acre] + real(r8), intent(in) :: fuel_depth ! fuel bed depth [ft] + + this%fuel_model_index = fuel_model_index + this%carrier = carrier + this%fuel_model_name = fuel_model_name + this%wind_adj_factor = wind_adj_factor + this%hr1_loading = hr1_loading*ustons_to_kg/acres_to_m2*0.45_r8 ! convert to kgC/m2 + this%hr10_loading = hr10_loading*ustons_to_kg/acres_to_m2*0.45_r8 ! convert to kgC/m2 + this%hr100_loading = hr100_loading*ustons_to_kg/acres_to_m2*0.45_r8 ! convert to kgC/m2 + this%live_herb_loading = live_herb_loading*ustons_to_kg/acres_to_m2*0.45_r8 ! convert to kgC/m2 + this%live_woody_loading = live_woody_loading*ustons_to_kg/acres_to_m2*0.45_r8 ! convert to kgC/m2 + this%fuel_depth = fuel_depth*ft_to_m ! convert to m + + end subroutine InitFuelModel + + ! -------------------------------------------------------------------------------------- + + subroutine AddFuelModel(this, fuel_model_index, carrier, fuel_model_name, & + wind_adj_factor, hr1_loading, hr10_loading, hr100_loading, live_herb_loading, & + live_woody_loading, fuel_depth) + ! + ! DESCRIPTION: + ! Adds a fuel model to the dynamic array + ! + ! NOTE THE UNITS ON INPUTS + ! + + ! ARGUMENTS: + class(fuel_types_array_class), intent(inout) :: this ! array of fuel models + integer, intent(in) :: fuel_model_index ! fuel model index + character(len=2), intent(in) :: carrier ! main carrier + character(len=*), intent(in) :: fuel_model_name ! fuel model long name + real(r8), intent(in) :: wind_adj_factor ! wind adjustment factor + real(r8), intent(in) :: hr1_loading ! loading for 1-hr fuels [tons/acre] + real(r8), intent(in) :: hr10_loading ! loading for 10-hr fuels [tons/acre] + real(r8), intent(in) :: hr100_loading ! loading for 100-hr fuels [tons/acre] + real(r8), intent(in) :: live_herb_loading ! loading for live herbacious fuels [tons/acre] + real(r8), intent(in) :: live_woody_loading ! loading for live woody fuels [tons/acre] + real(r8), intent(in) :: fuel_depth ! fuel bed depth [ft] + + ! LOCALS: + type(synthetic_fuel_type) :: fuel_model ! fuel model + type(synthetic_fuel_type), allocatable :: temporary_array(:) ! temporary array to hold data while re-allocating + + ! first make sure we have enough space in the array + if (allocated(this%fuel_types)) then + ! already allocated to some size + if (this%num_fuel_types == size(this%fuel_types)) then + ! need to add more space + allocate(temporary_array(size(this%fuel_types) + chunk_size)) + temporary_array(1:size(this%fuel_types)) = this%fuel_types + call move_alloc(temporary_array, this%fuel_types) + end if + + this%num_fuel_types = this%num_fuel_types + 1 + + else + ! first element in array + allocate(this%fuel_types(chunk_size)) + this%num_fuel_types = 1 + end if + + call fuel_model%InitFuelModel(fuel_model_index, carrier, fuel_model_name, & + wind_adj_factor, hr1_loading, hr10_loading, hr100_loading, live_herb_loading, & + live_woody_loading, fuel_depth) + + this%fuel_types(this%num_fuel_types) = fuel_model + + end subroutine AddFuelModel + + ! -------------------------------------------------------------------------------------- + + integer function FuelModelPosition(this, fuel_model_index) + ! + ! DESCRIPTION: + ! Returns the index of a desired fuel model + ! + + ! ARGUMENTS: + class(fuel_types_array_class), intent(in) :: this ! array of fuel models + integer, intent(in) :: fuel_model_index ! desired fuel model index + + ! LOCALS: + integer :: i ! looping index + + do i = 1, this%num_fuel_types + if (this%fuel_types(i)%fuel_model_index == fuel_model_index) then + FuelModelPosition = i + return + end if + end do + write(*, '(a, i2, a)') "Cannot find the fuel model index ", fuel_model_index, "." + stop + + end function FuelModelPosition + + ! -------------------------------------------------------------------------------------- + + subroutine GetFuelModels(this) + ! + ! DESCRIPTION: + ! Returns an array of hard-coded fuel models + ! these are taken from the fire behavior fuel models in Scott & Burgan 2005 + ! + + ! ARGUMENTS: + class(fuel_types_array_class), intent(inout) :: this ! array of fuel models + + call this%AddFuelModel(fuel_model_index=1, carrier='GR', fuel_model_name='short grass', & + wind_adj_factor=0.36_r8, hr1_loading=0.7_r8, hr10_loading=0.0_r8, hr100_loading=0.0_r8, & + live_herb_loading=0.0_r8, live_woody_loading=0.0_r8, fuel_depth=1.0_r8) + + call this%AddFuelModel(fuel_model_index=2, carrier='GR', fuel_model_name='timber and grass understory', & + wind_adj_factor=0.36_r8, hr1_loading=2.0_r8, hr10_loading=1.0_r8, hr100_loading=0.5_r8, & + live_herb_loading=0.5_r8, live_woody_loading=0.0_r8, fuel_depth=1.0_r8) + + call this%AddFuelModel(fuel_model_index=101, carrier='GR', fuel_model_name='short, sparse dry climate grass', & + wind_adj_factor=0.31_r8, hr1_loading=0.1_r8, hr10_loading=0.0_r8, hr100_loading=0.0_r8, & + live_herb_loading=0.3_r8, live_woody_loading=0.0_r8, fuel_depth=0.4_r8) + + call this%AddFuelModel(fuel_model_index=102, carrier='GR', fuel_model_name='low load dry climate grass', & + wind_adj_factor=0.36_r8, hr1_loading=0.1_r8, hr10_loading=0.0_r8, hr100_loading=0.0_r8, & + live_herb_loading=1.0_r8, live_woody_loading=0.0_r8, fuel_depth=1.0_r8) + + call this%AddFuelModel(fuel_model_index=183, carrier='TL', fuel_model_name='moderate load conifer litter', & + wind_adj_factor=0.29_r8, hr1_loading=0.5_r8, hr10_loading=2.2_r8, hr100_loading=2.8_r8, & + live_herb_loading=0.0_r8, live_woody_loading=0.0_r8, fuel_depth=0.3_r8) + + call this%AddFuelModel(fuel_model_index=164, carrier='TU', fuel_model_name='dwarf conifer with understory', & + wind_adj_factor=0.32_r8, hr1_loading=4.5_r8, hr10_loading=0.0_r8, hr100_loading=0.0_r8, & + live_herb_loading=0.0_r8, live_woody_loading=2.0_r8, fuel_depth=0.5_r8) + + end subroutine GetFuelModels + + ! -------------------------------------------------------------------------------------- + +end module SyntheticFuelTypes \ No newline at end of file diff --git a/testing/functional_testing/fire/fuel_plotting.py b/testing/functional_testing/fire/fuel_plotting.py new file mode 100644 index 0000000000..7476e3f190 --- /dev/null +++ b/testing/functional_testing/fire/fuel_plotting.py @@ -0,0 +1,60 @@ +"""Utility functions for fuel functional unit tests +""" +import os +import math +import pandas as pd +import numpy as np +import xarray as xr +import matplotlib +import matplotlib.pyplot as plt + +def plot_fuel_dat(run_dir, out_file, save_figs, plot_dir): + """Plot output associated with fuel tests + + Args: + run_dir (str): run directory + out_file (str): output file + save_figs (bool): whether or not to save the figures + plot_dir (str): plot directory + """ + + fuel_dat = xr.open_dataset(os.path.join(run_dir, out_file)) + + plot_NI_dat(fuel_dat, save_figs, plot_dir) + plot_barchart(fuel_dat, 'fuel_loading', 'Fuel loading', 'kgC m$^{-2}$', save_figs, plot_dir) + plot_barchart(fuel_dat, 'frac_loading', 'Fractional fuel loading', '0-1', save_figs, plot_dir) + +def plot_barchart(fuel_dat, var, varname, units, save_figs, plot_dir): + litter_classes = ['twigs', 'small branches', 'large branches', 'dead leaves', 'live grass'] + + fuel_models = ['Fuel model ' + str(m) for m in np.unique(fuel_dat.fuel_model)] + data_dict = {'{}'.format(lc): fuel_dat.isel(litter_class=i)[var].values for i, lc in enumerate(litter_classes)} + + fig, ax = plt.subplots() + bottom = np.zeros(len(fuel_models)) + for litter_class, dat in data_dict.items(): + p = ax.bar(fuel_models, dat, 0.5, label=litter_class, bottom=bottom) + bottom += dat + box = ax.get_position() + ax.set_position([box.x0, box.y0, box.width * 0.75, box.height]) + plt.ylabel(f'{varname} ({units})', fontsize=11) + plt.legend(loc='center left', bbox_to_anchor=(1, 0.5)) + + +def plot_NI_dat(fuel_dat, save_figs, plot_dir): + """Plot output for Nesterov index + + Args: + fuel_dat (Xarray Dataset): output fuel data + save_figs (bool): whether or not to save the figures + plot_dir (str): plot directory + """ + + plt.figure() + fuel_dat.NI.plot() + plt.xlabel('Time', fontsize=11) + plt.ylabel('Nesterov Index', fontsize=11) + + if save_figs: + fig_name = os.path.join(plot_dir, "Nesterov_plot.png") + plt.savefig(fig_name) \ No newline at end of file diff --git a/testing/run_fates_tests.py b/testing/run_fates_tests.py index a2b9bd9c4d..a7aec41890 100755 --- a/testing/run_fates_tests.py +++ b/testing/run_fates_tests.py @@ -34,6 +34,7 @@ from utils import copy_file, create_nc_file from functional_testing.allometry.allometry_plotting import plot_allometry_dat from functional_testing.math_utils.math_plotting import plot_quadratic_dat +from functional_testing.fire.fuel_plotting import plot_fuel_dat add_cime_lib_to_path() From 7bf630b74fbd1963e9652554b9ec09b9b7b4cb3f Mon Sep 17 00:00:00 2001 From: adrifoster Date: Mon, 6 May 2024 18:21:00 -0600 Subject: [PATCH 015/111] get rid of patch-level variables moved into fuel class --- biogeochem/EDPatchDynamicsMod.F90 | 8 ++++---- fire/SFMainMod.F90 | 4 ++-- main/EDInitMod.F90 | 5 ----- main/FatesHistoryInterfaceMod.F90 | 10 +++++----- main/FatesRestartInterfaceMod.F90 | 4 ++-- 5 files changed, 13 insertions(+), 18 deletions(-) diff --git a/biogeochem/EDPatchDynamicsMod.F90 b/biogeochem/EDPatchDynamicsMod.F90 index 1860980e76..6116f73aa4 100644 --- a/biogeochem/EDPatchDynamicsMod.F90 +++ b/biogeochem/EDPatchDynamicsMod.F90 @@ -2752,13 +2752,13 @@ subroutine fuse_2_patches(csite, dp, rp) call rp%tveg_longterm%FuseRMean(dp%tveg_longterm,rp%area*inv_sum_area) - rp%fuel_eff_moist = (dp%fuel_eff_moist*dp%area + rp%fuel_eff_moist*rp%area) * inv_sum_area + rp%fuel%average_moisture = (dp%fuel%average_moisture*dp%area + rp%fuel%average_moisture*rp%area) * inv_sum_area rp%livegrass = (dp%livegrass*dp%area + rp%livegrass*rp%area) * inv_sum_area rp%fuel%total_loading = (dp%fuel%total_loading*dp%area + rp%fuel%total_loading*rp%area) * inv_sum_area rp%fuel%frac_loading = (dp%fuel%frac_loading(:)*dp%area + rp%fuel%frac_loading(:)*rp%area) * inv_sum_area - rp%fuel_bulkd = (dp%fuel_bulkd*dp%area + rp%fuel_bulkd*rp%area) * inv_sum_area - rp%fuel_sav = (dp%fuel_sav*dp%area + rp%fuel_sav*rp%area) * inv_sum_area - rp%fuel_mef = (dp%fuel_mef*dp%area + rp%fuel_mef*rp%area) * inv_sum_area + rp%fuel%bulk_density = (dp%fuel%bulk_density*dp%area + rp%fuel%bulk_density*rp%area) * inv_sum_area + rp%fuel%SAV = (dp%fuel%SAV*dp%area + rp%fuel%SAV*rp%area) * inv_sum_area + rp%fuel%MEF = (dp%fuel%MEF*dp%area + rp%fuel%MEF*rp%area) * inv_sum_area rp%ros_front = (dp%ros_front*dp%area + rp%ros_front*rp%area) * inv_sum_area rp%tau_l = (dp%tau_l*dp%area + rp%tau_l*rp%area) * inv_sum_area rp%tfc_ros = (dp%tfc_ros*dp%area + rp%tfc_ros*rp%area) * inv_sum_area diff --git a/fire/SFMainMod.F90 b/fire/SFMainMod.F90 index 5c535a6e9f..709058d946 100644 --- a/fire/SFMainMod.F90 +++ b/fire/SFMainMod.F90 @@ -317,7 +317,7 @@ subroutine rate_of_spread (currentSite) ! mw_weight = relative fuel moisture/fuel moisture of extinction ! average values for litter pools (dead leaves, twigs, small and large branches) plus grass - mw_weight = currentPatch%currentPatch%fuel%average_moisture/currentPatch%fuel_mef + mw_weight = currentPatch%currentPatch%fuel%average_moisture/currentPatch%fuel%MEF ! Equation in table A1 Thonicke et al. 2010. ! moist_damp is unitless @@ -377,7 +377,7 @@ subroutine ground_fuel_consumption ( currentSite ) ! Calculate fraction of litter is burnt for all classes. ! Equation B1 in Thonicke et al. 2010--- do c = 1, nfsc !work out the burnt fraction for all pools, even if those pools dont exist. - moist = currentPatch%litter_moisture(c) + moist = currentPatch%fuel%effective_moisture(c) ! 1. Very dry litter if (moist <= SF_val_min_moisture(c)) then currentPatch%burnt_frac_litter(c) = 1.0_r8 diff --git a/main/EDInitMod.F90 b/main/EDInitMod.F90 index 418bd3b3ea..70a81fe603 100644 --- a/main/EDInitMod.F90 +++ b/main/EDInitMod.F90 @@ -813,12 +813,7 @@ subroutine init_patches( nsites, sites, bc_in) currentPatch => sites(s)%youngest_patch do while(associated(currentPatch)) - currentPatch%litter_moisture(:) = 0._r8 - currentPatch%fuel_eff_moist = 0._r8 currentPatch%livegrass = 0._r8 - currentPatch%fuel_bulkd = 0._r8 - currentPatch%fuel_sav = 0._r8 - currentPatch%fuel_mef = 0._r8 currentPatch%ros_front = 0._r8 currentPatch%tau_l = 0._r8 currentPatch%tfc_ros = 0._r8 diff --git a/main/FatesHistoryInterfaceMod.F90 b/main/FatesHistoryInterfaceMod.F90 index e32c59122f..bf0160d43b 100644 --- a/main/FatesHistoryInterfaceMod.F90 +++ b/main/FatesHistoryInterfaceMod.F90 @@ -2631,10 +2631,10 @@ subroutine update_history_dyn1(this,nc,nsites,sites,bc_in) hio_tfc_ros_si(io_si) = hio_tfc_ros_si(io_si) + cpatch%TFC_ROS * cpatch%area * AREA_INV hio_fire_intensity_si(io_si) = hio_fire_intensity_si(io_si) + cpatch%FI * cpatch%area * AREA_INV * J_per_kJ hio_fire_area_si(io_si) = hio_fire_area_si(io_si) + cpatch%frac_burnt * cpatch%area * AREA_INV / sec_per_day - hio_fire_fuel_bulkd_si(io_si) = hio_fire_fuel_bulkd_si(io_si) + cpatch%fuel_bulkd * cpatch%area * AREA_INV - hio_fire_fuel_eff_moist_si(io_si) = hio_fire_fuel_eff_moist_si(io_si) + cpatch%fuel_eff_moist * cpatch%area * AREA_INV - hio_fire_fuel_sav_si(io_si) = hio_fire_fuel_sav_si(io_si) + cpatch%fuel_sav * cpatch%area * AREA_INV / m_per_cm - hio_fire_fuel_mef_si(io_si) = hio_fire_fuel_mef_si(io_si) + cpatch%fuel_mef * cpatch%area * AREA_INV + hio_fire_fuel_bulkd_si(io_si) = hio_fire_fuel_bulkd_si(io_si) + cpatch%fuel%bulk_density * cpatch%area * AREA_INV + hio_fire_fuel_eff_moist_si(io_si) = hio_fire_fuel_eff_moist_si(io_si) + cpatch%fuel%average_moisture * cpatch%area * AREA_INV + hio_fire_fuel_sav_si(io_si) = hio_fire_fuel_sav_si(io_si) + cpatch%fuel%SAV * cpatch%area * AREA_INV / m_per_cm + hio_fire_fuel_mef_si(io_si) = hio_fire_fuel_mef_si(io_si) + cpatch%fuel%MEF * cpatch%area * AREA_INV hio_sum_fuel_si(io_si) = hio_sum_fuel_si(io_si) + cpatch%fuel%total_loading * cpatch%area * AREA_INV hio_fire_intensity_area_product_si(io_si) = hio_fire_intensity_area_product_si(io_si) + & @@ -4192,7 +4192,7 @@ subroutine update_history_dyn2(this,nc,nsites,sites,bc_in) cpatch%fuel%frac_loading(i_fuel) * cpatch%fuel%total_loading * cpatch%area * AREA_INV hio_litter_moisture_si_fuel(io_si, i_fuel) = hio_litter_moisture_si_fuel(io_si, i_fuel) + & - cpatch%litter_moisture(i_fuel) * cpatch%area * AREA_INV + cpatch%fuel%effective_moisture(i_fuel) * cpatch%area * AREA_INV hio_fuel_amount_si_fuel(io_si, i_fuel) = hio_fuel_amount_si_fuel(io_si, i_fuel) + & cpatch%fuel%frac_loading(i_fuel) * cpatch%fuel%total_loading * cpatch%area * AREA_INV diff --git a/main/FatesRestartInterfaceMod.F90 b/main/FatesRestartInterfaceMod.F90 index ffef39eb6f..d52fe9b6f4 100644 --- a/main/FatesRestartInterfaceMod.F90 +++ b/main/FatesRestartInterfaceMod.F90 @@ -2488,7 +2488,7 @@ subroutine set_restart_vectors(this,nc,nsites,sites) io_idx_pa_cwd = io_idx_co_1st do i = 1,nfsc - this%rvars(ir_litter_moisture_pa_nfsc)%r81d(io_idx_pa_cwd) = cpatch%litter_moisture(i) + this%rvars(ir_litter_moisture_pa_nfsc)%r81d(io_idx_pa_cwd) = cpatch%fuel%effective_moisture(i) io_idx_pa_cwd = io_idx_pa_cwd + 1 end do @@ -3440,7 +3440,7 @@ subroutine get_restart_vectors(this, nc, nsites, sites) io_idx_pa_cwd = io_idx_co_1st do i = 1,nfsc - cpatch%litter_moisture(i) = this%rvars(ir_litter_moisture_pa_nfsc)%r81d(io_idx_pa_cwd) + cpatch%fuel%effective_moisture(i) = this%rvars(ir_litter_moisture_pa_nfsc)%r81d(io_idx_pa_cwd) io_idx_pa_cwd = io_idx_pa_cwd + 1 end do From 8104707966b33187b6b2b6021b5ec9b74e439e37 Mon Sep 17 00:00:00 2001 From: Charlie Koven Date: Tue, 7 May 2024 14:44:35 -0700 Subject: [PATCH 016/111] first set of changes to move growth respiration calculation from half-hourly to daily --- biogeochem/EDCohortDynamicsMod.F90 | 15 +++-- biogeochem/FatesCohortMod.F90 | 47 ++++++++------- biogeochem/FatesSoilBGCFluxMod.F90 | 2 +- biogeophys/EDAccumulateFluxesMod.F90 | 8 +-- biogeophys/FatesPlantRespPhotosynthMod.F90 | 10 +--- main/EDMainMod.F90 | 36 +++++++++--- main/FatesHistoryInterfaceMod.F90 | 4 +- main/FatesRestartInterfaceMod.F90 | 68 ++++++++++++++-------- 8 files changed, 115 insertions(+), 75 deletions(-) diff --git a/biogeochem/EDCohortDynamicsMod.F90 b/biogeochem/EDCohortDynamicsMod.F90 index ef87851ec1..e362cfb96a 100644 --- a/biogeochem/EDCohortDynamicsMod.F90 +++ b/biogeochem/EDCohortDynamicsMod.F90 @@ -1066,11 +1066,16 @@ subroutine fuse_cohorts(currentSite, currentPatch, bc_in) nextc%n*nextc%gpp_acc)/newn currentCohort%npp_acc = (currentCohort%n*currentCohort%npp_acc + & nextc%n*nextc%npp_acc)/newn - currentCohort%resp_acc = (currentCohort%n*currentCohort%resp_acc + & - nextc%n*nextc%resp_acc)/newn - currentCohort%resp_acc_hold = & - (currentCohort%n*currentCohort%resp_acc_hold + & - nextc%n*nextc%resp_acc_hold)/newn + currentCohort%resp_m_acc = (currentCohort%n*currentCohort%resp_m_acc + & + nextc%n*nextc%resp_m_acc)/newn + currentCohort%resp_m_acc_hold = & + (currentCohort%n*currentCohort%resp_m_acc_hold + & + nextc%n*nextc%resp_m_acc_hold)/newn + currentCohort%resp_g_acc = (currentCohort%n*currentCohort%resp_g_acc + & + nextc%n*nextc%resp_g_acc)/newn + currentCohort%resp_g_acc_hold = & + (currentCohort%n*currentCohort%resp_g_acc_hold + & + nextc%n*nextc%resp_g_acc_hold)/newn currentCohort%npp_acc_hold = & (currentCohort%n*currentCohort%npp_acc_hold + & nextc%n*nextc%npp_acc_hold)/newn diff --git a/biogeochem/FatesCohortMod.F90 b/biogeochem/FatesCohortMod.F90 index e98c4a345a..64a4d929af 100644 --- a/biogeochem/FatesCohortMod.F90 +++ b/biogeochem/FatesCohortMod.F90 @@ -142,13 +142,15 @@ module FatesCohortMod real(r8) :: gpp_acc real(r8) :: gpp_acc_hold - real(r8) :: npp_tstep ! Net Primary Production (see above *) real(r8) :: npp_acc real(r8) :: npp_acc_hold - real(r8) :: resp_tstep ! Autotrophic respiration (see above *) - real(r8) :: resp_acc - real(r8) :: resp_acc_hold + real(r8) :: resp_m_tstep ! Maintenance respiration (see above *) + real(r8) :: resp_m_acc + real(r8) :: resp_m_acc_hold + + real(r8) :: resp_g_acc ! Growth respication can only be calculated at the daily timestep + real(r8) :: resp_g_acc_hold real(r8) :: c13disc_clm ! carbon 13 discrimination in new synthesized carbon at each indiv/timestep [ppm] real(r8) :: c13disc_acc ! carbon 13 discrimination in new synthesized carbon at each indiv/day @@ -376,12 +378,13 @@ subroutine NanValues(this) this%gpp_tstep = nan this%gpp_acc = nan this%gpp_acc_hold = nan - this%npp_tstep = nan this%npp_acc = nan this%npp_acc_hold = nan - this%resp_tstep = nan - this%resp_acc = nan - this%resp_acc_hold = nan + this%resp_m_tstep = nan + this%resp_m_acc = nan + this%resp_m_acc_hold = nan + this%resp_g_acc = nan + this%resp_g_acc_hold = nan this%c13disc_clm = nan this%c13disc_acc = nan this%vcmax25top = nan @@ -478,17 +481,18 @@ subroutine ZeroValues(this) this%size_class_lasttimestep = 0 this%gpp_tstep = 0._r8 this%gpp_acc = 0._r8 - this%npp_tstep = 0._r8 this%npp_acc = 0._r8 - this%resp_tstep = 0._r8 - this%resp_acc = 0._r8 + this%resp_m_tstep = 0._r8 + this%resp_m_acc = 0._r8 + this%resp_g_acc = 0._r8 ! do not zero these, they are not built ! so more appropriate to leave unzerod ! to prevent uninitialized use ! this%gpp_acc_hold = nan ! this%npp_acc_hold = nan - ! this%resp_acc_hold = nan + ! this%resp_m_acc_hold = nan + ! this%resp_g_acc_hold = nan this%c13disc_clm = 0._r8 this%c13disc_acc = 0._r8 @@ -707,12 +711,13 @@ subroutine Copy(this, copyCohort) copyCohort%gpp_tstep = this%gpp_tstep copyCohort%gpp_acc = this%gpp_acc copyCohort%gpp_acc_hold = this%gpp_acc_hold - copyCohort%npp_tstep = this%npp_tstep copyCohort%npp_acc = this%npp_acc copyCohort%npp_acc_hold = this%npp_acc_hold - copyCohort%resp_tstep = this%resp_tstep - copyCohort%resp_acc = this%resp_acc - copyCohort%resp_acc_hold = this%resp_acc_hold + copyCohort%resp_m_tstep = this%resp_m_tstep + copyCohort%resp_m_acc = this%resp_m_acc + copyCohort%resp_m_acc_hold = this%resp_m_acc_hold + copyCohort%resp_g_acc = this%resp_g_acc + copyCohort%resp_g_acc_hold = this%resp_g_acc_hold copyCohort%c13disc_clm = this%c13disc_clm copyCohort%c13disc_acc = this%c13disc_acc copyCohort%vcmax25top = this%vcmax25top @@ -1048,14 +1053,14 @@ subroutine Dump(this) write(fates_log(),*) 'cohort%gpp_acc = ', this%gpp_acc write(fates_log(),*) 'cohort%gpp_tstep = ', this%gpp_tstep write(fates_log(),*) 'cohort%npp_acc_hold = ', this%npp_acc_hold - write(fates_log(),*) 'cohort%npp_tstep = ', this%npp_tstep write(fates_log(),*) 'cohort%npp_acc = ', this%npp_acc - write(fates_log(),*) 'cohort%resp_tstep = ', this%resp_tstep - write(fates_log(),*) 'cohort%resp_acc = ', this%resp_acc - write(fates_log(),*) 'cohort%resp_acc_hold = ', this%resp_acc_hold + write(fates_log(),*) 'cohort%resp_m_tstep = ', this%resp_m_tstep + write(fates_log(),*) 'cohort%resp_m_acc = ', this%resp_m_acc + write(fates_log(),*) 'cohort%resp_m_acc_hold = ', this%resp_m_acc_hold + write(fates_log(),*) 'cohort%resp_g_acc = ', this%resp_g_acc + write(fates_log(),*) 'cohort%resp_g_acc_hold = ', this%resp_g_acc_hold write(fates_log(),*) 'cohort%rdark = ', this%rdark write(fates_log(),*) 'cohort%resp_m = ', this%resp_m - write(fates_log(),*) 'cohort%resp_g_tstep = ', this%resp_g_tstep write(fates_log(),*) 'cohort%livestem_mr = ', this%livestem_mr write(fates_log(),*) 'cohort%livecroot_mr = ', this%livecroot_mr write(fates_log(),*) 'cohort%froot_mr = ', this%froot_mr diff --git a/biogeochem/FatesSoilBGCFluxMod.F90 b/biogeochem/FatesSoilBGCFluxMod.F90 index 9d813c32b3..ae102eaf15 100644 --- a/biogeochem/FatesSoilBGCFluxMod.F90 +++ b/biogeochem/FatesSoilBGCFluxMod.F90 @@ -348,7 +348,7 @@ subroutine PrepCH4BCs(csite,bc_in,bc_out) ! this is a best (bad) guess at fine root MR + total root GR ! (kgC/indiv/yr) -> gC/m2/s bc_out%root_resp(1:bc_in%nlevsoil) = bc_out%root_resp(1:bc_in%nlevsoil) + & - ccohort%resp_acc_hold*years_per_day*g_per_kg*days_per_sec* & + (ccohort%resp_m_acc_hold + ccohort%resp_g_acc_hold)*years_per_day*g_per_kg*days_per_sec* & ccohort%n*area_inv*(1._r8-prt_params%allom_agb_frac(pft)) * csite%rootfrac_scr(1:bc_in%nlevsoil) end if diff --git a/biogeophys/EDAccumulateFluxesMod.F90 b/biogeophys/EDAccumulateFluxesMod.F90 index 6a1bb001c0..d74c965e31 100644 --- a/biogeophys/EDAccumulateFluxesMod.F90 +++ b/biogeophys/EDAccumulateFluxesMod.F90 @@ -3,7 +3,7 @@ module EDAccumulateFluxesMod !------------------------------------------------------------------------------ ! !DESCRIPTION: ! This routine accumulates NPP, GPP and respiration of each cohort over the course of each 24 hour period. - ! The fluxes are stored per cohort, and the npp_tstep (etc) fluxes are calcualted in EDPhotosynthesis + ! The fluxes are stored per cohort, and the gpp_tstep (etc) fluxes are calculated in EDPhotosynthesis ! This routine cannot be in EDPhotosynthesis because EDPhotosynthesis is a loop and therefore would ! erroneously add these things up multiple times. ! Rosie Fisher. March 2014. @@ -82,15 +82,13 @@ subroutine AccumulateFluxes_ED(nsites, sites, bc_in, bc_out, dt_time) if ( debug ) then - write(fates_log(),*) 'EDAccumFlux 64 ',ccohort%npp_tstep write(fates_log(),*) 'EDAccumFlux 66 ',ccohort%gpp_tstep - write(fates_log(),*) 'EDAccumFlux 67 ',ccohort%resp_tstep + write(fates_log(),*) 'EDAccumFlux 67 ',ccohort%resp_m_tstep endif - ccohort%npp_acc = ccohort%npp_acc + ccohort%npp_tstep ccohort%gpp_acc = ccohort%gpp_acc + ccohort%gpp_tstep - ccohort%resp_acc = ccohort%resp_acc + ccohort%resp_tstep + ccohort%resp_m_acc = ccohort%resp_m_acc + ccohort%resp_m_tstep ccohort%sym_nfix_daily = ccohort%sym_nfix_daily + ccohort%sym_nfix_tstep diff --git a/biogeophys/FatesPlantRespPhotosynthMod.F90 b/biogeophys/FatesPlantRespPhotosynthMod.F90 index 0aad6eb977..430ca555bf 100644 --- a/biogeophys/FatesPlantRespPhotosynthMod.F90 +++ b/biogeophys/FatesPlantRespPhotosynthMod.F90 @@ -987,6 +987,7 @@ subroutine FatesPlantRespPhotosynthDrive (nsites, sites,bc_in,bc_out,dtime) ! convert from kgC/indiv/s to kgC/indiv/timestep currentCohort%resp_m = currentCohort%resp_m * dtime + currentCohort%resp_m_tstep = currentCohort%resp_m ! these two things are the same. we should get rid of one of them. which? currentCohort%gpp_tstep = currentCohort%gpp_tstep * dtime currentCohort%ts_net_uptake = currentCohort%ts_net_uptake * dtime @@ -995,15 +996,6 @@ subroutine FatesPlantRespPhotosynthDrive (nsites, sites,bc_in,bc_out,dtime) if ( debug ) write(fates_log(),*) 'EDPhoto 913 ', currentCohort%resp_m - currentCohort%resp_g_tstep = prt_params%grperc(ft) * & - (max(0._r8,currentCohort%gpp_tstep - currentCohort%resp_m)) - - - currentCohort%resp_tstep = currentCohort%resp_m + & - currentCohort%resp_g_tstep ! kgC/indiv/ts - currentCohort%npp_tstep = currentCohort%gpp_tstep - & - currentCohort%resp_tstep ! kgC/indiv/ts - ! Accumulate the combined conductance (stomatal+leaf boundary layer) ! Note that currentCohort%g_sb_laweight is weighted by the leaf area ! of each cohort and has units of [m/s] * [m2 leaf] diff --git a/main/EDMainMod.F90 b/main/EDMainMod.F90 index 159e942c6a..22cb94d992 100644 --- a/main/EDMainMod.F90 +++ b/main/EDMainMod.F90 @@ -335,7 +335,8 @@ subroutine ed_integrate_state_variables(currentSite, bc_in, bc_out ) use FatesConstantsMod, only : itrue use FatesConstantsMod , only : nearzero use EDCanopyStructureMod , only : canopy_structure - + use PRTParametersMod , only : prt_params + ! !ARGUMENTS: type(ed_site_type) , intent(inout) :: currentSite @@ -498,7 +499,7 @@ subroutine ed_integrate_state_variables(currentSite, bc_in, bc_out ) ! We don't explicitly define a respiration rate for prescribe phys ! but we do need to pass mass balance. So we say it is zero respiration currentCohort%gpp_acc = currentCohort%npp_acc - currentCohort%resp_acc = 0._r8 + currentCohort%resp_m_acc = 0._r8 end if @@ -514,14 +515,30 @@ subroutine ed_integrate_state_variables(currentSite, bc_in, bc_out ) ! photosynthesis step ! ----------------------------------------------------------------------------- - currentCohort%npp_acc_hold = currentCohort%npp_acc * real(hlm_days_per_year,r8) currentCohort%gpp_acc_hold = currentCohort%gpp_acc * real(hlm_days_per_year,r8) - currentCohort%resp_acc_hold = currentCohort%resp_acc * real(hlm_days_per_year,r8) + currentCohort%resp_m_acc_hold = currentCohort%resp_m_acc * real(hlm_days_per_year,r8) + + ! at this point we have the info we need to calculate growth respiration + ! as a "tax" on the difference between daily GPP and daily maintenance respiration + if (hlm_use_ed_prescribed_phys .eq. itrue) then + currentCohort%resp_g_acc = prt_params%grperc(ft) * & + max(0._r8,(currentCohort%gpp_acc - currentCohort%resp_m_acc)) + currentCohort%resp_g_acc_hold = currentCohort%resp_g_acc * real(hlm_days_per_year,r8) + else + ! set growth respiration to zero in prescribed physiology mode, + ! that way the npp_acc vars will be set to the nominal gpp values set above. + currentCohort%resp_g_acc = 0._r8 + currentCohort%resp_g_acc_hold = 0._r8 + endif + + ! calculate the npp as the difference between gpp and autotrophic respiration + currentCohort%npp_acc = currentCohort%gpp_acc - (currentCohort%resp_m_acc + currentCohort%resp_g_acc) + currentCohort%npp_acc_hold = currentCohort%gpp_acc_hold - (currentCohort%resp_m_acc_hold + currentCohort%resp_g_acc_hold) ! Passing gpp_acc_hold to HLM bc_out%gpp_site = bc_out%gpp_site + currentCohort%gpp_acc_hold * & AREA_INV * currentCohort%n / hlm_days_per_year / sec_per_day - bc_out%ar_site = bc_out%ar_site + currentCohort%resp_acc_hold * & + bc_out%ar_site = bc_out%ar_site + (currentCohort%resp_m_acc_hold + currentCohort%resp_g_acc_hold) * & AREA_INV * currentCohort%n / hlm_days_per_year / sec_per_day ! Conduct Maintenance Turnover (parteh) @@ -630,7 +647,8 @@ subroutine ed_integrate_state_variables(currentSite, bc_in, bc_out ) currentCohort%gpp_acc * currentCohort%n site_cmass%aresp_acc = site_cmass%aresp_acc + & - (currentCohort%resp_acc+currentCohort%resp_excess) * currentCohort%n + (currentCohort%resp_m_acc+currentCohort%resp_g_acc+currentCohort%resp_excess) * & + currentCohort%n call currentCohort%prt%CheckMassConservation(ft,5) @@ -1050,11 +1068,13 @@ subroutine bypass_dynamics(currentSite) currentCohort%npp_acc_hold = currentCohort%npp_acc * real(hlm_days_per_year,r8) currentCohort%gpp_acc_hold = currentCohort%gpp_acc * real(hlm_days_per_year,r8) - currentCohort%resp_acc_hold = currentCohort%resp_acc * real(hlm_days_per_year,r8) + currentCohort%resp_g_acc_hold = currentCohort%resp_g_acc * real(hlm_days_per_year,r8) + currentCohort%resp_m_acc_hold = currentCohort%resp_m_acc * real(hlm_days_per_year,r8) currentCohort%npp_acc = 0.0_r8 currentCohort%gpp_acc = 0.0_r8 - currentCohort%resp_acc = 0.0_r8 + currentCohort%resp_g_acc = 0.0_r8 + currentCohort%resp_m_acc = 0.0_r8 ! No need to set the "net_art" terms to zero ! they are zeroed at the beginning of the daily step diff --git a/main/FatesHistoryInterfaceMod.F90 b/main/FatesHistoryInterfaceMod.F90 index c80186a58b..8b8615a75c 100644 --- a/main/FatesHistoryInterfaceMod.F90 +++ b/main/FatesHistoryInterfaceMod.F90 @@ -3918,7 +3918,7 @@ subroutine update_history_dyn2(this,nc,nsites,sites,bc_in) hio_gpp_canopy_si_scpf(io_si,scpf) = hio_gpp_canopy_si_scpf(io_si,scpf) + & n_perm2*ccohort%gpp_acc_hold / days_per_year / sec_per_day hio_ar_canopy_si_scpf(io_si,scpf) = hio_ar_canopy_si_scpf(io_si,scpf) + & - n_perm2*ccohort%resp_acc_hold / days_per_year / sec_per_day + n_perm2*(ccohort%resp_m_acc_hold + ccohort%resp_g_acc_hold) / days_per_year / sec_per_day ! growth increment hio_ddbh_canopy_si_scpf(io_si,scpf) = hio_ddbh_canopy_si_scpf(io_si,scpf) + & ccohort%ddbhdt*ccohort%n * m_per_cm / m2_per_ha @@ -4053,7 +4053,7 @@ subroutine update_history_dyn2(this,nc,nsites,sites,bc_in) hio_gpp_understory_si_scpf(io_si,scpf) = hio_gpp_understory_si_scpf(io_si,scpf) + & n_perm2*ccohort%gpp_acc_hold / days_per_year / sec_per_day hio_ar_understory_si_scpf(io_si,scpf) = hio_ar_understory_si_scpf(io_si,scpf) + & - n_perm2*ccohort%resp_acc_hold / days_per_year / sec_per_day + n_perm2*(ccohort%resp_m_acc_hold + ccohort%resp_g_acc_hold) / days_per_year / sec_per_day ! growth increment hio_ddbh_understory_si_scpf(io_si,scpf) = hio_ddbh_understory_si_scpf(io_si,scpf) + & diff --git a/main/FatesRestartInterfaceMod.F90 b/main/FatesRestartInterfaceMod.F90 index 90e282253b..3300d2297a 100644 --- a/main/FatesRestartInterfaceMod.F90 +++ b/main/FatesRestartInterfaceMod.F90 @@ -132,10 +132,12 @@ module FatesRestartInterfaceMod integer :: ir_nplant_co integer :: ir_gpp_acc_co integer :: ir_npp_acc_co - integer :: ir_resp_acc_co + integer :: ir_resp_m_acc_co + integer :: ir_resp_g_acc_co integer :: ir_gpp_acc_hold_co integer :: ir_npp_acc_hold_co - integer :: ir_resp_acc_hold_co + integer :: ir_resp_m_acc_hold_co + integer :: ir_resp_g_acc_hold_co integer :: ir_resp_excess_co integer :: ir_bmort_co integer :: ir_hmort_co @@ -180,7 +182,7 @@ module FatesRestartInterfaceMod integer :: ir_ddbhdt_co - integer :: ir_resp_tstep_co + integer :: ir_resp_m_tstep_co integer :: ir_pft_co integer :: ir_status_co integer :: ir_efleaf_co @@ -853,10 +855,15 @@ subroutine define_restart_vars(this, initialize_variables) units='kgC/indiv', flushval = flushzero, & hlms='CLM:ALM', initialize=initialize_variables, ivar=ivar, index = ir_npp_acc_co ) - call this%set_restart_var(vname='fates_resp_acc', vtype=cohort_r8, & - long_name='ed cohort - accumulated respiration over dynamics step', & + call this%set_restart_var(vname='fates_resp_m_acc', vtype=cohort_r8, & + long_name='ed cohort - accumulated maintenance respiration over dynamics step', & units='kgC/indiv', flushval = flushzero, & - hlms='CLM:ALM', initialize=initialize_variables, ivar=ivar, index = ir_resp_acc_co ) + hlms='CLM:ALM', initialize=initialize_variables, ivar=ivar, index = ir_resp_m_acc_co ) + + call this%set_restart_var(vname='fates_resp_g_acc', vtype=cohort_r8, & + long_name='ed cohort - accumulated growth respiration over dynamics step', & + units='kgC/indiv', flushval = flushzero, & + hlms='CLM:ALM', initialize=initialize_variables, ivar=ivar, index = ir_resp_g_acc_co ) call this%set_restart_var(vname='fates_gpp_acc_hold', vtype=cohort_r8, & long_name='ed cohort - current step gpp', & @@ -868,10 +875,15 @@ subroutine define_restart_vars(this, initialize_variables) units='kgC/indiv/year', flushval = flushzero, & hlms='CLM:ALM', initialize=initialize_variables, ivar=ivar, index = ir_npp_acc_hold_co ) - call this%set_restart_var(vname='fates_resp_acc_hold', vtype=cohort_r8, & - long_name='ed cohort - current step resp', & + call this%set_restart_var(vname='fates_resp_m_acc_hold', vtype=cohort_r8, & + long_name='ed cohort - current step maint resp', & + units='kgC/indiv/year', flushval = flushzero, & + hlms='CLM:ALM', initialize=initialize_variables, ivar=ivar, index = ir_resp_m_acc_hold_co ) + + call this%set_restart_var(vname='fates_resp_g_acc_hold', vtype=cohort_r8, & + long_name='ed cohort - current step growth resp', & units='kgC/indiv/year', flushval = flushzero, & - hlms='CLM:ALM', initialize=initialize_variables, ivar=ivar, index = ir_resp_acc_hold_co ) + hlms='CLM:ALM', initialize=initialize_variables, ivar=ivar, index = ir_resp_g_acc_hold_co ) call this%set_restart_var(vname='fates_resp_excess', vtype=cohort_r8, & long_name='ed cohort - maintenance respiration deficit', & @@ -935,10 +947,10 @@ subroutine define_restart_vars(this, initialize_variables) units='cm/year', flushval = flushzero, & hlms='CLM:ALM', initialize=initialize_variables, ivar=ivar, index = ir_ddbhdt_co ) - call this%set_restart_var(vname='fates_resp_tstep', vtype=cohort_r8, & - long_name='ed cohort - autotrophic respiration over timestep', & + call this%set_restart_var(vname='fates_resp_m_tstep', vtype=cohort_r8, & + long_name='ed cohort - maintenance respiration over timestep', & units='kgC/indiv/timestep', flushval = flushzero, & - hlms='CLM:ALM', initialize=initialize_variables, ivar=ivar, index = ir_resp_tstep_co ) + hlms='CLM:ALM', initialize=initialize_variables, ivar=ivar, index = ir_resp_m_tstep_co ) call this%set_restart_var(vname='fates_pft', vtype=cohort_int, & long_name='ed cohort - plant functional type', units='index', flushval = flushzero, & @@ -2056,9 +2068,11 @@ subroutine set_restart_vectors(this,nc,nsites,sites) rio_nplant_co => this%rvars(ir_nplant_co)%r81d, & rio_gpp_acc_co => this%rvars(ir_gpp_acc_co)%r81d, & rio_npp_acc_co => this%rvars(ir_npp_acc_co)%r81d, & - rio_resp_acc_co => this%rvars(ir_resp_acc_co)%r81d, & + rio_resp_m_acc_co => this%rvars(ir_resp_m_acc_co)%r81d, & + rio_resp_g_acc_co => this%rvars(ir_resp_g_acc_co)%r81d, & rio_gpp_acc_hold_co => this%rvars(ir_gpp_acc_hold_co)%r81d, & - rio_resp_acc_hold_co => this%rvars(ir_resp_acc_hold_co)%r81d, & + rio_resp_m_acc_hold_co => this%rvars(ir_resp_m_acc_hold_co)%r81d, & + rio_resp_g_acc_hold_co => this%rvars(ir_resp_g_acc_hold_co)%r81d, & rio_npp_acc_hold_co => this%rvars(ir_npp_acc_hold_co)%r81d, & rio_resp_excess_co => this%rvars(ir_resp_excess_co)%r81d, & rio_bmort_co => this%rvars(ir_bmort_co)%r81d, & @@ -2072,7 +2086,7 @@ subroutine set_restart_vectors(this,nc,nsites,sites) rio_lmort_collateral_co => this%rvars(ir_lmort_collateral_co)%r81d, & rio_lmort_infra_co => this%rvars(ir_lmort_infra_co)%r81d, & rio_ddbhdt_co => this%rvars(ir_ddbhdt_co)%r81d, & - rio_resp_tstep_co => this%rvars(ir_resp_tstep_co)%r81d, & + rio_resp_m_tstep_co => this%rvars(ir_resp_m_tstep_co)%r81d, & rio_pft_co => this%rvars(ir_pft_co)%int1d, & rio_status_co => this%rvars(ir_status_co)%int1d, & rio_efleaf_co => this%rvars(ir_efleaf_co)%r81d, & @@ -2378,9 +2392,11 @@ subroutine set_restart_vectors(this,nc,nsites,sites) rio_nplant_co(io_idx_co) = ccohort%n rio_gpp_acc_co(io_idx_co) = ccohort%gpp_acc rio_npp_acc_co(io_idx_co) = ccohort%npp_acc - rio_resp_acc_co(io_idx_co) = ccohort%resp_acc + rio_resp_m_acc_co(io_idx_co) = ccohort%resp_m_acc + rio_resp_g_acc_co(io_idx_co) = ccohort%resp_g_acc rio_gpp_acc_hold_co(io_idx_co) = ccohort%gpp_acc_hold - rio_resp_acc_hold_co(io_idx_co) = ccohort%resp_acc_hold + rio_resp_m_acc_hold_co(io_idx_co) = ccohort%resp_m_acc_hold + rio_resp_g_acc_hold_co(io_idx_co) = ccohort%resp_g_acc_hold rio_npp_acc_hold_co(io_idx_co) = ccohort%npp_acc_hold rio_resp_excess_co(io_idx_co) = ccohort%resp_excess @@ -2400,7 +2416,7 @@ subroutine set_restart_vectors(this,nc,nsites,sites) rio_lmort_infra_co(io_idx_co) = ccohort%lmort_infra rio_ddbhdt_co(io_idx_co) = ccohort%ddbhdt - rio_resp_tstep_co(io_idx_co) = ccohort%resp_tstep + rio_resp_m_tstep_co(io_idx_co) = ccohort%resp_m_tstep rio_pft_co(io_idx_co) = ccohort%pft rio_status_co(io_idx_co) = ccohort%status_coh rio_efleaf_co(io_idx_co) = ccohort%efleaf_coh @@ -3023,9 +3039,11 @@ subroutine get_restart_vectors(this, nc, nsites, sites) rio_nplant_co => this%rvars(ir_nplant_co)%r81d, & rio_gpp_acc_co => this%rvars(ir_gpp_acc_co)%r81d, & rio_npp_acc_co => this%rvars(ir_npp_acc_co)%r81d, & - rio_resp_acc_co => this%rvars(ir_resp_acc_co)%r81d, & + rio_resp_m_acc_co => this%rvars(ir_resp_m_acc_co)%r81d, & + rio_resp_g_acc_co => this%rvars(ir_resp_g_acc_co)%r81d, & rio_gpp_acc_hold_co => this%rvars(ir_gpp_acc_hold_co)%r81d, & - rio_resp_acc_hold_co => this%rvars(ir_resp_acc_hold_co)%r81d, & + rio_resp_m_acc_hold_co => this%rvars(ir_resp_m_acc_hold_co)%r81d, & + rio_resp_g_acc_hold_co => this%rvars(ir_resp_g_acc_hold_co)%r81d, & rio_npp_acc_hold_co => this%rvars(ir_npp_acc_hold_co)%r81d, & rio_resp_excess_co => this%rvars(ir_resp_excess_co)%r81d, & rio_bmort_co => this%rvars(ir_bmort_co)%r81d, & @@ -3039,7 +3057,7 @@ subroutine get_restart_vectors(this, nc, nsites, sites) rio_lmort_collateral_co => this%rvars(ir_lmort_collateral_co)%r81d, & rio_lmort_infra_co => this%rvars(ir_lmort_infra_co)%r81d, & rio_ddbhdt_co => this%rvars(ir_ddbhdt_co)%r81d, & - rio_resp_tstep_co => this%rvars(ir_resp_tstep_co)%r81d, & + rio_resp_m_tstep_co => this%rvars(ir_resp_m_tstep_co)%r81d, & rio_pft_co => this%rvars(ir_pft_co)%int1d, & rio_status_co => this%rvars(ir_status_co)%int1d, & rio_efleaf_co => this%rvars(ir_efleaf_co)%r81d, & @@ -3319,9 +3337,11 @@ subroutine get_restart_vectors(this, nc, nsites, sites) ccohort%n = rio_nplant_co(io_idx_co) ccohort%gpp_acc = rio_gpp_acc_co(io_idx_co) ccohort%npp_acc = rio_npp_acc_co(io_idx_co) - ccohort%resp_acc = rio_resp_acc_co(io_idx_co) + ccohort%resp_m_acc = rio_resp_m_acc_co(io_idx_co) + ccohort%resp_g_acc = rio_resp_g_acc_co(io_idx_co) ccohort%gpp_acc_hold = rio_gpp_acc_hold_co(io_idx_co) - ccohort%resp_acc_hold = rio_resp_acc_hold_co(io_idx_co) + ccohort%resp_m_acc_hold = rio_resp_m_acc_hold_co(io_idx_co) + ccohort%resp_g_acc_hold = rio_resp_g_acc_hold_co(io_idx_co) ccohort%npp_acc_hold = rio_npp_acc_hold_co(io_idx_co) ccohort%resp_excess = rio_resp_excess_co(io_idx_co) @@ -3341,7 +3361,7 @@ subroutine get_restart_vectors(this, nc, nsites, sites) ccohort%lmort_infra = rio_lmort_infra_co(io_idx_co) ccohort%ddbhdt = rio_ddbhdt_co(io_idx_co) - ccohort%resp_tstep = rio_resp_tstep_co(io_idx_co) + ccohort%resp_m_tstep = rio_resp_m_tstep_co(io_idx_co) ccohort%pft = rio_pft_co(io_idx_co) ccohort%status_coh = rio_status_co(io_idx_co) ccohort%efleaf_coh = rio_efleaf_co(io_idx_co) From d03b44f463ecee3b724de11e8f505c5abfeaca7d Mon Sep 17 00:00:00 2001 From: Charlie Koven Date: Wed, 8 May 2024 16:51:09 -0700 Subject: [PATCH 017/111] starting to modify history vars --- main/FatesHistoryInterfaceMod.F90 | 83 +++++++++++++------------------ 1 file changed, 34 insertions(+), 49 deletions(-) diff --git a/main/FatesHistoryInterfaceMod.F90 b/main/FatesHistoryInterfaceMod.F90 index 8b8615a75c..a2faecb9c0 100644 --- a/main/FatesHistoryInterfaceMod.F90 +++ b/main/FatesHistoryInterfaceMod.F90 @@ -2377,6 +2377,8 @@ subroutine update_history_dyn1(this,nc,nsites,sites,bc_in) hio_sum_fuel_si => this%hvars(ih_sum_fuel_si)%r81d, & hio_litter_in_si => this%hvars(ih_litter_in_si)%r81d, & hio_litter_out_si => this%hvars(ih_litter_out_si)%r81d, & + hio_npp_si => this%hvars(ih_npp_si)%r82d, & + hio_growth_resp_si => this%hvars(ih_growth_resp_si)%r81d, & hio_seed_bank_si => this%hvars(ih_seed_bank_si)%r81d, & hio_ungerm_seed_bank_si => this%hvars(ih_ungerm_seed_bank_si)%r81d, & hio_seedling_pool_si => this%hvars(ih_seedling_pool_si)%r81d, & @@ -2814,6 +2816,12 @@ subroutine update_history_dyn1(this,nc,nsites,sites,bc_in) ! have any meaning, otherwise they are just inialization values notnew: if( .not.(ccohort%isnew) ) then + hio_npp_si(io_si) = hio_npp_si(io_si) + & + ccohort%npp_acc_hold * n_perm2 / days_per_year / sec_per_day + + hio_growth_resp_si(io_si) = hio_growth_resp_si(io_si) + & + ccohort%resp_g_acc_hold * n_perm2 / days_per_year / sec_per_day + ! Turnover pools [kgC/day] * [day/yr] = [kgC/yr] sapw_m_turnover = ccohort%prt%GetTurnover(sapw_organ, carbon12_element) * days_per_year store_m_turnover = ccohort%prt%GetTurnover(store_organ, carbon12_element) * days_per_year @@ -3069,7 +3077,7 @@ subroutine update_history_dyn2(this,nc,nsites,sites,bc_in) hio_canopycrownarea_si_pft => this%hvars(ih_canopycrownarea_si_pft)%r82d, & hio_gpp_si_pft => this%hvars(ih_gpp_si_pft)%r82d, & hio_gpp_sec_si_pft => this%hvars(ih_gpp_sec_si_pft)%r82d, & - hio_npp_si_pft => this%hvars(ih_npp_si_pft)%r82d, & + hio_npp_si_pft => this%hvars(ih_npp_si_pft)%r82d, & hio_npp_sec_si_pft => this%hvars(ih_npp_sec_si_pft)%r82d, & hio_fragmentation_scaler_sl => this%hvars(ih_fragmentation_scaler_sl)%r82d, & hio_litter_in_elem => this%hvars(ih_litter_in_elem)%r82d, & @@ -3221,6 +3229,7 @@ subroutine update_history_dyn2(this,nc,nsites,sites,bc_in) hio_npatches_si_age => this%hvars(ih_npatches_si_age)%r82d, & hio_zstar_si_age => this%hvars(ih_zstar_si_age)%r82d, & hio_biomass_si_age => this%hvars(ih_biomass_si_age)%r82d, & + hio_npp_si_age => this%hvars(ih_npp_si_age)%r82d, & hio_agesince_anthrodist_si_age => this%hvars(ih_agesince_anthrodist_si_age)%r82d, & hio_secondarylands_area_si_age => this%hvars(ih_secondarylands_area_si_age)%r82d, & hio_area_si_landuse => this%hvars(ih_area_si_landuse)%r82d, & @@ -3852,6 +3861,11 @@ subroutine update_history_dyn2(this,nc,nsites,sites,bc_in) hio_biomass_si_scls(io_si,scls) = hio_biomass_si_scls(io_si,scls) + & total_m * ccohort%n * AREA_INV + ! age-resolved cohort-based areas + + hio_npp_si_age(io_si,cpatch%age_class) = hio_npp_si_age(io_si,cpatch%age_class) + & + ccohort%n * ccohort%npp_acc_hold * AREA_INV / days_per_year / sec_per_day + ! update size-class x patch-age related quantities iscag = get_sizeage_class_index(ccohort%dbh,cpatch%age) @@ -4873,13 +4887,11 @@ subroutine update_history_hifrq1(this,nc,nsites,sites,bc_in,bc_out,dt_tstep) associate( hio_gpp_si => this%hvars(ih_gpp_si)%r81d, & hio_gpp_secondary_si => this%hvars(ih_gpp_secondary_si)%r81d, & - hio_npp_si => this%hvars(ih_npp_si)%r81d, & hio_npp_secondary_si => this%hvars(ih_npp_secondary_si)%r81d, & hio_aresp_si => this%hvars(ih_aresp_si)%r81d, & hio_aresp_secondary_si => this%hvars(ih_aresp_secondary_si)%r81d, & hio_maint_resp_si => this%hvars(ih_maint_resp_si)%r81d, & hio_maint_resp_secondary_si => this%hvars(ih_maint_resp_secondary_si)%r81d, & - hio_growth_resp_si => this%hvars(ih_growth_resp_si)%r81d, & hio_growth_resp_secondary_si => this%hvars(ih_growth_resp_secondary_si)%r81d, & hio_c_stomata_si => this%hvars(ih_c_stomata_si)%r81d, & hio_c_lblayer_si => this%hvars(ih_c_lblayer_si)%r81d, & @@ -5012,10 +5024,7 @@ subroutine update_history_hifrq1(this,nc,nsites,sites,bc_in,bc_out,dt_tstep) ! scale up cohort fluxes to the site level ! these fluxes have conversions of [kg/plant/timestep] -> [kg/m2/s] - hio_npp_si(io_si) = hio_npp_si(io_si) + & - ccohort%npp_tstep * n_perm2 * dt_tstep_inv - - ! Net Ecosystem Production [kgC/m2/s] + ! Net Ecosystem Production [kgC/m2/s] !cdk: use yesterday's growth respiration hio_nep_si(io_si) = hio_nep_si(io_si) + & ccohort%npp_tstep * n_perm2 * dt_tstep_inv @@ -5025,9 +5034,6 @@ subroutine update_history_hifrq1(this,nc,nsites,sites,bc_in,bc_out,dt_tstep) hio_aresp_si(io_si) = hio_aresp_si(io_si) + & ccohort%resp_tstep * n_perm2 * dt_tstep_inv - hio_growth_resp_si(io_si) = hio_growth_resp_si(io_si) + & - ccohort%resp_g_tstep * n_perm2 * dt_tstep_inv - hio_maint_resp_si(io_si) = hio_maint_resp_si(io_si) + & ccohort%resp_m * n_perm2 * dt_tstep_inv @@ -5036,18 +5042,9 @@ subroutine update_history_hifrq1(this,nc,nsites,sites,bc_in,bc_out,dt_tstep) ! Secondary forest only if(cpatch%land_use_label .eq. secondaryland) then - hio_npp_secondary_si(io_si) = hio_npp_secondary_si(io_si) + & - ccohort%npp_tstep * n_perm2 * dt_tstep_inv - hio_gpp_secondary_si(io_si) = hio_gpp_secondary_si(io_si) + & ccohort%gpp_tstep * n_perm2 * dt_tstep_inv - hio_aresp_secondary_si(io_si) = hio_aresp_secondary_si(io_si) + & - ccohort%resp_tstep * n_perm2 * dt_tstep_inv - - hio_growth_resp_secondary_si(io_si) = hio_growth_resp_secondary_si(io_si) + & - ccohort%resp_g_tstep * n_perm2 * dt_tstep_inv - hio_maint_resp_secondary_si(io_si) = hio_maint_resp_secondary_si(io_si) + & ccohort%resp_m * n_perm2 * dt_tstep_inv end if @@ -5069,17 +5066,11 @@ subroutine update_history_hifrq1(this,nc,nsites,sites,bc_in,bc_out,dt_tstep) hio_gpp_canopy_si(io_si) = hio_gpp_canopy_si(io_si) + & ccohort%gpp_tstep * n_perm2 * dt_tstep_inv - hio_ar_canopy_si(io_si) = hio_ar_canopy_si(io_si) + & - ccohort%resp_tstep * n_perm2 * dt_tstep_inv - else hio_gpp_understory_si(io_si) = hio_gpp_understory_si(io_si) + & ccohort%gpp_tstep * n_perm2 * dt_tstep_inv - hio_ar_understory_si(io_si) = hio_ar_understory_si(io_si) + & - ccohort%resp_tstep * n_perm2 * dt_tstep_inv - end if end if if_notnew @@ -5165,7 +5156,6 @@ subroutine update_history_hifrq2(this,nc,nsites,sites,bc_in,bc_out,dt_tstep) hio_resp_g_understory_si_scls => this%hvars(ih_resp_g_understory_si_scls)%r82d, & hio_resp_m_understory_si_scls => this%hvars(ih_resp_m_understory_si_scls)%r82d, & hio_gpp_si_age => this%hvars(ih_gpp_si_age)%r82d, & - hio_npp_si_age => this%hvars(ih_npp_si_age)%r82d, & hio_c_stomata_si_age => this%hvars(ih_c_stomata_si_age)%r82d, & hio_c_lblayer_si_age => this%hvars(ih_c_lblayer_si_age)%r82d, & hio_parsun_z_si_cnlf => this%hvars(ih_parsun_z_si_cnlf)%r82d, & @@ -5285,9 +5275,6 @@ subroutine update_history_hifrq2(this,nc,nsites,sites,bc_in,bc_out,dt_tstep) hio_gpp_si_age(io_si,cpatch%age_class) = hio_gpp_si_age(io_si,cpatch%age_class) & + ccohort%gpp_tstep * ccohort%n * dt_tstep_inv - hio_npp_si_age(io_si,cpatch%age_class) = hio_npp_si_age(io_si,cpatch%age_class) & - + npp * ccohort%n * dt_tstep_inv - ! accumulate fluxes on canopy- and understory- separated fluxes if (ccohort%canopy_layer .eq. 1) then @@ -5522,11 +5509,8 @@ subroutine update_history_hifrq2(this,nc,nsites,sites,bc_in,bc_out,dt_tstep) if (patch_area_by_age(ipa2) .gt. nearzero) then hio_gpp_si_age(io_si, ipa2) = & hio_gpp_si_age(io_si, ipa2) / (patch_area_by_age(ipa2)) - hio_npp_si_age(io_si, ipa2) = & - hio_npp_si_age(io_si, ipa2) / (patch_area_by_age(ipa2)) else hio_gpp_si_age(io_si, ipa2) = 0._r8 - hio_npp_si_age(io_si, ipa2) = 0._r8 endif ! Normalize resistance diagnostics @@ -6367,6 +6351,18 @@ subroutine define_history_vars(this, initialize_variables) upfreq=group_dyna_simple, ivar=ivar, initialize=initialize_variables, & index = ih_reproc_si) + call this%set_history_var(vname='FATES_NPP', units='kg m-2 s-1', & + long='net primary production in kg carbon per m2 per second', & + use_default='active', avgflag='A', vtype=site_r8, hlms='CLM:ALM', & + upfreq=group_dyna_simple, ivar=ivar, initialize=initialize_variables, index = ih_npp_si) + + call this%set_history_var(vname='FATES_GROWTH_RESP', units='kg m-2 s-1', & + long='growth respiration in kg carbon per m2 per second', & + use_default='active', avgflag='A', vtype=site_r8, hlms='CLM:ALM', & + upfreq=group_dyna_simple, ivar=ivar, initialize=initialize_variables, & + index = ih_growth_resp_si) + + ! Output specific to the chemical species dynamics used (parteh) call this%set_history_var(vname='FATES_L2FR', units='kg kg-1', & long='The leaf to fineroot biomass multiplier for target allometry', & @@ -7383,6 +7379,12 @@ subroutine define_history_vars(this, initialize_variables) hlms='CLM:ALM', upfreq=group_dyna_complx, ivar=ivar, & initialize=initialize_variables, index = ih_npp_si_agepft) + call this%set_history_var(vname='FATES_NPP_AP', units='kg m-2 s-1', & + long='net primary productivity by age bin in kg carbon per m2 per second', & + use_default='inactive', avgflag='A', vtype=site_age_r8, & + hlms='CLM:ALM', upfreq=group_dyna_complx, ivar=ivar, initialize=initialize_variables, & + index = ih_npp_si_age) + call this%set_history_var(vname='FATES_VEGC_APPF',units = 'kg m-2', & long='biomass per PFT in each age bin in kg carbon per m2', & use_default='inactive', avgflag='A', vtype=site_agepft_r8, & @@ -8487,11 +8489,6 @@ subroutine define_history_vars(this, initialize_variables) ! Ecosystem Carbon Fluxes (updated rapidly, upfreq=group_hifr_simple) - call this%set_history_var(vname='FATES_NPP', units='kg m-2 s-1', & - long='net primary production in kg carbon per m2 per second', & - use_default='active', avgflag='A', vtype=site_r8, hlms='CLM:ALM', & - upfreq=group_hifr_simple, ivar=ivar, initialize=initialize_variables, index = ih_npp_si) - call this%set_history_var(vname='FATES_NPP_SECONDARY', units='kg m-2 s-1', & long='net primary production in kg carbon per m2 per second, secondary patches', & use_default='active', avgflag='A', vtype=site_r8, hlms='CLM:ALM', & @@ -8517,12 +8514,6 @@ subroutine define_history_vars(this, initialize_variables) use_default='active', avgflag='A', vtype=site_r8, hlms='CLM:ALM', & upfreq=group_hifr_simple, ivar=ivar, initialize=initialize_variables, index = ih_aresp_secondary_si) - call this%set_history_var(vname='FATES_GROWTH_RESP', units='kg m-2 s-1', & - long='growth respiration in kg carbon per m2 per second', & - use_default='active', avgflag='A', vtype=site_r8, hlms='CLM:ALM', & - upfreq=group_hifr_simple, ivar=ivar, initialize=initialize_variables, & - index = ih_growth_resp_si) - call this%set_history_var(vname='FATES_GROWTH_RESP_SECONDARY', units='kg m-2 s-1', & long='growth respiration in kg carbon per m2 per second, secondary patches', & use_default='active', avgflag='A', vtype=site_r8, hlms='CLM:ALM', & @@ -8669,12 +8660,6 @@ subroutine define_history_vars(this, initialize_variables) ! over the short timestep. We turn off these variables when we want ! to save time (and some space) - call this%set_history_var(vname='FATES_NPP_AP', units='kg m-2 s-1', & - long='net primary productivity by age bin in kg carbon per m2 per second', & - use_default='inactive', avgflag='A', vtype=site_age_r8, & - hlms='CLM:ALM', upfreq=group_hifr_complx, ivar=ivar, initialize=initialize_variables, & - index = ih_npp_si_age) - call this%set_history_var(vname='FATES_GPP_AP', units='kg m-2 s-1', & long='gross primary productivity by age bin in kg carbon per m2 per second', & use_default='inactive', avgflag='A', vtype=site_age_r8, & From 2df2832f64e035d46ae5cd8052b2d1bc84b09d17 Mon Sep 17 00:00:00 2001 From: Charlie Koven Date: Thu, 9 May 2024 14:27:44 -0700 Subject: [PATCH 018/111] more edits to history fields --- biogeophys/FatesPlantRespPhotosynthMod.F90 | 6 +-- main/FatesHistoryInterfaceMod.F90 | 59 +++++++--------------- 2 files changed, 20 insertions(+), 45 deletions(-) diff --git a/biogeophys/FatesPlantRespPhotosynthMod.F90 b/biogeophys/FatesPlantRespPhotosynthMod.F90 index 430ca555bf..e66b9138f9 100644 --- a/biogeophys/FatesPlantRespPhotosynthMod.F90 +++ b/biogeophys/FatesPlantRespPhotosynthMod.F90 @@ -735,7 +735,7 @@ subroutine FatesPlantRespPhotosynthDrive (nsites, sites,bc_in,bc_out,dtime) ! Zero cohort flux accumulators. currentCohort%npp_tstep = 0.0_r8 - currentCohort%resp_tstep = 0.0_r8 + currentCohort%resp_m_tstep = 0.0_r8 currentCohort%gpp_tstep = 0.0_r8 currentCohort%rdark = 0.0_r8 currentCohort%resp_m = 0.0_r8 @@ -987,12 +987,12 @@ subroutine FatesPlantRespPhotosynthDrive (nsites, sites,bc_in,bc_out,dtime) ! convert from kgC/indiv/s to kgC/indiv/timestep currentCohort%resp_m = currentCohort%resp_m * dtime - currentCohort%resp_m_tstep = currentCohort%resp_m ! these two things are the same. we should get rid of one of them. which? + currentCohort%resp_m_tstep = currentCohort%resp_m ! these two things are the same. we should get rid of one them currentCohort%gpp_tstep = currentCohort%gpp_tstep * dtime currentCohort%ts_net_uptake = currentCohort%ts_net_uptake * dtime if ( debug ) write(fates_log(),*) 'EDPhoto 911 ', currentCohort%gpp_tstep - if ( debug ) write(fates_log(),*) 'EDPhoto 912 ', currentCohort%resp_tstep + if ( debug ) write(fates_log(),*) 'EDPhoto 912 ', currentCohort%resp_m_tstep if ( debug ) write(fates_log(),*) 'EDPhoto 913 ', currentCohort%resp_m diff --git a/main/FatesHistoryInterfaceMod.F90 b/main/FatesHistoryInterfaceMod.F90 index a2faecb9c0..a6934c26ea 100644 --- a/main/FatesHistoryInterfaceMod.F90 +++ b/main/FatesHistoryInterfaceMod.F90 @@ -349,11 +349,9 @@ module FatesHistoryInterfaceMod integer :: ih_understory_biomass_si integer :: ih_maint_resp_unreduced_si - integer :: ih_npp_secondary_si + ! these "secondary" flux diagnostics should be reorganized to be site x land-use type rather than just secondary, and also moved to daily timestep. integer :: ih_gpp_secondary_si - integer :: ih_aresp_secondary_si integer :: ih_maint_resp_secondary_si - integer :: ih_growth_resp_secondary_si integer :: ih_primaryland_fusion_error_si @@ -2378,6 +2376,7 @@ subroutine update_history_dyn1(this,nc,nsites,sites,bc_in) hio_litter_in_si => this%hvars(ih_litter_in_si)%r81d, & hio_litter_out_si => this%hvars(ih_litter_out_si)%r81d, & hio_npp_si => this%hvars(ih_npp_si)%r82d, & + hio_aresp_si => this%hvars(ih_aresp_si)%r81d, & hio_growth_resp_si => this%hvars(ih_growth_resp_si)%r81d, & hio_seed_bank_si => this%hvars(ih_seed_bank_si)%r81d, & hio_ungerm_seed_bank_si => this%hvars(ih_ungerm_seed_bank_si)%r81d, & @@ -2822,6 +2821,9 @@ subroutine update_history_dyn1(this,nc,nsites,sites,bc_in) hio_growth_resp_si(io_si) = hio_growth_resp_si(io_si) + & ccohort%resp_g_acc_hold * n_perm2 / days_per_year / sec_per_day + hio_aresp_si(io_si) = hio_aresp_si(io_si) + & + (ccohort%resp_g_acc_hold + ccohort%resp_m_acc_hold) * n_perm2 / days_per_year / sec_per_day + ! Turnover pools [kgC/day] * [day/yr] = [kgC/yr] sapw_m_turnover = ccohort%prt%GetTurnover(sapw_organ, carbon12_element) * days_per_year store_m_turnover = ccohort%prt%GetTurnover(store_organ, carbon12_element) * days_per_year @@ -4887,12 +4889,8 @@ subroutine update_history_hifrq1(this,nc,nsites,sites,bc_in,bc_out,dt_tstep) associate( hio_gpp_si => this%hvars(ih_gpp_si)%r81d, & hio_gpp_secondary_si => this%hvars(ih_gpp_secondary_si)%r81d, & - hio_npp_secondary_si => this%hvars(ih_npp_secondary_si)%r81d, & - hio_aresp_si => this%hvars(ih_aresp_si)%r81d, & - hio_aresp_secondary_si => this%hvars(ih_aresp_secondary_si)%r81d, & hio_maint_resp_si => this%hvars(ih_maint_resp_si)%r81d, & hio_maint_resp_secondary_si => this%hvars(ih_maint_resp_secondary_si)%r81d, & - hio_growth_resp_secondary_si => this%hvars(ih_growth_resp_secondary_si)%r81d, & hio_c_stomata_si => this%hvars(ih_c_stomata_si)%r81d, & hio_c_lblayer_si => this%hvars(ih_c_lblayer_si)%r81d, & hio_vis_rad_err_si => this%hvars(ih_vis_rad_err_si)%r81d, & @@ -5024,16 +5022,14 @@ subroutine update_history_hifrq1(this,nc,nsites,sites,bc_in,bc_out,dt_tstep) ! scale up cohort fluxes to the site level ! these fluxes have conversions of [kg/plant/timestep] -> [kg/m2/s] - ! Net Ecosystem Production [kgC/m2/s] !cdk: use yesterday's growth respiration + ! Net Ecosystem Production [kgC/m2/s]. Use yesterday's growth respiration hio_nep_si(io_si) = hio_nep_si(io_si) + & - ccohort%npp_tstep * n_perm2 * dt_tstep_inv + (ccohort%gpp_tstep-ccohort%resp_m) * n_perm2 * dt_tstep_inv - & + ccohort%resp_g_acc_hold * n_perm2 / days_per_year / sec_per_day hio_gpp_si(io_si) = hio_gpp_si(io_si) + & ccohort%gpp_tstep * n_perm2 * dt_tstep_inv - hio_aresp_si(io_si) = hio_aresp_si(io_si) + & - ccohort%resp_tstep * n_perm2 * dt_tstep_inv - hio_maint_resp_si(io_si) = hio_maint_resp_si(io_si) + & ccohort%resp_m * n_perm2 * dt_tstep_inv @@ -5118,7 +5114,6 @@ subroutine update_history_hifrq2(this,nc,nsites,sites,bc_in,bc_out,dt_tstep) real(r8) :: n_density ! individual of cohort per m2. real(r8) :: resp_g ! growth respiration per timestep [kgC/indiv/step] real(r8) :: npp ! npp for this time-step (adjusted for g resp) [kgC/indiv/step] - real(r8) :: aresp ! autotrophic respiration (adjusted for g resp) [kgC/indiv/step] real(r8) :: n_perm2 ! individuals per m2 for the whole column real(r8) :: patch_area_by_age(nlevage) ! patch area in each bin for normalizing purposes real(r8) :: canopy_area_by_age(nlevage) ! canopy area in each bin for normalizing purposes @@ -5234,21 +5229,17 @@ subroutine update_history_hifrq2(this,nc,nsites,sites,bc_in,bc_out,dt_tstep) if ( .not. ccohort%isnew ) then - npp = ccohort%npp_tstep - resp_g = ccohort%resp_g_tstep - aresp = ccohort%resp_tstep - ! Calculate index for the scpf class associate( scpf => ccohort%size_by_pft_class, & scls => ccohort%size_class ) - ! Total AR (kgC/m2/s) = (kgC/plant/step) / (s/step) * (plant/m2) + ! Total AR (kgC/m2/s) = (kgC/plant/step) / (s/step) * (plant/m2) ! CDK: this should be daily hio_ar_si_scpf(io_si,scpf) = hio_ar_si_scpf(io_si,scpf) + & - (ccohort%resp_tstep*dt_tstep_inv) * n_perm2 + (ccohort%resp_m_tstep*dt_tstep_inv) * n_perm2 - ! Growth AR (kgC/m2/s) + ! Growth AR (kgC/m2/s) ! CDK: this should be daily hio_ar_grow_si_scpf(io_si,scpf) = hio_ar_grow_si_scpf(io_si,scpf) + & - (resp_g*dt_tstep_inv) * n_perm2 + (ccohort%resp_g_tstep*dt_tstep_inv) * n_perm2 ! Maint AR (kgC/m2/s) hio_ar_maint_si_scpf(io_si,scpf) = hio_ar_maint_si_scpf(io_si,scpf) + & @@ -6356,6 +6347,11 @@ subroutine define_history_vars(this, initialize_variables) use_default='active', avgflag='A', vtype=site_r8, hlms='CLM:ALM', & upfreq=group_dyna_simple, ivar=ivar, initialize=initialize_variables, index = ih_npp_si) + call this%set_history_var(vname='FATES_AUTORESP', units='kg m-2 s-1', & + long='autotrophic respiration in kg carbon per m2 per second', & + use_default='active', avgflag='A', vtype=site_r8, hlms='CLM:ALM', & + upfreq=group_dyna_simple, ivar=ivar, initialize=initialize_variables, index = ih_aresp_si) + call this%set_history_var(vname='FATES_GROWTH_RESP', units='kg m-2 s-1', & long='growth respiration in kg carbon per m2 per second', & use_default='active', avgflag='A', vtype=site_r8, hlms='CLM:ALM', & @@ -8489,11 +8485,6 @@ subroutine define_history_vars(this, initialize_variables) ! Ecosystem Carbon Fluxes (updated rapidly, upfreq=group_hifr_simple) - call this%set_history_var(vname='FATES_NPP_SECONDARY', units='kg m-2 s-1', & - long='net primary production in kg carbon per m2 per second, secondary patches', & - use_default='active', avgflag='A', vtype=site_r8, hlms='CLM:ALM', & - upfreq=group_hifr_simple, ivar=ivar, initialize=initialize_variables, index = ih_npp_secondary_si) - call this%set_history_var(vname='FATES_GPP', units='kg m-2 s-1', & long='gross primary production in kg carbon per m2 per second', & use_default='active', avgflag='A', vtype=site_r8, hlms='CLM:ALM', & @@ -8504,22 +8495,6 @@ subroutine define_history_vars(this, initialize_variables) use_default='active', avgflag='A', vtype=site_r8, hlms='CLM:ALM', & upfreq=group_hifr_simple, ivar=ivar, initialize=initialize_variables, index = ih_gpp_secondary_si) - call this%set_history_var(vname='FATES_AUTORESP', units='kg m-2 s-1', & - long='autotrophic respiration in kg carbon per m2 per second', & - use_default='active', avgflag='A', vtype=site_r8, hlms='CLM:ALM', & - upfreq=group_hifr_simple, ivar=ivar, initialize=initialize_variables, index = ih_aresp_si) - - call this%set_history_var(vname='FATES_AUTORESP_SECONDARY', units='kg m-2 s-1', & - long='autotrophic respiration in kg carbon per m2 per second, secondary patches', & - use_default='active', avgflag='A', vtype=site_r8, hlms='CLM:ALM', & - upfreq=group_hifr_simple, ivar=ivar, initialize=initialize_variables, index = ih_aresp_secondary_si) - - call this%set_history_var(vname='FATES_GROWTH_RESP_SECONDARY', units='kg m-2 s-1', & - long='growth respiration in kg carbon per m2 per second, secondary patches', & - use_default='active', avgflag='A', vtype=site_r8, hlms='CLM:ALM', & - upfreq=group_hifr_simple, ivar=ivar, initialize=initialize_variables, & - index = ih_growth_resp_secondary_si) - call this%set_history_var(vname='FATES_MAINT_RESP', units='kg m-2 s-1', & long='maintenance respiration in kg carbon per m2 land area per second', & use_default='active', avgflag='A', vtype=site_r8, hlms='CLM:ALM', & From 57df197813f3b868a61bb396cb5c0d04ad6f679c Mon Sep 17 00:00:00 2001 From: adrifoster Date: Mon, 3 Jun 2024 13:16:42 -0600 Subject: [PATCH 019/111] fix merge errors --- fire/FatesFuelMod.F90 | 2 +- fire/SFMainMod.F90 | 281 +++++++++--------- .../fire/FatesTestFireMod.F90 | 35 ++- .../functional_testing/fire/FatesTestFuel.F90 | 37 ++- .../functional_testing/fire/fuel_plotting.py | 1 - unit_testing/fire/CMakeLists.txt | 27 -- unit_testing/fire/FatesTestFireMod.F90 | 229 -------------- unit_testing/fire/FatesTestFuel.F90 | 95 ------ unit_testing/fire/SyntheticFuelTypes.F90 | 213 ------------- unit_testing/fire/fuel_plotting.py | 60 ---- 10 files changed, 198 insertions(+), 782 deletions(-) delete mode 100644 unit_testing/fire/CMakeLists.txt delete mode 100644 unit_testing/fire/FatesTestFireMod.F90 delete mode 100644 unit_testing/fire/FatesTestFuel.F90 delete mode 100644 unit_testing/fire/SyntheticFuelTypes.F90 delete mode 100644 unit_testing/fire/fuel_plotting.py diff --git a/fire/FatesFuelMod.F90 b/fire/FatesFuelMod.F90 index 50ea2fc88a..2eab02464a 100644 --- a/fire/FatesFuelMod.F90 +++ b/fire/FatesFuelMod.F90 @@ -15,7 +15,7 @@ module FatesFuelMod type, public :: fuel_type real(r8) :: loading(nfsc_notrunks) ! fuel loading of non-trunks fuel class [kgC/m2] - real(r8) :: effective_moisture(nfsc) ! fuel effective moisture all fuel class [m3/m3] + real(r8) :: effective_moisture(nfsc) ! fuel effective moisture all fuel class (moisture/MEF) [m3/m3] real(r8) :: frac_loading(nfsc_notrunks) ! fractional loading of non-trunk fuel classes [0-1] real(r8) :: total_loading ! total fuel loading - DOES NOT INCLUDE TRUNKS [kgC/m2] real(r8) :: average_moisture ! weighted average of fuel moisture across non-trunk fuel classes [m3/m3] diff --git a/fire/SFMainMod.F90 b/fire/SFMainMod.F90 index e65ee8ec8c..99d3fc169a 100644 --- a/fire/SFMainMod.F90 +++ b/fire/SFMainMod.F90 @@ -1,4 +1,4 @@ - module SFMainMod +module SFMainMod ! ============================================================================ ! All subroutines related to the SPITFIRE fire routine. @@ -58,153 +58,152 @@ subroutine fire_model(currentSite, bc_in) ! DESCRIPTION: ! Runs the daily fire model - ! ARGUMENTS: - type(ed_site_type), intent(inout), target :: currentSite ! site object - type(bc_in_type), intent(in) :: bc_in ! BC in object - - ! LOCALS: - type (fates_patch_type), pointer :: currentPatch ! patch object - - ! zero fire things - currentPatch => currentSite%youngest_patch - do while(associated(currentPatch)) - currentPatch%frac_burnt = 0.0_r8 - currentPatch%fire = 0 - currentPatch => currentPatch%older - end do - - if (hlm_spitfire_mode > hlm_sf_nofire_def) then - call UpdateFireWeather(currentSite, bc_in) - call UpdateFuelCharacteristics(currentSite) - call rate_of_spread(currentSite) - call ground_fuel_consumption(currentSite) - call area_burnt_intensity(currentSite, bc_in) - call crown_scorching(currentSite) - call crown_damage(currentSite) - call cambial_damage_kill(currentSite) - call post_fire_mortality(currentSite) - end if - - end subroutine fire_model + ! ARGUMENTS: + type(ed_site_type), intent(inout), target :: currentSite ! site object + type(bc_in_type), intent(in) :: bc_in ! BC in object + + ! LOCALS: + type (fates_patch_type), pointer :: currentPatch ! patch object + + ! zero fire things + currentPatch => currentSite%youngest_patch + do while(associated(currentPatch)) + currentPatch%frac_burnt = 0.0_r8 + currentPatch%fire = 0 + currentPatch => currentPatch%older + end do + + if (hlm_spitfire_mode > hlm_sf_nofire_def) then + call UpdateFireWeather(currentSite, bc_in) + call UpdateFuelCharacteristics(currentSite) + call rate_of_spread(currentSite) + call ground_fuel_consumption(currentSite) + call area_burnt_intensity(currentSite, bc_in) + call crown_scorching(currentSite) + call crown_damage(currentSite) + call cambial_damage_kill(currentSite) + call post_fire_mortality(currentSite) + end if + + end subroutine fire_model !--------------------------------------------------------------------------------------- - subroutine UpdateFireWeather(currentSite, bc_in) - ! - ! DESCRIPTION: - ! Updates the site's fire weather index and calculates effective windspeed based on - ! vegetation characteristics - ! - ! Currently we use tree and grass fraction averaged over whole grid (site) to - ! prevent extreme divergence - - use FatesConstantsMod, only : tfrz => t_water_freeze_k_1atm - use FatesConstantsMod, only : sec_per_day, sec_per_min - use EDTypesMod, only : CalculateTreeGrassArea - - ! CONSTANTS: - real(r8), parameter :: wind_atten_treed = 0.4_r8 ! wind attenuation factor for tree fraction - real(r8), parameter :: wind_atten_grass = 0.6_r8 ! wind attenuation factor for grass fraction - - ! ARGUMENTS: - type(ed_site_type), intent(inout), target :: currentSite - type(bc_in_type), intent(in) :: bc_in - - ! LOCALS: - type(fates_patch_type), pointer :: currentPatch ! patch object - real(r8) :: temp_C ! daily averaged temperature [deg C] - real(r8) :: precip ! daily precip [mm/day] - real(r8) :: rh ! daily relative humidity [%] - real(r8) :: wind ! wind speed [m/s] - real(r8) :: tree_fraction ! site-level tree fraction [0-1] - real(r8) :: grass_fraction ! site-level grass fraction [0-1] - real(r8) :: bare_fraction ! site-level bare ground fraction [0-1] - integer :: iofp ! index of oldest the fates patch - - ! NOTE that the boundary conditions of temperature, precipitation and relative humidity - ! are available at the patch level. We are currently using a simplification where the whole site - ! is simply using the values associated with the first patch. - ! which probably won't have much impact, unless we decide to ever calculated fire weather for each patch. - - currentPatch => currentSite%oldest_patch - - ! If the oldest patch is a bareground patch (i.e. nocomp mode is on) use the first vegetated patch - ! for the iofp index (i.e. the next younger patch) - if (currentPatch%nocomp_pft_label == nocomp_bareground) then - currentPatch => currentPatch%younger - endif - - iofp = currentPatch%patchno - temp_C = currentPatch%tveg24%GetMean() - tfrz - precip = bc_in%precip24_pa(iofp)*sec_per_day - rh = bc_in%relhumid24_pa(iofp) - wind = bc_in%wind24_pa(iofp) - - ! convert to m/min - currentSite%wind = wind*sec_per_min - - ! update fire weather index - call currentSite%fireWeather%UpdateIndex(temp_C, precip, rh, wind) - - ! calculate site-level tree, grass, and bare fraction - call CalculateTreeGrassArea(currentSite, tree_fraction, grass_fraction, bare_fraction) - - ! update effective wind speed - call currentSite%fireWeather%UpdateEffectiveWindSpeed(wind*sec_per_min, tree_fraction, & - grass_fraction, bare_fraction) - - end subroutine UpdateFireWeather - - !-------------------------------------------------------------------------------------- - - subroutine UpdateFuelCharacteristics(currentSite) - ! - ! DESCRIPTION: - ! Updates fuel characteristics on each patch of the site - - use SFParamsMod, only : SF_val_drying_ratio, SF_val_SAV, SF_val_FBD - - ! ARGUMENTS: - type(ed_site_type), intent(in), target :: currentSite ! site object - - ! LOCALS: - type(fates_patch_type), pointer :: currentPatch ! FATES patch - type(litter_type), pointer :: litter ! pointer to patch litter class - - currentPatch => currentSite%oldest_patch - do while(associated(currentPatch)) - - if (currentPatch%nocomp_pft_label /= nocomp_bareground) then - - ! calculate live grass [kgC/m2] - call currentPatch%UpdateLiveGrass() - - ! update fuel loading [kgC/m2] - litter => currentPatch%litter(element_pos(carbon12_element)) - call currentPatch%fuel%CalculateLoading(sum(litter%leaf_fines(:)), & - litter%ag_cwd(1), litter%ag_cwd(2), litter%ag_cwd(3), litter%ag_cwd(4), & - currentPatch%livegrass) - - ! sum up fuel types and calculate fractional loading for each - call currentPatch%fuel%SumLoading() - call currentPatch%fuel%CalculateFractionalLoading() - - ! calculate fuel moisture [m3/m3] - call currentPatch%fuel%UpdateFuelMoisture(SF_val_SAV, SF_val_drying_ratio, & - currentSite%fireWeather) - - ! calculate geometric properties - call currentPatch%fuel%AverageBulkDensity(SF_val_FBD) - call currentPatch%fuel%AverageSAV(SF_val_SAV) + subroutine UpdateFireWeather(currentSite, bc_in) + ! + ! DESCRIPTION: + ! Updates the site's fire weather index and calculates effective windspeed based on + ! vegetation characteristics + ! + ! Currently we use tree and grass fraction averaged over whole grid (site) to + ! prevent extreme divergence + + use FatesConstantsMod, only : tfrz => t_water_freeze_k_1atm + use FatesConstantsMod, only : sec_per_day, sec_per_min + use EDTypesMod, only : CalculateTreeGrassArea + + ! CONSTANTS: + real(r8), parameter :: wind_atten_treed = 0.4_r8 ! wind attenuation factor for tree fraction + real(r8), parameter :: wind_atten_grass = 0.6_r8 ! wind attenuation factor for grass fraction + + ! ARGUMENTS: + type(ed_site_type), intent(inout), target :: currentSite + type(bc_in_type), intent(in) :: bc_in + + ! LOCALS: + type(fates_patch_type), pointer :: currentPatch ! patch object + real(r8) :: temp_C ! daily averaged temperature [deg C] + real(r8) :: precip ! daily precip [mm/day] + real(r8) :: rh ! daily relative humidity [%] + real(r8) :: wind ! wind speed [m/s] + real(r8) :: tree_fraction ! site-level tree fraction [0-1] + real(r8) :: grass_fraction ! site-level grass fraction [0-1] + real(r8) :: bare_fraction ! site-level bare ground fraction [0-1] + integer :: iofp ! index of oldest the fates patch + + ! NOTE that the boundary conditions of temperature, precipitation and relative humidity + ! are available at the patch level. We are currently using a simplification where the whole site + ! is simply using the values associated with the first patch. + ! which probably won't have much impact, unless we decide to ever calculated fire weather for each patch. + + currentPatch => currentSite%oldest_patch + + ! If the oldest patch is a bareground patch (i.e. nocomp mode is on) use the first vegetated patch + ! for the iofp index (i.e. the next younger patch) + if (currentPatch%nocomp_pft_label == nocomp_bareground) then + currentPatch => currentPatch%younger + endif + + iofp = currentPatch%patchno + temp_C = currentPatch%tveg24%GetMean() - tfrz + precip = bc_in%precip24_pa(iofp)*sec_per_day + rh = bc_in%relhumid24_pa(iofp) + wind = bc_in%wind24_pa(iofp) - end if - currentPatch => currentPatch%younger + ! convert to m/min + currentSite%wind = wind*sec_per_min - end do + ! update fire weather index + call currentSite%fireWeather%UpdateIndex(temp_C, precip, rh, wind) - end subroutine UpdateFuelCharacteristics + ! calculate site-level tree, grass, and bare fraction + call CalculateTreeGrassArea(currentSite, tree_fraction, grass_fraction, bare_fraction) - !-------------------------------------------------------------------------------------- + ! update effective wind speed + call currentSite%fireWeather%UpdateEffectiveWindSpeed(wind*sec_per_min, tree_fraction, & + grass_fraction, bare_fraction) + + end subroutine UpdateFireWeather + + !--------------------------------------------------------------------------------------- + + subroutine UpdateFuelCharacteristics(currentSite) + ! + ! DESCRIPTION: + ! Updates fuel characteristics on each patch of the site + + use SFParamsMod, only : SF_val_drying_ratio, SF_val_SAV, SF_val_FBD + + ! ARGUMENTS: + type(ed_site_type), intent(in), target :: currentSite ! site object + + ! LOCALS: + type(fates_patch_type), pointer :: currentPatch ! FATES patch + type(litter_type), pointer :: litter ! pointer to patch litter class + + currentPatch => currentSite%oldest_patch + do while(associated(currentPatch)) + + if (currentPatch%nocomp_pft_label /= nocomp_bareground) then + + ! calculate live grass [kgC/m2] + call currentPatch%UpdateLiveGrass() + + ! update fuel loading [kgC/m2] + litter => currentPatch%litter(element_pos(carbon12_element)) + call currentPatch%fuel%CalculateLoading(sum(litter%leaf_fines(:)), & + litter%ag_cwd(1), litter%ag_cwd(2), litter%ag_cwd(3), litter%ag_cwd(4), & + currentPatch%livegrass) + + ! sum up fuel classes and calculate fractional loading for each + call currentPatch%fuel%SumLoading() + call currentPatch%fuel%CalculateFractionalLoading() + + ! calculate fuel moisture [m3/m3] + call currentPatch%fuel%UpdateFuelMoisture(SF_val_SAV, SF_val_drying_ratio, & + currentSite%fireWeather) + + ! calculate geometric properties + call currentPatch%fuel%AverageBulkDensity(SF_val_FBD) + call currentPatch%fuel%AverageSAV(SF_val_SAV) + + end if + currentPatch => currentPatch%younger + end do + + end subroutine UpdateFuelCharacteristics + + !--------------------------------------------------------------------------------------- subroutine rate_of_spread (currentSite) !*****************************************************************. diff --git a/testing/functional_testing/fire/FatesTestFireMod.F90 b/testing/functional_testing/fire/FatesTestFireMod.F90 index 03316f09be..5c13f0d913 100644 --- a/testing/functional_testing/fire/FatesTestFireMod.F90 +++ b/testing/functional_testing/fire/FatesTestFireMod.F90 @@ -100,7 +100,7 @@ end subroutine ReadDatmData !===================================================================================== subroutine WriteFireData(out_file, nsteps, nfuelmods, temp_degC, precip, rh, NI, & - loading, frac_loading, total_loading, fuel_models) + loading, frac_loading, fuel_BD, fuel_SAV, total_loading, fuel_models) ! ! DESCRIPTION: ! writes out data from the unit test @@ -117,6 +117,8 @@ subroutine WriteFireData(out_file, nsteps, nfuelmods, temp_degC, precip, rh, NI, real(r8), intent(in) :: loading(:,:) real(r8), intent(in) :: frac_loading(:,:) real(r8), intent(in) :: total_loading(:) + real(r8), intent(in) :: fuel_BD(:) + real(r8), intent(in) :: fuel_SAV(:) integer, intent(in) :: fuel_models(:) ! LOCALS: @@ -125,10 +127,14 @@ subroutine WriteFireData(out_file, nsteps, nfuelmods, temp_degC, precip, rh, NI, integer :: i ! looping index character(len=20) :: dim_names(3) ! dimension names integer :: dimIDs(3) ! dimension IDs - integer :: timeID, litterID, modID + ! variable IDS + integer :: timeID, litterID + integer :: modID integer :: tempID, precipID integer :: rhID, NIID, loadingID - integer :: frac_loadingID, tot_loadingID + integer :: frac_loadingID + integer :: tot_loadingID + integer :: BDID, SAVID ! create pft indices allocate(time_index(nsteps)) @@ -164,6 +170,7 @@ subroutine WriteFireData(out_file, nsteps, nfuelmods, temp_degC, precip, rh, NI, [character(len=20) :: 'units', 'long_name'], & [character(len=150) :: '', 'fuel model index'], 2, modID) + ! then register actual variables ! register temperature @@ -199,18 +206,32 @@ subroutine WriteFireData(out_file, nsteps, nfuelmods, temp_degC, precip, rh, NI, ! register fractional fuel loading call RegisterVar(ncid, 'frac_loading', dimIDs(2:3), type_double, & [character(len=20) :: 'coordinates', 'units', 'long_name'], & - [character(len=150) :: 'litter_class fuel_model', '', 'fractional loading'], & + [character(len=150) :: 'litter_class fuel_model', '', 'fractional loading'], & 3, frac_loadingID) ! register total fuel loading - call RegisterVar(ncid, 'total_loading', dimIDs(3:3), type_double, & - [character(len=20) :: 'coordinates', 'units', 'long_name'], & + call RegisterVar(ncid, 'total_loading', dimIDs(3:3), type_double, & + [character(len=20) :: 'coordinates', 'units', 'long_name'], & [character(len=150) :: 'fuel_model', 'kgC m-2', 'total loading'], & 3, tot_loadingID) + ! register fuel bulk density + call RegisterVar(ncid, 'bulk_density', dimIDs(3:3), type_double, & + [character(len=20) :: 'coordinates', 'units', 'long_name'], & + [character(len=150) :: 'fuel_model', 'kg m-3', 'fuel bulk density'], & + 3, BDID) + + ! register fuel SAV + call RegisterVar(ncid, 'SAV', dimIDs(3:3), type_double, & + [character(len=20) :: 'coordinates', 'units', 'long_name'], & + [character(len=150) :: 'fuel_model', 'cm-1', 'fuel surface area to volume ratio'], & + 3, SAVID) + + ! finish defining variables call EndNCDef(ncid) + ! write out data call WriteVar(ncid, timeID, time_index) call WriteVar(ncid, litterID, (/1, 2, 3, 4, 5/)) call WriteVar(ncid, modID, fuel_models) @@ -221,6 +242,8 @@ subroutine WriteFireData(out_file, nsteps, nfuelmods, temp_degC, precip, rh, NI, call WriteVar(ncid, loadingID, loading(:,:)) call WriteVar(ncid, frac_loadingID, frac_loading(:,:)) call WriteVar(ncid, tot_loadingID, total_loading(:)) + call WriteVar(ncid, BDID, fuel_BD(:)) + call WriteVar(ncid, SAVID, fuel_SAV(:)) call CloseNCFile(ncid) diff --git a/testing/functional_testing/fire/FatesTestFuel.F90 b/testing/functional_testing/fire/FatesTestFuel.F90 index 745e5db2e3..56aa10d4c6 100644 --- a/testing/functional_testing/fire/FatesTestFuel.F90 +++ b/testing/functional_testing/fire/FatesTestFuel.F90 @@ -10,6 +10,8 @@ program FatesTestFuel use SFNesterovMod, only : nesterov_index use FatesFuelMod, only : fuel_type use FatesFuelClassesMod, only : nfsc, nfsc_notrunks + use SFParamsMod, only : SF_val_SAV, SF_val_drying_ratio + use SFParamsMod, only : SF_val_FBD implicit none @@ -28,7 +30,9 @@ program FatesTestFuel real(r8), allocatable :: fuel_loading(:,:) ! fuel loading [kgC/m2] real(r8), allocatable :: total_loading(:) ! total fuel loading [kgC/m2] real(r8), allocatable :: frac_loading(:,:) ! fractional fuel loading [0-1] - integer :: i ! looping index + real(r8), allocatable :: fuel_BD(:) ! bulk density of fuel [kg/m3] + real(r8), allocatable :: fuel_SAV(:) ! fuel surface area to volume ratio [/cm] + integer :: i, f ! looping indices integer :: num_fuel_models ! number of fuel models to test ! CONSTANTS: @@ -49,6 +53,8 @@ program FatesTestFuel allocate(NI(n_days)) allocate(fuel_loading(nfsc_notrunks, num_fuel_models)) allocate(frac_loading(nfsc_notrunks, num_fuel_models)) + allocate(fuel_BD(num_fuel_models)) + allocate(fuel_SAV(num_fuel_models)) allocate(total_loading(num_fuel_models)) ! read in parameter file name and DATM file from command line @@ -69,27 +75,40 @@ program FatesTestFuel ! set up fuel objects and calculate loading allocate(fuel(num_fuel_models)) call fuel_types_array%GetFuelModels() - do i = 1, num_fuel_models + do f = 1, num_fuel_models ! uses data from fuel_models to initialize fuel - call SetUpFuel(fuel(i), fuel_types_array, fuel_models(i)) + call SetUpFuel(fuel(f), fuel_types_array, fuel_models(f)) ! sum up fuel and calculate loading - call fuel(i)%SumLoading() - call fuel(i)%CalculateFractionalLoading() - fuel_loading(:,i) = fuel(i)%loading(:) - total_loading(i) = fuel(i)%total_loading - frac_loading(:,i) = fuel(i)%frac_loading(:) + call fuel(f)%SumLoading() + call fuel(f)%CalculateFractionalLoading() + + ! calculate geometric properties + call fuel(f)%AverageBulkDensity(SF_val_FBD) + call fuel(f)%AverageSAV(SF_val_SAV) + + ! save values + fuel_loading(:,f) = fuel(f)%loading(:) + total_loading(f) = fuel(f)%total_loading + frac_loading(:,f) = fuel(f)%frac_loading(:) + fuel_BD(f) = fuel(f)%bulk_density + fuel_SAV(f) = fuel(f)%SAV end do ! run on time steps do i = 1, n_days call fireWeather%UpdateIndex(temp_degC(i), precip(i), rh(i), wind(i)) NI(i) = fireWeather%fire_weather_index + + ! calculate fuel moisture [m3/m3] + do f = 1, num_fuel_models + call fuel(f)%UpdateFuelMoisture(SF_val_SAV, SF_val_drying_ratio, fireWeather) + end do end do ! write out data call WriteFireData(out_file, n_days, num_fuel_models, temp_degC, precip, rh, NI, & - fuel_loading, frac_loading, total_loading, fuel_models) + fuel_loading, frac_loading, fuel_BD, fuel_SAV, total_loading, fuel_models) end program FatesTestFuel \ No newline at end of file diff --git a/testing/functional_testing/fire/fuel_plotting.py b/testing/functional_testing/fire/fuel_plotting.py index 7476e3f190..2003adee93 100644 --- a/testing/functional_testing/fire/fuel_plotting.py +++ b/testing/functional_testing/fire/fuel_plotting.py @@ -40,7 +40,6 @@ def plot_barchart(fuel_dat, var, varname, units, save_figs, plot_dir): plt.ylabel(f'{varname} ({units})', fontsize=11) plt.legend(loc='center left', bbox_to_anchor=(1, 0.5)) - def plot_NI_dat(fuel_dat, save_figs, plot_dir): """Plot output for Nesterov index diff --git a/unit_testing/fire/CMakeLists.txt b/unit_testing/fire/CMakeLists.txt deleted file mode 100644 index b6f0dd3f2f..0000000000 --- a/unit_testing/fire/CMakeLists.txt +++ /dev/null @@ -1,27 +0,0 @@ -set(fire_test_sources - FatesTestFuel.F90 - FatesTestFireMod.F90 - SyntheticFuelTypes.F90) - -set(NETCDF_C_DIR ${NETCDF_C_PATH}) -set(NETCDF_FORTRAN_DIR ${NETCDF_F_PATH}) - -FIND_PATH(NETCDFC_FOUND libnetcdf.a ${NETCDF_C_DIR}/lib) -FIND_PATH(NETCDFF_FOUND libnetcdff.a ${NETCDF_FORTRAN_DIR}/lib) - - -include_directories(${NETCDF_C_DIR}/include - ${NETCDF_FORTRAN_DIR}/include) - -link_directories(${NETCDF_C_DIR}/lib - ${NETCDF_FORTRAN_DIR}/lib - ${PFUNIT_TOP_DIR}/lib) - -add_executable(FATES_fuel_exe ${fire_test_sources}) - -target_link_libraries(FATES_fuel_exe - netcdf - netcdff - fates - csm_share - funit) \ No newline at end of file diff --git a/unit_testing/fire/FatesTestFireMod.F90 b/unit_testing/fire/FatesTestFireMod.F90 deleted file mode 100644 index 03316f09be..0000000000 --- a/unit_testing/fire/FatesTestFireMod.F90 +++ /dev/null @@ -1,229 +0,0 @@ -module FatesTestFireMod - ! - ! DESCRIPTION: - ! Module to support testing the FATES SPIFTIRE model - ! - - use FatesConstantsMod, only : r8 => fates_r8 - use EDTypesMod, only : ed_site_type - use FatesPatchMod, only : fates_patch_type - use SFNesterovMod, only : nesterov_index - use FatesUnitTestIOMod, only : OpenNCFile, GetVar, CloseNCFile, RegisterNCDims - use FatesUnitTestIOMod, only : RegisterVar, EndNCDef, WriteVar - use FatesUnitTestIOMod, only : type_double, type_int - use FatesFuelClassesMod, only : nfsc, nfsc_notrunks - use SyntheticFuelTypes, only : fuel_types_array_class - use SFParamsMod, only : SF_val_CWD_frac - use FatesFuelMod, only : fuel_type - - implicit none - private - - public :: SetUpFuel, ReadDatmData, WriteFireData - - contains - - !===================================================================================== - - subroutine SetUpFuel(fuel, fuel_models, fuel_model_index) - ! - ! DESCRIPTION: - ! Sets up fuel loading - ! - - ! ARGUMENTS: - type(fuel_type), intent(inout) :: fuel ! fuel object - type(fuel_types_array_class), intent(in) :: fuel_models ! array of fuel models - integer, intent(in) :: fuel_model_index ! fuel model index - - ! LOCALS: - integer :: i ! position of fuel model in array - real(r8) :: leaf_litter ! leaf litter [kg/m2] - real(r8) :: twig_litter ! twig litter [kg/m2] - real(r8) :: small_branch_litter ! small branch litter [kg/m2] - real(r8) :: large_branch_litter ! large branch litter [kg/m2] - real(r8) :: grass_litter ! grass litter [kg/m2] - - - ! get fuel model position in array - i = fuel_models%FuelModelPosition(fuel_model_index) - - ! fuel model data - leaf_litter = fuel_models%fuel_types(i)%hr1_loading - twig_litter = fuel_models%fuel_types(i)%hr10_loading - - ! small vs. large branches based on input parameter file - small_branch_litter = fuel_models%fuel_types(i)%hr100_loading*SF_val_CWD_frac(2)/ & - (SF_val_CWD_frac(2) + SF_val_CWD_frac(3)) - large_branch_litter = fuel_models%fuel_types(i)%hr100_loading*SF_val_CWD_frac(3)/ & - (SF_val_CWD_frac(2) + SF_val_CWD_frac(3)) - - grass_litter = fuel_models%fuel_types(i)%live_herb_loading - - call fuel%CalculateLoading(leaf_litter, twig_litter, small_branch_litter, & - large_branch_litter, 0.0_r8, grass_litter) - - end subroutine SetUpFuel - - !===================================================================================== - - subroutine ReadDatmData(nc_file, temp_degC, precip, rh, wind) - ! - ! DESCRIPTION: - ! Reads and returns DATM data - ! - - ! ARGUMENTS: - character(len=*), intent(in) :: nc_file ! netcdf file with DATM data - real(r8), allocatable, intent(out) :: temp_degC(:) ! daily air temperature [degC] - real(r8), allocatable, intent(out) :: precip(:) ! daily precipitation [mm] - real(r8), allocatable, intent(out) :: rh(:) ! daily relative humidity [%] - real(r8), allocatable, intent(out) :: wind(:) ! daily wind speed [m/s] - - ! LOCALS: - integer :: ncid ! netcdf file unit number - - ! open file - call OpenNCFile(trim(nc_file), ncid, 'read') - - ! read in data - call GetVar(ncid, 'temp_degC', temp_degC) - call GetVar(ncid, 'precip', precip) - call GetVar(ncid, 'RH', rh) - call GetVar(ncid, 'wind', wind) - - ! close file - call CloseNCFile(ncid) - - end subroutine ReadDatmData - - !===================================================================================== - - subroutine WriteFireData(out_file, nsteps, nfuelmods, temp_degC, precip, rh, NI, & - loading, frac_loading, total_loading, fuel_models) - ! - ! DESCRIPTION: - ! writes out data from the unit test - ! - - ! ARGUMENTS: - character(len=*), intent(in) :: out_file - integer, intent(in) :: nsteps - integer, intent(in) :: nfuelmods - real(r8), intent(in) :: temp_degC(:) - real(r8), intent(in) :: precip(:) - real(r8), intent(in) :: rh(:) - real(r8), intent(in) :: NI(:) - real(r8), intent(in) :: loading(:,:) - real(r8), intent(in) :: frac_loading(:,:) - real(r8), intent(in) :: total_loading(:) - integer, intent(in) :: fuel_models(:) - - ! LOCALS: - integer, allocatable :: time_index(:) ! array of time index - integer :: ncid ! netcdf id - integer :: i ! looping index - character(len=20) :: dim_names(3) ! dimension names - integer :: dimIDs(3) ! dimension IDs - integer :: timeID, litterID, modID - integer :: tempID, precipID - integer :: rhID, NIID, loadingID - integer :: frac_loadingID, tot_loadingID - - ! create pft indices - allocate(time_index(nsteps)) - do i = 1, nsteps - time_index(i) = i - end do - - ! dimension names - dim_names = [character(len=20) :: 'time', 'litter_class', 'fuel_model'] - - ! open file - call OpenNCFile(trim(out_file), ncid, 'readwrite') - - ! register dimensions - call RegisterNCDims(ncid, dim_names, (/nsteps, nfsc_notrunks, nfuelmods/), 3, dimIDs) - - ! first register dimension variables - - ! register time - call RegisterVar(ncid, 'time', dimIDs(1:1), type_int, & - [character(len=20) :: 'time_origin', 'units', 'calendar', 'long_name'], & - [character(len=150) :: '2018-01-01 00:00:00', 'days since 2018-01-01 00:00:00', & - 'gregorian', 'time'], & - 4, timeID) - - ! register litter class - call RegisterVar(ncid, 'litter_class', dimIDs(2:2), type_int, & - [character(len=20) :: 'units', 'long_name'], & - [character(len=150) :: '', 'fuel class'], 2, litterID) - - ! register fuel models - call RegisterVar(ncid, 'fuel_model', dimIDs(3:3), type_int, & - [character(len=20) :: 'units', 'long_name'], & - [character(len=150) :: '', 'fuel model index'], 2, modID) - - ! then register actual variables - - ! register temperature - call RegisterVar(ncid, 'temp_degC', dimIDs(1:1), type_double, & - [character(len=20) :: 'coordinates', 'units', 'long_name'], & - [character(len=150) :: 'time', 'degrees C', 'air temperature'], & - 3, tempID) - - ! register precipitation - call RegisterVar(ncid, 'precip', dimIDs(1:1), type_double, & - [character(len=20) :: 'coordinates', 'units', 'long_name'], & - [character(len=150) :: 'time', 'mm', 'precipitation'], & - 3, precipID) - - ! register relative humidity - call RegisterVar(ncid, 'RH', dimIDs(1:1), type_double, & - [character(len=20) :: 'coordinates', 'units', 'long_name'], & - [character(len=150) :: 'time', '%', 'relative humidity'], & - 3, rhID) - - ! register Nesterov Index - call RegisterVar(ncid, 'NI', dimIDs(1:1), type_double, & - [character(len=20) :: 'coordinates', 'units', 'long_name'], & - [character(len=150) :: 'time', '', 'Nesterov Index'], & - 3, NIID) - - ! register fuel loading - call RegisterVar(ncid, 'fuel_loading', dimIDs(2:3), type_double, & - [character(len=20) :: 'coordinates', 'units', 'long_name'], & - [character(len=150) :: 'litter_class fuel_model', 'kgC m-2', 'fuel loading'], & - 3, loadingID) - - ! register fractional fuel loading - call RegisterVar(ncid, 'frac_loading', dimIDs(2:3), type_double, & - [character(len=20) :: 'coordinates', 'units', 'long_name'], & - [character(len=150) :: 'litter_class fuel_model', '', 'fractional loading'], & - 3, frac_loadingID) - - ! register total fuel loading - call RegisterVar(ncid, 'total_loading', dimIDs(3:3), type_double, & - [character(len=20) :: 'coordinates', 'units', 'long_name'], & - [character(len=150) :: 'fuel_model', 'kgC m-2', 'total loading'], & - 3, tot_loadingID) - - ! finish defining variables - call EndNCDef(ncid) - - call WriteVar(ncid, timeID, time_index) - call WriteVar(ncid, litterID, (/1, 2, 3, 4, 5/)) - call WriteVar(ncid, modID, fuel_models) - call WriteVar(ncid, tempID, temp_degC(:)) - call WriteVar(ncid, precipID, precip(:)) - call WriteVar(ncid, rhID, rh(:)) - call WriteVar(ncid, NIID, NI(:)) - call WriteVar(ncid, loadingID, loading(:,:)) - call WriteVar(ncid, frac_loadingID, frac_loading(:,:)) - call WriteVar(ncid, tot_loadingID, total_loading(:)) - - call CloseNCFile(ncid) - - end subroutine WriteFireData - -end module FatesTestFireMod \ No newline at end of file diff --git a/unit_testing/fire/FatesTestFuel.F90 b/unit_testing/fire/FatesTestFuel.F90 deleted file mode 100644 index 745e5db2e3..0000000000 --- a/unit_testing/fire/FatesTestFuel.F90 +++ /dev/null @@ -1,95 +0,0 @@ -program FatesTestFuel - - use FatesConstantsMod, only : r8 => fates_r8 - use EDTypesMod, only : ed_site_type - use FatesTestFireMod, only : SetUpFuel, ReadDatmData, WriteFireData - use FatesArgumentUtils, only : command_line_arg - use FatesUnitTestParamReaderMod, only : fates_unit_test_param_reader - use SyntheticFuelTypes, only : fuel_types_array_class - use SFFireWeatherMod, only : fire_weather - use SFNesterovMod, only : nesterov_index - use FatesFuelMod, only : fuel_type - use FatesFuelClassesMod, only : nfsc, nfsc_notrunks - - implicit none - - ! LOCALS: - type(fates_unit_test_param_reader) :: param_reader ! param reader instance - type(fuel_types_array_class) :: fuel_types_array ! array of fuel models - class(fire_weather), pointer :: fireWeather ! fire weather object - type(fuel_type), allocatable :: fuel(:) ! fuel objects - character(len=:), allocatable :: param_file ! input parameter file - character(len=:), allocatable :: datm_file ! input DATM driver file - real(r8), allocatable :: temp_degC(:) ! daily air temperature [degC] - real(r8), allocatable :: precip(:) ! daily precipitation [mm] - real(r8), allocatable :: rh(:) ! daily relative humidity [%] - real(r8), allocatable :: wind(:) ! daily wind speed [m/s] - real(r8), allocatable :: NI(:) ! Nesterov index - real(r8), allocatable :: fuel_loading(:,:) ! fuel loading [kgC/m2] - real(r8), allocatable :: total_loading(:) ! total fuel loading [kgC/m2] - real(r8), allocatable :: frac_loading(:,:) ! fractional fuel loading [0-1] - integer :: i ! looping index - integer :: num_fuel_models ! number of fuel models to test - - ! CONSTANTS: - integer, parameter :: n_days = 365 ! number of days to run simulation - character(len=*), parameter :: out_file = 'fuel_out.nc' ! output file - - ! fuel models to test - integer, parameter, dimension(3) :: fuel_models = (/102, 183, 164/) - - ! number of fuel models to test - num_fuel_models = size(fuel_models) - - ! allocate arrays - allocate(temp_degC(n_days)) - allocate(precip(n_days)) - allocate(rh(n_days)) - allocate(wind(n_days)) - allocate(NI(n_days)) - allocate(fuel_loading(nfsc_notrunks, num_fuel_models)) - allocate(frac_loading(nfsc_notrunks, num_fuel_models)) - allocate(total_loading(num_fuel_models)) - - ! read in parameter file name and DATM file from command line - param_file = command_line_arg(1) - datm_file = command_line_arg(2) - - ! read in parameter file - call param_reader%Init(param_file) - call param_reader%RetrieveParameters() - - ! read in DATM data - call ReadDatmData(datm_file, temp_degC, precip, rh, wind) - - ! set up fire weather class - allocate(nesterov_index :: fireWeather) - call fireWeather%Init() - - ! set up fuel objects and calculate loading - allocate(fuel(num_fuel_models)) - call fuel_types_array%GetFuelModels() - do i = 1, num_fuel_models - - ! uses data from fuel_models to initialize fuel - call SetUpFuel(fuel(i), fuel_types_array, fuel_models(i)) - - ! sum up fuel and calculate loading - call fuel(i)%SumLoading() - call fuel(i)%CalculateFractionalLoading() - fuel_loading(:,i) = fuel(i)%loading(:) - total_loading(i) = fuel(i)%total_loading - frac_loading(:,i) = fuel(i)%frac_loading(:) - end do - - ! run on time steps - do i = 1, n_days - call fireWeather%UpdateIndex(temp_degC(i), precip(i), rh(i), wind(i)) - NI(i) = fireWeather%fire_weather_index - end do - - ! write out data - call WriteFireData(out_file, n_days, num_fuel_models, temp_degC, precip, rh, NI, & - fuel_loading, frac_loading, total_loading, fuel_models) - -end program FatesTestFuel \ No newline at end of file diff --git a/unit_testing/fire/SyntheticFuelTypes.F90 b/unit_testing/fire/SyntheticFuelTypes.F90 deleted file mode 100644 index 7e62209820..0000000000 --- a/unit_testing/fire/SyntheticFuelTypes.F90 +++ /dev/null @@ -1,213 +0,0 @@ -module SyntheticFuelTypes - - use FatesConstantsMod, only : r8 => fates_r8 - - implicit none - private - - integer, parameter :: chunk_size = 10 - real(r8), parameter :: ustons_to_kg = 907.185_r8 - real(r8), parameter :: acres_to_m2 = 4046.86_r8 - real(r8), parameter :: ft_to_m = 0.3048_r8 - - ! holds data for fake fuel models that can be used for functional - ! testing of the FATES fire model - ! these are taken from the fire behavior fuel models in Scott & Burgan 2005 - type, public :: synthetic_fuel_type - - integer :: fuel_model_index ! fuel model index - character(len=2) :: carrier ! carrier ('GR', 'GS', etc.) - character(len=5) :: fuel_model_code ! carrier plus fuel model - character(len=100) :: fuel_model_name ! long name of fuel model - real(r8) :: wind_adj_factor ! wind adjustment factor - real(r8) :: hr1_loading ! fuel loading for 1 hour fuels [kg/m2] - real(r8) :: hr10_loading ! fuel loading for 10 hour fuels [kg/m2] - real(r8) :: hr100_loading ! fuel loading for 100 hour fuels [kg/m2] - real(r8) :: live_herb_loading ! fuel loading for live herbacious fuels [kg/m2] - real(r8) :: live_woody_loading ! fuel loading for live woody fuels [kg/m2] - real(r8) :: fuel_depth ! fuel bed depth [m] - contains - - procedure :: InitFuelModel - - end type synthetic_fuel_type - - ! -------------------------------------------------------------------------------------- - - ! a class to just hold an array of these fuel models - type, public :: fuel_types_array_class - - type(synthetic_fuel_type), allocatable :: fuel_types(:) ! array of fuel models - integer :: num_fuel_types ! number of total fuel models - - contains - - procedure :: AddFuelModel - procedure :: GetFuelModels - procedure :: FuelModelPosition - - end type fuel_types_array_class - - ! -------------------------------------------------------------------------------------- - - contains - - subroutine InitFuelModel(this, fuel_model_index, carrier, fuel_model_name, & - wind_adj_factor, hr1_loading, hr10_loading, hr100_loading, live_herb_loading, & - live_woody_loading, fuel_depth) - ! - ! DESCRIPTION: - ! Initializes the fuel model with input characteristics - ! Also converts units as needed - ! - ! NOTE THE UNITS ON INPUTS - ! - - ! ARGUMENTS: - class(synthetic_fuel_type), intent(inout) :: this - integer, intent(in) :: fuel_model_index ! fuel model index - character(len=2), intent(in) :: carrier ! main carrier - character(len=*), intent(in) :: fuel_model_name ! fuel model long name - real(r8), intent(in) :: wind_adj_factor ! wind adjustment factor - real(r8), intent(in) :: hr1_loading ! loading for 1-hr fuels [tons/acre] - real(r8), intent(in) :: hr10_loading ! loading for 10-hr fuels [tons/acre] - real(r8), intent(in) :: hr100_loading ! loading for 100-hr fuels [tons/acre] - real(r8), intent(in) :: live_herb_loading ! loading for live herbacious fuels [tons/acre] - real(r8), intent(in) :: live_woody_loading ! loading for live woody fuels [tons/acre] - real(r8), intent(in) :: fuel_depth ! fuel bed depth [ft] - - this%fuel_model_index = fuel_model_index - this%carrier = carrier - this%fuel_model_name = fuel_model_name - this%wind_adj_factor = wind_adj_factor - this%hr1_loading = hr1_loading*ustons_to_kg/acres_to_m2*0.45_r8 ! convert to kgC/m2 - this%hr10_loading = hr10_loading*ustons_to_kg/acres_to_m2*0.45_r8 ! convert to kgC/m2 - this%hr100_loading = hr100_loading*ustons_to_kg/acres_to_m2*0.45_r8 ! convert to kgC/m2 - this%live_herb_loading = live_herb_loading*ustons_to_kg/acres_to_m2*0.45_r8 ! convert to kgC/m2 - this%live_woody_loading = live_woody_loading*ustons_to_kg/acres_to_m2*0.45_r8 ! convert to kgC/m2 - this%fuel_depth = fuel_depth*ft_to_m ! convert to m - - end subroutine InitFuelModel - - ! -------------------------------------------------------------------------------------- - - subroutine AddFuelModel(this, fuel_model_index, carrier, fuel_model_name, & - wind_adj_factor, hr1_loading, hr10_loading, hr100_loading, live_herb_loading, & - live_woody_loading, fuel_depth) - ! - ! DESCRIPTION: - ! Adds a fuel model to the dynamic array - ! - ! NOTE THE UNITS ON INPUTS - ! - - ! ARGUMENTS: - class(fuel_types_array_class), intent(inout) :: this ! array of fuel models - integer, intent(in) :: fuel_model_index ! fuel model index - character(len=2), intent(in) :: carrier ! main carrier - character(len=*), intent(in) :: fuel_model_name ! fuel model long name - real(r8), intent(in) :: wind_adj_factor ! wind adjustment factor - real(r8), intent(in) :: hr1_loading ! loading for 1-hr fuels [tons/acre] - real(r8), intent(in) :: hr10_loading ! loading for 10-hr fuels [tons/acre] - real(r8), intent(in) :: hr100_loading ! loading for 100-hr fuels [tons/acre] - real(r8), intent(in) :: live_herb_loading ! loading for live herbacious fuels [tons/acre] - real(r8), intent(in) :: live_woody_loading ! loading for live woody fuels [tons/acre] - real(r8), intent(in) :: fuel_depth ! fuel bed depth [ft] - - ! LOCALS: - type(synthetic_fuel_type) :: fuel_model ! fuel model - type(synthetic_fuel_type), allocatable :: temporary_array(:) ! temporary array to hold data while re-allocating - - ! first make sure we have enough space in the array - if (allocated(this%fuel_types)) then - ! already allocated to some size - if (this%num_fuel_types == size(this%fuel_types)) then - ! need to add more space - allocate(temporary_array(size(this%fuel_types) + chunk_size)) - temporary_array(1:size(this%fuel_types)) = this%fuel_types - call move_alloc(temporary_array, this%fuel_types) - end if - - this%num_fuel_types = this%num_fuel_types + 1 - - else - ! first element in array - allocate(this%fuel_types(chunk_size)) - this%num_fuel_types = 1 - end if - - call fuel_model%InitFuelModel(fuel_model_index, carrier, fuel_model_name, & - wind_adj_factor, hr1_loading, hr10_loading, hr100_loading, live_herb_loading, & - live_woody_loading, fuel_depth) - - this%fuel_types(this%num_fuel_types) = fuel_model - - end subroutine AddFuelModel - - ! -------------------------------------------------------------------------------------- - - integer function FuelModelPosition(this, fuel_model_index) - ! - ! DESCRIPTION: - ! Returns the index of a desired fuel model - ! - - ! ARGUMENTS: - class(fuel_types_array_class), intent(in) :: this ! array of fuel models - integer, intent(in) :: fuel_model_index ! desired fuel model index - - ! LOCALS: - integer :: i ! looping index - - do i = 1, this%num_fuel_types - if (this%fuel_types(i)%fuel_model_index == fuel_model_index) then - FuelModelPosition = i - return - end if - end do - write(*, '(a, i2, a)') "Cannot find the fuel model index ", fuel_model_index, "." - stop - - end function FuelModelPosition - - ! -------------------------------------------------------------------------------------- - - subroutine GetFuelModels(this) - ! - ! DESCRIPTION: - ! Returns an array of hard-coded fuel models - ! these are taken from the fire behavior fuel models in Scott & Burgan 2005 - ! - - ! ARGUMENTS: - class(fuel_types_array_class), intent(inout) :: this ! array of fuel models - - call this%AddFuelModel(fuel_model_index=1, carrier='GR', fuel_model_name='short grass', & - wind_adj_factor=0.36_r8, hr1_loading=0.7_r8, hr10_loading=0.0_r8, hr100_loading=0.0_r8, & - live_herb_loading=0.0_r8, live_woody_loading=0.0_r8, fuel_depth=1.0_r8) - - call this%AddFuelModel(fuel_model_index=2, carrier='GR', fuel_model_name='timber and grass understory', & - wind_adj_factor=0.36_r8, hr1_loading=2.0_r8, hr10_loading=1.0_r8, hr100_loading=0.5_r8, & - live_herb_loading=0.5_r8, live_woody_loading=0.0_r8, fuel_depth=1.0_r8) - - call this%AddFuelModel(fuel_model_index=101, carrier='GR', fuel_model_name='short, sparse dry climate grass', & - wind_adj_factor=0.31_r8, hr1_loading=0.1_r8, hr10_loading=0.0_r8, hr100_loading=0.0_r8, & - live_herb_loading=0.3_r8, live_woody_loading=0.0_r8, fuel_depth=0.4_r8) - - call this%AddFuelModel(fuel_model_index=102, carrier='GR', fuel_model_name='low load dry climate grass', & - wind_adj_factor=0.36_r8, hr1_loading=0.1_r8, hr10_loading=0.0_r8, hr100_loading=0.0_r8, & - live_herb_loading=1.0_r8, live_woody_loading=0.0_r8, fuel_depth=1.0_r8) - - call this%AddFuelModel(fuel_model_index=183, carrier='TL', fuel_model_name='moderate load conifer litter', & - wind_adj_factor=0.29_r8, hr1_loading=0.5_r8, hr10_loading=2.2_r8, hr100_loading=2.8_r8, & - live_herb_loading=0.0_r8, live_woody_loading=0.0_r8, fuel_depth=0.3_r8) - - call this%AddFuelModel(fuel_model_index=164, carrier='TU', fuel_model_name='dwarf conifer with understory', & - wind_adj_factor=0.32_r8, hr1_loading=4.5_r8, hr10_loading=0.0_r8, hr100_loading=0.0_r8, & - live_herb_loading=0.0_r8, live_woody_loading=2.0_r8, fuel_depth=0.5_r8) - - end subroutine GetFuelModels - - ! -------------------------------------------------------------------------------------- - -end module SyntheticFuelTypes \ No newline at end of file diff --git a/unit_testing/fire/fuel_plotting.py b/unit_testing/fire/fuel_plotting.py deleted file mode 100644 index 7476e3f190..0000000000 --- a/unit_testing/fire/fuel_plotting.py +++ /dev/null @@ -1,60 +0,0 @@ -"""Utility functions for fuel functional unit tests -""" -import os -import math -import pandas as pd -import numpy as np -import xarray as xr -import matplotlib -import matplotlib.pyplot as plt - -def plot_fuel_dat(run_dir, out_file, save_figs, plot_dir): - """Plot output associated with fuel tests - - Args: - run_dir (str): run directory - out_file (str): output file - save_figs (bool): whether or not to save the figures - plot_dir (str): plot directory - """ - - fuel_dat = xr.open_dataset(os.path.join(run_dir, out_file)) - - plot_NI_dat(fuel_dat, save_figs, plot_dir) - plot_barchart(fuel_dat, 'fuel_loading', 'Fuel loading', 'kgC m$^{-2}$', save_figs, plot_dir) - plot_barchart(fuel_dat, 'frac_loading', 'Fractional fuel loading', '0-1', save_figs, plot_dir) - -def plot_barchart(fuel_dat, var, varname, units, save_figs, plot_dir): - litter_classes = ['twigs', 'small branches', 'large branches', 'dead leaves', 'live grass'] - - fuel_models = ['Fuel model ' + str(m) for m in np.unique(fuel_dat.fuel_model)] - data_dict = {'{}'.format(lc): fuel_dat.isel(litter_class=i)[var].values for i, lc in enumerate(litter_classes)} - - fig, ax = plt.subplots() - bottom = np.zeros(len(fuel_models)) - for litter_class, dat in data_dict.items(): - p = ax.bar(fuel_models, dat, 0.5, label=litter_class, bottom=bottom) - bottom += dat - box = ax.get_position() - ax.set_position([box.x0, box.y0, box.width * 0.75, box.height]) - plt.ylabel(f'{varname} ({units})', fontsize=11) - plt.legend(loc='center left', bbox_to_anchor=(1, 0.5)) - - -def plot_NI_dat(fuel_dat, save_figs, plot_dir): - """Plot output for Nesterov index - - Args: - fuel_dat (Xarray Dataset): output fuel data - save_figs (bool): whether or not to save the figures - plot_dir (str): plot directory - """ - - plt.figure() - fuel_dat.NI.plot() - plt.xlabel('Time', fontsize=11) - plt.ylabel('Nesterov Index', fontsize=11) - - if save_figs: - fig_name = os.path.join(plot_dir, "Nesterov_plot.png") - plt.savefig(fig_name) \ No newline at end of file From 50e48873f76724d35799fa9634a777c0bc88162a Mon Sep 17 00:00:00 2001 From: adrifoster Date: Tue, 4 Jun 2024 09:42:25 -0600 Subject: [PATCH 020/111] update graph --- .../functional_testing/fire/fuel_plotting.py | 27 +++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/testing/functional_testing/fire/fuel_plotting.py b/testing/functional_testing/fire/fuel_plotting.py index 2003adee93..648234ce00 100644 --- a/testing/functional_testing/fire/fuel_plotting.py +++ b/testing/functional_testing/fire/fuel_plotting.py @@ -23,22 +23,33 @@ def plot_fuel_dat(run_dir, out_file, save_figs, plot_dir): plot_NI_dat(fuel_dat, save_figs, plot_dir) plot_barchart(fuel_dat, 'fuel_loading', 'Fuel loading', 'kgC m$^{-2}$', save_figs, plot_dir) plot_barchart(fuel_dat, 'frac_loading', 'Fractional fuel loading', '0-1', save_figs, plot_dir) + plot_barchart(fuel_dat, 'bulk_density', 'Fuel bulk density', 'kg m$^{-3}$', save_figs, plot_dir, by_litter_type=False) + plot_barchart(fuel_dat, 'SAV', 'Fuel surface area to volume ratio', 'cm$^{-1}$', save_figs, plot_dir, by_litter_type=False) + +def plot_barchart(fuel_dat, var, varname, units, save_figs, plot_dir, by_litter_type=True): -def plot_barchart(fuel_dat, var, varname, units, save_figs, plot_dir): litter_classes = ['twigs', 'small branches', 'large branches', 'dead leaves', 'live grass'] - + colors = ['darksalmon', 'peru', 'saddlebrown', 'moccasin', 'yellowgreen'] fuel_models = ['Fuel model ' + str(m) for m in np.unique(fuel_dat.fuel_model)] - data_dict = {'{}'.format(lc): fuel_dat.isel(litter_class=i)[var].values for i, lc in enumerate(litter_classes)} + + if by_litter_type: + data_dict = {lc: fuel_dat.isel(litter_class=i)[var].values for i, lc in enumerate(litter_classes)} + else: + data_dict = fuel_dat[var].values fig, ax = plt.subplots() - bottom = np.zeros(len(fuel_models)) - for litter_class, dat in data_dict.items(): - p = ax.bar(fuel_models, dat, 0.5, label=litter_class, bottom=bottom) - bottom += dat + if by_litter_type: + bottom = np.zeros(len(fuel_models)) + for i, (litter_class, dat) in enumerate(data_dict.items()): + p = ax.bar(fuel_models, dat, 0.5, label=litter_class, bottom=bottom, color=colors[i]) + bottom += dat + plt.legend(loc='center left', bbox_to_anchor=(1, 0.5)) + else: + p = ax.bar(fuel_models, data_dict, color='darkcyan') + box = ax.get_position() ax.set_position([box.x0, box.y0, box.width * 0.75, box.height]) plt.ylabel(f'{varname} ({units})', fontsize=11) - plt.legend(loc='center left', bbox_to_anchor=(1, 0.5)) def plot_NI_dat(fuel_dat, save_figs, plot_dir): """Plot output for Nesterov index From b6cad147f50f9d4fc586eea77037b0f9cbae5861 Mon Sep 17 00:00:00 2001 From: Jessica Needham <10586303+JessicaNeedham@users.noreply.github.com> Date: Mon, 1 Jul 2024 13:46:59 -0700 Subject: [PATCH 021/111] Update main/FatesHistoryInterfaceMod.F90 --- main/FatesHistoryInterfaceMod.F90 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/FatesHistoryInterfaceMod.F90 b/main/FatesHistoryInterfaceMod.F90 index a6934c26ea..a1b1010445 100644 --- a/main/FatesHistoryInterfaceMod.F90 +++ b/main/FatesHistoryInterfaceMod.F90 @@ -2375,7 +2375,7 @@ subroutine update_history_dyn1(this,nc,nsites,sites,bc_in) hio_sum_fuel_si => this%hvars(ih_sum_fuel_si)%r81d, & hio_litter_in_si => this%hvars(ih_litter_in_si)%r81d, & hio_litter_out_si => this%hvars(ih_litter_out_si)%r81d, & - hio_npp_si => this%hvars(ih_npp_si)%r82d, & + hio_npp_si => this%hvars(ih_npp_si)%r81d, & hio_aresp_si => this%hvars(ih_aresp_si)%r81d, & hio_growth_resp_si => this%hvars(ih_growth_resp_si)%r81d, & hio_seed_bank_si => this%hvars(ih_seed_bank_si)%r81d, & From ce55f1ccf61b261e0d330925a46e61d0948cb697 Mon Sep 17 00:00:00 2001 From: adrifoster Date: Fri, 30 Aug 2024 12:56:03 -0600 Subject: [PATCH 022/111] add rest of fuel models --- .../functional_testing/fire/FatesTestFuel.F90 | 8 +- .../fire/SyntheticFuelTypes.F90 | 266 +++++++++++++++--- 2 files changed, 231 insertions(+), 43 deletions(-) diff --git a/testing/functional_testing/fire/FatesTestFuel.F90 b/testing/functional_testing/fire/FatesTestFuel.F90 index 56aa10d4c6..9a316c79d4 100644 --- a/testing/functional_testing/fire/FatesTestFuel.F90 +++ b/testing/functional_testing/fire/FatesTestFuel.F90 @@ -5,7 +5,7 @@ program FatesTestFuel use FatesTestFireMod, only : SetUpFuel, ReadDatmData, WriteFireData use FatesArgumentUtils, only : command_line_arg use FatesUnitTestParamReaderMod, only : fates_unit_test_param_reader - use SyntheticFuelTypes, only : fuel_types_array_class + use SyntheticFuelModels, only : fuel_models_array_class use SFFireWeatherMod, only : fire_weather use SFNesterovMod, only : nesterov_index use FatesFuelMod, only : fuel_type @@ -17,7 +17,7 @@ program FatesTestFuel ! LOCALS: type(fates_unit_test_param_reader) :: param_reader ! param reader instance - type(fuel_types_array_class) :: fuel_types_array ! array of fuel models + type(fuel_models_array_class) :: fuel_models_array ! array of fuel models class(fire_weather), pointer :: fireWeather ! fire weather object type(fuel_type), allocatable :: fuel(:) ! fuel objects character(len=:), allocatable :: param_file ! input parameter file @@ -74,11 +74,11 @@ program FatesTestFuel ! set up fuel objects and calculate loading allocate(fuel(num_fuel_models)) - call fuel_types_array%GetFuelModels() + call fuel_models_array%GetFuelModels() do f = 1, num_fuel_models ! uses data from fuel_models to initialize fuel - call SetUpFuel(fuel(f), fuel_types_array, fuel_models(f)) + call SetUpFuel(fuel(f), fuel_models_array, fuel_models(f)) ! sum up fuel and calculate loading call fuel(f)%SumLoading() diff --git a/testing/functional_testing/fire/SyntheticFuelTypes.F90 b/testing/functional_testing/fire/SyntheticFuelTypes.F90 index 7e62209820..2d2060b8a1 100644 --- a/testing/functional_testing/fire/SyntheticFuelTypes.F90 +++ b/testing/functional_testing/fire/SyntheticFuelTypes.F90 @@ -1,4 +1,4 @@ -module SyntheticFuelTypes +module SyntheticFuelModels use FatesConstantsMod, only : r8 => fates_r8 @@ -13,7 +13,7 @@ module SyntheticFuelTypes ! holds data for fake fuel models that can be used for functional ! testing of the FATES fire model ! these are taken from the fire behavior fuel models in Scott & Burgan 2005 - type, public :: synthetic_fuel_type + type, public :: synthetic_fuel_model integer :: fuel_model_index ! fuel model index character(len=2) :: carrier ! carrier ('GR', 'GS', etc.) @@ -30,15 +30,15 @@ module SyntheticFuelTypes procedure :: InitFuelModel - end type synthetic_fuel_type + end type synthetic_fuel_model ! -------------------------------------------------------------------------------------- ! a class to just hold an array of these fuel models - type, public :: fuel_types_array_class + type, public :: fuel_models_array_class - type(synthetic_fuel_type), allocatable :: fuel_types(:) ! array of fuel models - integer :: num_fuel_types ! number of total fuel models + type(synthetic_fuel_model), allocatable :: fuel_models(:) ! array of fuel models + integer :: num_fuel_models ! number of total fuel models contains @@ -46,7 +46,7 @@ module SyntheticFuelTypes procedure :: GetFuelModels procedure :: FuelModelPosition - end type fuel_types_array_class + end type fuel_models_array_class ! -------------------------------------------------------------------------------------- @@ -64,17 +64,17 @@ subroutine InitFuelModel(this, fuel_model_index, carrier, fuel_model_name, ! ! ARGUMENTS: - class(synthetic_fuel_type), intent(inout) :: this - integer, intent(in) :: fuel_model_index ! fuel model index - character(len=2), intent(in) :: carrier ! main carrier - character(len=*), intent(in) :: fuel_model_name ! fuel model long name - real(r8), intent(in) :: wind_adj_factor ! wind adjustment factor - real(r8), intent(in) :: hr1_loading ! loading for 1-hr fuels [tons/acre] - real(r8), intent(in) :: hr10_loading ! loading for 10-hr fuels [tons/acre] - real(r8), intent(in) :: hr100_loading ! loading for 100-hr fuels [tons/acre] - real(r8), intent(in) :: live_herb_loading ! loading for live herbacious fuels [tons/acre] - real(r8), intent(in) :: live_woody_loading ! loading for live woody fuels [tons/acre] - real(r8), intent(in) :: fuel_depth ! fuel bed depth [ft] + class(synthetic_fuel_model), intent(inout) :: this + integer, intent(in) :: fuel_model_index ! fuel model index + character(len=2), intent(in) :: carrier ! main carrier + character(len=*), intent(in) :: fuel_model_name ! fuel model long name + real(r8), intent(in) :: wind_adj_factor ! wind adjustment factor + real(r8), intent(in) :: hr1_loading ! loading for 1-hr fuels [tons/acre] + real(r8), intent(in) :: hr10_loading ! loading for 10-hr fuels [tons/acre] + real(r8), intent(in) :: hr100_loading ! loading for 100-hr fuels [tons/acre] + real(r8), intent(in) :: live_herb_loading ! loading for live herbacious fuels [tons/acre] + real(r8), intent(in) :: live_woody_loading ! loading for live woody fuels [tons/acre] + real(r8), intent(in) :: fuel_depth ! fuel bed depth [ft] this%fuel_model_index = fuel_model_index this%carrier = carrier @@ -102,21 +102,21 @@ subroutine AddFuelModel(this, fuel_model_index, carrier, fuel_model_name, ! ! ARGUMENTS: - class(fuel_types_array_class), intent(inout) :: this ! array of fuel models - integer, intent(in) :: fuel_model_index ! fuel model index - character(len=2), intent(in) :: carrier ! main carrier - character(len=*), intent(in) :: fuel_model_name ! fuel model long name - real(r8), intent(in) :: wind_adj_factor ! wind adjustment factor - real(r8), intent(in) :: hr1_loading ! loading for 1-hr fuels [tons/acre] - real(r8), intent(in) :: hr10_loading ! loading for 10-hr fuels [tons/acre] - real(r8), intent(in) :: hr100_loading ! loading for 100-hr fuels [tons/acre] - real(r8), intent(in) :: live_herb_loading ! loading for live herbacious fuels [tons/acre] - real(r8), intent(in) :: live_woody_loading ! loading for live woody fuels [tons/acre] - real(r8), intent(in) :: fuel_depth ! fuel bed depth [ft] + class(fuel_models_array_class), intent(inout) :: this ! array of fuel models + integer, intent(in) :: fuel_model_index ! fuel model index + character(len=2), intent(in) :: carrier ! main carrier + character(len=*), intent(in) :: fuel_model_name ! fuel model long name + real(r8), intent(in) :: wind_adj_factor ! wind adjustment factor + real(r8), intent(in) :: hr1_loading ! loading for 1-hr fuels [tons/acre] + real(r8), intent(in) :: hr10_loading ! loading for 10-hr fuels [tons/acre] + real(r8), intent(in) :: hr100_loading ! loading for 100-hr fuels [tons/acre] + real(r8), intent(in) :: live_herb_loading ! loading for live herbacious fuels [tons/acre] + real(r8), intent(in) :: live_woody_loading ! loading for live woody fuels [tons/acre] + real(r8), intent(in) :: fuel_depth ! fuel bed depth [ft] ! LOCALS: - type(synthetic_fuel_type) :: fuel_model ! fuel model - type(synthetic_fuel_type), allocatable :: temporary_array(:) ! temporary array to hold data while re-allocating + type(synthetic_fuel_model) :: fuel_model ! fuel model + type(synthetic_fuel_model), allocatable :: temporary_array(:) ! temporary array to hold data while re-allocating ! first make sure we have enough space in the array if (allocated(this%fuel_types)) then @@ -153,8 +153,8 @@ integer function FuelModelPosition(this, fuel_model_index) ! ! ARGUMENTS: - class(fuel_types_array_class), intent(in) :: this ! array of fuel models - integer, intent(in) :: fuel_model_index ! desired fuel model index + class(fuel_models_array_class), intent(in) :: this ! array of fuel models + integer, intent(in) :: fuel_model_index ! desired fuel model index ! LOCALS: integer :: i ! looping index @@ -180,7 +180,7 @@ subroutine GetFuelModels(this) ! ! ARGUMENTS: - class(fuel_types_array_class), intent(inout) :: this ! array of fuel models + class(fuel_models_array_class), intent(inout) :: this ! array of fuel models call this%AddFuelModel(fuel_model_index=1, carrier='GR', fuel_model_name='short grass', & wind_adj_factor=0.36_r8, hr1_loading=0.7_r8, hr10_loading=0.0_r8, hr100_loading=0.0_r8, & @@ -190,6 +190,50 @@ subroutine GetFuelModels(this) wind_adj_factor=0.36_r8, hr1_loading=2.0_r8, hr10_loading=1.0_r8, hr100_loading=0.5_r8, & live_herb_loading=0.5_r8, live_woody_loading=0.0_r8, fuel_depth=1.0_r8) + call this%AddFuelModel(fuel_model_index=3, carrier='GR', fuel_model_name='tall grass', & + wind_adj_factor=0.44_r8, hr1_loading=3.0_r8, hr10_loading=0.0_r8, hr100_loading=0.0_r8, & + live_herb_loading=0.0_r8, live_woody_loading=0.0_r8, fuel_depth=2.5_r8) + + call this%AddFuelModel(fuel_model_index=4, carrier='SH', fuel_model_name='chapparal', & + wind_adj_factor=0.55_r8, hr1_loading=5.0_r8, hr10_loading=4.0_r8, hr100_loading=2.0_r8, & + live_herb_loading=0.0_r8, live_woody_loading=5.0_r8, fuel_depth=6.0_r8) + + call this%AddFuelModel(fuel_model_index=5, carrier='SH', fuel_model_name='brush', & + wind_adj_factor=0.42_r8, hr1_loading=1.0_r8, hr10_loading=0.5_r8, hr100_loading=0.0_r8, & + live_herb_loading=0.0_r8, live_woody_loading=2.0_r8, fuel_depth=2.0_r8) + + call this%AddFuelModel(fuel_model_index=6, carrier='SH', fuel_model_name='dormant brush', & + wind_adj_factor=0.44_r8, hr1_loading=1.5_r8, hr10_loading=2.5_r8, hr100_loading=2.0_r8, & + live_herb_loading=0.0_r8, live_woody_loading=0.0_r8, fuel_depth=2.5_r8) + + call this%AddFuelModel(fuel_model_index=7, carrier='SH', fuel_model_name='southern rough', & + wind_adj_factor=0.44_r8, hr1_loading=1.1_r8, hr10_loading=1.9_r8, hr100_loading=1.0_r8, & + live_herb_loading=0.0_r8, live_woody_loading=0.4_r8, fuel_depth=2.5_r8) + + call this%AddFuelModel(fuel_model_index=8, carrier='TL', fuel_model_name='compact timber litter', & + wind_adj_factor=0.28_r8, hr1_loading=1.5_r8, hr10_loading=1.0_r8, hr100_loading=2.5_r8, & + live_herb_loading=0.0_r8, live_woody_loading=0.0_r8, fuel_depth=0.2_r8) + + call this%AddFuelModel(fuel_model_index=9, carrier='TL', fuel_model_name='hardwood litter', & + wind_adj_factor=0.28_r8, hr1_loading=2.9_r8, hr10_loading=0.4_r8, hr100_loading=0.2_r8, & + live_herb_loading=0.0_r8, live_woody_loading=0.0_r8, fuel_depth=0.2_r8) + + call this%AddFuelModel(fuel_model_index=10, carrier='TU', fuel_model_name='timber and litter understorey', & + wind_adj_factor=0.46_r8, hr1_loading=3.0_r8, hr10_loading=2.0_r8, hr100_loading=5.0_r8, & + live_herb_loading=0.0_r8, live_woody_loading=2.0_r8, fuel_depth=1.0_r8) + + call this%AddFuelModel(fuel_model_index=11, carrier='SB', fuel_model_name='light slash', & + wind_adj_factor=0.36_r8, hr1_loading=1.5_r8, hr10_loading=4.5_r8, hr100_loading=5.5_r8, & + live_herb_loading=0.0_r8, live_woody_loading=0.0_r8, fuel_depth=1.0_r8) + + call this%AddFuelModel(fuel_model_index=12, carrier='SB', fuel_model_name='medium slash', & + wind_adj_factor=0.43_r8, hr1_loading=4.0_r8, hr10_loading=14.0_r8, hr100_loading=16.5_r8, & + live_herb_loading=0.0_r8, live_woody_loading=0.0_r8, fuel_depth=2.3_r8) + + call this%AddFuelModel(fuel_model_index=13, carrier='SB', fuel_model_name='heavy slash', & + wind_adj_factor=0.46_r8, hr1_loading=7.0_r8, hr10_loading=23.0_r8, hr100_loading=28.1_r8, & + live_herb_loading=0.0_r8, live_woody_loading=0.0_r8, fuel_depth=3.0_r8) + call this%AddFuelModel(fuel_model_index=101, carrier='GR', fuel_model_name='short, sparse dry climate grass', & wind_adj_factor=0.31_r8, hr1_loading=0.1_r8, hr10_loading=0.0_r8, hr100_loading=0.0_r8, & live_herb_loading=0.3_r8, live_woody_loading=0.0_r8, fuel_depth=0.4_r8) @@ -198,16 +242,160 @@ subroutine GetFuelModels(this) wind_adj_factor=0.36_r8, hr1_loading=0.1_r8, hr10_loading=0.0_r8, hr100_loading=0.0_r8, & live_herb_loading=1.0_r8, live_woody_loading=0.0_r8, fuel_depth=1.0_r8) - call this%AddFuelModel(fuel_model_index=183, carrier='TL', fuel_model_name='moderate load conifer litter', & - wind_adj_factor=0.29_r8, hr1_loading=0.5_r8, hr10_loading=2.2_r8, hr100_loading=2.8_r8, & - live_herb_loading=0.0_r8, live_woody_loading=0.0_r8, fuel_depth=0.3_r8) + call this%AddFuelModel(fuel_model_index=103, carrier='GR', fuel_model_name='low load very coarse humid climate grass', & + wind_adj_factor=0.42_r8, hr1_loading=0.1_r8, hr10_loading=0.4_r8, hr100_loading=0.0_r8, & + live_herb_loading=1.5_r8, live_woody_loading=0.0_r8, fuel_depth=2.0_r8) + + call this%AddFuelModel(fuel_model_index=104, carrier='GR', fuel_model_name='moderate load dry climate grass', & + wind_adj_factor=0.42_r8, hr1_loading=0.3_r8, hr10_loading=0.0_r8, hr100_loading=0.0_r8, & + live_herb_loading=1.9_r8, live_woody_loading=0.0_r8, fuel_depth=2.0_r8) + + call this%AddFuelModel(fuel_model_index=105, carrier='GR', fuel_model_name='low load humid climate grass', & + wind_adj_factor=0.39_r8, hr1_loading=0.4_r8, hr10_loading=0.0_r8, hr100_loading=0.0_r8, & + live_herb_loading=2.5_r8, live_woody_loading=0.0_r8, fuel_depth=1.5_r8) + + call this%AddFuelModel(fuel_model_index=106, carrier='GR', fuel_model_name='moderate load humid climate grass', & + wind_adj_factor=0.39_r8, hr1_loading=0.1_r8, hr10_loading=0.0_r8, hr100_loading=0.0_r8, & + live_herb_loading=3.4_r8, live_woody_loading=0.0_r8, fuel_depth=1.5_r8) + + call this%AddFuelModel(fuel_model_index=107, carrier='GR', fuel_model_name='high load dry climate grass', & + wind_adj_factor=0.46_r8, hr1_loading=1.0_r8, hr10_loading=0.0_r8, hr100_loading=0.0_r8, & + live_herb_loading=5.4_r8, live_woody_loading=0.0_r8, fuel_depth=3.0_r8) + + call this%AddFuelModel(fuel_model_index=108, carrier='GR', fuel_model_name='high load humid climate grass', & + wind_adj_factor=0.49_r8, hr1_loading=0.5_r8, hr10_loading=1.0_r8, hr100_loading=0.0_r8, & + live_herb_loading=7.3_r8, live_woody_loading=0.0_r8, fuel_depth=4.0_r8) + + call this%AddFuelModel(fuel_model_index=109, carrier='GR', fuel_model_name='very high load humid climate grass-shrub', & + wind_adj_factor=0.52_r8, hr1_loading=1.0_r8, hr10_loading=1.0_r8, hr100_loading=0.0_r8, & + live_herb_loading=9.0_r8, live_woody_loading=0.0_r8, fuel_depth=5.0_r8) + + call this%AddFuelModel(fuel_model_index=121, carrier='GS', fuel_model_name='low load dry climate grass-shrub', & + wind_adj_factor=0.35_r8, hr1_loading=0.2_r8, hr10_loading=0.0_r8, hr100_loading=0.0_r8, & + live_herb_loading=0.5_r8, live_woody_loading=0.7_r8, fuel_depth=0.9_r8) + + call this%AddFuelModel(fuel_model_index=122, carrier='GS', fuel_model_name='moderate load dry climate grass-shrub', & + wind_adj_factor=0.39_r8, hr1_loading=0.5_r8, hr10_loading=0.5_r8, hr100_loading=0.0_r8, & + live_herb_loading=0.6_r8, live_woody_loading=1.0_r8, fuel_depth=1.5_r8) + + call this%AddFuelModel(fuel_model_index=123, carrier='GS', fuel_model_name='moderate load humid climate grass-shrub', & + wind_adj_factor=0.41_r8, hr1_loading=0.3_r8, hr10_loading=0.3_r8, hr100_loading=0.0_r8, & + live_herb_loading=1.5_r8, live_woody_loading=1.3_r8, fuel_depth=1.8_r8) + + call this%AddFuelModel(fuel_model_index=124, carrier='GS', fuel_model_name='high load humid climate grass-shrub', & + wind_adj_factor=0.42_r8, hr1_loading=1.9_r8, hr10_loading=0.3_r8, hr100_loading=0.1_r8, & + live_herb_loading=3.4_r8, live_woody_loading=7.1_r8, fuel_depth=2.1_r8) + + call this%AddFuelModel(fuel_model_index=141, carrier='SH', fuel_model_name='low load dry climate shrub', & + wind_adj_factor=0.36_r8, hr1_loading=0.3_r8, hr10_loading=0.3_r8, hr100_loading=0.0_r8, & + live_herb_loading=0.2_r8, live_woody_loading=1.3_r8, fuel_depth=1.0_r8) + + call this%AddFuelModel(fuel_model_index=142, carrier='SH', fuel_model_name='moderate load dry climate shrub', & + wind_adj_factor=0.36_r8, hr1_loading=1.4_r8, hr10_loading=2.4_r8, hr100_loading=0.8_r8, & + live_herb_loading=0.0_r8, live_woody_loading=3.9_r8, fuel_depth=1.0_r8) + + call this%AddFuelModel(fuel_model_index=143, carrier='SH', fuel_model_name='moderate load humid climate shrub', & + wind_adj_factor=0.44_r8, hr1_loading=0.5_r8, hr10_loading=3.0_r8, hr100_loading=0.0_r8, & + live_herb_loading=0.0_r8, live_woody_loading=6.2_r8, fuel_depth=2.4_r8) + + call this%AddFuelModel(fuel_model_index=144, carrier='SH', fuel_model_name='low load humid climate timber-shrub', & + wind_adj_factor=0.46_r8, hr1_loading=0.9_r8, hr10_loading=1.2_r8, hr100_loading=0.2_r8, & + live_herb_loading=0.0_r8, live_woody_loading=2.6_r8, fuel_depth=3.0_r8) + + call this%AddFuelModel(fuel_model_index=145, carrier='SH', fuel_model_name='high load dry climate shrub', & + wind_adj_factor=0.55_r8, hr1_loading=3.6_r8, hr10_loading=2.1_r8, hr100_loading=0.0_r8, & + live_herb_loading=0.0_r8, live_woody_loading=2.9_r8, fuel_depth=6.0_r8) + + call this%AddFuelModel(fuel_model_index=146, carrier='SH', fuel_model_name='low load humid climate shrub', & + wind_adj_factor=0.42_r8, hr1_loading=2.9_r8, hr10_loading=1.5_r8, hr100_loading=0.0_r8, & + live_herb_loading=0.0_r8, live_woody_loading=1.4_r8, fuel_depth=2.0_r8) + + call this%AddFuelModel(fuel_model_index=147, carrier='SH', fuel_model_name='very high load dry climate shrub', & + wind_adj_factor=0.55_r8, hr1_loading=3.5_r8, hr10_loading=5.3_r8, hr100_loading=2.2_r8, & + live_herb_loading=0.0_r8, live_woody_loading=3.4_r8, fuel_depth=6.0_r8) + + call this%AddFuelModel(fuel_model_index=148, carrier='SH', fuel_model_name='high load humid climate shrub', & + wind_adj_factor=0.46_r8, hr1_loading=2.1_r8, hr10_loading=3.4_r8, hr100_loading=0.9_r8, & + live_herb_loading=0.0_r8, live_woody_loading=4.4_r8, fuel_depth=3.0_r8) + + call this%AddFuelModel(fuel_model_index=149, carrier='SH', fuel_model_name='very high load humid climate shrub', & + wind_adj_factor=0.5_r8, hr1_loading=4.5_r8, hr10_loading=2.5_r8, hr100_loading=0.0_r8, & + live_herb_loading=1.6_r8, live_woody_loading=7.0_r8, fuel_depth=4.4_r8) + + call this%AddFuelModel(fuel_model_index=161, carrier='TU', fuel_model_name='light load dry climate timber-grass-shrub', & + wind_adj_factor=0.33_r8, hr1_loading=0.2_r8, hr10_loading=0.9_r8, hr100_loading=1.5_r8, & + live_herb_loading=0.2_r8, live_woody_loading=0.9_r8, fuel_depth=0.6_r8) + + call this%AddFuelModel(fuel_model_index=162, carrier='TU', fuel_model_name='moderate load humid climate timber-shrub', & + wind_adj_factor=0.36_r8, hr1_loading=1.0_r8, hr10_loading=1.8_r8, hr100_loading=1.3_r8, & + live_herb_loading=0.0_r8, live_woody_loading=0.2_r8, fuel_depth=1.0_r8) + + call this%AddFuelModel(fuel_model_index=163, carrier='TU', fuel_model_name='moderate load humid climate timber-grass-shrub', & + wind_adj_factor=0.38_r8, hr1_loading=1.1_r8, hr10_loading=0.2_r8, hr100_loading=0.2_r8, & + live_herb_loading=0.3_r8, live_woody_loading=0.7_r8, fuel_depth=1.3_r8) call this%AddFuelModel(fuel_model_index=164, carrier='TU', fuel_model_name='dwarf conifer with understory', & wind_adj_factor=0.32_r8, hr1_loading=4.5_r8, hr10_loading=0.0_r8, hr100_loading=0.0_r8, & live_herb_loading=0.0_r8, live_woody_loading=2.0_r8, fuel_depth=0.5_r8) + + call this%AddFuelModel(fuel_model_index=165, carrier='TU', fuel_model_name='very high load dry climate timber-shrub', & + wind_adj_factor=0.33_r8, hr1_loading=4.0_r8, hr10_loading=4.0_r8, hr100_loading=3.0_r8, & + live_herb_loading=0.0_r8, live_woody_loading=3.0_r8, fuel_depth=1.0_r8) + + call this%AddFuelModel(fuel_model_index=181, carrier='TL', fuel_model_name='low load compact conifer litter', & + wind_adj_factor=0.28_r8, hr1_loading=1.0_r8, hr10_loading=2.2_r8, hr100_loading=3.6_r8, & + live_herb_loading=0.0_r8, live_woody_loading=0.0_r8, fuel_depth=0.2_r8) + + call this%AddFuelModel(fuel_model_index=182, carrier='TL', fuel_model_name='low load broadleaf litter', & + wind_adj_factor=0.28_r8, hr1_loading=1.4_r8, hr10_loading=2.3_r8, hr100_loading=2.2_r8, & + live_herb_loading=0.0_r8, live_woody_loading=0.0_r8, fuel_depth=0.2_r8) + + call this%AddFuelModel(fuel_model_index=183, carrier='TL', fuel_model_name='moderate load conifer litter', & + wind_adj_factor=0.29_r8, hr1_loading=0.5_r8, hr10_loading=2.2_r8, hr100_loading=2.8_r8, & + live_herb_loading=0.0_r8, live_woody_loading=0.0_r8, fuel_depth=0.3_r8) + + call this%AddFuelModel(fuel_model_index=184, carrier='TL', fuel_model_name='small downed logs', & + wind_adj_factor=0.31_r8, hr1_loading=0.5_r8, hr10_loading=1.5_r8, hr100_loading=4.2_r8, & + live_herb_loading=0.0_r8, live_woody_loading=0.0_r8, fuel_depth=0.4_r8) + + call this%AddFuelModel(fuel_model_index=185, carrier='TL', fuel_model_name='high load conifer litter', & + wind_adj_factor=0.33_r8, hr1_loading=1.2_r8, hr10_loading=2.5_r8, hr100_loading=4.4_r8, & + live_herb_loading=0.0_r8, live_woody_loading=0.0_r8, fuel_depth=0.6_r8) + + call this%AddFuelModel(fuel_model_index=186, carrier='TL', fuel_model_name='moderate load broadleaf litter', & + wind_adj_factor=0.29_r8, hr1_loading=2.4_r8, hr10_loading=1.2_r8, hr100_loading=1.2_r8, & + live_herb_loading=0.0_r8, live_woody_loading=0.0_r8, fuel_depth=0.3_r8) + + call this%AddFuelModel(fuel_model_index=187, carrier='TL', fuel_model_name='large downed logs', & + wind_adj_factor=0.31_r8, hr1_loading=0.3_r8, hr10_loading=1.4_r8, hr100_loading=8.1_r8, & + live_herb_loading=0.0_r8, live_woody_loading=0.0_r8, fuel_depth=0.4_r8) + + call this%AddFuelModel(fuel_model_index=188, carrier='TL', fuel_model_name='long-needle litter', & + wind_adj_factor=0.29_r8, hr1_loading=5.0_r8, hr10_loading=1.4_r8, hr100_loading=1.1_r8, & + live_herb_loading=0.0_r8, live_woody_loading=0.0_r8, fuel_depth=0.3_r8) + + call this%AddFuelModel(fuel_model_index=189, carrier='TL', fuel_model_name='very high load broadleaf litter', & + wind_adj_factor=0.33_r8, hr1_loading=6.7_r8, hr10_loading=3.3_r8, hr100_loading=4.2_r8, & + live_herb_loading=0.0_r8, live_woody_loading=0.0_r8, fuel_depth=0.6_r8) + + call this%AddFuelModel(fuel_model_index=201, carrier='SB', fuel_model_name='low load activity fuel', & + wind_adj_factor=0.36_r8, hr1_loading=1.5_r8, hr10_loading=3.0_r8, hr100_loading=11.1_r8, & + live_herb_loading=0.0_r8, live_woody_loading=0.0_r8, fuel_depth=1.0_r8) + + call this%AddFuelModel(fuel_model_index=202, carrier='SB', fuel_model_name='moderate load activity fuel or low load blowdown', & + wind_adj_factor=0.36_r8, hr1_loading=4.5_r8, hr10_loading=4.3_r8, hr100_loading=4.0_r8, & + live_herb_loading=0.0_r8, live_woody_loading=0.0_r8, fuel_depth=1.0_r8) + + call this%AddFuelModel(fuel_model_index=203, carrier='SB', fuel_model_name='high load activity fuel or moderate load blowdown', & + wind_adj_factor=0.38_r8, hr1_loading=5.5_r8, hr10_loading=2.8_r8, hr100_loading=3.0_r8, & + live_herb_loading=0.0_r8, live_woody_loading=0.0_r8, fuel_depth=1.2_r8) + + call this%AddFuelModel(fuel_model_index=204, carrier='SB', fuel_model_name='high load blowdown', & + wind_adj_factor=0.45_r8, hr1_loading=5.3_r8, hr10_loading=3.5_r8, hr100_loading=5.3_r8, & + live_herb_loading=0.0_r8, live_woody_loading=0.0_r8, fuel_depth=2.7_r8) - end subroutine GetFuelModels + end subroutine GetFuelModels ! -------------------------------------------------------------------------------------- -end module SyntheticFuelTypes \ No newline at end of file +end module SyntheticFuelModels \ No newline at end of file From d9ff87b7b8c2c103464858d34057cf4b93d212d2 Mon Sep 17 00:00:00 2001 From: adrifoster Date: Fri, 30 Aug 2024 13:41:55 -0600 Subject: [PATCH 023/111] rename some things --- .../functional_testing/fire/CMakeLists.txt | 2 +- .../fire/FatesTestFireMod.F90 | 84 ++++++++++++------- .../functional_testing/fire/FatesTestFuel.F90 | 57 ++++++++----- ...cFuelTypes.F90 => SyntheticFuelModels.F90} | 22 ++--- testing/testing_shr/FatesUnitTestIOMod.F90 | 41 ++++++++- 5 files changed, 142 insertions(+), 64 deletions(-) rename testing/functional_testing/fire/{SyntheticFuelTypes.F90 => SyntheticFuelModels.F90} (97%) diff --git a/testing/functional_testing/fire/CMakeLists.txt b/testing/functional_testing/fire/CMakeLists.txt index b6f0dd3f2f..273fce5c43 100644 --- a/testing/functional_testing/fire/CMakeLists.txt +++ b/testing/functional_testing/fire/CMakeLists.txt @@ -1,7 +1,7 @@ set(fire_test_sources FatesTestFuel.F90 FatesTestFireMod.F90 - SyntheticFuelTypes.F90) + SyntheticFuelModels.F90) set(NETCDF_C_DIR ${NETCDF_C_PATH}) set(NETCDF_FORTRAN_DIR ${NETCDF_F_PATH}) diff --git a/testing/functional_testing/fire/FatesTestFireMod.F90 b/testing/functional_testing/fire/FatesTestFireMod.F90 index 5c13f0d913..e81d273f47 100644 --- a/testing/functional_testing/fire/FatesTestFireMod.F90 +++ b/testing/functional_testing/fire/FatesTestFireMod.F90 @@ -10,9 +10,9 @@ module FatesTestFireMod use SFNesterovMod, only : nesterov_index use FatesUnitTestIOMod, only : OpenNCFile, GetVar, CloseNCFile, RegisterNCDims use FatesUnitTestIOMod, only : RegisterVar, EndNCDef, WriteVar - use FatesUnitTestIOMod, only : type_double, type_int + use FatesUnitTestIOMod, only : type_double, type_int, type_char use FatesFuelClassesMod, only : nfsc, nfsc_notrunks - use SyntheticFuelTypes, only : fuel_types_array_class + use SyntheticFuelModels, only : fuel_models_array_class use SFParamsMod, only : SF_val_CWD_frac use FatesFuelMod, only : fuel_type @@ -25,16 +25,18 @@ module FatesTestFireMod !===================================================================================== - subroutine SetUpFuel(fuel, fuel_models, fuel_model_index) + subroutine SetUpFuel(fuel, fuel_model_array, fuel_model_index, fuel_name, fuel_carrier) ! ! DESCRIPTION: ! Sets up fuel loading ! ! ARGUMENTS: - type(fuel_type), intent(inout) :: fuel ! fuel object - type(fuel_types_array_class), intent(in) :: fuel_models ! array of fuel models - integer, intent(in) :: fuel_model_index ! fuel model index + type(fuel_type), intent(inout) :: fuel ! fuel object + type(fuel_models_array_class), intent(in) :: fuel_model_array ! array of fuel models + integer, intent(in) :: fuel_model_index ! fuel model index + character(len=100), intent(out) :: fuel_name ! name of fuel model + character(len=2), intent(out) :: fuel_carrier ! fuel carrier for fuel model ! LOCALS: integer :: i ! position of fuel model in array @@ -46,19 +48,22 @@ subroutine SetUpFuel(fuel, fuel_models, fuel_model_index) ! get fuel model position in array - i = fuel_models%FuelModelPosition(fuel_model_index) + i = fuel_model_array%FuelModelPosition(fuel_model_index) ! fuel model data - leaf_litter = fuel_models%fuel_types(i)%hr1_loading - twig_litter = fuel_models%fuel_types(i)%hr10_loading + leaf_litter = fuel_model_array%fuel_models(i)%hr1_loading + twig_litter = fuel_model_array%fuel_models(i)%hr10_loading ! small vs. large branches based on input parameter file - small_branch_litter = fuel_models%fuel_types(i)%hr100_loading*SF_val_CWD_frac(2)/ & + small_branch_litter = fuel_model_array%fuel_models(i)%hr100_loading*SF_val_CWD_frac(2)/ & (SF_val_CWD_frac(2) + SF_val_CWD_frac(3)) - large_branch_litter = fuel_models%fuel_types(i)%hr100_loading*SF_val_CWD_frac(3)/ & + large_branch_litter = fuel_model_array%fuel_models(i)%hr100_loading*SF_val_CWD_frac(3)/ & (SF_val_CWD_frac(2) + SF_val_CWD_frac(3)) - grass_litter = fuel_models%fuel_types(i)%live_herb_loading + grass_litter = fuel_model_array%fuel_models(i)%live_herb_loading + + fuel_name = fuel_model_array%fuel_models(i)%fuel_model_name + fuel_carrier = fuel_model_array%fuel_models(i)%carrier call fuel%CalculateLoading(leaf_litter, twig_litter, small_branch_litter, & large_branch_litter, 0.0_r8, grass_litter) @@ -100,27 +105,30 @@ end subroutine ReadDatmData !===================================================================================== subroutine WriteFireData(out_file, nsteps, nfuelmods, temp_degC, precip, rh, NI, & - loading, frac_loading, fuel_BD, fuel_SAV, total_loading, fuel_models) + loading, frac_loading, fuel_BD, fuel_SAV, total_loading, fuel_moisture, & + fuel_models, carriers) ! ! DESCRIPTION: ! writes out data from the unit test ! ! ARGUMENTS: - character(len=*), intent(in) :: out_file - integer, intent(in) :: nsteps - integer, intent(in) :: nfuelmods - real(r8), intent(in) :: temp_degC(:) - real(r8), intent(in) :: precip(:) - real(r8), intent(in) :: rh(:) - real(r8), intent(in) :: NI(:) - real(r8), intent(in) :: loading(:,:) - real(r8), intent(in) :: frac_loading(:,:) - real(r8), intent(in) :: total_loading(:) - real(r8), intent(in) :: fuel_BD(:) - real(r8), intent(in) :: fuel_SAV(:) - integer, intent(in) :: fuel_models(:) - + character(len=*), intent(in) :: out_file + integer, intent(in) :: nsteps + integer, intent(in) :: nfuelmods + real(r8), intent(in) :: temp_degC(:) + real(r8), intent(in) :: precip(:) + real(r8), intent(in) :: rh(:) + real(r8), intent(in) :: NI(:) + real(r8), intent(in) :: loading(:,:) + real(r8), intent(in) :: frac_loading(:,:) + real(r8), intent(in) :: total_loading(:) + real(r8), intent(in) :: fuel_moisture(:,:) + real(r8), intent(in) :: fuel_BD(:) + real(r8), intent(in) :: fuel_SAV(:) + integer, intent(in) :: fuel_models(:) + character(len=2), intent(in) :: carriers(:) + ! LOCALS: integer, allocatable :: time_index(:) ! array of time index integer :: ncid ! netcdf id @@ -135,13 +143,15 @@ subroutine WriteFireData(out_file, nsteps, nfuelmods, temp_degC, precip, rh, NI, integer :: frac_loadingID integer :: tot_loadingID integer :: BDID, SAVID + integer :: moistID + integer :: cID ! create pft indices allocate(time_index(nsteps)) do i = 1, nsteps time_index(i) = i end do - + ! dimension names dim_names = [character(len=20) :: 'time', 'litter_class', 'fuel_model'] @@ -170,8 +180,13 @@ subroutine WriteFireData(out_file, nsteps, nfuelmods, temp_degC, precip, rh, NI, [character(len=20) :: 'units', 'long_name'], & [character(len=150) :: '', 'fuel model index'], 2, modID) - ! then register actual variables + + ! register fuel carriers + call RegisterVar(ncid, 'carrier', dimIDs(3:3), type_char, & + [character(len=20) :: 'coordinates', 'units', 'long_name'], & + [character(len=150) :: 'fuel_model_index', '', 'carrier of fuel'], & + 3, cID) ! register temperature call RegisterVar(ncid, 'temp_degC', dimIDs(1:1), type_double, & @@ -197,6 +212,12 @@ subroutine WriteFireData(out_file, nsteps, nfuelmods, temp_degC, precip, rh, NI, [character(len=150) :: 'time', '', 'Nesterov Index'], & 3, NIID) + ! register fuel moisture + call RegisterVar(ncid, 'fuel_moisture', (/dimIDs(1), dimIDs(3)/), type_double, & + [character(len=20) :: 'coordinates', 'units', 'long_name'], & + [character(len=150) :: 'time fuel_model', 'm3 m-3', 'average fuel moisture'], & + 3, moistID) + ! register fuel loading call RegisterVar(ncid, 'fuel_loading', dimIDs(2:3), type_double, & [character(len=20) :: 'coordinates', 'units', 'long_name'], & @@ -227,14 +248,14 @@ subroutine WriteFireData(out_file, nsteps, nfuelmods, temp_degC, precip, rh, NI, [character(len=150) :: 'fuel_model', 'cm-1', 'fuel surface area to volume ratio'], & 3, SAVID) - ! finish defining variables call EndNCDef(ncid) ! write out data call WriteVar(ncid, timeID, time_index) call WriteVar(ncid, litterID, (/1, 2, 3, 4, 5/)) - call WriteVar(ncid, modID, fuel_models) + call WriteVar(ncid, modID, fuel_models(:)) + call WriteVar(ncid, cID, carriers(:)) call WriteVar(ncid, tempID, temp_degC(:)) call WriteVar(ncid, precipID, precip(:)) call WriteVar(ncid, rhID, rh(:)) @@ -242,6 +263,7 @@ subroutine WriteFireData(out_file, nsteps, nfuelmods, temp_degC, precip, rh, NI, call WriteVar(ncid, loadingID, loading(:,:)) call WriteVar(ncid, frac_loadingID, frac_loading(:,:)) call WriteVar(ncid, tot_loadingID, total_loading(:)) + call WriteVar(ncid, moistiD, fuel_moisture(:,:)) call WriteVar(ncid, BDID, fuel_BD(:)) call WriteVar(ncid, SAVID, fuel_SAV(:)) diff --git a/testing/functional_testing/fire/FatesTestFuel.F90 b/testing/functional_testing/fire/FatesTestFuel.F90 index 9a316c79d4..18f56749fb 100644 --- a/testing/functional_testing/fire/FatesTestFuel.F90 +++ b/testing/functional_testing/fire/FatesTestFuel.F90 @@ -16,31 +16,42 @@ program FatesTestFuel implicit none ! LOCALS: - type(fates_unit_test_param_reader) :: param_reader ! param reader instance + type(fates_unit_test_param_reader) :: param_reader ! param reader instance type(fuel_models_array_class) :: fuel_models_array ! array of fuel models - class(fire_weather), pointer :: fireWeather ! fire weather object - type(fuel_type), allocatable :: fuel(:) ! fuel objects - character(len=:), allocatable :: param_file ! input parameter file - character(len=:), allocatable :: datm_file ! input DATM driver file - real(r8), allocatable :: temp_degC(:) ! daily air temperature [degC] - real(r8), allocatable :: precip(:) ! daily precipitation [mm] - real(r8), allocatable :: rh(:) ! daily relative humidity [%] - real(r8), allocatable :: wind(:) ! daily wind speed [m/s] - real(r8), allocatable :: NI(:) ! Nesterov index - real(r8), allocatable :: fuel_loading(:,:) ! fuel loading [kgC/m2] - real(r8), allocatable :: total_loading(:) ! total fuel loading [kgC/m2] - real(r8), allocatable :: frac_loading(:,:) ! fractional fuel loading [0-1] - real(r8), allocatable :: fuel_BD(:) ! bulk density of fuel [kg/m3] - real(r8), allocatable :: fuel_SAV(:) ! fuel surface area to volume ratio [/cm] - integer :: i, f ! looping indices - integer :: num_fuel_models ! number of fuel models to test + class(fire_weather), pointer :: fireWeather ! fire weather object + type(fuel_type), allocatable :: fuel(:) ! fuel objects + character(len=:), allocatable :: param_file ! input parameter file + character(len=:), allocatable :: datm_file ! input DATM driver file + real(r8), allocatable :: temp_degC(:) ! daily air temperature [degC] + real(r8), allocatable :: precip(:) ! daily precipitation [mm] + real(r8), allocatable :: rh(:) ! daily relative humidity [%] + real(r8), allocatable :: wind(:) ! daily wind speed [m/s] + real(r8), allocatable :: NI(:) ! Nesterov index + real(r8), allocatable :: fuel_loading(:,:) ! fuel loading [kgC/m2] + real(r8), allocatable :: total_loading(:) ! total fuel loading [kgC/m2] + real(r8), allocatable :: frac_loading(:,:) ! fractional fuel loading [0-1] + real(r8), allocatable :: fuel_BD(:) ! bulk density of fuel [kg/m3] + real(r8), allocatable :: fuel_SAV(:) ! fuel surface area to volume ratio [/cm] + real(r8), allocatable :: fuel_moisture(:,:) ! fuel moisture [m3/m3] + character(len=100), allocatable :: fuel_names(:) ! names of fuel models + character(len=2), allocatable :: carriers(:) ! carriers of fuel models + integer :: i, f ! looping indices + integer :: num_fuel_models ! number of fuel models to test ! CONSTANTS: integer, parameter :: n_days = 365 ! number of days to run simulation character(len=*), parameter :: out_file = 'fuel_out.nc' ! output file ! fuel models to test - integer, parameter, dimension(3) :: fuel_models = (/102, 183, 164/) + !integer, parameter, dimension(3) :: fuel_models = (/102, 183, 164/) + integer, parameter, dimension(52) :: fuel_models = (/1, 2, 101, 102, 104, 107, 121, & + 122, 3, 103, 105, 106, 108, 109, & + 123, 124, 4, 5, 6, 141, 142, 145, & + 147, 161, 164, 10, 7, 143, 144, & + 146, 148, 149, 162, 163, 8, 9, & + 181, 182, 183, 184, 185, 186, 187, & + 188, 189, 11, 12, 13, 201, 202, & + 203, 204/) ! number of fuel models to test num_fuel_models = size(fuel_models) @@ -51,11 +62,14 @@ program FatesTestFuel allocate(rh(n_days)) allocate(wind(n_days)) allocate(NI(n_days)) + allocate(fuel_moisture(n_days, num_fuel_models)) allocate(fuel_loading(nfsc_notrunks, num_fuel_models)) allocate(frac_loading(nfsc_notrunks, num_fuel_models)) allocate(fuel_BD(num_fuel_models)) allocate(fuel_SAV(num_fuel_models)) allocate(total_loading(num_fuel_models)) + allocate(fuel_names(num_fuel_models)) + allocate(carriers(num_fuel_models)) ! read in parameter file name and DATM file from command line param_file = command_line_arg(1) @@ -78,7 +92,7 @@ program FatesTestFuel do f = 1, num_fuel_models ! uses data from fuel_models to initialize fuel - call SetUpFuel(fuel(f), fuel_models_array, fuel_models(f)) + call SetUpFuel(fuel(f), fuel_models_array, fuel_models(f), fuel_names(f), carriers(f)) ! sum up fuel and calculate loading call fuel(f)%SumLoading() @@ -94,6 +108,7 @@ program FatesTestFuel frac_loading(:,f) = fuel(f)%frac_loading(:) fuel_BD(f) = fuel(f)%bulk_density fuel_SAV(f) = fuel(f)%SAV + end do ! run on time steps @@ -104,11 +119,13 @@ program FatesTestFuel ! calculate fuel moisture [m3/m3] do f = 1, num_fuel_models call fuel(f)%UpdateFuelMoisture(SF_val_SAV, SF_val_drying_ratio, fireWeather) + fuel_moisture(i, f) = fuel(f)%average_moisture end do end do ! write out data call WriteFireData(out_file, n_days, num_fuel_models, temp_degC, precip, rh, NI, & - fuel_loading, frac_loading, fuel_BD, fuel_SAV, total_loading, fuel_models) + fuel_loading, frac_loading, fuel_BD, fuel_SAV, total_loading, fuel_moisture, & + fuel_models, carriers) end program FatesTestFuel \ No newline at end of file diff --git a/testing/functional_testing/fire/SyntheticFuelTypes.F90 b/testing/functional_testing/fire/SyntheticFuelModels.F90 similarity index 97% rename from testing/functional_testing/fire/SyntheticFuelTypes.F90 rename to testing/functional_testing/fire/SyntheticFuelModels.F90 index 2d2060b8a1..c04db80437 100644 --- a/testing/functional_testing/fire/SyntheticFuelTypes.F90 +++ b/testing/functional_testing/fire/SyntheticFuelModels.F90 @@ -119,28 +119,28 @@ subroutine AddFuelModel(this, fuel_model_index, carrier, fuel_model_name, type(synthetic_fuel_model), allocatable :: temporary_array(:) ! temporary array to hold data while re-allocating ! first make sure we have enough space in the array - if (allocated(this%fuel_types)) then + if (allocated(this%fuel_models)) then ! already allocated to some size - if (this%num_fuel_types == size(this%fuel_types)) then + if (this%num_fuel_models == size(this%fuel_models)) then ! need to add more space - allocate(temporary_array(size(this%fuel_types) + chunk_size)) - temporary_array(1:size(this%fuel_types)) = this%fuel_types - call move_alloc(temporary_array, this%fuel_types) + allocate(temporary_array(size(this%fuel_models) + chunk_size)) + temporary_array(1:size(this%fuel_models)) = this%fuel_models + call move_alloc(temporary_array, this%fuel_models) end if - this%num_fuel_types = this%num_fuel_types + 1 + this%num_fuel_models = this%num_fuel_models + 1 else ! first element in array - allocate(this%fuel_types(chunk_size)) - this%num_fuel_types = 1 + allocate(this%fuel_models(chunk_size)) + this%num_fuel_models = 1 end if call fuel_model%InitFuelModel(fuel_model_index, carrier, fuel_model_name, & wind_adj_factor, hr1_loading, hr10_loading, hr100_loading, live_herb_loading, & live_woody_loading, fuel_depth) - this%fuel_types(this%num_fuel_types) = fuel_model + this%fuel_models(this%num_fuel_models) = fuel_model end subroutine AddFuelModel @@ -159,8 +159,8 @@ integer function FuelModelPosition(this, fuel_model_index) ! LOCALS: integer :: i ! looping index - do i = 1, this%num_fuel_types - if (this%fuel_types(i)%fuel_model_index == fuel_model_index) then + do i = 1, this%num_fuel_models + if (this%fuel_models(i)%fuel_model_index == fuel_model_index) then FuelModelPosition = i return end if diff --git a/testing/testing_shr/FatesUnitTestIOMod.F90 b/testing/testing_shr/FatesUnitTestIOMod.F90 index 8f6ea1141a..20dd4f198e 100644 --- a/testing/testing_shr/FatesUnitTestIOMod.F90 +++ b/testing/testing_shr/FatesUnitTestIOMod.F90 @@ -10,7 +10,8 @@ module FatesUnitTestIOMod ! LOCALS integer, public, parameter :: type_double = 1 ! type integer, public, parameter :: type_int = 2 ! type - + integer, public, parameter :: type_char = 3 ! type + interface GetVar module procedure GetVarScalarReal module procedure GetVar1DReal @@ -26,6 +27,8 @@ module FatesUnitTestIOMod module procedure WriteVar2DReal module procedure WriteVar1DInt module procedure WriteVar2DInt + module procedure WriteVar1DChar + module procedure WriteVar2DChar end interface public :: OpenNCFile @@ -473,6 +476,8 @@ subroutine RegisterVar(ncid, var_name, dimID, type, att_names, atts, num_atts, v nc_type = NF90_DOUBLE else if (type == type_int) then nc_type = NF90_INT + else if (type == type_char) then + nc_type = NF90_CHAR else write(*, *) "Must pick correct type" stop @@ -570,5 +575,39 @@ subroutine WriteVar2DInt(ncid, varID, data) end subroutine WriteVar2DInt ! ===================================================================================== + + subroutine WriteVar1DChar(ncid, varID, data) + ! + ! DESCRIPTION: + ! Write 1D character data + ! + + ! ARGUMENTS: + integer, intent(in) :: ncid ! netcdf file id + integer, intent(in) :: varID ! variable ID + character(len=*), intent(in) :: data(:) ! data to write + + call Check(nf90_put_var(ncid, varID, data(:))) + + end subroutine WriteVar1DChar + + ! ===================================================================================== + + subroutine WriteVar2DChar(ncid, varID, data) + ! + ! DESCRIPTION: + ! Write 2D character data + ! + + ! ARGUMENTS: + integer, intent(in) :: ncid ! netcdf file id + integer, intent(in) :: varID ! variable ID + character(len=*), intent(in) :: data(:,:) ! data to write + + call Check(nf90_put_var(ncid, varID, data(:,:))) + + end subroutine WriteVar2DChar + + ! ===================================================================================== end module FatesUnitTestIOMod \ No newline at end of file From ca3ed12574cd27301471023753815337c4bcfd98 Mon Sep 17 00:00:00 2001 From: adrifoster Date: Fri, 30 Aug 2024 15:20:40 -0600 Subject: [PATCH 024/111] refactor --- biogeochem/EDPatchDynamicsMod.F90 | 18 +++---- biogeochem/FatesPatchMod.F90 | 6 +-- fire/FatesFuelMod.F90 | 79 +++++++++++++++++++++++++++++++ fire/SFMainMod.F90 | 54 ++------------------- main/EDInitMod.F90 | 2 - main/FatesHistoryInterfaceMod.F90 | 2 +- 6 files changed, 95 insertions(+), 66 deletions(-) diff --git a/biogeochem/EDPatchDynamicsMod.F90 b/biogeochem/EDPatchDynamicsMod.F90 index e07ac6d28d..e64a240c03 100644 --- a/biogeochem/EDPatchDynamicsMod.F90 +++ b/biogeochem/EDPatchDynamicsMod.F90 @@ -764,11 +764,11 @@ subroutine spawn_patches( currentSite, bc_in) ! Transfer the litter existing already in the donor patch to the new patch ! This call will only transfer non-burned litter to new patch - ! and burned litter to atmosphere. Thus it is important to zero burnt_frac_litter when + ! and burned litter to atmosphere. Thus it is important to zero fuel%frac_burnt when ! fire is not the current disturbance regime. if(i_disturbance_type .ne. dtype_ifire) then - currentPatch%burnt_frac_litter(:) = 0._r8 + currentPatch%fuel%frac_burnt(:) = 0._r8 end if call CopyPatchMeansTimers(currentPatch, newPatch) @@ -1052,7 +1052,7 @@ subroutine spawn_patches( currentSite, bc_in) ! Grasses determine their fraction of leaves burned here - leaf_burn_frac = currentPatch%burnt_frac_litter(lg_sf) + leaf_burn_frac = currentPatch%fuel%frac_burnt(fuel_classes%live_grass()) endif ! Perform a check to make sure that spitfire gave @@ -1720,7 +1720,7 @@ subroutine split_patch(currentSite, currentPatch, new_patch, fraction_to_keep, a call TransLitterNewPatch( currentSite, currentPatch, new_patch, temp_area) - currentPatch%burnt_frac_litter(:) = 0._r8 + currentPatch%fuel%frac_burnt(:) = 0._r8 ! Next, we loop through the cohorts in the donor patch, copy them with ! area modified number density into the new-patch, and apply survivorship. @@ -2075,10 +2075,10 @@ subroutine TransLitterNewPatch(currentSite, & ! Transfer above ground CWD donatable_mass = curr_litt%ag_cwd(c) * patch_site_areadis * & - (1._r8 - currentPatch%burnt_frac_litter(c)) + (1._r8 - currentPatch%fuel%frac_burnt(c)) burned_mass = curr_litt%ag_cwd(c) * patch_site_areadis * & - currentPatch%burnt_frac_litter(c) + currentPatch%fuel%frac_burnt(c) new_litt%ag_cwd(c) = new_litt%ag_cwd(c) + donatable_mass*donate_m2 curr_litt%ag_cwd(c) = curr_litt%ag_cwd(c) + donatable_mass*retain_m2 @@ -2099,10 +2099,10 @@ subroutine TransLitterNewPatch(currentSite, & ! Transfer leaf fines donatable_mass = curr_litt%leaf_fines(dcmpy) * patch_site_areadis * & - (1._r8 - currentPatch%burnt_frac_litter(dl_sf)) + (1._r8 - currentPatch%fuel%frac_burnt(fuel_clases%dead_leaves())) burned_mass = curr_litt%leaf_fines(dcmpy) * patch_site_areadis * & - currentPatch%burnt_frac_litter(dl_sf) + currentPatch%fuel%frac_burnt(fuel_classes%dead_leaves()) new_litt%leaf_fines(dcmpy) = new_litt%leaf_fines(dcmpy) + donatable_mass*donate_m2 curr_litt%leaf_fines(dcmpy) = curr_litt%leaf_fines(dcmpy) + donatable_mass*retain_m2 @@ -3297,7 +3297,7 @@ subroutine fuse_2_patches(csite, dp, rp) rp%ros_back = (dp%ros_back*dp%area + rp%ros_back*rp%area) * inv_sum_area rp%scorch_ht(:) = (dp%scorch_ht(:)*dp%area + rp%scorch_ht(:)*rp%area) * inv_sum_area rp%frac_burnt = (dp%frac_burnt*dp%area + rp%frac_burnt*rp%area) * inv_sum_area - rp%burnt_frac_litter(:) = (dp%burnt_frac_litter(:)*dp%area + rp%burnt_frac_litter(:)*rp%area) * inv_sum_area + rp%fuel%frac_burnt(:) = (dp%fuel%frac_burnt(:)*dp%area + rp%fuel%frac_burnt(:)*rp%area) * inv_sum_area rp%btran_ft(:) = (dp%btran_ft(:)*dp%area + rp%btran_ft(:)*rp%area) * inv_sum_area rp%zstar = (dp%zstar*dp%area + rp%zstar*rp%area) * inv_sum_area rp%c_stomata = (dp%c_stomata*dp%area + rp%c_stomata*rp%area) * inv_sum_area diff --git a/biogeochem/FatesPatchMod.F90 b/biogeochem/FatesPatchMod.F90 index c1e7bfe448..7029a5770c 100644 --- a/biogeochem/FatesPatchMod.F90 +++ b/biogeochem/FatesPatchMod.F90 @@ -219,8 +219,6 @@ module FatesPatchMod real(r8) :: scorch_ht(maxpft) ! scorch height [m] real(r8) :: frac_burnt ! fraction burnt [0-1/day] real(r8) :: tfc_ros ! total intensity-relevant fuel consumed - no trunks [kgC/m2 of burned ground/day] - real(r8) :: burnt_frac_litter(nfsc) ! fraction of each litter pool burned, conditional on it being burned [0-1] - !--------------------------------------------------------------------------- ! PLANT HYDRAULICS (not currently used in hydraulics RGK 03-2018) @@ -510,8 +508,7 @@ subroutine NanValues(this) this%scorch_ht(:) = nan this%frac_burnt = nan this%tfc_ros = nan - this%burnt_frac_litter(:) = nan - + end subroutine NanValues !=========================================================================== @@ -596,7 +593,6 @@ subroutine ZeroValues(this) this%scorch_ht(:) = 0.0_r8 this%frac_burnt = 0.0_r8 this%tfc_ros = 0.0_r8 - this%burnt_frac_litter(:) = 0.0_r8 end subroutine ZeroValues diff --git a/fire/FatesFuelMod.F90 b/fire/FatesFuelMod.F90 index 2eab02464a..b17ea071be 100644 --- a/fire/FatesFuelMod.F90 +++ b/fire/FatesFuelMod.F90 @@ -15,8 +15,10 @@ module FatesFuelMod type, public :: fuel_type real(r8) :: loading(nfsc_notrunks) ! fuel loading of non-trunks fuel class [kgC/m2] + real(r8) :: trunk_loading ! fuel loading of trunk fuel class [kgC/m2] real(r8) :: effective_moisture(nfsc) ! fuel effective moisture all fuel class (moisture/MEF) [m3/m3] real(r8) :: frac_loading(nfsc_notrunks) ! fractional loading of non-trunk fuel classes [0-1] + real(r8) :: frac_burnt(nfsc) ! fraction of litter burnt by fire [0-1] real(r8) :: total_loading ! total fuel loading - DOES NOT INCLUDE TRUNKS [kgC/m2] real(r8) :: average_moisture ! weighted average of fuel moisture across non-trunk fuel classes [m3/m3] real(r8) :: bulk_density ! weighted average of bulk density across non-trunk fuel classes [kg/m3] @@ -32,6 +34,7 @@ module FatesFuelMod procedure :: UpdateFuelMoisture procedure :: AverageBulkDensity procedure :: AverageSAV + procedure :: BurnFuel end type fuel_type @@ -46,7 +49,9 @@ subroutine Init(this) ! just zero everything this%loading(1:nfsc_notrunks) = 0.0_r8 + this%trunk_loading = 0.0_r8 this%frac_loading(1:nfsc_notrunks) = 0.0_r8 + this%frac_burnt(1:nfsc) = 0.0_r8 this%effective_moisture(1:nfsc) = 0.0_r8 this%total_loading = 0.0_r8 this%average_moisture = 0.0_r8 @@ -77,6 +82,7 @@ subroutine CalculateLoading(this, leaf_litter, twig_litter, small_branch_litter, this%loading(fuel_classes%small_branches()) = small_branch_litter this%loading(fuel_classes%large_branches()) = large_branch_litter this%loading(fuel_classes%live_grass()) = live_grass + this%trunk_loading = trunk_litter end subroutine CalculateLoading @@ -163,6 +169,79 @@ end subroutine UpdateFuelMoisture !------------------------------------------------------------------------------------- + real(r8) function CalculateFractionBurnt(effective_moisture, min_moisture, & + mid_moisture, moisture_coeff_low, moisture_slope_low, moisture_coeff_mid, & + moisture_slope_mid) + ! DESCRIPTION: + ! Calculates fraction burnt of fuel based on input fuel moisture + + ! Based on Equation B1 from Thonicke et al. 2010 + + ! ARGUMENTS: + real(r8), intent(in) :: effective_moisture ! effective fuel moisture [m3/m3] + real(r8), intent(in) :: min_moisture ! minimum moisture content for initial equation [m3/m3] + real(r8), intent(in) :: mid_moisture ! medium moisture content for initial equation [m3/m3] + real(r8), intent(in) :: moisture_coeff_low ! coefficient for low moisture content [m3/m3] + real(r8), intent(in) :: moisture_slope_low ! slope for low moisture content [m3/m3] + real(r8), intent(in) :: moisture_coeff_mid ! coefficient for medium moisture content [m3/m3] + real(r8), intent(in) :: moisture_slope_mid ! slope for low medium content [m3/m3] + + if (effective_moisture <= min_moisture) then + CalculateFractionBurnt = 1.0_r8 + else if (effective_moisture > min_moisture .and. effective_moisture <= mid_moisture) then + CalculateFractionBurnt = max(0.0_r8, min(1.0_r8, moisture_coeff_low - & + moisture_slope_low*effective_moisture)) + else if (effective_moisture > mid_moisture .and. effective_moisture <= 1.0_r8) then + CalculateFractionBurnt = max(0.0_r8, min(1.0_r8, moisture_coeff_mid - & + moisture_slope_mid*effective_moisture)) + else + CalculateFractionBurnt = 0.0_r8 + end if + + end function CalculateFractionBurnt + + !------------------------------------------------------------------------------------- + + subroutine BurnFuel(this, fuel_consumed) + ! DESCRIPTION: + ! Calculates how much fuel burns + + ! USES + use SFParamsMod, only : SF_val_miner_total, SF_val_min_moisture + use SFParamsMod, only : SF_val_mid_moisture, SF_val_low_moisture_Coeff + use SFParamsMod, only : SF_val_low_moisture_Slope, SF_val_mid_moisture_Coeff + use SFParamsMod, only : SF_val_mid_moisture_Slope + + ! ARGUMENTS: + class(fuel_type), intent(inout) :: this ! fuel class + real(r8), intent(out) :: fuel_consumed(nsfc_notrunks) ! amount of fuel consumed in non-trunk litter classes [kgC/m2] + + ! LOCALS: + integer :: i ! looping index for fuel classes + + do i in 1, nfsc + this%frac_burnt = CalculateFractionBurnt(this%effective_moisture(i), & + SF_val_min_moisture(i), SF_val_mid_moisture(i), SF_val_low_moisture_Coeff(i), & + SF_val_low_moisture_Slope(i), SF_val_mid_moisture_Coeff(i), & + SF_val_mid_moisture_Slope(i)) + end do + + ! we can't ever kill all of the grass + this%frac_burnt(fuel_classes%live_grass()) = min(0.8_r8, & + this%frac_burnt(fuel_classes%live_grass())) + + ! reduce burnt amount for mineral content + this%frac_burnt(1:nfsc) = this%frac_burnt(1:nfsc)*(1.0_r8 - SF_val_miner_total) + + ! calculate amount of fuel burned + do i = 1, nfsc_notrunks + fuel_consumed(i) = this%frac_burnt(i)*this%loading(i) + end do + + end subroutine BurnFuel + + !------------------------------------------------------------------------------------- + subroutine CalculateFuelMoistureNesterov(sav_fuel, drying_ratio, NI, moisture) ! ! DESCRIPTION: diff --git a/fire/SFMainMod.F90 b/fire/SFMainMod.F90 index 1d85a49607..2fba212704 100644 --- a/fire/SFMainMod.F90 +++ b/fire/SFMainMod.F90 @@ -102,7 +102,6 @@ subroutine UpdateFireWeather(currentSite, bc_in) use FatesConstantsMod, only : sec_per_day, sec_per_min use EDTypesMod, only : CalculateTreeGrassAreaSite - ! ARGUMENTS: type(ed_site_type), intent(inout), target :: currentSite type(bc_in_type), intent(in) :: bc_in @@ -348,10 +347,6 @@ subroutine ground_fuel_consumption ( currentSite ) !***************************************************************** !returns the the hypothetic fuel consumed by the fire - use SFParamsMod, only : SF_val_miner_total, SF_val_min_moisture, & - SF_val_mid_moisture, SF_val_low_moisture_Coeff, SF_val_low_moisture_Slope, & - SF_val_mid_moisture_Coeff, SF_val_mid_moisture_Slope - type(ed_site_type) , intent(in), target :: currentSite type(fates_patch_type), pointer :: currentPatch type(litter_type), pointer :: litt_c ! carbon 12 litter pool @@ -367,46 +362,8 @@ subroutine ground_fuel_consumption ( currentSite ) do while(associated(currentPatch)) if(currentPatch%nocomp_pft_label .ne. nocomp_bareground)then - - currentPatch%burnt_frac_litter(:) = 1.0_r8 - ! Calculate fraction of litter is burnt for all classes. - ! Equation B1 in Thonicke et al. 2010--- - do c = 1, nfsc !work out the burnt fraction for all pools, even if those pools dont exist. - moist = currentPatch%fuel%effective_moisture(c) - ! 1. Very dry litter - if (moist <= SF_val_min_moisture(c)) then - currentPatch%burnt_frac_litter(c) = 1.0_r8 - endif - ! 2. Low to medium moistures - if (moist > SF_val_min_moisture(c).and.moist <= SF_val_mid_moisture(c)) then - currentPatch%burnt_frac_litter(c) = max(0.0_r8,min(1.0_r8,SF_val_low_moisture_Coeff(c)- & - SF_val_low_moisture_Slope(c)*moist)) - else - ! For medium to high moistures. - if (moist > SF_val_mid_moisture(c).and.moist <= 1.0_r8) then - currentPatch%burnt_frac_litter(c) = max(0.0_r8,min(1.0_r8,SF_val_mid_moisture_Coeff(c)- & - SF_val_mid_moisture_Slope(c)*moist)) - endif - - endif - ! Very wet litter - if (moist >= 1.0_r8) then !this shouldn't happen? - currentPatch%burnt_frac_litter(c) = 0.0_r8 - endif - enddo !c - - ! we can't ever kill -all- of the grass. - currentPatch%burnt_frac_litter(lg_sf) = min(0.8_r8,currentPatch%burnt_frac_litter(lg_sf )) - - ! reduce burnt amount for mineral content. - currentPatch%burnt_frac_litter(:) = currentPatch%burnt_frac_litter(:) * (1.0_r8-SF_val_miner_total) - - !---Calculate amount of fuel burnt.--- - - litt_c => currentPatch%litter(element_pos(carbon12_element)) - FC_ground(tw_sf:tr_sf) = currentPatch%burnt_frac_litter(tw_sf:tr_sf) * litt_c%ag_cwd(tw_sf:tr_sf) - FC_ground(dl_sf) = currentPatch%burnt_frac_litter(dl_sf) * sum(litt_c%leaf_fines(:)) - FC_ground(lg_sf) = currentPatch%burnt_frac_litter(lg_sf) * currentPatch%livegrass + + currentPatch%fuel%BurnFuel(fc_ground) ! Following used for determination of cambial kill follows from Peterson & Ryan (1986) scheme ! less empirical cf current scheme used in SPITFIRE which attempts to mesh Rothermel @@ -415,17 +372,16 @@ subroutine ground_fuel_consumption ( currentSite ) ! taul is the duration of the lethal heating. ! The /10 is to convert from kgC/m2 into gC/cm2, as in the Peterson and Ryan paper #Rosie,Jun 2013 - do c = 1,nfsc + do c = 1,nfsc_notrunks tau_b(c) = 39.4_r8 *(currentPatch%currentPatch%fuel%frac_loading(c)*currentPatch%fuel%total_loading/0.45_r8/10._r8)* & - (1.0_r8-((1.0_r8-currentPatch%burnt_frac_litter(c))**0.5_r8)) + (1.0_r8-((1.0_r8-currentPatch%fuel%frac_burnt(c))**0.5_r8)) enddo - tau_b(tr_sf) = 0.0_r8 ! Cap the residence time to 8mins, as suggested by literature survey by P&R (1986). currentPatch%tau_l = min(8.0_r8,sum(tau_b)) !---calculate overall fuel consumed by spreading fire --- ! ignore 1000hr fuels. Just interested in fuels affecting ROS - currentPatch%TFC_ROS = sum(FC_ground)-FC_ground(tr_sf) + currentPatch%TFC_ROS = sum(FC_ground) end if ! nocomp_pft_label check diff --git a/main/EDInitMod.F90 b/main/EDInitMod.F90 index 6f8359c3c6..65ce8da4df 100644 --- a/main/EDInitMod.F90 +++ b/main/EDInitMod.F90 @@ -982,8 +982,6 @@ subroutine init_patches( nsites, sites, bc_in) currentPatch%ros_back = 0._r8 currentPatch%scorch_ht(:) = 0._r8 currentPatch%frac_burnt = 0._r8 - currentPatch%burnt_frac_litter(:) = 0._r8 - currentPatch => currentPatch%older enddo enddo diff --git a/main/FatesHistoryInterfaceMod.F90 b/main/FatesHistoryInterfaceMod.F90 index 9cf0b2d304..d419d68fc7 100644 --- a/main/FatesHistoryInterfaceMod.F90 +++ b/main/FatesHistoryInterfaceMod.F90 @@ -4268,7 +4268,7 @@ subroutine update_history_dyn2(this,nc,nsites,sites,bc_in) cpatch%fuel%frac_loading(i_fuel) * cpatch%fuel%total_loading * cpatch%area * AREA_INV hio_burnt_frac_litter_si_fuel(io_si, i_fuel) = hio_burnt_frac_litter_si_fuel(io_si, i_fuel) + & - cpatch%burnt_frac_litter(i_fuel) * cpatch%frac_burnt * cpatch%area * AREA_INV + cpatch%fuel%frac_burnt(i_fuel) * cpatch%frac_burnt * cpatch%area * AREA_INV end do From 2263e6f8292792a9a05575023eec1f8265ef7bf2 Mon Sep 17 00:00:00 2001 From: Adrianna Foster Date: Wed, 4 Sep 2024 10:51:31 -0600 Subject: [PATCH 025/111] fix bugs --- biogeochem/EDPatchDynamicsMod.F90 | 5 ++--- biogeochem/EDPhysiologyMod.F90 | 6 +++--- fire/FatesFuelMod.F90 | 4 ++-- fire/SFMainMod.F90 | 10 +++++----- main/FatesHistoryInterfaceMod.F90 | 4 ++-- main/FatesInterfaceMod.F90 | 2 +- main/FatesRestartInterfaceMod.F90 | 3 ++- 7 files changed, 17 insertions(+), 17 deletions(-) diff --git a/biogeochem/EDPatchDynamicsMod.F90 b/biogeochem/EDPatchDynamicsMod.F90 index e64a240c03..9273473f29 100644 --- a/biogeochem/EDPatchDynamicsMod.F90 +++ b/biogeochem/EDPatchDynamicsMod.F90 @@ -37,8 +37,7 @@ module EDPatchDynamicsMod use FatesConstantsMod , only : ican_upper use PRTGenericMod , only : num_elements use PRTGenericMod , only : element_list - use FatesLitterMod , only : lg_sf - use FatesLitterMod , only : dl_sf + use FatesFuelClassesMod , only : fuel_classes use FatesConstantsMod , only : N_DIST_TYPES use EDTypesMod , only : AREA_INV use EDTypesMod , only : dump_site @@ -2099,7 +2098,7 @@ subroutine TransLitterNewPatch(currentSite, & ! Transfer leaf fines donatable_mass = curr_litt%leaf_fines(dcmpy) * patch_site_areadis * & - (1._r8 - currentPatch%fuel%frac_burnt(fuel_clases%dead_leaves())) + (1._r8 - currentPatch%fuel%frac_burnt(fuel_classes%dead_leaves())) burned_mass = curr_litt%leaf_fines(dcmpy) * patch_site_areadis * & currentPatch%fuel%frac_burnt(fuel_classes%dead_leaves()) diff --git a/biogeochem/EDPhysiologyMod.F90 b/biogeochem/EDPhysiologyMod.F90 index 0f080127c9..113be7537a 100644 --- a/biogeochem/EDPhysiologyMod.F90 +++ b/biogeochem/EDPhysiologyMod.F90 @@ -53,7 +53,7 @@ module EDPhysiologyMod use EDTypesMod , only : site_massbal_type use EDTypesMod , only : numlevsoil_max use EDTypesMod , only : numWaterMem - use FatesLitterMod , only : dl_sf + use FatesFuelClassesMod , only : fuel_classes use EDParamsMod , only : dinc_vai, dlower_vai use EDTypesMod , only : area_inv use EDTypesMod , only : AREA @@ -3250,11 +3250,11 @@ subroutine CWDOut( litt, fragmentation_scaler, nlev_eff_decomp ) do dcmpy = 1,ndcmpy litt%leaf_fines_frag(dcmpy) = litt%leaf_fines(dcmpy) * & - years_per_day * SF_val_max_decomp(dl_sf) * fragmentation_scaler(soil_layer_index) + years_per_day * SF_val_max_decomp(fuel_classes%dead_leaves()) * fragmentation_scaler(soil_layer_index) do ilyr = 1,nlev_eff_decomp litt%root_fines_frag(dcmpy,ilyr) = litt%root_fines(dcmpy,ilyr) * & - years_per_day * SF_val_max_decomp(dl_sf) * fragmentation_scaler(ilyr) + years_per_day * SF_val_max_decomp(fuel_classes%dead_leaves()) * fragmentation_scaler(ilyr) end do enddo diff --git a/fire/FatesFuelMod.F90 b/fire/FatesFuelMod.F90 index b17ea071be..3aa2d5305f 100644 --- a/fire/FatesFuelMod.F90 +++ b/fire/FatesFuelMod.F90 @@ -214,12 +214,12 @@ subroutine BurnFuel(this, fuel_consumed) ! ARGUMENTS: class(fuel_type), intent(inout) :: this ! fuel class - real(r8), intent(out) :: fuel_consumed(nsfc_notrunks) ! amount of fuel consumed in non-trunk litter classes [kgC/m2] + real(r8), intent(out) :: fuel_consumed(nfsc_notrunks) ! amount of fuel consumed in non-trunk litter classes [kgC/m2] ! LOCALS: integer :: i ! looping index for fuel classes - do i in 1, nfsc + do i = 1, nfsc this%frac_burnt = CalculateFractionBurnt(this%effective_moisture(i), & SF_val_min_moisture(i), SF_val_mid_moisture(i), SF_val_low_moisture_Coeff(i), & SF_val_low_moisture_Slope(i), SF_val_mid_moisture_Coeff(i), & diff --git a/fire/SFMainMod.F90 b/fire/SFMainMod.F90 index 2fba212704..af4ac44ee5 100644 --- a/fire/SFMainMod.F90 +++ b/fire/SFMainMod.F90 @@ -25,7 +25,7 @@ module SFMainMod use FatesCohortMod, only : fates_cohort_type use EDtypesMod, only : AREA use FatesLitterMod, only : litter_type - use FatesFuelClassesMod, only : nfsc + use FatesFuelClassesMod, only : nfsc, nfsc_notrunks use PRTGenericMod, only : leaf_organ use PRTGenericMod, only : carbon12_element use PRTGenericMod, only : sapw_organ @@ -241,7 +241,7 @@ subroutine rate_of_spread (currentSite) ! ----start spreading--- if ( hlm_masterproc == itrue .and.debug) write(fates_log(),*) & - 'SF - currentPatch%fuel%bulk_density ',currentPatch%currentPatch%fuel%bulk_density + 'SF - currentPatch%fuel%bulk_density ',currentPatch%fuel%bulk_density if ( hlm_masterproc == itrue .and.debug) write(fates_log(),*) & 'SF - SF_val_part_dens ',SF_val_part_dens @@ -311,7 +311,7 @@ subroutine rate_of_spread (currentSite) ! mw_weight = relative fuel moisture/fuel moisture of extinction ! average values for litter pools (dead leaves, twigs, small and large branches) plus grass - mw_weight = currentPatch%currentPatch%fuel%average_moisture/currentPatch%fuel%MEF + mw_weight = currentPatch%fuel%average_moisture/currentPatch%fuel%MEF ! Equation in table A1 Thonicke et al. 2010. ! moist_damp is unitless @@ -363,7 +363,7 @@ subroutine ground_fuel_consumption ( currentSite ) if(currentPatch%nocomp_pft_label .ne. nocomp_bareground)then - currentPatch%fuel%BurnFuel(fc_ground) + call currentPatch%fuel%BurnFuel(fc_ground) ! Following used for determination of cambial kill follows from Peterson & Ryan (1986) scheme ! less empirical cf current scheme used in SPITFIRE which attempts to mesh Rothermel @@ -373,7 +373,7 @@ subroutine ground_fuel_consumption ( currentSite ) ! The /10 is to convert from kgC/m2 into gC/cm2, as in the Peterson and Ryan paper #Rosie,Jun 2013 do c = 1,nfsc_notrunks - tau_b(c) = 39.4_r8 *(currentPatch%currentPatch%fuel%frac_loading(c)*currentPatch%fuel%total_loading/0.45_r8/10._r8)* & + tau_b(c) = 39.4_r8 *(currentPatch%fuel%frac_loading(c)*currentPatch%fuel%total_loading/0.45_r8/10._r8)* & (1.0_r8-((1.0_r8-currentPatch%fuel%frac_burnt(c))**0.5_r8)) enddo ! Cap the residence time to 8mins, as suggested by literature survey by P&R (1986). diff --git a/main/FatesHistoryInterfaceMod.F90 b/main/FatesHistoryInterfaceMod.F90 index d419d68fc7..3a8abb92d1 100644 --- a/main/FatesHistoryInterfaceMod.F90 +++ b/main/FatesHistoryInterfaceMod.F90 @@ -121,7 +121,7 @@ module FatesHistoryInterfaceMod use FatesSizeAgeTypeIndicesMod, only : get_layersizetype_class_index use FatesSizeAgeTypeIndicesMod, only : get_age_class_index - use FatesLitterMod , only : nfsc + use FatesFuelClassesMod , only : nfsc use FatesLitterMod , only : ncwd use FatesConstantsMod , only : ican_upper use FatesConstantsMod , only : ican_ustory @@ -4255,7 +4255,7 @@ subroutine update_history_dyn2(this,nc,nsites,sites,bc_in) hio_fragmentation_scaler_sl(io_si,ilyr) = hio_fragmentation_scaler_sl(io_si,ilyr) + cpatch%fragmentation_scaler(ilyr) * cpatch%area * AREA_INV end do - do i_fuel = 1,nfsc + do i_fuel = 1, nfsc i_agefuel = get_agefuel_class_index(cpatch%age,i_fuel) hio_fuel_amount_age_fuel(io_si,i_agefuel) = hio_fuel_amount_age_fuel(io_si,i_agefuel) + & diff --git a/main/FatesInterfaceMod.F90 b/main/FatesInterfaceMod.F90 index 12eb83bcc0..0120173759 100644 --- a/main/FatesInterfaceMod.F90 +++ b/main/FatesInterfaceMod.F90 @@ -1122,7 +1122,7 @@ end subroutine InitPARTEHGlobals subroutine fates_history_maps - use FatesLitterMod, only : NFSC + use FatesFuelClassesMod, only : nfsc use EDParamsMod, only : nclmax use EDParamsMod, only : nlevleaf use EDParamsMod, only : ED_val_history_sizeclass_bin_edges diff --git a/main/FatesRestartInterfaceMod.F90 b/main/FatesRestartInterfaceMod.F90 index 67764c6ab8..7d5a0a54c5 100644 --- a/main/FatesRestartInterfaceMod.F90 +++ b/main/FatesRestartInterfaceMod.F90 @@ -40,7 +40,8 @@ module FatesRestartInterfaceMod use FatesInterfaceTypesMod, only : nlevsclass use FatesInterfaceTypesMod, only : nlevdamage use FatesLitterMod, only : litter_type - use FatesLitterMod, only : ncwd, nfsc + use FatesLitterMod, only : ncwd + use FatesFuelClassesMod, only : nfsc use FatesLitterMod, only : ndcmpy use EDTypesMod, only : area use EDParamsMod, only : nlevleaf From b1500c9834d6cc3552c36cbb84ce50e07a6d2260 Mon Sep 17 00:00:00 2001 From: adrifoster Date: Wed, 4 Sep 2024 10:57:10 -0600 Subject: [PATCH 026/111] updates --- biogeochem/EDPatchDynamicsMod.F90 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/biogeochem/EDPatchDynamicsMod.F90 b/biogeochem/EDPatchDynamicsMod.F90 index e64a240c03..c2eae64b5a 100644 --- a/biogeochem/EDPatchDynamicsMod.F90 +++ b/biogeochem/EDPatchDynamicsMod.F90 @@ -3297,7 +3297,7 @@ subroutine fuse_2_patches(csite, dp, rp) rp%ros_back = (dp%ros_back*dp%area + rp%ros_back*rp%area) * inv_sum_area rp%scorch_ht(:) = (dp%scorch_ht(:)*dp%area + rp%scorch_ht(:)*rp%area) * inv_sum_area rp%frac_burnt = (dp%frac_burnt*dp%area + rp%frac_burnt*rp%area) * inv_sum_area - rp%fuel%frac_burnt(:) = (dp%fuel%frac_burnt(:)*dp%area + rp%fuel%frac_burnt(:)*rp%area) * inv_sum_area + rp%fuel%frac_burnt(:) = (dp%fuel%frac_burnt(:)*dp%area + rp%fuel%frac_burnt(:)*rp%area) * inv_sum_area rp%btran_ft(:) = (dp%btran_ft(:)*dp%area + rp%btran_ft(:)*rp%area) * inv_sum_area rp%zstar = (dp%zstar*dp%area + rp%zstar*rp%area) * inv_sum_area rp%c_stomata = (dp%c_stomata*dp%area + rp%c_stomata*rp%area) * inv_sum_area From 4a16132c141c15ff3852337cd3cea6cfb8eb8fec Mon Sep 17 00:00:00 2001 From: adrifoster Date: Wed, 4 Sep 2024 11:59:14 -0600 Subject: [PATCH 027/111] fix plotting --- .../functional_testing/fire/FatesTestFuel.F90 | 18 +++++----- .../functional_testing/fire/fuel_plotting.py | 33 ++++++++++++++++--- 2 files changed, 38 insertions(+), 13 deletions(-) diff --git a/testing/functional_testing/fire/FatesTestFuel.F90 b/testing/functional_testing/fire/FatesTestFuel.F90 index 18f56749fb..06f678a117 100644 --- a/testing/functional_testing/fire/FatesTestFuel.F90 +++ b/testing/functional_testing/fire/FatesTestFuel.F90 @@ -43,15 +43,15 @@ program FatesTestFuel character(len=*), parameter :: out_file = 'fuel_out.nc' ! output file ! fuel models to test - !integer, parameter, dimension(3) :: fuel_models = (/102, 183, 164/) - integer, parameter, dimension(52) :: fuel_models = (/1, 2, 101, 102, 104, 107, 121, & - 122, 3, 103, 105, 106, 108, 109, & - 123, 124, 4, 5, 6, 141, 142, 145, & - 147, 161, 164, 10, 7, 143, 144, & - 146, 148, 149, 162, 163, 8, 9, & - 181, 182, 183, 184, 185, 186, 187, & - 188, 189, 11, 12, 13, 201, 202, & - 203, 204/) + integer, parameter, dimension(3) :: fuel_models = (/102, 183, 164/) + ! integer, parameter, dimension(52) :: fuel_models = (/1, 2, 101, 102, 104, 107, 121, & + ! 122, 3, 103, 105, 106, 108, 109, & + ! 123, 124, 4, 5, 6, 141, 142, 145, & + ! 147, 161, 164, 10, 7, 143, 144, & + ! 146, 148, 149, 162, 163, 8, 9, & + ! 181, 182, 183, 184, 185, 186, 187, & + ! 188, 189, 11, 12, 13, 201, 202, & + ! 203, 204/) ! number of fuel models to test num_fuel_models = size(fuel_models) diff --git a/testing/functional_testing/fire/fuel_plotting.py b/testing/functional_testing/fire/fuel_plotting.py index 648234ce00..79546e9b7d 100644 --- a/testing/functional_testing/fire/fuel_plotting.py +++ b/testing/functional_testing/fire/fuel_plotting.py @@ -21,6 +21,7 @@ def plot_fuel_dat(run_dir, out_file, save_figs, plot_dir): fuel_dat = xr.open_dataset(os.path.join(run_dir, out_file)) plot_NI_dat(fuel_dat, save_figs, plot_dir) + plot_moisture_dat(fuel_dat, save_figs, plot_dir) plot_barchart(fuel_dat, 'fuel_loading', 'Fuel loading', 'kgC m$^{-2}$', save_figs, plot_dir) plot_barchart(fuel_dat, 'frac_loading', 'Fractional fuel loading', '0-1', save_figs, plot_dir) plot_barchart(fuel_dat, 'bulk_density', 'Fuel bulk density', 'kg m$^{-3}$', save_figs, plot_dir, by_litter_type=False) @@ -30,26 +31,32 @@ def plot_barchart(fuel_dat, var, varname, units, save_figs, plot_dir, by_litter_ litter_classes = ['twigs', 'small branches', 'large branches', 'dead leaves', 'live grass'] colors = ['darksalmon', 'peru', 'saddlebrown', 'moccasin', 'yellowgreen'] - fuel_models = ['Fuel model ' + str(m) for m in np.unique(fuel_dat.fuel_model)] + fuel_models = [str(f) for f in fuel_dat.fuel_model.values] if by_litter_type: data_dict = {lc: fuel_dat.isel(litter_class=i)[var].values for i, lc in enumerate(litter_classes)} else: data_dict = fuel_dat[var].values - fig, ax = plt.subplots() + _, ax = plt.subplots() if by_litter_type: bottom = np.zeros(len(fuel_models)) for i, (litter_class, dat) in enumerate(data_dict.items()): - p = ax.bar(fuel_models, dat, 0.5, label=litter_class, bottom=bottom, color=colors[i]) + ax.bar(fuel_models, dat, 0.5, label=litter_class, bottom=bottom, color=colors[i]) bottom += dat plt.legend(loc='center left', bbox_to_anchor=(1, 0.5)) else: - p = ax.bar(fuel_models, data_dict, color='darkcyan') + ax.bar(fuel_models, data_dict, color='darkcyan') box = ax.get_position() ax.set_position([box.x0, box.y0, box.width * 0.75, box.height]) plt.ylabel(f'{varname} ({units})', fontsize=11) + plt.xticks(rotation=90) + plt.xlabel('Fuel Model') + + if save_figs: + fig_name = os.path.join(plot_dir, f"{varname}_plot.png") + plt.savefig(fig_name) def plot_NI_dat(fuel_dat, save_figs, plot_dir): """Plot output for Nesterov index @@ -67,4 +74,22 @@ def plot_NI_dat(fuel_dat, save_figs, plot_dir): if save_figs: fig_name = os.path.join(plot_dir, "Nesterov_plot.png") + plt.savefig(fig_name) + +def plot_moisture_dat(fuel_dat, save_figs, plot_dir): + """Plot output for fuel moisture + + Args: + fuel_dat (Xarray Dataset): output fuel data + save_figs (bool): whether or not to save the figures + plot_dir (str): plot directory + """ + + plt.figure() + fuel_dat.fuel_moisture.plot(hue='fuel_model') + plt.xlabel('Time', fontsize=11) + plt.ylabel('Fuel Moisture', fontsize=11) + + if save_figs: + fig_name = os.path.join(plot_dir, "fuel_moisture_plot.png") plt.savefig(fig_name) \ No newline at end of file From 7b00ed3f2355a608d9edc2a65f3e7e50d335616b Mon Sep 17 00:00:00 2001 From: Adrianna Foster Date: Fri, 6 Sep 2024 11:26:45 -0600 Subject: [PATCH 028/111] fix b4b changes --- fire/FatesFuelClassesMod.F90 | 11 +- fire/FatesFuelMod.F90 | 212 +++++++++++++++++------------------ fire/SFMainMod.F90 | 153 +++++++++++++++++++++---- 3 files changed, 235 insertions(+), 141 deletions(-) diff --git a/fire/FatesFuelClassesMod.F90 b/fire/FatesFuelClassesMod.F90 index b263e0cf47..a127017fc3 100644 --- a/fire/FatesFuelClassesMod.F90 +++ b/fire/FatesFuelClassesMod.F90 @@ -5,8 +5,8 @@ module FatesFuelClassesMod implicit none private - integer, parameter, public :: nfsc = ncwd + 2 ! number of total fuel classes - integer, parameter, public :: nfsc_notrunks = ncwd + 1 ! number of fuel classes without trunks + integer, parameter, public :: nfsc = 6 ! number of total fuel classes + !integer, parameter, public :: nfsc_notrunks = nfsc - 1 ! number of fuel classes without trunks type :: fuel_classes_type ! There are six fuel classes: @@ -15,9 +15,10 @@ module FatesFuelClassesMod integer, private :: twigs_i = 1 ! array index for twigs pool integer, private :: small_branches_i = 2 ! array index for small branches pool integer, private :: large_branches_i = 3 ! array index for large branches pool - integer, private :: dead_leaves_i = 4 ! array index for dead leaves pool - integer, private :: live_grass_i = 5 ! array index for live grass pool - integer, private :: trunks_i = 6 ! array index for trunks pool + integer, private :: dead_leaves_i = 5 ! array index for dead leaves pool + integer, private :: live_grass_i = 6 ! array index for live grass pool + + integer, private :: trunks_i = 4 ! array index for trunks pool contains diff --git a/fire/FatesFuelMod.F90 b/fire/FatesFuelMod.F90 index 3aa2d5305f..9b0760af4d 100644 --- a/fire/FatesFuelMod.F90 +++ b/fire/FatesFuelMod.F90 @@ -1,6 +1,6 @@ module FatesFuelMod - use FatesFuelClassesMod, only : nfsc, nfsc_notrunks, fuel_classes + use FatesFuelClassesMod, only : nfsc, fuel_classes use FatesConstantsMod, only : r8 => fates_r8 use FatesConstantsMod, only : nearzero use SFNesterovMod, only : nesterov_index @@ -14,16 +14,16 @@ module FatesFuelMod type, public :: fuel_type - real(r8) :: loading(nfsc_notrunks) ! fuel loading of non-trunks fuel class [kgC/m2] - real(r8) :: trunk_loading ! fuel loading of trunk fuel class [kgC/m2] - real(r8) :: effective_moisture(nfsc) ! fuel effective moisture all fuel class (moisture/MEF) [m3/m3] - real(r8) :: frac_loading(nfsc_notrunks) ! fractional loading of non-trunk fuel classes [0-1] - real(r8) :: frac_burnt(nfsc) ! fraction of litter burnt by fire [0-1] - real(r8) :: total_loading ! total fuel loading - DOES NOT INCLUDE TRUNKS [kgC/m2] - real(r8) :: average_moisture ! weighted average of fuel moisture across non-trunk fuel classes [m3/m3] - real(r8) :: bulk_density ! weighted average of bulk density across non-trunk fuel classes [kg/m3] - real(r8) :: SAV ! weighted average of surface area to volume ratio across non-trunk fuel classes [/cm] - real(r8) :: MEF ! weighted average of moisture of extinction across non-trunk fuel classes [m3/m3] + real(r8) :: loading(nfsc) ! fuel loading of non-trunks fuel class [kgC/m2] + real(r8) :: trunk_loading ! fuel loading of trunk fuel class [kgC/m2] + real(r8) :: effective_moisture(nfsc) ! fuel effective moisture all fuel class (moisture/MEF) [m3/m3] + real(r8) :: frac_loading(nfsc) ! fractional loading of non-trunk fuel classes [0-1] + real(r8) :: frac_burnt(nfsc) ! fraction of litter burnt by fire [0-1] + real(r8) :: total_loading ! total fuel loading - DOES NOT INCLUDE TRUNKS [kgC/m2] + real(r8) :: average_moisture ! weighted average of fuel moisture across non-trunk fuel classes [m3/m3] + real(r8) :: bulk_density ! weighted average of bulk density across non-trunk fuel classes [kg/m3] + real(r8) :: SAV ! weighted average of surface area to volume ratio across non-trunk fuel classes [/cm] + real(r8) :: MEF ! weighted average of moisture of extinction across non-trunk fuel classes [m3/m3] contains @@ -34,7 +34,6 @@ module FatesFuelMod procedure :: UpdateFuelMoisture procedure :: AverageBulkDensity procedure :: AverageSAV - procedure :: BurnFuel end type fuel_type @@ -48,9 +47,9 @@ subroutine Init(this) class(fuel_type), intent(inout) :: this ! fuel class ! just zero everything - this%loading(1:nfsc_notrunks) = 0.0_r8 + this%loading(1:nfsc) = 0.0_r8 this%trunk_loading = 0.0_r8 - this%frac_loading(1:nfsc_notrunks) = 0.0_r8 + this%frac_loading(1:nfsc) = 0.0_r8 this%frac_burnt(1:nfsc) = 0.0_r8 this%effective_moisture(1:nfsc) = 0.0_r8 this%total_loading = 0.0_r8 @@ -82,6 +81,8 @@ subroutine CalculateLoading(this, leaf_litter, twig_litter, small_branch_litter, this%loading(fuel_classes%small_branches()) = small_branch_litter this%loading(fuel_classes%large_branches()) = large_branch_litter this%loading(fuel_classes%live_grass()) = live_grass + + this%loading(fuel_classes%trunks()) = trunk_litter this%trunk_loading = trunk_litter end subroutine CalculateLoading @@ -95,7 +96,15 @@ subroutine SumLoading(this) ! ARGUMENTS: class(fuel_type), intent(inout) :: this ! fuel class - this%total_loading = sum(this%loading(1:nfsc_notrunks)) + ! LOCALS: + integer :: i ! looping index + + this%total_loading = 0.0_r8 + do i = 1, nfsc + if (i /= fuel_classes%trunks()) then + this%total_loading = this%total_loading + this%loading(i) + end if + end do end subroutine SumLoading @@ -107,14 +116,21 @@ subroutine CalculateFractionalLoading(this) ! ARGUMENTS: class(fuel_type), intent(inout) :: this ! fuel class + + ! LOCALS: + integer :: i ! looping index ! sum up loading just in case call this%SumLoading() - + if (this%total_loading > nearzero) then - this%frac_loading(1:nfsc_notrunks) = this%loading(1:nfsc_notrunks)/this%total_loading + do i = 1, nfsc + if (i /= fuel_classes%trunks()) then + this%frac_loading(i) = this%loading(i)/this%total_loading + end if + end do else - this%frac_loading(1:nfsc_notrunks) = 0.0_r8 + this%frac_loading(1:nfsc) = 0.0_r8 end if end subroutine CalculateFractionalLoading @@ -124,7 +140,9 @@ end subroutine CalculateFractionalLoading subroutine UpdateFuelMoisture(this, sav_fuel, drying_ratio, fireWeatherClass) ! DESCRIPTION: ! Updates fuel moisture depending on what fire weather class is in use - + + use SFParamsMod, only : SF_val_SAV, SF_val_drying_ratio + ! ARGUMENTS: class(fuel_type), intent(inout) :: this ! fuel class real(r8), intent(in) :: sav_fuel(nfsc) ! surface area to volume ratio of all fuel types [/cm] @@ -134,114 +152,69 @@ subroutine UpdateFuelMoisture(this, sav_fuel, drying_ratio, fireWeatherClass) real(r8) :: moisture(nfsc) ! fuel moisture [m3/m3] real(r8) :: moisture_of_extinction(nfsc) ! fuel moisture of extinction [m3/m3] integer :: i ! looping index + integer :: tw_sf, dl_sf, lg_sf, lb_sf, tr_sf + + tw_sf = fuel_classes%twigs() + dl_sf = fuel_classes%dead_leaves() + lg_sf = fuel_classes%live_grass() + lb_sf = fuel_classes%large_branches() + tr_sf = fuel_classes%trunks() if (this%total_loading > nearzero) then ! calculate fuel moisture [m3/m3] for each fuel class depending on what ! fire weather class is in use select type (fireWeatherClass) class is (nesterov_index) - call CalculateFuelMoistureNesterov(sav_fuel, drying_ratio, & + call CalculateFuelMoistureNesterov(sav_fuel, drying_ratio, & fireWeatherClass%fire_weather_index, moisture) class default write(fates_log(), *) 'Unknown fire weather class selected.' write(fates_log(), *) 'Choose a different fire weather class or upate this subroutine.' call endrun(msg=errMsg( __FILE__, __LINE__)) end select - - ! calculate moisture of extinction and fuel effective moisture + + this%average_moisture = 0.0_r8 + this%MEF = 0.0_r8 do i = 1, nfsc + ! calculate moisture of extinction and fuel effective moisture moisture_of_extinction(i) = MoistureOfExtinction(sav_fuel(i)) this%effective_moisture(i) = moisture(i)/moisture_of_extinction(i) + + ! average fuel moisture and MEF across all fuel types except trunks [m3/m3] + if (i /= fuel_classes%trunks()) then + this%average_moisture = this%average_moisture + this%frac_loading(i)*moisture(i) + this%MEF = this%MEF + this%frac_loading(i)*moisture_of_extinction(i) + end if end do - ! average fuel moisture across all fuel types except trunks [m3/m3] - this%average_moisture = sum(this%frac_loading(1:nfsc_notrunks)*moisture(1:nfsc_notrunks)) + + ! this%MEF = sum(this%frac_loading(tw_sf:lb_sf)*moisture_of_extinction(tw_sf:lb_sf)) + ! this%average_moisture = sum(this%frac_loading(tw_sf:lb_sf)*moisture(tw_sf:lb_sf)) + + ! this%MEF = this%MEF + sum(this%frac_loading(dl_sf:lg_sf)*moisture_of_extinction(dl_sf:lg_sf)) + ! this%average_moisture = this%average_moisture + sum(this%frac_loading(dl_sf:lg_sf)*moisture(dl_sf:lg_sf)) + + ! ! Correct averaging for the fact that we are not using the trunks pool for fire ROS and intensity (5) + ! ! Consumption of fuel in trunk pool does not influence fire ROS or intensity (Pyne 1996) + ! if ((1.0_r8 - this%frac_loading(tr_sf)) > nearzero) then + ! this%MEF = this%MEF*(1.0_r8/(1.0_r8 - this%frac_loading(tr_sf))) + ! this%average_moisture = this%average_moisture*(1.0_r8/(1.0_r8 - this%frac_loading(tr_sf))) + ! else + ! ! somehow the fuel is all trunk. put dummy values from large branches so as not to break things later in code. + ! this%MEF = moisture_of_extinction(lb_sf) + ! this%average_moisture = moisture(lb_sf) + ! endif - ! calculate average moisture of extinction across all fuel types except trunks - this%MEF = sum(this%frac_loading(1:nfsc_notrunks)*moisture_of_extinction(1:nfsc_notrunks)) else this%effective_moisture(1:nfsc) = 0.0_r8 - this%average_moisture = 0.0_r8 - this%MEF = 0.0_r8 + this%average_moisture = 0.0000000001_r8 + this%MEF = 0.0000000001_r8 end if end subroutine UpdateFuelMoisture !------------------------------------------------------------------------------------- - - real(r8) function CalculateFractionBurnt(effective_moisture, min_moisture, & - mid_moisture, moisture_coeff_low, moisture_slope_low, moisture_coeff_mid, & - moisture_slope_mid) - ! DESCRIPTION: - ! Calculates fraction burnt of fuel based on input fuel moisture - - ! Based on Equation B1 from Thonicke et al. 2010 - - ! ARGUMENTS: - real(r8), intent(in) :: effective_moisture ! effective fuel moisture [m3/m3] - real(r8), intent(in) :: min_moisture ! minimum moisture content for initial equation [m3/m3] - real(r8), intent(in) :: mid_moisture ! medium moisture content for initial equation [m3/m3] - real(r8), intent(in) :: moisture_coeff_low ! coefficient for low moisture content [m3/m3] - real(r8), intent(in) :: moisture_slope_low ! slope for low moisture content [m3/m3] - real(r8), intent(in) :: moisture_coeff_mid ! coefficient for medium moisture content [m3/m3] - real(r8), intent(in) :: moisture_slope_mid ! slope for low medium content [m3/m3] - - if (effective_moisture <= min_moisture) then - CalculateFractionBurnt = 1.0_r8 - else if (effective_moisture > min_moisture .and. effective_moisture <= mid_moisture) then - CalculateFractionBurnt = max(0.0_r8, min(1.0_r8, moisture_coeff_low - & - moisture_slope_low*effective_moisture)) - else if (effective_moisture > mid_moisture .and. effective_moisture <= 1.0_r8) then - CalculateFractionBurnt = max(0.0_r8, min(1.0_r8, moisture_coeff_mid - & - moisture_slope_mid*effective_moisture)) - else - CalculateFractionBurnt = 0.0_r8 - end if - - end function CalculateFractionBurnt - - !------------------------------------------------------------------------------------- - - subroutine BurnFuel(this, fuel_consumed) - ! DESCRIPTION: - ! Calculates how much fuel burns - - ! USES - use SFParamsMod, only : SF_val_miner_total, SF_val_min_moisture - use SFParamsMod, only : SF_val_mid_moisture, SF_val_low_moisture_Coeff - use SFParamsMod, only : SF_val_low_moisture_Slope, SF_val_mid_moisture_Coeff - use SFParamsMod, only : SF_val_mid_moisture_Slope - - ! ARGUMENTS: - class(fuel_type), intent(inout) :: this ! fuel class - real(r8), intent(out) :: fuel_consumed(nfsc_notrunks) ! amount of fuel consumed in non-trunk litter classes [kgC/m2] - - ! LOCALS: - integer :: i ! looping index for fuel classes - - do i = 1, nfsc - this%frac_burnt = CalculateFractionBurnt(this%effective_moisture(i), & - SF_val_min_moisture(i), SF_val_mid_moisture(i), SF_val_low_moisture_Coeff(i), & - SF_val_low_moisture_Slope(i), SF_val_mid_moisture_Coeff(i), & - SF_val_mid_moisture_Slope(i)) - end do - - ! we can't ever kill all of the grass - this%frac_burnt(fuel_classes%live_grass()) = min(0.8_r8, & - this%frac_burnt(fuel_classes%live_grass())) - - ! reduce burnt amount for mineral content - this%frac_burnt(1:nfsc) = this%frac_burnt(1:nfsc)*(1.0_r8 - SF_val_miner_total) - - ! calculate amount of fuel burned - do i = 1, nfsc_notrunks - fuel_consumed(i) = this%frac_burnt(i)*this%loading(i) - end do - - end subroutine BurnFuel - - !------------------------------------------------------------------------------------- - + subroutine CalculateFuelMoistureNesterov(sav_fuel, drying_ratio, NI, moisture) ! ! DESCRIPTION: @@ -279,14 +252,29 @@ real(r8) function MoistureOfExtinction(sav) ! DESCRIPTION: ! Calculates moisture of extinction based on input surface area to volume ratio - ! MEF (moisure of extinction) depends on compactness of fuel, depth, particle size, wind, slope - ! Eqn here is eqn 27 from Peterson and Ryan (1986) "Modeling Postfire Conifer Mortality for Long-Range Planning" - ! MEF: pine needles=0.30 (text near EQ 28 Rothermal 1972) - ! Table II-1 NFFL mixed fuels models from Rothermal 1983 Gen. Tech. Rep. INT-143 - ! MEF: short grass=0.12,tall grass=0.25,chaparral=0.20,closed timber litter=0.30,hardwood litter=0.25 - ! Thonicke 2010 SAV values propagated thru P&R86 eqn below gives MEF:tw=0.355, sb=0.44, lb=0.525, tr=0.63, dg=0.248, lg=0.248 - ! Lasslop 2014 Table 1 MEF PFT level:grass=0.2,shrubs=0.3,TropEverGrnTree=0.2,TropDecid Tree=0.3, Extra-trop Tree=0.3 - + ! MEF (moisure of extinction) depends on compactness of fuel, depth, particle size, + ! wind, and slope + ! Equation here is Eq. 27 from Peterson and Ryan (1986) "Modeling Postfire Conifer + ! Mortality for Long-Range Planning" + ! + ! Example MEFs: + ! pine needles = 0.30 (Rothermal 1972) + ! short grass = 0.12 (Rothermel 1983; Gen. Tech. Rep. INT-143; Table II-1) + ! tall grass = 0.24 (Rothermel 1983) + ! chaparral = 0.20 (Rothermel 1983) + ! closed timber litter = 0.30 (Rothermel 1983) + ! hardwood litter = 0.25 (Rothermel 1983) + ! grass = 0.2 (Lasslop 2014; Table 1) + ! shrubs = 0.3 (Lasslop 2014; Table 1) + ! tropical evergreen trees = 0.2 (Lasslop 2014; Table 1) + ! tropical deciduous trees = 0.3 (Lasslop 2014; Table 1) + ! extratropical trees = 0.3 (Lasslop 2014; Table 1) + ! + ! SAV values from Thonicke 2010 give: + ! twigs = 0.355, small branches = 0.44, large branches = 0.525, trunks = 0.63 + ! dead leaves = 0.248, live grass = 0.248 + ! + ! ARGUMENTS: real(r8), intent(in) :: sav ! fuel surface area to volume ratio [/cm] @@ -313,7 +301,7 @@ subroutine AverageBulkDensity(this, bulk_density) real(r8), intent(in) :: bulk_density(nfsc) ! bulk density of all fuel types [kg/m2] if (this%total_loading > nearzero) then - this%bulk_density = sum(this%frac_loading(1:nfsc_notrunks)*bulk_density(1:nfsc_notrunks)) + this%bulk_density = sum(this%frac_loading(1:nfsc)*bulk_density(1:nfsc)) else this%bulk_density = 0.0_r8 end if @@ -331,7 +319,7 @@ subroutine AverageSAV(this, sav_fuel) real(r8), intent(in) :: sav_fuel(nfsc) ! surface area to volume ratio of all fuel types [/cm] if (this%total_loading > nearzero) then - this%SAV = sum(this%frac_loading(1:nfsc_notrunks)*sav_fuel(1:nfsc_notrunks)) + this%SAV = sum(this%frac_loading(1:nfsc)*sav_fuel(1:nfsc)) else this%SAV = 0.0_r8 end if diff --git a/fire/SFMainMod.F90 b/fire/SFMainMod.F90 index af4ac44ee5..442cf38545 100644 --- a/fire/SFMainMod.F90 +++ b/fire/SFMainMod.F90 @@ -25,14 +25,15 @@ module SFMainMod use FatesCohortMod, only : fates_cohort_type use EDtypesMod, only : AREA use FatesLitterMod, only : litter_type - use FatesFuelClassesMod, only : nfsc, nfsc_notrunks + use FatesFuelClassesMod, only : nfsc use PRTGenericMod, only : leaf_organ use PRTGenericMod, only : carbon12_element use PRTGenericMod, only : sapw_organ use PRTGenericMod, only : struct_organ use FatesInterfaceTypesMod, only : numpft use FatesAllometryMod, only : CrownDepth - + use FatesFuelClassesMod, only : fuel_classes + implicit none private @@ -158,7 +159,8 @@ subroutine UpdateFuelCharacteristics(currentSite) ! DESCRIPTION: ! Updates fuel characteristics on each patch of the site - use SFParamsMod, only : SF_val_drying_ratio, SF_val_SAV, SF_val_FBD + use SFParamsMod, only : SF_val_drying_ratio, SF_val_SAV, SF_val_FBD + use FatesConstantsMod, only : nearzero ! ARGUMENTS: type(ed_site_type), intent(in), target :: currentSite ! site object @@ -166,7 +168,16 @@ subroutine UpdateFuelCharacteristics(currentSite) ! LOCALS: type(fates_patch_type), pointer :: currentPatch ! FATES patch type(litter_type), pointer :: litter ! pointer to patch litter class - + integer :: lg_sf, tr_sf, lb_sf, sb_sf, dl_sf, tw_sf + integer :: i + + dl_sf = fuel_classes%dead_leaves() + lg_sf = fuel_classes%live_grass() + tr_sf = fuel_classes%trunks() + lb_sf = fuel_classes%large_branches() + sb_sf = fuel_classes%small_branches() + tw_sf = fuel_classes%twigs() + currentPatch => currentSite%oldest_patch do while(associated(currentPatch)) @@ -177,22 +188,67 @@ subroutine UpdateFuelCharacteristics(currentSite) ! update fuel loading [kgC/m2] litter => currentPatch%litter(element_pos(carbon12_element)) - call currentPatch%fuel%CalculateLoading(sum(litter%leaf_fines(:)), & - litter%ag_cwd(1), litter%ag_cwd(2), litter%ag_cwd(3), litter%ag_cwd(4), & - currentPatch%livegrass) - - ! sum up fuel classes and calculate fractional loading for each - call currentPatch%fuel%SumLoading() - call currentPatch%fuel%CalculateFractionalLoading() - - ! calculate fuel moisture [m3/m3] - call currentPatch%fuel%UpdateFuelMoisture(SF_val_SAV, SF_val_drying_ratio, & - currentSite%fireWeather) - - ! calculate geometric properties - call currentPatch%fuel%AverageBulkDensity(SF_val_FBD) - call currentPatch%fuel%AverageSAV(SF_val_SAV) - + call currentPatch%fuel%CalculateLoading(sum(litter%leaf_fines(:)), & + litter%ag_cwd(1), litter%ag_cwd(2), litter%ag_cwd(3), litter%ag_cwd(4), & + currentPatch%livegrass) + + ! sum up fuel classes and calculate fractional loading for each + call currentPatch%fuel%SumLoading() + call currentPatch%fuel%CalculateFractionalLoading() + + if (currentPatch%fuel%total_loading > 0.0) then + + ! calculate fuel moisture [m3/m3] + call currentPatch%fuel%UpdateFuelMoisture(SF_val_SAV, SF_val_drying_ratio, & + currentSite%fireWeather) + + ! calculate geometric properties + !call currentPatch%fuel%AverageBulkDensity(SF_val_FBD) + !call currentPatch%fuel%AverageSAV(SF_val_SAV) + + currentPatch%fuel%bulk_density = 0.0_r8 + currentPatch%fuel%SAV = 0.0_r8 + do i = 1, nfsc + ! average bulk density and SAV across all fuel types except trunks + if (i /= fuel_classes%trunks()) then + currentPatch%fuel%bulk_density = currentPatch%fuel%bulk_density + currentPatch%fuel%frac_loading(i)*SF_val_FBD(i) + currentPatch%fuel%SAV = currentPatch%fuel%SAV + currentPatch%fuel%frac_loading(i)*SF_val_SAV(i) + end if + end do + + ! ! Average properties over the first three litter pools (twigs, s branches, l branches) + ! currentPatch%fuel%bulk_density = sum(currentPatch%fuel%frac_loading(tw_sf:lb_sf) * SF_val_FBD(tw_sf:lb_sf)) + ! currentPatch%fuel%SAV = sum(currentPatch%fuel%frac_loading(tw_sf:lb_sf) * SF_val_SAV(tw_sf:lb_sf)) + + ! ! Add on properties of dead leaves and live grass pools (5 & 6) + ! currentPatch%fuel%bulk_density = currentPatch%fuel%bulk_density + sum(currentPatch%fuel%frac_loading(dl_sf:lg_sf) * SF_val_FBD(dl_sf:lg_sf)) + ! currentPatch%fuel%SAV = currentPatch%fuel%SAV + sum(currentPatch%fuel%frac_loading(dl_sf:lg_sf) * SF_val_SAV(dl_sf:lg_sf)) + + ! ! Correct averaging for the fact that we are not using the trunks pool for fire ROS and intensity (5) + ! ! Consumption of fuel in trunk pool does not influence fire ROS or intensity (Pyne 1996) + ! if ((1.0_r8 - currentPatch%fuel%frac_loading(tr_sf)) > nearzero) then + ! currentPatch%fuel%bulk_density = currentPatch%fuel%bulk_density*(1.0_r8/(1.0_r8-currentPatch%fuel%frac_loading(tr_sf))) + ! currentPatch%fuel%SAV = currentPatch%fuel%SAV*(1.0_r8/(1.0_r8-currentPatch%fuel%frac_loading(tr_sf))) + ! else + ! ! somehow the fuel is all trunk. put dummy values from large branches so as not to break things later in code. + ! currentPatch%fuel%bulk_density = SF_val_FBD(lb_sf) + ! currentPatch%fuel%SAV = SF_val_SAV(lb_sf) + ! endif + + ! ! remove trunks from patch%sum_fuel because they should not be included in fire equations + ! ! NOTE: ACF will update this soon to be more clean/bug-proof + ! currentPatch%fuel%total_loading = currentPatch%fuel%total_loading - litter%ag_cwd(tr_sf) + + else + + currentPatch%fuel%SAV = sum(SF_val_SAV(1:nfsc))/(nfsc) ! make average sav to avoid crashing code. + + ! FIX(SPM,032414) refactor...should not have 0 fuel unless everything is burnt off + currentPatch%fuel%bulk_density = 0.0000000001_r8 + currentPatch%fuel%frac_loading(:) = 0.0000000001_r8 + currentPatch%fuel%total_loading = 0.0000000001_r8 + + endif end if currentPatch => currentPatch%younger end do @@ -346,6 +402,9 @@ end subroutine rate_of_spread subroutine ground_fuel_consumption ( currentSite ) !***************************************************************** !returns the the hypothetic fuel consumed by the fire + use SFParamsMod, only: SF_val_mid_moisture, SF_val_mid_moisture_Coeff, SF_val_mid_moisture_Slope + use SFParamsMod, only : SF_val_min_moisture, SF_val_low_moisture_Coeff, SF_val_low_moisture_Slope + use SFParamsMod, only : SF_val_miner_total type(ed_site_type) , intent(in), target :: currentSite type(fates_patch_type), pointer :: currentPatch @@ -354,8 +413,13 @@ subroutine ground_fuel_consumption ( currentSite ) real(r8) :: moist !effective fuel moisture real(r8) :: tau_b(nfsc) !lethal heating rates for each fuel class (min) real(r8) :: fc_ground(nfsc) !total amount of fuel consumed per area of burned ground (kg C / m2 of burned area) - + integer :: tr_sf, tw_sf, dl_sf, lg_sf integer :: c + + tr_sf = fuel_classes%trunks() + tw_sf = fuel_classes%twigs() + dl_sf = fuel_classes%dead_leaves() + lg_sf = fuel_classes%live_grass() currentPatch => currentSite%oldest_patch; @@ -363,7 +427,47 @@ subroutine ground_fuel_consumption ( currentSite ) if(currentPatch%nocomp_pft_label .ne. nocomp_bareground)then - call currentPatch%fuel%BurnFuel(fc_ground) + currentPatch%fuel%frac_burnt(:) = 1.0_r8 + ! Calculate fraction of litter is burnt for all classes. + ! Equation B1 in Thonicke et al. 2010--- + do c = 1, nfsc !work out the burnt fraction for all pools, even if those pools dont exist. + moist = currentPatch%fuel%effective_moisture(c) + ! 1. Very dry litter + if (moist <= SF_val_min_moisture(c)) then + currentPatch%fuel%frac_burnt(c) = 1.0_r8 + endif + ! 2. Low to medium moistures + if (moist > SF_val_min_moisture(c).and.moist <= SF_val_mid_moisture(c)) then + currentPatch%fuel%frac_burnt(c) = max(0.0_r8,min(1.0_r8,SF_val_low_moisture_Coeff(c)- & + SF_val_low_moisture_Slope(c)*moist)) + else + ! For medium to high moistures. + if (moist > SF_val_mid_moisture(c).and.moist <= 1.0_r8) then + currentPatch%fuel%frac_burnt(c) = max(0.0_r8,min(1.0_r8,SF_val_mid_moisture_Coeff(c)- & + SF_val_mid_moisture_Slope(c)*moist)) + endif + + endif + ! Very wet litter + if (moist >= 1.0_r8) then !this shouldn't happen? + currentPatch%fuel%frac_burnt(c) = 0.0_r8 + endif + enddo !c + + ! we can't ever kill -all- of the grass. + currentPatch%fuel%frac_burnt(lg_sf) = min(0.8_r8,currentPatch%fuel%frac_burnt(lg_sf )) + + ! reduce burnt amount for mineral content. + currentPatch%fuel%frac_burnt(:) = currentPatch%fuel%frac_burnt(:) * (1.0_r8-SF_val_miner_total) + + !---Calculate amount of fuel burnt.--- + + litt_c => currentPatch%litter(element_pos(carbon12_element)) + FC_ground(tw_sf:tr_sf) = currentPatch%fuel%frac_burnt(tw_sf:tr_sf) * litt_c%ag_cwd(tw_sf:tr_sf) + FC_ground(dl_sf) = currentPatch%fuel%frac_burnt(dl_sf) * sum(litt_c%leaf_fines(:)) + FC_ground(lg_sf) = currentPatch%fuel%frac_burnt(lg_sf) * currentPatch%livegrass + + !call currentPatch%fuel%BurnFuel(fc_ground) ! Following used for determination of cambial kill follows from Peterson & Ryan (1986) scheme ! less empirical cf current scheme used in SPITFIRE which attempts to mesh Rothermel @@ -372,16 +476,17 @@ subroutine ground_fuel_consumption ( currentSite ) ! taul is the duration of the lethal heating. ! The /10 is to convert from kgC/m2 into gC/cm2, as in the Peterson and Ryan paper #Rosie,Jun 2013 - do c = 1,nfsc_notrunks + do c = 1,nfsc tau_b(c) = 39.4_r8 *(currentPatch%fuel%frac_loading(c)*currentPatch%fuel%total_loading/0.45_r8/10._r8)* & (1.0_r8-((1.0_r8-currentPatch%fuel%frac_burnt(c))**0.5_r8)) enddo + tau_b(tr_sf) = 0.0_r8 ! Cap the residence time to 8mins, as suggested by literature survey by P&R (1986). currentPatch%tau_l = min(8.0_r8,sum(tau_b)) !---calculate overall fuel consumed by spreading fire --- ! ignore 1000hr fuels. Just interested in fuels affecting ROS - currentPatch%TFC_ROS = sum(FC_ground) + currentPatch%TFC_ROS = sum(FC_ground)-FC_ground(tr_sf) end if ! nocomp_pft_label check From f208cb87ed395be09ad94c03acd482748beb78ac Mon Sep 17 00:00:00 2001 From: jessica needham Date: Mon, 9 Sep 2024 06:25:50 -0700 Subject: [PATCH 029/111] fix write of inventory init --- main/FatesInventoryInitMod.F90 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/main/FatesInventoryInitMod.F90 b/main/FatesInventoryInitMod.F90 index 22a48537b5..2ca4c3554f 100644 --- a/main/FatesInventoryInitMod.F90 +++ b/main/FatesInventoryInitMod.F90 @@ -1179,10 +1179,10 @@ subroutine write_inventory_type1(currentSite) else ilon_sign = 'W' end if - - write(pss_name_out,'(A8, I2.2, A1, I5.5, A1)') & + + write(pss_name_out,'(A8,I2.2,A1,I5.5,A1,A1,I3.3,A1,I5.5,A1,A4)') & 'pss_out_',ilat_int,'.',ilat_dec,ilat_sign,'_',ilon_int,'.',ilon_dec,ilon_sign,'.txt' - write(css_name_out,'(A8, I2.2, A1, A1, I3.3, A1)') & + write(css_name_out,'(A8,I2.2,A1,I5.5,A1,A1,I3.3,A1,I5.5,A1,A4)') & 'css_out_',ilat_int,'.',ilat_dec,ilat_sign,'_',ilon_int,'.',ilon_dec,ilon_sign,'.txt' pss_file_out = shr_file_getUnit() @@ -1208,7 +1208,7 @@ subroutine write_inventory_type1(currentSite) do while(associated(currentcohort)) icohort=icohort+1 write(css_file_out,*) '0000 ',trim(patch_str), & - currentCohort%dbh,currentCohort%height,currentCohort%pft,currentCohort%n/currentPatch%area + currentCohort%dbh,-3.0_r8,currentCohort%pft,currentCohort%n/currentPatch%area currentcohort => currentcohort%shorter end do From 20ceac215e7bea5ae18bc421c29def977d1bf53e Mon Sep 17 00:00:00 2001 From: adrifoster Date: Mon, 9 Sep 2024 09:59:47 -0600 Subject: [PATCH 030/111] remove space --- fire/FatesFuelMod.F90 | 1 - 1 file changed, 1 deletion(-) diff --git a/fire/FatesFuelMod.F90 b/fire/FatesFuelMod.F90 index 9b0760af4d..e78ccbe456 100644 --- a/fire/FatesFuelMod.F90 +++ b/fire/FatesFuelMod.F90 @@ -81,7 +81,6 @@ subroutine CalculateLoading(this, leaf_litter, twig_litter, small_branch_litter, this%loading(fuel_classes%small_branches()) = small_branch_litter this%loading(fuel_classes%large_branches()) = large_branch_litter this%loading(fuel_classes%live_grass()) = live_grass - this%loading(fuel_classes%trunks()) = trunk_litter this%trunk_loading = trunk_litter From d58d0b1172cdf5e9916ffcdc01040985f701d507 Mon Sep 17 00:00:00 2001 From: adrifoster Date: Tue, 10 Sep 2024 10:02:49 -0600 Subject: [PATCH 031/111] fix testing errors --- .../functional_testing/fire/FatesTestFireMod.F90 | 4 ++-- testing/functional_testing/fire/FatesTestFuel.F90 | 14 +++----------- .../fire/SyntheticFuelModels.F90 | 9 +++++++++ 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/testing/functional_testing/fire/FatesTestFireMod.F90 b/testing/functional_testing/fire/FatesTestFireMod.F90 index e81d273f47..feff87b951 100644 --- a/testing/functional_testing/fire/FatesTestFireMod.F90 +++ b/testing/functional_testing/fire/FatesTestFireMod.F90 @@ -11,7 +11,7 @@ module FatesTestFireMod use FatesUnitTestIOMod, only : OpenNCFile, GetVar, CloseNCFile, RegisterNCDims use FatesUnitTestIOMod, only : RegisterVar, EndNCDef, WriteVar use FatesUnitTestIOMod, only : type_double, type_int, type_char - use FatesFuelClassesMod, only : nfsc, nfsc_notrunks + use FatesFuelClassesMod, only : nfsc use SyntheticFuelModels, only : fuel_models_array_class use SFParamsMod, only : SF_val_CWD_frac use FatesFuelMod, only : fuel_type @@ -159,7 +159,7 @@ subroutine WriteFireData(out_file, nsteps, nfuelmods, temp_degC, precip, rh, NI, call OpenNCFile(trim(out_file), ncid, 'readwrite') ! register dimensions - call RegisterNCDims(ncid, dim_names, (/nsteps, nfsc_notrunks, nfuelmods/), 3, dimIDs) + call RegisterNCDims(ncid, dim_names, (/nsteps, nfsc, nfuelmods/), 3, dimIDs) ! first register dimension variables diff --git a/testing/functional_testing/fire/FatesTestFuel.F90 b/testing/functional_testing/fire/FatesTestFuel.F90 index 06f678a117..fd59783ecf 100644 --- a/testing/functional_testing/fire/FatesTestFuel.F90 +++ b/testing/functional_testing/fire/FatesTestFuel.F90 @@ -9,7 +9,7 @@ program FatesTestFuel use SFFireWeatherMod, only : fire_weather use SFNesterovMod, only : nesterov_index use FatesFuelMod, only : fuel_type - use FatesFuelClassesMod, only : nfsc, nfsc_notrunks + use FatesFuelClassesMod, only : nfsc use SFParamsMod, only : SF_val_SAV, SF_val_drying_ratio use SFParamsMod, only : SF_val_FBD @@ -44,14 +44,6 @@ program FatesTestFuel ! fuel models to test integer, parameter, dimension(3) :: fuel_models = (/102, 183, 164/) - ! integer, parameter, dimension(52) :: fuel_models = (/1, 2, 101, 102, 104, 107, 121, & - ! 122, 3, 103, 105, 106, 108, 109, & - ! 123, 124, 4, 5, 6, 141, 142, 145, & - ! 147, 161, 164, 10, 7, 143, 144, & - ! 146, 148, 149, 162, 163, 8, 9, & - ! 181, 182, 183, 184, 185, 186, 187, & - ! 188, 189, 11, 12, 13, 201, 202, & - ! 203, 204/) ! number of fuel models to test num_fuel_models = size(fuel_models) @@ -63,8 +55,8 @@ program FatesTestFuel allocate(wind(n_days)) allocate(NI(n_days)) allocate(fuel_moisture(n_days, num_fuel_models)) - allocate(fuel_loading(nfsc_notrunks, num_fuel_models)) - allocate(frac_loading(nfsc_notrunks, num_fuel_models)) + allocate(fuel_loading(nfsc, num_fuel_models)) + allocate(frac_loading(nfsc, num_fuel_models)) allocate(fuel_BD(num_fuel_models)) allocate(fuel_SAV(num_fuel_models)) allocate(total_loading(num_fuel_models)) diff --git a/testing/functional_testing/fire/SyntheticFuelModels.F90 b/testing/functional_testing/fire/SyntheticFuelModels.F90 index c04db80437..34eddfd76a 100644 --- a/testing/functional_testing/fire/SyntheticFuelModels.F90 +++ b/testing/functional_testing/fire/SyntheticFuelModels.F90 @@ -5,6 +5,15 @@ module SyntheticFuelModels implicit none private + integer, parameter, public, dimension(52) :: all_fuel_models = (/1, 2, 101, 102, 104, & + 107, 121, 122, 3, 103, 105, 106, & + 108, 109, 123, 124, 4, 5, 6, 141, & + 142, 145, 147, 161, 164, 10, 7, & + 143, 144, 146, 148, 149, 162, & + 163, 8, 9, 181, 182, 183, 184, & + 185, 186, 187, 188, 189, 11, 12, & + 13, 201, 202, 203, 204/) + integer, parameter :: chunk_size = 10 real(r8), parameter :: ustons_to_kg = 907.185_r8 real(r8), parameter :: acres_to_m2 = 4046.86_r8 From c42cda9ad0a03cffa2a1e5752a55298181e901d8 Mon Sep 17 00:00:00 2001 From: Adrianna Foster Date: Wed, 11 Sep 2024 11:17:38 -0600 Subject: [PATCH 032/111] updates --- fire/FatesFuelMod.F90 | 52 +++++++++++++------------- fire/SFMainMod.F90 | 85 ++++++++----------------------------------- 2 files changed, 41 insertions(+), 96 deletions(-) diff --git a/fire/FatesFuelMod.F90 b/fire/FatesFuelMod.F90 index 9b0760af4d..dd889ccb2b 100644 --- a/fire/FatesFuelMod.F90 +++ b/fire/FatesFuelMod.F90 @@ -130,7 +130,8 @@ subroutine CalculateFractionalLoading(this) end if end do else - this%frac_loading(1:nfsc) = 0.0_r8 + this%frac_loading(1:nfsc) = 0.0000000001_r8 + this%total_loading = 0.0000000001_r8 end if end subroutine CalculateFractionalLoading @@ -186,25 +187,7 @@ subroutine UpdateFuelMoisture(this, sav_fuel, drying_ratio, fireWeatherClass) this%MEF = this%MEF + this%frac_loading(i)*moisture_of_extinction(i) end if end do - - - ! this%MEF = sum(this%frac_loading(tw_sf:lb_sf)*moisture_of_extinction(tw_sf:lb_sf)) - ! this%average_moisture = sum(this%frac_loading(tw_sf:lb_sf)*moisture(tw_sf:lb_sf)) - - ! this%MEF = this%MEF + sum(this%frac_loading(dl_sf:lg_sf)*moisture_of_extinction(dl_sf:lg_sf)) - ! this%average_moisture = this%average_moisture + sum(this%frac_loading(dl_sf:lg_sf)*moisture(dl_sf:lg_sf)) - - ! ! Correct averaging for the fact that we are not using the trunks pool for fire ROS and intensity (5) - ! ! Consumption of fuel in trunk pool does not influence fire ROS or intensity (Pyne 1996) - ! if ((1.0_r8 - this%frac_loading(tr_sf)) > nearzero) then - ! this%MEF = this%MEF*(1.0_r8/(1.0_r8 - this%frac_loading(tr_sf))) - ! this%average_moisture = this%average_moisture*(1.0_r8/(1.0_r8 - this%frac_loading(tr_sf))) - ! else - ! ! somehow the fuel is all trunk. put dummy values from large branches so as not to break things later in code. - ! this%MEF = moisture_of_extinction(lb_sf) - ! this%average_moisture = moisture(lb_sf) - ! endif - + else this%effective_moisture(1:nfsc) = 0.0_r8 this%average_moisture = 0.0000000001_r8 @@ -300,12 +283,21 @@ subroutine AverageBulkDensity(this, bulk_density) class(fuel_type), intent(inout) :: this ! fuel class real(r8), intent(in) :: bulk_density(nfsc) ! bulk density of all fuel types [kg/m2] - if (this%total_loading > nearzero) then - this%bulk_density = sum(this%frac_loading(1:nfsc)*bulk_density(1:nfsc)) - else + ! LOCALS: + integer :: i ! looping index + + if (this%total_loading > nearzero) then this%bulk_density = 0.0_r8 + do i = 1, nfsc + ! average bulk density across all fuel types except trunks + if (i /= fuel_classes%trunks()) then + this%bulk_density = this%bulk_density + this%frac_loading(i)*bulk_density(i) + end if + end do + else + this%bulk_density = 0.0000000001_r8 end if - + end subroutine AverageBulkDensity !------------------------------------------------------------------------------------- @@ -318,10 +310,16 @@ subroutine AverageSAV(this, sav_fuel) class(fuel_type), intent(inout) :: this ! fuel class real(r8), intent(in) :: sav_fuel(nfsc) ! surface area to volume ratio of all fuel types [/cm] - if (this%total_loading > nearzero) then - this%SAV = sum(this%frac_loading(1:nfsc)*sav_fuel(1:nfsc)) - else + if (this%total_loading > nearzero) then this%SAV = 0.0_r8 + do i = 1, nfsc + ! average bulk density across all fuel types except trunks + if (i /= fuel_classes%trunks()) then + this%SAV = this%SAV + this%frac_loading(i)*sav_fuel(i) + end if + end do + else + this%SAV = sum(sav_fuel(1:nfsc))/nfsc ! make average sav to avoid crashing code end if end subroutine AverageSAV diff --git a/fire/SFMainMod.F90 b/fire/SFMainMod.F90 index 442cf38545..c7d3f1dad6 100644 --- a/fire/SFMainMod.F90 +++ b/fire/SFMainMod.F90 @@ -158,9 +158,9 @@ subroutine UpdateFuelCharacteristics(currentSite) ! ! DESCRIPTION: ! Updates fuel characteristics on each patch of the site + ! - use SFParamsMod, only : SF_val_drying_ratio, SF_val_SAV, SF_val_FBD - use FatesConstantsMod, only : nearzero + use SFParamsMod, only : SF_val_drying_ratio, SF_val_SAV, SF_val_FBD ! ARGUMENTS: type(ed_site_type), intent(in), target :: currentSite ! site object @@ -168,15 +168,6 @@ subroutine UpdateFuelCharacteristics(currentSite) ! LOCALS: type(fates_patch_type), pointer :: currentPatch ! FATES patch type(litter_type), pointer :: litter ! pointer to patch litter class - integer :: lg_sf, tr_sf, lb_sf, sb_sf, dl_sf, tw_sf - integer :: i - - dl_sf = fuel_classes%dead_leaves() - lg_sf = fuel_classes%live_grass() - tr_sf = fuel_classes%trunks() - lb_sf = fuel_classes%large_branches() - sb_sf = fuel_classes%small_branches() - tw_sf = fuel_classes%twigs() currentPatch => currentSite%oldest_patch do while(associated(currentPatch)) @@ -188,69 +179,25 @@ subroutine UpdateFuelCharacteristics(currentSite) ! update fuel loading [kgC/m2] litter => currentPatch%litter(element_pos(carbon12_element)) - call currentPatch%fuel%CalculateLoading(sum(litter%leaf_fines(:)), & - litter%ag_cwd(1), litter%ag_cwd(2), litter%ag_cwd(3), litter%ag_cwd(4), & - currentPatch%livegrass) + call currentPatch%fuel%CalculateLoading(sum(litter%leaf_fines(:)), & + litter%ag_cwd(1), litter%ag_cwd(2), litter%ag_cwd(3), litter%ag_cwd(4), & + currentPatch%livegrass) - ! sum up fuel classes and calculate fractional loading for each - call currentPatch%fuel%SumLoading() - call currentPatch%fuel%CalculateFractionalLoading() + ! sum up fuel classes and calculate fractional loading for each + call currentPatch%fuel%SumLoading() + call currentPatch%fuel%CalculateFractionalLoading() + + ! calculate fuel moisture [m3/m3] + call currentPatch%fuel%UpdateFuelMoisture(SF_val_SAV, SF_val_drying_ratio, & + currentSite%fireWeather) - if (currentPatch%fuel%total_loading > 0.0) then - - ! calculate fuel moisture [m3/m3] - call currentPatch%fuel%UpdateFuelMoisture(SF_val_SAV, SF_val_drying_ratio, & - currentSite%fireWeather) - - ! calculate geometric properties - !call currentPatch%fuel%AverageBulkDensity(SF_val_FBD) - !call currentPatch%fuel%AverageSAV(SF_val_SAV) + ! calculate geometric properties + call currentPatch%fuel%AverageBulkDensity(SF_val_FBD) + call currentPatch%fuel%AverageSAV(SF_val_SAV) - currentPatch%fuel%bulk_density = 0.0_r8 - currentPatch%fuel%SAV = 0.0_r8 - do i = 1, nfsc - ! average bulk density and SAV across all fuel types except trunks - if (i /= fuel_classes%trunks()) then - currentPatch%fuel%bulk_density = currentPatch%fuel%bulk_density + currentPatch%fuel%frac_loading(i)*SF_val_FBD(i) - currentPatch%fuel%SAV = currentPatch%fuel%SAV + currentPatch%fuel%frac_loading(i)*SF_val_SAV(i) - end if - end do - - ! ! Average properties over the first three litter pools (twigs, s branches, l branches) - ! currentPatch%fuel%bulk_density = sum(currentPatch%fuel%frac_loading(tw_sf:lb_sf) * SF_val_FBD(tw_sf:lb_sf)) - ! currentPatch%fuel%SAV = sum(currentPatch%fuel%frac_loading(tw_sf:lb_sf) * SF_val_SAV(tw_sf:lb_sf)) - - ! ! Add on properties of dead leaves and live grass pools (5 & 6) - ! currentPatch%fuel%bulk_density = currentPatch%fuel%bulk_density + sum(currentPatch%fuel%frac_loading(dl_sf:lg_sf) * SF_val_FBD(dl_sf:lg_sf)) - ! currentPatch%fuel%SAV = currentPatch%fuel%SAV + sum(currentPatch%fuel%frac_loading(dl_sf:lg_sf) * SF_val_SAV(dl_sf:lg_sf)) - - ! ! Correct averaging for the fact that we are not using the trunks pool for fire ROS and intensity (5) - ! ! Consumption of fuel in trunk pool does not influence fire ROS or intensity (Pyne 1996) - ! if ((1.0_r8 - currentPatch%fuel%frac_loading(tr_sf)) > nearzero) then - ! currentPatch%fuel%bulk_density = currentPatch%fuel%bulk_density*(1.0_r8/(1.0_r8-currentPatch%fuel%frac_loading(tr_sf))) - ! currentPatch%fuel%SAV = currentPatch%fuel%SAV*(1.0_r8/(1.0_r8-currentPatch%fuel%frac_loading(tr_sf))) - ! else - ! ! somehow the fuel is all trunk. put dummy values from large branches so as not to break things later in code. - ! currentPatch%fuel%bulk_density = SF_val_FBD(lb_sf) - ! currentPatch%fuel%SAV = SF_val_SAV(lb_sf) - ! endif - - ! ! remove trunks from patch%sum_fuel because they should not be included in fire equations - ! ! NOTE: ACF will update this soon to be more clean/bug-proof - ! currentPatch%fuel%total_loading = currentPatch%fuel%total_loading - litter%ag_cwd(tr_sf) - - else - - currentPatch%fuel%SAV = sum(SF_val_SAV(1:nfsc))/(nfsc) ! make average sav to avoid crashing code. - - ! FIX(SPM,032414) refactor...should not have 0 fuel unless everything is burnt off - currentPatch%fuel%bulk_density = 0.0000000001_r8 - currentPatch%fuel%frac_loading(:) = 0.0000000001_r8 - currentPatch%fuel%total_loading = 0.0000000001_r8 - - endif end if currentPatch => currentPatch%younger + end do end subroutine UpdateFuelCharacteristics From 3d9d014b52b79871baae64085e71be225d9a9f5c Mon Sep 17 00:00:00 2001 From: Adrianna Foster Date: Thu, 12 Sep 2024 15:25:41 -0600 Subject: [PATCH 033/111] fixing errors --- biogeochem/EDPatchDynamicsMod.F90 | 26 +++-- biogeochem/FatesPatchMod.F90 | 6 +- fire/FatesFuelMod.F90 | 82 ++++++++++---- fire/SFMainMod.F90 | 29 +++-- testing/CMakeLists.txt | 3 +- testing/run_fates_tests.py | 9 ++ .../fire_fuel_test/CMakeLists.txt | 5 + .../fire_fuel_test/test_FireFuel.pf | 100 ++++++++++++++++++ 8 files changed, 216 insertions(+), 44 deletions(-) create mode 100644 testing/unit_testing/fire_fuel_test/CMakeLists.txt create mode 100644 testing/unit_testing/fire_fuel_test/test_FireFuel.pf diff --git a/biogeochem/EDPatchDynamicsMod.F90 b/biogeochem/EDPatchDynamicsMod.F90 index 781aee2959..36b29d99fb 100644 --- a/biogeochem/EDPatchDynamicsMod.F90 +++ b/biogeochem/EDPatchDynamicsMod.F90 @@ -3251,6 +3251,8 @@ subroutine fuse_2_patches(csite, dp, rp) do el = 1,num_elements call rp%litter(el)%FuseLitter(rp%area,dp%area,dp%litter(el)) end do + + call rp%fuel%Fuse(rp%area, dp%area, dp%fuel) if ( rp%land_use_label .ne. dp%land_use_label) then write(fates_log(),*) 'trying to fuse patches with different land_use_label values' @@ -3281,22 +3283,24 @@ subroutine fuse_2_patches(csite, dp, rp) call rp%tveg_longterm%FuseRMean(dp%tveg_longterm,rp%area*inv_sum_area) - rp%fuel%average_moisture = (dp%fuel%average_moisture*dp%area + rp%fuel%average_moisture*rp%area) * inv_sum_area - rp%livegrass = (dp%livegrass*dp%area + rp%livegrass*rp%area) * inv_sum_area - rp%fuel%total_loading = (dp%fuel%total_loading*dp%area + rp%fuel%total_loading*rp%area) * inv_sum_area - rp%fuel%frac_loading = (dp%fuel%frac_loading(:)*dp%area + rp%fuel%frac_loading(:)*rp%area) * inv_sum_area - rp%fuel%bulk_density = (dp%fuel%bulk_density*dp%area + rp%fuel%bulk_density*rp%area) * inv_sum_area - rp%fuel%SAV = (dp%fuel%SAV*dp%area + rp%fuel%SAV*rp%area) * inv_sum_area - rp%fuel%MEF = (dp%fuel%MEF*dp%area + rp%fuel%MEF*rp%area) * inv_sum_area - rp%ros_front = (dp%ros_front*dp%area + rp%ros_front*rp%area) * inv_sum_area - rp%tau_l = (dp%tau_l*dp%area + rp%tau_l*rp%area) * inv_sum_area + rp%fuel%average_moisture = (dp%fuel%average_moisture*dp%area + rp%fuel%average_moisture*rp%area) * inv_sum_area + rp%fuel%effective_moisture = (dp%fuel%effective_moisture(:)*dp%area + rp%fuel%effective_moisture(:)*rp%area) * inv_sum_area + rp%livegrass = (dp%livegrass*dp%area + rp%livegrass*rp%area) * inv_sum_area + !rp%fuel%total_loading = (dp%fuel%total_loading*dp%area + rp%fuel%total_loading*rp%area) * inv_sum_area + !rp%fuel%frac_loading = (dp%fuel%frac_loading(:)*dp%area + rp%fuel%frac_loading(:)*rp%area) * inv_sum_area + !rp%fuel%loading = (dp%fuel%loading(:)*dp%area + rp%fuel%loading(:)*rp%area) * inv_sum_area + !rp%fuel%bulk_density = (dp%fuel%bulk_density*dp%area + rp%fuel%bulk_density*rp%area) * inv_sum_area + !rp%fuel%SAV = (dp%fuel%SAV*dp%area + rp%fuel%SAV*rp%area) * inv_sum_area + !rp%fuel%MEF = (dp%fuel%MEF*dp%area + rp%fuel%MEF*rp%area) * inv_sum_area + rp%ros_front = (dp%ros_front*dp%area + rp%ros_front*rp%area) * inv_sum_area + rp%tau_l = (dp%tau_l*dp%area + rp%tau_l*rp%area) * inv_sum_area rp%tfc_ros = (dp%tfc_ros*dp%area + rp%tfc_ros*rp%area) * inv_sum_area rp%fi = (dp%fi*dp%area + rp%fi*rp%area) * inv_sum_area rp%fd = (dp%fd*dp%area + rp%fd*rp%area) * inv_sum_area rp%ros_back = (dp%ros_back*dp%area + rp%ros_back*rp%area) * inv_sum_area rp%scorch_ht(:) = (dp%scorch_ht(:)*dp%area + rp%scorch_ht(:)*rp%area) * inv_sum_area - rp%frac_burnt = (dp%frac_burnt*dp%area + rp%frac_burnt*rp%area) * inv_sum_area - rp%fuel%frac_burnt(:) = (dp%fuel%frac_burnt(:)*dp%area + rp%fuel%frac_burnt(:)*rp%area) * inv_sum_area + !rp%frac_burnt = (dp%frac_burnt*dp%area + rp%frac_burnt*rp%area) * inv_sum_area + !rp%fuel%frac_burnt(:) = (dp%fuel%frac_burnt(:)*dp%area + rp%fuel%frac_burnt(:)*rp%area) * inv_sum_area rp%btran_ft(:) = (dp%btran_ft(:)*dp%area + rp%btran_ft(:)*rp%area) * inv_sum_area rp%zstar = (dp%zstar*dp%area + rp%zstar*rp%area) * inv_sum_area rp%c_stomata = (dp%c_stomata*dp%area + rp%c_stomata*rp%area) * inv_sum_area diff --git a/biogeochem/FatesPatchMod.F90 b/biogeochem/FatesPatchMod.F90 index 7029a5770c..2b8cba368c 100644 --- a/biogeochem/FatesPatchMod.F90 +++ b/biogeochem/FatesPatchMod.F90 @@ -214,10 +214,10 @@ module FatesPatchMod real(r8) :: fi ! average fire intensity of flaming front [kJ/m/s] or [kW/m] integer :: fire ! is there a fire? [1=yes; 0=no] real(r8) :: fd ! fire duration [min] + real(r8) :: frac_burnt ! fraction of patch burnt by fire ! fire effects real(r8) :: scorch_ht(maxpft) ! scorch height [m] - real(r8) :: frac_burnt ! fraction burnt [0-1/day] real(r8) :: tfc_ros ! total intensity-relevant fuel consumed - no trunks [kgC/m2 of burned ground/day] !--------------------------------------------------------------------------- @@ -506,8 +506,8 @@ subroutine NanValues(this) this%fire = fates_unset_int this%fd = nan this%scorch_ht(:) = nan + this%tfc_ros = nan this%frac_burnt = nan - this%tfc_ros = nan end subroutine NanValues @@ -591,8 +591,8 @@ subroutine ZeroValues(this) this%fi = 0.0_r8 this%fd = 0.0_r8 this%scorch_ht(:) = 0.0_r8 - this%frac_burnt = 0.0_r8 this%tfc_ros = 0.0_r8 + this%frac_burnt = 0.0_r8 end subroutine ZeroValues diff --git a/fire/FatesFuelMod.F90 b/fire/FatesFuelMod.F90 index d0764ba25a..2b5b3eae76 100644 --- a/fire/FatesFuelMod.F90 +++ b/fire/FatesFuelMod.F90 @@ -14,20 +14,20 @@ module FatesFuelMod type, public :: fuel_type - real(r8) :: loading(nfsc) ! fuel loading of non-trunks fuel class [kgC/m2] - real(r8) :: trunk_loading ! fuel loading of trunk fuel class [kgC/m2] - real(r8) :: effective_moisture(nfsc) ! fuel effective moisture all fuel class (moisture/MEF) [m3/m3] - real(r8) :: frac_loading(nfsc) ! fractional loading of non-trunk fuel classes [0-1] - real(r8) :: frac_burnt(nfsc) ! fraction of litter burnt by fire [0-1] - real(r8) :: total_loading ! total fuel loading - DOES NOT INCLUDE TRUNKS [kgC/m2] - real(r8) :: average_moisture ! weighted average of fuel moisture across non-trunk fuel classes [m3/m3] - real(r8) :: bulk_density ! weighted average of bulk density across non-trunk fuel classes [kg/m3] - real(r8) :: SAV ! weighted average of surface area to volume ratio across non-trunk fuel classes [/cm] - real(r8) :: MEF ! weighted average of moisture of extinction across non-trunk fuel classes [m3/m3] + real(r8) :: loading(nfsc) ! fuel loading of each fuel class [kgC/m2] + real(r8) :: effective_moisture(nfsc) ! fuel effective moisture all fuel class (moisture/MEF) [m3/m3] + real(r8) :: frac_loading(nfsc) ! fractional loading of all fuel classes [0-1] + real(r8) :: frac_burnt(nfsc) ! fraction of litter burnt by fire [0-1] + real(r8) :: total_loading ! total fuel loading - DOES NOT INCLUDE TRUNKS [kgC/m2] + real(r8) :: average_moisture ! weighted average of fuel moisture across non-trunk fuel classes [m3/m3] + real(r8) :: bulk_density ! weighted average of bulk density across non-trunk fuel classes [kg/m3] + real(r8) :: SAV ! weighted average of surface area to volume ratio across non-trunk fuel classes [/cm] + real(r8) :: MEF ! weighted average of moisture of extinction across non-trunk fuel classes [m3/m3] contains procedure :: Init + procedure :: Fuse procedure :: CalculateLoading procedure :: SumLoading procedure :: CalculateFractionalLoading @@ -48,7 +48,6 @@ subroutine Init(this) ! just zero everything this%loading(1:nfsc) = 0.0_r8 - this%trunk_loading = 0.0_r8 this%frac_loading(1:nfsc) = 0.0_r8 this%frac_burnt(1:nfsc) = 0.0_r8 this%effective_moisture(1:nfsc) = 0.0_r8 @@ -59,6 +58,48 @@ subroutine Init(this) this%MEF = 0.0_r8 end subroutine Init + + !------------------------------------------------------------------------------------- + + subroutine Fuse(this, self_area, donor_area, donor_fuel) + ! DESCRIPTION: + ! Fuse attributes of this object with another + + ! ARGUMENTS: + class(fuel_type), intent(inout) :: this ! fuel class + real(r8), intent(in) :: self_area ! area of this fuel class's patch [m2] + real(r8), intent(in) :: donor_area ! area of donor fuel class's patch [m2] + type(fuel_type), intent(in) :: donor_fuel ! donor fuel class + + ! LOCALS: + integer :: i ! looping index + real(r8) :: self_weight ! weighting of the receiving fuel class + real(r8) :: donor_weight ! weighting of the donor fuel class + + self_weight = self_area/(donor_area + self_area) + donor_weight = 1.0_r8 - self_weight + + do i = 1, nfsc + this%loading(i) = this%loading(i)*self_weight + & + donor_fuel%loading(i)*donor_weight + this%frac_loading(i) = this%frac_loading(i)*self_weight + & + donor_fuel%frac_loading(i)*donor_weight + this%frac_burnt(i) = this%frac_burnt(i)*self_weight + & + donor_fuel%frac_burnt(i)*donor_weight + this%effective_moisture(i) = this%effective_moisture(i)*self_weight + & + donor_fuel%effective_moisture(i)*donor_weight + end do + + this%total_loading = this%total_loading*self_weight + & + donor_fuel%total_loading*donor_weight + this%average_moisture = this%average_moisture*self_weight + & + donor_fuel%average_moisture*donor_weight + this%bulk_density = this%bulk_density*self_weight + & + donor_fuel%bulk_density*donor_weight + this%SAV = this%SAV*self_weight + donor_fuel%SAV*donor_weight + this%MEF = this%MEF*self_weight + donor_fuel%MEF*donor_weight + + end subroutine Fuse !------------------------------------------------------------------------------------- @@ -82,7 +123,6 @@ subroutine CalculateLoading(this, leaf_litter, twig_litter, small_branch_litter, this%loading(fuel_classes%large_branches()) = large_branch_litter this%loading(fuel_classes%live_grass()) = live_grass this%loading(fuel_classes%trunks()) = trunk_litter - this%trunk_loading = trunk_litter end subroutine CalculateLoading @@ -126,6 +166,8 @@ subroutine CalculateFractionalLoading(this) do i = 1, nfsc if (i /= fuel_classes%trunks()) then this%frac_loading(i) = this%loading(i)/this%total_loading + else + this%frac_loading(i) = 0.0_r8 end if end do else @@ -141,8 +183,6 @@ subroutine UpdateFuelMoisture(this, sav_fuel, drying_ratio, fireWeatherClass) ! DESCRIPTION: ! Updates fuel moisture depending on what fire weather class is in use - use SFParamsMod, only : SF_val_SAV, SF_val_drying_ratio - ! ARGUMENTS: class(fuel_type), intent(inout) :: this ! fuel class real(r8), intent(in) :: sav_fuel(nfsc) ! surface area to volume ratio of all fuel types [/cm] @@ -152,14 +192,7 @@ subroutine UpdateFuelMoisture(this, sav_fuel, drying_ratio, fireWeatherClass) real(r8) :: moisture(nfsc) ! fuel moisture [m3/m3] real(r8) :: moisture_of_extinction(nfsc) ! fuel moisture of extinction [m3/m3] integer :: i ! looping index - integer :: tw_sf, dl_sf, lg_sf, lb_sf, tr_sf - - tw_sf = fuel_classes%twigs() - dl_sf = fuel_classes%dead_leaves() - lg_sf = fuel_classes%live_grass() - lb_sf = fuel_classes%large_branches() - tr_sf = fuel_classes%trunks() - + if (this%total_loading > nearzero) then ! calculate fuel moisture [m3/m3] for each fuel class depending on what ! fire weather class is in use @@ -309,6 +342,9 @@ subroutine AverageSAV(this, sav_fuel) class(fuel_type), intent(inout) :: this ! fuel class real(r8), intent(in) :: sav_fuel(nfsc) ! surface area to volume ratio of all fuel types [/cm] + ! LOCALS: + integer :: i ! looping index + if (this%total_loading > nearzero) then this%SAV = 0.0_r8 do i = 1, nfsc @@ -323,6 +359,6 @@ subroutine AverageSAV(this, sav_fuel) end subroutine AverageSAV - !------------------------------------------------------------------------------------- + !--------------------------------------------------------------------------------------- end module FatesFuelMod \ No newline at end of file diff --git a/fire/SFMainMod.F90 b/fire/SFMainMod.F90 index c7d3f1dad6..0ba9ff1904 100644 --- a/fire/SFMainMod.F90 +++ b/fire/SFMainMod.F90 @@ -73,6 +73,8 @@ subroutine fire_model(currentSite, bc_in) currentPatch%fire = 0 currentPatch => currentPatch%older end do + + if (hlm_spitfire_mode > hlm_sf_nofire_def) then call UpdateFireWeather(currentSite, bc_in) @@ -213,7 +215,7 @@ subroutine rate_of_spread (currentSite) SF_val_part_dens, & SF_val_miner_damp, & SF_val_fuel_energy - + use FatesConstantsMod, only : nearzero type(ed_site_type), intent(in), target :: currentSite type(fates_patch_type), pointer :: currentPatch @@ -236,7 +238,12 @@ subroutine rate_of_spread (currentSite) do while(associated(currentPatch)) - if(currentPatch%nocomp_pft_label .ne. nocomp_bareground)then + if(currentPatch%nocomp_pft_label .ne. nocomp_bareground .and. currentPatch%fuel%total_loading > 0.0_r8)then + + if (hlm_masterproc == itrue) then + write(fates_log(), *) 'bulk_density', currentPatch%fuel%bulk_density + write(fates_log(), *) 'sav', currentPatch%fuel%SAV + end if ! remove mineral content from net fuel load per Thonicke 2010 for ir calculation currentPatch%fuel%total_loading = currentPatch%fuel%total_loading * (1.0_r8 - SF_val_miner_total) !net of minerals @@ -249,16 +256,26 @@ subroutine rate_of_spread (currentSite) 'SF - SF_val_part_dens ',SF_val_part_dens ! beta = packing ratio (unitless) - ! fraction of fuel array volume occupied by fuel or compactness of fuel bed + ! fraction of fuel array volume occupied by fuel or compactness of fuel bed beta = currentPatch%fuel%bulk_density/SF_val_part_dens ! Equation A6 in Thonicke et al. 2010 - ! packing ratio (unitless) - beta_op = 0.200395_r8 *(currentPatch%fuel%SAV**(-0.8189_r8)) + ! packing ratio (unitless) + if (currentPatch%fuel%SAV < nearzero) then + if ( hlm_masterproc == itrue) write(fates_log(),*) 'SF - sav ',currentPatch%fuel%SAV + if ( hlm_masterproc == itrue) write(fates_log(),*) 'SF - loading ',currentPatch%fuel%total_loading + beta_op = 0.0_r8 + else + beta_op = 0.200395_r8 *(currentPatch%fuel%SAV**(-0.8189_r8)) + end if if ( hlm_masterproc == itrue .and.debug) write(fates_log(),*) 'SF - beta ',beta if ( hlm_masterproc == itrue .and.debug) write(fates_log(),*) 'SF - beta_op ',beta_op - beta_ratio = beta/beta_op !unitless + if (beta_op < nearzero) then + beta_ratio = 0.0_r8 + else + beta_ratio = beta/beta_op !unitless + end if if(write_sf == itrue)then if ( hlm_masterproc == itrue ) write(fates_log(),*) 'esf ',currentPatch%fuel%average_moisture diff --git a/testing/CMakeLists.txt b/testing/CMakeLists.txt index e6e600108f..71960cc587 100644 --- a/testing/CMakeLists.txt +++ b/testing/CMakeLists.txt @@ -6,4 +6,5 @@ add_subdirectory(functional_testing/math_utils fates_math_ftest) add_subdirectory(functional_testing/fire fates_fuel_test) ## Unit tests -add_subdirectory(unit_testing/fire_weather_test fates_fire_weather_utest) \ No newline at end of file +add_subdirectory(unit_testing/fire_weather_test fates_fire_weather_utest) +add_subdirectory(unit_testing/fire_fuel_test fates_fire_fuel_utest) \ No newline at end of file diff --git a/testing/run_fates_tests.py b/testing/run_fates_tests.py index 1ceac40c85..2e12f84d4b 100755 --- a/testing/run_fates_tests.py +++ b/testing/run_fates_tests.py @@ -88,6 +88,15 @@ "use_param_file": False, "other_args": [], "plotting_function": None, + }, + "fuel":{ + "test_dir": "fates_fire_fuel_utest", + "test_exe": None, + "out_file": None, + "has_unit_test": True, + "use_param_file": False, + "other_args": [], + "plotting_function": None, } } diff --git a/testing/unit_testing/fire_fuel_test/CMakeLists.txt b/testing/unit_testing/fire_fuel_test/CMakeLists.txt new file mode 100644 index 0000000000..3efadb9021 --- /dev/null +++ b/testing/unit_testing/fire_fuel_test/CMakeLists.txt @@ -0,0 +1,5 @@ +set(pfunit_sources test_FireFuel.pf) + +add_pfunit_ctest(FireFuel + TEST_SOURCES "${pfunit_sources}" + LINK_LIBRARIES fates csm_share) \ No newline at end of file diff --git a/testing/unit_testing/fire_fuel_test/test_FireFuel.pf b/testing/unit_testing/fire_fuel_test/test_FireFuel.pf new file mode 100644 index 0000000000..5271283e52 --- /dev/null +++ b/testing/unit_testing/fire_fuel_test/test_FireFuel.pf @@ -0,0 +1,100 @@ +module test_FireFuel + ! + ! DESCRIPTION: + ! Test the FATES fuel portion of the SPITFIRE model + ! + use FatesConstantsMod, only : r8 => fates_r8 + use FatesFuelMod, only : fuel_type + use FatesFuelClassesMod, only : fuel_classes, nfsc + use funit + + implicit none + + @TestCase + type, extends(TestCase) :: TestFireFuel + type(fuel_type) :: fuel + contains + procedure :: setUp + end type TestFireFuel + + real(r8), parameter :: tol = 1.e-13_r8 + + contains + + subroutine setUp(this) + class(TestFireFuel), intent(inout) :: this + call this%fuel%Init() + end subroutine setUp + + @Test + subroutine CalculateLoading_CorrectInputOrder(this) + ! test that the calculate loading subroutine correctly sets the fuel values + class(TestFireFuel), intent(inout) :: this ! fuel test object + real(r8) :: leaf_litter = 5.0_r8 ! leaf litter [kgC/m2] + real(r8) :: twig_litter = 10.0_r8 ! twig litter [kgC/m2] + real(r8) :: sm_br_litter = 15.0_r8 ! small branch litter [kgC/m2] + real(r8) :: lg_br_litter = 20.0_r8 ! large branch litter [kgC/m2] + real(r8) :: trunk_litter = 25.0_r8 ! trunk branch litter [kgC/m2] + real(r8) :: live_grass = 30.0_r8 ! live grass [kgC/m2] + + call this%fuel%CalculateLoading(leaf_litter, twig_litter, sm_br_litter, & + lg_br_litter, trunk_litter, live_grass) + + @assertEqual(this%fuel%loading(fuel_classes%dead_leaves()), leaf_litter, tolerance=tol) + @assertEqual(this%fuel%loading(fuel_classes%twigs()), twig_litter, tolerance=tol) + @assertEqual(this%fuel%loading(fuel_classes%small_branches()), sm_br_litter, tolerance=tol) + @assertEqual(this%fuel%loading(fuel_classes%large_branches()), lg_br_litter, tolerance=tol) + @assertEqual(this%fuel%loading(fuel_classes%trunks()), trunk_litter, tolerance=tol) + @assertEqual(this%fuel%loading(fuel_classes%live_grass()), live_grass, tolerance=tol) + + end subroutine CalculateLoading_CorrectInputOrder + + @Test + subroutine SumLoading_CorrectValues(this) + ! test that the fuel is summed correctly (and ignores trunks) + class(TestFireFuel), intent(inout) :: this ! fuel test object + real(r8) :: dummy_litter = 5.0_r8 ! dummy litter value [kgC/m2] + real(r8) :: trunk_litter = 100.0_r8 ! trunk branch litter [kgC/m2] + real(r8) :: total_loading ! what total loading should be [kgC/m2] + + total_loading = dummy_litter*5.0_r8 + + call this%fuel%CalculateLoading(dummy_litter, dummy_litter, dummy_litter, & + dummy_litter, trunk_litter, dummy_litter) + + call this%fuel%SumLoading() + + @assertEqual(this%fuel%total_loading, total_loading, tolerance=tol) + + end subroutine SumLoading_CorrectValues + + @Test + subroutine CalculateFractionalLoading_CorrectValues(this) + ! test that the fractional loading is calculated correctly (and ignores trunks) + class(TestFireFuel), intent(inout) :: this ! fuel test object + real(r8) :: dummy_litter = 5.0_r8 ! dummy litter value [kgC/m2] + real(r8) :: trunk_litter = 100.0_r8 ! trunk branch litter [kgC/m2] + real(r8) :: total_loading ! what total loading should be [kgC/m2] + real(r8) :: frac_loading ! what the fractional loading should be [0-1] + integer :: i ! looping index + + total_loading = dummy_litter*float(nfsc - 1) + frac_loading = dummy_litter/total_loading + + call this%fuel%CalculateLoading(dummy_litter, dummy_litter, dummy_litter, & + dummy_litter, trunk_litter, dummy_litter) + + call this%fuel%SumLoading() + call this%fuel%CalculateFractionalLoading() + + do i = 1, nfsc + if (i /= fuel_classes%trunks()) then + @assertEqual(this%fuel%frac_loading(i), frac_loading, tolerance=tol) + else + @assertEqual(this%fuel%frac_loading(i), 0.0_r8, tolerance=tol) + end if + end do + + end subroutine CalculateFractionalLoading_CorrectValues + +end module test_FireFuel \ No newline at end of file From 59053b92cfb1ab444f3b435032ec2ea384229b8e Mon Sep 17 00:00:00 2001 From: Adrianna Foster Date: Fri, 13 Sep 2024 12:51:54 -0600 Subject: [PATCH 034/111] updates --- fire/SFMainMod.F90 | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/fire/SFMainMod.F90 b/fire/SFMainMod.F90 index 0ba9ff1904..337b41b398 100644 --- a/fire/SFMainMod.F90 +++ b/fire/SFMainMod.F90 @@ -140,7 +140,10 @@ subroutine UpdateFireWeather(currentSite, bc_in) wind = bc_in%wind24_pa(iofp) ! convert to m/min - currentSite%wind = wind*sec_per_min + currentSite%wind = wind*sec_per_min + if (hlm_masterproc == itrue) then + write(fates_log(),*) 'wind_in', wind + end if ! update fire weather index call currentSite%fireWeather%UpdateIndex(temp_C, precip, rh, wind) @@ -196,6 +199,11 @@ subroutine UpdateFuelCharacteristics(currentSite) ! calculate geometric properties call currentPatch%fuel%AverageBulkDensity(SF_val_FBD) call currentPatch%fuel%AverageSAV(SF_val_SAV) + if (currentSite%lat == 10.0 .and. currentSite%lon == 120.0 .and. currentPatch%patchno ==2 ) then + write(fates_log(), *) 'sav', currentPatch%fuel%SAV + write(fates_log(), *) 'bd', currentPatch%fuel%bulk_density + write(fates_log(), *) 'leaf_fines', sum(litter%leaf_fines(:)) + end if end if currentPatch => currentPatch%younger @@ -238,13 +246,8 @@ subroutine rate_of_spread (currentSite) do while(associated(currentPatch)) - if(currentPatch%nocomp_pft_label .ne. nocomp_bareground .and. currentPatch%fuel%total_loading > 0.0_r8)then - - if (hlm_masterproc == itrue) then - write(fates_log(), *) 'bulk_density', currentPatch%fuel%bulk_density - write(fates_log(), *) 'sav', currentPatch%fuel%SAV - end if - + if(currentPatch%nocomp_pft_label .ne. nocomp_bareground .and. currentPatch%fuel%total_loading > nearzero)then + ! remove mineral content from net fuel load per Thonicke 2010 for ir calculation currentPatch%fuel%total_loading = currentPatch%fuel%total_loading * (1.0_r8 - SF_val_miner_total) !net of minerals From 00d02de32dca5d9fb2188e76e891beaafb1ff6aa Mon Sep 17 00:00:00 2001 From: adrifoster Date: Tue, 17 Sep 2024 10:03:37 -0600 Subject: [PATCH 035/111] fix parameter file --- fire/FatesFuelClassesMod.F90 | 10 ++++------ parameter_files/fates_params_default.cdl | 18 +++++++++--------- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/fire/FatesFuelClassesMod.F90 b/fire/FatesFuelClassesMod.F90 index a127017fc3..6678c3c5a2 100644 --- a/fire/FatesFuelClassesMod.F90 +++ b/fire/FatesFuelClassesMod.F90 @@ -5,19 +5,17 @@ module FatesFuelClassesMod implicit none private - integer, parameter, public :: nfsc = 6 ! number of total fuel classes - !integer, parameter, public :: nfsc_notrunks = nfsc - 1 ! number of fuel classes without trunks - + integer, parameter, public :: nfsc = 6 ! number of total fuel classes + type :: fuel_classes_type ! There are six fuel classes: - ! 1) twigs, 2) small branches, 3) large branches - ! 4) dead leaves, 5) live grass, 6) trunks + ! 1) twigs, 2) small branches, 3) large branches 4) trunks + ! 5) dead leaves, 6) live grass integer, private :: twigs_i = 1 ! array index for twigs pool integer, private :: small_branches_i = 2 ! array index for small branches pool integer, private :: large_branches_i = 3 ! array index for large branches pool integer, private :: dead_leaves_i = 5 ! array index for dead leaves pool integer, private :: live_grass_i = 6 ! array index for live grass pool - integer, private :: trunks_i = 4 ! array index for trunks pool contains diff --git a/parameter_files/fates_params_default.cdl b/parameter_files/fates_params_default.cdl index 5f78c35699..b66336bbf2 100644 --- a/parameter_files/fates_params_default.cdl +++ b/parameter_files/fates_params_default.cdl @@ -1637,23 +1637,23 @@ data: 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 ; - fates_fire_FBD = 15.4, 16.8, 19.6, 4, 4, 999 ; + fates_fire_FBD = 15.4, 16.8, 19.6, 999, 4, 4 ; - fates_fire_low_moisture_Coeff = 1.12, 1.09, 0.98, 1.15, 1.15, 0.8 ; + fates_fire_low_moisture_Coeff = 1.12, 1.09, 0.98, 0.8, 1.15, 1.15 ; - fates_fire_low_moisture_Slope = 0.62, 0.72, 0.85, 0.62, 0.62, 0.8 ; + fates_fire_low_moisture_Slope = 0.62, 0.72, 0.85, 0.8, 0.62, 0.62 ; - fates_fire_mid_moisture = 0.72, 0.51, 0.38, 0.8, 0.8, 1 ; + fates_fire_mid_moisture = 0.72, 0.51, 0.38, 1, 0.8, 0.8 ; - fates_fire_mid_moisture_Coeff = 2.35, 1.47, 1.06, 3.2, 3.2, 0.8 ; + fates_fire_mid_moisture_Coeff = 2.35, 1.47, 1.06, 0.8, 3.2, 3.2 ; - fates_fire_mid_moisture_Slope = 2.35, 1.47, 1.06, 3.2, 3.2, 0.8 ; + fates_fire_mid_moisture_Slope = 2.35, 1.47, 1.06, 0.8, 3.2, 3.2 ; - fates_fire_min_moisture = 0.18, 0.12, 0, 0.24, 0.24, 0 ; + fates_fire_min_moisture = 0.18, 0.12, 0, 0, 0.24, 0.24 ; - fates_fire_SAV = 13, 3.58, 0.98, 66, 66, 0.2 ; + fates_fire_SAV = 13, 3.58, 0.98, 0.2, 66, 66 ; - fates_frag_maxdecomp = 0.52, 0.383, 0.383, 1, 999, 0.19 ; + fates_frag_maxdecomp = 0.52, 0.383, 0.383, 0.19, 1, 999 ; fates_frag_cwd_frac = 0.045, 0.075, 0.21, 0.67 ; From 82b3f04743c71c3e35bd7cb3765b3b95ec1e7a93 Mon Sep 17 00:00:00 2001 From: Adrianna Foster Date: Tue, 24 Sep 2024 14:50:14 -0600 Subject: [PATCH 036/111] fix div zero issues --- fire/FatesFuelMod.F90 | 13 ++++++------- fire/SFMainMod.F90 | 7 ------- 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/fire/FatesFuelMod.F90 b/fire/FatesFuelMod.F90 index 2b5b3eae76..fde854a00e 100644 --- a/fire/FatesFuelMod.F90 +++ b/fire/FatesFuelMod.F90 @@ -171,8 +171,8 @@ subroutine CalculateFractionalLoading(this) end if end do else - this%frac_loading(1:nfsc) = 0.0000000001_r8 - this%total_loading = 0.0000000001_r8 + this%frac_loading(1:nfsc) = 0.0_r8 + this%total_loading = 0.0_r8 end if end subroutine CalculateFractionalLoading @@ -222,8 +222,8 @@ subroutine UpdateFuelMoisture(this, sav_fuel, drying_ratio, fireWeatherClass) else this%effective_moisture(1:nfsc) = 0.0_r8 - this%average_moisture = 0.0000000001_r8 - this%MEF = 0.0000000001_r8 + this%average_moisture = 0.0_r8 + this%MEF = 0.0_r8 end if end subroutine UpdateFuelMoisture @@ -254,7 +254,6 @@ subroutine CalculateFuelMoistureNesterov(sav_fuel, drying_ratio, NI, moisture) else alpha_FMC = sav_fuel(i)/drying_ratio end if - ! Equation moisture(i) = exp(-1.0_r8*alpha_FMC*NI) end do @@ -327,7 +326,7 @@ subroutine AverageBulkDensity(this, bulk_density) end if end do else - this%bulk_density = 0.0000000001_r8 + this%bulk_density = sum(bulk_density(1:nfsc))/nfsc end if end subroutine AverageBulkDensity @@ -354,7 +353,7 @@ subroutine AverageSAV(this, sav_fuel) end if end do else - this%SAV = sum(sav_fuel(1:nfsc))/nfsc ! make average sav to avoid crashing code + this%SAV = sum(sav_fuel(1:nfsc))/nfsc end if end subroutine AverageSAV diff --git a/fire/SFMainMod.F90 b/fire/SFMainMod.F90 index 337b41b398..505ec3e7a0 100644 --- a/fire/SFMainMod.F90 +++ b/fire/SFMainMod.F90 @@ -199,11 +199,6 @@ subroutine UpdateFuelCharacteristics(currentSite) ! calculate geometric properties call currentPatch%fuel%AverageBulkDensity(SF_val_FBD) call currentPatch%fuel%AverageSAV(SF_val_SAV) - if (currentSite%lat == 10.0 .and. currentSite%lon == 120.0 .and. currentPatch%patchno ==2 ) then - write(fates_log(), *) 'sav', currentPatch%fuel%SAV - write(fates_log(), *) 'bd', currentPatch%fuel%bulk_density - write(fates_log(), *) 'leaf_fines', sum(litter%leaf_fines(:)) - end if end if currentPatch => currentPatch%younger @@ -265,8 +260,6 @@ subroutine rate_of_spread (currentSite) ! Equation A6 in Thonicke et al. 2010 ! packing ratio (unitless) if (currentPatch%fuel%SAV < nearzero) then - if ( hlm_masterproc == itrue) write(fates_log(),*) 'SF - sav ',currentPatch%fuel%SAV - if ( hlm_masterproc == itrue) write(fates_log(),*) 'SF - loading ',currentPatch%fuel%total_loading beta_op = 0.0_r8 else beta_op = 0.200395_r8 *(currentPatch%fuel%SAV**(-0.8189_r8)) From b7f1a01c5a313f23645a6fc269dad94d6031a130 Mon Sep 17 00:00:00 2001 From: Adrianna Foster Date: Tue, 24 Sep 2024 15:09:45 -0600 Subject: [PATCH 037/111] fix comments --- biogeochem/EDPatchDynamicsMod.F90 | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/biogeochem/EDPatchDynamicsMod.F90 b/biogeochem/EDPatchDynamicsMod.F90 index 36b29d99fb..e27e9247a2 100644 --- a/biogeochem/EDPatchDynamicsMod.F90 +++ b/biogeochem/EDPatchDynamicsMod.F90 @@ -3286,12 +3286,6 @@ subroutine fuse_2_patches(csite, dp, rp) rp%fuel%average_moisture = (dp%fuel%average_moisture*dp%area + rp%fuel%average_moisture*rp%area) * inv_sum_area rp%fuel%effective_moisture = (dp%fuel%effective_moisture(:)*dp%area + rp%fuel%effective_moisture(:)*rp%area) * inv_sum_area rp%livegrass = (dp%livegrass*dp%area + rp%livegrass*rp%area) * inv_sum_area - !rp%fuel%total_loading = (dp%fuel%total_loading*dp%area + rp%fuel%total_loading*rp%area) * inv_sum_area - !rp%fuel%frac_loading = (dp%fuel%frac_loading(:)*dp%area + rp%fuel%frac_loading(:)*rp%area) * inv_sum_area - !rp%fuel%loading = (dp%fuel%loading(:)*dp%area + rp%fuel%loading(:)*rp%area) * inv_sum_area - !rp%fuel%bulk_density = (dp%fuel%bulk_density*dp%area + rp%fuel%bulk_density*rp%area) * inv_sum_area - !rp%fuel%SAV = (dp%fuel%SAV*dp%area + rp%fuel%SAV*rp%area) * inv_sum_area - !rp%fuel%MEF = (dp%fuel%MEF*dp%area + rp%fuel%MEF*rp%area) * inv_sum_area rp%ros_front = (dp%ros_front*dp%area + rp%ros_front*rp%area) * inv_sum_area rp%tau_l = (dp%tau_l*dp%area + rp%tau_l*rp%area) * inv_sum_area rp%tfc_ros = (dp%tfc_ros*dp%area + rp%tfc_ros*rp%area) * inv_sum_area @@ -3299,8 +3293,7 @@ subroutine fuse_2_patches(csite, dp, rp) rp%fd = (dp%fd*dp%area + rp%fd*rp%area) * inv_sum_area rp%ros_back = (dp%ros_back*dp%area + rp%ros_back*rp%area) * inv_sum_area rp%scorch_ht(:) = (dp%scorch_ht(:)*dp%area + rp%scorch_ht(:)*rp%area) * inv_sum_area - !rp%frac_burnt = (dp%frac_burnt*dp%area + rp%frac_burnt*rp%area) * inv_sum_area - !rp%fuel%frac_burnt(:) = (dp%fuel%frac_burnt(:)*dp%area + rp%fuel%frac_burnt(:)*rp%area) * inv_sum_area + rp%frac_burnt = (dp%frac_burnt*dp%area + rp%frac_burnt*rp%area) * inv_sum_area rp%btran_ft(:) = (dp%btran_ft(:)*dp%area + rp%btran_ft(:)*rp%area) * inv_sum_area rp%zstar = (dp%zstar*dp%area + rp%zstar*rp%area) * inv_sum_area rp%c_stomata = (dp%c_stomata*dp%area + rp%c_stomata*rp%area) * inv_sum_area From 3136ebbe4202636494db5ab27fae935f1e29ddef Mon Sep 17 00:00:00 2001 From: adrifoster Date: Tue, 24 Sep 2024 15:20:02 -0600 Subject: [PATCH 038/111] fuel plotting --- testing/functional_testing/fire/FatesTestFireMod.F90 | 2 +- testing/functional_testing/fire/fuel_plotting.py | 4 ++-- testing/run_fates_tests.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/testing/functional_testing/fire/FatesTestFireMod.F90 b/testing/functional_testing/fire/FatesTestFireMod.F90 index feff87b951..d84df7f1be 100644 --- a/testing/functional_testing/fire/FatesTestFireMod.F90 +++ b/testing/functional_testing/fire/FatesTestFireMod.F90 @@ -253,7 +253,7 @@ subroutine WriteFireData(out_file, nsteps, nfuelmods, temp_degC, precip, rh, NI, ! write out data call WriteVar(ncid, timeID, time_index) - call WriteVar(ncid, litterID, (/1, 2, 3, 4, 5/)) + call WriteVar(ncid, litterID, (/1, 2, 3, 4, 5, 6/)) call WriteVar(ncid, modID, fuel_models(:)) call WriteVar(ncid, cID, carriers(:)) call WriteVar(ncid, tempID, temp_degC(:)) diff --git a/testing/functional_testing/fire/fuel_plotting.py b/testing/functional_testing/fire/fuel_plotting.py index 79546e9b7d..e3d061f64b 100644 --- a/testing/functional_testing/fire/fuel_plotting.py +++ b/testing/functional_testing/fire/fuel_plotting.py @@ -29,8 +29,8 @@ def plot_fuel_dat(run_dir, out_file, save_figs, plot_dir): def plot_barchart(fuel_dat, var, varname, units, save_figs, plot_dir, by_litter_type=True): - litter_classes = ['twigs', 'small branches', 'large branches', 'dead leaves', 'live grass'] - colors = ['darksalmon', 'peru', 'saddlebrown', 'moccasin', 'yellowgreen'] + litter_classes = ['twigs', 'small branches', 'large branches', 'trunks', 'dead leaves', 'live grass'] + colors = ['darksalmon', 'peru', 'saddlebrown', 'black', 'moccasin', 'yellowgreen'] fuel_models = [str(f) for f in fuel_dat.fuel_model.values] if by_litter_type: diff --git a/testing/run_fates_tests.py b/testing/run_fates_tests.py index 2e12f84d4b..4c97a7e0e1 100755 --- a/testing/run_fates_tests.py +++ b/testing/run_fates_tests.py @@ -89,7 +89,7 @@ "other_args": [], "plotting_function": None, }, - "fuel":{ + "fuelu":{ "test_dir": "fates_fire_fuel_utest", "test_exe": None, "out_file": None, From 6eed28052144d991d074d5e265878f99e012eae4 Mon Sep 17 00:00:00 2001 From: Adrianna Foster Date: Wed, 2 Oct 2024 14:21:13 -0600 Subject: [PATCH 039/111] updates for b4b testing --- biogeochem/EDPatchDynamicsMod.F90 | 1 - fire/FatesFuelMod.F90 | 32 +++++++++++++---------- fire/SFMainMod.F90 | 43 +++++++++++++++++++++---------- 3 files changed, 47 insertions(+), 29 deletions(-) diff --git a/biogeochem/EDPatchDynamicsMod.F90 b/biogeochem/EDPatchDynamicsMod.F90 index e27e9247a2..6c7a059c48 100644 --- a/biogeochem/EDPatchDynamicsMod.F90 +++ b/biogeochem/EDPatchDynamicsMod.F90 @@ -3388,7 +3388,6 @@ subroutine fuse_2_patches(csite, dp, rp) olderp%younger => null() end if - if(associated(olderp))then ! Update the older patch's new younger patch (becuase it isn't dp anymore) olderp%younger => youngerp diff --git a/fire/FatesFuelMod.F90 b/fire/FatesFuelMod.F90 index fde854a00e..a04bbf7a3f 100644 --- a/fire/FatesFuelMod.F90 +++ b/fire/FatesFuelMod.F90 @@ -162,24 +162,24 @@ subroutine CalculateFractionalLoading(this) ! sum up loading just in case call this%SumLoading() - if (this%total_loading > nearzero) then + if (this%total_loading > 0.0_r8) then do i = 1, nfsc if (i /= fuel_classes%trunks()) then this%frac_loading(i) = this%loading(i)/this%total_loading else - this%frac_loading(i) = 0.0_r8 + this%frac_loading(i) = 0.0000000001_r8 end if end do else - this%frac_loading(1:nfsc) = 0.0_r8 - this%total_loading = 0.0_r8 + this%frac_loading(1:nfsc) = 0.0000000001_r8 + this%total_loading = 0.0000000001_r8 end if end subroutine CalculateFractionalLoading !------------------------------------------------------------------------------------- - subroutine UpdateFuelMoisture(this, sav_fuel, drying_ratio, fireWeatherClass) + subroutine UpdateFuelMoisture(this, sav_fuel, drying_ratio, fireWeatherClass, MEF_trunks, moisture_trunks) ! DESCRIPTION: ! Updates fuel moisture depending on what fire weather class is in use @@ -188,12 +188,14 @@ subroutine UpdateFuelMoisture(this, sav_fuel, drying_ratio, fireWeatherClass) real(r8), intent(in) :: sav_fuel(nfsc) ! surface area to volume ratio of all fuel types [/cm] real(r8), intent(in) :: drying_ratio ! drying ratio class(fire_weather), intent(in) :: fireWeatherClass ! fireWeatherClass + real(r8), intent(out) :: MEF_trunks ! drying ratio + real(r8), intent(out) :: moisture_trunks ! drying ratio real(r8) :: moisture(nfsc) ! fuel moisture [m3/m3] real(r8) :: moisture_of_extinction(nfsc) ! fuel moisture of extinction [m3/m3] integer :: i ! looping index - if (this%total_loading > nearzero) then + if (this%total_loading + this%loading(fuel_classes%trunks()) > 0.0_r8) then ! calculate fuel moisture [m3/m3] for each fuel class depending on what ! fire weather class is in use select type (fireWeatherClass) @@ -219,13 +221,15 @@ subroutine UpdateFuelMoisture(this, sav_fuel, drying_ratio, fireWeatherClass) this%MEF = this%MEF + this%frac_loading(i)*moisture_of_extinction(i) end if end do + MEF_trunks = moisture_of_extinction(fuel_classes%trunks()) + moisture_trunks = moisture(fuel_classes%trunks()) else - this%effective_moisture(1:nfsc) = 0.0_r8 - this%average_moisture = 0.0_r8 - this%MEF = 0.0_r8 + this%effective_moisture(1:nfsc) = 0.0000000001_r8 + this%average_moisture = 0.0000000001_r8 + this%MEF = 0.0000000001_r8 end if - + end subroutine UpdateFuelMoisture !------------------------------------------------------------------------------------- @@ -296,7 +300,7 @@ real(r8) function MoistureOfExtinction(sav) real(r8), parameter :: MEF_a = 0.524_r8 real(r8), parameter :: MEF_b = 0.066_r8 - if (sav <= nearzero) then + if (sav <= 0.0_r8) then MoistureOfExtinction = 0.0_r8 else MoistureOfExtinction = MEF_a - MEF_b*log(sav) @@ -317,7 +321,7 @@ subroutine AverageBulkDensity(this, bulk_density) ! LOCALS: integer :: i ! looping index - if (this%total_loading > nearzero) then + if (this%total_loading > 0.0_r8) then this%bulk_density = 0.0_r8 do i = 1, nfsc ! average bulk density across all fuel types except trunks @@ -326,7 +330,7 @@ subroutine AverageBulkDensity(this, bulk_density) end if end do else - this%bulk_density = sum(bulk_density(1:nfsc))/nfsc + this%bulk_density = 0.0000000001_r8 !sum(bulk_density(1:nfsc))/nfsc end if end subroutine AverageBulkDensity @@ -344,7 +348,7 @@ subroutine AverageSAV(this, sav_fuel) ! LOCALS: integer :: i ! looping index - if (this%total_loading > nearzero) then + if (this%total_loading > 0.0_r8) then this%SAV = 0.0_r8 do i = 1, nfsc ! average bulk density across all fuel types except trunks diff --git a/fire/SFMainMod.F90 b/fire/SFMainMod.F90 index 505ec3e7a0..7b89d48dce 100644 --- a/fire/SFMainMod.F90 +++ b/fire/SFMainMod.F90 @@ -8,7 +8,7 @@ module SFMainMod use FatesConstantsMod, only : r8 => fates_r8 use FatesConstantsMod, only : itrue, ifalse use FatesConstantsMod, only : pi_const - use FatesConstantsMod, only : nocomp_bareground + use FatesConstantsMod, only : nocomp_bareground, nearzero use FatesGlobals, only : fates_log use FatesInterfaceTypesMod, only : hlm_masterproc use FatesInterfaceTypesMod, only : hlm_spitfire_mode @@ -74,8 +74,6 @@ subroutine fire_model(currentSite, bc_in) currentPatch => currentPatch%older end do - - if (hlm_spitfire_mode > hlm_sf_nofire_def) then call UpdateFireWeather(currentSite, bc_in) call UpdateFuelCharacteristics(currentSite) @@ -173,6 +171,7 @@ subroutine UpdateFuelCharacteristics(currentSite) ! LOCALS: type(fates_patch_type), pointer :: currentPatch ! FATES patch type(litter_type), pointer :: litter ! pointer to patch litter class + real(r8) :: MEF_trunks, fuel_moisture_trunks currentPatch => currentSite%oldest_patch do while(associated(currentPatch)) @@ -190,16 +189,32 @@ subroutine UpdateFuelCharacteristics(currentSite) ! sum up fuel classes and calculate fractional loading for each call currentPatch%fuel%SumLoading() - call currentPatch%fuel%CalculateFractionalLoading() - - ! calculate fuel moisture [m3/m3] - call currentPatch%fuel%UpdateFuelMoisture(SF_val_SAV, SF_val_drying_ratio, & - currentSite%fireWeather) - ! calculate geometric properties - call currentPatch%fuel%AverageBulkDensity(SF_val_FBD) - call currentPatch%fuel%AverageSAV(SF_val_SAV) - + if (currentPatch%fuel%total_loading + currentPatch%fuel%loading(fuel_classes%trunks()) > 0.0) then + call currentPatch%fuel%CalculateFractionalLoading() + + ! calculate fuel moisture [m3/m3] + call currentPatch%fuel%UpdateFuelMoisture(SF_val_SAV, SF_val_drying_ratio, & + currentSite%fireWeather, MEF_trunks, fuel_moisture_trunks) + + ! calculate geometric properties + call currentPatch%fuel%AverageBulkDensity(SF_val_FBD) + call currentPatch%fuel%AverageSAV(SF_val_SAV) + + if (currentPatch%fuel%total_loading <= 0.0 .and. currentPatch%fuel%loading(fuel_classes%trunks()) > nearzero) then + currentPatch%fuel%bulk_density = SF_val_FBD(fuel_classes%trunks()) + currentPatch%fuel%SAV = SF_val_SAV(fuel_classes%trunks()) + currentPatch%fuel%MEF = MEF_trunks + currentPatch%fuel%average_moisture = fuel_moisture_trunks + end if + else + currentPatch%fuel%SAV = sum(SF_val_SAV(1:nfsc))/(nfsc) + currentPatch%fuel%average_moisture = 0.0000000001_r8 + currentPatch%fuel%bulk_density = 0.0000000001_r8 + currentPatch%fuel%frac_loading(:) = 0.0000000001_r8 + currentPatch%fuel%MEF = 0.0000000001_r8 + currentPatch%fuel%total_loading = 0.0000000001_r8 + end if end if currentPatch => currentPatch%younger @@ -241,7 +256,7 @@ subroutine rate_of_spread (currentSite) do while(associated(currentPatch)) - if(currentPatch%nocomp_pft_label .ne. nocomp_bareground .and. currentPatch%fuel%total_loading > nearzero)then + if(currentPatch%nocomp_pft_label .ne. nocomp_bareground) then ! remove mineral content from net fuel load per Thonicke 2010 for ir calculation currentPatch%fuel%total_loading = currentPatch%fuel%total_loading * (1.0_r8 - SF_val_miner_total) !net of minerals @@ -350,7 +365,7 @@ subroutine rate_of_spread (currentSite) ! backward ROS from Can FBP System (1992) in m/min ! backward ROS wind not changed by vegetation currentPatch%ROS_back = currentPatch%ROS_front*exp(-0.012_r8*currentSite%wind) - + end if ! nocomp_pft_label check currentPatch => currentPatch%younger From 817491b9cb1003295e1e8b86d177fe5eeb013caf Mon Sep 17 00:00:00 2001 From: Adrianna Foster Date: Thu, 17 Oct 2024 10:30:43 -0600 Subject: [PATCH 040/111] update zero checking --- fire/FatesFuelMod.F90 | 22 +++++++++++----------- fire/SFMainMod.F90 | 37 ++++++++++--------------------------- 2 files changed, 21 insertions(+), 38 deletions(-) diff --git a/fire/FatesFuelMod.F90 b/fire/FatesFuelMod.F90 index a04bbf7a3f..46eb936407 100644 --- a/fire/FatesFuelMod.F90 +++ b/fire/FatesFuelMod.F90 @@ -162,17 +162,17 @@ subroutine CalculateFractionalLoading(this) ! sum up loading just in case call this%SumLoading() - if (this%total_loading > 0.0_r8) then + if (this%total_loading > nearzero) then do i = 1, nfsc if (i /= fuel_classes%trunks()) then this%frac_loading(i) = this%loading(i)/this%total_loading else - this%frac_loading(i) = 0.0000000001_r8 + this%frac_loading(i) = 0.0_r8 end if end do else - this%frac_loading(1:nfsc) = 0.0000000001_r8 - this%total_loading = 0.0000000001_r8 + this%frac_loading(1:nfsc) = 0.0_r8 + this%total_loading = 0.0_r8 end if end subroutine CalculateFractionalLoading @@ -195,7 +195,7 @@ subroutine UpdateFuelMoisture(this, sav_fuel, drying_ratio, fireWeatherClass, ME real(r8) :: moisture_of_extinction(nfsc) ! fuel moisture of extinction [m3/m3] integer :: i ! looping index - if (this%total_loading + this%loading(fuel_classes%trunks()) > 0.0_r8) then + if (this%total_loading + this%loading(fuel_classes%trunks()) > nearzero) then ! calculate fuel moisture [m3/m3] for each fuel class depending on what ! fire weather class is in use select type (fireWeatherClass) @@ -225,9 +225,9 @@ subroutine UpdateFuelMoisture(this, sav_fuel, drying_ratio, fireWeatherClass, ME moisture_trunks = moisture(fuel_classes%trunks()) else - this%effective_moisture(1:nfsc) = 0.0000000001_r8 - this%average_moisture = 0.0000000001_r8 - this%MEF = 0.0000000001_r8 + this%effective_moisture(1:nfsc) = 0.0_r8 + this%average_moisture = 0.0_r8 + this%MEF = 0.0_r8 end if end subroutine UpdateFuelMoisture @@ -321,7 +321,7 @@ subroutine AverageBulkDensity(this, bulk_density) ! LOCALS: integer :: i ! looping index - if (this%total_loading > 0.0_r8) then + if (this%total_loading > nearzero) then this%bulk_density = 0.0_r8 do i = 1, nfsc ! average bulk density across all fuel types except trunks @@ -330,7 +330,7 @@ subroutine AverageBulkDensity(this, bulk_density) end if end do else - this%bulk_density = 0.0000000001_r8 !sum(bulk_density(1:nfsc))/nfsc + this%bulk_density = sum(bulk_density(1:nfsc))/nfsc end if end subroutine AverageBulkDensity @@ -348,7 +348,7 @@ subroutine AverageSAV(this, sav_fuel) ! LOCALS: integer :: i ! looping index - if (this%total_loading > 0.0_r8) then + if (this%total_loading > nearzero) then this%SAV = 0.0_r8 do i = 1, nfsc ! average bulk density across all fuel types except trunks diff --git a/fire/SFMainMod.F90 b/fire/SFMainMod.F90 index 7b89d48dce..a7d9a49d7c 100644 --- a/fire/SFMainMod.F90 +++ b/fire/SFMainMod.F90 @@ -189,35 +189,18 @@ subroutine UpdateFuelCharacteristics(currentSite) ! sum up fuel classes and calculate fractional loading for each call currentPatch%fuel%SumLoading() - - if (currentPatch%fuel%total_loading + currentPatch%fuel%loading(fuel_classes%trunks()) > 0.0) then - call currentPatch%fuel%CalculateFractionalLoading() - - ! calculate fuel moisture [m3/m3] - call currentPatch%fuel%UpdateFuelMoisture(SF_val_SAV, SF_val_drying_ratio, & - currentSite%fireWeather, MEF_trunks, fuel_moisture_trunks) - - ! calculate geometric properties - call currentPatch%fuel%AverageBulkDensity(SF_val_FBD) - call currentPatch%fuel%AverageSAV(SF_val_SAV) + call currentPatch%fuel%CalculateFractionalLoading() - if (currentPatch%fuel%total_loading <= 0.0 .and. currentPatch%fuel%loading(fuel_classes%trunks()) > nearzero) then - currentPatch%fuel%bulk_density = SF_val_FBD(fuel_classes%trunks()) - currentPatch%fuel%SAV = SF_val_SAV(fuel_classes%trunks()) - currentPatch%fuel%MEF = MEF_trunks - currentPatch%fuel%average_moisture = fuel_moisture_trunks - end if - else - currentPatch%fuel%SAV = sum(SF_val_SAV(1:nfsc))/(nfsc) - currentPatch%fuel%average_moisture = 0.0000000001_r8 - currentPatch%fuel%bulk_density = 0.0000000001_r8 - currentPatch%fuel%frac_loading(:) = 0.0000000001_r8 - currentPatch%fuel%MEF = 0.0000000001_r8 - currentPatch%fuel%total_loading = 0.0000000001_r8 - end if + ! calculate fuel moisture [m3/m3] + call currentPatch%fuel%UpdateFuelMoisture(SF_val_SAV, SF_val_drying_ratio, & + currentSite%fireWeather, MEF_trunks, fuel_moisture_trunks) + + ! calculate geometric properties + call currentPatch%fuel%AverageBulkDensity(SF_val_FBD) + call currentPatch%fuel%AverageSAV(SF_val_SAV) + end if currentPatch => currentPatch%younger - end do end subroutine UpdateFuelCharacteristics @@ -256,7 +239,7 @@ subroutine rate_of_spread (currentSite) do while(associated(currentPatch)) - if(currentPatch%nocomp_pft_label .ne. nocomp_bareground) then + if(currentPatch%nocomp_pft_label .ne. nocomp_bareground .and. currentPatch%fuel%total_loading > nearzero) then ! remove mineral content from net fuel load per Thonicke 2010 for ir calculation currentPatch%fuel%total_loading = currentPatch%fuel%total_loading * (1.0_r8 - SF_val_miner_total) !net of minerals From 04d06d9a15a9506bed7922641af0d13645fe85cc Mon Sep 17 00:00:00 2001 From: Adrianna Foster Date: Thu, 17 Oct 2024 11:40:21 -0600 Subject: [PATCH 041/111] remove dbugging lines --- fire/FatesFuelMod.F90 | 8 ++------ fire/SFMainMod.F90 | 5 +---- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/fire/FatesFuelMod.F90 b/fire/FatesFuelMod.F90 index 46eb936407..2b82c56a9d 100644 --- a/fire/FatesFuelMod.F90 +++ b/fire/FatesFuelMod.F90 @@ -179,7 +179,7 @@ end subroutine CalculateFractionalLoading !------------------------------------------------------------------------------------- - subroutine UpdateFuelMoisture(this, sav_fuel, drying_ratio, fireWeatherClass, MEF_trunks, moisture_trunks) + subroutine UpdateFuelMoisture(this, sav_fuel, drying_ratio, fireWeatherClass) ! DESCRIPTION: ! Updates fuel moisture depending on what fire weather class is in use @@ -188,8 +188,6 @@ subroutine UpdateFuelMoisture(this, sav_fuel, drying_ratio, fireWeatherClass, ME real(r8), intent(in) :: sav_fuel(nfsc) ! surface area to volume ratio of all fuel types [/cm] real(r8), intent(in) :: drying_ratio ! drying ratio class(fire_weather), intent(in) :: fireWeatherClass ! fireWeatherClass - real(r8), intent(out) :: MEF_trunks ! drying ratio - real(r8), intent(out) :: moisture_trunks ! drying ratio real(r8) :: moisture(nfsc) ! fuel moisture [m3/m3] real(r8) :: moisture_of_extinction(nfsc) ! fuel moisture of extinction [m3/m3] @@ -221,9 +219,7 @@ subroutine UpdateFuelMoisture(this, sav_fuel, drying_ratio, fireWeatherClass, ME this%MEF = this%MEF + this%frac_loading(i)*moisture_of_extinction(i) end if end do - MEF_trunks = moisture_of_extinction(fuel_classes%trunks()) - moisture_trunks = moisture(fuel_classes%trunks()) - + else this%effective_moisture(1:nfsc) = 0.0_r8 this%average_moisture = 0.0_r8 diff --git a/fire/SFMainMod.F90 b/fire/SFMainMod.F90 index a7d9a49d7c..9e4f690c20 100644 --- a/fire/SFMainMod.F90 +++ b/fire/SFMainMod.F90 @@ -139,9 +139,6 @@ subroutine UpdateFireWeather(currentSite, bc_in) ! convert to m/min currentSite%wind = wind*sec_per_min - if (hlm_masterproc == itrue) then - write(fates_log(),*) 'wind_in', wind - end if ! update fire weather index call currentSite%fireWeather%UpdateIndex(temp_C, precip, rh, wind) @@ -193,7 +190,7 @@ subroutine UpdateFuelCharacteristics(currentSite) ! calculate fuel moisture [m3/m3] call currentPatch%fuel%UpdateFuelMoisture(SF_val_SAV, SF_val_drying_ratio, & - currentSite%fireWeather, MEF_trunks, fuel_moisture_trunks) + currentSite%fireWeather) ! calculate geometric properties call currentPatch%fuel%AverageBulkDensity(SF_val_FBD) From a001c7150ed7536af8eee2c6c4120c2cb9e9fc30 Mon Sep 17 00:00:00 2001 From: Xiulin Gao Date: Wed, 23 Oct 2024 10:28:52 -0700 Subject: [PATCH 042/111] add subroutine to calculate sapwood cross-sectional area for grass PFT --- biogeochem/FatesAllometryMod.F90 | 43 ++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/biogeochem/FatesAllometryMod.F90 b/biogeochem/FatesAllometryMod.F90 index 3cf1ab36a4..99a15de626 100644 --- a/biogeochem/FatesAllometryMod.F90 +++ b/biogeochem/FatesAllometryMod.F90 @@ -93,6 +93,7 @@ module FatesAllometryMod use FatesConstantsMod, only : fates_unset_r8 use FatesConstantsMod, only : itrue use FatesConstantsMod, only : nearzero + use FatesConstantsMod, only : pi_const use shr_log_mod , only : errMsg => shr_log_errMsg use FatesGlobals , only : fates_log use FatesGlobals , only : endrun => fates_endrun @@ -1045,8 +1046,10 @@ subroutine bsap_allom(d,ipft,crowndamage,canopy_trim,elongf_stem, sapw_area,bsap ! dead woody biomass. So bsap = bagw. Might remove the bsap and bdead for grass ! in the future as there is no need to distinguish the two for grass above- and belowground biomass + call sapwood_area_grass(d,sapw_area) call bagw_allom(d,ipft,crowndamage,elongf_stem,bagw,dbagwdd) call bbgw_allom(d,ipft, elongf_stem,bbgw,dbbgwdd) + bsap = bagw + bbgw ! This is a grass-only functionnal type, no need to run crown-damage effects @@ -1411,6 +1414,46 @@ subroutine bsap_ltarg_slatop(d,h,dhdd,bleaf,dbleafdd,ipft, & return end subroutine bsap_ltarg_slatop + + +! ============================================================================ + ! Area of sap wood cross-section specifically for grass PFT + ! ============================================================================ + + subroutine sapwood_area_grass(d,sapw_area) + !--------------------------------------------------------------------------- + ! This function calculates sapwood cross-sectional area specifically for grass + ! PFT using basal diameter (cm) of the entire plant as size reference, + ! assume basal area as the sum of cross-sectional area of each grass tiller + ! so that water transport through sapwood can be seen as a collective behavior + ! of all tillers + ! No reference. Might update this to more theoretical-based approach once there + ! is empirical evidence + !---------------- + ! Input arguments + !---------------- + ! d -- basal diameter [cm] + + !---------------- + ! Output variables + !---------------- + ! sapw_area -- sapwood cross-sectional area [m2] + + !---Arguments + real(r8), intent(in) :: d ! plant basal diameter [ cm] + real(r8), intent(out) :: sapw_area ! sapwood cross-sectional area [ m2] + + ! Calculate sapwood cross-sectional area assuming sapwood geometry as a + ! cylinder and basal diameter is the diameter of the cylinder + sapw_area = (pi_const * (d / (2.0_r8)**2.0_r8)) / cm2_per_m2 + + return + + end subroutine sapwood_area_grass + + + + ! ============================================================================ ! Specific storage relationships ! ============================================================================ From 862706823d734664c30f4dfc7287931f62906bbb Mon Sep 17 00:00:00 2001 From: Xiulin Gao Date: Wed, 23 Oct 2024 15:54:55 -0700 Subject: [PATCH 043/111] correct area calculation --- biogeochem/FatesAllometryMod.F90 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/biogeochem/FatesAllometryMod.F90 b/biogeochem/FatesAllometryMod.F90 index 99a15de626..7b18ef9498 100644 --- a/biogeochem/FatesAllometryMod.F90 +++ b/biogeochem/FatesAllometryMod.F90 @@ -1445,7 +1445,7 @@ subroutine sapwood_area_grass(d,sapw_area) ! Calculate sapwood cross-sectional area assuming sapwood geometry as a ! cylinder and basal diameter is the diameter of the cylinder - sapw_area = (pi_const * (d / (2.0_r8)**2.0_r8)) / cm2_per_m2 + sapw_area = (pi_const * ((d / 2.0_r8)**2.0_r8)) / cm2_per_m2 return From 0628cb76b1df8dc6a99a31799439de1a9a9609bf Mon Sep 17 00:00:00 2001 From: Xiulin Gao Date: Wed, 23 Oct 2024 15:58:08 -0700 Subject: [PATCH 044/111] update in-code documentation --- biogeochem/FatesAllometryMod.F90 | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/biogeochem/FatesAllometryMod.F90 b/biogeochem/FatesAllometryMod.F90 index 7b18ef9498..9951af1ded 100644 --- a/biogeochem/FatesAllometryMod.F90 +++ b/biogeochem/FatesAllometryMod.F90 @@ -1424,8 +1424,9 @@ subroutine sapwood_area_grass(d,sapw_area) !--------------------------------------------------------------------------- ! This function calculates sapwood cross-sectional area specifically for grass ! PFT using basal diameter (cm) of the entire plant as size reference, - ! assume basal area as the sum of cross-sectional area of each grass tiller - ! so that water transport through sapwood can be seen as a collective behavior + ! assume sapwood area of the entire plant as the sum of the cross-sectional area + ! of each grass tiller + ! such that water transport through sapwood can be seen as a collective behavior ! of all tillers ! No reference. Might update this to more theoretical-based approach once there ! is empirical evidence From 9080ba37506445d2bdaa11d68e0b43316cfbc75b Mon Sep 17 00:00:00 2001 From: Charlie Koven Date: Tue, 29 Oct 2024 11:34:17 -0700 Subject: [PATCH 045/111] Apply suggestions from code review Co-authored-by: Jessica Needham <10586303+JessicaNeedham@users.noreply.github.com> --- biogeophys/FatesPlantRespPhotosynthMod.F90 | 2 +- main/EDMainMod.F90 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/biogeophys/FatesPlantRespPhotosynthMod.F90 b/biogeophys/FatesPlantRespPhotosynthMod.F90 index e66b9138f9..eed9458993 100644 --- a/biogeophys/FatesPlantRespPhotosynthMod.F90 +++ b/biogeophys/FatesPlantRespPhotosynthMod.F90 @@ -734,7 +734,7 @@ subroutine FatesPlantRespPhotosynthDrive (nsites, sites,bc_in,bc_out,dtime) end do leaf_layer_loop ! Zero cohort flux accumulators. - currentCohort%npp_tstep = 0.0_r8 + currentCohort%resp_m_tstep = 0.0_r8 currentCohort%gpp_tstep = 0.0_r8 currentCohort%rdark = 0.0_r8 diff --git a/main/EDMainMod.F90 b/main/EDMainMod.F90 index 22cb94d992..807deb0496 100644 --- a/main/EDMainMod.F90 +++ b/main/EDMainMod.F90 @@ -520,7 +520,7 @@ subroutine ed_integrate_state_variables(currentSite, bc_in, bc_out ) ! at this point we have the info we need to calculate growth respiration ! as a "tax" on the difference between daily GPP and daily maintenance respiration - if (hlm_use_ed_prescribed_phys .eq. itrue) then + if (hlm_use_ed_prescribed_phys .eq. ifalse) then currentCohort%resp_g_acc = prt_params%grperc(ft) * & max(0._r8,(currentCohort%gpp_acc - currentCohort%resp_m_acc)) currentCohort%resp_g_acc_hold = currentCohort%resp_g_acc * real(hlm_days_per_year,r8) From b5f39560228a8de2caf203b7098c0a271c729b9c Mon Sep 17 00:00:00 2001 From: adrifoster Date: Tue, 29 Oct 2024 14:52:37 -0600 Subject: [PATCH 046/111] update nfsc variable name --- biogeochem/FatesPatchMod.F90 | 1 - fire/FatesFuelClassesMod.F90 | 4 +- fire/FatesFuelMod.F90 | 99 ++++++++++--------- fire/SFMainMod.F90 | 10 +- fire/SFParamsMod.F90 | 20 ++-- main/FatesHistoryInterfaceMod.F90 | 4 +- main/FatesInterfaceMod.F90 | 12 +-- main/FatesRestartInterfaceMod.F90 | 6 +- .../fire/FatesTestFireMod.F90 | 4 +- .../functional_testing/fire/FatesTestFuel.F90 | 6 +- .../fire_fuel_test/test_FireFuel.pf | 6 +- 11 files changed, 88 insertions(+), 84 deletions(-) diff --git a/biogeochem/FatesPatchMod.F90 b/biogeochem/FatesPatchMod.F90 index 2b8cba368c..52c2802520 100644 --- a/biogeochem/FatesPatchMod.F90 +++ b/biogeochem/FatesPatchMod.F90 @@ -13,7 +13,6 @@ module FatesPatchMod use FatesUtilsMod, only : check_var_real use FatesCohortMod, only : fates_cohort_type use FatesRunningMeanMod, only : rmean_type, rmean_arr_type - use FatesFuelClassesMod, only : nfsc use FatesLitterMod, only : litter_type use FatesFuelMod, only : fuel_type use PRTGenericMod, only : num_elements diff --git a/fire/FatesFuelClassesMod.F90 b/fire/FatesFuelClassesMod.F90 index 6678c3c5a2..257c822a9f 100644 --- a/fire/FatesFuelClassesMod.F90 +++ b/fire/FatesFuelClassesMod.F90 @@ -5,7 +5,7 @@ module FatesFuelClassesMod implicit none private - integer, parameter, public :: nfsc = 6 ! number of total fuel classes + integer, parameter, public :: num_fuel_classes = 6 ! number of total fuel classes type :: fuel_classes_type ! There are six fuel classes: @@ -60,4 +60,4 @@ integer function live_grass(this) live_grass = this%live_grass_i end function live_grass -end module FatesFuelClassesMod \ No newline at end of file +end module FatesFuelClassesMod diff --git a/fire/FatesFuelMod.F90 b/fire/FatesFuelMod.F90 index 2b82c56a9d..406e130bb0 100644 --- a/fire/FatesFuelMod.F90 +++ b/fire/FatesFuelMod.F90 @@ -1,6 +1,6 @@ module FatesFuelMod - use FatesFuelClassesMod, only : nfsc, fuel_classes + use FatesFuelClassesMod, only : num_fuel_classes, fuel_classes use FatesConstantsMod, only : r8 => fates_r8 use FatesConstantsMod, only : nearzero use SFNesterovMod, only : nesterov_index @@ -14,15 +14,15 @@ module FatesFuelMod type, public :: fuel_type - real(r8) :: loading(nfsc) ! fuel loading of each fuel class [kgC/m2] - real(r8) :: effective_moisture(nfsc) ! fuel effective moisture all fuel class (moisture/MEF) [m3/m3] - real(r8) :: frac_loading(nfsc) ! fractional loading of all fuel classes [0-1] - real(r8) :: frac_burnt(nfsc) ! fraction of litter burnt by fire [0-1] - real(r8) :: total_loading ! total fuel loading - DOES NOT INCLUDE TRUNKS [kgC/m2] - real(r8) :: average_moisture ! weighted average of fuel moisture across non-trunk fuel classes [m3/m3] - real(r8) :: bulk_density ! weighted average of bulk density across non-trunk fuel classes [kg/m3] - real(r8) :: SAV ! weighted average of surface area to volume ratio across non-trunk fuel classes [/cm] - real(r8) :: MEF ! weighted average of moisture of extinction across non-trunk fuel classes [m3/m3] + real(r8) :: loading(num_fuel_classes) ! fuel loading of each fuel class [kgC/m2] + real(r8) :: effective_moisture(num_fuel_classes) ! fuel effective moisture all fuel class (moisture/MEF) [m3/m3] + real(r8) :: frac_loading(num_fuel_classes) ! fractional loading of all fuel classes [0-1] + real(r8) :: frac_burnt(num_fuel_classes) ! fraction of litter burnt by fire [0-1] + real(r8) :: non_trunk_loading ! total fuel loading excluding trunks [kgC/m2] + real(r8) :: average_moisture ! weighted average of fuel moisture across non-trunk fuel classes [m3/m3] + real(r8) :: bulk_density ! weighted average of bulk density across non-trunk fuel classes [kg/m3] + real(r8) :: SAV ! weighted average of surface area to volume ratio across non-trunk fuel classes [/cm] + real(r8) :: MEF ! weighted average of moisture of extinction across non-trunk fuel classes [m3/m3] contains @@ -47,11 +47,11 @@ subroutine Init(this) class(fuel_type), intent(inout) :: this ! fuel class ! just zero everything - this%loading(1:nfsc) = 0.0_r8 - this%frac_loading(1:nfsc) = 0.0_r8 - this%frac_burnt(1:nfsc) = 0.0_r8 - this%effective_moisture(1:nfsc) = 0.0_r8 - this%total_loading = 0.0_r8 + this%loading(1:num_fuel_classes) = 0.0_r8 + this%frac_loading(1:num_fuel_classes) = 0.0_r8 + this%frac_burnt(1:num_fuel_classes) = 0.0_r8 + this%effective_moisture(1:num_fuel_classes) = 0.0_r8 + this%non_trunk_loading = 0.0_r8 this%average_moisture = 0.0_r8 this%bulk_density = 0.0_r8 this%SAV = 0.0_r8 @@ -79,7 +79,7 @@ subroutine Fuse(this, self_area, donor_area, donor_fuel) self_weight = self_area/(donor_area + self_area) donor_weight = 1.0_r8 - self_weight - do i = 1, nfsc + do i = 1, num_fuel_classes this%loading(i) = this%loading(i)*self_weight + & donor_fuel%loading(i)*donor_weight this%frac_loading(i) = this%frac_loading(i)*self_weight + & @@ -90,8 +90,8 @@ subroutine Fuse(this, self_area, donor_area, donor_fuel) donor_fuel%effective_moisture(i)*donor_weight end do - this%total_loading = this%total_loading*self_weight + & - donor_fuel%total_loading*donor_weight + this%non_trunk_loading = this%non_trunk_loading*self_weight + & + donor_fuel%non_trunk_loading*donor_weight this%average_moisture = this%average_moisture*self_weight + & donor_fuel%average_moisture*donor_weight this%bulk_density = this%bulk_density*self_weight + & @@ -130,7 +130,12 @@ end subroutine CalculateLoading subroutine SumLoading(this) ! DESCRIPTION: - ! Sums up the loading + ! Sums up the loading - excludes trunks + ! + ! Only the 1-h, 10-h and 100-h fuel classes influence fire spread + ! Rothermel, 1972 (USDA FS GTR INT-115) + ! Wilson, 1982 (UTINT-289) + ! Pyne et al., 1996 (Introduction to wildland fire) ! ARGUMENTS: class(fuel_type), intent(inout) :: this ! fuel class @@ -138,10 +143,10 @@ subroutine SumLoading(this) ! LOCALS: integer :: i ! looping index - this%total_loading = 0.0_r8 - do i = 1, nfsc + this%non_trunk_loading = 0.0_r8 + do i = 1, num_fuel_classes if (i /= fuel_classes%trunks()) then - this%total_loading = this%total_loading + this%loading(i) + this%non_trunk_loading = this%non_trunk_loading + this%loading(i) end if end do @@ -163,7 +168,7 @@ subroutine CalculateFractionalLoading(this) call this%SumLoading() if (this%total_loading > nearzero) then - do i = 1, nfsc + do i = 1, num_fuel_classes if (i /= fuel_classes%trunks()) then this%frac_loading(i) = this%loading(i)/this%total_loading else @@ -171,7 +176,7 @@ subroutine CalculateFractionalLoading(this) end if end do else - this%frac_loading(1:nfsc) = 0.0_r8 + this%frac_loading(1:num_fuel_classes) = 0.0_r8 this%total_loading = 0.0_r8 end if @@ -184,14 +189,14 @@ subroutine UpdateFuelMoisture(this, sav_fuel, drying_ratio, fireWeatherClass) ! Updates fuel moisture depending on what fire weather class is in use ! ARGUMENTS: - class(fuel_type), intent(inout) :: this ! fuel class - real(r8), intent(in) :: sav_fuel(nfsc) ! surface area to volume ratio of all fuel types [/cm] - real(r8), intent(in) :: drying_ratio ! drying ratio - class(fire_weather), intent(in) :: fireWeatherClass ! fireWeatherClass + class(fuel_type), intent(inout) :: this ! fuel class + real(r8), intent(in) :: sav_fuel(num_fuel_classes) ! surface area to volume ratio of all fuel types [/cm] + real(r8), intent(in) :: drying_ratio ! drying ratio + class(fire_weather), intent(in) :: fireWeatherClass ! fireWeatherClass - real(r8) :: moisture(nfsc) ! fuel moisture [m3/m3] - real(r8) :: moisture_of_extinction(nfsc) ! fuel moisture of extinction [m3/m3] - integer :: i ! looping index + real(r8) :: moisture(num_fuel_classes) ! fuel moisture [m3/m3] + real(r8) :: moisture_of_extinction(num_fuel_classes) ! fuel moisture of extinction [m3/m3] + integer :: i ! looping index if (this%total_loading + this%loading(fuel_classes%trunks()) > nearzero) then ! calculate fuel moisture [m3/m3] for each fuel class depending on what @@ -208,7 +213,7 @@ subroutine UpdateFuelMoisture(this, sav_fuel, drying_ratio, fireWeatherClass) this%average_moisture = 0.0_r8 this%MEF = 0.0_r8 - do i = 1, nfsc + do i = 1, num_fuel_classes ! calculate moisture of extinction and fuel effective moisture moisture_of_extinction(i) = MoistureOfExtinction(sav_fuel(i)) this%effective_moisture(i) = moisture(i)/moisture_of_extinction(i) @@ -221,7 +226,7 @@ subroutine UpdateFuelMoisture(this, sav_fuel, drying_ratio, fireWeatherClass) end do else - this%effective_moisture(1:nfsc) = 0.0_r8 + this%effective_moisture(1:num_fuel_classes) = 0.0_r8 this%average_moisture = 0.0_r8 this%MEF = 0.0_r8 end if @@ -236,16 +241,16 @@ subroutine CalculateFuelMoistureNesterov(sav_fuel, drying_ratio, NI, moisture) ! Updates fuel moisture ! ARGUMENTS: - real(r8), intent(in) :: sav_fuel(nfsc) ! surface area to volume ratio of all fuel types [/cm] - real(r8), intent(in) :: drying_ratio ! drying ratio - real(r8), intent(in) :: NI ! Nesterov Index - real(r8), intent(out) :: moisture(nfsc) ! moisture of litter [m3/m3] + real(r8), intent(in) :: sav_fuel(num_fuel_classes) ! surface area to volume ratio of all fuel types [/cm] + real(r8), intent(in) :: drying_ratio ! drying ratio + real(r8), intent(in) :: NI ! Nesterov Index + real(r8), intent(out) :: moisture(num_fuel_classes) ! moisture of litter [m3/m3] ! LOCALS integer :: i ! looping index real(r8) :: alpha_FMC ! intermediate variable for calculating fuel moisture - do i = 1, nfsc + do i = 1, num_fuel_classes if (i == fuel_classes%live_grass()) then ! live grass moisture is a function of SAV and changes via Nesterov Index ! along the same relationship as the 1 hour fuels @@ -311,22 +316,22 @@ subroutine AverageBulkDensity(this, bulk_density) ! Calculates average bulk density (not including trunks) ! ARGUMENTS: - class(fuel_type), intent(inout) :: this ! fuel class - real(r8), intent(in) :: bulk_density(nfsc) ! bulk density of all fuel types [kg/m2] + class(fuel_type), intent(inout) :: this ! fuel class + real(r8), intent(in) :: bulk_density(num_fuel_classes) ! bulk density of all fuel types [kg/m2] ! LOCALS: integer :: i ! looping index if (this%total_loading > nearzero) then this%bulk_density = 0.0_r8 - do i = 1, nfsc + do i = 1, num_fuel_classes ! average bulk density across all fuel types except trunks if (i /= fuel_classes%trunks()) then this%bulk_density = this%bulk_density + this%frac_loading(i)*bulk_density(i) end if end do else - this%bulk_density = sum(bulk_density(1:nfsc))/nfsc + this%bulk_density = sum(bulk_density(1:num_fuel_classes))/num_fuel_classes end if end subroutine AverageBulkDensity @@ -338,26 +343,26 @@ subroutine AverageSAV(this, sav_fuel) ! Calculates average surface area to volume ratio (not including trunks) ! ARGUMENTS: - class(fuel_type), intent(inout) :: this ! fuel class - real(r8), intent(in) :: sav_fuel(nfsc) ! surface area to volume ratio of all fuel types [/cm] + class(fuel_type), intent(inout) :: this ! fuel class + real(r8), intent(in) :: sav_fuel(num_fuel_classes) ! surface area to volume ratio of all fuel types [/cm] ! LOCALS: integer :: i ! looping index if (this%total_loading > nearzero) then this%SAV = 0.0_r8 - do i = 1, nfsc + do i = 1, num_fuel_classes ! average bulk density across all fuel types except trunks if (i /= fuel_classes%trunks()) then this%SAV = this%SAV + this%frac_loading(i)*sav_fuel(i) end if end do else - this%SAV = sum(sav_fuel(1:nfsc))/nfsc + this%SAV = sum(sav_fuel(1:num_fuel_classes))/num_fuel_classes end if end subroutine AverageSAV !--------------------------------------------------------------------------------------- -end module FatesFuelMod \ No newline at end of file +end module FatesFuelMod diff --git a/fire/SFMainMod.F90 b/fire/SFMainMod.F90 index 9e4f690c20..fac83574e8 100644 --- a/fire/SFMainMod.F90 +++ b/fire/SFMainMod.F90 @@ -25,7 +25,7 @@ module SFMainMod use FatesCohortMod, only : fates_cohort_type use EDtypesMod, only : AREA use FatesLitterMod, only : litter_type - use FatesFuelClassesMod, only : nfsc + use FatesFuelClassesMod, only : num_fuel_classes use PRTGenericMod, only : leaf_organ use PRTGenericMod, only : carbon12_element use PRTGenericMod, only : sapw_organ @@ -366,8 +366,8 @@ subroutine ground_fuel_consumption ( currentSite ) type(litter_type), pointer :: litt_c ! carbon 12 litter pool real(r8) :: moist !effective fuel moisture - real(r8) :: tau_b(nfsc) !lethal heating rates for each fuel class (min) - real(r8) :: fc_ground(nfsc) !total amount of fuel consumed per area of burned ground (kg C / m2 of burned area) + real(r8) :: tau_b(num_fuel_classes) !lethal heating rates for each fuel class (min) + real(r8) :: fc_ground(num_fuel_classes) !total amount of fuel consumed per area of burned ground (kg C / m2 of burned area) integer :: tr_sf, tw_sf, dl_sf, lg_sf integer :: c @@ -385,7 +385,7 @@ subroutine ground_fuel_consumption ( currentSite ) currentPatch%fuel%frac_burnt(:) = 1.0_r8 ! Calculate fraction of litter is burnt for all classes. ! Equation B1 in Thonicke et al. 2010--- - do c = 1, nfsc !work out the burnt fraction for all pools, even if those pools dont exist. + do c = 1, num_fuel_classes !work out the burnt fraction for all pools, even if those pools dont exist. moist = currentPatch%fuel%effective_moisture(c) ! 1. Very dry litter if (moist <= SF_val_min_moisture(c)) then @@ -431,7 +431,7 @@ subroutine ground_fuel_consumption ( currentSite ) ! taul is the duration of the lethal heating. ! The /10 is to convert from kgC/m2 into gC/cm2, as in the Peterson and Ryan paper #Rosie,Jun 2013 - do c = 1,nfsc + do c = 1,num_fuel_classes tau_b(c) = 39.4_r8 *(currentPatch%fuel%frac_loading(c)*currentPatch%fuel%total_loading/0.45_r8/10._r8)* & (1.0_r8-((1.0_r8-currentPatch%fuel%frac_burnt(c))**0.5_r8)) enddo diff --git a/fire/SFParamsMod.F90 b/fire/SFParamsMod.F90 index c51b14cdb8..d688f1976c 100644 --- a/fire/SFParamsMod.F90 +++ b/fire/SFParamsMod.F90 @@ -4,7 +4,7 @@ module SFParamsMod ! use FatesConstantsMod, only : r8 => fates_r8 use FatesConstantsMod, only : fates_check_param_set - use FatesFuelClassesMod, only : nfsc + use FatesFuelClassesMod, only : num_fuel_classes use FatesLitterMod, only : ncwd use FatesParametersInterface, only : param_string_length use FatesGlobals, only : fates_log @@ -28,15 +28,15 @@ module SFParamsMod real(r8),protected, public :: SF_val_drying_ratio real(r8),protected, public :: SF_val_fire_threshold ! threshold for fires that spread or go out. kW/m (Pyne 1996) real(r8),protected, public :: SF_val_CWD_frac(ncwd) - real(r8),protected, public :: SF_val_max_decomp(NFSC) - real(r8),protected, public :: SF_val_SAV(NFSC) - real(r8),protected, public :: SF_val_FBD(NFSC) - real(r8),protected, public :: SF_val_min_moisture(NFSC) + real(r8),protected, public :: SF_val_max_decomp(num_fuel_classes) + real(r8),protected, public :: SF_val_SAV(num_fuel_classes) + real(r8),protected, public :: SF_val_FBD(num_fuel_classes) + real(r8),protected, public :: SF_val_min_moisture(num_fuel_classes) real(r8),protected, public :: SF_val_mid_moisture(NFSC) - real(r8),protected, public :: SF_val_low_moisture_Coeff(NFSC) - real(r8),protected, public :: SF_val_low_moisture_Slope(NFSC) - real(r8),protected, public :: SF_val_mid_moisture_Coeff(NFSC) - real(r8),protected, public :: SF_val_mid_moisture_Slope(NFSC) + real(r8),protected, public :: SF_val_low_moisture_Coeff(num_fuel_classes) + real(r8),protected, public :: SF_val_low_moisture_Slope(num_fuel_classes) + real(r8),protected, public :: SF_val_mid_moisture_Coeff(num_fuel_classes) + real(r8),protected, public :: SF_val_mid_moisture_Slope(num_fuel_classes) character(len=param_string_length),parameter :: SF_name_fdi_alpha = "fates_fire_fdi_alpha" character(len=param_string_length),parameter :: SF_name_miner_total = "fates_fire_miner_total" @@ -92,7 +92,7 @@ subroutine SpitFireCheckParams(is_master) if(.not.is_master) return ! Move these checks to initialization - do c = 1,nfsc + do c = 1,num_fuel_classes if ( SF_val_max_decomp(c) < 0._r8) then write(fates_log(),*) 'Decomposition rates should be >0' write(fates_log(),*) 'c = ',c,' SF_val_max_decomp(c) = ',SF_val_max_decomp(c) diff --git a/main/FatesHistoryInterfaceMod.F90 b/main/FatesHistoryInterfaceMod.F90 index 3a8abb92d1..928c98d533 100644 --- a/main/FatesHistoryInterfaceMod.F90 +++ b/main/FatesHistoryInterfaceMod.F90 @@ -121,7 +121,7 @@ module FatesHistoryInterfaceMod use FatesSizeAgeTypeIndicesMod, only : get_layersizetype_class_index use FatesSizeAgeTypeIndicesMod, only : get_age_class_index - use FatesFuelClassesMod , only : nfsc + use FatesFuelClassesMod , only : num_fuel_classes use FatesLitterMod , only : ncwd use FatesConstantsMod , only : ican_upper use FatesConstantsMod , only : ican_ustory @@ -4255,7 +4255,7 @@ subroutine update_history_dyn2(this,nc,nsites,sites,bc_in) hio_fragmentation_scaler_sl(io_si,ilyr) = hio_fragmentation_scaler_sl(io_si,ilyr) + cpatch%fragmentation_scaler(ilyr) * cpatch%area * AREA_INV end do - do i_fuel = 1, nfsc + do i_fuel = 1, num_fuel_classes i_agefuel = get_agefuel_class_index(cpatch%age,i_fuel) hio_fuel_amount_age_fuel(io_si,i_agefuel) = hio_fuel_amount_age_fuel(io_si,i_agefuel) + & diff --git a/main/FatesInterfaceMod.F90 b/main/FatesInterfaceMod.F90 index 0120173759..e77b3e34bc 100644 --- a/main/FatesInterfaceMod.F90 +++ b/main/FatesInterfaceMod.F90 @@ -1122,7 +1122,7 @@ end subroutine InitPARTEHGlobals subroutine fates_history_maps - use FatesFuelClassesMod, only : nfsc + use FatesFuelClassesMod, only : num_fuel_classes use EDParamsMod, only : nclmax use EDParamsMod, only : nlevleaf use EDParamsMod, only : ED_val_history_sizeclass_bin_edges @@ -1157,7 +1157,7 @@ subroutine fates_history_maps allocate( fates_hdim_scmap_levscpf(1:nlevsclass*numpft)) allocate( fates_hdim_levpft(1:numpft )) allocate( fates_hdim_levlanduse(1:n_landuse_cats)) - allocate( fates_hdim_levfuel(1:NFSC )) + allocate( fates_hdim_levfuel(1:num_fuel_classes )) allocate( fates_hdim_levcwdsc(1:NCWD )) allocate( fates_hdim_levage(1:nlevage )) allocate( fates_hdim_levheight(1:nlevheight )) @@ -1180,8 +1180,8 @@ subroutine fates_history_maps allocate( fates_hdim_pftmap_levscagpft(nlevsclass * nlevage * numpft)) allocate( fates_hdim_agmap_levagepft(nlevage * numpft)) allocate( fates_hdim_pftmap_levagepft(nlevage * numpft)) - allocate( fates_hdim_agmap_levagefuel(nlevage * nfsc)) - allocate( fates_hdim_fscmap_levagefuel(nlevage * nfsc)) + allocate( fates_hdim_agmap_levagefuel(nlevage * num_fuel_classes)) + allocate( fates_hdim_fscmap_levagefuel(nlevage * num_fuel_classes)) allocate( fates_hdim_elmap_levelpft(num_elements*numpft)) allocate( fates_hdim_elmap_levelcwd(num_elements*ncwd)) @@ -1211,7 +1211,7 @@ subroutine fates_history_maps end do ! make fuel array - do ifuel=1,NFSC + do ifuel=1,num_fuel_classes fates_hdim_levfuel(ifuel) = ifuel end do @@ -1356,7 +1356,7 @@ subroutine fates_history_maps i=0 do iage=1,nlevage - do ifuel=1,NFSC + do ifuel=1,num_fuel_classes i=i+1 fates_hdim_agmap_levagefuel(i) = iage fates_hdim_fscmap_levagefuel(i) = ifuel diff --git a/main/FatesRestartInterfaceMod.F90 b/main/FatesRestartInterfaceMod.F90 index 7d5a0a54c5..217b10e4fd 100644 --- a/main/FatesRestartInterfaceMod.F90 +++ b/main/FatesRestartInterfaceMod.F90 @@ -41,7 +41,7 @@ module FatesRestartInterfaceMod use FatesInterfaceTypesMod, only : nlevdamage use FatesLitterMod, only : litter_type use FatesLitterMod, only : ncwd - use FatesFuelClassesMod, only : nfsc + use FatesFuelClassesMod, only : num_fuel_classes use FatesLitterMod, only : ndcmpy use EDTypesMod, only : area use EDParamsMod, only : nlevleaf @@ -2540,7 +2540,7 @@ subroutine set_restart_vectors(this,nc,nsites,sites) end do io_idx_pa_cwd = io_idx_co_1st - do i = 1,nfsc + do i = 1,num_fuel_classes this%rvars(ir_litter_moisture_pa_nfsc)%r81d(io_idx_pa_cwd) = cpatch%fuel%effective_moisture(i) io_idx_pa_cwd = io_idx_pa_cwd + 1 end do @@ -3509,7 +3509,7 @@ subroutine get_restart_vectors(this, nc, nsites, sites) end do io_idx_pa_cwd = io_idx_co_1st - do i = 1,nfsc + do i = 1,num_fuel_classes cpatch%fuel%effective_moisture(i) = this%rvars(ir_litter_moisture_pa_nfsc)%r81d(io_idx_pa_cwd) io_idx_pa_cwd = io_idx_pa_cwd + 1 end do diff --git a/testing/functional_testing/fire/FatesTestFireMod.F90 b/testing/functional_testing/fire/FatesTestFireMod.F90 index d84df7f1be..2dfb62d5db 100644 --- a/testing/functional_testing/fire/FatesTestFireMod.F90 +++ b/testing/functional_testing/fire/FatesTestFireMod.F90 @@ -11,7 +11,7 @@ module FatesTestFireMod use FatesUnitTestIOMod, only : OpenNCFile, GetVar, CloseNCFile, RegisterNCDims use FatesUnitTestIOMod, only : RegisterVar, EndNCDef, WriteVar use FatesUnitTestIOMod, only : type_double, type_int, type_char - use FatesFuelClassesMod, only : nfsc + use FatesFuelClassesMod, only : num_fuel_classes use SyntheticFuelModels, only : fuel_models_array_class use SFParamsMod, only : SF_val_CWD_frac use FatesFuelMod, only : fuel_type @@ -159,7 +159,7 @@ subroutine WriteFireData(out_file, nsteps, nfuelmods, temp_degC, precip, rh, NI, call OpenNCFile(trim(out_file), ncid, 'readwrite') ! register dimensions - call RegisterNCDims(ncid, dim_names, (/nsteps, nfsc, nfuelmods/), 3, dimIDs) + call RegisterNCDims(ncid, dim_names, (/nsteps, num_fuel_classes, nfuelmods/), 3, dimIDs) ! first register dimension variables diff --git a/testing/functional_testing/fire/FatesTestFuel.F90 b/testing/functional_testing/fire/FatesTestFuel.F90 index fd59783ecf..9f811dae06 100644 --- a/testing/functional_testing/fire/FatesTestFuel.F90 +++ b/testing/functional_testing/fire/FatesTestFuel.F90 @@ -9,7 +9,7 @@ program FatesTestFuel use SFFireWeatherMod, only : fire_weather use SFNesterovMod, only : nesterov_index use FatesFuelMod, only : fuel_type - use FatesFuelClassesMod, only : nfsc + use FatesFuelClassesMod, only : num_fuel_classes use SFParamsMod, only : SF_val_SAV, SF_val_drying_ratio use SFParamsMod, only : SF_val_FBD @@ -55,8 +55,8 @@ program FatesTestFuel allocate(wind(n_days)) allocate(NI(n_days)) allocate(fuel_moisture(n_days, num_fuel_models)) - allocate(fuel_loading(nfsc, num_fuel_models)) - allocate(frac_loading(nfsc, num_fuel_models)) + allocate(fuel_loading(num_fuel_classes, num_fuel_models)) + allocate(frac_loading(num_fuel_classes, num_fuel_models)) allocate(fuel_BD(num_fuel_models)) allocate(fuel_SAV(num_fuel_models)) allocate(total_loading(num_fuel_models)) diff --git a/testing/unit_testing/fire_fuel_test/test_FireFuel.pf b/testing/unit_testing/fire_fuel_test/test_FireFuel.pf index 5271283e52..d1d8de4475 100644 --- a/testing/unit_testing/fire_fuel_test/test_FireFuel.pf +++ b/testing/unit_testing/fire_fuel_test/test_FireFuel.pf @@ -5,7 +5,7 @@ module test_FireFuel ! use FatesConstantsMod, only : r8 => fates_r8 use FatesFuelMod, only : fuel_type - use FatesFuelClassesMod, only : fuel_classes, nfsc + use FatesFuelClassesMod, only : fuel_classes, num_fuel_classes use funit implicit none @@ -78,7 +78,7 @@ module test_FireFuel real(r8) :: frac_loading ! what the fractional loading should be [0-1] integer :: i ! looping index - total_loading = dummy_litter*float(nfsc - 1) + total_loading = dummy_litter*float(num_fuel_classes - 1) frac_loading = dummy_litter/total_loading call this%fuel%CalculateLoading(dummy_litter, dummy_litter, dummy_litter, & @@ -87,7 +87,7 @@ module test_FireFuel call this%fuel%SumLoading() call this%fuel%CalculateFractionalLoading() - do i = 1, nfsc + do i = 1, num_fuel_classes if (i /= fuel_classes%trunks()) then @assertEqual(this%fuel%frac_loading(i), frac_loading, tolerance=tol) else From 195dce4336af0dc38fadee26f0ec8c5135ded387 Mon Sep 17 00:00:00 2001 From: adrifoster Date: Tue, 29 Oct 2024 14:56:45 -0600 Subject: [PATCH 047/111] change total_loading to non_trunk_loading --- fire/FatesFuelMod.F90 | 12 ++--- fire/SFMainMod.F90 | 10 ++-- main/FatesHistoryInterfaceMod.F90 | 8 +-- .../fire/FatesTestFireMod.F90 | 10 ++-- .../functional_testing/fire/FatesTestFuel.F90 | 50 +++++++++---------- .../fire_fuel_test/test_FireFuel.pf | 12 ++--- 6 files changed, 51 insertions(+), 51 deletions(-) diff --git a/fire/FatesFuelMod.F90 b/fire/FatesFuelMod.F90 index 406e130bb0..90b71bab9b 100644 --- a/fire/FatesFuelMod.F90 +++ b/fire/FatesFuelMod.F90 @@ -167,17 +167,17 @@ subroutine CalculateFractionalLoading(this) ! sum up loading just in case call this%SumLoading() - if (this%total_loading > nearzero) then + if (this%non_trunk_loading > nearzero) then do i = 1, num_fuel_classes if (i /= fuel_classes%trunks()) then - this%frac_loading(i) = this%loading(i)/this%total_loading + this%frac_loading(i) = this%loading(i)/this%non_trunk_loading else this%frac_loading(i) = 0.0_r8 end if end do else this%frac_loading(1:num_fuel_classes) = 0.0_r8 - this%total_loading = 0.0_r8 + this%non_trunk_loading = 0.0_r8 end if end subroutine CalculateFractionalLoading @@ -198,7 +198,7 @@ subroutine UpdateFuelMoisture(this, sav_fuel, drying_ratio, fireWeatherClass) real(r8) :: moisture_of_extinction(num_fuel_classes) ! fuel moisture of extinction [m3/m3] integer :: i ! looping index - if (this%total_loading + this%loading(fuel_classes%trunks()) > nearzero) then + if (this%non_trunk_loading + this%loading(fuel_classes%trunks()) > nearzero) then ! calculate fuel moisture [m3/m3] for each fuel class depending on what ! fire weather class is in use select type (fireWeatherClass) @@ -322,7 +322,7 @@ subroutine AverageBulkDensity(this, bulk_density) ! LOCALS: integer :: i ! looping index - if (this%total_loading > nearzero) then + if (this%non_trunk_loading > nearzero) then this%bulk_density = 0.0_r8 do i = 1, num_fuel_classes ! average bulk density across all fuel types except trunks @@ -349,7 +349,7 @@ subroutine AverageSAV(this, sav_fuel) ! LOCALS: integer :: i ! looping index - if (this%total_loading > nearzero) then + if (this%non_trunk_loading > nearzero) then this%SAV = 0.0_r8 do i = 1, num_fuel_classes ! average bulk density across all fuel types except trunks diff --git a/fire/SFMainMod.F90 b/fire/SFMainMod.F90 index fac83574e8..c08164c92f 100644 --- a/fire/SFMainMod.F90 +++ b/fire/SFMainMod.F90 @@ -236,10 +236,10 @@ subroutine rate_of_spread (currentSite) do while(associated(currentPatch)) - if(currentPatch%nocomp_pft_label .ne. nocomp_bareground .and. currentPatch%fuel%total_loading > nearzero) then + if(currentPatch%nocomp_pft_label .ne. nocomp_bareground .and. currentPatch%fuel%non_trunk_loading > nearzero) then ! remove mineral content from net fuel load per Thonicke 2010 for ir calculation - currentPatch%fuel%total_loading = currentPatch%fuel%total_loading * (1.0_r8 - SF_val_miner_total) !net of minerals + currentPatch%fuel%non_trunk_loading = currentPatch%fuel%non_trunk_loading * (1.0_r8 - SF_val_miner_total) !net of minerals ! ----start spreading--- @@ -330,8 +330,8 @@ subroutine rate_of_spread (currentSite) (3.52_r8*(mw_weight**3.0_r8)))) ! ir = reaction intenisty in kJ/m2/min - ! currentPatch%fuel%total_loading converted from kgC/m2 to kgBiomass/m2 for ir calculation - ir = reaction_v_opt*(currentPatch%fuel%total_loading/0.45_r8)*SF_val_fuel_energy*moist_damp*SF_val_miner_damp + ! currentPatch%fuel%non_trunk_loading converted from kgC/m2 to kgBiomass/m2 for ir calculation + ir = reaction_v_opt*(currentPatch%fuel%non_trunk_loading/0.45_r8)*SF_val_fuel_energy*moist_damp*SF_val_miner_damp ! write(fates_log(),*) 'ir',gamma_aptr,moist_damp,SF_val_fuel_energy,SF_val_miner_damp @@ -432,7 +432,7 @@ subroutine ground_fuel_consumption ( currentSite ) ! The /10 is to convert from kgC/m2 into gC/cm2, as in the Peterson and Ryan paper #Rosie,Jun 2013 do c = 1,num_fuel_classes - tau_b(c) = 39.4_r8 *(currentPatch%fuel%frac_loading(c)*currentPatch%fuel%total_loading/0.45_r8/10._r8)* & + tau_b(c) = 39.4_r8 *(currentPatch%fuel%frac_loading(c)*currentPatch%fuel%non_trunk_loading/0.45_r8/10._r8)* & (1.0_r8-((1.0_r8-currentPatch%fuel%frac_burnt(c))**0.5_r8)) enddo tau_b(tr_sf) = 0.0_r8 diff --git a/main/FatesHistoryInterfaceMod.F90 b/main/FatesHistoryInterfaceMod.F90 index 928c98d533..6cc016111c 100644 --- a/main/FatesHistoryInterfaceMod.F90 +++ b/main/FatesHistoryInterfaceMod.F90 @@ -2679,7 +2679,7 @@ subroutine update_history_dyn1(this,nc,nsites,sites,bc_in) hio_fire_fuel_eff_moist_si(io_si) = hio_fire_fuel_eff_moist_si(io_si) + cpatch%fuel%average_moisture * cpatch%area * AREA_INV hio_fire_fuel_sav_si(io_si) = hio_fire_fuel_sav_si(io_si) + cpatch%fuel%SAV * cpatch%area * AREA_INV / m_per_cm hio_fire_fuel_mef_si(io_si) = hio_fire_fuel_mef_si(io_si) + cpatch%fuel%MEF * cpatch%area * AREA_INV - hio_sum_fuel_si(io_si) = hio_sum_fuel_si(io_si) + cpatch%fuel%total_loading * cpatch%area * AREA_INV + hio_sum_fuel_si(io_si) = hio_sum_fuel_si(io_si) + cpatch%fuel%non_trunk_loading * cpatch%area * AREA_INV hio_fire_intensity_area_product_si(io_si) = hio_fire_intensity_area_product_si(io_si) + & cpatch%FI * cpatch%frac_burnt * cpatch%area * AREA_INV * J_per_kJ @@ -3475,7 +3475,7 @@ subroutine update_history_dyn2(this,nc,nsites,sites,bc_in) ! Fuel sum [kg/m2] hio_fire_sum_fuel_si_age(io_si, cpatch%age_class) = hio_fire_sum_fuel_si_age(io_si, cpatch%age_class) + & - cpatch%fuel%total_loading * cpatch%area * AREA_INV + cpatch%fuel%non_trunk_loading * cpatch%area * AREA_INV @@ -4259,13 +4259,13 @@ subroutine update_history_dyn2(this,nc,nsites,sites,bc_in) i_agefuel = get_agefuel_class_index(cpatch%age,i_fuel) hio_fuel_amount_age_fuel(io_si,i_agefuel) = hio_fuel_amount_age_fuel(io_si,i_agefuel) + & - cpatch%fuel%frac_loading(i_fuel) * cpatch%fuel%total_loading * cpatch%area * AREA_INV + cpatch%fuel%frac_loading(i_fuel) * cpatch%fuel%non_trunk_loading * cpatch%area * AREA_INV hio_litter_moisture_si_fuel(io_si, i_fuel) = hio_litter_moisture_si_fuel(io_si, i_fuel) + & cpatch%fuel%effective_moisture(i_fuel) * cpatch%area * AREA_INV hio_fuel_amount_si_fuel(io_si, i_fuel) = hio_fuel_amount_si_fuel(io_si, i_fuel) + & - cpatch%fuel%frac_loading(i_fuel) * cpatch%fuel%total_loading * cpatch%area * AREA_INV + cpatch%fuel%frac_loading(i_fuel) * cpatch%fuel%non_trunk_loading * cpatch%area * AREA_INV hio_burnt_frac_litter_si_fuel(io_si, i_fuel) = hio_burnt_frac_litter_si_fuel(io_si, i_fuel) + & cpatch%fuel%frac_burnt(i_fuel) * cpatch%frac_burnt * cpatch%area * AREA_INV diff --git a/testing/functional_testing/fire/FatesTestFireMod.F90 b/testing/functional_testing/fire/FatesTestFireMod.F90 index 2dfb62d5db..e9b2b1668e 100644 --- a/testing/functional_testing/fire/FatesTestFireMod.F90 +++ b/testing/functional_testing/fire/FatesTestFireMod.F90 @@ -105,7 +105,7 @@ end subroutine ReadDatmData !===================================================================================== subroutine WriteFireData(out_file, nsteps, nfuelmods, temp_degC, precip, rh, NI, & - loading, frac_loading, fuel_BD, fuel_SAV, total_loading, fuel_moisture, & + loading, frac_loading, fuel_BD, fuel_SAV, non_trunk_loading, fuel_moisture, & fuel_models, carriers) ! ! DESCRIPTION: @@ -122,7 +122,7 @@ subroutine WriteFireData(out_file, nsteps, nfuelmods, temp_degC, precip, rh, NI, real(r8), intent(in) :: NI(:) real(r8), intent(in) :: loading(:,:) real(r8), intent(in) :: frac_loading(:,:) - real(r8), intent(in) :: total_loading(:) + real(r8), intent(in) :: non_trunk_loading(:) real(r8), intent(in) :: fuel_moisture(:,:) real(r8), intent(in) :: fuel_BD(:) real(r8), intent(in) :: fuel_SAV(:) @@ -230,8 +230,8 @@ subroutine WriteFireData(out_file, nsteps, nfuelmods, temp_degC, precip, rh, NI, [character(len=150) :: 'litter_class fuel_model', '', 'fractional loading'], & 3, frac_loadingID) - ! register total fuel loading - call RegisterVar(ncid, 'total_loading', dimIDs(3:3), type_double, & + ! register non-trunk fuel loading + call RegisterVar(ncid, 'non_trunk_loading', dimIDs(3:3), type_double, & [character(len=20) :: 'coordinates', 'units', 'long_name'], & [character(len=150) :: 'fuel_model', 'kgC m-2', 'total loading'], & 3, tot_loadingID) @@ -262,7 +262,7 @@ subroutine WriteFireData(out_file, nsteps, nfuelmods, temp_degC, precip, rh, NI, call WriteVar(ncid, NIID, NI(:)) call WriteVar(ncid, loadingID, loading(:,:)) call WriteVar(ncid, frac_loadingID, frac_loading(:,:)) - call WriteVar(ncid, tot_loadingID, total_loading(:)) + call WriteVar(ncid, tot_loadingID, non_trunk_loading(:)) call WriteVar(ncid, moistiD, fuel_moisture(:,:)) call WriteVar(ncid, BDID, fuel_BD(:)) call WriteVar(ncid, SAVID, fuel_SAV(:)) diff --git a/testing/functional_testing/fire/FatesTestFuel.F90 b/testing/functional_testing/fire/FatesTestFuel.F90 index 9f811dae06..bbbdde237f 100644 --- a/testing/functional_testing/fire/FatesTestFuel.F90 +++ b/testing/functional_testing/fire/FatesTestFuel.F90 @@ -16,27 +16,27 @@ program FatesTestFuel implicit none ! LOCALS: - type(fates_unit_test_param_reader) :: param_reader ! param reader instance - type(fuel_models_array_class) :: fuel_models_array ! array of fuel models - class(fire_weather), pointer :: fireWeather ! fire weather object - type(fuel_type), allocatable :: fuel(:) ! fuel objects - character(len=:), allocatable :: param_file ! input parameter file - character(len=:), allocatable :: datm_file ! input DATM driver file - real(r8), allocatable :: temp_degC(:) ! daily air temperature [degC] - real(r8), allocatable :: precip(:) ! daily precipitation [mm] - real(r8), allocatable :: rh(:) ! daily relative humidity [%] - real(r8), allocatable :: wind(:) ! daily wind speed [m/s] - real(r8), allocatable :: NI(:) ! Nesterov index - real(r8), allocatable :: fuel_loading(:,:) ! fuel loading [kgC/m2] - real(r8), allocatable :: total_loading(:) ! total fuel loading [kgC/m2] - real(r8), allocatable :: frac_loading(:,:) ! fractional fuel loading [0-1] - real(r8), allocatable :: fuel_BD(:) ! bulk density of fuel [kg/m3] - real(r8), allocatable :: fuel_SAV(:) ! fuel surface area to volume ratio [/cm] - real(r8), allocatable :: fuel_moisture(:,:) ! fuel moisture [m3/m3] - character(len=100), allocatable :: fuel_names(:) ! names of fuel models - character(len=2), allocatable :: carriers(:) ! carriers of fuel models - integer :: i, f ! looping indices - integer :: num_fuel_models ! number of fuel models to test + type(fates_unit_test_param_reader) :: param_reader ! param reader instance + type(fuel_models_array_class) :: fuel_models_array ! array of fuel models + class(fire_weather), pointer :: fireWeather ! fire weather object + type(fuel_type), allocatable :: fuel(:) ! fuel objects + character(len=:), allocatable :: param_file ! input parameter file + character(len=:), allocatable :: datm_file ! input DATM driver file + real(r8), allocatable :: temp_degC(:) ! daily air temperature [degC] + real(r8), allocatable :: precip(:) ! daily precipitation [mm] + real(r8), allocatable :: rh(:) ! daily relative humidity [%] + real(r8), allocatable :: wind(:) ! daily wind speed [m/s] + real(r8), allocatable :: NI(:) ! Nesterov index + real(r8), allocatable :: fuel_loading(:,:) ! fuel loading [kgC/m2] + real(r8), allocatable :: non_trunk_loading(:) ! non-trunk fuel loading [kgC/m2] + real(r8), allocatable :: frac_loading(:,:) ! fractional fuel loading [0-1] + real(r8), allocatable :: fuel_BD(:) ! bulk density of fuel [kg/m3] + real(r8), allocatable :: fuel_SAV(:) ! fuel surface area to volume ratio [/cm] + real(r8), allocatable :: fuel_moisture(:,:) ! fuel moisture [m3/m3] + character(len=100), allocatable :: fuel_names(:) ! names of fuel models + character(len=2), allocatable :: carriers(:) ! carriers of fuel models + integer :: i, f ! looping indices + integer :: num_fuel_models ! number of fuel models to test ! CONSTANTS: integer, parameter :: n_days = 365 ! number of days to run simulation @@ -59,7 +59,7 @@ program FatesTestFuel allocate(frac_loading(num_fuel_classes, num_fuel_models)) allocate(fuel_BD(num_fuel_models)) allocate(fuel_SAV(num_fuel_models)) - allocate(total_loading(num_fuel_models)) + allocate(non_trunk_loading(num_fuel_models)) allocate(fuel_names(num_fuel_models)) allocate(carriers(num_fuel_models)) @@ -96,7 +96,7 @@ program FatesTestFuel ! save values fuel_loading(:,f) = fuel(f)%loading(:) - total_loading(f) = fuel(f)%total_loading + non_trunk_loading(f) = fuel(f)%non_trunk_loading frac_loading(:,f) = fuel(f)%frac_loading(:) fuel_BD(f) = fuel(f)%bulk_density fuel_SAV(f) = fuel(f)%SAV @@ -117,7 +117,7 @@ program FatesTestFuel ! write out data call WriteFireData(out_file, n_days, num_fuel_models, temp_degC, precip, rh, NI, & - fuel_loading, frac_loading, fuel_BD, fuel_SAV, total_loading, fuel_moisture, & + fuel_loading, frac_loading, fuel_BD, fuel_SAV, non_trunk_loading, fuel_moisture, & fuel_models, carriers) -end program FatesTestFuel \ No newline at end of file +end program FatesTestFuel diff --git a/testing/unit_testing/fire_fuel_test/test_FireFuel.pf b/testing/unit_testing/fire_fuel_test/test_FireFuel.pf index d1d8de4475..1eb009bc16 100644 --- a/testing/unit_testing/fire_fuel_test/test_FireFuel.pf +++ b/testing/unit_testing/fire_fuel_test/test_FireFuel.pf @@ -55,16 +55,16 @@ module test_FireFuel class(TestFireFuel), intent(inout) :: this ! fuel test object real(r8) :: dummy_litter = 5.0_r8 ! dummy litter value [kgC/m2] real(r8) :: trunk_litter = 100.0_r8 ! trunk branch litter [kgC/m2] - real(r8) :: total_loading ! what total loading should be [kgC/m2] + real(r8) :: non_trunk_loading ! what non-trunk loading should be [kgC/m2] - total_loading = dummy_litter*5.0_r8 + non_trunk_loading = dummy_litter*5.0_r8 call this%fuel%CalculateLoading(dummy_litter, dummy_litter, dummy_litter, & dummy_litter, trunk_litter, dummy_litter) call this%fuel%SumLoading() - @assertEqual(this%fuel%total_loading, total_loading, tolerance=tol) + @assertEqual(this%fuel%non_trunk_loading, non_trunk_loading, tolerance=tol) end subroutine SumLoading_CorrectValues @@ -74,12 +74,12 @@ module test_FireFuel class(TestFireFuel), intent(inout) :: this ! fuel test object real(r8) :: dummy_litter = 5.0_r8 ! dummy litter value [kgC/m2] real(r8) :: trunk_litter = 100.0_r8 ! trunk branch litter [kgC/m2] - real(r8) :: total_loading ! what total loading should be [kgC/m2] + real(r8) :: non_trunk_loading ! what non-trunk loading should be [kgC/m2] real(r8) :: frac_loading ! what the fractional loading should be [0-1] integer :: i ! looping index - total_loading = dummy_litter*float(num_fuel_classes - 1) - frac_loading = dummy_litter/total_loading + non_trunk_loading = dummy_litter*float(num_fuel_classes - 1) + frac_loading = dummy_litter/non_trunk_loading call this%fuel%CalculateLoading(dummy_litter, dummy_litter, dummy_litter, & dummy_litter, trunk_litter, dummy_litter) From 6f6ac0b4d7f7ee040e389431468408514adf2de4 Mon Sep 17 00:00:00 2001 From: adrifoster Date: Tue, 29 Oct 2024 15:01:26 -0600 Subject: [PATCH 048/111] other updates --- biogeochem/EDPatchDynamicsMod.F90 | 2 -- biogeochem/FatesPatchMod.F90 | 8 ++++---- testing/functional_testing/fire/FatesTestFireMod.F90 | 2 +- testing/functional_testing/fire/fuel_plotting.py | 3 ++- testing/testing_shr/FatesArgumentUtils.F90 | 12 ++++++------ testing/unit_testing/fire_fuel_test/CMakeLists.txt | 3 ++- 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/biogeochem/EDPatchDynamicsMod.F90 b/biogeochem/EDPatchDynamicsMod.F90 index 6c7a059c48..fea0274bb3 100644 --- a/biogeochem/EDPatchDynamicsMod.F90 +++ b/biogeochem/EDPatchDynamicsMod.F90 @@ -3283,8 +3283,6 @@ subroutine fuse_2_patches(csite, dp, rp) call rp%tveg_longterm%FuseRMean(dp%tveg_longterm,rp%area*inv_sum_area) - rp%fuel%average_moisture = (dp%fuel%average_moisture*dp%area + rp%fuel%average_moisture*rp%area) * inv_sum_area - rp%fuel%effective_moisture = (dp%fuel%effective_moisture(:)*dp%area + rp%fuel%effective_moisture(:)*rp%area) * inv_sum_area rp%livegrass = (dp%livegrass*dp%area + rp%livegrass*rp%area) * inv_sum_area rp%ros_front = (dp%ros_front*dp%area + rp%ros_front*rp%area) * inv_sum_area rp%tau_l = (dp%tau_l*dp%area + rp%tau_l*rp%area) * inv_sum_area diff --git a/biogeochem/FatesPatchMod.F90 b/biogeochem/FatesPatchMod.F90 index 52c2802520..79ef7f9b08 100644 --- a/biogeochem/FatesPatchMod.F90 +++ b/biogeochem/FatesPatchMod.F90 @@ -906,24 +906,24 @@ subroutine FreeMemory(this, regeneration_model, numpft) end if if (istat/=0) then - write(fates_log(),*) 'dealloc009: fail on deallocate patch vectors:'//trim(smsg) + write(fates_log(),*) 'dealloc010: fail on deallocate patch vectors:'//trim(smsg) call endrun(msg=errMsg(sourcefile, __LINE__)) endif ! deallocate running means deallocate(this%tveg24, stat=istat, errmsg=smsg) if (istat/=0) then - write(fates_log(),*) 'dealloc010: fail on deallocate(this%tveg24):'//trim(smsg) + write(fates_log(),*) 'dealloc011: fail on deallocate(this%tveg24):'//trim(smsg) call endrun(msg=errMsg(sourcefile, __LINE__)) endif deallocate(this%tveg_lpa, stat=istat, errmsg=smsg) if (istat/=0) then - write(fates_log(),*) 'dealloc011: fail on deallocate(this%tveg_lpa):'//trim(smsg) + write(fates_log(),*) 'dealloc012: fail on deallocate(this%tveg_lpa):'//trim(smsg) call endrun(msg=errMsg(sourcefile, __LINE__)) endif deallocate(this%tveg_longterm, stat=istat, errmsg=smsg) if (istat/=0) then - write(fates_log(),*) 'dealloc012: fail on deallocate(this%tveg_longterm):'//trim(smsg) + write(fates_log(),*) 'dealloc013: fail on deallocate(this%tveg_longterm):'//trim(smsg) call endrun(msg=errMsg(sourcefile, __LINE__)) endif diff --git a/testing/functional_testing/fire/FatesTestFireMod.F90 b/testing/functional_testing/fire/FatesTestFireMod.F90 index e9b2b1668e..7076abd5f7 100644 --- a/testing/functional_testing/fire/FatesTestFireMod.F90 +++ b/testing/functional_testing/fire/FatesTestFireMod.F90 @@ -271,4 +271,4 @@ subroutine WriteFireData(out_file, nsteps, nfuelmods, temp_degC, precip, rh, NI, end subroutine WriteFireData -end module FatesTestFireMod \ No newline at end of file +end module FatesTestFireMod diff --git a/testing/functional_testing/fire/fuel_plotting.py b/testing/functional_testing/fire/fuel_plotting.py index e3d061f64b..b1c2ffef1d 100644 --- a/testing/functional_testing/fire/fuel_plotting.py +++ b/testing/functional_testing/fire/fuel_plotting.py @@ -92,4 +92,5 @@ def plot_moisture_dat(fuel_dat, save_figs, plot_dir): if save_figs: fig_name = os.path.join(plot_dir, "fuel_moisture_plot.png") - plt.savefig(fig_name) \ No newline at end of file + plt.savefig(fig_name) + \ No newline at end of file diff --git a/testing/testing_shr/FatesArgumentUtils.F90 b/testing/testing_shr/FatesArgumentUtils.F90 index 18c3ee6103..ed247fa157 100644 --- a/testing/testing_shr/FatesArgumentUtils.F90 +++ b/testing/testing_shr/FatesArgumentUtils.F90 @@ -25,12 +25,12 @@ function command_line_arg(arg_position) if (n_args < arg_position) then write(*, '(a, i2, a, i2)') "Incorrect number of arguments: ", n_args, ". Should be at least", arg_position, "." stop - else - call get_command_argument(arg_position, length=arglen) - allocate(character(arglen) :: command_line_arg) - call get_command_argument(arg_position, value=command_line_arg) - endif + end if + + call get_command_argument(arg_position, length=arglen) + allocate(character(arglen) :: command_line_arg) + call get_command_argument(arg_position, value=command_line_arg) end function command_line_arg -end module FatesArgumentUtils \ No newline at end of file +end module FatesArgumentUtils diff --git a/testing/unit_testing/fire_fuel_test/CMakeLists.txt b/testing/unit_testing/fire_fuel_test/CMakeLists.txt index 3efadb9021..3ebdaef4ad 100644 --- a/testing/unit_testing/fire_fuel_test/CMakeLists.txt +++ b/testing/unit_testing/fire_fuel_test/CMakeLists.txt @@ -2,4 +2,5 @@ set(pfunit_sources test_FireFuel.pf) add_pfunit_ctest(FireFuel TEST_SOURCES "${pfunit_sources}" - LINK_LIBRARIES fates csm_share) \ No newline at end of file + LINK_LIBRARIES fates csm_share) + \ No newline at end of file From b4367834abf505eb4e5f53af9bc7e4b83af4685c Mon Sep 17 00:00:00 2001 From: adrifoster Date: Tue, 29 Oct 2024 15:17:43 -0600 Subject: [PATCH 049/111] updating variable names --- fire/FatesFuelMod.F90 | 71 +++++++++++-------- fire/SFMainMod.F90 | 52 +++++++------- .../fire/FatesTestFireMod.F90 | 2 +- .../functional_testing/fire/FatesTestFuel.F90 | 4 +- .../fire_fuel_test/test_FireFuel.pf | 12 ++-- 5 files changed, 76 insertions(+), 65 deletions(-) diff --git a/fire/FatesFuelMod.F90 b/fire/FatesFuelMod.F90 index 90b71bab9b..52322ed5ce 100644 --- a/fire/FatesFuelMod.F90 +++ b/fire/FatesFuelMod.F90 @@ -20,15 +20,15 @@ module FatesFuelMod real(r8) :: frac_burnt(num_fuel_classes) ! fraction of litter burnt by fire [0-1] real(r8) :: non_trunk_loading ! total fuel loading excluding trunks [kgC/m2] real(r8) :: average_moisture ! weighted average of fuel moisture across non-trunk fuel classes [m3/m3] - real(r8) :: bulk_density ! weighted average of bulk density across non-trunk fuel classes [kg/m3] - real(r8) :: SAV ! weighted average of surface area to volume ratio across non-trunk fuel classes [/cm] - real(r8) :: MEF ! weighted average of moisture of extinction across non-trunk fuel classes [m3/m3] + real(r8) :: bulk_density_notrunks ! weighted average of bulk density across non-trunk fuel classes [kg/m3] + real(r8) :: SAV_notrunks ! weighted average of surface area to volume ratio across non-trunk fuel classes [/cm] + real(r8) :: MEF_notrunks ! weighted average of moisture of extinction across non-trunk fuel classes [m3/m3] contains procedure :: Init procedure :: Fuse - procedure :: CalculateLoading + procedure :: UpdateLoading procedure :: SumLoading procedure :: CalculateFractionalLoading procedure :: UpdateFuelMoisture @@ -53,9 +53,9 @@ subroutine Init(this) this%effective_moisture(1:num_fuel_classes) = 0.0_r8 this%non_trunk_loading = 0.0_r8 this%average_moisture = 0.0_r8 - this%bulk_density = 0.0_r8 - this%SAV = 0.0_r8 - this%MEF = 0.0_r8 + this%bulk_density_notrunks = 0.0_r8 + this%SAV_notrunks = 0.0_r8 + this%MEF_notrunks = 0.0_r8 end subroutine Init @@ -90,23 +90,23 @@ subroutine Fuse(this, self_area, donor_area, donor_fuel) donor_fuel%effective_moisture(i)*donor_weight end do - this%non_trunk_loading = this%non_trunk_loading*self_weight + & + this%non_trunk_loading = this%non_trunk_loading*self_weight + & donor_fuel%non_trunk_loading*donor_weight this%average_moisture = this%average_moisture*self_weight + & donor_fuel%average_moisture*donor_weight - this%bulk_density = this%bulk_density*self_weight + & - donor_fuel%bulk_density*donor_weight - this%SAV = this%SAV*self_weight + donor_fuel%SAV*donor_weight - this%MEF = this%MEF*self_weight + donor_fuel%MEF*donor_weight + this%bulk_density_notrunks = this%bulk_density_notrunks*self_weight + & + donor_fuel%bulk_density_notrunks*donor_weight + this%SAV_notrunks = this%SAV_notrunks*self_weight + donor_fuel%SAV_notrunks*donor_weight + this%MEF_notrunks = this%MEF_notrunks*self_weight + donor_fuel%MEF_notrunks*donor_weight end subroutine Fuse !------------------------------------------------------------------------------------- - subroutine CalculateLoading(this, leaf_litter, twig_litter, small_branch_litter, & + subroutine UpdateLoading(this, leaf_litter, twig_litter, small_branch_litter, & large_branch_litter, trunk_litter, live_grass) ! DESCRIPTION: - ! Calculates loading for each fuel type + ! Updates loading for each fuel type ! ARGUMENTS: class(fuel_type), intent(inout) :: this ! fuel class @@ -124,7 +124,7 @@ subroutine CalculateLoading(this, leaf_litter, twig_litter, small_branch_litter, this%loading(fuel_classes%live_grass()) = live_grass this%loading(fuel_classes%trunks()) = trunk_litter - end subroutine CalculateLoading + end subroutine UpdateLoading !------------------------------------------------------------------------------------- @@ -212,7 +212,7 @@ subroutine UpdateFuelMoisture(this, sav_fuel, drying_ratio, fireWeatherClass) end select this%average_moisture = 0.0_r8 - this%MEF = 0.0_r8 + this%MEF_notrunks = 0.0_r8 do i = 1, num_fuel_classes ! calculate moisture of extinction and fuel effective moisture moisture_of_extinction(i) = MoistureOfExtinction(sav_fuel(i)) @@ -221,14 +221,14 @@ subroutine UpdateFuelMoisture(this, sav_fuel, drying_ratio, fireWeatherClass) ! average fuel moisture and MEF across all fuel types except trunks [m3/m3] if (i /= fuel_classes%trunks()) then this%average_moisture = this%average_moisture + this%frac_loading(i)*moisture(i) - this%MEF = this%MEF + this%frac_loading(i)*moisture_of_extinction(i) + this%MEF_notrunks = this%MEF_notrunks + this%frac_loading(i)*moisture_of_extinction(i) end if end do else this%effective_moisture(1:num_fuel_classes) = 0.0_r8 this%average_moisture = 0.0_r8 - this%MEF = 0.0_r8 + this%MEF_notrunks = 0.0_r8 end if end subroutine UpdateFuelMoisture @@ -277,7 +277,7 @@ real(r8) function MoistureOfExtinction(sav) ! Mortality for Long-Range Planning" ! ! Example MEFs: - ! pine needles = 0.30 (Rothermal 1972) + ! pine needles = 0.30 (Rothermel 1972) ! short grass = 0.12 (Rothermel 1983; Gen. Tech. Rep. INT-143; Table II-1) ! tall grass = 0.24 (Rothermel 1983) ! chaparral = 0.20 (Rothermel 1983) @@ -302,7 +302,8 @@ real(r8) function MoistureOfExtinction(sav) real(r8), parameter :: MEF_b = 0.066_r8 if (sav <= 0.0_r8) then - MoistureOfExtinction = 0.0_r8 + write(fates_log(), *) 'SAV cannot be negative - SAV = ' // sav + call endrun(msg=errMsg(__FILE__, __LINE__)) else MoistureOfExtinction = MEF_a - MEF_b*log(sav) end if @@ -311,9 +312,14 @@ end function MoistureOfExtinction !------------------------------------------------------------------------------------- - subroutine AverageBulkDensity(this, bulk_density) + subroutine AverageBulkDensity_NoTrunks(this, bulk_density) ! DESCRIPTION: ! Calculates average bulk density (not including trunks) + ! + ! Only the 1-h, 10-h and 100-h fuel classes influence fire spread + ! Rothermel, 1972 (USDA FS GTR INT-115) + ! Wilson, 1982 (UTINT-289) + ! Pyne et al., 1996 (Introduction to wildland fire) ! ARGUMENTS: class(fuel_type), intent(inout) :: this ! fuel class @@ -323,24 +329,29 @@ subroutine AverageBulkDensity(this, bulk_density) integer :: i ! looping index if (this%non_trunk_loading > nearzero) then - this%bulk_density = 0.0_r8 + this%bulk_density_notrunks = 0.0_r8 do i = 1, num_fuel_classes ! average bulk density across all fuel types except trunks if (i /= fuel_classes%trunks()) then - this%bulk_density = this%bulk_density + this%frac_loading(i)*bulk_density(i) + this%bulk_density_notrunks = this%bulk_density_notrunks + this%frac_loading(i)*bulk_density(i) end if end do else - this%bulk_density = sum(bulk_density(1:num_fuel_classes))/num_fuel_classes + this%bulk_density_notrunks = sum(bulk_density(1:num_fuel_classes))/num_fuel_classes end if - end subroutine AverageBulkDensity + end subroutine AverageBulkDensity_NoTrunks !------------------------------------------------------------------------------------- - subroutine AverageSAV(this, sav_fuel) + subroutine AverageSAV_NoTrunks(this, sav_fuel) ! DESCRIPTION: ! Calculates average surface area to volume ratio (not including trunks) + ! + ! Only the 1-h, 10-h and 100-h fuel classes influence fire spread + ! Rothermel, 1972 (USDA FS GTR INT-115) + ! Wilson, 1982 (UTINT-289) + ! Pyne et al., 1996 (Introduction to wildland fire) ! ARGUMENTS: class(fuel_type), intent(inout) :: this ! fuel class @@ -350,18 +361,18 @@ subroutine AverageSAV(this, sav_fuel) integer :: i ! looping index if (this%non_trunk_loading > nearzero) then - this%SAV = 0.0_r8 + this%SAV_notrunks = 0.0_r8 do i = 1, num_fuel_classes ! average bulk density across all fuel types except trunks if (i /= fuel_classes%trunks()) then - this%SAV = this%SAV + this%frac_loading(i)*sav_fuel(i) + this%SAV_notrunks = this%SAV_notrunks + this%frac_loading(i)*sav_fuel(i) end if end do else - this%SAV = sum(sav_fuel(1:num_fuel_classes))/num_fuel_classes + this%SAV_notrunks = sum(sav_fuel(1:num_fuel_classes))/num_fuel_classes end if - end subroutine AverageSAV + end subroutine AverageSAV_NoTrunks !--------------------------------------------------------------------------------------- diff --git a/fire/SFMainMod.F90 b/fire/SFMainMod.F90 index c08164c92f..d4286e8566 100644 --- a/fire/SFMainMod.F90 +++ b/fire/SFMainMod.F90 @@ -180,7 +180,7 @@ subroutine UpdateFuelCharacteristics(currentSite) ! update fuel loading [kgC/m2] litter => currentPatch%litter(element_pos(carbon12_element)) - call currentPatch%fuel%CalculateLoading(sum(litter%leaf_fines(:)), & + call currentPatch%fuel%UpdateLoading(sum(litter%leaf_fines(:)), & litter%ag_cwd(1), litter%ag_cwd(2), litter%ag_cwd(3), litter%ag_cwd(4), & currentPatch%livegrass) @@ -193,8 +193,8 @@ subroutine UpdateFuelCharacteristics(currentSite) currentSite%fireWeather) ! calculate geometric properties - call currentPatch%fuel%AverageBulkDensity(SF_val_FBD) - call currentPatch%fuel%AverageSAV(SF_val_SAV) + call currentPatch%fuel%AverageBulkDensity_NoTrunks(SF_val_FBD) + call currentPatch%fuel%AverageSAV_NoTrunks(SF_val_SAV) end if currentPatch => currentPatch%younger @@ -218,7 +218,7 @@ subroutine rate_of_spread (currentSite) type(fates_patch_type), pointer :: currentPatch - ! Rothermal fire spread model parameters. + ! Rothermel fire spread model parameters. real(r8) beta,beta_op ! weighted average of packing ratio (unitless) real(r8) ir ! reaction intensity (kJ/m2/min) real(r8) xi,eps,phi_wind ! all are unitless @@ -244,20 +244,20 @@ subroutine rate_of_spread (currentSite) ! ----start spreading--- if ( hlm_masterproc == itrue .and.debug) write(fates_log(),*) & - 'SF - currentPatch%fuel%bulk_density ',currentPatch%fuel%bulk_density + 'SF - currentPatch%fuel%bulk_density_notrunks',currentPatch%fuel%bulk_density_notrunks if ( hlm_masterproc == itrue .and.debug) write(fates_log(),*) & 'SF - SF_val_part_dens ',SF_val_part_dens ! beta = packing ratio (unitless) ! fraction of fuel array volume occupied by fuel or compactness of fuel bed - beta = currentPatch%fuel%bulk_density/SF_val_part_dens + beta = currentPatch%fuel%bulk_density_notrunks/SF_val_part_dens ! Equation A6 in Thonicke et al. 2010 ! packing ratio (unitless) - if (currentPatch%fuel%SAV < nearzero) then - beta_op = 0.0_r8 + if (currentPatch%fuel%SAV_notrunks < nearzero) then + beta_op = 0.0_r8 else - beta_op = 0.200395_r8 *(currentPatch%fuel%SAV**(-0.8189_r8)) + beta_op = 0.200395_r8 *(currentPatch%fuel%SAV_notrunks**(-0.8189_r8)) end if if ( hlm_masterproc == itrue .and.debug) write(fates_log(),*) 'SF - beta ',beta @@ -269,25 +269,25 @@ subroutine rate_of_spread (currentSite) end if if(write_sf == itrue)then - if ( hlm_masterproc == itrue ) write(fates_log(),*) 'esf ',currentPatch%fuel%average_moisture + if ( hlm_masterproc == itrue ) write(fates_log(),*) 'average moisture',currentPatch%fuel%average_moisture endif ! ---heat of pre-ignition--- ! Equation A4 in Thonicke et al. 2010 - ! Rothermal EQ12= 250 Btu/lb + 1116 Btu/lb * average_moisture - ! conversion of Rothermal (1972) EQ12 in BTU/lb to current kJ/kg + ! Rothermel EQ12= 250 Btu/lb + 1116 Btu/lb * average_moisture + ! conversion of Rothermel (1972) EQ12 in BTU/lb to current kJ/kg ! q_ig in kJ/kg q_ig = q_dry +2594.0_r8 * currentPatch%fuel%average_moisture ! ---effective heating number--- ! Equation A3 in Thonicke et al. 2010. - eps = exp(-4.528_r8 / currentPatch%fuel%SAV) + eps = exp(-4.528_r8 / currentPatch%fuel%SAV_notrunks) ! Equation A7 in Thonicke et al. 2010 per eqn 49 from Rothermel 1972 - b = 0.15988_r8 * (currentPatch%fuel%SAV**0.54_r8) + b = 0.15988_r8 * (currentPatch%fuel%SAV_notrunks**0.54_r8) ! Equation A8 in Thonicke et al. 2010 per eqn 48 from Rothermel 1972 - c = 7.47_r8 * (exp(-0.8711_r8 * (currentPatch%fuel%SAV**0.55_r8))) + c = 7.47_r8 * (exp(-0.8711_r8 * (currentPatch%fuel%SAV_notrunks**0.55_r8))) ! Equation A9 in Thonicke et al. 2010. (appears to have typo, using coefficient eqn.50 Rothermel 1972) - e = 0.715_r8 * (exp(-0.01094_r8 * currentPatch%fuel%SAV)) + e = 0.715_r8 * (exp(-0.01094_r8 * currentPatch%fuel%SAV_notrunks)) if (debug) then if ( hlm_masterproc == itrue .and.debug) write(fates_log(),*) 'SF - c ',c @@ -303,26 +303,26 @@ subroutine rate_of_spread (currentSite) ! ---propagating flux---- - ! Equation A2 in Thonicke et al.2010 and Eq. 42 Rothermal 1972 + ! Equation A2 in Thonicke et al.2010 and Eq. 42 Rothermel 1972 ! xi (unitless) - xi = (exp((0.792_r8 + 3.7597_r8 * (currentPatch%fuel%SAV**0.5_r8)) * (beta+0.1_r8))) / & - (192_r8+7.9095_r8 * currentPatch%fuel%SAV) + xi = (exp((0.792_r8 + 3.7597_r8 * (currentPatch%fuel%SAV_notrunks**0.5_r8)) * (beta+0.1_r8))) / & + (192_r8+7.9095_r8 * currentPatch%fuel%SAV_notrunks) ! ---reaction intensity---- ! Equation in table A1 Thonicke et al. 2010. - a = 8.9033_r8 * (currentPatch%fuel%SAV**(-0.7913_r8)) + a = 8.9033_r8 * (currentPatch%fuel%SAV_notrunks**(-0.7913_r8)) a_beta = exp(a*(1.0_r8-beta_ratio)) !dummy variable for reaction_v_opt equation ! Equation in table A1 Thonicke et al. 2010. ! reaction_v_max and reaction_v_opt = reaction velocity in units of per min - ! reaction_v_max = Equation 36 in Rothermal 1972 and Fig 12 - reaction_v_max = 1.0_r8 / (0.0591_r8 + 2.926_r8* (currentPatch%fuel%SAV**(-1.5_r8))) - ! reaction_v_opt = Equation 38 in Rothermal 1972 and Fig 11 + ! reaction_v_max = Equation 36 in Rothermel 1972 and Fig 12 + reaction_v_max = 1.0_r8 / (0.0591_r8 + 2.926_r8* (currentPatch%fuel%SAV_notrunks**(-1.5_r8))) + ! reaction_v_opt = Equation 38 in Rothermel 1972 and Fig 11 reaction_v_opt = reaction_v_max*(beta_ratio**a)*a_beta ! mw_weight = relative fuel moisture/fuel moisture of extinction ! average values for litter pools (dead leaves, twigs, small and large branches) plus grass - mw_weight = currentPatch%fuel%average_moisture/currentPatch%fuel%MEF + mw_weight = currentPatch%fuel%average_moisture/currentPatch%fuel%MEF_notrunks ! Equation in table A1 Thonicke et al. 2010. ! moist_damp is unitless @@ -335,11 +335,11 @@ subroutine rate_of_spread (currentSite) ! write(fates_log(),*) 'ir',gamma_aptr,moist_damp,SF_val_fuel_energy,SF_val_miner_damp - if (((currentPatch%fuel%bulk_density) <= 0.0_r8).or.(eps <= 0.0_r8).or.(q_ig <= 0.0_r8)) then + if (((currentPatch%fuel%bulk_density_notrunks) <= 0.0_r8).or.(eps <= 0.0_r8).or.(q_ig <= 0.0_r8)) then currentPatch%ROS_front = 0.0_r8 else ! Equation 9. Thonicke et al. 2010. ! forward ROS in m/min - currentPatch%ROS_front = (ir*xi*(1.0_r8+phi_wind)) / (currentPatch%fuel%bulk_density*eps*q_ig) + currentPatch%ROS_front = (ir*xi*(1.0_r8+phi_wind)) / (currentPatch%fuel%bulk_density_notrunks*eps*q_ig) endif ! Equation 10 in Thonicke et al. 2010 ! backward ROS from Can FBP System (1992) in m/min diff --git a/testing/functional_testing/fire/FatesTestFireMod.F90 b/testing/functional_testing/fire/FatesTestFireMod.F90 index 7076abd5f7..c37982039b 100644 --- a/testing/functional_testing/fire/FatesTestFireMod.F90 +++ b/testing/functional_testing/fire/FatesTestFireMod.F90 @@ -65,7 +65,7 @@ subroutine SetUpFuel(fuel, fuel_model_array, fuel_model_index, fuel_name, fuel_c fuel_name = fuel_model_array%fuel_models(i)%fuel_model_name fuel_carrier = fuel_model_array%fuel_models(i)%carrier - call fuel%CalculateLoading(leaf_litter, twig_litter, small_branch_litter, & + call fuel%UpdateLoading(leaf_litter, twig_litter, small_branch_litter, & large_branch_litter, 0.0_r8, grass_litter) end subroutine SetUpFuel diff --git a/testing/functional_testing/fire/FatesTestFuel.F90 b/testing/functional_testing/fire/FatesTestFuel.F90 index bbbdde237f..cd2621c449 100644 --- a/testing/functional_testing/fire/FatesTestFuel.F90 +++ b/testing/functional_testing/fire/FatesTestFuel.F90 @@ -98,8 +98,8 @@ program FatesTestFuel fuel_loading(:,f) = fuel(f)%loading(:) non_trunk_loading(f) = fuel(f)%non_trunk_loading frac_loading(:,f) = fuel(f)%frac_loading(:) - fuel_BD(f) = fuel(f)%bulk_density - fuel_SAV(f) = fuel(f)%SAV + fuel_BD(f) = fuel(f)%bulk_density_notrunks + fuel_SAV(f) = fuel(f)%SAV_notrunks end do diff --git a/testing/unit_testing/fire_fuel_test/test_FireFuel.pf b/testing/unit_testing/fire_fuel_test/test_FireFuel.pf index 1eb009bc16..ffb6e9d524 100644 --- a/testing/unit_testing/fire_fuel_test/test_FireFuel.pf +++ b/testing/unit_testing/fire_fuel_test/test_FireFuel.pf @@ -27,7 +27,7 @@ module test_FireFuel end subroutine setUp @Test - subroutine CalculateLoading_CorrectInputOrder(this) + subroutine UpdateLoading_CorrectInputOrder(this) ! test that the calculate loading subroutine correctly sets the fuel values class(TestFireFuel), intent(inout) :: this ! fuel test object real(r8) :: leaf_litter = 5.0_r8 ! leaf litter [kgC/m2] @@ -37,7 +37,7 @@ module test_FireFuel real(r8) :: trunk_litter = 25.0_r8 ! trunk branch litter [kgC/m2] real(r8) :: live_grass = 30.0_r8 ! live grass [kgC/m2] - call this%fuel%CalculateLoading(leaf_litter, twig_litter, sm_br_litter, & + call this%fuel%UpdateLoading(leaf_litter, twig_litter, sm_br_litter, & lg_br_litter, trunk_litter, live_grass) @assertEqual(this%fuel%loading(fuel_classes%dead_leaves()), leaf_litter, tolerance=tol) @@ -47,7 +47,7 @@ module test_FireFuel @assertEqual(this%fuel%loading(fuel_classes%trunks()), trunk_litter, tolerance=tol) @assertEqual(this%fuel%loading(fuel_classes%live_grass()), live_grass, tolerance=tol) - end subroutine CalculateLoading_CorrectInputOrder + end subroutine UpdateLoading_CorrectInputOrder @Test subroutine SumLoading_CorrectValues(this) @@ -59,7 +59,7 @@ module test_FireFuel non_trunk_loading = dummy_litter*5.0_r8 - call this%fuel%CalculateLoading(dummy_litter, dummy_litter, dummy_litter, & + call this%fuel%UpdateLoading(dummy_litter, dummy_litter, dummy_litter, & dummy_litter, trunk_litter, dummy_litter) call this%fuel%SumLoading() @@ -81,7 +81,7 @@ module test_FireFuel non_trunk_loading = dummy_litter*float(num_fuel_classes - 1) frac_loading = dummy_litter/non_trunk_loading - call this%fuel%CalculateLoading(dummy_litter, dummy_litter, dummy_litter, & + call this%fuel%UpdateLoading(dummy_litter, dummy_litter, dummy_litter, & dummy_litter, trunk_litter, dummy_litter) call this%fuel%SumLoading() @@ -97,4 +97,4 @@ module test_FireFuel end subroutine CalculateFractionalLoading_CorrectValues -end module test_FireFuel \ No newline at end of file +end module test_FireFuel From 920c549ba2f67ffe2f26c38de6ffa44b52b1fb07 Mon Sep 17 00:00:00 2001 From: adrifoster Date: Wed, 30 Oct 2024 10:14:37 -0600 Subject: [PATCH 050/111] big fixes --- fire/FatesFuelMod.F90 | 6 +- fire/SFParamsMod.F90 | 2 +- testing/CMakeLists.txt | 4 +- .../functional_testing/fire/FatesTestFuel.F90 | 4 +- .../functional_testing/fire/fuel_plotting.py | 96 --------- testing/functional_testing/fire/fuel_test.py | 187 ++++++++++++++++++ testing/functional_tests.cfg | 6 +- testing/run_functional_tests.py | 13 +- testing/unit_tests.cfg | 3 + testing/utils.py | 47 +---- 10 files changed, 216 insertions(+), 152 deletions(-) delete mode 100644 testing/functional_testing/fire/fuel_plotting.py create mode 100644 testing/functional_testing/fire/fuel_test.py diff --git a/fire/FatesFuelMod.F90 b/fire/FatesFuelMod.F90 index 52322ed5ce..54182df66a 100644 --- a/fire/FatesFuelMod.F90 +++ b/fire/FatesFuelMod.F90 @@ -32,8 +32,8 @@ module FatesFuelMod procedure :: SumLoading procedure :: CalculateFractionalLoading procedure :: UpdateFuelMoisture - procedure :: AverageBulkDensity - procedure :: AverageSAV + procedure :: AverageBulkDensity_NoTrunks + procedure :: AverageSAV_NoTrunks end type fuel_type @@ -302,7 +302,7 @@ real(r8) function MoistureOfExtinction(sav) real(r8), parameter :: MEF_b = 0.066_r8 if (sav <= 0.0_r8) then - write(fates_log(), *) 'SAV cannot be negative - SAV = ' // sav + write(fates_log(), *) 'SAV cannot be negative - SAV' call endrun(msg=errMsg(__FILE__, __LINE__)) else MoistureOfExtinction = MEF_a - MEF_b*log(sav) diff --git a/fire/SFParamsMod.F90 b/fire/SFParamsMod.F90 index d688f1976c..65d87e5c6d 100644 --- a/fire/SFParamsMod.F90 +++ b/fire/SFParamsMod.F90 @@ -32,7 +32,7 @@ module SFParamsMod real(r8),protected, public :: SF_val_SAV(num_fuel_classes) real(r8),protected, public :: SF_val_FBD(num_fuel_classes) real(r8),protected, public :: SF_val_min_moisture(num_fuel_classes) - real(r8),protected, public :: SF_val_mid_moisture(NFSC) + real(r8),protected, public :: SF_val_mid_moisture(num_fuel_classes) real(r8),protected, public :: SF_val_low_moisture_Coeff(num_fuel_classes) real(r8),protected, public :: SF_val_low_moisture_Slope(num_fuel_classes) real(r8),protected, public :: SF_val_mid_moisture_Coeff(num_fuel_classes) diff --git a/testing/CMakeLists.txt b/testing/CMakeLists.txt index 71960cc587..7c19f7cc99 100644 --- a/testing/CMakeLists.txt +++ b/testing/CMakeLists.txt @@ -3,8 +3,8 @@ ## Functional tests add_subdirectory(functional_testing/allometry fates_allom_ftest) add_subdirectory(functional_testing/math_utils fates_math_ftest) -add_subdirectory(functional_testing/fire fates_fuel_test) +add_subdirectory(functional_testing/fire fates_fuel_ftest) ## Unit tests add_subdirectory(unit_testing/fire_weather_test fates_fire_weather_utest) -add_subdirectory(unit_testing/fire_fuel_test fates_fire_fuel_utest) \ No newline at end of file +add_subdirectory(unit_testing/fire_fuel_test fates_fire_fuel_utest) diff --git a/testing/functional_testing/fire/FatesTestFuel.F90 b/testing/functional_testing/fire/FatesTestFuel.F90 index cd2621c449..6684ba8ee1 100644 --- a/testing/functional_testing/fire/FatesTestFuel.F90 +++ b/testing/functional_testing/fire/FatesTestFuel.F90 @@ -91,8 +91,8 @@ program FatesTestFuel call fuel(f)%CalculateFractionalLoading() ! calculate geometric properties - call fuel(f)%AverageBulkDensity(SF_val_FBD) - call fuel(f)%AverageSAV(SF_val_SAV) + call fuel(f)%AverageBulkDensity_NoTrunks(SF_val_FBD) + call fuel(f)%AverageSAV_NoTrunks(SF_val_SAV) ! save values fuel_loading(:,f) = fuel(f)%loading(:) diff --git a/testing/functional_testing/fire/fuel_plotting.py b/testing/functional_testing/fire/fuel_plotting.py deleted file mode 100644 index b1c2ffef1d..0000000000 --- a/testing/functional_testing/fire/fuel_plotting.py +++ /dev/null @@ -1,96 +0,0 @@ -"""Utility functions for fuel functional unit tests -""" -import os -import math -import pandas as pd -import numpy as np -import xarray as xr -import matplotlib -import matplotlib.pyplot as plt - -def plot_fuel_dat(run_dir, out_file, save_figs, plot_dir): - """Plot output associated with fuel tests - - Args: - run_dir (str): run directory - out_file (str): output file - save_figs (bool): whether or not to save the figures - plot_dir (str): plot directory - """ - - fuel_dat = xr.open_dataset(os.path.join(run_dir, out_file)) - - plot_NI_dat(fuel_dat, save_figs, plot_dir) - plot_moisture_dat(fuel_dat, save_figs, plot_dir) - plot_barchart(fuel_dat, 'fuel_loading', 'Fuel loading', 'kgC m$^{-2}$', save_figs, plot_dir) - plot_barchart(fuel_dat, 'frac_loading', 'Fractional fuel loading', '0-1', save_figs, plot_dir) - plot_barchart(fuel_dat, 'bulk_density', 'Fuel bulk density', 'kg m$^{-3}$', save_figs, plot_dir, by_litter_type=False) - plot_barchart(fuel_dat, 'SAV', 'Fuel surface area to volume ratio', 'cm$^{-1}$', save_figs, plot_dir, by_litter_type=False) - -def plot_barchart(fuel_dat, var, varname, units, save_figs, plot_dir, by_litter_type=True): - - litter_classes = ['twigs', 'small branches', 'large branches', 'trunks', 'dead leaves', 'live grass'] - colors = ['darksalmon', 'peru', 'saddlebrown', 'black', 'moccasin', 'yellowgreen'] - fuel_models = [str(f) for f in fuel_dat.fuel_model.values] - - if by_litter_type: - data_dict = {lc: fuel_dat.isel(litter_class=i)[var].values for i, lc in enumerate(litter_classes)} - else: - data_dict = fuel_dat[var].values - - _, ax = plt.subplots() - if by_litter_type: - bottom = np.zeros(len(fuel_models)) - for i, (litter_class, dat) in enumerate(data_dict.items()): - ax.bar(fuel_models, dat, 0.5, label=litter_class, bottom=bottom, color=colors[i]) - bottom += dat - plt.legend(loc='center left', bbox_to_anchor=(1, 0.5)) - else: - ax.bar(fuel_models, data_dict, color='darkcyan') - - box = ax.get_position() - ax.set_position([box.x0, box.y0, box.width * 0.75, box.height]) - plt.ylabel(f'{varname} ({units})', fontsize=11) - plt.xticks(rotation=90) - plt.xlabel('Fuel Model') - - if save_figs: - fig_name = os.path.join(plot_dir, f"{varname}_plot.png") - plt.savefig(fig_name) - -def plot_NI_dat(fuel_dat, save_figs, plot_dir): - """Plot output for Nesterov index - - Args: - fuel_dat (Xarray Dataset): output fuel data - save_figs (bool): whether or not to save the figures - plot_dir (str): plot directory - """ - - plt.figure() - fuel_dat.NI.plot() - plt.xlabel('Time', fontsize=11) - plt.ylabel('Nesterov Index', fontsize=11) - - if save_figs: - fig_name = os.path.join(plot_dir, "Nesterov_plot.png") - plt.savefig(fig_name) - -def plot_moisture_dat(fuel_dat, save_figs, plot_dir): - """Plot output for fuel moisture - - Args: - fuel_dat (Xarray Dataset): output fuel data - save_figs (bool): whether or not to save the figures - plot_dir (str): plot directory - """ - - plt.figure() - fuel_dat.fuel_moisture.plot(hue='fuel_model') - plt.xlabel('Time', fontsize=11) - plt.ylabel('Fuel Moisture', fontsize=11) - - if save_figs: - fig_name = os.path.join(plot_dir, "fuel_moisture_plot.png") - plt.savefig(fig_name) - \ No newline at end of file diff --git a/testing/functional_testing/fire/fuel_test.py b/testing/functional_testing/fire/fuel_test.py new file mode 100644 index 0000000000..b9cd151621 --- /dev/null +++ b/testing/functional_testing/fire/fuel_test.py @@ -0,0 +1,187 @@ +""" +Concrete class for running the fuel functional test for FATES. +""" +import os +import numpy as np +import xarray as xr +import matplotlib.pyplot as plt +from functional_class import FunctionalTest + + +class FuelTest(FunctionalTest): + """Fuel test class""" + + name = "fuel" + + def __init__(self, test_dict): + super().__init__( + FuelTest.name, + test_dict["test_dir"], + test_dict["test_exe"], + test_dict["out_file"], + test_dict["use_param_file"], + test_dict["other_args"], + ) + self.plot = True + + def plot_output(self, run_dir: str, save_figs: bool, plot_dir: str): + """Plot output associated with fuel tests + + Args: + run_dir (str): run directory + out_file (str): output file + save_figs (bool): whether or not to save the figures + plot_dir (str): plot directory + """ + + fuel_dat = xr.open_dataset(os.path.join(run_dir, self.out_file)) + + self.plot_NI_dat(fuel_dat, save_figs, plot_dir) + self.plot_moisture_dat(fuel_dat, save_figs, plot_dir) + self.plot_barchart( + fuel_dat, + "fuel_loading", + "Fuel loading", + "kgC m$^{-2}$", + save_figs, + plot_dir, + ) + self.plot_barchart( + fuel_dat, + "frac_loading", + "Fractional fuel loading", + "0-1", + save_figs, + plot_dir, + ) + self.plot_barchart( + fuel_dat, + "bulk_density", + "Fuel bulk density", + "kg m$^{-3}$", + save_figs, + plot_dir, + by_litter_type=False, + ) + self.plot_barchart( + fuel_dat, + "SAV", + "Fuel surface area to volume ratio", + "cm$^{-1}$", + save_figs, + plot_dir, + by_litter_type=False, + ) + + @staticmethod + def plot_barchart( + fuel_dat: xr.Dataset, + var: str, + varname: str, + units: str, + save_figs: bool, + plot_dir: bool, + by_litter_type: bool = True, + ): + """Plots fuel data output as a bar chart + + Args: + fuel_dat (xr.Dataset): fuel data output + var (str): variable to plot + varname (str): variable name for x axis + units (str): units description + save_figs (bool): whether or not to save figure + plot_dir (bool): where to save figure + by_litter_type (bool, optional): whether the bar chart is by litter type. Defaults to True. + """ + + litter_classes = [ + "twigs", + "small branches", + "large branches", + "trunks", + "dead leaves", + "live grass", + ] + colors = [ + "darksalmon", + "peru", + "saddlebrown", + "black", + "moccasin", + "yellowgreen", + ] + fuel_models = [str(f) for f in fuel_dat.fuel_model.values] + + if by_litter_type: + data_dict = { + lc: fuel_dat.isel(litter_class=i)[var].values + for i, lc in enumerate(litter_classes) + } + else: + data_dict = fuel_dat[var].values + + _, ax = plt.subplots() + if by_litter_type: + bottom = np.zeros(len(fuel_models)) + for i, (litter_class, dat) in enumerate(data_dict.items()): + ax.bar( + fuel_models, + dat, + 0.5, + label=litter_class, + bottom=bottom, + color=colors[i], + ) + bottom += dat + plt.legend(loc="center left", bbox_to_anchor=(1, 0.5)) + else: + ax.bar(fuel_models, data_dict, color="darkcyan") + + box = ax.get_position() + ax.set_position([box.x0, box.y0, box.width * 0.75, box.height]) + plt.ylabel(f"{varname} ({units})", fontsize=11) + plt.xticks(rotation=90) + plt.xlabel("Fuel Model") + + if save_figs: + fig_name = os.path.join(plot_dir, f"{varname}_plot.png") + plt.savefig(fig_name) + + @staticmethod + def plot_NI_dat(fuel_dat: xr.Dataset, save_figs: bool, plot_dir: str): + """Plot output for Nesterov index + + Args: + fuel_dat (Xarray Dataset): output fuel data + save_figs (bool): whether or not to save the figures + plot_dir (str): plot directory + """ + + plt.figure() + fuel_dat.NI.plot() + plt.xlabel("Time", fontsize=11) + plt.ylabel("Nesterov Index", fontsize=11) + + if save_figs: + fig_name = os.path.join(plot_dir, "Nesterov_plot.png") + plt.savefig(fig_name) + + @staticmethod + def plot_moisture_dat(fuel_dat: xr.Dataset, save_figs: bool, plot_dir: str): + """Plot output for fuel moisture + + Args: + fuel_dat (Xarray Dataset): output fuel data + save_figs (bool): whether or not to save the figures + plot_dir (str): plot directory + """ + + plt.figure() + fuel_dat.fuel_moisture.plot(hue="fuel_model") + plt.xlabel("Time", fontsize=11) + plt.ylabel("Fuel Moisture", fontsize=11) + + if save_figs: + fig_name = os.path.join(plot_dir, "fuel_moisture_plot.png") + plt.savefig(fig_name) diff --git a/testing/functional_tests.cfg b/testing/functional_tests.cfg index 0c859e1559..46924c307c 100644 --- a/testing/functional_tests.cfg +++ b/testing/functional_tests.cfg @@ -13,4 +13,8 @@ use_param_file = False other_args = [] [fuel] -test_dir = fates_fuel_ftest \ No newline at end of file +test_dir = fates_fuel_ftest +test_exe = FATES_fuel_exe +out_file = fuel_out.nc +use_param_file = True +other_args = ['../testing/test_data/BONA_datm.nc'] diff --git a/testing/run_functional_tests.py b/testing/run_functional_tests.py index 5f80a3f095..8a2078628a 100755 --- a/testing/run_functional_tests.py +++ b/testing/run_functional_tests.py @@ -36,12 +36,19 @@ # add testing subclasses here from functional_class import FunctionalTest -from functional_testing.allometry.allometry_test import AllometryTest # pylint: disable=unused-import -from functional_testing.math_utils.math_utils_test import QuadraticTest # pylint: disable=unused-import +from functional_testing.allometry.allometry_test import ( + AllometryTest, +) # pylint: disable=unused-import +from functional_testing.math_utils.math_utils_test import ( + QuadraticTest, +) # pylint: disable=unused-import +from functional_testing.fire.fuel_test import FuelTest # pylint: disable=unused-import add_cime_lib_to_path() -from CIME.utils import run_cmd_no_fail # pylint: disable=wrong-import-position,import-error,wrong-import-order +from CIME.utils import ( + run_cmd_no_fail, +) # pylint: disable=wrong-import-position,import-error,wrong-import-order # constants for this script _DEFAULT_CONFIG_FILE = "functional_tests.cfg" diff --git a/testing/unit_tests.cfg b/testing/unit_tests.cfg index 3e5ee536f4..179b924735 100644 --- a/testing/unit_tests.cfg +++ b/testing/unit_tests.cfg @@ -1,2 +1,5 @@ [fire_weather] test_dir = fates_fire_weather_utest + +[fire_fuel] +test_dir = fates_fire_fuel_utest diff --git a/testing/utils.py b/testing/utils.py index eaafe9f110..06fd74db0b 100644 --- a/testing/utils.py +++ b/testing/utils.py @@ -10,7 +10,9 @@ add_cime_lib_to_path() -from CIME.utils import run_cmd_no_fail # pylint: disable=wrong-import-position,import-error,wrong-import-order +from CIME.utils import ( + run_cmd_no_fail, +) # pylint: disable=wrong-import-position,import-error,wrong-import-order def round_up(num: float, decimals: int = 0) -> float: @@ -119,48 +121,6 @@ def get_color_palette(number: int) -> list: ] return colors[:number] -<<<<<<< HEAD - -def blank_plot(x_max, x_min, y_max, y_min, draw_horizontal_lines=False): - """Generate a blank plot with set attributes - - Args: - x_max (float): maximum x value - x_min (float): minimum x value - y_max (float): maximum y value - y_min (float): minimum y value - draw_horizontal_lines (bool, optional): whether or not to draw horizontal - lines across plot. Defaults to False. - """ - - plt.figure(figsize=(7, 5)) - axis = plt.subplot(111) - axis.spines["top"].set_visible(False) - axis.spines["bottom"].set_visible(False) - axis.spines["right"].set_visible(False) - axis.spines["left"].set_visible(False) - - axis.get_xaxis().tick_bottom() - axis.get_yaxis().tick_left() - - plt.xlim(0.0, x_max) - plt.ylim(0.0, y_max) - - plt.yticks(fontsize=10) - plt.xticks(fontsize=10) - - if draw_horizontal_lines: - inc = (int(y_max) - y_min)/20 - for i in range(0, 20): - plt.plot(range(math.floor(x_min), math.ceil(x_max)), - [0.0 + i*inc] * len(range(math.floor(x_min), math.ceil(x_max))), - "--", lw=0.5, color="black", alpha=0.3) - - plt.tick_params(bottom=False, top=False, left=False, right=False) - - return plt -||||||| 825579d0 -======= def config_to_dict(config_file: str) -> dict: @@ -297,4 +257,3 @@ def blank_plot( ) plt.tick_params(bottom=False, top=False, left=False, right=False) ->>>>>>> main From 7fc224251a876748cdd6bda6e4ea504f234121eb Mon Sep 17 00:00:00 2001 From: adrifoster Date: Wed, 30 Oct 2024 10:42:20 -0600 Subject: [PATCH 051/111] more updates --- fire/FatesFuelMod.F90 | 14 +++++++------- fire/SFMainMod.F90 | 6 +++--- main/FatesHistoryInterfaceMod.F90 | 2 +- testing/functional_testing/fire/FatesTestFuel.F90 | 2 +- .../fire/SyntheticFuelModels.F90 | 2 +- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/fire/FatesFuelMod.F90 b/fire/FatesFuelMod.F90 index 54182df66a..8aa8d00d74 100644 --- a/fire/FatesFuelMod.F90 +++ b/fire/FatesFuelMod.F90 @@ -19,7 +19,7 @@ module FatesFuelMod real(r8) :: frac_loading(num_fuel_classes) ! fractional loading of all fuel classes [0-1] real(r8) :: frac_burnt(num_fuel_classes) ! fraction of litter burnt by fire [0-1] real(r8) :: non_trunk_loading ! total fuel loading excluding trunks [kgC/m2] - real(r8) :: average_moisture ! weighted average of fuel moisture across non-trunk fuel classes [m3/m3] + real(r8) :: average_moisture_notrunks ! weighted average of fuel moisture across non-trunk fuel classes [m3/m3] real(r8) :: bulk_density_notrunks ! weighted average of bulk density across non-trunk fuel classes [kg/m3] real(r8) :: SAV_notrunks ! weighted average of surface area to volume ratio across non-trunk fuel classes [/cm] real(r8) :: MEF_notrunks ! weighted average of moisture of extinction across non-trunk fuel classes [m3/m3] @@ -52,7 +52,7 @@ subroutine Init(this) this%frac_burnt(1:num_fuel_classes) = 0.0_r8 this%effective_moisture(1:num_fuel_classes) = 0.0_r8 this%non_trunk_loading = 0.0_r8 - this%average_moisture = 0.0_r8 + this%average_moisture_notrunks = 0.0_r8 this%bulk_density_notrunks = 0.0_r8 this%SAV_notrunks = 0.0_r8 this%MEF_notrunks = 0.0_r8 @@ -92,8 +92,8 @@ subroutine Fuse(this, self_area, donor_area, donor_fuel) this%non_trunk_loading = this%non_trunk_loading*self_weight + & donor_fuel%non_trunk_loading*donor_weight - this%average_moisture = this%average_moisture*self_weight + & - donor_fuel%average_moisture*donor_weight + this%average_moisture_notrunks = this%average_moisture_notrunks*self_weight + & + donor_fuel%average_moisture_notrunks*donor_weight this%bulk_density_notrunks = this%bulk_density_notrunks*self_weight + & donor_fuel%bulk_density_notrunks*donor_weight this%SAV_notrunks = this%SAV_notrunks*self_weight + donor_fuel%SAV_notrunks*donor_weight @@ -211,7 +211,7 @@ subroutine UpdateFuelMoisture(this, sav_fuel, drying_ratio, fireWeatherClass) call endrun(msg=errMsg( __FILE__, __LINE__)) end select - this%average_moisture = 0.0_r8 + this%average_moisture_notrunks = 0.0_r8 this%MEF_notrunks = 0.0_r8 do i = 1, num_fuel_classes ! calculate moisture of extinction and fuel effective moisture @@ -220,14 +220,14 @@ subroutine UpdateFuelMoisture(this, sav_fuel, drying_ratio, fireWeatherClass) ! average fuel moisture and MEF across all fuel types except trunks [m3/m3] if (i /= fuel_classes%trunks()) then - this%average_moisture = this%average_moisture + this%frac_loading(i)*moisture(i) + this%average_moisture_notrunks = this%average_moisture_notrunks + this%frac_loading(i)*moisture(i) this%MEF_notrunks = this%MEF_notrunks + this%frac_loading(i)*moisture_of_extinction(i) end if end do else this%effective_moisture(1:num_fuel_classes) = 0.0_r8 - this%average_moisture = 0.0_r8 + this%average_moisture_notrunks = 0.0_r8 this%MEF_notrunks = 0.0_r8 end if diff --git a/fire/SFMainMod.F90 b/fire/SFMainMod.F90 index d4286e8566..97bbcb6d54 100644 --- a/fire/SFMainMod.F90 +++ b/fire/SFMainMod.F90 @@ -269,7 +269,7 @@ subroutine rate_of_spread (currentSite) end if if(write_sf == itrue)then - if ( hlm_masterproc == itrue ) write(fates_log(),*) 'average moisture',currentPatch%fuel%average_moisture + if ( hlm_masterproc == itrue ) write(fates_log(),*) 'average moisture',currentPatch%fuel%average_moisture_notrunks endif ! ---heat of pre-ignition--- @@ -277,7 +277,7 @@ subroutine rate_of_spread (currentSite) ! Rothermel EQ12= 250 Btu/lb + 1116 Btu/lb * average_moisture ! conversion of Rothermel (1972) EQ12 in BTU/lb to current kJ/kg ! q_ig in kJ/kg - q_ig = q_dry +2594.0_r8 * currentPatch%fuel%average_moisture + q_ig = q_dry +2594.0_r8 * currentPatch%fuel%average_moisture_notrunks ! ---effective heating number--- ! Equation A3 in Thonicke et al. 2010. @@ -322,7 +322,7 @@ subroutine rate_of_spread (currentSite) ! mw_weight = relative fuel moisture/fuel moisture of extinction ! average values for litter pools (dead leaves, twigs, small and large branches) plus grass - mw_weight = currentPatch%fuel%average_moisture/currentPatch%fuel%MEF_notrunks + mw_weight = currentPatch%fuel%average_moisture_notrunks/currentPatch%fuel%MEF_notrunks ! Equation in table A1 Thonicke et al. 2010. ! moist_damp is unitless diff --git a/main/FatesHistoryInterfaceMod.F90 b/main/FatesHistoryInterfaceMod.F90 index a04bbb7350..f6c166a53b 100644 --- a/main/FatesHistoryInterfaceMod.F90 +++ b/main/FatesHistoryInterfaceMod.F90 @@ -2685,7 +2685,7 @@ subroutine update_history_dyn1(this,nc,nsites,sites,bc_in) hio_fire_intensity_si(io_si) = hio_fire_intensity_si(io_si) + cpatch%FI * cpatch%area * AREA_INV * J_per_kJ hio_fire_area_si(io_si) = hio_fire_area_si(io_si) + cpatch%frac_burnt * cpatch%area * AREA_INV / sec_per_day hio_fire_fuel_bulkd_si(io_si) = hio_fire_fuel_bulkd_si(io_si) + cpatch%fuel%bulk_density * cpatch%area * AREA_INV - hio_fire_fuel_eff_moist_si(io_si) = hio_fire_fuel_eff_moist_si(io_si) + cpatch%fuel%average_moisture * cpatch%area * AREA_INV + hio_fire_fuel_eff_moist_si(io_si) = hio_fire_fuel_eff_moist_si(io_si) + cpatch%fuel%average_moisture_notrunks * cpatch%area * AREA_INV hio_fire_fuel_sav_si(io_si) = hio_fire_fuel_sav_si(io_si) + cpatch%fuel%SAV * cpatch%area * AREA_INV / m_per_cm hio_fire_fuel_mef_si(io_si) = hio_fire_fuel_mef_si(io_si) + cpatch%fuel%MEF * cpatch%area * AREA_INV hio_sum_fuel_si(io_si) = hio_sum_fuel_si(io_si) + cpatch%fuel%non_trunk_loading * cpatch%area * AREA_INV diff --git a/testing/functional_testing/fire/FatesTestFuel.F90 b/testing/functional_testing/fire/FatesTestFuel.F90 index 6684ba8ee1..42237e095e 100644 --- a/testing/functional_testing/fire/FatesTestFuel.F90 +++ b/testing/functional_testing/fire/FatesTestFuel.F90 @@ -111,7 +111,7 @@ program FatesTestFuel ! calculate fuel moisture [m3/m3] do f = 1, num_fuel_models call fuel(f)%UpdateFuelMoisture(SF_val_SAV, SF_val_drying_ratio, fireWeather) - fuel_moisture(i, f) = fuel(f)%average_moisture + fuel_moisture(i, f) = fuel(f)%average_moisture_notrunks end do end do diff --git a/testing/functional_testing/fire/SyntheticFuelModels.F90 b/testing/functional_testing/fire/SyntheticFuelModels.F90 index 2e54dd2192..219b4c5794 100644 --- a/testing/functional_testing/fire/SyntheticFuelModels.F90 +++ b/testing/functional_testing/fire/SyntheticFuelModels.F90 @@ -6,7 +6,7 @@ module SyntheticFuelModels private ! Fuel model numbers come from Scott and Burgen (2005) RMRS-GTR-153 - + integer, parameter, public, dimension(52) :: all_fuel_models = (/1, 2, 101, 102, 104, & 107, 121, 122, 3, 103, 105, 106, & 108, 109, 123, 124, 4, 5, 6, 141, & From a0dea3b51a2370ba13ce811d6c90b662f3c06833 Mon Sep 17 00:00:00 2001 From: Ryan Knox Date: Wed, 30 Oct 2024 13:27:02 -0400 Subject: [PATCH 052/111] Added respiration excess to sub-daily npp tracking by holding it like gpp does. Fixed some output units. Fixed carbon flux tracking when we bypass (ie st3 and satphen). --- biogeochem/EDCohortDynamicsMod.F90 | 5 ++- biogeochem/FatesCohortMod.F90 | 21 +++++----- biogeophys/EDAccumulateFluxesMod.F90 | 7 ---- biogeophys/FatesPlantRespPhotosynthMod.F90 | 36 +++++----------- main/EDMainMod.F90 | 49 +++++++++++++++++----- main/FatesHistoryInterfaceMod.F90 | 12 +++--- 6 files changed, 69 insertions(+), 61 deletions(-) diff --git a/biogeochem/EDCohortDynamicsMod.F90 b/biogeochem/EDCohortDynamicsMod.F90 index e362cfb96a..ce9691c2ac 100644 --- a/biogeochem/EDCohortDynamicsMod.F90 +++ b/biogeochem/EDCohortDynamicsMod.F90 @@ -1083,8 +1083,9 @@ subroutine fuse_cohorts(currentSite, currentPatch, bc_in) (currentCohort%n*currentCohort%gpp_acc_hold + & nextc%n*nextc%gpp_acc_hold)/newn - currentCohort%resp_excess = (currentCohort%n*currentCohort%resp_excess + & - nextc%n*nextc%resp_excess)/newn + currentCohort%resp_excess_hold = & + (currentCohort%n*currentCohort%resp_excess_hold + & + nextc%n*nextc%resp_excess_hold)/newn currentCohort%dmort = (currentCohort%n*currentCohort%dmort + & nextc%n*nextc%dmort)/newn diff --git a/biogeochem/FatesCohortMod.F90 b/biogeochem/FatesCohortMod.F90 index 64a4d929af..9d3b5522d8 100644 --- a/biogeochem/FatesCohortMod.F90 +++ b/biogeochem/FatesCohortMod.F90 @@ -205,11 +205,16 @@ module FatesCohortMod integer :: twostr_col ! The column index in the two-stream solution that this cohort is part of ! RESPIRATION COMPONENTS + real(r8) :: resp_excess_hold ! respiration of excess carbon [kgC/indiv/day] + ! note: this is flagged "hold" because it is calculated + ! at the end of the day (dynamics) but is used + ! on the following day (like growth respiration) + ! to aid in reporting a more accurate sub-daily + ! NEP + real(r8) :: rdark ! dark respiration [kgC/indiv/s] real(r8) :: resp_g_tstep ! growth respiration [kgC/indiv/timestep] - real(r8) :: resp_m ! maintenance respiration [kgC/indiv/timestep] real(r8) :: resp_m_unreduced ! diagnostic-only unreduced maintenance respiration [kgC/indiv/timestep] - real(r8) :: resp_excess ! respiration of excess carbon [kgC/indiv/day] real(r8) :: livestem_mr ! aboveground live stem maintenance respiration [kgC/indiv/s] real(r8) :: livecroot_mr ! belowground live stem maintenance respiration [kgC/indiv/s] real(r8) :: froot_mr ! live fine root maintenance respiration [kgC/indiv/s] @@ -415,9 +420,8 @@ subroutine NanValues(this) ! RESPIRATION COMPONENTS this%rdark = nan this%resp_g_tstep = nan - this%resp_m = nan this%resp_m_unreduced = nan - this%resp_excess = nan + this%resp_excess_hold = nan this%livestem_mr = nan this%livecroot_mr = nan this%froot_mr = nan @@ -493,6 +497,7 @@ subroutine ZeroValues(this) ! this%npp_acc_hold = nan ! this%resp_m_acc_hold = nan ! this%resp_g_acc_hold = nan + ! this%resp_excess_hold = nan this%c13disc_clm = 0._r8 this%c13disc_acc = 0._r8 @@ -522,9 +527,7 @@ subroutine ZeroValues(this) this%seed_prod = 0._r8 this%rdark = 0._r8 this%resp_g_tstep = 0._r8 - this%resp_m = 0._r8 this%resp_m_unreduced = 0._r8 - this%resp_excess = 0._r8 this%livestem_mr = 0._r8 this%livecroot_mr = 0._r8 this%froot_mr = 0._r8 @@ -751,9 +754,8 @@ subroutine Copy(this, copyCohort) ! RESPIRATION COMPONENTS copyCohort%rdark = this%rdark copyCohort%resp_g_tstep = this%resp_g_tstep - copyCohort%resp_m = this%resp_m copyCohort%resp_m_unreduced = this%resp_m_unreduced - copyCohort%resp_excess = this%resp_excess + copyCohort%resp_excess_hold = this%resp_excess_hold copyCohort%livestem_mr = this%livestem_mr copyCohort%livecroot_mr = this%livecroot_mr copyCohort%froot_mr = this%froot_mr @@ -886,7 +888,7 @@ subroutine InitPRTBoundaryConditions(this) call this%prt%RegisterBCIn(acnp_bc_in_id_cdamage, bc_ival=this%crowndamage) call this%prt%RegisterBCInOut(acnp_bc_inout_id_dbh, bc_rval=this%dbh) - call this%prt%RegisterBCInOut(acnp_bc_inout_id_resp_excess, bc_rval=this%resp_excess) + call this%prt%RegisterBCInOut(acnp_bc_inout_id_resp_excess, bc_rval=this%resp_excess_hold) call this%prt%RegisterBCInOut(acnp_bc_inout_id_l2fr, bc_rval=this%l2fr) call this%prt%RegisterBCInOut(acnp_bc_inout_id_cx_int, bc_rval=this%cx_int) call this%prt%RegisterBCInOut(acnp_bc_inout_id_emadcxdt, bc_rval=this%ema_dcxdt) @@ -1060,7 +1062,6 @@ subroutine Dump(this) write(fates_log(),*) 'cohort%resp_g_acc = ', this%resp_g_acc write(fates_log(),*) 'cohort%resp_g_acc_hold = ', this%resp_g_acc_hold write(fates_log(),*) 'cohort%rdark = ', this%rdark - write(fates_log(),*) 'cohort%resp_m = ', this%resp_m write(fates_log(),*) 'cohort%livestem_mr = ', this%livestem_mr write(fates_log(),*) 'cohort%livecroot_mr = ', this%livecroot_mr write(fates_log(),*) 'cohort%froot_mr = ', this%froot_mr diff --git a/biogeophys/EDAccumulateFluxesMod.F90 b/biogeophys/EDAccumulateFluxesMod.F90 index d74c965e31..5bab6b5191 100644 --- a/biogeophys/EDAccumulateFluxesMod.F90 +++ b/biogeophys/EDAccumulateFluxesMod.F90 @@ -80,13 +80,6 @@ subroutine AccumulateFluxes_ED(nsites, sites, bc_in, bc_out, dt_time) ! Accumulate fluxes from hourly to daily values. ! _tstep fluxes are KgC/indiv/timestep _acc are KgC/indiv/day - if ( debug ) then - - write(fates_log(),*) 'EDAccumFlux 66 ',ccohort%gpp_tstep - write(fates_log(),*) 'EDAccumFlux 67 ',ccohort%resp_m_tstep - - endif - ccohort%gpp_acc = ccohort%gpp_acc + ccohort%gpp_tstep ccohort%resp_m_acc = ccohort%resp_m_acc + ccohort%resp_m_tstep diff --git a/biogeophys/FatesPlantRespPhotosynthMod.F90 b/biogeophys/FatesPlantRespPhotosynthMod.F90 index eed9458993..3578a92e3b 100644 --- a/biogeophys/FatesPlantRespPhotosynthMod.F90 +++ b/biogeophys/FatesPlantRespPhotosynthMod.F90 @@ -738,7 +738,6 @@ subroutine FatesPlantRespPhotosynthDrive (nsites, sites,bc_in,bc_out,dtime) currentCohort%resp_m_tstep = 0.0_r8 currentCohort%gpp_tstep = 0.0_r8 currentCohort%rdark = 0.0_r8 - currentCohort%resp_m = 0.0_r8 currentCohort%ts_net_uptake = 0.0_r8 currentCohort%c13disc_clm = 0.0_r8 @@ -963,39 +962,24 @@ subroutine FatesPlantRespPhotosynthDrive (nsites, sites,bc_in,bc_out,dtime) ! calcualate some fluxes that are sums and nets of the base fluxes ! ------------------------------------------------------------------ - if ( debug ) write(fates_log(),*) 'EDPhoto 904 ', currentCohort%resp_m - if ( debug ) write(fates_log(),*) 'EDPhoto 905 ', currentCohort%rdark - if ( debug ) write(fates_log(),*) 'EDPhoto 906 ', currentCohort%livestem_mr - if ( debug ) write(fates_log(),*) 'EDPhoto 907 ', currentCohort%livecroot_mr - if ( debug ) write(fates_log(),*) 'EDPhoto 908 ', currentCohort%froot_mr - - - ! add on whole plant respiration values in kgC/indiv/s-1 - currentCohort%resp_m = currentCohort%livestem_mr + & + currentCohort%resp_m_tstep = currentCohort%livestem_mr + & currentCohort%livecroot_mr + & - currentCohort%froot_mr - + currentCohort%froot_mr + & + currentCohort%rdark + ! no drought response right now.. something like: - ! resp_m = resp_m * (1.0_r8 - currentPatch%btran_ft(currentCohort%pft) * & + ! resp_m_tstep = resp_m_tstep * (1.0_r8 - currentPatch%btran_ft(currentCohort%pft) * & ! EDPftvarcon_inst%resp_drought_response(ft)) - currentCohort%resp_m = currentCohort%resp_m + currentCohort%rdark - - ! save as a diagnostic the un-throttled maintenance respiration to be able to know how strong this is - currentCohort%resp_m_unreduced = currentCohort%resp_m / maintresp_reduction_factor - ! convert from kgC/indiv/s to kgC/indiv/timestep - currentCohort%resp_m = currentCohort%resp_m * dtime - currentCohort%resp_m_tstep = currentCohort%resp_m ! these two things are the same. we should get rid of one them + currentCohort%resp_m_tstep = currentCohort%resp_m_tstep * dtime currentCohort%gpp_tstep = currentCohort%gpp_tstep * dtime currentCohort%ts_net_uptake = currentCohort%ts_net_uptake * dtime - - if ( debug ) write(fates_log(),*) 'EDPhoto 911 ', currentCohort%gpp_tstep - if ( debug ) write(fates_log(),*) 'EDPhoto 912 ', currentCohort%resp_m_tstep - if ( debug ) write(fates_log(),*) 'EDPhoto 913 ', currentCohort%resp_m - - + + ! save as a diagnostic the un-throttled maintenance respiration to be able to know how strong this is + currentCohort%resp_m_unreduced = currentCohort%resp_m_tstep / maintresp_reduction_factor + ! Accumulate the combined conductance (stomatal+leaf boundary layer) ! Note that currentCohort%g_sb_laweight is weighted by the leaf area ! of each cohort and has units of [m/s] * [m2 leaf] diff --git a/main/EDMainMod.F90 b/main/EDMainMod.F90 index 807deb0496..5aef853476 100644 --- a/main/EDMainMod.F90 +++ b/main/EDMainMod.F90 @@ -475,7 +475,7 @@ subroutine ed_integrate_state_variables(currentSite, bc_in, bc_out ) currentPatch%btran_ft, mean_temp, & currentPatch%land_use_label, & currentPatch%age_since_anthro_disturbance, frac_site_primary, & - harvestable_forest_c, harvest_tag) + harvestable_forest_c, harvest_tag) ! ----------------------------------------------------------------------------- ! Apply Plant Allocation and Reactive Transport @@ -532,14 +532,11 @@ subroutine ed_integrate_state_variables(currentSite, bc_in, bc_out ) endif ! calculate the npp as the difference between gpp and autotrophic respiration + ! (NPP is also updated if there is any excess respiration from nutrient limitations) currentCohort%npp_acc = currentCohort%gpp_acc - (currentCohort%resp_m_acc + currentCohort%resp_g_acc) currentCohort%npp_acc_hold = currentCohort%gpp_acc_hold - (currentCohort%resp_m_acc_hold + currentCohort%resp_g_acc_hold) - ! Passing gpp_acc_hold to HLM - bc_out%gpp_site = bc_out%gpp_site + currentCohort%gpp_acc_hold * & - AREA_INV * currentCohort%n / hlm_days_per_year / sec_per_day - bc_out%ar_site = bc_out%ar_site + (currentCohort%resp_m_acc_hold + currentCohort%resp_g_acc_hold) * & - AREA_INV * currentCohort%n / hlm_days_per_year / sec_per_day + ! Conduct Maintenance Turnover (parteh) if(debug) call currentCohort%prt%CheckMassConservation(ft,3) @@ -567,7 +564,7 @@ subroutine ed_integrate_state_variables(currentSite, bc_in, bc_out ) currentCohort%daily_n_gain = currentCohort%daily_nh4_uptake + & currentCohort%daily_no3_uptake + currentCohort%sym_nfix_daily - currentCohort%resp_excess = 0._r8 + currentCohort%resp_excess_hold = 0._r8 end if if_not_newlyrecovered @@ -616,11 +613,28 @@ subroutine ed_integrate_state_variables(currentSite, bc_in, bc_out ) end if call currentCohort%prt%DailyPRT(phase=3) + + ! If nutrients are limiting growth, and carbon continues + ! to accumulate beyond the plant's storage capacity, then + ! it will burn carbon as what we call "excess respiration" + ! We must subtract this term from NPP + + currentCohort%npp_acc_hold = currentCohort%npp_acc_hold - currentCohort%resp_excess_hold + + ! Passing gpp_acc_hold to HLM + bc_out%gpp_site = bc_out%gpp_site + currentCohort%gpp_acc_hold * & + AREA_INV * currentCohort%n / hlm_days_per_year / sec_per_day + bc_out%ar_site = bc_out%ar_site + (currentCohort%resp_m_acc_hold + & + currentCohort%resp_g_acc_hold + currentCohort%resp_excess_hold) * & + AREA_INV * currentCohort%n / hlm_days_per_year / sec_per_day + ! Update the mass balance tracking for the daily nutrient uptake flux ! Then zero out the daily uptakes, they have been used ! ----------------------------------------------------------------------------- + + call EffluxIntoLitterPools(currentSite, currentPatch, currentCohort, bc_in ) @@ -647,7 +661,7 @@ subroutine ed_integrate_state_variables(currentSite, bc_in, bc_out ) currentCohort%gpp_acc * currentCohort%n site_cmass%aresp_acc = site_cmass%aresp_acc + & - (currentCohort%resp_m_acc+currentCohort%resp_g_acc+currentCohort%resp_excess) * & + (currentCohort%resp_m_acc+currentCohort%resp_g_acc+currentCohort%resp_excess_hold) * & currentCohort%n call currentCohort%prt%CheckMassConservation(ft,5) @@ -1004,7 +1018,7 @@ subroutine TotalBalanceCheck (currentSite, call_index ) write(fates_log(),*) 'leaf: ',leaf_m,' structure: ',struct_m,' store: ',store_m write(fates_log(),*) 'fineroot: ',fnrt_m,' repro: ',repro_m,' sapwood: ',sapw_m write(fates_log(),*) 'num plant: ',currentCohort%n - write(fates_log(),*) 'resp excess: ',currentCohort%resp_excess*currentCohort%n + write(fates_log(),*) 'resp excess: ',currentCohort%resp_excess_hold*currentCohort%n if(element_list(el).eq.nitrogen_element) then write(fates_log(),*) 'NH4 uptake: ',currentCohort%daily_nh4_uptake*currentCohort%n @@ -1059,6 +1073,9 @@ subroutine bypass_dynamics(currentSite) type(fates_patch_type), pointer :: currentPatch type(fates_cohort_type), pointer :: currentCohort + bc_out%gpp_site = 0._r8 + bc_out%ar_site = 0._r8 + currentPatch => currentSite%youngest_patch do while(associated(currentPatch)) currentCohort => currentPatch%shortest @@ -1066,11 +1083,16 @@ subroutine bypass_dynamics(currentSite) currentCohort%isnew=.false. + + currentCohort%resp_g_acc = prt_params%grperc(ft) * & + max(0._r8,(currentCohort%gpp_acc - currentCohort%resp_m_acc)) + currentCohort%npp_acc_hold = currentCohort%npp_acc * real(hlm_days_per_year,r8) currentCohort%gpp_acc_hold = currentCohort%gpp_acc * real(hlm_days_per_year,r8) currentCohort%resp_g_acc_hold = currentCohort%resp_g_acc * real(hlm_days_per_year,r8) currentCohort%resp_m_acc_hold = currentCohort%resp_m_acc * real(hlm_days_per_year,r8) - + + currentCohort%resp_excess_hold = 0._r8 currentCohort%npp_acc = 0.0_r8 currentCohort%gpp_acc = 0.0_r8 currentCohort%resp_g_acc = 0.0_r8 @@ -1097,6 +1119,13 @@ subroutine bypass_dynamics(currentSite) ! as they should just be zero, no uptake ! in ST3 mode. + ! Passing + bc_out%gpp_site = bc_out%gpp_site + currentCohort%gpp_acc_hold * & + AREA_INV * currentCohort%n / hlm_days_per_year / sec_per_day + bc_out%ar_site = bc_out%ar_site + (currentCohort%resp_m_acc_hold + & + currentCohort%resp_g_acc_hold + currentCohort%resp_excess_hold) * & + AREA_INV * currentCohort%n / hlm_days_per_year / sec_per_day + currentCohort => currentCohort%taller enddo currentPatch => currentPatch%older diff --git a/main/FatesHistoryInterfaceMod.F90 b/main/FatesHistoryInterfaceMod.F90 index a1b1010445..cc6a61c157 100644 --- a/main/FatesHistoryInterfaceMod.F90 +++ b/main/FatesHistoryInterfaceMod.F90 @@ -5024,14 +5024,14 @@ subroutine update_history_hifrq1(this,nc,nsites,sites,bc_in,bc_out,dt_tstep) ! Net Ecosystem Production [kgC/m2/s]. Use yesterday's growth respiration hio_nep_si(io_si) = hio_nep_si(io_si) + & - (ccohort%gpp_tstep-ccohort%resp_m) * n_perm2 * dt_tstep_inv - & + (ccohort%gpp_tstep-ccohort%resp_m_tstep) * n_perm2 * dt_tstep_inv - & ccohort%resp_g_acc_hold * n_perm2 / days_per_year / sec_per_day hio_gpp_si(io_si) = hio_gpp_si(io_si) + & ccohort%gpp_tstep * n_perm2 * dt_tstep_inv hio_maint_resp_si(io_si) = hio_maint_resp_si(io_si) + & - ccohort%resp_m * n_perm2 * dt_tstep_inv + ccohort%resp_m_tstep * n_perm2 * dt_tstep_inv hio_maint_resp_unreduced_si(io_si) = hio_maint_resp_unreduced_si(io_si) + & ccohort%resp_m_unreduced * n_perm2 * dt_tstep_inv @@ -5042,7 +5042,7 @@ subroutine update_history_hifrq1(this,nc,nsites,sites,bc_in,bc_out,dt_tstep) ccohort%gpp_tstep * n_perm2 * dt_tstep_inv hio_maint_resp_secondary_si(io_si) = hio_maint_resp_secondary_si(io_si) + & - ccohort%resp_m * n_perm2 * dt_tstep_inv + ccohort%resp_m_tstep * n_perm2 * dt_tstep_inv end if ! Maintenance respiration of different organs @@ -5243,7 +5243,7 @@ subroutine update_history_hifrq2(this,nc,nsites,sites,bc_in,bc_out,dt_tstep) ! Maint AR (kgC/m2/s) hio_ar_maint_si_scpf(io_si,scpf) = hio_ar_maint_si_scpf(io_si,scpf) + & - (ccohort%resp_m*dt_tstep_inv) * n_perm2 + (ccohort%resp_m_tstep*dt_tstep_inv) * n_perm2 ! Maintenance AR partition variables are stored as rates (kgC/plant/s) ! (kgC/m2/s) = (kgC/plant/s) * (plant/m2) @@ -5281,7 +5281,7 @@ subroutine update_history_hifrq2(this,nc,nsites,sites,bc_in,bc_out,dt_tstep) hio_resp_g_canopy_si_scls(io_si,scls) = hio_resp_g_canopy_si_scls(io_si,scls) + & resp_g * ccohort%n * dt_tstep_inv * ha_per_m2 hio_resp_m_canopy_si_scls(io_si,scls) = hio_resp_m_canopy_si_scls(io_si,scls) + & - ccohort%resp_m * ccohort%n * dt_tstep_inv * ha_per_m2 + ccohort%resp_m_tstep * ccohort%n * dt_tstep_inv * ha_per_m2 else ! size-resolved respiration fluxes are in kg C / m2 / s @@ -5296,7 +5296,7 @@ subroutine update_history_hifrq2(this,nc,nsites,sites,bc_in,bc_out,dt_tstep) hio_resp_g_understory_si_scls(io_si,scls) = hio_resp_g_understory_si_scls(io_si,scls) + & resp_g * ccohort%n * dt_tstep_inv * ha_per_m2 hio_resp_m_understory_si_scls(io_si,scls) = hio_resp_m_understory_si_scls(io_si,scls) + & - ccohort%resp_m * ccohort%n * dt_tstep_inv * ha_per_m2 + ccohort%resp_m_tstep * ccohort%n * dt_tstep_inv * ha_per_m2 endif end associate endif From f5a7caad435700def48cf5ff8ac7ba3853e9d8be Mon Sep 17 00:00:00 2001 From: Ryan Knox Date: Wed, 30 Oct 2024 14:04:24 -0400 Subject: [PATCH 053/111] removing resp_g_acc --- biogeochem/FatesCohortMod.F90 | 4 ---- main/EDMainMod.F90 | 27 +++++++++++++-------------- main/FatesHistoryInterfaceMod.F90 | 28 ++++++++++++++-------------- 3 files changed, 27 insertions(+), 32 deletions(-) diff --git a/biogeochem/FatesCohortMod.F90 b/biogeochem/FatesCohortMod.F90 index 9d3b5522d8..495b1818cf 100644 --- a/biogeochem/FatesCohortMod.F90 +++ b/biogeochem/FatesCohortMod.F90 @@ -213,7 +213,6 @@ module FatesCohortMod ! NEP real(r8) :: rdark ! dark respiration [kgC/indiv/s] - real(r8) :: resp_g_tstep ! growth respiration [kgC/indiv/timestep] real(r8) :: resp_m_unreduced ! diagnostic-only unreduced maintenance respiration [kgC/indiv/timestep] real(r8) :: livestem_mr ! aboveground live stem maintenance respiration [kgC/indiv/s] real(r8) :: livecroot_mr ! belowground live stem maintenance respiration [kgC/indiv/s] @@ -419,7 +418,6 @@ subroutine NanValues(this) ! RESPIRATION COMPONENTS this%rdark = nan - this%resp_g_tstep = nan this%resp_m_unreduced = nan this%resp_excess_hold = nan this%livestem_mr = nan @@ -526,7 +524,6 @@ subroutine ZeroValues(this) this%daily_p_demand = -9._r8 this%seed_prod = 0._r8 this%rdark = 0._r8 - this%resp_g_tstep = 0._r8 this%resp_m_unreduced = 0._r8 this%livestem_mr = 0._r8 this%livecroot_mr = 0._r8 @@ -753,7 +750,6 @@ subroutine Copy(this, copyCohort) ! RESPIRATION COMPONENTS copyCohort%rdark = this%rdark - copyCohort%resp_g_tstep = this%resp_g_tstep copyCohort%resp_m_unreduced = this%resp_m_unreduced copyCohort%resp_excess_hold = this%resp_excess_hold copyCohort%livestem_mr = this%livestem_mr diff --git a/main/EDMainMod.F90 b/main/EDMainMod.F90 index 5aef853476..d49d7c40d1 100644 --- a/main/EDMainMod.F90 +++ b/main/EDMainMod.F90 @@ -521,21 +521,22 @@ subroutine ed_integrate_state_variables(currentSite, bc_in, bc_out ) ! at this point we have the info we need to calculate growth respiration ! as a "tax" on the difference between daily GPP and daily maintenance respiration if (hlm_use_ed_prescribed_phys .eq. ifalse) then - currentCohort%resp_g_acc = prt_params%grperc(ft) * & - max(0._r8,(currentCohort%gpp_acc - currentCohort%resp_m_acc)) - currentCohort%resp_g_acc_hold = currentCohort%resp_g_acc * real(hlm_days_per_year,r8) + + currentCohort%resp_g_acc_hold = prt_params%grperc(ft) * & + max(0._r8,(currentCohort%gpp_acc - currentCohort%resp_m_acc)) * real(hlm_days_per_year,r8) + else ! set growth respiration to zero in prescribed physiology mode, ! that way the npp_acc vars will be set to the nominal gpp values set above. - currentCohort%resp_g_acc = 0._r8 currentCohort%resp_g_acc_hold = 0._r8 endif ! calculate the npp as the difference between gpp and autotrophic respiration ! (NPP is also updated if there is any excess respiration from nutrient limitations) - currentCohort%npp_acc = currentCohort%gpp_acc - (currentCohort%resp_m_acc + currentCohort%resp_g_acc) - currentCohort%npp_acc_hold = currentCohort%gpp_acc_hold - (currentCohort%resp_m_acc_hold + currentCohort%resp_g_acc_hold) - + currentCohort%npp_acc = currentCohort%gpp_acc - & + (currentCohort%resp_m_acc + currentCohort%resp_g_acc_hold/real(hlm_days_per_year,r8)) + currentCohort%npp_acc_hold = currentCohort%gpp_acc_hold - & + (currentCohort%resp_m_acc_hold + currentCohort%resp_g_acc_hold) ! Conduct Maintenance Turnover (parteh) @@ -661,8 +662,9 @@ subroutine ed_integrate_state_variables(currentSite, bc_in, bc_out ) currentCohort%gpp_acc * currentCohort%n site_cmass%aresp_acc = site_cmass%aresp_acc + & - (currentCohort%resp_m_acc+currentCohort%resp_g_acc+currentCohort%resp_excess_hold) * & - currentCohort%n + currentCohort%resp_m_acc*currentCohort%n + & + (currentCohort%resp_g_acc_hold+currentCohort%resp_excess_hold) * & + currentCohort%n/real( hlm_days_per_year,r8) call currentCohort%prt%CheckMassConservation(ft,5) @@ -1083,19 +1085,16 @@ subroutine bypass_dynamics(currentSite) currentCohort%isnew=.false. - - currentCohort%resp_g_acc = prt_params%grperc(ft) * & - max(0._r8,(currentCohort%gpp_acc - currentCohort%resp_m_acc)) + currentCohort%resp_g_acc_hold = prt_params%grperc(ft) * & + max(0._r8,(currentCohort%gpp_acc - currentCohort%resp_m_acc))*real(hlm_days_per_year,r8) currentCohort%npp_acc_hold = currentCohort%npp_acc * real(hlm_days_per_year,r8) currentCohort%gpp_acc_hold = currentCohort%gpp_acc * real(hlm_days_per_year,r8) - currentCohort%resp_g_acc_hold = currentCohort%resp_g_acc * real(hlm_days_per_year,r8) currentCohort%resp_m_acc_hold = currentCohort%resp_m_acc * real(hlm_days_per_year,r8) currentCohort%resp_excess_hold = 0._r8 currentCohort%npp_acc = 0.0_r8 currentCohort%gpp_acc = 0.0_r8 - currentCohort%resp_g_acc = 0.0_r8 currentCohort%resp_m_acc = 0.0_r8 ! No need to set the "net_art" terms to zero diff --git a/main/FatesHistoryInterfaceMod.F90 b/main/FatesHistoryInterfaceMod.F90 index cc6a61c157..d8ed8987ba 100644 --- a/main/FatesHistoryInterfaceMod.F90 +++ b/main/FatesHistoryInterfaceMod.F90 @@ -2822,7 +2822,8 @@ subroutine update_history_dyn1(this,nc,nsites,sites,bc_in) ccohort%resp_g_acc_hold * n_perm2 / days_per_year / sec_per_day hio_aresp_si(io_si) = hio_aresp_si(io_si) + & - (ccohort%resp_g_acc_hold + ccohort%resp_m_acc_hold) * n_perm2 / days_per_year / sec_per_day + (ccohort%resp_g_acc_hold + ccohort%resp_m_acc_hold + & + ccohort%resp_excess_hold) * n_perm2 / days_per_year / sec_per_day ! Turnover pools [kgC/day] * [day/yr] = [kgC/yr] sapw_m_turnover = ccohort%prt%GetTurnover(sapw_organ, carbon12_element) * days_per_year @@ -3934,7 +3935,8 @@ subroutine update_history_dyn2(this,nc,nsites,sites,bc_in) hio_gpp_canopy_si_scpf(io_si,scpf) = hio_gpp_canopy_si_scpf(io_si,scpf) + & n_perm2*ccohort%gpp_acc_hold / days_per_year / sec_per_day hio_ar_canopy_si_scpf(io_si,scpf) = hio_ar_canopy_si_scpf(io_si,scpf) + & - n_perm2*(ccohort%resp_m_acc_hold + ccohort%resp_g_acc_hold) / days_per_year / sec_per_day + n_perm2*(ccohort%resp_m_acc_hold + ccohort%resp_g_acc_hold + & + ccohort%resp_excess_hold) / days_per_year / sec_per_day ! growth increment hio_ddbh_canopy_si_scpf(io_si,scpf) = hio_ddbh_canopy_si_scpf(io_si,scpf) + & ccohort%ddbhdt*ccohort%n * m_per_cm / m2_per_ha @@ -4069,7 +4071,8 @@ subroutine update_history_dyn2(this,nc,nsites,sites,bc_in) hio_gpp_understory_si_scpf(io_si,scpf) = hio_gpp_understory_si_scpf(io_si,scpf) + & n_perm2*ccohort%gpp_acc_hold / days_per_year / sec_per_day hio_ar_understory_si_scpf(io_si,scpf) = hio_ar_understory_si_scpf(io_si,scpf) + & - n_perm2*(ccohort%resp_m_acc_hold + ccohort%resp_g_acc_hold) / days_per_year / sec_per_day + n_perm2*(ccohort%resp_m_acc_hold + ccohort%resp_g_acc_hold + & + ccohort%resp_excess_hold) / days_per_year / sec_per_day ! growth increment hio_ddbh_understory_si_scpf(io_si,scpf) = hio_ddbh_understory_si_scpf(io_si,scpf) + & @@ -5025,7 +5028,7 @@ subroutine update_history_hifrq1(this,nc,nsites,sites,bc_in,bc_out,dt_tstep) ! Net Ecosystem Production [kgC/m2/s]. Use yesterday's growth respiration hio_nep_si(io_si) = hio_nep_si(io_si) + & (ccohort%gpp_tstep-ccohort%resp_m_tstep) * n_perm2 * dt_tstep_inv - & - ccohort%resp_g_acc_hold * n_perm2 / days_per_year / sec_per_day + (ccohort%resp_g_acc_hold+ccohort%resp_excess_hold) * n_perm2 / days_per_year / sec_per_day hio_gpp_si(io_si) = hio_gpp_si(io_si) + & ccohort%gpp_tstep * n_perm2 * dt_tstep_inv @@ -5112,14 +5115,12 @@ subroutine update_history_hifrq2(this,nc,nsites,sites,bc_in,bc_out,dt_tstep) integer :: ivar ! index of IO variable object vector integer :: ft ! functional type index real(r8) :: n_density ! individual of cohort per m2. - real(r8) :: resp_g ! growth respiration per timestep [kgC/indiv/step] - real(r8) :: npp ! npp for this time-step (adjusted for g resp) [kgC/indiv/step] real(r8) :: n_perm2 ! individuals per m2 for the whole column real(r8) :: patch_area_by_age(nlevage) ! patch area in each bin for normalizing purposes real(r8) :: canopy_area_by_age(nlevage) ! canopy area in each bin for normalizing purposes real(r8) :: site_area_veg_inv ! 1/area of the site that is not bare-ground integer :: ipa2 ! patch incrementer - integer :: clllpf_indx, cnlf_indx, ipft, ican, ileaf ! more iterators and indices + integer :: clllpf_indx, cnlf_indx, ipft, ican, ileaf ! more iterators and indices real(r8) :: clllpf_area ! area footprint (m2) for the current cl x ll x pft bin real(r8) :: clll_area ! area footprint (m2) for the cl x ll bin (ie adds up pfts in parallel) real(r8) :: cl_area ! total weight of all ll x pft bins in the canopy layer @@ -5211,8 +5212,6 @@ subroutine update_history_hifrq2(this,nc,nsites,sites,bc_in,bc_out,dt_tstep) canopy_area_by_age(cpatch%age_class) = & canopy_area_by_age(cpatch%age_class) + cpatch%total_canopy_area - - ! Canopy resitance terms hio_c_stomata_si_age(io_si,cpatch%age_class) = & hio_c_stomata_si_age(io_si,cpatch%age_class) + & @@ -5233,13 +5232,14 @@ subroutine update_history_hifrq2(this,nc,nsites,sites,bc_in,bc_out,dt_tstep) associate( scpf => ccohort%size_by_pft_class, & scls => ccohort%size_class ) - ! Total AR (kgC/m2/s) = (kgC/plant/step) / (s/step) * (plant/m2) ! CDK: this should be daily + ! Total AR (kgC/m2/s) = (kgC/plant/step) / (s/step) * (plant/m2) hio_ar_si_scpf(io_si,scpf) = hio_ar_si_scpf(io_si,scpf) + & - (ccohort%resp_m_tstep*dt_tstep_inv) * n_perm2 + (ccohort%resp_m_tstep*dt_tstep_inv) * n_perm2 + & + ccohort%resp_g_acc_hold * n_perm2 / days_per_year / sec_per_day ! Growth AR (kgC/m2/s) ! CDK: this should be daily hio_ar_grow_si_scpf(io_si,scpf) = hio_ar_grow_si_scpf(io_si,scpf) + & - (ccohort%resp_g_tstep*dt_tstep_inv) * n_perm2 + ccohort%resp_g_acc_hold * n_perm2 / days_per_year / sec_per_day ! Maint AR (kgC/m2/s) hio_ar_maint_si_scpf(io_si,scpf) = hio_ar_maint_si_scpf(io_si,scpf) + & @@ -5279,7 +5279,7 @@ subroutine update_history_hifrq2(this,nc,nsites,sites,bc_in,bc_out,dt_tstep) hio_froot_mr_canopy_si_scls(io_si,scls) = hio_froot_mr_canopy_si_scls(io_si,scls) + & ccohort%froot_mr * ccohort%n * ha_per_m2 hio_resp_g_canopy_si_scls(io_si,scls) = hio_resp_g_canopy_si_scls(io_si,scls) + & - resp_g * ccohort%n * dt_tstep_inv * ha_per_m2 + ccohort%resp_g_acc_hold * n_perm2 / days_per_year / sec_per_day hio_resp_m_canopy_si_scls(io_si,scls) = hio_resp_m_canopy_si_scls(io_si,scls) + & ccohort%resp_m_tstep * ccohort%n * dt_tstep_inv * ha_per_m2 else @@ -5294,7 +5294,7 @@ subroutine update_history_hifrq2(this,nc,nsites,sites,bc_in,bc_out,dt_tstep) hio_froot_mr_understory_si_scls(io_si,scls) = hio_froot_mr_understory_si_scls(io_si,scls) + & ccohort%froot_mr * ccohort%n * ha_per_m2 hio_resp_g_understory_si_scls(io_si,scls) = hio_resp_g_understory_si_scls(io_si,scls) + & - resp_g * ccohort%n * dt_tstep_inv * ha_per_m2 + ccohort%resp_g_acc_hold * n_perm2 / days_per_year / sec_per_day hio_resp_m_understory_si_scls(io_si,scls) = hio_resp_m_understory_si_scls(io_si,scls) + & ccohort%resp_m_tstep * ccohort%n * dt_tstep_inv * ha_per_m2 endif From 1d520346dfda16a5a9ef61391829935f6c12cc73 Mon Sep 17 00:00:00 2001 From: Ryan Knox Date: Wed, 30 Oct 2024 15:02:42 -0400 Subject: [PATCH 054/111] removing resp_g_acc, it is unnecessary --- biogeochem/EDCohortDynamicsMod.F90 | 2 -- biogeochem/FatesCohortMod.F90 | 6 ------ main/FatesRestartInterfaceMod.F90 | 10 ---------- 3 files changed, 18 deletions(-) diff --git a/biogeochem/EDCohortDynamicsMod.F90 b/biogeochem/EDCohortDynamicsMod.F90 index ce9691c2ac..e8597397ea 100644 --- a/biogeochem/EDCohortDynamicsMod.F90 +++ b/biogeochem/EDCohortDynamicsMod.F90 @@ -1071,8 +1071,6 @@ subroutine fuse_cohorts(currentSite, currentPatch, bc_in) currentCohort%resp_m_acc_hold = & (currentCohort%n*currentCohort%resp_m_acc_hold + & nextc%n*nextc%resp_m_acc_hold)/newn - currentCohort%resp_g_acc = (currentCohort%n*currentCohort%resp_g_acc + & - nextc%n*nextc%resp_g_acc)/newn currentCohort%resp_g_acc_hold = & (currentCohort%n*currentCohort%resp_g_acc_hold + & nextc%n*nextc%resp_g_acc_hold)/newn diff --git a/biogeochem/FatesCohortMod.F90 b/biogeochem/FatesCohortMod.F90 index 495b1818cf..0b359fe2c7 100644 --- a/biogeochem/FatesCohortMod.F90 +++ b/biogeochem/FatesCohortMod.F90 @@ -148,8 +148,6 @@ module FatesCohortMod real(r8) :: resp_m_tstep ! Maintenance respiration (see above *) real(r8) :: resp_m_acc real(r8) :: resp_m_acc_hold - - real(r8) :: resp_g_acc ! Growth respication can only be calculated at the daily timestep real(r8) :: resp_g_acc_hold real(r8) :: c13disc_clm ! carbon 13 discrimination in new synthesized carbon at each indiv/timestep [ppm] @@ -387,7 +385,6 @@ subroutine NanValues(this) this%resp_m_tstep = nan this%resp_m_acc = nan this%resp_m_acc_hold = nan - this%resp_g_acc = nan this%resp_g_acc_hold = nan this%c13disc_clm = nan this%c13disc_acc = nan @@ -486,7 +483,6 @@ subroutine ZeroValues(this) this%npp_acc = 0._r8 this%resp_m_tstep = 0._r8 this%resp_m_acc = 0._r8 - this%resp_g_acc = 0._r8 ! do not zero these, they are not built ! so more appropriate to leave unzerod @@ -716,7 +712,6 @@ subroutine Copy(this, copyCohort) copyCohort%resp_m_tstep = this%resp_m_tstep copyCohort%resp_m_acc = this%resp_m_acc copyCohort%resp_m_acc_hold = this%resp_m_acc_hold - copyCohort%resp_g_acc = this%resp_g_acc copyCohort%resp_g_acc_hold = this%resp_g_acc_hold copyCohort%c13disc_clm = this%c13disc_clm copyCohort%c13disc_acc = this%c13disc_acc @@ -1055,7 +1050,6 @@ subroutine Dump(this) write(fates_log(),*) 'cohort%resp_m_tstep = ', this%resp_m_tstep write(fates_log(),*) 'cohort%resp_m_acc = ', this%resp_m_acc write(fates_log(),*) 'cohort%resp_m_acc_hold = ', this%resp_m_acc_hold - write(fates_log(),*) 'cohort%resp_g_acc = ', this%resp_g_acc write(fates_log(),*) 'cohort%resp_g_acc_hold = ', this%resp_g_acc_hold write(fates_log(),*) 'cohort%rdark = ', this%rdark write(fates_log(),*) 'cohort%livestem_mr = ', this%livestem_mr diff --git a/main/FatesRestartInterfaceMod.F90 b/main/FatesRestartInterfaceMod.F90 index 3300d2297a..67f7be996e 100644 --- a/main/FatesRestartInterfaceMod.F90 +++ b/main/FatesRestartInterfaceMod.F90 @@ -133,7 +133,6 @@ module FatesRestartInterfaceMod integer :: ir_gpp_acc_co integer :: ir_npp_acc_co integer :: ir_resp_m_acc_co - integer :: ir_resp_g_acc_co integer :: ir_gpp_acc_hold_co integer :: ir_npp_acc_hold_co integer :: ir_resp_m_acc_hold_co @@ -860,11 +859,6 @@ subroutine define_restart_vars(this, initialize_variables) units='kgC/indiv', flushval = flushzero, & hlms='CLM:ALM', initialize=initialize_variables, ivar=ivar, index = ir_resp_m_acc_co ) - call this%set_restart_var(vname='fates_resp_g_acc', vtype=cohort_r8, & - long_name='ed cohort - accumulated growth respiration over dynamics step', & - units='kgC/indiv', flushval = flushzero, & - hlms='CLM:ALM', initialize=initialize_variables, ivar=ivar, index = ir_resp_g_acc_co ) - call this%set_restart_var(vname='fates_gpp_acc_hold', vtype=cohort_r8, & long_name='ed cohort - current step gpp', & units='kgC/indiv/year', flushval = flushzero, & @@ -2069,7 +2063,6 @@ subroutine set_restart_vectors(this,nc,nsites,sites) rio_gpp_acc_co => this%rvars(ir_gpp_acc_co)%r81d, & rio_npp_acc_co => this%rvars(ir_npp_acc_co)%r81d, & rio_resp_m_acc_co => this%rvars(ir_resp_m_acc_co)%r81d, & - rio_resp_g_acc_co => this%rvars(ir_resp_g_acc_co)%r81d, & rio_gpp_acc_hold_co => this%rvars(ir_gpp_acc_hold_co)%r81d, & rio_resp_m_acc_hold_co => this%rvars(ir_resp_m_acc_hold_co)%r81d, & rio_resp_g_acc_hold_co => this%rvars(ir_resp_g_acc_hold_co)%r81d, & @@ -2393,7 +2386,6 @@ subroutine set_restart_vectors(this,nc,nsites,sites) rio_gpp_acc_co(io_idx_co) = ccohort%gpp_acc rio_npp_acc_co(io_idx_co) = ccohort%npp_acc rio_resp_m_acc_co(io_idx_co) = ccohort%resp_m_acc - rio_resp_g_acc_co(io_idx_co) = ccohort%resp_g_acc rio_gpp_acc_hold_co(io_idx_co) = ccohort%gpp_acc_hold rio_resp_m_acc_hold_co(io_idx_co) = ccohort%resp_m_acc_hold rio_resp_g_acc_hold_co(io_idx_co) = ccohort%resp_g_acc_hold @@ -3040,7 +3032,6 @@ subroutine get_restart_vectors(this, nc, nsites, sites) rio_gpp_acc_co => this%rvars(ir_gpp_acc_co)%r81d, & rio_npp_acc_co => this%rvars(ir_npp_acc_co)%r81d, & rio_resp_m_acc_co => this%rvars(ir_resp_m_acc_co)%r81d, & - rio_resp_g_acc_co => this%rvars(ir_resp_g_acc_co)%r81d, & rio_gpp_acc_hold_co => this%rvars(ir_gpp_acc_hold_co)%r81d, & rio_resp_m_acc_hold_co => this%rvars(ir_resp_m_acc_hold_co)%r81d, & rio_resp_g_acc_hold_co => this%rvars(ir_resp_g_acc_hold_co)%r81d, & @@ -3338,7 +3329,6 @@ subroutine get_restart_vectors(this, nc, nsites, sites) ccohort%gpp_acc = rio_gpp_acc_co(io_idx_co) ccohort%npp_acc = rio_npp_acc_co(io_idx_co) ccohort%resp_m_acc = rio_resp_m_acc_co(io_idx_co) - ccohort%resp_g_acc = rio_resp_g_acc_co(io_idx_co) ccohort%gpp_acc_hold = rio_gpp_acc_hold_co(io_idx_co) ccohort%resp_m_acc_hold = rio_resp_m_acc_hold_co(io_idx_co) ccohort%resp_g_acc_hold = rio_resp_g_acc_hold_co(io_idx_co) From ee9e919c9bead499d6f688175d902f21d814ff5a Mon Sep 17 00:00:00 2001 From: Charlie Koven Date: Wed, 30 Oct 2024 14:05:58 -0700 Subject: [PATCH 055/111] fix typo in modify_fates_paramfile.py --- tools/modify_fates_paramfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/modify_fates_paramfile.py b/tools/modify_fates_paramfile.py index a2a31188b3..f1b9f20399 100755 --- a/tools/modify_fates_paramfile.py +++ b/tools/modify_fates_paramfile.py @@ -157,7 +157,7 @@ def main(): 'fates_history_damage_bins', 'fates_NCWD','fates_litterclass','fates_leafage_class', \ 'fates_plant_organs','fates_hydr_organs','fates_hlm_pftno', \ - 'fates_leafage_class','fates_landuse_class']: + 'fates_leafage_class','fates_landuseclass']: otherdimpresent = True otherdimname = var.dimensions[i] otherdimlength = var.shape[i] From 3f688d70d1563e3688434a3c923393c088f408e3 Mon Sep 17 00:00:00 2001 From: Ryan Knox Date: Wed, 30 Oct 2024 22:32:43 -0400 Subject: [PATCH 056/111] various bug fixes for growth respiration update --- biogeochem/FatesSoilBGCFluxMod.F90 | 3 --- main/EDMainMod.F90 | 18 ++++++++++-------- main/FatesHistoryInterfaceMod.F90 | 2 +- main/FatesRestartInterfaceMod.F90 | 4 ++-- 4 files changed, 13 insertions(+), 14 deletions(-) diff --git a/biogeochem/FatesSoilBGCFluxMod.F90 b/biogeochem/FatesSoilBGCFluxMod.F90 index efff982d8c..2dd3f816a4 100644 --- a/biogeochem/FatesSoilBGCFluxMod.F90 +++ b/biogeochem/FatesSoilBGCFluxMod.F90 @@ -123,9 +123,7 @@ subroutine UnPackNutrientAquisitionBCs(sites, bc_in) ! Locals integer :: nsites ! number of sites integer :: s ! site loop index - integer :: j ! soil layer integer :: icomp ! competitor index - integer :: id ! decomp layer index integer :: pft ! pft index type(fates_patch_type), pointer :: cpatch ! current patch pointer type(fates_cohort_type), pointer :: ccohort ! current cohort pointer @@ -647,7 +645,6 @@ subroutine FluxIntoLitterPools(csite, bc_in, bc_out) integer :: nlev_eff_decomp ! number of effective decomp layers real(r8) :: area_frac ! fraction of site's area of current patch real(r8) :: z_decomp ! Used for calculating depth midpoints of decomp layers - integer :: s ! Site index integer :: el ! Element index (C,N,P,etc) integer :: j ! Soil layer index integer :: id ! Decomposition layer index diff --git a/main/EDMainMod.F90 b/main/EDMainMod.F90 index 983d830356..19569735e1 100644 --- a/main/EDMainMod.F90 +++ b/main/EDMainMod.F90 @@ -102,6 +102,7 @@ module EDMainMod use PRTGenericMod, only : repro_organ use PRTGenericMod, only : struct_organ use PRTLossFluxesMod, only : PRTMaintTurnover + use PRTParametersMod , only : prt_params use EDPftvarcon, only : EDPftvarcon_inst use FatesHistoryInterfaceMod, only : fates_hist @@ -232,7 +233,7 @@ subroutine ed_ecosystem_dynamics(currentSite, bc_in, bc_out) ! values. If we aren't entering that sequence, we need to set the flag ! Make sure cohorts are marked as non-recruits - call bypass_dynamics(currentSite) + call bypass_dynamics(currentSite,bc_out) end if @@ -340,7 +341,7 @@ subroutine ed_integrate_state_variables(currentSite, bc_in, bc_out ) use FatesConstantsMod, only : itrue use FatesConstantsMod , only : nearzero use EDCanopyStructureMod , only : canopy_structure - use PRTParametersMod , only : prt_params + ! !ARGUMENTS: @@ -666,7 +667,7 @@ subroutine ed_integrate_state_variables(currentSite, bc_in, bc_out ) ! Save NPP diagnostic for flux accounting [kg/m2/day] currentSite%flux_diags%npp = currentSite%flux_diags%npp + & - (currentCohort%npp_acc_hold/hlm_days_per_year - currentCohort%resp_excess) * currentCohort%n * area_inv + currentCohort%npp_acc_hold/hlm_days_per_year * currentCohort%n * area_inv ! And simultaneously add the input fluxes to mass balance accounting site_cmass%gpp_acc = site_cmass%gpp_acc + & @@ -699,7 +700,7 @@ subroutine ed_integrate_state_variables(currentSite, bc_in, bc_out ) currentCohort%npp_acc = 0.0_r8 currentCohort%gpp_acc = 0.0_r8 - currentCohort%resp_acc = 0.0_r8 + currentCohort%resp_m_acc = 0.0_r8 ! BOC...update tree 'hydraulic geometry' ! (size --> heights of elements --> hydraulic path lengths --> @@ -1075,7 +1076,7 @@ end subroutine TotalBalanceCheck ! ===================================================================================== - subroutine bypass_dynamics(currentSite) + subroutine bypass_dynamics(currentSite, bc_out) ! ---------------------------------------------------------------------------------- ! If dynamics are bypassed, various fluxes, rates and flags need to be set @@ -1085,8 +1086,9 @@ subroutine bypass_dynamics(currentSite) ! ---------------------------------------------------------------------------------- ! Arguments - type(ed_site_type) , intent(inout), target :: currentSite - + type(ed_site_type) , intent(inout) :: currentSite + type(bc_out_type) , intent(inout) :: bc_out + ! Locals type(fates_patch_type), pointer :: currentPatch type(fates_cohort_type), pointer :: currentCohort @@ -1101,7 +1103,7 @@ subroutine bypass_dynamics(currentSite) currentCohort%isnew=.false. - currentCohort%resp_g_acc_hold = prt_params%grperc(ft) * & + currentCohort%resp_g_acc_hold = prt_params%grperc(currentCohort%pft) * & max(0._r8,(currentCohort%gpp_acc - currentCohort%resp_m_acc))*real(hlm_days_per_year,r8) currentCohort%npp_acc_hold = currentCohort%npp_acc * real(hlm_days_per_year,r8) diff --git a/main/FatesHistoryInterfaceMod.F90 b/main/FatesHistoryInterfaceMod.F90 index 9a1a85dbdf..b460b18cfc 100644 --- a/main/FatesHistoryInterfaceMod.F90 +++ b/main/FatesHistoryInterfaceMod.F90 @@ -2157,7 +2157,7 @@ subroutine update_history_nutrflux(this,csite) ! Excess carbon respired this%hvars(ih_excess_resp_si)%r81d(io_si) = & this%hvars(ih_excess_resp_si)%r81d(io_si) + & - ccohort%resp_excess*uconv + ccohort%resp_excess_hold*uconv/days_per_year case (nitrogen_element) diff --git a/main/FatesRestartInterfaceMod.F90 b/main/FatesRestartInterfaceMod.F90 index 6e53316f41..c4117acae4 100644 --- a/main/FatesRestartInterfaceMod.F90 +++ b/main/FatesRestartInterfaceMod.F90 @@ -2476,7 +2476,7 @@ subroutine set_restart_vectors(this,nc,nsites,sites) rio_resp_g_acc_hold_co(io_idx_co) = ccohort%resp_g_acc_hold rio_npp_acc_hold_co(io_idx_co) = ccohort%npp_acc_hold - rio_resp_excess_co(io_idx_co) = ccohort%resp_excess + rio_resp_excess_co(io_idx_co) = ccohort%resp_excess_hold rio_bmort_co(io_idx_co) = ccohort%bmort rio_hmort_co(io_idx_co) = ccohort%hmort @@ -3442,7 +3442,7 @@ subroutine get_restart_vectors(this, nc, nsites, sites) ccohort%resp_m_acc_hold = rio_resp_m_acc_hold_co(io_idx_co) ccohort%resp_g_acc_hold = rio_resp_g_acc_hold_co(io_idx_co) ccohort%npp_acc_hold = rio_npp_acc_hold_co(io_idx_co) - ccohort%resp_excess = rio_resp_excess_co(io_idx_co) + ccohort%resp_excess_hold = rio_resp_excess_co(io_idx_co) ccohort%bmort = rio_bmort_co(io_idx_co) ccohort%hmort = rio_hmort_co(io_idx_co) From a9758b3f4aa92d76e0ed983ea3d923b83dc344a3 Mon Sep 17 00:00:00 2001 From: Ryan Knox Date: Wed, 30 Oct 2024 22:33:43 -0400 Subject: [PATCH 057/111] Added hard-coded override to revert to original format for inventory files --- main/FatesInventoryInitMod.F90 | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/main/FatesInventoryInitMod.F90 b/main/FatesInventoryInitMod.F90 index 22a48537b5..5e90d8254a 100644 --- a/main/FatesInventoryInitMod.F90 +++ b/main/FatesInventoryInitMod.F90 @@ -798,6 +798,7 @@ subroutine set_inventory_cohort_type1(csite,bc_in,css_file_unit,npatches, & class(prt_vartypes), pointer :: prt_obj real(r8) :: c_time ! Time patch was recorded character(len=patchname_strlen) :: p_name ! The patch associated with this cohort + character(len=patchname_strlen) :: c_name ! Cohort name real(r8) :: c_dbh ! diameter at breast height (cm) real(r8) :: c_height ! tree height (m) integer :: c_pft ! plant functional type index @@ -840,11 +841,17 @@ subroutine set_inventory_cohort_type1(csite,bc_in,css_file_unit,npatches, & real(r8), parameter :: abnormal_large_dbh = 500.0_r8 ! I've never heard of a tree > 3m real(r8), parameter :: abnormal_large_height = 500.0_r8 ! I've never heard of a tree > 500m tall integer, parameter :: recruitstatus = 0 + logical, parameter :: old_type1_override = .false. - - read(css_file_unit,fmt=*,iostat=ios) c_time, p_name, c_dbh, & - c_height, c_pft, c_nplant - + if(old_type1_override) then + ! time patch cohort dbh hite pft nplant bdead alive Avgrg + read(css_file_unit,fmt=*,iostat=ios) c_time, p_name, c_name, c_dbh, & + c_height, c_pft, c_nplant + else + read(css_file_unit,fmt=*,iostat=ios) c_time, p_name, c_dbh, & + c_height, c_pft, c_nplant + end if + if( debug_inv) then write(*,fmt=wr_fmt) & c_time, p_name, c_dbh, c_height, c_pft, c_nplant From 50bf6b93050fe3675f253418afdd6d275d5457b3 Mon Sep 17 00:00:00 2001 From: Ryan Knox Date: Wed, 30 Oct 2024 23:15:04 -0400 Subject: [PATCH 058/111] syntax updates and minor bug fix for respiration update --- biogeochem/FatesCohortMod.F90 | 9 ++++++--- main/EDMainMod.F90 | 4 +++- main/FatesHistoryInterfaceMod.F90 | 2 +- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/biogeochem/FatesCohortMod.F90 b/biogeochem/FatesCohortMod.F90 index 28d95650c3..96669a1112 100644 --- a/biogeochem/FatesCohortMod.F90 +++ b/biogeochem/FatesCohortMod.F90 @@ -133,8 +133,11 @@ module FatesCohortMod ! after the dynamics call-sequence is completed. [kgC/indiv/day] ! _acc_hold: While _acc is zero'd after the dynamics call sequence and then integrated, ! _acc_hold "holds" the integrated value until the next time dynamics is - ! called. This is necessary for restarts. This variable also has units - ! converted to a useful rate [kgC/indiv/yr] + ! called. This is useful because growth and excess respiration + ! are calculated once daily, but we want to remove the average + ! flux from the daily NEP signal, so we remove it from the next day. + ! The hold variables are also useful for rebuilding history on restart. + ! Units converted to a useful rate [kgC/indiv/yr] ! -------------------------------------------------------------------------- real(r8) :: gpp_tstep ! Gross Primary Production (see above *) @@ -202,7 +205,7 @@ module FatesCohortMod integer :: twostr_col ! The column index in the two-stream solution that this cohort is part of ! RESPIRATION COMPONENTS - real(r8) :: resp_excess_hold ! respiration of excess carbon [kgC/indiv/day] + real(r8) :: resp_excess_hold ! respiration of excess carbon [kgC/indiv/yr] ! note: this is flagged "hold" because it is calculated ! at the end of the day (dynamics) but is used ! on the following day (like growth respiration) diff --git a/main/EDMainMod.F90 b/main/EDMainMod.F90 index 19569735e1..de1aa810a9 100644 --- a/main/EDMainMod.F90 +++ b/main/EDMainMod.F90 @@ -625,7 +625,9 @@ subroutine ed_integrate_state_variables(currentSite, bc_in, bc_out ) ! If nutrients are limiting growth, and carbon continues ! to accumulate beyond the plant's storage capacity, then ! it will burn carbon as what we call "excess respiration" - ! We must subtract this term from NPP + ! We must subtract this term from NPP. We do not need to subtract it from + ! currentCohort%npp_acc, it has already been removed from this in + ! the daily growth code (PARTEH). currentCohort%npp_acc_hold = currentCohort%npp_acc_hold - currentCohort%resp_excess_hold diff --git a/main/FatesHistoryInterfaceMod.F90 b/main/FatesHistoryInterfaceMod.F90 index b460b18cfc..87137b92a7 100644 --- a/main/FatesHistoryInterfaceMod.F90 +++ b/main/FatesHistoryInterfaceMod.F90 @@ -5306,7 +5306,7 @@ subroutine update_history_hifrq2(this,nc,nsites,sites,bc_in,bc_out,dt_tstep) ! Total AR (kgC/m2/s) = (kgC/plant/step) / (s/step) * (plant/m2) hio_ar_si_scpf(io_si,scpf) = hio_ar_si_scpf(io_si,scpf) + & (ccohort%resp_m_tstep*dt_tstep_inv) * n_perm2 + & - ccohort%resp_g_acc_hold * n_perm2 / days_per_year / sec_per_day + (ccohort%resp_g_acc_hold + ccohort%resp_excess_hold)* n_perm2 / days_per_year / sec_per_day ! Growth AR (kgC/m2/s) ! CDK: this should be daily hio_ar_grow_si_scpf(io_si,scpf) = hio_ar_grow_si_scpf(io_si,scpf) + & From e0c705804511eb9945517c93333e5f8a5b8a7794 Mon Sep 17 00:00:00 2001 From: adrifoster Date: Thu, 31 Oct 2024 10:14:49 -0600 Subject: [PATCH 059/111] fix comment --- .../functional_testing/fire/SyntheticFuelModels.F90 | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/testing/functional_testing/fire/SyntheticFuelModels.F90 b/testing/functional_testing/fire/SyntheticFuelModels.F90 index 219b4c5794..ce1c8e85e2 100644 --- a/testing/functional_testing/fire/SyntheticFuelModels.F90 +++ b/testing/functional_testing/fire/SyntheticFuelModels.F90 @@ -119,11 +119,11 @@ subroutine AddFuelModel(this, fuel_model_index, carrier, fuel_model_name, character(len=2), intent(in) :: carrier ! main carrier character(len=*), intent(in) :: fuel_model_name ! fuel model long name real(r8), intent(in) :: wind_adj_factor ! wind adjustment factor - real(r8), intent(in) :: hr1_loading ! loading for 1-hr fuels [tons/acre] - real(r8), intent(in) :: hr10_loading ! loading for 10-hr fuels [tons/acre] - real(r8), intent(in) :: hr100_loading ! loading for 100-hr fuels [tons/acre] - real(r8), intent(in) :: live_herb_loading ! loading for live herbacious fuels [tons/acre] - real(r8), intent(in) :: live_woody_loading ! loading for live woody fuels [tons/acre] + real(r8), intent(in) :: hr1_loading ! loading for 1-hr fuels [US tons/acre] + real(r8), intent(in) :: hr10_loading ! loading for 10-hr fuels [US tons/acre] + real(r8), intent(in) :: hr100_loading ! loading for 100-hr fuels [US tons/acre] + real(r8), intent(in) :: live_herb_loading ! loading for live herbacious fuels [US tons/acre] + real(r8), intent(in) :: live_woody_loading ! loading for live woody fuels [US tons/acre] real(r8), intent(in) :: fuel_depth ! fuel bed depth [ft] ! LOCALS: From cd33abc9676b5178d5839eddbd347285f3a89abb Mon Sep 17 00:00:00 2001 From: Ryan Knox Date: Thu, 31 Oct 2024 15:32:17 -0400 Subject: [PATCH 060/111] Added recursive checks for singularity in two-stream --- radiation/TwoStreamMLPEMod.F90 | 90 +++++++++++++++++++++++++--------- 1 file changed, 66 insertions(+), 24 deletions(-) diff --git a/radiation/TwoStreamMLPEMod.F90 b/radiation/TwoStreamMLPEMod.F90 index 941a57c32e..bd0015f892 100644 --- a/radiation/TwoStreamMLPEMod.F90 +++ b/radiation/TwoStreamMLPEMod.F90 @@ -39,7 +39,8 @@ Module TwoStreamMLPEMod integer, parameter :: twostr_vis = 1 ! Named index of visible shortwave radiation integer, parameter :: twostr_nir = 2 ! Named index for near infrared shortwave radiation - + integer, parameter :: max_bands = 2 ! maximum number of bands (for scratch space) + ! Allowable error, as a fraction of total incident for total canopy ! radiation balance checks @@ -893,26 +894,31 @@ subroutine ZenithPrep(this,cosz_in) ! notably the scattering coefficient "om". class(twostream_type) :: this - integer :: ib ! band index, matches indexing of rad_params + real(r8),intent(in) :: cosz_in ! Un-protected cosine of the zenith angle - real(r8) :: cosz ! the near-zero protected cosz - integer :: ican ! scattering element canopy layer index (top down) - integer :: icol ! scattering element column - real(r8) :: asu ! single scattering albedo + real(r8) :: cosz ! the near-zero protected cosz + integer :: ican ! scattering element canopy layer index (top down) + integer :: icol ! scattering element column + integer :: ib ! band index, matches indexing of rad_params + integer :: ib2 ! band inner loop index while testing for singularity + real(r8) :: asu ! single scattering albedo real(r8) :: gdir real(r8) :: tmp0,tmp1,tmp2 - real(r8) :: betab_veg ! beam backscatter for vegetation (no snow) - real(r8) :: betab_om ! multiplication of beam backscatter and reflectance - real(r8) :: om_veg ! scattering coefficient for vegetation (no snow) - real(r8) :: Kb_sing ! the KB_leaf that would generate a singularity - ! with the scelb%a parameter - real(r8) :: Kb_stem ! actual optical depth of stem with not planar geometry effects - ! usually the base value + real(r8) :: betab_veg ! beam backscatter for vegetation (no snow) + real(r8) :: betab_om ! multiplication of beam backscatter and reflectance + real(r8) :: om_veg ! scattering coefficient for vegetation (no snow) + real(r8) :: Kb_sing(max_bands) ! the KB_leaf that would generate a singularity + ! with the scelb%a parameter + real(r8) :: Kb_stem ! actual optical depth of stem with not planar geometry effects + ! usually the base value real(r8), parameter :: Kb_stem_base = 1.0_r8 real(r8), parameter :: sing_tol = 0.01_r8 ! allowable difference between - ! the Kb_leaf that creates - ! a singularity and the actual + ! the Kb_leaf that creates + ! a singularity and the actual + logical :: is_sing ! use this to control if we are actively trying to remove a singularity + integer :: iter_sing ! iterator check to ensure we don't try to fix a singularity indefinitely + real(r8) :: Kb_eff ! When testing for singularity, this is either the stem or stem and leaf optical depth if( (cosz_in-1.0) > nearzero ) then write(log_unit,*)"The cosine of the zenith angle cannot exceed 1" @@ -950,6 +956,7 @@ subroutine ZenithPrep(this,cosz_in) scelg%Kb_leaf = min(kb_max,rad_params%clumping_index(ft) * gdir / cosz) ! To avoid singularities, we need to make sure that Kb =/ a + ! for any of the bands... ! If they are too similar, it will create a very large ! term in the linear solution and generate solution errors ! Lets identify the Kb_leaf that gives a singularity. @@ -960,22 +967,57 @@ subroutine ZenithPrep(this,cosz_in) ! (a*(lai+sai) - sai*kb_stem)/lai = Kb_sing ! or.. adjust stem Kb? ! (a*(lai+sai) - lai*kb_leaf)/sai = kb_stem_sing + if(scelg%lai>nearzero) then - do ib = 1,this%n_bands - Kb_sing = (this%band(ib)%scelb(ican,icol)%a*(scelg%lai+scelg%sai) - scelg%sai*Kb_stem)/scelg%lai - if(abs(scelg%Kb_leaf - Kb_sing)nearzero) then + Kb_sing(ib) = (this%band(ib)%scelb(ican,icol)%a*(scelg%lai+scelg%sai) - scelg%sai*Kb_stem)/scelg%lai + else + Kb_sing(ib) = this%band(ib)%scelb(ican,icol)%a + end if + if(abs(Kb_eff - Kb_sing(ib))nearzero) then + scelg%Kb_leaf = Kb_eff + else + Kb_stem = Kb_eff end if + ! RGK: My sense is that snow should be adding optical depth ! but we don't have any precedent for that in the FATES ! code or old CLM. Re-view this. From 52842b37843522995796fa7a1144e31ee2054130 Mon Sep 17 00:00:00 2001 From: Adrianna Foster Date: Mon, 4 Nov 2024 10:06:17 -0700 Subject: [PATCH 061/111] fix variable names --- main/FatesHistoryInterfaceMod.F90 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/main/FatesHistoryInterfaceMod.F90 b/main/FatesHistoryInterfaceMod.F90 index f6c166a53b..1215f1745a 100644 --- a/main/FatesHistoryInterfaceMod.F90 +++ b/main/FatesHistoryInterfaceMod.F90 @@ -2684,10 +2684,10 @@ subroutine update_history_dyn1(this,nc,nsites,sites,bc_in) hio_tfc_ros_si(io_si) = hio_tfc_ros_si(io_si) + cpatch%TFC_ROS * cpatch%area * AREA_INV hio_fire_intensity_si(io_si) = hio_fire_intensity_si(io_si) + cpatch%FI * cpatch%area * AREA_INV * J_per_kJ hio_fire_area_si(io_si) = hio_fire_area_si(io_si) + cpatch%frac_burnt * cpatch%area * AREA_INV / sec_per_day - hio_fire_fuel_bulkd_si(io_si) = hio_fire_fuel_bulkd_si(io_si) + cpatch%fuel%bulk_density * cpatch%area * AREA_INV + hio_fire_fuel_bulkd_si(io_si) = hio_fire_fuel_bulkd_si(io_si) + cpatch%fuel%bulk_density_notrunks * cpatch%area * AREA_INV hio_fire_fuel_eff_moist_si(io_si) = hio_fire_fuel_eff_moist_si(io_si) + cpatch%fuel%average_moisture_notrunks * cpatch%area * AREA_INV - hio_fire_fuel_sav_si(io_si) = hio_fire_fuel_sav_si(io_si) + cpatch%fuel%SAV * cpatch%area * AREA_INV / m_per_cm - hio_fire_fuel_mef_si(io_si) = hio_fire_fuel_mef_si(io_si) + cpatch%fuel%MEF * cpatch%area * AREA_INV + hio_fire_fuel_sav_si(io_si) = hio_fire_fuel_sav_si(io_si) + cpatch%fuel%SAV_notrunks * cpatch%area * AREA_INV / m_per_cm + hio_fire_fuel_mef_si(io_si) = hio_fire_fuel_mef_si(io_si) + cpatch%fuel%MEF_notrunks * cpatch%area * AREA_INV hio_sum_fuel_si(io_si) = hio_sum_fuel_si(io_si) + cpatch%fuel%non_trunk_loading * cpatch%area * AREA_INV hio_fire_intensity_area_product_si(io_si) = hio_fire_intensity_area_product_si(io_si) + & From 128e0a92566888f1edb0d35a50f42f6bdf33decd Mon Sep 17 00:00:00 2001 From: Adrianna Foster Date: Tue, 12 Nov 2024 09:40:34 -0700 Subject: [PATCH 062/111] fix class vs. type --- biogeochem/FatesPatchMod.F90 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/biogeochem/FatesPatchMod.F90 b/biogeochem/FatesPatchMod.F90 index 79ef7f9b08..bfe09ed88d 100644 --- a/biogeochem/FatesPatchMod.F90 +++ b/biogeochem/FatesPatchMod.F90 @@ -790,8 +790,8 @@ subroutine UpdateLiveGrass(this) class(fates_patch_type), intent(inout) :: this ! patch ! LOCALS: - real(r8) :: live_grass ! live grass [kgC/m2] - class(fates_cohort_type), pointer :: currentCohort ! cohort type + real(r8) :: live_grass ! live grass [kgC/m2] + type(fates_cohort_type), pointer :: currentCohort ! cohort type live_grass = 0.0_r8 currentCohort => this%tallest From bdb253fd9262cbb721a3f2ef033e16d73c2cd77e Mon Sep 17 00:00:00 2001 From: Adrianna Foster Date: Tue, 12 Nov 2024 10:58:38 -0700 Subject: [PATCH 063/111] fix capitalization --- biogeochem/FatesPatchMod.F90 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/biogeochem/FatesPatchMod.F90 b/biogeochem/FatesPatchMod.F90 index bfe09ed88d..f8afc711db 100644 --- a/biogeochem/FatesPatchMod.F90 +++ b/biogeochem/FatesPatchMod.F90 @@ -755,7 +755,7 @@ subroutine UpdateTreeGrassArea(this) class(fates_patch_type), intent(inout) :: this ! patch object ! LOCALS: - type(fates_cohort_Type), pointer :: currentCohort ! cohort object + type(fates_cohort_type), pointer :: currentCohort ! cohort object real(r8) :: tree_area ! treed area of patch [m2] real(r8) :: grass_area ! grass area of patch [m2] From 4b6fd9b7ff5826b630a179d48e8e74ba9b9d7a3d Mon Sep 17 00:00:00 2001 From: Gregory Lemieux Date: Tue, 12 Nov 2024 16:26:05 -0800 Subject: [PATCH 064/111] refactor singularity check loop --- radiation/TwoStreamMLPEMod.F90 | 35 ++++++++++++---------------------- 1 file changed, 12 insertions(+), 23 deletions(-) diff --git a/radiation/TwoStreamMLPEMod.F90 b/radiation/TwoStreamMLPEMod.F90 index bd0015f892..2084486341 100644 --- a/radiation/TwoStreamMLPEMod.F90 +++ b/radiation/TwoStreamMLPEMod.F90 @@ -977,6 +977,13 @@ subroutine ZenithPrep(this,cosz_in) ! Assume there is a singularity so that we test for it is_sing = .true. iter_sing = 0 + + ! Compute the singularity for all bands + Kb_sing(:) = this%band(:)%scelb(ican,icol)%a + if (scelg%lai>nearzero) then + Kb_sing(:) = (Kb_sing(:) * (scelg%lai+scelg%sai) - scelg%sai*Kb_stem)/scelg%lai + end if + do_test_sing: do while(is_sing) ! Now that we have commited to testing it, assume the solution works is_sing = .false. @@ -986,29 +993,11 @@ subroutine ZenithPrep(this,cosz_in) call endrun(msg=errMsg(sourcefile, __LINE__)) end if ! Test to see if there is a singularity and make corrections if needed - do ib = 1,this%n_bands - if(scelg%lai>nearzero) then - Kb_sing(ib) = (this%band(ib)%scelb(ican,icol)%a*(scelg%lai+scelg%sai) - scelg%sai*Kb_stem)/scelg%lai - else - Kb_sing(ib) = this%band(ib)%scelb(ican,icol)%a - end if - if(abs(Kb_eff - Kb_sing(ib))nearzero) then From c81282b04bfee06c8513d61990f1e5606548b3bd Mon Sep 17 00:00:00 2001 From: Gregory Lemieux Date: Tue, 12 Nov 2024 16:30:56 -0800 Subject: [PATCH 065/111] remove the need for a check variable --- radiation/TwoStreamMLPEMod.F90 | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/radiation/TwoStreamMLPEMod.F90 b/radiation/TwoStreamMLPEMod.F90 index 2084486341..c3c20b424e 100644 --- a/radiation/TwoStreamMLPEMod.F90 +++ b/radiation/TwoStreamMLPEMod.F90 @@ -993,8 +993,7 @@ subroutine ZenithPrep(this,cosz_in) call endrun(msg=errMsg(sourcefile, __LINE__)) end if ! Test to see if there is a singularity and make corrections if needed - Kb_sing_check(:) = (abs(Kb_sing(:) - Kb_eff)) < sing_tol - if any(Kb_sing_check(:)) then + if any((abs(Kb_sing(:) - Kb_eff)) < sing_tol) then Kb_eff = Kb_eff + sing_tol is_sing = .true. end if From 2202c461b5fb6fa1aa9398a5b19624771a3169f9 Mon Sep 17 00:00:00 2001 From: Gregory Lemieux Date: Wed, 13 Nov 2024 15:30:18 -0800 Subject: [PATCH 066/111] update subroutine case to PascalCase --- biogeochem/FatesAllometryMod.F90 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/biogeochem/FatesAllometryMod.F90 b/biogeochem/FatesAllometryMod.F90 index 9951af1ded..359836e664 100644 --- a/biogeochem/FatesAllometryMod.F90 +++ b/biogeochem/FatesAllometryMod.F90 @@ -1046,7 +1046,7 @@ subroutine bsap_allom(d,ipft,crowndamage,canopy_trim,elongf_stem, sapw_area,bsap ! dead woody biomass. So bsap = bagw. Might remove the bsap and bdead for grass ! in the future as there is no need to distinguish the two for grass above- and belowground biomass - call sapwood_area_grass(d,sapw_area) + call SapwoodAreaGrass(d,sapw_area) call bagw_allom(d,ipft,crowndamage,elongf_stem,bagw,dbagwdd) call bbgw_allom(d,ipft, elongf_stem,bbgw,dbbgwdd) @@ -1420,7 +1420,7 @@ end subroutine bsap_ltarg_slatop ! Area of sap wood cross-section specifically for grass PFT ! ============================================================================ - subroutine sapwood_area_grass(d,sapw_area) + subroutine SapwoodAreaGrass(d,sapw_area) !--------------------------------------------------------------------------- ! This function calculates sapwood cross-sectional area specifically for grass ! PFT using basal diameter (cm) of the entire plant as size reference, @@ -1450,7 +1450,7 @@ subroutine sapwood_area_grass(d,sapw_area) return - end subroutine sapwood_area_grass + end subroutine SapwoodAreaGrass From 1365dee1ccdc99710d563dcef95a2f1065065764 Mon Sep 17 00:00:00 2001 From: Gregory Lemieux Date: Wed, 13 Nov 2024 15:33:11 -0800 Subject: [PATCH 067/111] formatting space alignment --- biogeochem/FatesAllometryMod.F90 | 60 +++++++++++++++----------------- 1 file changed, 29 insertions(+), 31 deletions(-) diff --git a/biogeochem/FatesAllometryMod.F90 b/biogeochem/FatesAllometryMod.F90 index 359836e664..dfd04793cf 100644 --- a/biogeochem/FatesAllometryMod.F90 +++ b/biogeochem/FatesAllometryMod.F90 @@ -1416,44 +1416,42 @@ end subroutine bsap_ltarg_slatop -! ============================================================================ + ! ============================================================================ ! Area of sap wood cross-section specifically for grass PFT ! ============================================================================ subroutine SapwoodAreaGrass(d,sapw_area) - !--------------------------------------------------------------------------- - ! This function calculates sapwood cross-sectional area specifically for grass - ! PFT using basal diameter (cm) of the entire plant as size reference, - ! assume sapwood area of the entire plant as the sum of the cross-sectional area - ! of each grass tiller - ! such that water transport through sapwood can be seen as a collective behavior - ! of all tillers - ! No reference. Might update this to more theoretical-based approach once there - ! is empirical evidence - !---------------- - ! Input arguments - !---------------- - ! d -- basal diameter [cm] - - !---------------- - ! Output variables - !---------------- - ! sapw_area -- sapwood cross-sectional area [m2] - - !---Arguments - real(r8), intent(in) :: d ! plant basal diameter [ cm] - real(r8), intent(out) :: sapw_area ! sapwood cross-sectional area [ m2] - - ! Calculate sapwood cross-sectional area assuming sapwood geometry as a - ! cylinder and basal diameter is the diameter of the cylinder - sapw_area = (pi_const * ((d / 2.0_r8)**2.0_r8)) / cm2_per_m2 - return - - end subroutine SapwoodAreaGrass + !--------------------------------------------------------------------------- + ! This function calculates sapwood cross-sectional area specifically for grass + ! PFT using basal diameter (cm) of the entire plant as size reference, + ! assume sapwood area of the entire plant as the sum of the cross-sectional area + ! of each grass tiller + ! such that water transport through sapwood can be seen as a collective behavior + ! of all tillers + ! No reference. Might update this to more theoretical-based approach once there + ! is empirical evidence + !---------------- + ! Input arguments + !---------------- + ! d -- basal diameter [cm] + + !---------------- + ! Output variables + !---------------- + ! sapw_area -- sapwood cross-sectional area [m2] + + !---Arguments + real(r8), intent(in) :: d ! plant basal diameter [ cm] + real(r8), intent(out) :: sapw_area ! sapwood cross-sectional area [ m2] + + ! Calculate sapwood cross-sectional area assuming sapwood geometry as a + ! cylinder and basal diameter is the diameter of the cylinder + sapw_area = (pi_const * ((d / 2.0_r8)**2.0_r8)) / cm2_per_m2 - + return + end subroutine SapwoodAreaGrass ! ============================================================================ ! Specific storage relationships From 205fc9892ccfaa55eab3b7e7c1ae9f17c94c22c2 Mon Sep 17 00:00:00 2001 From: adrifoster Date: Thu, 14 Nov 2024 16:03:02 -0700 Subject: [PATCH 068/111] initial cleanup --- fire/SFMainMod.F90 | 258 +++++++++++++++++++++------------------------ 1 file changed, 119 insertions(+), 139 deletions(-) diff --git a/fire/SFMainMod.F90 b/fire/SFMainMod.F90 index 97bbcb6d54..bae9c7bf1e 100644 --- a/fire/SFMainMod.F90 +++ b/fire/SFMainMod.F90 @@ -77,7 +77,7 @@ subroutine fire_model(currentSite, bc_in) if (hlm_spitfire_mode > hlm_sf_nofire_def) then call UpdateFireWeather(currentSite, bc_in) call UpdateFuelCharacteristics(currentSite) - call rate_of_spread(currentSite) + call CalculateSurfaceRateOfSpread(currentSite) call ground_fuel_consumption(currentSite) call area_burnt_intensity(currentSite, bc_in) call crown_scorching(currentSite) @@ -204,154 +204,134 @@ end subroutine UpdateFuelCharacteristics !--------------------------------------------------------------------------------------- - subroutine rate_of_spread (currentSite) - !*****************************************************************. - !Routine called daily from within ED within a site loop. - !Returns the updated currentPatch%ROS_front value for each patch. + subroutine CalculateSurfaceRateOfSpread(currentSite) + ! + ! DESCRIPTION: + ! Calculates potential rate of spread based on fuel characteristics for + ! each patch of a site + ! - use SFParamsMod, only : SF_val_miner_total, & - SF_val_part_dens, & - SF_val_miner_damp, & - SF_val_fuel_energy + use SFParamsMod, only : SF_val_miner_total, SF_val_part_dens, SF_val_miner_damp + use SFParamsMod, only : SF_val_fuel_energy use FatesConstantsMod, only : nearzero - type(ed_site_type), intent(in), target :: currentSite - - type(fates_patch_type), pointer :: currentPatch - - ! Rothermel fire spread model parameters. - real(r8) beta,beta_op ! weighted average of packing ratio (unitless) - real(r8) ir ! reaction intensity (kJ/m2/min) - real(r8) xi,eps,phi_wind ! all are unitless - real(r8) q_ig ! heat of pre-ignition (kJ/kg) - real(r8) reaction_v_opt,reaction_v_max !reaction velocity (per min)!optimum and maximum - real(r8) moist_damp,mw_weight ! moisture dampening coefficient and ratio fuel moisture to extinction - real(r8) beta_ratio ! ratio of beta/beta_op - real(r8) a_beta ! dummy variable for product of a* beta_ratio for react_v_opt equation - real(r8) a,b,c,e ! function of fuel sav - logical, parameter :: debug_windspeed = .false. !for debugging - real(r8),parameter :: q_dry = 581.0_r8 !heat of pre-ignition of dry fuels (kJ/kg) + ! ARGUMENTS: + type(ed_site_type), intent(in), target :: currentSite ! site object - currentPatch=>currentSite%oldest_patch; + ! LOCALS: + type(fates_patch_type), pointer :: currentPatch ! patch object + real(r8) :: beta ! packing ratio [unitless] + real(r8) :: beta_op ! optimum packing ratio [unitless] + real(r8) :: ir ! reaction intensity [kJ/m2/min] + real(r8) :: xi ! propagating flux ratio [unitless] + real(r8) :: eps ! effective heating number [unitless] + real(r8) :: phi_wind ! wind factor [unitless] + real(r8) :: q_ig ! heat of pre-ignition [kJ/kg] + real(r8) :: reaction_v_opt ! optimum reaction velocity [/min] + real(r8) :: reaction_v_max ! maximum reaction velocity [/min] + real(r8) :: moist_damp ! moisture dampening coefficient [unitless] + real(r8) :: mw_weight ! ratio of fuel moisture to extinction moisture [unitless] + real(r8) :: beta_ratio ! relative packing ratio [unitless] + real(r8) :: a_beta ! dummy variable + real(r8) :: a,b,c,e ! dummy variables + + ! CONSTANTS: + real(r8), parameter :: q_dry = 581.0_r8 ! heat of pre-ignition of dry fuels [kJ/kg] + currentPatch => currentSite%oldest_patch do while(associated(currentPatch)) + if (currentPatch%nocomp_pft_label /= nocomp_bareground .and. currentPatch%fuel%non_trunk_loading > nearzero) then - if(currentPatch%nocomp_pft_label .ne. nocomp_bareground .and. currentPatch%fuel%non_trunk_loading > nearzero) then - - ! remove mineral content from net fuel load per Thonicke 2010 for ir calculation - currentPatch%fuel%non_trunk_loading = currentPatch%fuel%non_trunk_loading * (1.0_r8 - SF_val_miner_total) !net of minerals - - ! ----start spreading--- - - if ( hlm_masterproc == itrue .and.debug) write(fates_log(),*) & - 'SF - currentPatch%fuel%bulk_density_notrunks',currentPatch%fuel%bulk_density_notrunks - if ( hlm_masterproc == itrue .and.debug) write(fates_log(),*) & - 'SF - SF_val_part_dens ',SF_val_part_dens - - ! beta = packing ratio (unitless) - ! fraction of fuel array volume occupied by fuel or compactness of fuel bed - beta = currentPatch%fuel%bulk_density_notrunks/SF_val_part_dens - - ! Equation A6 in Thonicke et al. 2010 - ! packing ratio (unitless) - if (currentPatch%fuel%SAV_notrunks < nearzero) then - beta_op = 0.0_r8 - else - beta_op = 0.200395_r8 *(currentPatch%fuel%SAV_notrunks**(-0.8189_r8)) - end if - - if ( hlm_masterproc == itrue .and.debug) write(fates_log(),*) 'SF - beta ',beta - if ( hlm_masterproc == itrue .and.debug) write(fates_log(),*) 'SF - beta_op ',beta_op - if (beta_op < nearzero) then - beta_ratio = 0.0_r8 - else - beta_ratio = beta/beta_op !unitless - end if - - if(write_sf == itrue)then - if ( hlm_masterproc == itrue ) write(fates_log(),*) 'average moisture',currentPatch%fuel%average_moisture_notrunks - endif - - ! ---heat of pre-ignition--- - ! Equation A4 in Thonicke et al. 2010 - ! Rothermel EQ12= 250 Btu/lb + 1116 Btu/lb * average_moisture - ! conversion of Rothermel (1972) EQ12 in BTU/lb to current kJ/kg - ! q_ig in kJ/kg - q_ig = q_dry +2594.0_r8 * currentPatch%fuel%average_moisture_notrunks - - ! ---effective heating number--- - ! Equation A3 in Thonicke et al. 2010. - eps = exp(-4.528_r8 / currentPatch%fuel%SAV_notrunks) - ! Equation A7 in Thonicke et al. 2010 per eqn 49 from Rothermel 1972 - b = 0.15988_r8 * (currentPatch%fuel%SAV_notrunks**0.54_r8) - ! Equation A8 in Thonicke et al. 2010 per eqn 48 from Rothermel 1972 - c = 7.47_r8 * (exp(-0.8711_r8 * (currentPatch%fuel%SAV_notrunks**0.55_r8))) - ! Equation A9 in Thonicke et al. 2010. (appears to have typo, using coefficient eqn.50 Rothermel 1972) - e = 0.715_r8 * (exp(-0.01094_r8 * currentPatch%fuel%SAV_notrunks)) - - if (debug) then - if ( hlm_masterproc == itrue .and.debug) write(fates_log(),*) 'SF - c ',c - if ( hlm_masterproc == itrue .and.debug) write(fates_log(),*) 'SF - b ',b - if ( hlm_masterproc == itrue .and.debug) write(fates_log(),*) 'SF - beta_ratio ',beta_ratio - if ( hlm_masterproc == itrue .and.debug) write(fates_log(),*) 'SF - e ',e - endif - - ! Equation A5 in Thonicke et al. 2010 - ! phi_wind (unitless) - ! convert current_wspeed (wind at elev relevant to fire) from m/min to ft/min for Rothermel ROS eqn - phi_wind = c * ((3.281_r8*currentSite%fireWeather%effective_windspeed)**b)*(beta_ratio**(-e)) - - - ! ---propagating flux---- - ! Equation A2 in Thonicke et al.2010 and Eq. 42 Rothermel 1972 - ! xi (unitless) - xi = (exp((0.792_r8 + 3.7597_r8 * (currentPatch%fuel%SAV_notrunks**0.5_r8)) * (beta+0.1_r8))) / & - (192_r8+7.9095_r8 * currentPatch%fuel%SAV_notrunks) - - ! ---reaction intensity---- - ! Equation in table A1 Thonicke et al. 2010. - a = 8.9033_r8 * (currentPatch%fuel%SAV_notrunks**(-0.7913_r8)) - a_beta = exp(a*(1.0_r8-beta_ratio)) !dummy variable for reaction_v_opt equation - - ! Equation in table A1 Thonicke et al. 2010. - ! reaction_v_max and reaction_v_opt = reaction velocity in units of per min - ! reaction_v_max = Equation 36 in Rothermel 1972 and Fig 12 - reaction_v_max = 1.0_r8 / (0.0591_r8 + 2.926_r8* (currentPatch%fuel%SAV_notrunks**(-1.5_r8))) - ! reaction_v_opt = Equation 38 in Rothermel 1972 and Fig 11 - reaction_v_opt = reaction_v_max*(beta_ratio**a)*a_beta - - ! mw_weight = relative fuel moisture/fuel moisture of extinction - ! average values for litter pools (dead leaves, twigs, small and large branches) plus grass - mw_weight = currentPatch%fuel%average_moisture_notrunks/currentPatch%fuel%MEF_notrunks - - ! Equation in table A1 Thonicke et al. 2010. - ! moist_damp is unitless - moist_damp = max(0.0_r8,(1.0_r8 - (2.59_r8 * mw_weight) + (5.11_r8 * (mw_weight**2.0_r8)) - & - (3.52_r8*(mw_weight**3.0_r8)))) - - ! ir = reaction intenisty in kJ/m2/min - ! currentPatch%fuel%non_trunk_loading converted from kgC/m2 to kgBiomass/m2 for ir calculation - ir = reaction_v_opt*(currentPatch%fuel%non_trunk_loading/0.45_r8)*SF_val_fuel_energy*moist_damp*SF_val_miner_damp - - ! write(fates_log(),*) 'ir',gamma_aptr,moist_damp,SF_val_fuel_energy,SF_val_miner_damp - - if (((currentPatch%fuel%bulk_density_notrunks) <= 0.0_r8).or.(eps <= 0.0_r8).or.(q_ig <= 0.0_r8)) then + ! remove mineral content from net fuel load per Thonicke 2010 for ir calculation + currentPatch%fuel%non_trunk_loading = currentPatch%fuel%non_trunk_loading*(1.0_r8 - SF_val_miner_total) !net of minerals + + ! beta = packing ratio (unitless) + ! fraction of fuel array volume occupied by fuel or compactness of fuel bed + beta = currentPatch%fuel%bulk_density_notrunks/SF_val_part_dens + + ! Equation A6 in Thonicke et al. 2010 + ! packing ratio (unitless) + if (currentPatch%fuel%SAV_notrunks < nearzero) then + beta_op = 0.0_r8 + else + beta_op = 0.200395_r8*(currentPatch%fuel%SAV_notrunks**(-0.8189_r8)) + end if + if (beta_op < nearzero) then + beta_ratio = 0.0_r8 + else + beta_ratio = beta/beta_op + end if + + ! ---heat of pre-ignition--- + ! Equation A4 in Thonicke et al. 2010 + ! Rothermel EQ12= 250 Btu/lb + 1116 Btu/lb * average_moisture + ! conversion of Rothermel (1972) EQ12 in BTU/lb to current kJ/kg + ! q_ig in kJ/kg + q_ig = q_dry + 2594.0_r8*currentPatch%fuel%average_moisture_notrunks + + ! ---effective heating number--- + ! Equation A3 in Thonicke et al. 2010. + eps = exp(-4.528_r8/currentPatch%fuel%SAV_notrunks) + ! Equation A7 in Thonicke et al. 2010 per eqn 49 from Rothermel 1972 + b = 0.15988_r8*(currentPatch%fuel%SAV_notrunks**0.54_r8) + ! Equation A8 in Thonicke et al. 2010 per eqn 48 from Rothermel 1972 + c = 7.47_r8*(exp(-0.8711_r8*(currentPatch%fuel%SAV_notrunks**0.55_r8))) + ! Equation A9 in Thonicke et al. 2010. (appears to have typo, using coefficient eqn.50 Rothermel 1972) + e = 0.715_r8*(exp(-0.01094_r8*currentPatch%fuel%SAV_notrunks)) + + ! Equation A5 in Thonicke et al. 2010 + ! phi_wind (unitless) + ! convert current_wspeed (wind at elev relevant to fire) from m/min to ft/min for Rothermel ROS eqn + phi_wind = c*((3.281_r8*currentSite%fireWeather%effective_windspeed)**b)*(beta_ratio**(-e)) + + ! ---propagating flux---- + ! Equation A2 in Thonicke et al.2010 and Eq. 42 Rothermel 1972 + ! xi (unitless) + xi = (exp((0.792_r8 + 3.7597_r8*(currentPatch%fuel%SAV_notrunks**0.5_r8))*(beta+0.1_r8)))/ & + (192_r8+7.9095_r8*currentPatch%fuel%SAV_notrunks) + + ! ---reaction intensity---- + ! Equation in table A1 Thonicke et al. 2010. + a = 8.9033_r8*(currentPatch%fuel%SAV_notrunks**(-0.7913_r8)) + a_beta = exp(a*(1.0_r8-beta_ratio)) ! dummy variable for reaction_v_opt equation + + ! Equation in table A1 Thonicke et al. 2010. + ! reaction_v_max and reaction_v_opt = reaction velocity in units of per min + ! reaction_v_max = Equation 36 in Rothermel 1972 and Fig 12 + reaction_v_max = 1.0_r8/(0.0591_r8 + 2.926_r8*(currentPatch%fuel%SAV_notrunks**(-1.5_r8))) + ! reaction_v_opt = Equation 38 in Rothermel 1972 and Fig 11 + reaction_v_opt = reaction_v_max*(beta_ratio**a)*a_beta + + ! mw_weight = relative fuel moisture/fuel moisture of extinction + ! average values for litter pools (dead leaves, twigs, small and large branches) plus grass + mw_weight = currentPatch%fuel%average_moisture_notrunks/currentPatch%fuel%MEF_notrunks + + ! Equation in table A1 Thonicke et al. 2010. + ! moist_damp is unitless + moist_damp = max(0.0_r8, (1.0_r8 - (2.59_r8*mw_weight) + (5.11_r8*(mw_weight**2.0_r8)) - & + (3.52_r8*(mw_weight**3.0_r8)))) + + ! ir = reaction intenisty in kJ/m2/min + ! currentPatch%fuel%non_trunk_loading converted from kgC/m2 to kgBiomass/m2 for ir calculation + ir = reaction_v_opt*(currentPatch%fuel%non_trunk_loading/0.45_r8)*SF_val_fuel_energy*moist_damp*SF_val_miner_damp + + if (((currentPatch%fuel%bulk_density_notrunks) <= 0.0_r8) .or. (eps <= 0.0_r8) .or. (q_ig <= 0.0_r8)) then currentPatch%ROS_front = 0.0_r8 - else ! Equation 9. Thonicke et al. 2010. - ! forward ROS in m/min - currentPatch%ROS_front = (ir*xi*(1.0_r8+phi_wind)) / (currentPatch%fuel%bulk_density_notrunks*eps*q_ig) - endif - ! Equation 10 in Thonicke et al. 2010 - ! backward ROS from Can FBP System (1992) in m/min - ! backward ROS wind not changed by vegetation - currentPatch%ROS_back = currentPatch%ROS_front*exp(-0.012_r8*currentSite%wind) - - end if ! nocomp_pft_label check - currentPatch => currentPatch%younger + else ! Equation 9. Thonicke et al. 2010. + ! forward ROS in m/min + currentPatch%ROS_front = (ir*xi*(1.0_r8+phi_wind))/(currentPatch%fuel%bulk_density_notrunks*eps*q_ig) + endif + ! Equation 10 in Thonicke et al. 2010 + ! backward ROS from Can FBP System (1992) in m/min + ! backward ROS wind not changed by vegetation + currentPatch%ROS_back = currentPatch%ROS_front*exp(-0.012_r8*currentSite%wind) - enddo !end patch loop + end if + currentPatch => currentPatch%younger + + end do - end subroutine rate_of_spread + end subroutine CalculateSurfaceRateOfSpread !***************************************************************** subroutine ground_fuel_consumption ( currentSite ) From a3cf233b1b84e115d8acc46c80ce13f4a0fe50bc Mon Sep 17 00:00:00 2001 From: adrifoster Date: Thu, 14 Nov 2024 18:14:37 -0700 Subject: [PATCH 069/111] minor updates --- fire/SFEquationsMod.F90 | 20 ++++++++++++++++++++ fire/SFMainMod.F90 | 11 ++++++----- 2 files changed, 26 insertions(+), 5 deletions(-) create mode 100644 fire/SFEquationsMod.F90 diff --git a/fire/SFEquationsMod.F90 b/fire/SFEquationsMod.F90 new file mode 100644 index 0000000000..0a9cf9c2c9 --- /dev/null +++ b/fire/SFEquationsMod.F90 @@ -0,0 +1,20 @@ +module SFEquationsMod + ! ============================================================================ + ! Helper methods for the FATES SPITFIRE model + ! Most equations come from: + ! Thonicke et al. 2010, Biogeosciences 7:1991-2011 + ! Rothermel et al. 1972, Research Paper INT 115 + ! Albini et al. 1976, Research Report INT 30 + ! ============================================================================ + + use FatesConstantsMod, only : r8 => fates_r8 + + implicit none + private + + + + + + +end module SFEquationsMod \ No newline at end of file diff --git a/fire/SFMainMod.F90 b/fire/SFMainMod.F90 index bae9c7bf1e..947159a67c 100644 --- a/fire/SFMainMod.F90 +++ b/fire/SFMainMod.F90 @@ -54,7 +54,7 @@ module SFMainMod contains - subroutine fire_model(currentSite, bc_in) + subroutine DailyFireModel(currentSite, bc_in) ! ! DESCRIPTION: ! Runs the daily fire model @@ -86,7 +86,7 @@ subroutine fire_model(currentSite, bc_in) call post_fire_mortality(currentSite) end if - end subroutine fire_model + end subroutine DailyFireModel !--------------------------------------------------------------------------------------- @@ -240,10 +240,11 @@ subroutine CalculateSurfaceRateOfSpread(currentSite) currentPatch => currentSite%oldest_patch do while(associated(currentPatch)) - if (currentPatch%nocomp_pft_label /= nocomp_bareground .and. currentPatch%fuel%non_trunk_loading > nearzero) then + if (currentPatch%nocomp_pft_label /= nocomp_bareground .and. & + currentPatch%fuel%non_trunk_loading > nearzero) then - ! remove mineral content from net fuel load per Thonicke 2010 for ir calculation - currentPatch%fuel%non_trunk_loading = currentPatch%fuel%non_trunk_loading*(1.0_r8 - SF_val_miner_total) !net of minerals + ! remove mineral content from fuel load per Thonicke 2010 + currentPatch%fuel%non_trunk_loading = currentPatch%fuel%non_trunk_loading*(1.0_r8 - SF_val_miner_total) ! beta = packing ratio (unitless) ! fraction of fuel array volume occupied by fuel or compactness of fuel bed From 291096a6b7960e6e122c4a142e48a37a305d3027 Mon Sep 17 00:00:00 2001 From: Marcos Longo Date: Sun, 17 Nov 2024 17:54:45 -0800 Subject: [PATCH 070/111] Minor fix in FatesPFTIndexSwapper.py. At least with my scipy configuration, the code would crash with scalars. The updates should be equivalent to the old code but do not cause errors. --- tools/FatesPFTIndexSwapper.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tools/FatesPFTIndexSwapper.py b/tools/FatesPFTIndexSwapper.py index c63f8891b7..07f71920c9 100755 --- a/tools/FatesPFTIndexSwapper.py +++ b/tools/FatesPFTIndexSwapper.py @@ -203,8 +203,9 @@ def main(argv): # Copy over the input data # Tedious, but I have to permute through all combinations of dimension position if( pft_dim_len == 0 ): - out_var = fp_out.createVariable(key,'d',(fp_in.variables.get(key).dimensions)) - out_var.assignValue(float(fp_in.variables.get(key).data)) + # Scalar: do not assume any dimensions. + out_var = fp_out.createVariable(key,'d',()) + out_var[()] = in_var[()] elif( (pft_dim_found==-1) & (prt_dim_found==-1) & (litt_dim_found==-1) & (hydro_dim_found==-1) & (landuse_dim_found==-1) ): out_var = fp_out.createVariable(key,'d',(fp_in.variables.get(key).dimensions)) out_var[:] = in_var[:] @@ -283,7 +284,7 @@ def main(argv): fp_in.close() fp_out.close() - print('Cloneing complete!') + print('Cloning complete!') exit(0) From fd7922f234822c7fe1c21fdc65660e82b4f17767 Mon Sep 17 00:00:00 2001 From: Marcos Longo Date: Mon, 18 Nov 2024 09:18:02 -0800 Subject: [PATCH 071/111] Editing modify_fates_paramfile.py similarly to FatesPFTIndexSwapper.py for scalars. --- tools/modify_fates_paramfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/modify_fates_paramfile.py b/tools/modify_fates_paramfile.py index a2a31188b3..074dba9f3d 100755 --- a/tools/modify_fates_paramfile.py +++ b/tools/modify_fates_paramfile.py @@ -133,7 +133,7 @@ def main(): for i in range(var.shape[0]): var[i] = outputval[i] elif(ndim_file==0): - var.assignValue(outputval[0]) + var[()] = outputval[()] else: print("Unhandled dimension size in modify_fates_paramfile.py") From 7539b402ae06ef790560d0a81b303cc886a25101 Mon Sep 17 00:00:00 2001 From: Marcos Longo Date: Mon, 18 Nov 2024 09:32:12 -0800 Subject: [PATCH 072/111] Updating ncvarsort.py so it uses the same netcdf libraries as other python scripts. --- tools/ncvarsort.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tools/ncvarsort.py b/tools/ncvarsort.py index 6583700ae3..eaab228c20 100755 --- a/tools/ncvarsort.py +++ b/tools/ncvarsort.py @@ -4,11 +4,19 @@ # --input or --fin: input filename. # --output or --fout: output filename. If missing, will assume its directly modifying the input file, and will prompt unless -O is specified -import netCDF4 as nc +#import netCDF4 as nc import sys import os import argparse +# Newer versions of scipy have dropped the netcdf module and +# netcdf functions are part of the io parent module +try: + from scipy import io as nc + +except ImportError: + from scipy.io import netcdf as nc + # program sorts the variables based on the provided list, and pulls them one at a time # from an existing file and adds them to a new file in the sorted order. # input/output based on code here: https://gist.github.com/guziy/8543562 From 7d30a3fb6c43f0bef6cb6a37b64e427d514dc677 Mon Sep 17 00:00:00 2001 From: Marcos Longo Date: Mon, 18 Nov 2024 09:37:44 -0800 Subject: [PATCH 073/111] Append extension nc to the temporary files. In some combinations of python/netcdf libraries, the lack of nc extension has caused the function to crash. --- tools/BatchPatchParams.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tools/BatchPatchParams.py b/tools/BatchPatchParams.py index cd3e934632..9245e6dbea 100755 --- a/tools/BatchPatchParams.py +++ b/tools/BatchPatchParams.py @@ -107,13 +107,17 @@ def main(): base_cdl = xmlroot.find('base_file').text new_cdl = xmlroot.find('new_file').text + # Append extension nc to temporary files + # (in some netcdf versions, the lack of extension causes failures) + ext_nc = ".nc" + # Convert the base cdl file into a temp nc binary - base_nc = os.popen('mktemp').read().rstrip('\n') + base_nc = os.popen('mktemp').read().rstrip('\n')+ext_nc gencmd = "ncgen -o "+base_nc+" "+base_cdl os.system(gencmd) # Generate a temp output file name - new_nc = os.popen('mktemp').read().rstrip('\n') + new_nc = os.popen('mktemp').read().rstrip('\n')+ext_nc os.system("ls "+base_nc) os.system("ls "+new_nc) @@ -190,7 +194,7 @@ def main(): fp_nc.close() # Sort the new file - newer_nc = os.popen('mktemp').read().rstrip('\n') + newer_nc = os.popen('mktemp').read().rstrip('\n')+ext_nc os.system("../tools/ncvarsort.py --fin "+new_nc+" --fout "+newer_nc+" --overwrite") From 5cb89ad14775b291450869401a3ef0d2db5aa2dc Mon Sep 17 00:00:00 2001 From: Marcos Longo Date: Mon, 18 Nov 2024 09:47:42 -0800 Subject: [PATCH 074/111] Additional edits to ncvarsort.py to align with the scipy/io/netcdf library. --- tools/ncvarsort.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/tools/ncvarsort.py b/tools/ncvarsort.py index eaab228c20..7f35293690 100755 --- a/tools/ncvarsort.py +++ b/tools/ncvarsort.py @@ -33,7 +33,7 @@ def main(): args = parser.parse_args() # # open the input dataset - dsin = nc.Dataset(args.fnamein, 'r') + dsin = nc.netcdf_file(args.fnamein, 'r') # # make empty lists to hold the variable names in. the first of these is a list of sub-lists, # one for each type of variable (based on dimensionality). @@ -106,7 +106,7 @@ def main(): else: raise ValueError('Output file already exists and overwrite flag not specified for filename: '+args.fnameout) # - dsout = nc.Dataset(args.fnameout, "w") + dsout = nc.netcdf_file(args.fnameout, "w") # #Copy dimensions for dname, the_dim in dsin.dimensions.items(): @@ -126,14 +126,20 @@ def main(): # as well as all metadata to the new file. for i in range(len(varnames_list_sorted)): v_name = varnames_list_sorted[i] - varin = dsin.variables[v_name] + varin = dsin.variables.get[v_name] outVar = dsout.createVariable(v_name, varin.datatype, varin.dimensions) + + n_dimensions = len(varin.dimensions) if args.debug: if (verbose): print(v_name) # outVar.setncatts({k: varin.getncattr(k) for k in varin.ncattrs()}) - outVar[:] = varin[:] + if ( n_dimensions == 0): + outVar[()] = varin[()] + else: + outVar[:] = varin[:] # + # copy global attributes dsout.setncatts({k: dsin.getncattr(k) for k in dsin.ncattrs()})# # From f2b44986fa34a34342dd1b6c826eecbd3b8e76b6 Mon Sep 17 00:00:00 2001 From: Marcos Longo Date: Mon, 18 Nov 2024 09:51:41 -0800 Subject: [PATCH 075/111] Force integer in createDimension in ncvarsort.py I copied this from FatesPFTIndexSwapper.py, because the code was giving an attribute error. --- tools/ncvarsort.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/ncvarsort.py b/tools/ncvarsort.py index 7f35293690..562b82374c 100755 --- a/tools/ncvarsort.py +++ b/tools/ncvarsort.py @@ -112,7 +112,7 @@ def main(): for dname, the_dim in dsin.dimensions.items(): if args.debug: if (verbose): print(dname, the_dim.size) - dsout.createDimension(dname, the_dim.size ) + dsout.createDimension(dname, int(the_dim.size) ) # if (verbose): print() # From b417a78e8ce657826517dae1558efe0b3bff5060 Mon Sep 17 00:00:00 2001 From: Marcos Longo Date: Mon, 18 Nov 2024 09:54:56 -0800 Subject: [PATCH 076/111] Dropping attribute size from "the_dim" in ncvartsort.py Apparently this is different between scipy/io/netcdf and netCDF4 --- tools/ncvarsort.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/ncvarsort.py b/tools/ncvarsort.py index 562b82374c..a2d96cab43 100755 --- a/tools/ncvarsort.py +++ b/tools/ncvarsort.py @@ -111,8 +111,8 @@ def main(): #Copy dimensions for dname, the_dim in dsin.dimensions.items(): if args.debug: - if (verbose): print(dname, the_dim.size) - dsout.createDimension(dname, int(the_dim.size) ) + if (verbose): print(dname, the_dim) + dsout.createDimension(dname, int(the_dim) ) # if (verbose): print() # From 612d4e010edb270895518885bb2e81a1d284f855 Mon Sep 17 00:00:00 2001 From: Marcos Longo Date: Mon, 18 Nov 2024 09:57:58 -0800 Subject: [PATCH 077/111] Bug fix when retrieving variables in ncvarsort.py. Fixing a bug I introduced when I replaced variable retrieval with variables.get. I had forgotten to replace square brackets with parentheses. --- tools/ncvarsort.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/ncvarsort.py b/tools/ncvarsort.py index a2d96cab43..eb01fe52f2 100755 --- a/tools/ncvarsort.py +++ b/tools/ncvarsort.py @@ -126,7 +126,7 @@ def main(): # as well as all metadata to the new file. for i in range(len(varnames_list_sorted)): v_name = varnames_list_sorted[i] - varin = dsin.variables.get[v_name] + varin = dsin.variables.get(v_name) outVar = dsout.createVariable(v_name, varin.datatype, varin.dimensions) n_dimensions = len(varin.dimensions) From 7bf2f7cd75414edb3662f83f01b07d1b3da1f431 Mon Sep 17 00:00:00 2001 From: Marcos Longo Date: Mon, 18 Nov 2024 10:02:52 -0800 Subject: [PATCH 078/111] Replacing datatype with typecode in ncvarsort.py --- tools/ncvarsort.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/ncvarsort.py b/tools/ncvarsort.py index eb01fe52f2..98fa74002f 100755 --- a/tools/ncvarsort.py +++ b/tools/ncvarsort.py @@ -127,7 +127,7 @@ def main(): for i in range(len(varnames_list_sorted)): v_name = varnames_list_sorted[i] varin = dsin.variables.get(v_name) - outVar = dsout.createVariable(v_name, varin.datatype, varin.dimensions) + outVar = dsout.createVariable(v_name, varin.typecode, varin.dimensions) n_dimensions = len(varin.dimensions) if args.debug: From f36f34a550242b8dc7c6b5c8aec7910aa0757fdf Mon Sep 17 00:00:00 2001 From: Marcos Longo Date: Mon, 18 Nov 2024 10:08:24 -0800 Subject: [PATCH 079/111] More fixes in ncvarsort.py to be compatible to scipy.io.netcdf --- tools/ncvarsort.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/tools/ncvarsort.py b/tools/ncvarsort.py index 98fa74002f..4a4eac8496 100755 --- a/tools/ncvarsort.py +++ b/tools/ncvarsort.py @@ -126,15 +126,18 @@ def main(): # as well as all metadata to the new file. for i in range(len(varnames_list_sorted)): v_name = varnames_list_sorted[i] - varin = dsin.variables.get(v_name) - outVar = dsout.createVariable(v_name, varin.typecode, varin.dimensions) + v_type = dsin.variables[v_name].type + v_dims = dsin.variables[v_name].dimensions + varin = dsin.variables.get(v_name) - n_dimensions = len(varin.dimensions) + outVar = dsout.createVariable(v_name, v_type, v_dims) + + n_dims = len(v_dims) if args.debug: if (verbose): print(v_name) # outVar.setncatts({k: varin.getncattr(k) for k in varin.ncattrs()}) - if ( n_dimensions == 0): + if ( n_dims == 0): outVar[()] = varin[()] else: outVar[:] = varin[:] From 14a5cb44c5d82c0eac2920058a5c1a121e39b95a Mon Sep 17 00:00:00 2001 From: Marcos Longo Date: Mon, 18 Nov 2024 10:09:46 -0800 Subject: [PATCH 080/111] Fix typo in my last update --- tools/ncvarsort.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/ncvarsort.py b/tools/ncvarsort.py index 4a4eac8496..070cd70c8e 100755 --- a/tools/ncvarsort.py +++ b/tools/ncvarsort.py @@ -126,7 +126,7 @@ def main(): # as well as all metadata to the new file. for i in range(len(varnames_list_sorted)): v_name = varnames_list_sorted[i] - v_type = dsin.variables[v_name].type + v_type = dsin.variables[v_name].typecode v_dims = dsin.variables[v_name].dimensions varin = dsin.variables.get(v_name) From 9b2384c921ee80b66a1d452ba38abaaf5ef93470 Mon Sep 17 00:00:00 2001 From: Marcos Longo Date: Mon, 18 Nov 2024 10:27:52 -0800 Subject: [PATCH 081/111] Multiple fixes to ncvarsort.py to be compatible with scipy.io.netcdf --- tools/ncvarsort.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/tools/ncvarsort.py b/tools/ncvarsort.py index 070cd70c8e..255475b910 100755 --- a/tools/ncvarsort.py +++ b/tools/ncvarsort.py @@ -126,25 +126,31 @@ def main(): # as well as all metadata to the new file. for i in range(len(varnames_list_sorted)): v_name = varnames_list_sorted[i] - v_type = dsin.variables[v_name].typecode - v_dims = dsin.variables[v_name].dimensions varin = dsin.variables.get(v_name) - + v_type = dsin.variables[v_name].typecode() + v_dims = varin.dimensions + print(" V_NAME = ",v_name,"; V_TYPE = ",v_type,"; V_DIMS = ",v_dims) outVar = dsout.createVariable(v_name, v_type, v_dims) n_dims = len(v_dims) if args.debug: if (verbose): print(v_name) # - outVar.setncatts({k: varin.getncattr(k) for k in varin.ncattrs()}) + + # Copy attributes + for v_attr in varin._attributes: + setattr(outVar,v_attr,getattr(varin,v_attr)) + if ( n_dims == 0): outVar[()] = varin[()] else: outVar[:] = varin[:] # - # copy global attributes - dsout.setncatts({k: dsin.getncattr(k) for k in dsin.ncattrs()})# + # copy global attributes + for g_attr in dsin._attributes: + setattr(dsout,g_attr,getattr(dsin,g_attr)) + # # close the output file dsin.close() From c07ef109fdd344cef02760a4e4c1031a9dcae6d3 Mon Sep 17 00:00:00 2001 From: jessica needham Date: Tue, 19 Nov 2024 11:40:21 -0800 Subject: [PATCH 082/111] fix CNP allocation when reproductive frac is 1 --- parteh/PRTAllometricCNPMod.F90 | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/parteh/PRTAllometricCNPMod.F90 b/parteh/PRTAllometricCNPMod.F90 index ec29fff40a..2f6beed4b5 100644 --- a/parteh/PRTAllometricCNPMod.F90 +++ b/parteh/PRTAllometricCNPMod.F90 @@ -2540,8 +2540,13 @@ subroutine EstimateGrowthNC(this,target_c,target_dcdd,state_mask,avg_nc,avg_pc) ! repro_w = (total_w + repro_w)*repro_c_frac = total_w*repro_c_frac + repro_w*repro_c_frac ! repro_w * (1 - repro_c_frac) = total_w*repro_c_frac ! repro_w = total_w * repro_c_frac/(1-repro_c_frac) + + if(repro_c_frac - 1._r8 < nearzero) then + repro_w = repro_c_frac + else + repro_w = total_w * repro_c_frac/(1._r8 - repro_c_frac) + end if - repro_w = total_w * repro_c_frac/(1._r8 - repro_c_frac) total_w = total_w + repro_w avg_nc = avg_nc + repro_w * nc_repro avg_pc = avg_pc + repro_w * pc_repro From 589f263667c9b29dd4ae69a0eba3b1689568ea25 Mon Sep 17 00:00:00 2001 From: Gregory Lemieux Date: Wed, 20 Nov 2024 09:39:17 -0800 Subject: [PATCH 083/111] fix if then typo around Kb_sing --- radiation/TwoStreamMLPEMod.F90 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/radiation/TwoStreamMLPEMod.F90 b/radiation/TwoStreamMLPEMod.F90 index c3c20b424e..c4bb99fec1 100644 --- a/radiation/TwoStreamMLPEMod.F90 +++ b/radiation/TwoStreamMLPEMod.F90 @@ -993,7 +993,7 @@ subroutine ZenithPrep(this,cosz_in) call endrun(msg=errMsg(sourcefile, __LINE__)) end if ! Test to see if there is a singularity and make corrections if needed - if any((abs(Kb_sing(:) - Kb_eff)) < sing_tol) then + if (any((abs(Kb_sing(:) - Kb_eff)) < sing_tol)) then Kb_eff = Kb_eff + sing_tol is_sing = .true. end if From e7b332f8f97cfd48e38bef0f512a5997831398d9 Mon Sep 17 00:00:00 2001 From: Gregory Lemieux Date: Wed, 20 Nov 2024 09:39:42 -0800 Subject: [PATCH 084/111] fix Kb_sing calculation Using a do loop in place of broadcasting is necessary here per the fortran standard since the pointers and arrays are not contiguous in memory. --- radiation/TwoStreamMLPEMod.F90 | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/radiation/TwoStreamMLPEMod.F90 b/radiation/TwoStreamMLPEMod.F90 index c4bb99fec1..25b8efdc1b 100644 --- a/radiation/TwoStreamMLPEMod.F90 +++ b/radiation/TwoStreamMLPEMod.F90 @@ -979,10 +979,12 @@ subroutine ZenithPrep(this,cosz_in) iter_sing = 0 ! Compute the singularity for all bands - Kb_sing(:) = this%band(:)%scelb(ican,icol)%a - if (scelg%lai>nearzero) then - Kb_sing(:) = (Kb_sing(:) * (scelg%lai+scelg%sai) - scelg%sai*Kb_stem)/scelg%lai - end if + do ib = 1,this%n_bands + Kb_sing(ib) = this%band(ib)%scelb(ican,icol)%a + if (scelg%lai>nearzero) then + Kb_sing(ib) = (Kb_sing(ib) * (scelg%lai+scelg%sai) - scelg%sai*Kb_stem)/scelg%lai + end if + end do do_test_sing: do while(is_sing) ! Now that we have commited to testing it, assume the solution works From c1d04c44a5feb75e7154d9e404d80adced0a99a1 Mon Sep 17 00:00:00 2001 From: jessica needham Date: Wed, 20 Nov 2024 09:44:31 -0800 Subject: [PATCH 085/111] reverse logic for if statement --- parteh/PRTAllometricCNPMod.F90 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parteh/PRTAllometricCNPMod.F90 b/parteh/PRTAllometricCNPMod.F90 index 2f6beed4b5..afa8e58259 100644 --- a/parteh/PRTAllometricCNPMod.F90 +++ b/parteh/PRTAllometricCNPMod.F90 @@ -2541,7 +2541,7 @@ subroutine EstimateGrowthNC(this,target_c,target_dcdd,state_mask,avg_nc,avg_pc) ! repro_w * (1 - repro_c_frac) = total_w*repro_c_frac ! repro_w = total_w * repro_c_frac/(1-repro_c_frac) - if(repro_c_frac - 1._r8 < nearzero) then + if(1._r8 - repro_c_frac < nearzero) then repro_w = repro_c_frac else repro_w = total_w * repro_c_frac/(1._r8 - repro_c_frac) From df6c5e7c9c6ecf1a998c199a72e031175ae856ac Mon Sep 17 00:00:00 2001 From: Shijie Shu <92333861+sshu88@users.noreply.github.com> Date: Wed, 20 Nov 2024 11:03:59 -0800 Subject: [PATCH 086/111] Bug fix for issue #1289 Fix the issue of no wood product when forced by area-based harvest. https://github.com/NGEET/fates/issues/1289 --- biogeochem/EDLoggingMortalityMod.F90 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/biogeochem/EDLoggingMortalityMod.F90 b/biogeochem/EDLoggingMortalityMod.F90 index 73ea7231e7..a091e4f3d6 100644 --- a/biogeochem/EDLoggingMortalityMod.F90 +++ b/biogeochem/EDLoggingMortalityMod.F90 @@ -291,7 +291,7 @@ subroutine LoggingMortality_frac( pft_i, dbh, canopy_layer, lmort_direct, & ! transfer of area to secondary land is based on overall area affected, not just logged crown area ! l_degrad accounts for the affected area between logged crowns if(prt_params%woody(pft_i) == itrue)then ! only set logging rates for trees - if (cur_harvest_tag == 0) then + if (cur_harvest_tag == 0 .or. cur_harvest_tag == 2) then ! direct logging rates, based on dbh min and max criteria if (dbh >= logging_dbhmin .and. .not. & ((logging_dbhmax < fates_check_param_set) .and. (dbh >= logging_dbhmax )) ) then From 8bca0fb17b8b389cbf1cdd41a9d0a759a779d482 Mon Sep 17 00:00:00 2001 From: adrifoster Date: Mon, 2 Dec 2024 12:09:52 -0700 Subject: [PATCH 087/111] updates --- fire/SFEquationsMod.F90 | 128 +++++++++++++++++++++++++++++++++++++++- fire/SFMainMod.F90 | 89 ++++++++++------------------ 2 files changed, 157 insertions(+), 60 deletions(-) diff --git a/fire/SFEquationsMod.F90 b/fire/SFEquationsMod.F90 index 0a9cf9c2c9..ffd02e844b 100644 --- a/fire/SFEquationsMod.F90 +++ b/fire/SFEquationsMod.F90 @@ -12,9 +12,135 @@ module SFEquationsMod implicit none private + public :: OptimumPackingRatio + contains + real(r8) function OptimumPackingRatio(SAV) + ! + ! DESCRIPTION: + ! Calculates optimum packing ratio [unitless] + ! Equation A6 in Thonicke et al. 2010 + ! + + ! ARGUMENTS: + real(r8), intent(in) :: SAV ! surface area to volume ratio of fuel [/cm] + + ! CONSTANTS: + real(r8), parameter :: a = 0.200395_r8 + real(r8), parameter :: b = -0.8189_r8 + + if (SAV < nearzero) then + beta_op = 0.0_r8 + else + beta_op = a*(SAV**b) + end if + + end function OptimumPackingRatio + + !------------------------------------------------------------------------------------- + real(r8) function MaximumReactionVelocity(SAV) + ! + ! DESCRIPTION: + ! Calculates maximum reaction velocity in /min + ! From Equation 36 in Rothermel 1972; Fig. 12 + ! + + ! ARGUMENTS: + real(r8), intent(in) :: SAV ! fuel surface area to volume ratio [/cm] + + MaximumReactionVelocity = 1.0_r8/(0.0591_r8 + 2.926_r8*(SAV**(-1.5_r8))) + + end function MaximumReactionVelocity + + !--------------------------------------------------------------------------------------- + + real(r8) function OptimumReactionVelocity(max_reaction_vel, SAV, beta_ratio) + ! + ! DESCRIPTION: + ! Calculates optimum reaction velocity in /min + ! From Equation 38 in Rothermel 1972; Fig. 11 + ! + + ! ARGUMENTS: + real(r8), intent(in) :: max_reaction_vel ! maximum reaction velocity [/min] + real(r8), intent(in) :: SAV ! fuel surface area to volume ratio [/cm] + real(r8), intent(in) :: beta_ratio ! ratio of packing ratio to optimum packing ratio [0-1] + + ! LOCALS: + real (r8) :: a, a_beta ! intermediate variables + + ! Equations in Table A1 Thonicke et al. 2010 + a = 8.9033_r8*(SAV**(-0.7913_r8)) + a_beta = exp(a*(1.0_r8 - beta_ratio)) + + OptimumReactionVelocity = max_reaction_vel*(beta_ratio**a)*a_beta + + end function OptimumReactionVelocity + + !--------------------------------------------------------------------------------------- + + real(r8) function MoistureCoefficient(moisture, MEF) + ! + ! DESCRIPTION: + ! Calculates the moisture dampening coefficient for reaction intensity + ! Based on Equation in table A1 Thonicke et al. 2010. + ! + + ! ARGUMENTS: + real(r8), intent(in) :: moisture ! fuel moisture [m3/m3] + real(r8), intent(in) :: MEF ! fuel moisture of extinction [m3/m3] + + ! LOCALS: + real(r8) :: mw_weight ! relative fuel moisture/fuel moisture of extinction + + ! average values for litter pools (dead leaves, twigs, small and large branches), plus grass + mw_weight = moisture/MEF + + ! moist_damp is unitless + MoistureCoefficient = max(0.0_r8, (1.0_r8 - (2.59_r8*mw_weight) + & + (5.11_r8*(mw_weight**2.0_r8)) - (3.52_r8*(mw_weight**3.0_r8)))) + + if (MoistureCoefficient > 1.0_r8) MoistureCoefficient = 1.0_r8 + + end function MoistureCoefficient + + !--------------------------------------------------------------------------------------- + real(r8) function ReactionIntensity(fuel_loading, SAV, beta_ratio, moisture, MEF) + ! + ! DESCRIPTION: + ! Calculates reaction intensity in kJ/m2/min + ! + + ! USES + use SFParamsMod, only : SF_val_fuel_energy, SF_val_miner_damp + + ! ARGUMENTS: + real(r8), intent(in) :: fuel_loading ! net fuel loading [kg/m2] + real(r8), intent(in) :: SAV ! fuel surface area to volume ratio [/cm] + real(r8), intent(in) :: beta_ratio ! ratio of packing ratio to optimum packing ratio [0-1] + real(r8), intent(in) :: moisture ! fuel moisture [m3/m3] + real(r8), intent(in) :: MEF ! fuel moisture of extinction [m3/m3] + + ! LOCALS: + real(r8) :: max_reaction_vel ! maximum reaction velocity + real(r8) :: opt_reaction_vel ! optimum reaction velocity + real(r8) :: moist_coeff ! moisture dampening coefficient [0-1] + + ! calculate maximum reaction velocity [/min] + max_reaction_vel = MaximumReactionVelocity(SAV) + + ! calculate optimum reacion velocity [/min] + opt_reaction_vel = OptimumReactionVelocity(max_reaction_vel, SAV, beta_ratio) + + ! calculate moisture dampening coefficient [0-1] + moist_coeff = MoistureCoefficient(moisture, MEF) + + ReactionIntensity = opt_reaction_vel*fuel_loading*SF_val_fuel_energy* & + moist_coeff*SF_val_miner_damp -end module SFEquationsMod \ No newline at end of file + end function ReactionIntensity + +end module SFEquationsMod diff --git a/fire/SFMainMod.F90 b/fire/SFMainMod.F90 index 947159a67c..d6b632e082 100644 --- a/fire/SFMainMod.F90 +++ b/fire/SFMainMod.F90 @@ -180,7 +180,7 @@ subroutine UpdateFuelCharacteristics(currentSite) ! update fuel loading [kgC/m2] litter => currentPatch%litter(element_pos(carbon12_element)) - call currentPatch%fuel%UpdateLoading(sum(litter%leaf_fines(:)), & + call currentPatch%fuel%UpdateLoading(sum(litter%leaf_fines(:)), & litter%ag_cwd(1), litter%ag_cwd(2), litter%ag_cwd(3), litter%ag_cwd(4), & currentPatch%livegrass) @@ -211,29 +211,23 @@ subroutine CalculateSurfaceRateOfSpread(currentSite) ! each patch of a site ! - use SFParamsMod, only : SF_val_miner_total, SF_val_part_dens, SF_val_miner_damp - use SFParamsMod, only : SF_val_fuel_energy - use FatesConstantsMod, only : nearzero + use SFParamsMod, only : SF_val_miner_total, SF_val_part_dens + use SFEquationsMod, only : OptimumPackingRatio, ReactionIntensity ! ARGUMENTS: type(ed_site_type), intent(in), target :: currentSite ! site object ! LOCALS: - type(fates_patch_type), pointer :: currentPatch ! patch object - real(r8) :: beta ! packing ratio [unitless] - real(r8) :: beta_op ! optimum packing ratio [unitless] - real(r8) :: ir ! reaction intensity [kJ/m2/min] - real(r8) :: xi ! propagating flux ratio [unitless] - real(r8) :: eps ! effective heating number [unitless] - real(r8) :: phi_wind ! wind factor [unitless] - real(r8) :: q_ig ! heat of pre-ignition [kJ/kg] - real(r8) :: reaction_v_opt ! optimum reaction velocity [/min] - real(r8) :: reaction_v_max ! maximum reaction velocity [/min] - real(r8) :: moist_damp ! moisture dampening coefficient [unitless] - real(r8) :: mw_weight ! ratio of fuel moisture to extinction moisture [unitless] - real(r8) :: beta_ratio ! relative packing ratio [unitless] - real(r8) :: a_beta ! dummy variable - real(r8) :: a,b,c,e ! dummy variables + type(fates_patch_type), pointer :: currentPatch ! patch object + real(r8) :: beta ! packing ratio [unitless] + real(r8) :: beta_op ! optimum packing ratio [unitless] + real(r8) :: beta_ratio ! relative packing ratio [unitless] + real(r8) :: i_r ! reaction intensity [kJ/m2/min] + real(r8) :: xi ! propagating flux ratio [unitless] + real(r8) :: eps ! effective heating number [unitless] + real(r8) :: phi_wind ! wind factor [unitless] + real(r8) :: q_ig ! heat of pre-ignition [kJ/kg] + real(r8) :: b,c,e ! dummy variables ! CONSTANTS: real(r8), parameter :: q_dry = 581.0_r8 ! heat of pre-ignition of dry fuels [kJ/kg] @@ -242,27 +236,28 @@ subroutine CalculateSurfaceRateOfSpread(currentSite) do while(associated(currentPatch)) if (currentPatch%nocomp_pft_label /= nocomp_bareground .and. & currentPatch%fuel%non_trunk_loading > nearzero) then - - ! remove mineral content from fuel load per Thonicke 2010 - currentPatch%fuel%non_trunk_loading = currentPatch%fuel%non_trunk_loading*(1.0_r8 - SF_val_miner_total) - ! beta = packing ratio (unitless) - ! fraction of fuel array volume occupied by fuel or compactness of fuel bed + ! fraction of fuel array volume occupied by fuel, i.e. compactness of fuel bed beta = currentPatch%fuel%bulk_density_notrunks/SF_val_part_dens - - ! Equation A6 in Thonicke et al. 2010 - ! packing ratio (unitless) - if (currentPatch%fuel%SAV_notrunks < nearzero) then - beta_op = 0.0_r8 - else - beta_op = 0.200395_r8*(currentPatch%fuel%SAV_notrunks**(-0.8189_r8)) - end if + + ! optimum packing ratio [unitless] + beta_op = OptimumPackingRatio(currentPatch%fuel%SAV_notrunks) + + ! relative packing ratio if (beta_op < nearzero) then beta_ratio = 0.0_r8 else beta_ratio = beta/beta_op end if - + + ! remove mineral content from fuel load per Thonicke 2010 + currentPatch%fuel%non_trunk_loading = currentPatch%fuel%non_trunk_loading*(1.0_r8 - SF_val_miner_total) + + ! reaction intensity [kJ/m2/min] + i_r = ReactionIntensity(currentPatch%fuel%non_trunk_loading/0.45_r8, & + currentPatch%fuel%SAV_notrunks, beta_ratio, & + currentPatch%fuel%average_moisture_notrunks, currentPatch%fuel%MEF_notrunks) + ! ---heat of pre-ignition--- ! Equation A4 in Thonicke et al. 2010 ! Rothermel EQ12= 250 Btu/lb + 1116 Btu/lb * average_moisture @@ -291,31 +286,6 @@ subroutine CalculateSurfaceRateOfSpread(currentSite) xi = (exp((0.792_r8 + 3.7597_r8*(currentPatch%fuel%SAV_notrunks**0.5_r8))*(beta+0.1_r8)))/ & (192_r8+7.9095_r8*currentPatch%fuel%SAV_notrunks) - ! ---reaction intensity---- - ! Equation in table A1 Thonicke et al. 2010. - a = 8.9033_r8*(currentPatch%fuel%SAV_notrunks**(-0.7913_r8)) - a_beta = exp(a*(1.0_r8-beta_ratio)) ! dummy variable for reaction_v_opt equation - - ! Equation in table A1 Thonicke et al. 2010. - ! reaction_v_max and reaction_v_opt = reaction velocity in units of per min - ! reaction_v_max = Equation 36 in Rothermel 1972 and Fig 12 - reaction_v_max = 1.0_r8/(0.0591_r8 + 2.926_r8*(currentPatch%fuel%SAV_notrunks**(-1.5_r8))) - ! reaction_v_opt = Equation 38 in Rothermel 1972 and Fig 11 - reaction_v_opt = reaction_v_max*(beta_ratio**a)*a_beta - - ! mw_weight = relative fuel moisture/fuel moisture of extinction - ! average values for litter pools (dead leaves, twigs, small and large branches) plus grass - mw_weight = currentPatch%fuel%average_moisture_notrunks/currentPatch%fuel%MEF_notrunks - - ! Equation in table A1 Thonicke et al. 2010. - ! moist_damp is unitless - moist_damp = max(0.0_r8, (1.0_r8 - (2.59_r8*mw_weight) + (5.11_r8*(mw_weight**2.0_r8)) - & - (3.52_r8*(mw_weight**3.0_r8)))) - - ! ir = reaction intenisty in kJ/m2/min - ! currentPatch%fuel%non_trunk_loading converted from kgC/m2 to kgBiomass/m2 for ir calculation - ir = reaction_v_opt*(currentPatch%fuel%non_trunk_loading/0.45_r8)*SF_val_fuel_energy*moist_damp*SF_val_miner_damp - if (((currentPatch%fuel%bulk_density_notrunks) <= 0.0_r8) .or. (eps <= 0.0_r8) .or. (q_ig <= 0.0_r8)) then currentPatch%ROS_front = 0.0_r8 else ! Equation 9. Thonicke et al. 2010. @@ -329,10 +299,11 @@ subroutine CalculateSurfaceRateOfSpread(currentSite) end if currentPatch => currentPatch%younger - end do end subroutine CalculateSurfaceRateOfSpread + + !--------------------------------------------------------------------------------------- !***************************************************************** subroutine ground_fuel_consumption ( currentSite ) From f7ed823ce551c3aa6c19f5cbb253d0f63f962071 Mon Sep 17 00:00:00 2001 From: adrifoster Date: Mon, 2 Dec 2024 12:16:03 -0700 Subject: [PATCH 088/111] create temp variable --- fire/SFMainMod.F90 | 13 +++++++------ main/FatesHistoryInterfaceMod.F90 | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/fire/SFMainMod.F90 b/fire/SFMainMod.F90 index 97bbcb6d54..2066543478 100644 --- a/fire/SFMainMod.F90 +++ b/fire/SFMainMod.F90 @@ -228,6 +228,7 @@ subroutine rate_of_spread (currentSite) real(r8) beta_ratio ! ratio of beta/beta_op real(r8) a_beta ! dummy variable for product of a* beta_ratio for react_v_opt equation real(r8) a,b,c,e ! function of fuel sav + real(r8) non_mineral_loading ! non-mineral loading [kgC/m2] logical, parameter :: debug_windspeed = .false. !for debugging real(r8),parameter :: q_dry = 581.0_r8 !heat of pre-ignition of dry fuels (kJ/kg) @@ -239,7 +240,7 @@ subroutine rate_of_spread (currentSite) if(currentPatch%nocomp_pft_label .ne. nocomp_bareground .and. currentPatch%fuel%non_trunk_loading > nearzero) then ! remove mineral content from net fuel load per Thonicke 2010 for ir calculation - currentPatch%fuel%non_trunk_loading = currentPatch%fuel%non_trunk_loading * (1.0_r8 - SF_val_miner_total) !net of minerals + non_mineral_loading = currentPatch%fuel%non_trunk_loading*(1.0_r8 - SF_val_miner_total) !net of minerals ! ----start spreading--- @@ -330,8 +331,8 @@ subroutine rate_of_spread (currentSite) (3.52_r8*(mw_weight**3.0_r8)))) ! ir = reaction intenisty in kJ/m2/min - ! currentPatch%fuel%non_trunk_loading converted from kgC/m2 to kgBiomass/m2 for ir calculation - ir = reaction_v_opt*(currentPatch%fuel%non_trunk_loading/0.45_r8)*SF_val_fuel_energy*moist_damp*SF_val_miner_damp + ! non_mineral_loading converted from kgC/m2 to kgBiomass/m2 for ir calculation + ir = reaction_v_opt*(non_mineral_loading/0.45_r8)*SF_val_fuel_energy*moist_damp*SF_val_miner_damp ! write(fates_log(),*) 'ir',gamma_aptr,moist_damp,SF_val_fuel_energy,SF_val_miner_damp @@ -368,6 +369,7 @@ subroutine ground_fuel_consumption ( currentSite ) real(r8) :: moist !effective fuel moisture real(r8) :: tau_b(num_fuel_classes) !lethal heating rates for each fuel class (min) real(r8) :: fc_ground(num_fuel_classes) !total amount of fuel consumed per area of burned ground (kg C / m2 of burned area) + real(r8) :: non_mineral_loading ! non-mineral loading [kgC/m2] integer :: tr_sf, tw_sf, dl_sf, lg_sf integer :: c @@ -422,8 +424,6 @@ subroutine ground_fuel_consumption ( currentSite ) FC_ground(dl_sf) = currentPatch%fuel%frac_burnt(dl_sf) * sum(litt_c%leaf_fines(:)) FC_ground(lg_sf) = currentPatch%fuel%frac_burnt(lg_sf) * currentPatch%livegrass - !call currentPatch%fuel%BurnFuel(fc_ground) - ! Following used for determination of cambial kill follows from Peterson & Ryan (1986) scheme ! less empirical cf current scheme used in SPITFIRE which attempts to mesh Rothermel ! and P&R, and while solving potential inconsistencies, actually results in BIG values for @@ -431,8 +431,9 @@ subroutine ground_fuel_consumption ( currentSite ) ! taul is the duration of the lethal heating. ! The /10 is to convert from kgC/m2 into gC/cm2, as in the Peterson and Ryan paper #Rosie,Jun 2013 + non_mineral_loading = currentPatch%fuel%non_trunk_loading*(1.0_r8 - SF_val_miner_total) !net of minerals do c = 1,num_fuel_classes - tau_b(c) = 39.4_r8 *(currentPatch%fuel%frac_loading(c)*currentPatch%fuel%non_trunk_loading/0.45_r8/10._r8)* & + tau_b(c) = 39.4_r8 *(currentPatch%fuel%frac_loading(c)*non_mineral_loading/0.45_r8/10._r8)* & (1.0_r8-((1.0_r8-currentPatch%fuel%frac_burnt(c))**0.5_r8)) enddo tau_b(tr_sf) = 0.0_r8 diff --git a/main/FatesHistoryInterfaceMod.F90 b/main/FatesHistoryInterfaceMod.F90 index 55287c40f5..612e917ffa 100644 --- a/main/FatesHistoryInterfaceMod.F90 +++ b/main/FatesHistoryInterfaceMod.F90 @@ -7097,7 +7097,7 @@ subroutine define_history_vars(this, initialize_variables) index = ih_fire_intensity_si_age) call this%set_history_var(vname='FATES_FUEL_AMOUNT_AP', units='kg m-2', & - long='spitfire ground fuel (kg carbon per m2) related to FATES_ROS (omits 1000hr fuels) within each patch age bin (divide by FATES_PATCHAREA_AP to get fuel per unit area of that-age patch)', & + long='spitfire ground fuel (kg carbon per m2) related to FATES_ROS (omits 1000hr fuels, includes mineral portion) within each patch age bin (divide by FATES_PATCHAREA_AP to get fuel per unit area of that-age patch)', & use_default='active', avgflag='A', vtype=site_age_r8, hlms='CLM:ALM', & upfreq=group_dyna_complx, ivar=ivar, initialize=initialize_variables, & index = ih_fire_sum_fuel_si_age) From 0eb912c11c467b63b9cce294521f36e6da47b0c2 Mon Sep 17 00:00:00 2001 From: Shijie Shu <92333861+sshu88@users.noreply.github.com> Date: Mon, 2 Dec 2024 13:46:00 -0800 Subject: [PATCH 089/111] Update EDLoggingMortalityMod.F90 Use named integer constants for "harvest_tag" --- biogeochem/EDLoggingMortalityMod.F90 | 33 ++++++++++++---------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/biogeochem/EDLoggingMortalityMod.F90 b/biogeochem/EDLoggingMortalityMod.F90 index a091e4f3d6..c7d393f976 100644 --- a/biogeochem/EDLoggingMortalityMod.F90 +++ b/biogeochem/EDLoggingMortalityMod.F90 @@ -68,6 +68,7 @@ module EDLoggingMortalityMod use FatesConstantsMod , only : hlm_harvest_area_fraction use FatesConstantsMod , only : hlm_harvest_carbon use FatesConstantsMod, only : fates_check_param_set + use FatesConstantsMod, only : fates_no_harvest_debt, fates_with_harvest_debt, fates_bypass_harvest_debt implicit none private @@ -223,9 +224,6 @@ subroutine LoggingMortality_frac( pft_i, dbh, canopy_layer, lmort_direct, & integer, intent(out) :: harvest_tag(:) ! tag to record the harvest status ! for the calculation of harvest debt in C-based ! harvest mode - ! 0 - successful; - ! 1 - unsuccessful since not enough carbon - ! 2 - not applicable ! Local variables integer :: cur_harvest_tag ! the harvest tag of the cohort today @@ -266,9 +264,9 @@ subroutine LoggingMortality_frac( pft_i, dbh, canopy_layer, lmort_direct, & call get_harvest_rate_area (patch_anthro_disturbance_label, hlm_harvest_catnames, & hlm_harvest_rates, frac_site_primary, secondary_age, harvest_rate) - ! For area-based harvest, harvest_tag shall always be 2 (not applicable). - harvest_tag = 2 - cur_harvest_tag = 2 + ! For area-based harvest, harvest_tag shall always be fates_bypass_harvest_debt (not applicable). + harvest_tag = fates_bypass_harvest_debt + cur_harvest_tag = fates_bypass_harvest_debt if (fates_global_verbose()) then write(fates_log(), *) 'Successfully Read Harvest Rate from HLM.', hlm_harvest_rates(:), harvest_rate @@ -291,7 +289,7 @@ subroutine LoggingMortality_frac( pft_i, dbh, canopy_layer, lmort_direct, & ! transfer of area to secondary land is based on overall area affected, not just logged crown area ! l_degrad accounts for the affected area between logged crowns if(prt_params%woody(pft_i) == itrue)then ! only set logging rates for trees - if (cur_harvest_tag == 0 .or. cur_harvest_tag == 2) then + if (cur_harvest_tag == fates_no_harvest_debt .or. cur_harvest_tag == fates_bypass_harvest_debt) then ! direct logging rates, based on dbh min and max criteria if (dbh >= logging_dbhmin .and. .not. & ((logging_dbhmax < fates_check_param_set) .and. (dbh >= logging_dbhmax )) ) then @@ -550,10 +548,7 @@ subroutine get_harvest_rate_carbon (patch_anthro_disturbance_label, hlm_harvest_ real(r8), intent(in) :: secondary_age ! patch level age_since_anthro_disturbance real(r8), intent(in) :: harvestable_forest_c(:) ! site level forest c matching criteria available for harvest, kgC site-1 real(r8), intent(out) :: harvest_rate ! area fraction - integer, intent(inout) :: harvest_tag(:) ! 0. normal harvest; 1. current site does not have enough C but - ! can perform harvest by ignoring criteria; 2. current site does - ! not have enough carbon - ! This harvest tag shall be a patch level variable but since all + integer, intent(inout) :: harvest_tag(:) ! This harvest tag can be raused to patch level but since all ! logging functions happen within cohort loop we can only put the ! calculation here. Can think about optimizing the logging calculation ! in the future. @@ -573,7 +568,7 @@ subroutine get_harvest_rate_carbon (patch_anthro_disturbance_label, hlm_harvest_ harvest_rate = 0._r8 harvest_rate_c = 0._r8 harvest_rate_supply = 0._r8 - harvest_tag(:) = 2 + harvest_tag(:) = fates_bypass_harvest_debt ! Since we have five harvest categories from forcing data but in FATES non-forest harvest ! is merged with forest harvest, we only have three logging type in FATES (primary, secondary @@ -606,9 +601,9 @@ subroutine get_harvest_rate_carbon (patch_anthro_disturbance_label, hlm_harvest_ if(hlm_harvest_catnames(h_index) .eq. "HARVEST_VH1" ) then if(harvestable_forest_c(h_index) >= harvest_rate_c) then harvest_rate_supply = harvest_rate_supply + harvestable_forest_c(h_index) - harvest_tag(h_index) = 0 + harvest_tag(h_index) = fates_no_harvest_debt else - harvest_tag(h_index) = 1 + harvest_tag(h_index) = fates_with_harvest_debt end if end if else if (patch_anthro_disturbance_label .eq. secondaryforest .and. & @@ -616,9 +611,9 @@ subroutine get_harvest_rate_carbon (patch_anthro_disturbance_label, hlm_harvest_ if(hlm_harvest_catnames(h_index) .eq. "HARVEST_SH1" ) then if(harvestable_forest_c(h_index) >= harvest_rate_c) then harvest_rate_supply = harvest_rate_supply + harvestable_forest_c(h_index) - harvest_tag(h_index) = 0 + harvest_tag(h_index) = fates_no_harvest_debt else - harvest_tag(h_index) = 1 + harvest_tag(h_index) = fates_with_harvest_debt end if end if else if (patch_anthro_disturbance_label .eq. secondaryforest .and. & @@ -626,9 +621,9 @@ subroutine get_harvest_rate_carbon (patch_anthro_disturbance_label, hlm_harvest_ if(hlm_harvest_catnames(h_index) .eq. "HARVEST_SH2" ) then if(harvestable_forest_c(h_index) >= harvest_rate_c) then harvest_rate_supply = harvest_rate_supply + harvestable_forest_c(h_index) - harvest_tag(h_index) = 0 + harvest_tag(h_index) = fates_no_harvest_debt else - harvest_tag(h_index) = 1 + harvest_tag(h_index) = fates_with_harvest_debt end if end if end if @@ -1168,7 +1163,7 @@ subroutine get_harvest_debt(site_in, bc_in, harvest_tag) end do ! Next we get the harvest debt through the harvest tag do h_index = 1, hlm_num_lu_harvest_cats - if (harvest_tag(h_index) .eq. 1) then + if (harvest_tag(h_index) .eq. fates_with_harvest_debt) then if(bc_in%hlm_harvest_catnames(h_index) .eq. "HARVEST_VH1") then site_in%resources_management%harvest_debt = site_in%resources_management%harvest_debt + & harvest_debt_pri From 914393a4c69bf1f00e01750cecce69012d3b915d Mon Sep 17 00:00:00 2001 From: Shijie Shu <92333861+sshu88@users.noreply.github.com> Date: Mon, 2 Dec 2024 13:49:17 -0800 Subject: [PATCH 090/111] Update FatesConstantsMod.F90 Define named constants for "harvest_tag" --- main/FatesConstantsMod.F90 | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/main/FatesConstantsMod.F90 b/main/FatesConstantsMod.F90 index 66a9c56d8d..d38382a814 100644 --- a/main/FatesConstantsMod.F90 +++ b/main/FatesConstantsMod.F90 @@ -69,6 +69,10 @@ module FatesConstantsMod integer, parameter, public :: hlm_harvest_area_fraction = 1 ! Code for harvesting by area integer, parameter, public :: hlm_harvest_carbon = 2 ! Code for harvesting based on carbon extracted. + ! integer labels for specifying harvest debt status + integer, parameter, public :: fates_no_harvest_debt = 0 + integer, parameter, public :: fates_with_harvest_debt = 1 + integer, parameter, public :: fates_bypass_harvest_debt = 2 ! Do not calculate harvest debt for area based harvest ! Error Tolerances From 51a6a37a1df69f696cc43e1eb7d2648ca2e00cd7 Mon Sep 17 00:00:00 2001 From: Adrianna Foster Date: Thu, 5 Dec 2024 10:11:07 -0700 Subject: [PATCH 091/111] bug fix --- fire/SFEquationsMod.F90 | 6 ++++-- fire/SFMainMod.F90 | 6 +++--- main/EDMainMod.F90 | 4 ++-- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/fire/SFEquationsMod.F90 b/fire/SFEquationsMod.F90 index ffd02e844b..77fafb3346 100644 --- a/fire/SFEquationsMod.F90 +++ b/fire/SFEquationsMod.F90 @@ -8,11 +8,13 @@ module SFEquationsMod ! ============================================================================ use FatesConstantsMod, only : r8 => fates_r8 + use FatesConstantsMod, only : nearzero implicit none private public :: OptimumPackingRatio + public :: ReactionIntensity contains @@ -31,9 +33,9 @@ real(r8) function OptimumPackingRatio(SAV) real(r8), parameter :: b = -0.8189_r8 if (SAV < nearzero) then - beta_op = 0.0_r8 + OptimumPackingRatio = 0.0_r8 else - beta_op = a*(SAV**b) + OptimumPackingRatio = a*(SAV**b) end if end function OptimumPackingRatio diff --git a/fire/SFMainMod.F90 b/fire/SFMainMod.F90 index d6b632e082..673a5c1795 100644 --- a/fire/SFMainMod.F90 +++ b/fire/SFMainMod.F90 @@ -37,9 +37,9 @@ module SFMainMod implicit none private - public :: fire_model + public :: DailyFireModel public :: UpdateFuelCharacteristics - public :: rate_of_spread + public :: CalculateSurfaceRateOfSpread public :: ground_fuel_consumption public :: area_burnt_intensity public :: crown_scorching @@ -290,7 +290,7 @@ subroutine CalculateSurfaceRateOfSpread(currentSite) currentPatch%ROS_front = 0.0_r8 else ! Equation 9. Thonicke et al. 2010. ! forward ROS in m/min - currentPatch%ROS_front = (ir*xi*(1.0_r8+phi_wind))/(currentPatch%fuel%bulk_density_notrunks*eps*q_ig) + currentPatch%ROS_front = (i_r*xi*(1.0_r8+phi_wind))/(currentPatch%fuel%bulk_density_notrunks*eps*q_ig) endif ! Equation 10 in Thonicke et al. 2010 ! backward ROS from Can FBP System (1992) in m/min diff --git a/main/EDMainMod.F90 b/main/EDMainMod.F90 index 01111c0626..1a981121fb 100644 --- a/main/EDMainMod.F90 +++ b/main/EDMainMod.F90 @@ -58,7 +58,7 @@ module EDMainMod use FatesSoilBGCFluxMod , only : EffluxIntoLitterPools use FatesSoilBGCFluxMod , only : PrepNutrientAquisitionBCs use FatesSoilBGCFluxMod , only : PrepCH4BCs - use SFMainMod , only : fire_model + use SFMainMod , only : DailyFireModel use FatesSizeAgeTypeIndicesMod, only : get_age_class_index use FatesSizeAgeTypeIndicesMod, only : coagetype_class_index use FatesLitterMod , only : litter_type @@ -211,7 +211,7 @@ subroutine ed_ecosystem_dynamics(currentSite, bc_in, bc_out) ! If there are multiple patches on the site, the bareground patch is avoided ! at the level of the fire_model subroutines. if (currentSite%youngest_patch%patchno .ne. 0) then - call fire_model(currentSite, bc_in) + call DailyFireModel(currentSite, bc_in) end if ! Calculate disturbance and mortality based on previous timestep vegetation. From bc0379972b51dd0e67a18f59c3edda306a6f0cf2 Mon Sep 17 00:00:00 2001 From: Adrianna Foster Date: Fri, 6 Dec 2024 09:19:00 -0700 Subject: [PATCH 092/111] add ros --- fire/SFEquationsMod.F90 | 124 ++++++++++++++++++++++++++++++++++++++-- fire/SFMainMod.F90 | 47 +++++---------- 2 files changed, 133 insertions(+), 38 deletions(-) diff --git a/fire/SFEquationsMod.F90 b/fire/SFEquationsMod.F90 index 77fafb3346..4b078da589 100644 --- a/fire/SFEquationsMod.F90 +++ b/fire/SFEquationsMod.F90 @@ -15,6 +15,11 @@ module SFEquationsMod public :: OptimumPackingRatio public :: ReactionIntensity + public :: HeatofPreignition + public :: EffectiveHeatingNumber + public :: PhiWind + public :: PropagatingFlux + public :: RateOfSpread contains @@ -56,7 +61,7 @@ real(r8) function MaximumReactionVelocity(SAV) end function MaximumReactionVelocity - !--------------------------------------------------------------------------------------- + !------------------------------------------------------------------------------------- real(r8) function OptimumReactionVelocity(max_reaction_vel, SAV, beta_ratio) ! @@ -81,7 +86,7 @@ real(r8) function OptimumReactionVelocity(max_reaction_vel, SAV, beta_ratio) end function OptimumReactionVelocity - !--------------------------------------------------------------------------------------- + !------------------------------------------------------------------------------------- real(r8) function MoistureCoefficient(moisture, MEF) ! @@ -108,7 +113,7 @@ real(r8) function MoistureCoefficient(moisture, MEF) end function MoistureCoefficient - !--------------------------------------------------------------------------------------- + !------------------------------------------------------------------------------------- real(r8) function ReactionIntensity(fuel_loading, SAV, beta_ratio, moisture, MEF) ! @@ -140,9 +145,116 @@ real(r8) function ReactionIntensity(fuel_loading, SAV, beta_ratio, moisture, ME ! calculate moisture dampening coefficient [0-1] moist_coeff = MoistureCoefficient(moisture, MEF) - ReactionIntensity = opt_reaction_vel*fuel_loading*SF_val_fuel_energy* & - moist_coeff*SF_val_miner_damp + ReactionIntensity = opt_reaction_vel*fuel_loading*SF_val_fuel_energy* & + moist_coeff*SF_val_miner_damp + + end function ReactionIntensity + + !------------------------------------------------------------------------------------- + + real(r8) function HeatofPreignition(fuel_moisture) + ! + ! DESCRIPTION: + ! Calculates heat of pre-ignition in kJ/kg + ! + ! Equation A4 in Thonicke et al. 2010 + ! Rothermel EQ12= 250 Btu/lb + 1116 Btu/lb * average_moisture + ! conversion of Rothermel (1972) EQ12 in BTU/lb to current kJ/kg + ! + + ! ARGUMENTS: + real(r8), intent(in) :: fuel_moisture ! fuel moisture [m3/m3] + + ! CONTANTS: + real(r8), parameter :: q_dry = 581.0_r8 ! heat of pre-ignition of dry fuels [kJ/kg] + + HeatofPreignition = q_dry + 2594.0_r8*fuel_moisture + + end function HeatofPreignition - end function ReactionIntensity + !------------------------------------------------------------------------------------- + real(r8) function EffectiveHeatingNumber(SAV) + ! + ! DESCRIPTION: + ! Calculates effective heating number [unitless] + ! + ! Equation A3 in Thonicke et al. 2010 + + ! ARGUMENTS: + real(r8), intent(in) :: SAV ! fuel surface area to volume ratio [/cm] + + EffectiveHeatingNumber = exp(-4.528_r8/SAV) + + end function EffectiveHeatingNumber + + !------------------------------------------------------------------------------------- + + real(r8) function PhiWind(wind_speed, beta_ratio, SAV) + ! + ! DESCRIPTION: + ! Calculates wind factor [unitless] + ! + + ! ARGUMENTS: + real(r8), intent(in) :: wind_speed ! wind speed [m/min] + real(r8), intent(in) :: beta_ratio ! relative packing ratio [unitless] + real(r8), intent(in) :: SAV ! fuel surface area to volume ratio [/cm] + + ! LOCALS: + real(r8) :: b, c, e ! temporary variables + + ! Equation A7 in Thonicke et al. 2010 per eqn 49 from Rothermel 1972 + b = 0.15988_r8*(SAV**0.54_r8) + + ! Equation A8 in Thonicke et al. 2010 per eqn 48 from Rothermel 1972 + c = 7.47_r8*(exp(-0.8711_r8*(SAV**0.55_r8))) + + ! Equation A9 in Thonicke et al. 2010 (appears to have typo, using coefficient Eq. 50 Rothermel 1972) + e = 0.715_r8*(exp(-0.01094_r8*SAV)) + + ! Equation A5 in Thonicke et al. 2010 + ! convert wind_speed (wind at elev relevant to fire) from m/min to ft/min for Rothermel ROS Eq. + PhiWind = c*((3.281_r8*wind_speed)**b)*(beta_ratio**(-e)) + + end function PhiWind + + !------------------------------------------------------------------------------------- + + real(r8) function PropagatingFlux(beta, SAV) + ! + ! DESCRIPTION: + ! Calculates propagating flux [unitless] + ! Equation A2 in Thonicke et al. 2010 and Eq. 42 Rothermel 1972 + ! + + ! ARGUMENTS: + real(r8), intent(in) :: beta ! packing ratio [unitless] + real(r8), intent(in) :: SAV ! fuel surface area to volume ratio [/cm] + + PropagatingFlux = (exp((0.792_r8 + 3.7597_r8*(SAV**0.5_r8))*(beta + 0.1_r8)))/ & + (192.0_r8 + 7.9095_r8*SAV) + + end function PropagatingFlux + + !------------------------------------------------------------------------------------- + + real(r8) function RateOfSpread(beta, SAV) + ! + ! DESCRIPTION: + ! Calculates rate of spread + ! Equation A2 in Thonicke et al. 2010 and Eq. 42 Rothermel 1972 + ! + + ! ARGUMENTS: + real(r8), intent(in) :: beta ! packing ratio [unitless] + real(r8), intent(in) :: SAV ! fuel surface area to volume ratio [/cm] + + PropagatingFlux = (exp((0.792_r8 + 3.7597_r8*(SAV**0.5_r8))*(beta + 0.1_r8)))/ & + (192.0_r8 + 7.9095_r8*SAV) + + end function RateOfSpread + + !------------------------------------------------------------------------------------- + end module SFEquationsMod diff --git a/fire/SFMainMod.F90 b/fire/SFMainMod.F90 index 673a5c1795..3308d6edc0 100644 --- a/fire/SFMainMod.F90 +++ b/fire/SFMainMod.F90 @@ -213,6 +213,8 @@ subroutine CalculateSurfaceRateOfSpread(currentSite) use SFParamsMod, only : SF_val_miner_total, SF_val_part_dens use SFEquationsMod, only : OptimumPackingRatio, ReactionIntensity + use SFEquationsMod, only : HeatofPreignition, EffectiveHeatingNumber + use SFEquationsMod, only : PhiWind, PropagatingFlux ! ARGUMENTS: type(ed_site_type), intent(in), target :: currentSite ! site object @@ -227,10 +229,6 @@ subroutine CalculateSurfaceRateOfSpread(currentSite) real(r8) :: eps ! effective heating number [unitless] real(r8) :: phi_wind ! wind factor [unitless] real(r8) :: q_ig ! heat of pre-ignition [kJ/kg] - real(r8) :: b,c,e ! dummy variables - - ! CONSTANTS: - real(r8), parameter :: q_dry = 581.0_r8 ! heat of pre-ignition of dry fuels [kJ/kg] currentPatch => currentSite%oldest_patch do while(associated(currentPatch)) @@ -243,7 +241,7 @@ subroutine CalculateSurfaceRateOfSpread(currentSite) ! optimum packing ratio [unitless] beta_op = OptimumPackingRatio(currentPatch%fuel%SAV_notrunks) - ! relative packing ratio + ! relative packing ratio [unitless] if (beta_op < nearzero) then beta_ratio = 0.0_r8 else @@ -257,34 +255,19 @@ subroutine CalculateSurfaceRateOfSpread(currentSite) i_r = ReactionIntensity(currentPatch%fuel%non_trunk_loading/0.45_r8, & currentPatch%fuel%SAV_notrunks, beta_ratio, & currentPatch%fuel%average_moisture_notrunks, currentPatch%fuel%MEF_notrunks) + + ! heat of preignition [kJ/kg] + q_ig = HeatofPreignition(currentPatch%fuel%average_moisture_notrunks) + + ! effective heating number [unitless] + eps = EffectiveHeatingNumber(currentPatch%fuel%SAV_notrunks) - ! ---heat of pre-ignition--- - ! Equation A4 in Thonicke et al. 2010 - ! Rothermel EQ12= 250 Btu/lb + 1116 Btu/lb * average_moisture - ! conversion of Rothermel (1972) EQ12 in BTU/lb to current kJ/kg - ! q_ig in kJ/kg - q_ig = q_dry + 2594.0_r8*currentPatch%fuel%average_moisture_notrunks - - ! ---effective heating number--- - ! Equation A3 in Thonicke et al. 2010. - eps = exp(-4.528_r8/currentPatch%fuel%SAV_notrunks) - ! Equation A7 in Thonicke et al. 2010 per eqn 49 from Rothermel 1972 - b = 0.15988_r8*(currentPatch%fuel%SAV_notrunks**0.54_r8) - ! Equation A8 in Thonicke et al. 2010 per eqn 48 from Rothermel 1972 - c = 7.47_r8*(exp(-0.8711_r8*(currentPatch%fuel%SAV_notrunks**0.55_r8))) - ! Equation A9 in Thonicke et al. 2010. (appears to have typo, using coefficient eqn.50 Rothermel 1972) - e = 0.715_r8*(exp(-0.01094_r8*currentPatch%fuel%SAV_notrunks)) - - ! Equation A5 in Thonicke et al. 2010 - ! phi_wind (unitless) - ! convert current_wspeed (wind at elev relevant to fire) from m/min to ft/min for Rothermel ROS eqn - phi_wind = c*((3.281_r8*currentSite%fireWeather%effective_windspeed)**b)*(beta_ratio**(-e)) - - ! ---propagating flux---- - ! Equation A2 in Thonicke et al.2010 and Eq. 42 Rothermel 1972 - ! xi (unitless) - xi = (exp((0.792_r8 + 3.7597_r8*(currentPatch%fuel%SAV_notrunks**0.5_r8))*(beta+0.1_r8)))/ & - (192_r8+7.9095_r8*currentPatch%fuel%SAV_notrunks) + ! wind factor [unitless] + phi_wind = PhiWind(currentSite%fireWeather%effective_windspeed, beta_ratio, & + currentPatch%fuel%SAV_notrunks) + + ! propagating flux [unitless] + xi = PropagatingFlux(beta, currentPatch%fuel%SAV_notrunks) if (((currentPatch%fuel%bulk_density_notrunks) <= 0.0_r8) .or. (eps <= 0.0_r8) .or. (q_ig <= 0.0_r8)) then currentPatch%ROS_front = 0.0_r8 From 426899be6b5409211411eaa6bd3a414868663d2d Mon Sep 17 00:00:00 2001 From: Adrianna Foster Date: Fri, 6 Dec 2024 09:32:07 -0700 Subject: [PATCH 093/111] full refactor of ros --- fire/FatesFuelMod.F90 | 2 +- fire/SFEquationsMod.F90 | 47 +++++++++++++++++++++++++++++++++-------- fire/SFMainMod.F90 | 26 +++++++++++------------ 3 files changed, 51 insertions(+), 24 deletions(-) diff --git a/fire/FatesFuelMod.F90 b/fire/FatesFuelMod.F90 index 8aa8d00d74..4491b3d246 100644 --- a/fire/FatesFuelMod.F90 +++ b/fire/FatesFuelMod.F90 @@ -323,7 +323,7 @@ subroutine AverageBulkDensity_NoTrunks(this, bulk_density) ! ARGUMENTS: class(fuel_type), intent(inout) :: this ! fuel class - real(r8), intent(in) :: bulk_density(num_fuel_classes) ! bulk density of all fuel types [kg/m2] + real(r8), intent(in) :: bulk_density(num_fuel_classes) ! bulk density of all fuel types [kg/m3] ! LOCALS: integer :: i ! looping index diff --git a/fire/SFEquationsMod.F90 b/fire/SFEquationsMod.F90 index dd148e5919..254094fd37 100644 --- a/fire/SFEquationsMod.F90 +++ b/fire/SFEquationsMod.F90 @@ -19,7 +19,8 @@ module SFEquationsMod public :: EffectiveHeatingNumber public :: PhiWind public :: PropagatingFlux - public :: RateOfSpread + public :: ForwardRateOfSpread + public :: BackwardRateOfSpread contains @@ -28,6 +29,7 @@ real(r8) function OptimumPackingRatio(SAV) ! DESCRIPTION: ! Calculates optimum packing ratio [unitless] ! Equation A6 in Thonicke et al. 2010 + ! Rothermel 1972 Eq. 37 ! ! ARGUMENTS: @@ -241,22 +243,49 @@ end function PropagatingFlux !------------------------------------------------------------------------------------- - real(r8) function RateOfSpread(beta, SAV) + real(r8) function ForwardRateOfSpread(bulk_density, eps, q_ig, i_r, xi, phi_wind, ros) ! ! DESCRIPTION: - ! Calculates rate of spread - ! Equation A2 in Thonicke et al. 2010 and Eq. 42 Rothermel 1972 + ! Calculates forward rate of spread [m/min] + ! Equation 9. Thonicke et al. 2010 ! ! ARGUMENTS: - real(r8), intent(in) :: beta ! packing ratio [unitless] - real(r8), intent(in) :: SAV ! fuel surface area to volume ratio [/cm] + real(r8), intent(in) :: bulk_density ! fulk bulk density [kg/m3] + real(r8), intent(in) :: eps ! effective heating number [unitless] + real(r8), intent(in) :: q_ig ! heat of preignition [kJ/kg] + real(r8), intent(in) :: i_r ! reaction intensity [kJ/m2/min] + real(r8), intent(in) :: xi ! propagating flux [unitless] + real(r8), intent(in) :: phi_wind ! wind factor [unitless] - PropagatingFlux = (exp((0.792_r8 + 3.7597_r8*(SAV**0.5_r8))*(beta + 0.1_r8)))/ & - (192.0_r8 + 7.9095_r8*SAV) + if (((bulk_density) <= 0.0_r8) .or. (eps <= 0.0_r8) .or. (q_ig <= 0.0_r8)) then + ForwardRateOfSpread = 0.0_r8 + else + ForwardRateOfSpread = (i_r*xi*(1.0_r8 + phi_wind))/(bulk_density*eps*q_ig) + endif - end function RateOfSpread + end function ForwardRateOfSpread + !------------------------------------------------------------------------------------- + + real(r8) function BackwardRateOfSpread(ros_front, wind_speed) + ! + ! DESCRIPTION: + ! Calculates backwards rate of spread [m/min] + ! Equation 10 in Thonicke et al. 2010 + ! backward ROS from Can FBP System (1992) + ! backward ROS wind not changed by vegetation + ! + + ! ARGUMENTS: + real(r8), intent(in) :: ros_front ! forward rate of spread [m/min] + real(r8), intent(in) :: wind_speed ! wind speed [m/min] + + BackwardRateOfSpread = ros_front*exp(-0.012_r8*wind_speed) + + end function BackwardRateOfSpread + !------------------------------------------------------------------------------------- + end module SFEquationsMod diff --git a/fire/SFMainMod.F90 b/fire/SFMainMod.F90 index 4f85c8098d..8de508194d 100644 --- a/fire/SFMainMod.F90 +++ b/fire/SFMainMod.F90 @@ -214,7 +214,8 @@ subroutine CalculateSurfaceRateOfSpread(currentSite) use SFParamsMod, only : SF_val_miner_total, SF_val_part_dens use SFEquationsMod, only : OptimumPackingRatio, ReactionIntensity use SFEquationsMod, only : HeatofPreignition, EffectiveHeatingNumber - use SFEquationsMod, only : PhiWind, PropagatingFlux + use SFEquationsMod, only : PhiWind, PropagatingFlux + use SFEquationsMod, only : ForwardRateOfSpread, BackwardRateOfSpread ! ARGUMENTS: type(ed_site_type), intent(in), target :: currentSite ! site object @@ -240,7 +241,6 @@ subroutine CalculateSurfaceRateOfSpread(currentSite) beta = currentPatch%fuel%bulk_density_notrunks/SF_val_part_dens ! optimum packing ratio [unitless] - ! Rothermel 1972 Eq. 37 beta_op = OptimumPackingRatio(currentPatch%fuel%SAV_notrunks) ! relative packing ratio [unitless] @@ -269,18 +269,16 @@ subroutine CalculateSurfaceRateOfSpread(currentSite) currentPatch%fuel%SAV_notrunks) ! propagating flux [unitless] - xi = PropagatingFlux(beta, currentPatch%fuel%SAV_notrunks) - - if (((currentPatch%fuel%bulk_density_notrunks) <= 0.0_r8) .or. (eps <= 0.0_r8) .or. (q_ig <= 0.0_r8)) then - currentPatch%ROS_front = 0.0_r8 - else ! Equation 9. Thonicke et al. 2010. - ! forward ROS in m/min - currentPatch%ROS_front = (i_r*xi*(1.0_r8+phi_wind))/(currentPatch%fuel%bulk_density_notrunks*eps*q_ig) - endif - ! Equation 10 in Thonicke et al. 2010 - ! backward ROS from Can FBP System (1992) in m/min - ! backward ROS wind not changed by vegetation - currentPatch%ROS_back = currentPatch%ROS_front*exp(-0.012_r8*currentSite%wind) + xi = PropagatingFlux(beta, currentPatch%fuel%SAV_notrunks) + + ! forward rate of spread [m/min] + currentPatch%ROS_front = ForwardRateOfSpread(currentPatch%fuel%bulk_density_notrunks, & + eps, q_ig, i_r, xi, phi_wind) + + ! backwards rate of spread [m/min] + ! backward ROS wind not changed by vegetation - so use wind, not effective_windspeed + currentPatch%ROS_back = BackwardRateOfSpread(currentPatch%ROS_front, & + currentSite%wind) end if currentPatch => currentPatch%younger From acde77c0504ddda06115ad8149a57b447191d4a6 Mon Sep 17 00:00:00 2001 From: Adrianna Foster Date: Fri, 6 Dec 2024 09:42:20 -0700 Subject: [PATCH 094/111] fix bugs --- fire/SFEquationsMod.F90 | 31 +++++++++++++++---------------- fire/SFMainMod.F90 | 8 +++----- 2 files changed, 18 insertions(+), 21 deletions(-) diff --git a/fire/SFEquationsMod.F90 b/fire/SFEquationsMod.F90 index 254094fd37..73c6a09fe2 100644 --- a/fire/SFEquationsMod.F90 +++ b/fire/SFEquationsMod.F90 @@ -243,7 +243,7 @@ end function PropagatingFlux !------------------------------------------------------------------------------------- - real(r8) function ForwardRateOfSpread(bulk_density, eps, q_ig, i_r, xi, phi_wind, ros) + real(r8) function ForwardRateOfSpread(bulk_density, eps, q_ig, i_r, xi, phi_wind) ! ! DESCRIPTION: ! Calculates forward rate of spread [m/min] @@ -258,7 +258,7 @@ real(r8) function ForwardRateOfSpread(bulk_density, eps, q_ig, i_r, xi, phi_wind real(r8), intent(in) :: xi ! propagating flux [unitless] real(r8), intent(in) :: phi_wind ! wind factor [unitless] - if (((bulk_density) <= 0.0_r8) .or. (eps <= 0.0_r8) .or. (q_ig <= 0.0_r8)) then + if ((bulk_density <= nearzero) .or. (eps <= nearzero) .or. (q_ig <= nearzero)) then ForwardRateOfSpread = 0.0_r8 else ForwardRateOfSpread = (i_r*xi*(1.0_r8 + phi_wind))/(bulk_density*eps*q_ig) @@ -269,23 +269,22 @@ end function ForwardRateOfSpread !------------------------------------------------------------------------------------- real(r8) function BackwardRateOfSpread(ros_front, wind_speed) - ! - ! DESCRIPTION: - ! Calculates backwards rate of spread [m/min] - ! Equation 10 in Thonicke et al. 2010 - ! backward ROS from Can FBP System (1992) - ! backward ROS wind not changed by vegetation - ! + ! + ! DESCRIPTION: + ! Calculates backwards rate of spread [m/min] + ! Equation 10 in Thonicke et al. 2010 + ! backward ROS from Can FBP System (1992) + ! backward ROS wind not changed by vegetation + ! - ! ARGUMENTS: - real(r8), intent(in) :: ros_front ! forward rate of spread [m/min] - real(r8), intent(in) :: wind_speed ! wind speed [m/min] + ! ARGUMENTS: + real(r8), intent(in) :: ros_front ! forward rate of spread [m/min] + real(r8), intent(in) :: wind_speed ! wind speed [m/min] - BackwardRateOfSpread = ros_front*exp(-0.012_r8*wind_speed) + BackwardRateOfSpread = ros_front*exp(-0.012_r8*wind_speed) - end function BackwardRateOfSpread + end function BackwardRateOfSpread - !------------------------------------------------------------------------------------- + !------------------------------------------------------------------------------------- - end module SFEquationsMod diff --git a/fire/SFMainMod.F90 b/fire/SFMainMod.F90 index 8de508194d..fbe878ec06 100644 --- a/fire/SFMainMod.F90 +++ b/fire/SFMainMod.F90 @@ -251,10 +251,10 @@ subroutine CalculateSurfaceRateOfSpread(currentSite) end if ! remove mineral content from fuel load per Thonicke 2010 - non_mineral_loading = currentPatch%fuel%non_trunk_loading*(1.0_r8 - SF_val_miner_total) + currentPatch%fuel%non_trunk_loading = currentPatch%fuel%non_trunk_loading*(1.0_r8 - SF_val_miner_total) ! reaction intensity [kJ/m2/min] - i_r = ReactionIntensity(non_mineral_loading/0.45_r8, & + i_r = ReactionIntensity(currentPatch%fuel%non_trunk_loading/0.45_r8, & currentPatch%fuel%SAV_notrunks, beta_ratio, & currentPatch%fuel%average_moisture_notrunks, currentPatch%fuel%MEF_notrunks) @@ -303,7 +303,6 @@ subroutine ground_fuel_consumption ( currentSite ) real(r8) :: moist !effective fuel moisture real(r8) :: tau_b(num_fuel_classes) !lethal heating rates for each fuel class (min) real(r8) :: fc_ground(num_fuel_classes) !total amount of fuel consumed per area of burned ground (kg C / m2 of burned area) - real(r8) :: non_mineral_loading ! non-mineral loading [kgC/m2] integer :: tr_sf, tw_sf, dl_sf, lg_sf integer :: c @@ -365,9 +364,8 @@ subroutine ground_fuel_consumption ( currentSite ) ! taul is the duration of the lethal heating. ! The /10 is to convert from kgC/m2 into gC/cm2, as in the Peterson and Ryan paper #Rosie,Jun 2013 - non_mineral_loading = currentPatch%fuel%non_trunk_loading*(1.0_r8 - SF_val_miner_total) !net of minerals do c = 1,num_fuel_classes - tau_b(c) = 39.4_r8 *(currentPatch%fuel%frac_loading(c)*non_mineral_loading/0.45_r8/10._r8)* & + tau_b(c) = 39.4_r8 *(currentPatch%fuel%frac_loading(c)*currentPatch%fuel%non_trunk_loading/0.45_r8/10._r8)* & (1.0_r8-((1.0_r8-currentPatch%fuel%frac_burnt(c))**0.5_r8)) enddo tau_b(tr_sf) = 0.0_r8 From a1c587d3bc64df236db5a733d57f0121bde90d5c Mon Sep 17 00:00:00 2001 From: Adrianna Foster Date: Fri, 6 Dec 2024 11:16:03 -0700 Subject: [PATCH 095/111] Update FatesHistoryInterfaceMod.F90 --- main/FatesHistoryInterfaceMod.F90 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/FatesHistoryInterfaceMod.F90 b/main/FatesHistoryInterfaceMod.F90 index 612e917ffa..55287c40f5 100644 --- a/main/FatesHistoryInterfaceMod.F90 +++ b/main/FatesHistoryInterfaceMod.F90 @@ -7097,7 +7097,7 @@ subroutine define_history_vars(this, initialize_variables) index = ih_fire_intensity_si_age) call this%set_history_var(vname='FATES_FUEL_AMOUNT_AP', units='kg m-2', & - long='spitfire ground fuel (kg carbon per m2) related to FATES_ROS (omits 1000hr fuels, includes mineral portion) within each patch age bin (divide by FATES_PATCHAREA_AP to get fuel per unit area of that-age patch)', & + long='spitfire ground fuel (kg carbon per m2) related to FATES_ROS (omits 1000hr fuels) within each patch age bin (divide by FATES_PATCHAREA_AP to get fuel per unit area of that-age patch)', & use_default='active', avgflag='A', vtype=site_age_r8, hlms='CLM:ALM', & upfreq=group_dyna_complx, ivar=ivar, initialize=initialize_variables, & index = ih_fire_sum_fuel_si_age) From 31a4bb649bb3c8830afebb51de4cd51836ecaa82 Mon Sep 17 00:00:00 2001 From: adrifoster Date: Fri, 6 Dec 2024 11:25:37 -0700 Subject: [PATCH 096/111] add ros tests --- CMakeLists.txt | 3 + testing/CMakeLists.txt | 3 +- .../fire/{ => fuel}/CMakeLists.txt | 8 +- .../fire/{ => fuel}/FatesTestFuel.F90 | 0 .../fire/{ => fuel}/fuel_test.py | 0 .../fire/ros/CMakeLists.txt | 24 +++ .../fire/ros/FatestTestROS.F90 | 155 ++++++++++++++++++ .../functional_testing/fire/ros/ros_test.py | 35 ++++ .../fire/shr/CMakeLists.txt | 6 + .../fire/{ => shr}/FatesTestFireMod.F90 | 0 .../fire/{ => shr}/SyntheticFuelModels.F90 | 0 testing/functional_tests.cfg | 7 + 12 files changed, 235 insertions(+), 6 deletions(-) rename testing/functional_testing/fire/{ => fuel}/CMakeLists.txt (77%) rename testing/functional_testing/fire/{ => fuel}/FatesTestFuel.F90 (100%) rename testing/functional_testing/fire/{ => fuel}/fuel_test.py (100%) create mode 100644 testing/functional_testing/fire/ros/CMakeLists.txt create mode 100644 testing/functional_testing/fire/ros/FatestTestROS.F90 create mode 100644 testing/functional_testing/fire/ros/ros_test.py create mode 100644 testing/functional_testing/fire/shr/CMakeLists.txt rename testing/functional_testing/fire/{ => shr}/FatesTestFireMod.F90 (100%) rename testing/functional_testing/fire/{ => shr}/SyntheticFuelModels.F90 (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9760a39c1d..4298dcc46c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,7 +29,10 @@ add_subdirectory(${HLM_ROOT}/src/fates/biogeophys fates_biogeophys) add_subdirectory(${HLM_ROOT}/src/fates/parteh fates_parteh) add_subdirectory(${HLM_ROOT}/src/fates/fire fates_fire) add_subdirectory(${HLM_ROOT}/src/fates/radiation fates_radiation) + +# Testing directories add_subdirectory(${HLM_ROOT}/src/fates/testing/testing_shr test_share) +add_subdirectory(${HLM_ROOT}/src/fates/testing/functional_testing/fire/shr fire_share) # Remove shr_mpi_mod from share_sources. # This is needed because we want to use the mock shr_mpi_mod in place of the real one diff --git a/testing/CMakeLists.txt b/testing/CMakeLists.txt index 7c19f7cc99..be26ace27f 100644 --- a/testing/CMakeLists.txt +++ b/testing/CMakeLists.txt @@ -3,7 +3,8 @@ ## Functional tests add_subdirectory(functional_testing/allometry fates_allom_ftest) add_subdirectory(functional_testing/math_utils fates_math_ftest) -add_subdirectory(functional_testing/fire fates_fuel_ftest) +add_subdirectory(functional_testing/fire/fuel fates_fuel_ftest) +add_subdirectory(functional_testing/fire/ros fates_ros_ftest) ## Unit tests add_subdirectory(unit_testing/fire_weather_test fates_fire_weather_utest) diff --git a/testing/functional_testing/fire/CMakeLists.txt b/testing/functional_testing/fire/fuel/CMakeLists.txt similarity index 77% rename from testing/functional_testing/fire/CMakeLists.txt rename to testing/functional_testing/fire/fuel/CMakeLists.txt index cf4e55508d..0977087557 100644 --- a/testing/functional_testing/fire/CMakeLists.txt +++ b/testing/functional_testing/fire/fuel/CMakeLists.txt @@ -1,7 +1,5 @@ -set(fire_test_sources - FatesTestFuel.F90 - FatesTestFireMod.F90 - SyntheticFuelModels.F90) +set(fuel_test_sources + FatesTestFuel.F90) set(NETCDF_C_DIR ${NETCDF_C_PATH}) set(NETCDF_FORTRAN_DIR ${NETCDF_F_PATH}) @@ -17,7 +15,7 @@ link_directories(${NETCDF_C_DIR}/lib ${NETCDF_FORTRAN_DIR}/lib ${PFUNIT_TOP_DIR}/lib) -add_executable(FATES_fuel_exe ${fire_test_sources}) +add_executable(FATES_fuel_exe ${fuel_test_sources}) target_link_libraries(FATES_fuel_exe netcdf diff --git a/testing/functional_testing/fire/FatesTestFuel.F90 b/testing/functional_testing/fire/fuel/FatesTestFuel.F90 similarity index 100% rename from testing/functional_testing/fire/FatesTestFuel.F90 rename to testing/functional_testing/fire/fuel/FatesTestFuel.F90 diff --git a/testing/functional_testing/fire/fuel_test.py b/testing/functional_testing/fire/fuel/fuel_test.py similarity index 100% rename from testing/functional_testing/fire/fuel_test.py rename to testing/functional_testing/fire/fuel/fuel_test.py diff --git a/testing/functional_testing/fire/ros/CMakeLists.txt b/testing/functional_testing/fire/ros/CMakeLists.txt new file mode 100644 index 0000000000..a8838eaab6 --- /dev/null +++ b/testing/functional_testing/fire/ros/CMakeLists.txt @@ -0,0 +1,24 @@ +set(ros_test_sources + FatesTestROS.F90) + +set(NETCDF_C_DIR ${NETCDF_C_PATH}) +set(NETCDF_FORTRAN_DIR ${NETCDF_F_PATH}) + +FIND_PATH(NETCDFC_FOUND libnetcdf.a ${NETCDF_C_DIR}/lib) +FIND_PATH(NETCDFF_FOUND libnetcdff.a ${NETCDF_FORTRAN_DIR}/lib) + +include_directories(${NETCDF_C_DIR}/include + ${NETCDF_FORTRAN_DIR}/include) + +link_directories(${NETCDF_C_DIR}/lib + ${NETCDF_FORTRAN_DIR}/lib + ${PFUNIT_TOP_DIR}/lib) + +add_executable(FATES_ros_exe ${ros_test_sources}) + +target_link_libraries(FATES_ros_exe + netcdf + netcdff + fates + csm_share + funit) \ No newline at end of file diff --git a/testing/functional_testing/fire/ros/FatestTestROS.F90 b/testing/functional_testing/fire/ros/FatestTestROS.F90 new file mode 100644 index 0000000000..9dc3f70a34 --- /dev/null +++ b/testing/functional_testing/fire/ros/FatestTestROS.F90 @@ -0,0 +1,155 @@ +program FatesTestROS + + use FatesConstantsMod, only : r8 => fates_r8 + use FatesTestFireMod, only : SetUpFuel, WriteROSData + use FatesArgumentUtils, only : command_line_arg + use FatesUnitTestParamReaderMod, only : fates_unit_test_param_reader + use SyntheticFuelModels, only : fuel_models_array_class + use FatesFuelMod, only : fuel_type + use FatesFuelClassesMod, only : num_fuel_classes + use SFParamsMod, only : SF_val_SAV + use SFParamsMod, only : SF_val_FBD, SF_val_part_dens + use SFParamsMod, only : SF_val_miner_total + use SFMainMod, only : HeatOfPreignition, EffectiveHeatingNumber + use SFMainMod, only : PhiWind, PropagatingFlux + use SFMainMod, only : RateOfSpread, ReactionIntensity + + implicit none + + ! LOCALS: + type(fates_unit_test_param_reader) :: param_reader ! param reader instance + type(fuel_models_array_class) :: fuel_models_array ! array of fuel models + type(fuel_type), allocatable :: fuel(:) ! fuel objects + character(len=:), allocatable :: param_file ! input parameter file + real(r8), allocatable :: wind_speed(:) ! midflame wind speed [m/min] + real(r8) :: wind_adj ! adjusted windspeed [m/min] + real(r8), allocatable :: fuel_moisture_live(:,:) ! fuel moisture of live [m3/m3] + real(r8), allocatable :: fuel_moisture_dead(:,:) ! fuel moisture of dead [m3/m3] + real(r8), allocatable :: mef_live(:,:) ! live fuel moisture of extinction [m3/m3] + real(r8), allocatable :: beta(:) ! packing ratio [dimensionless] + real(r8), allocatable :: beta_op(:) ! optimum packing ratio [dimensionless] + real(r8), allocatable :: eps(:) ! effective heating number [dimensionless] + real(r8), allocatable :: prop_flux(:) ! propagating flux ratio [dimensionless] + real(r8), allocatable :: heat_sink(:,:) ! heat sink + real(r8), allocatable :: i_r(:,:) ! reaction intensity of live fuels [kJ/m2/min] + real(r8), allocatable :: phi_wind(:,:) ! wind factor [dimensionless] + real(r8), allocatable :: ros(:,:,:) ! rate of spread [m/min] + real(r8) :: beta_ratio ! ratio of packing ratio to optimum packing ratio [dimensionless] + real(r8) :: non_mineral_loading ! non mineral loading [kgC/m2] + integer :: num_fuel_models ! number of fuel models to simulate + character(len=100), allocatable :: fuel_names(:) ! names of fuel models + character(len=2), allocatable :: carriers(:) ! carriers of fuel models + integer :: num_wind ! array size + integer :: num_moist ! number of moisture classes + integer :: i, f, m ! looping indices + + ! CONSTANTS: + character(len=*), parameter :: out_file = 'ros_out.nc' ! output file + integer, parameter, dimension(6) :: fuel_models = (/4, 101, 102, 103, 104, 105/) ! fuel models to test + + ! fuel moisture values to test - from Scott & Bergen 2005 + real(r8), parameter, dimension(4) :: fuel_moisture_1hr = & + (/3.0_r8, 6.0_r8, 9.0_r8, 12.0_r8/) ! dead fuel moisture values for 1-hr fuels [%] + real(r8), parameter, dimension(4) :: fuel_moisture_10hr = & + (/4.0_r8, 7.0_r8, 10.0_r8, 13.0_r8/) ! dead fuel moisture values for 10-hr fuels [%] + real(r8), parameter, dimension(4) :: fuel_moisture_100hr = & + (/5.0_r8, 8.0_r8, 11.0_r8, 14.0_r8/) ! dead fuel moisture values for 100-hr fuels [%] + real(r8), parameter, dimension(4) :: fuel_moisture_live_herb = & + (/30.0_r8, 60.0_r8, 90.0_r8, 120.0_r8/) ! live herbaceous fuel moisture values [%] + real(r8), parameter, dimension(4) :: fuel_moisture_live_woody = & + (/60.0_r8, 90.0_r8, 120.0_r8, 150.0_r8/) ! live woody fuel moisture values [%] + real(r8), parameter :: min_wind = 0.0_r8 ! minimum wind speed to test [m/min] + real(r8), parameter :: max_wind = 540.0_r8 ! maximum wind speed to test [m/min] + real(r8), parameter :: wind_inc = 2.0_r8 ! wind increment to use [m/min] + + ! read in parameter file name and DATM file from command line + param_file = command_line_arg(1) + + ! read in parameter file + call param_reader%Init(param_file) + call param_reader%RetrieveParameters() + + num_wind = int((max_wind - min_wind)/wind_inc + 1) + num_moist = size(fuel_moisture_1hr) + num_fuel_models = size(fuel_models) + + allocate(wind_speed(num_wind)) + allocate(fuel(num_fuel_models)) + allocate(fuel_names(num_fuel_models)) + allocate(carriers(num_fuel_models)) + allocate(beta(num_fuel_models)) + allocate(beta_op(num_fuel_models)) + allocate(eps(num_fuel_models)) + allocate(prop_flux(num_fuel_models)) + allocate(fuel_moisture_live(num_fuel_models, num_moist)) + allocate(fuel_moisture_dead(num_fuel_models, num_moist)) + allocate(mef_live(num_fuel_models, num_moist)) + allocate(i_r(num_fuel_models, num_moist)) + allocate(heat_sink(num_fuel_models, num_moist)) + allocate(phi_wind(num_fuel_models, num_wind)) + allocate(ros(num_fuel_models, num_moist, num_wind)) + + ! set up fuel objects and calculate loading + call fuel_models_array%GetFuelModels() + + ! calculate ROS ------- + do f = 1, num_fuel_models + + ! uses data from fuel_models to initialize fuel + call SetUpFuel(fuel(f), fuel_models_array, fuel_models(f), fuel_names(f), carriers(f)) + + beta(f) = fuel(f)%bulk_density_notrunks/SF_val_part_dens + beta_op(f) = 0.200395_r8*(fuel(f)%SAV_notrunks**(-0.8189_r8)) + beta_ratio = beta(f)/beta_op(f) + + eps(f) = EffectiveHeatingNumber(fuel(f)%SAV_notrunks) + prop_flux(f) = PropagatingFlux(fuel(f)%SAV_notrunks, beta(f)) + + do m = 1, num_moist + + call fuel_models_array%fuel_models(f)%CalculateMoisture(fuel_moisture_1hr(m), & + fuel_moisture_10hr(m), fuel_moisture_100hr(m), fuel_moisture_live_herb(2), & + fuel_moisture_live_woody(2), fuel_moisture_live(f,m), fuel_moisture_dead(f,m), & + mef_live(f,m)) + + i_r(f,m) = ReactionIntensity(fuel_models_array%fuel_models(f)%net_loading_dead, & + fuel_models_array%fuel_models(f)%net_loading_live, fuel(f)%SAV_notrunks, & + beta_ratio, fuel_moisture_dead(f,m), fuel_moisture_live(f,m), & + fuel_models_array%fuel_models(f)%moist_extinct, mef_live(f,m)) + + call fuel_models_array%fuel_models(f)%CalculateHeatSink(fuel(f)%bulk_density_notrunks, & + heat_sink(f,m)) + + do i = 1, num_wind + + wind_speed(i) = min_wind + wind_inc*(i-1) + wind_adj = wind_speed(i)*(fuel_models_array%fuel_models(f)%wind_adj_factor) + + phi_wind(f,i) = PhiWind(wind_speed(i), beta_ratio, fuel(f)%SAV_notrunks, i_r(f,m)) + + ros(f,m,i) = (i_r(f,m)*prop_flux(f)*(1.0_r8 + phi_wind(f,i)))/heat_sink(f,m) + + end do + end do + end do + + call WriteROSData(out_file, num_wind, num_moist, num_fuel_models, wind_speed, beta, & + beta_op, eps, prop_flux, heat_sink, fuel_moisture_dead, i_r, phi_wind, ros, fuel_models) + + if (allocated(wind_speed)) deallocate(wind_speed) + if (allocated(fuel)) deallocate(fuel) + if (allocated(fuel_names)) deallocate(fuel_names) + if (allocated(carriers)) deallocate(carriers) + if (allocated(beta)) deallocate(beta) + if (allocated(beta_op)) deallocate(beta_op) + if (allocated(eps)) deallocate(eps) + if (allocated(prop_flux)) deallocate(prop_flux) + if (allocated(heat_sink)) deallocate(heat_sink) + if (allocated(fuel_moisture_live)) deallocate(fuel_moisture_live) + if (allocated(fuel_moisture_dead)) deallocate(fuel_moisture_dead) + if (allocated(mef_live)) deallocate(mef_live) + if (allocated(i_r)) deallocate(i_r) + if (allocated(phi_wind)) deallocate(phi_wind) + if (allocated(ros)) deallocate(ros) + +end program FatesTestROS \ No newline at end of file diff --git a/testing/functional_testing/fire/ros/ros_test.py b/testing/functional_testing/fire/ros/ros_test.py new file mode 100644 index 0000000000..d71c9e22fb --- /dev/null +++ b/testing/functional_testing/fire/ros/ros_test.py @@ -0,0 +1,35 @@ +""" +Concrete class for running the fuel functional test for FATES. +""" +import os +import numpy as np +import xarray as xr +import matplotlib.pyplot as plt +from functional_class import FunctionalTest + + +class ROSTest(FunctionalTest): + """ROS test class""" + + name = "ros" + + def __init__(self, test_dict): + super().__init__( + ROSTest.name, + test_dict["test_dir"], + test_dict["test_exe"], + test_dict["out_file"], + test_dict["use_param_file"], + test_dict["other_args"], + ) + self.plot = True + + def plot_output(self, run_dir: str, save_figs: bool, plot_dir: str): + """Plot output associated with fuel tests + + Args: + run_dir (str): run directory + out_file (str): output file + save_figs (bool): whether or not to save the figures + plot_dir (str): plot directory + """ \ No newline at end of file diff --git a/testing/functional_testing/fire/shr/CMakeLists.txt b/testing/functional_testing/fire/shr/CMakeLists.txt new file mode 100644 index 0000000000..b3ccabb546 --- /dev/null +++ b/testing/functional_testing/fire/shr/CMakeLists.txt @@ -0,0 +1,6 @@ +list(APPEND fates_sources + FatesTestFireMod.F90 + SyntheticFuelModels.F90 + ) + +sourcelist_to_parent(fates_sources) \ No newline at end of file diff --git a/testing/functional_testing/fire/FatesTestFireMod.F90 b/testing/functional_testing/fire/shr/FatesTestFireMod.F90 similarity index 100% rename from testing/functional_testing/fire/FatesTestFireMod.F90 rename to testing/functional_testing/fire/shr/FatesTestFireMod.F90 diff --git a/testing/functional_testing/fire/SyntheticFuelModels.F90 b/testing/functional_testing/fire/shr/SyntheticFuelModels.F90 similarity index 100% rename from testing/functional_testing/fire/SyntheticFuelModels.F90 rename to testing/functional_testing/fire/shr/SyntheticFuelModels.F90 diff --git a/testing/functional_tests.cfg b/testing/functional_tests.cfg index 46924c307c..5ae31d7c75 100644 --- a/testing/functional_tests.cfg +++ b/testing/functional_tests.cfg @@ -18,3 +18,10 @@ test_exe = FATES_fuel_exe out_file = fuel_out.nc use_param_file = True other_args = ['../testing/test_data/BONA_datm.nc'] + +[ros] +test_dir = fates_ros_ftest +test_exe = FATES_ros_exe +out_file = 'ros_out.nc' +use_param_file = True +other_args = [] From 0aef731e31e7d82393d6ca6cc3175859efb5adb1 Mon Sep 17 00:00:00 2001 From: adrifoster Date: Fri, 6 Dec 2024 13:10:01 -0700 Subject: [PATCH 097/111] test for propagating flux --- .../fire/ros/FatesTestROS.F90 | 182 ++++++++++++++++++ .../fire/ros/FatestTestROS.F90 | 155 --------------- .../functional_testing/fire/ros/ros_test.py | 69 ++++++- .../fire/shr/FatesTestFireMod.F90 | 12 +- testing/functional_tests.cfg | 2 +- testing/run_functional_tests.py | 3 +- 6 files changed, 259 insertions(+), 164 deletions(-) create mode 100644 testing/functional_testing/fire/ros/FatesTestROS.F90 delete mode 100644 testing/functional_testing/fire/ros/FatestTestROS.F90 diff --git a/testing/functional_testing/fire/ros/FatesTestROS.F90 b/testing/functional_testing/fire/ros/FatesTestROS.F90 new file mode 100644 index 0000000000..4ceb1db81c --- /dev/null +++ b/testing/functional_testing/fire/ros/FatesTestROS.F90 @@ -0,0 +1,182 @@ +program FatesTestROS + + use FatesConstantsMod, only : r8 => fates_r8 + use FatesArgumentUtils, only : command_line_arg + use FatesUnitTestParamReaderMod, only : fates_unit_test_param_reader + + implicit none + + ! LOCALS: + type(fates_unit_test_param_reader) :: param_reader ! param reader instance + character(len=:), allocatable :: param_file ! input parameter file + real(r8), allocatable :: SAV(:) ! fuel surface area to volume ratio [/cm] + real(r8), allocatable :: beta(:) ! packing ratio [unitless] + real(r8), allocatable :: propagating_flux(:,:) ! propagating flux [unitless] + + ! CONSTANTS: + character(len=*), parameter :: out_file = 'ros_out.nc' ! output file + + interface + + subroutine TestPropFlux(SAV, propagating_flux, beta) + + use FatesConstantsMod, only : r8 => fates_r8 + use SFEquationsMod, only : PropagatingFlux + implicit none + real(r8), allocatable, intent(out) :: SAV(:) + real(r8), allocatable, intent(out) :: propagating_flux(:,:) + real(r8), allocatable, intent(out) :: beta(:) + + end subroutine TestPropFlux + + subroutine WriteROSData(out_file, beta, SAV, propagating_flux) + + use FatesConstantsMod, only : r8 => fates_r8 + use FatesUnitTestIOMod, only : OpenNCFile, CloseNCFile, RegisterNCDims + use FatesUnitTestIOMod, only : RegisterVar, EndNCDef, WriteVar + use FatesUnitTestIOMod, only : type_double + implicit none + character(len=*), intent(in) :: out_file + real(r8), intent(in) :: beta(:) + real(r8), intent(in) :: SAV(:) + real(r8), intent(in) :: propagating_flux(:,:) + + end subroutine WriteROSData + + end interface + + ! read in parameter file name and DATM file from command line + param_file = command_line_arg(1) + + ! read in parameter file + call param_reader%Init(param_file) + call param_reader%RetrieveParameters() + + ! calculate propagating flux + call TestPropFlux(SAV, propagating_flux, beta) + + ! write output data + call WriteROSData(out_file, beta, SAV, propagating_flux) + + ! deallocate arrays + if (allocated(propagating_flux)) deallocate(propagating_flux) + if (allocated(SAV)) deallocate(SAV) + if (allocated(beta)) deallocate(beta) + +end program FatesTestROS + +!========================================================================================= + +subroutine TestPropFlux(SAV, propagating_flux, beta) + ! + ! DESCRIPTION: + ! Calculates propagating flux ratio of a range of SAV and packing ratio values + ! + use FatesConstantsMod, only : r8 => fates_r8 + use SFEquationsMod, only : PropagatingFlux + + implicit none + + ! ARGUMENTS: + real(r8), allocatable, intent(out) :: SAV(:) ! fuel surface area to volume ratio [/cm] + real(r8), allocatable, intent(out) :: propagating_flux(:,:) ! propagating flux [unitless] + real(r8), allocatable, intent(out) :: beta(:) ! packing ratio [unitless] + + ! CONSTANTS: + real(r8), parameter :: SAV_min = 0.0_r8 ! minimum SAV to calculate [/cm] + real(r8), parameter :: SAV_max = 115.0_r8 ! maximum SAV to calculate [/cm] + real(r8), parameter :: SAV_inc = 1.0_r8 ! SAV increment to scale [/cm] + real(r8), parameter, dimension(4) :: packing_ratio = (/0.02_r8, 0.01_r8, 0.005_r8, 0.001_r8/) ! packing ratios to use [unitless] + + ! LOCALS: + integer :: num_SAV ! size of SAV array + integer :: i, j ! looping indices + + ! allocate arrays + num_SAV = int((SAV_max - SAV_min)/SAV_inc + 1) + allocate(propagating_flux(num_SAV, size(packing_ratio))) + allocate(SAV(num_SAV)) + allocate(beta(size(packing_ratio))) + + do i = 1, num_SAV + + SAV(i) = SAV_min + SAV_inc*(i-1) + + do j = 1, size(packing_ratio) + beta(j) = packing_ratio(j) + propagating_flux(i,j) = PropagatingFlux(packing_ratio(j), SAV(i)) + end do + end do + +end subroutine TestPropFlux + +!========================================================================================= + +subroutine WriteROSData(out_file, beta, SAV, propagating_flux) + ! + ! DESCRIPTION: + ! writes out data from the test + ! + use FatesConstantsMod, only : r8 => fates_r8 + use FatesUnitTestIOMod, only : OpenNCFile, CloseNCFile, RegisterNCDims + use FatesUnitTestIOMod, only : RegisterVar, EndNCDef, WriteVar + use FatesUnitTestIOMod, only : type_double + + implicit none + + ! ARGUMENTS: + character(len=*), intent(in) :: out_file + real(r8), intent(in) :: beta(:) + real(r8), intent(in) :: SAV(:) + real(r8), intent(in) :: propagating_flux(:,:) + + ! LOCALS: + integer :: ncid ! netcdf id + character(len=20) :: dim_names(2) ! dimension names + integer :: dimIDs(2) ! dimension IDs + integer :: SAVID + integer :: betaID + integer :: propfluxID + + + ! dimension names + dim_names = [character(len=20) :: 'SAV_value', 'beta_value'] + + ! open file + call OpenNCFile(trim(out_file), ncid, 'readwrite') + + ! register dimensions + call RegisterNCDims(ncid, dim_names, (/size(SAV), size(beta)/), 2, dimIDs) + + ! first register dimension variables + + ! register SAV + call RegisterVar(ncid, 'SAV', dimIDs(1:1), type_double, & + [character(len=20) :: 'units', 'long_name'], & + [character(len=150) :: '/cm', 'fuel surface area to volume ratio'], 2, SAVID) + + ! register packing ratio + call RegisterVar(ncid, 'packing_ratio', dimIDs(2:2), type_double, & + [character(len=20) :: 'units', 'long_name'], & + [character(len=150) :: '', 'packing ratio'], 2, betaID) + + ! then register actual variables + + ! register propagating flux + call RegisterVar(ncid, 'prop_flux', dimIDs(1:2), type_double, & + [character(len=20) :: 'coordinates', 'units', 'long_name'], & + [character(len=150) :: 'SAV_value beta_value', '', 'propagating flux'], & + 3, propfluxID) + + ! finish defining variables + call EndNCDef(ncid) + + ! write out data + call WriteVar(ncid, SAVID, SAV(:)) + call WriteVar(ncid, betaID, beta(:)) + call WriteVar(ncid, propfluxID, propagating_flux(:,:)) + + ! close file + call CloseNCFile(ncid) + +end subroutine WriteROSData \ No newline at end of file diff --git a/testing/functional_testing/fire/ros/FatestTestROS.F90 b/testing/functional_testing/fire/ros/FatestTestROS.F90 deleted file mode 100644 index 9dc3f70a34..0000000000 --- a/testing/functional_testing/fire/ros/FatestTestROS.F90 +++ /dev/null @@ -1,155 +0,0 @@ -program FatesTestROS - - use FatesConstantsMod, only : r8 => fates_r8 - use FatesTestFireMod, only : SetUpFuel, WriteROSData - use FatesArgumentUtils, only : command_line_arg - use FatesUnitTestParamReaderMod, only : fates_unit_test_param_reader - use SyntheticFuelModels, only : fuel_models_array_class - use FatesFuelMod, only : fuel_type - use FatesFuelClassesMod, only : num_fuel_classes - use SFParamsMod, only : SF_val_SAV - use SFParamsMod, only : SF_val_FBD, SF_val_part_dens - use SFParamsMod, only : SF_val_miner_total - use SFMainMod, only : HeatOfPreignition, EffectiveHeatingNumber - use SFMainMod, only : PhiWind, PropagatingFlux - use SFMainMod, only : RateOfSpread, ReactionIntensity - - implicit none - - ! LOCALS: - type(fates_unit_test_param_reader) :: param_reader ! param reader instance - type(fuel_models_array_class) :: fuel_models_array ! array of fuel models - type(fuel_type), allocatable :: fuel(:) ! fuel objects - character(len=:), allocatable :: param_file ! input parameter file - real(r8), allocatable :: wind_speed(:) ! midflame wind speed [m/min] - real(r8) :: wind_adj ! adjusted windspeed [m/min] - real(r8), allocatable :: fuel_moisture_live(:,:) ! fuel moisture of live [m3/m3] - real(r8), allocatable :: fuel_moisture_dead(:,:) ! fuel moisture of dead [m3/m3] - real(r8), allocatable :: mef_live(:,:) ! live fuel moisture of extinction [m3/m3] - real(r8), allocatable :: beta(:) ! packing ratio [dimensionless] - real(r8), allocatable :: beta_op(:) ! optimum packing ratio [dimensionless] - real(r8), allocatable :: eps(:) ! effective heating number [dimensionless] - real(r8), allocatable :: prop_flux(:) ! propagating flux ratio [dimensionless] - real(r8), allocatable :: heat_sink(:,:) ! heat sink - real(r8), allocatable :: i_r(:,:) ! reaction intensity of live fuels [kJ/m2/min] - real(r8), allocatable :: phi_wind(:,:) ! wind factor [dimensionless] - real(r8), allocatable :: ros(:,:,:) ! rate of spread [m/min] - real(r8) :: beta_ratio ! ratio of packing ratio to optimum packing ratio [dimensionless] - real(r8) :: non_mineral_loading ! non mineral loading [kgC/m2] - integer :: num_fuel_models ! number of fuel models to simulate - character(len=100), allocatable :: fuel_names(:) ! names of fuel models - character(len=2), allocatable :: carriers(:) ! carriers of fuel models - integer :: num_wind ! array size - integer :: num_moist ! number of moisture classes - integer :: i, f, m ! looping indices - - ! CONSTANTS: - character(len=*), parameter :: out_file = 'ros_out.nc' ! output file - integer, parameter, dimension(6) :: fuel_models = (/4, 101, 102, 103, 104, 105/) ! fuel models to test - - ! fuel moisture values to test - from Scott & Bergen 2005 - real(r8), parameter, dimension(4) :: fuel_moisture_1hr = & - (/3.0_r8, 6.0_r8, 9.0_r8, 12.0_r8/) ! dead fuel moisture values for 1-hr fuels [%] - real(r8), parameter, dimension(4) :: fuel_moisture_10hr = & - (/4.0_r8, 7.0_r8, 10.0_r8, 13.0_r8/) ! dead fuel moisture values for 10-hr fuels [%] - real(r8), parameter, dimension(4) :: fuel_moisture_100hr = & - (/5.0_r8, 8.0_r8, 11.0_r8, 14.0_r8/) ! dead fuel moisture values for 100-hr fuels [%] - real(r8), parameter, dimension(4) :: fuel_moisture_live_herb = & - (/30.0_r8, 60.0_r8, 90.0_r8, 120.0_r8/) ! live herbaceous fuel moisture values [%] - real(r8), parameter, dimension(4) :: fuel_moisture_live_woody = & - (/60.0_r8, 90.0_r8, 120.0_r8, 150.0_r8/) ! live woody fuel moisture values [%] - real(r8), parameter :: min_wind = 0.0_r8 ! minimum wind speed to test [m/min] - real(r8), parameter :: max_wind = 540.0_r8 ! maximum wind speed to test [m/min] - real(r8), parameter :: wind_inc = 2.0_r8 ! wind increment to use [m/min] - - ! read in parameter file name and DATM file from command line - param_file = command_line_arg(1) - - ! read in parameter file - call param_reader%Init(param_file) - call param_reader%RetrieveParameters() - - num_wind = int((max_wind - min_wind)/wind_inc + 1) - num_moist = size(fuel_moisture_1hr) - num_fuel_models = size(fuel_models) - - allocate(wind_speed(num_wind)) - allocate(fuel(num_fuel_models)) - allocate(fuel_names(num_fuel_models)) - allocate(carriers(num_fuel_models)) - allocate(beta(num_fuel_models)) - allocate(beta_op(num_fuel_models)) - allocate(eps(num_fuel_models)) - allocate(prop_flux(num_fuel_models)) - allocate(fuel_moisture_live(num_fuel_models, num_moist)) - allocate(fuel_moisture_dead(num_fuel_models, num_moist)) - allocate(mef_live(num_fuel_models, num_moist)) - allocate(i_r(num_fuel_models, num_moist)) - allocate(heat_sink(num_fuel_models, num_moist)) - allocate(phi_wind(num_fuel_models, num_wind)) - allocate(ros(num_fuel_models, num_moist, num_wind)) - - ! set up fuel objects and calculate loading - call fuel_models_array%GetFuelModels() - - ! calculate ROS ------- - do f = 1, num_fuel_models - - ! uses data from fuel_models to initialize fuel - call SetUpFuel(fuel(f), fuel_models_array, fuel_models(f), fuel_names(f), carriers(f)) - - beta(f) = fuel(f)%bulk_density_notrunks/SF_val_part_dens - beta_op(f) = 0.200395_r8*(fuel(f)%SAV_notrunks**(-0.8189_r8)) - beta_ratio = beta(f)/beta_op(f) - - eps(f) = EffectiveHeatingNumber(fuel(f)%SAV_notrunks) - prop_flux(f) = PropagatingFlux(fuel(f)%SAV_notrunks, beta(f)) - - do m = 1, num_moist - - call fuel_models_array%fuel_models(f)%CalculateMoisture(fuel_moisture_1hr(m), & - fuel_moisture_10hr(m), fuel_moisture_100hr(m), fuel_moisture_live_herb(2), & - fuel_moisture_live_woody(2), fuel_moisture_live(f,m), fuel_moisture_dead(f,m), & - mef_live(f,m)) - - i_r(f,m) = ReactionIntensity(fuel_models_array%fuel_models(f)%net_loading_dead, & - fuel_models_array%fuel_models(f)%net_loading_live, fuel(f)%SAV_notrunks, & - beta_ratio, fuel_moisture_dead(f,m), fuel_moisture_live(f,m), & - fuel_models_array%fuel_models(f)%moist_extinct, mef_live(f,m)) - - call fuel_models_array%fuel_models(f)%CalculateHeatSink(fuel(f)%bulk_density_notrunks, & - heat_sink(f,m)) - - do i = 1, num_wind - - wind_speed(i) = min_wind + wind_inc*(i-1) - wind_adj = wind_speed(i)*(fuel_models_array%fuel_models(f)%wind_adj_factor) - - phi_wind(f,i) = PhiWind(wind_speed(i), beta_ratio, fuel(f)%SAV_notrunks, i_r(f,m)) - - ros(f,m,i) = (i_r(f,m)*prop_flux(f)*(1.0_r8 + phi_wind(f,i)))/heat_sink(f,m) - - end do - end do - end do - - call WriteROSData(out_file, num_wind, num_moist, num_fuel_models, wind_speed, beta, & - beta_op, eps, prop_flux, heat_sink, fuel_moisture_dead, i_r, phi_wind, ros, fuel_models) - - if (allocated(wind_speed)) deallocate(wind_speed) - if (allocated(fuel)) deallocate(fuel) - if (allocated(fuel_names)) deallocate(fuel_names) - if (allocated(carriers)) deallocate(carriers) - if (allocated(beta)) deallocate(beta) - if (allocated(beta_op)) deallocate(beta_op) - if (allocated(eps)) deallocate(eps) - if (allocated(prop_flux)) deallocate(prop_flux) - if (allocated(heat_sink)) deallocate(heat_sink) - if (allocated(fuel_moisture_live)) deallocate(fuel_moisture_live) - if (allocated(fuel_moisture_dead)) deallocate(fuel_moisture_dead) - if (allocated(mef_live)) deallocate(mef_live) - if (allocated(i_r)) deallocate(i_r) - if (allocated(phi_wind)) deallocate(phi_wind) - if (allocated(ros)) deallocate(ros) - -end program FatesTestROS \ No newline at end of file diff --git a/testing/functional_testing/fire/ros/ros_test.py b/testing/functional_testing/fire/ros/ros_test.py index d71c9e22fb..ea63c3ca92 100644 --- a/testing/functional_testing/fire/ros/ros_test.py +++ b/testing/functional_testing/fire/ros/ros_test.py @@ -4,6 +4,7 @@ import os import numpy as np import xarray as xr +import pandas as pd import matplotlib.pyplot as plt from functional_class import FunctionalTest @@ -32,4 +33,70 @@ def plot_output(self, run_dir: str, save_figs: bool, plot_dir: str): out_file (str): output file save_figs (bool): whether or not to save the figures plot_dir (str): plot directory - """ \ No newline at end of file + """ + + # read in ros data + ros_dat = xr.open_dataset(os.path.join(run_dir, self.out_file)) + + self.plot_prop_flux(ros_dat, save_figs, plot_dir) + + @staticmethod + def plot_prop_flux(data: xr.Dataset, save_fig: bool, plot_dir: str = None): + """Plot propagating flux + + Args: + data (xarray DataSet): the data set + save_fig (bool): whether or not to write out plot + plot_dir (str): if saving figure, where to write to + """ + data_frame = pd.DataFrame( + { + "SAV": np.tile(data.SAV, len(data.packing_ratio)), + "packing_ratio": np.repeat(data.packing_ratio, len(data.SAV)), + "prop_flux": data.prop_flux.values.flatten(), + } + ) + + data_frame['SAV_ft'] = data_frame.SAV*30.48 + + max_SAV = data_frame["SAV_ft"].max() + max_prop_flux = 0.14 + + plt.figure(figsize=(7, 5)) + axis = plt.subplot(111) + axis.spines["top"].set_visible(False) + axis.spines["bottom"].set_visible(False) + axis.spines["right"].set_visible(False) + axis.spines["left"].set_visible(False) + + axis.get_xaxis().tick_bottom() + axis.get_yaxis().tick_left() + + plt.xlim(0.0, max_SAV) + plt.ylim(0.0, max_prop_flux) + + plt.yticks(fontsize=10) + plt.xticks(fontsize=10) + plt.grid(True) + + packing_ratio = np.unique(data_frame.packing_ratio.values) + colors = ['#6B8939', '#99291F','#CC9728', '#2C778A'] + + for i, beta in enumerate(packing_ratio): + dat = data_frame[data_frame.packing_ratio == beta] + plt.plot( + dat.SAV_ft.values, + dat['prop_flux'].values, + lw=2, + color=colors[i], + label=beta, + ) + + plt.xlabel("Surface-area-to-volume ratio (ft$^{-1}$)", fontsize=11) + plt.ylabel("Propagating flux ratio", fontsize=11) + plt.legend(loc="upper left", title="Packing ratio") + + if save_fig: + fig_name = os.path.join(plot_dir, "prop_flux_plot.png") + plt.savefig(fig_name) + \ No newline at end of file diff --git a/testing/functional_testing/fire/shr/FatesTestFireMod.F90 b/testing/functional_testing/fire/shr/FatesTestFireMod.F90 index c37982039b..5de706387e 100644 --- a/testing/functional_testing/fire/shr/FatesTestFireMod.F90 +++ b/testing/functional_testing/fire/shr/FatesTestFireMod.F90 @@ -39,12 +39,12 @@ subroutine SetUpFuel(fuel, fuel_model_array, fuel_model_index, fuel_name, fuel_c character(len=2), intent(out) :: fuel_carrier ! fuel carrier for fuel model ! LOCALS: - integer :: i ! position of fuel model in array - real(r8) :: leaf_litter ! leaf litter [kg/m2] - real(r8) :: twig_litter ! twig litter [kg/m2] - real(r8) :: small_branch_litter ! small branch litter [kg/m2] - real(r8) :: large_branch_litter ! large branch litter [kg/m2] - real(r8) :: grass_litter ! grass litter [kg/m2] + integer :: i ! position of fuel model in array + real(r8) :: leaf_litter ! leaf litter [kg/m2] + real(r8) :: twig_litter ! twig litter [kg/m2] + real(r8) :: small_branch_litter ! small branch litter [kg/m2] + real(r8) :: large_branch_litter ! large branch litter [kg/m2] + real(r8) :: grass_litter ! grass litter [kg/m2] ! get fuel model position in array diff --git a/testing/functional_tests.cfg b/testing/functional_tests.cfg index 5ae31d7c75..1de704ac16 100644 --- a/testing/functional_tests.cfg +++ b/testing/functional_tests.cfg @@ -22,6 +22,6 @@ other_args = ['../testing/test_data/BONA_datm.nc'] [ros] test_dir = fates_ros_ftest test_exe = FATES_ros_exe -out_file = 'ros_out.nc' +out_file = ros_out.nc use_param_file = True other_args = [] diff --git a/testing/run_functional_tests.py b/testing/run_functional_tests.py index 8a2078628a..44bd1dfe08 100755 --- a/testing/run_functional_tests.py +++ b/testing/run_functional_tests.py @@ -42,7 +42,8 @@ from functional_testing.math_utils.math_utils_test import ( QuadraticTest, ) # pylint: disable=unused-import -from functional_testing.fire.fuel_test import FuelTest # pylint: disable=unused-import +from functional_testing.fire.fuel.fuel_test import FuelTest # pylint: disable=unused-import +from functional_testing.fire.ros.ros_test import ROSTest # pylint: disable=unused-import add_cime_lib_to_path() From a2ce597e7826d32e9d9e64ba8ace9252fcbb1e87 Mon Sep 17 00:00:00 2001 From: adrifoster Date: Fri, 6 Dec 2024 14:42:26 -0700 Subject: [PATCH 098/111] add tests --- fire/CMakeLists.txt | 1 + fire/SFEquationsMod.F90 | 9 +- .../fire/ros/FatesTestROS.F90 | 260 +++++++++++++++++- .../functional_testing/fire/ros/ros_test.py | 126 ++++++++- 4 files changed, 380 insertions(+), 16 deletions(-) diff --git a/fire/CMakeLists.txt b/fire/CMakeLists.txt index f3b0f84ca5..b018217dd8 100644 --- a/fire/CMakeLists.txt +++ b/fire/CMakeLists.txt @@ -5,6 +5,7 @@ list(APPEND fates_sources SFNesterovMod.F90 FatesFuelMod.F90 FatesFuelClassesMod.F90 + SFEquationsMod.F90 ) sourcelist_to_parent(fates_sources) diff --git a/fire/SFEquationsMod.F90 b/fire/SFEquationsMod.F90 index 73c6a09fe2..3cb51c32f9 100644 --- a/fire/SFEquationsMod.F90 +++ b/fire/SFEquationsMod.F90 @@ -13,6 +13,8 @@ module SFEquationsMod implicit none private + public :: MaximumReactionVelocity + public :: OptimumReactionVelocity public :: OptimumPackingRatio public :: ReactionIntensity public :: HeatofPreignition @@ -188,7 +190,12 @@ real(r8) function EffectiveHeatingNumber(SAV) ! ARGUMENTS: real(r8), intent(in) :: SAV ! fuel surface area to volume ratio [/cm] - EffectiveHeatingNumber = exp(-4.528_r8/SAV) + if (SAV < nearzero) then + EffectiveHeatingNumber = 0.0_r8 + else + EffectiveHeatingNumber = exp(-4.528_r8/SAV) + end if + end function EffectiveHeatingNumber diff --git a/testing/functional_testing/fire/ros/FatesTestROS.F90 b/testing/functional_testing/fire/ros/FatesTestROS.F90 index 4ceb1db81c..56bce1a215 100644 --- a/testing/functional_testing/fire/ros/FatesTestROS.F90 +++ b/testing/functional_testing/fire/ros/FatesTestROS.F90 @@ -7,11 +7,17 @@ program FatesTestROS implicit none ! LOCALS: - type(fates_unit_test_param_reader) :: param_reader ! param reader instance - character(len=:), allocatable :: param_file ! input parameter file - real(r8), allocatable :: SAV(:) ! fuel surface area to volume ratio [/cm] - real(r8), allocatable :: beta(:) ! packing ratio [unitless] - real(r8), allocatable :: propagating_flux(:,:) ! propagating flux [unitless] + type(fates_unit_test_param_reader) :: param_reader ! param reader instance + character(len=:), allocatable :: param_file ! input parameter file + real(r8), allocatable :: SAV(:) ! fuel surface area to volume ratio (for prop flux) [/cm] + real(r8), allocatable :: beta(:) ! packing ratio [unitless] + real(r8), allocatable :: propagating_flux(:,:) ! propagating flux [unitless] + real(r8), allocatable :: SAV_values(:) ! fuel surface area to volume ratio (for reaction vel) [/cm] + real(r8), allocatable :: beta_ratio(:) ! relative packing ratio [unitless] + real(r8), allocatable :: reaction_velocity(:,:) ! reaction velocity [/min] + real(r8), allocatable :: fuel_moisture(:) ! fuel moisture [m3/m3] + real(r8), allocatable :: q_ig(:) ! heat of preignition [kJ/kg] + real(r8), allocatable :: eps(:) ! effective heating number [unitless] ! CONSTANTS: character(len=*), parameter :: out_file = 'ros_out.nc' ! output file @@ -29,7 +35,35 @@ subroutine TestPropFlux(SAV, propagating_flux, beta) end subroutine TestPropFlux - subroutine WriteROSData(out_file, beta, SAV, propagating_flux) + subroutine TestReactionVelocity(SAV, reaction_velocity, beta_ratio) + + use FatesConstantsMod, only : r8 => fates_r8 + use SFEquationsMod, only : OptimumReactionVelocity, MaximumReactionVelocity + implicit none + real(r8), allocatable, intent(out) :: SAV(:) + real(r8), allocatable, intent(out) :: reaction_velocity(:,:) + real(r8), allocatable, intent(out) :: beta_ratio(:) + + end subroutine TestReactionVelocity + + subroutine TestHeatofPreignition(fuel_moisture, q_ig) + use FatesConstantsMod, only : r8 => fates_r8 + use SFEquationsMod, only : HeatofPreignition + implicit none + real(r8), allocatable, intent(out) :: fuel_moisture(:) + real(r8), allocatable, intent(out) :: q_ig(:) + end subroutine TestHeatofPreignition + + subroutine TestEffectiveHeatingNumber(eps) + use FatesConstantsMod, only : r8 => fates_r8 + use SFEquationsMod, only : EffectiveHeatingNumber + implicit none + + real(r8), allocatable, intent(out) :: eps(:) + end subroutine TestEffectiveHeatingNumber + + subroutine WriteROSData(out_file, beta, SAV, propagating_flux, SAV_values, & + beta_ratio, reaction_velocity, fuel_moisture, q_ig, eps) use FatesConstantsMod, only : r8 => fates_r8 use FatesUnitTestIOMod, only : OpenNCFile, CloseNCFile, RegisterNCDims @@ -40,6 +74,12 @@ subroutine WriteROSData(out_file, beta, SAV, propagating_flux) real(r8), intent(in) :: beta(:) real(r8), intent(in) :: SAV(:) real(r8), intent(in) :: propagating_flux(:,:) + real(r8), intent(in) :: SAV_values(:) + real(r8), intent(in) :: beta_ratio(:) + real(r8), intent(in) :: reaction_velocity(:,:) + real(r8), intent(in) :: fuel_moisture(:) + real(r8), intent(in) :: q_ig(:) + real(r8), intent(in) :: eps(:) end subroutine WriteROSData @@ -55,13 +95,29 @@ end subroutine WriteROSData ! calculate propagating flux call TestPropFlux(SAV, propagating_flux, beta) + ! calculate reaction velocity + call TestReactionVelocity(SAV_values, reaction_velocity, beta_ratio) + + ! calculate heat of preignition + call TestHeatofPreignition(fuel_moisture, q_ig) + + ! calculate effective heating number + call TestEffectiveHeatingNumber(eps) + ! write output data - call WriteROSData(out_file, beta, SAV, propagating_flux) + call WriteROSData(out_file, beta, SAV, propagating_flux, SAV_values, beta_ratio, & + reaction_velocity, fuel_moisture, q_ig, eps) ! deallocate arrays if (allocated(propagating_flux)) deallocate(propagating_flux) if (allocated(SAV)) deallocate(SAV) if (allocated(beta)) deallocate(beta) + if (allocated(reaction_velocity)) deallocate(reaction_velocity) + if (allocated(SAV_values)) deallocate(SAV_values) + if (allocated(beta_ratio)) deallocate(beta_ratio) + if (allocated(fuel_moisture)) deallocate(fuel_moisture) + if (allocated(q_ig)) deallocate(q_ig) + if (allocated(eps)) deallocate(eps) end program FatesTestROS @@ -112,7 +168,131 @@ end subroutine TestPropFlux !========================================================================================= -subroutine WriteROSData(out_file, beta, SAV, propagating_flux) +subroutine TestReactionVelocity(SAV, reaction_velocity, beta_ratio) + ! + ! DESCRIPTION: + ! Calculates reaction velocity of a range of SAV and relative packing ratios + ! + + use FatesConstantsMod, only : r8 => fates_r8 + use SFEquationsMod, only : OptimumReactionVelocity, MaximumReactionVelocity + + implicit none + + ! ARGUMENTS: + real(r8), allocatable, intent(out) :: SAV(:) ! fuel surface area to volume ratio [/cm] + real(r8), allocatable, intent(out) :: reaction_velocity(:,:) ! reaction velocity [/min] + real(r8), allocatable, intent(out) :: beta_ratio(:) ! relative packing ratio [unitless] + + ! CONSTANTS: + real(r8), parameter :: beta_r_min = 0.1_r8 ! minimum beta_ratio to calculate [unitless] + real(r8), parameter :: beta_r_max = 5.0_r8 ! maximum beta_ratio to calculate [unitless] + real(r8), parameter :: beta_r_inc = 0.1_r8 ! beta_ratio increment to scale [unitless] + real(r8), parameter, dimension(5) :: SAV_vals = (/100.0_r8, 500.0_r8, 1000.0_r8, 2000.0_r8, 3000.0_r8/) ! SAV to use [/ft] + + ! LOCALS: + integer :: num_beta_r ! size of beta_ratio array + integer :: i, j ! looping indices + real(r8) :: max_reaction_vel ! maximum reaction velocity [/min] + + ! allocate arrays + num_beta_r = int((beta_r_max - beta_r_min)/beta_r_inc + 1) + allocate(reaction_velocity(num_beta_r, size(SAV_vals))) + allocate(beta_ratio(num_beta_r)) + allocate(SAV(size(SAV_vals))) + + do i = 1, num_beta_r + + beta_ratio(i) = beta_r_min + beta_r_inc*(i-1) + + do j = 1, size(SAV_vals) + SAV(j) = SAV_vals(j)/30.48_r8 ! convert from /ft to /cm + max_reaction_vel = MaximumReactionVelocity(SAV(j)) + reaction_velocity(i,j) = OptimumReactionVelocity(max_reaction_vel, SAV(j), & + beta_ratio(i)) + end do + end do + +end subroutine TestReactionVelocity + +!========================================================================================= + +subroutine TestHeatofPreignition(fuel_moisture, q_ig) + ! + ! DESCRIPTION: + ! Calculates heat of preignition for a range of fuel moisture values + ! + use FatesConstantsMod, only : r8 => fates_r8 + use SFEquationsMod, only : HeatofPreignition + + implicit none + + ! ARGUMENTS: + real(r8), allocatable, intent(out) :: fuel_moisture(:) ! fuel moisture [m3/m3] + real(r8), allocatable, intent(out) :: q_ig(:) ! heat of preignition [kJ/kg] + + ! CONSTANTS: + real(r8), parameter :: moist_min = 0.0_r8 ! minimum fuel moisture to calculate [m3/m3] + real(r8), parameter :: moist_max = 200.0_r8 ! maximum fuel moisture to calculate [m3/m3] + real(r8), parameter :: moist_inc = 0.5_r8 ! fuel moisture increment to scale [m3/m3] + + ! LOCALS: + integer :: num_moist ! size of fuel moisture array + integer :: i ! looping index + + ! allocate arrays + num_moist = int((moist_max - moist_min)/moist_inc + 1) + allocate(fuel_moisture(num_moist)) + allocate(q_ig(num_moist)) + + do i = 1, num_moist + fuel_moisture(i) = moist_min + moist_inc*(i-1) + q_ig(i) = HeatofPreignition(fuel_moisture(i)) + end do + +end subroutine TestHeatofPreignition + +!========================================================================================= + +subroutine TestEffectiveHeatingNumber(eps) + ! + ! DESCRIPTION: + ! Calculates effective heating number for a range of SAV values + ! + use FatesConstantsMod, only : r8 => fates_r8 + use SFEquationsMod, only : EffectiveHeatingNumber + + implicit none + + ! ARGUMENTS: + real(r8), allocatable, intent(out) :: eps(:) ! effective heating number [unitless] + + ! CONSTANTS: + real(r8), parameter :: SAV_min = 0.0_r8 ! minimum SAV to calculate [/cm] + real(r8), parameter :: SAV_max = 115.0_r8 ! maximum SAV to calculate [/cm] + real(r8), parameter :: SAV_inc = 1.0_r8 ! SAV increment to scale [/cm] + + ! LOCALS: + real(r8), allocatable :: SAV(:) ! fuel surface area to volume ratio [/cm] + integer :: num_SAV ! size of SAV array + integer :: i ! looping index + + ! allocate arrays + num_SAV = int((SAV_max - SAV_min)/SAV_inc + 1) + allocate(SAV(num_SAV)) + allocate(eps(num_SAV)) + + do i = 1, num_SAV + SAV(i) = SAV_min + SAV_inc*(i-1) + eps(i) = EffectiveHeatingNumber(SAV(i)) + end do + +end subroutine TestEffectiveHeatingNumber + +!========================================================================================= + +subroutine WriteROSData(out_file, beta, SAV, propagating_flux, SAV_values, beta_ratio, & + reaction_velocity, fuel_moisture, q_ig, eps) ! ! DESCRIPTION: ! writes out data from the test @@ -129,24 +309,36 @@ subroutine WriteROSData(out_file, beta, SAV, propagating_flux) real(r8), intent(in) :: beta(:) real(r8), intent(in) :: SAV(:) real(r8), intent(in) :: propagating_flux(:,:) + real(r8), intent(in) :: SAV_values(:) + real(r8), intent(in) :: beta_ratio(:) + real(r8), intent(in) :: reaction_velocity(:,:) + real(r8), intent(in) :: fuel_moisture(:) + real(r8), intent(in) :: q_ig(:) + real(r8), intent(in) :: eps(:) ! LOCALS: integer :: ncid ! netcdf id - character(len=20) :: dim_names(2) ! dimension names - integer :: dimIDs(2) ! dimension IDs + character(len=20) :: dim_names(5) ! dimension names + integer :: dimIDs(5) ! dimension IDs integer :: SAVID integer :: betaID integer :: propfluxID - + integer :: SAVindID + integer :: beta_r_ID + integer :: reactionvelID + integer :: moistID + integer :: qigID + integer :: epsID ! dimension names - dim_names = [character(len=20) :: 'SAV_value', 'beta_value'] + dim_names = [character(len=20) :: 'SAV', 'packing_ratio', 'SAV_ind', 'beta_ratio', 'fuel_moisture'] ! open file call OpenNCFile(trim(out_file), ncid, 'readwrite') ! register dimensions - call RegisterNCDims(ncid, dim_names, (/size(SAV), size(beta)/), 2, dimIDs) + call RegisterNCDims(ncid, dim_names, (/size(SAV), size(beta), size(SAV_values), & + size(beta_ratio), size(fuel_moisture)/), 5, dimIDs) ! first register dimension variables @@ -159,15 +351,49 @@ subroutine WriteROSData(out_file, beta, SAV, propagating_flux) call RegisterVar(ncid, 'packing_ratio', dimIDs(2:2), type_double, & [character(len=20) :: 'units', 'long_name'], & [character(len=150) :: '', 'packing ratio'], 2, betaID) + + ! register SAV values + call RegisterVar(ncid, 'SAV_ind', dimIDs(3:3), type_double, & + [character(len=20) :: 'units', 'long_name'], & + [character(len=150) :: '/cm', 'fuel surface area to volume ratio'], 2, SAVindID) + + ! register relative packing ratio + call RegisterVar(ncid, 'beta_ratio', dimIDs(4:4), type_double, & + [character(len=20) :: 'units', 'long_name'], & + [character(len=150) :: '', 'relative packing ratio'], 2, beta_r_ID) + + ! register fuel moisture + call RegisterVar(ncid, 'fuel_moisture', dimIDs(5:5), type_double, & + [character(len=20) :: 'units', 'long_name'], & + [character(len=150) :: 'm3/m3', 'fuel moisture'], 2, moistID) ! then register actual variables ! register propagating flux call RegisterVar(ncid, 'prop_flux', dimIDs(1:2), type_double, & [character(len=20) :: 'coordinates', 'units', 'long_name'], & - [character(len=150) :: 'SAV_value beta_value', '', 'propagating flux'], & + [character(len=150) :: 'SAV packing_ratio', '', 'propagating flux'], & 3, propfluxID) + ! register reaction velocity + call RegisterVar(ncid, 'reaction_velocity', (/dimIDs(4), dimIDs(3)/), type_double, & + [character(len=20) :: 'coordinates', 'units', 'long_name'], & + [character(len=150) :: 'SAV_ind beta_ratio', '/min', 'reaction velocity'], & + 3, reactionvelID) + + ! register heat of preignition + call RegisterVar(ncid, 'q_ig', dimIDs(5:5), type_double, & + [character(len=20) :: 'coordinates', 'units', 'long_name'], & + [character(len=150) :: 'fuel_moisture', 'kJ/kg', 'heat of preignition'], & + 3, qigID) + + ! register effective heating number + call RegisterVar(ncid, 'eps', dimIDs(1:1), type_double, & + [character(len=20) :: 'coordinates', 'units', 'long_name'], & + [character(len=150) :: 'SAV', '', 'effective heating number'], & + 3, epsID) + + ! finish defining variables call EndNCDef(ncid) @@ -175,6 +401,12 @@ subroutine WriteROSData(out_file, beta, SAV, propagating_flux) call WriteVar(ncid, SAVID, SAV(:)) call WriteVar(ncid, betaID, beta(:)) call WriteVar(ncid, propfluxID, propagating_flux(:,:)) + call WriteVar(ncid, SAVindID, SAV_values(:)) + call WriteVar(ncid, beta_r_ID, beta_ratio(:)) + call WriteVar(ncid, reactionvelID, reaction_velocity(:,:)) + call WriteVar(ncid, moistID, fuel_moisture(:)) + call WriteVar(ncid, qigID, q_ig(:)) + call WriteVar(ncid, epsID, eps(:)) ! close file call CloseNCFile(ncid) diff --git a/testing/functional_testing/fire/ros/ros_test.py b/testing/functional_testing/fire/ros/ros_test.py index ea63c3ca92..bcc34cef96 100644 --- a/testing/functional_testing/fire/ros/ros_test.py +++ b/testing/functional_testing/fire/ros/ros_test.py @@ -7,6 +7,7 @@ import pandas as pd import matplotlib.pyplot as plt from functional_class import FunctionalTest +from utils import blank_plot class ROSTest(FunctionalTest): @@ -39,6 +40,9 @@ def plot_output(self, run_dir: str, save_figs: bool, plot_dir: str): ros_dat = xr.open_dataset(os.path.join(run_dir, self.out_file)) self.plot_prop_flux(ros_dat, save_figs, plot_dir) + self.plot_reaction_vel(ros_dat, save_figs, plot_dir) + self.plot_qig(ros_dat, save_figs, plot_dir) + self.plot_eps(ros_dat, save_figs, plot_dir) @staticmethod def plot_prop_flux(data: xr.Dataset, save_fig: bool, plot_dir: str = None): @@ -99,4 +103,124 @@ def plot_prop_flux(data: xr.Dataset, save_fig: bool, plot_dir: str = None): if save_fig: fig_name = os.path.join(plot_dir, "prop_flux_plot.png") plt.savefig(fig_name) - \ No newline at end of file + + @staticmethod + def plot_reaction_vel(data: xr.Dataset, save_fig: bool, plot_dir: str = None): + """Plot reaction velocity + + Args: + data (xarray DataSet): the data set + save_fig (bool): whether or not to write out plot + plot_dir (str): if saving figure, where to write to + """ + data_frame = pd.DataFrame( + { + "beta_ratio": np.tile(data.beta_ratio, len(data.SAV_ind)), + "SAV": np.repeat(data.SAV_ind, len(data.beta_ratio)), + "reaction_vel": data.reaction_velocity.values.flatten(), + } + ) + + data_frame['SAV_ft'] = data_frame.SAV*30.48 + + max_beta = data_frame["beta_ratio"].max() + max_reaction_vel = 18 + + blank_plot(max_beta, 0.0, max_reaction_vel, 0.0, draw_horizontal_lines=True) + + SAV_vals = np.unique(data_frame.SAV_ft.values) + colors = ['#793922', '#6B8939', '#99291F','#CC9728', '#2C778A'] + colors.reverse() + + for i, sav in enumerate(SAV_vals): + dat = data_frame[data_frame.SAV_ft == sav] + plt.plot( + dat.beta_ratio.values, + dat['reaction_vel'].values, + lw=2, + color=colors[i], + label=sav, + ) + + plt.xlabel("Relative packing ratio", fontsize=11) + plt.ylabel("Reaction velocity (min$^{-1}$)", fontsize=11) + plt.legend(loc="upper right", title="Surface-area-to-volume ratio (ft$^{-1}$)") + + if save_fig: + fig_name = os.path.join(plot_dir, "reaction_vel_plot.png") + plt.savefig(fig_name) + + @staticmethod + def plot_qig(data: xr.Dataset, save_fig: bool, plot_dir: str = None): + """Plot heat of preignition + + Args: + data (xarray DataSet): the data set + save_fig (bool): whether or not to write out plot + plot_dir (str): if saving figure, where to write to + """ + data_frame = pd.DataFrame( + { + "fuel_moisture": data.fuel_moisture, + "q_ig": data.q_ig.values.flatten(), + } + ) + + data_frame['fuel_moisture_perc'] = data_frame.fuel_moisture*100.0 + data_frame['q_ig_btu'] = data_frame.q_ig*0.947817/2.20462 + + max_moist = 200.0 + max_qig = 2500.0 + + blank_plot(max_moist, 0.0, max_qig, 0.0, draw_horizontal_lines=True) + + plt.plot( + data_frame.fuel_moisture_perc.values, + data_frame['q_ig_btu'].values, + lw=2, + color='k', + ) + + plt.xlabel("Fuel moisture (%)", fontsize=11) + plt.ylabel("Heat of Preignition (Btu lb$^{-1}$)", fontsize=11) + + if save_fig: + fig_name = os.path.join(plot_dir, "qig_plot.png") + plt.savefig(fig_name) + + @staticmethod + def plot_eps(data: xr.Dataset, save_fig: bool, plot_dir: str = None): + """Plot effective heating number + + Args: + data (xarray DataSet): the data set + save_fig (bool): whether or not to write out plot + plot_dir (str): if saving figure, where to write to + """ + data_frame = pd.DataFrame( + { + "SAV": data.SAV, + "eps": data.eps.values.flatten(), + } + ) + + data_frame['SAV_ft'] = data_frame.SAV*30.48 + + max_SAV = 3500.0 + max_eps = 1.0 + + blank_plot(max_SAV, 0.0, max_eps, 0.0, draw_horizontal_lines=True) + + plt.plot( + data_frame.SAV_ft.values, + data_frame['eps'].values, + lw=2, + color='k', + ) + + plt.xlabel("Surface-area-to-volume ratio (ft$^{-1}$)", fontsize=11) + plt.ylabel("Effective heating number", fontsize=11) + + if save_fig: + fig_name = os.path.join(plot_dir, "eps_plot.png") + plt.savefig(fig_name) \ No newline at end of file From 2b62d7adb56ce6d0946ed5b8c98279a91aa85dd1 Mon Sep 17 00:00:00 2001 From: adrifoster Date: Fri, 6 Dec 2024 14:43:45 -0700 Subject: [PATCH 099/111] remove blank line --- fire/SFEquationsMod.F90 | 1 - 1 file changed, 1 deletion(-) diff --git a/fire/SFEquationsMod.F90 b/fire/SFEquationsMod.F90 index 3cb51c32f9..6daaafeb1e 100644 --- a/fire/SFEquationsMod.F90 +++ b/fire/SFEquationsMod.F90 @@ -196,7 +196,6 @@ real(r8) function EffectiveHeatingNumber(SAV) EffectiveHeatingNumber = exp(-4.528_r8/SAV) end if - end function EffectiveHeatingNumber !------------------------------------------------------------------------------------- From e7a7c54492897ecd519fd16eb29c035c65d2e5c3 Mon Sep 17 00:00:00 2001 From: adrifoster Date: Mon, 9 Dec 2024 15:24:22 -0700 Subject: [PATCH 100/111] update test --- .../functional_testing/fire/fuel/FatesTestFuel.F90 | 7 +++++-- .../fire/shr/FatesTestFireMod.F90 | 13 +++++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/testing/functional_testing/fire/fuel/FatesTestFuel.F90 b/testing/functional_testing/fire/fuel/FatesTestFuel.F90 index 42237e095e..3f185985aa 100644 --- a/testing/functional_testing/fire/fuel/FatesTestFuel.F90 +++ b/testing/functional_testing/fire/fuel/FatesTestFuel.F90 @@ -33,6 +33,7 @@ program FatesTestFuel real(r8), allocatable :: fuel_BD(:) ! bulk density of fuel [kg/m3] real(r8), allocatable :: fuel_SAV(:) ! fuel surface area to volume ratio [/cm] real(r8), allocatable :: fuel_moisture(:,:) ! fuel moisture [m3/m3] + real(r8), allocatable :: fuel_MEF(:,:) ! fuel moisture of extinction [m3/m3 character(len=100), allocatable :: fuel_names(:) ! names of fuel models character(len=2), allocatable :: carriers(:) ! carriers of fuel models integer :: i, f ! looping indices @@ -43,7 +44,7 @@ program FatesTestFuel character(len=*), parameter :: out_file = 'fuel_out.nc' ! output file ! fuel models to test - integer, parameter, dimension(3) :: fuel_models = (/102, 183, 164/) + integer, parameter, dimension(5) :: fuel_models = (/102, 183, 164, 104, 163/) ! number of fuel models to test num_fuel_models = size(fuel_models) @@ -55,6 +56,7 @@ program FatesTestFuel allocate(wind(n_days)) allocate(NI(n_days)) allocate(fuel_moisture(n_days, num_fuel_models)) + allocate(fuel_MEF(n_days, num_fuel_models)) allocate(fuel_loading(num_fuel_classes, num_fuel_models)) allocate(frac_loading(num_fuel_classes, num_fuel_models)) allocate(fuel_BD(num_fuel_models)) @@ -112,12 +114,13 @@ program FatesTestFuel do f = 1, num_fuel_models call fuel(f)%UpdateFuelMoisture(SF_val_SAV, SF_val_drying_ratio, fireWeather) fuel_moisture(i, f) = fuel(f)%average_moisture_notrunks + fuel_MEF(i, f) = fuel(f)%MEF_notrunks end do end do ! write out data call WriteFireData(out_file, n_days, num_fuel_models, temp_degC, precip, rh, NI, & fuel_loading, frac_loading, fuel_BD, fuel_SAV, non_trunk_loading, fuel_moisture, & - fuel_models, carriers) + fuel_MEF, fuel_models, carriers) end program FatesTestFuel diff --git a/testing/functional_testing/fire/shr/FatesTestFireMod.F90 b/testing/functional_testing/fire/shr/FatesTestFireMod.F90 index 5de706387e..a9ce2be08e 100644 --- a/testing/functional_testing/fire/shr/FatesTestFireMod.F90 +++ b/testing/functional_testing/fire/shr/FatesTestFireMod.F90 @@ -106,7 +106,7 @@ end subroutine ReadDatmData subroutine WriteFireData(out_file, nsteps, nfuelmods, temp_degC, precip, rh, NI, & loading, frac_loading, fuel_BD, fuel_SAV, non_trunk_loading, fuel_moisture, & - fuel_models, carriers) + fuel_MEF, fuel_models, carriers) ! ! DESCRIPTION: ! writes out data from the unit test @@ -124,6 +124,7 @@ subroutine WriteFireData(out_file, nsteps, nfuelmods, temp_degC, precip, rh, NI, real(r8), intent(in) :: frac_loading(:,:) real(r8), intent(in) :: non_trunk_loading(:) real(r8), intent(in) :: fuel_moisture(:,:) + real(r8), intent(in) :: fuel_MEF(:,:) real(r8), intent(in) :: fuel_BD(:) real(r8), intent(in) :: fuel_SAV(:) integer, intent(in) :: fuel_models(:) @@ -144,7 +145,7 @@ subroutine WriteFireData(out_file, nsteps, nfuelmods, temp_degC, precip, rh, NI, integer :: tot_loadingID integer :: BDID, SAVID integer :: moistID - integer :: cID + integer :: cID, mefID ! create pft indices allocate(time_index(nsteps)) @@ -218,6 +219,13 @@ subroutine WriteFireData(out_file, nsteps, nfuelmods, temp_degC, precip, rh, NI, [character(len=150) :: 'time fuel_model', 'm3 m-3', 'average fuel moisture'], & 3, moistID) + ! register fuel MEF + call RegisterVar(ncid, 'fuel_MEF', (/dimIDs(1), dimIDs(3)/), type_double, & + [character(len=20) :: 'coordinates', 'units', 'long_name'], & + [character(len=150) :: 'time fuel_model', 'm3 m-3', 'average fuel moisture of extinction'], & + 3, mefID) + + ! register fuel loading call RegisterVar(ncid, 'fuel_loading', dimIDs(2:3), type_double, & [character(len=20) :: 'coordinates', 'units', 'long_name'], & @@ -266,6 +274,7 @@ subroutine WriteFireData(out_file, nsteps, nfuelmods, temp_degC, precip, rh, NI, call WriteVar(ncid, moistiD, fuel_moisture(:,:)) call WriteVar(ncid, BDID, fuel_BD(:)) call WriteVar(ncid, SAVID, fuel_SAV(:)) + call WriteVar(ncid, mefID, fuel_MEF(:,:)) call CloseNCFile(ncid) From 79fb219b1f0882d098aeff5e6364398fcdcc6ef6 Mon Sep 17 00:00:00 2001 From: Adrianna Foster Date: Wed, 11 Dec 2024 09:30:12 -0700 Subject: [PATCH 101/111] initial fix --- biogeochem/EDPatchDynamicsMod.F90 | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/biogeochem/EDPatchDynamicsMod.F90 b/biogeochem/EDPatchDynamicsMod.F90 index 9b02c9e14a..a9734a0a56 100644 --- a/biogeochem/EDPatchDynamicsMod.F90 +++ b/biogeochem/EDPatchDynamicsMod.F90 @@ -767,10 +767,6 @@ subroutine spawn_patches( currentSite, bc_in) ! and burned litter to atmosphere. Thus it is important to zero fuel%frac_burnt when ! fire is not the current disturbance regime. - if(i_disturbance_type .ne. dtype_ifire) then - currentPatch%fuel%frac_burnt(:) = 0._r8 - end if - call CopyPatchMeansTimers(currentPatch, newPatch) call TransLitterNewPatch( currentSite, currentPatch, newPatch, patch_site_areadis) @@ -1045,7 +1041,6 @@ subroutine spawn_patches( currentSite, bc_in) ! Some of of the leaf mass from living plants has been ! burned off. Here, we remove that mass, and ! tally it in the flux we sent to the atmosphere - if(prt_params%woody(currentCohort%pft) == itrue)then leaf_burn_frac = currentCohort%fraction_crown_burned else @@ -1922,7 +1917,8 @@ end subroutine set_patchno subroutine TransLitterNewPatch(currentSite, & currentPatch, & newPatch, & - patch_site_areadis) + patch_site_areadis, & + dist_type) ! ----------------------------------------------------------------------------------- ! @@ -1971,6 +1967,7 @@ subroutine TransLitterNewPatch(currentSite, & type(fates_patch_type) , intent(inout) :: newPatch ! New patch real(r8) , intent(in) :: patch_site_areadis ! Area being donated ! by current patch + integer, intent(in) :: dist_type ! disturbance type ! locals @@ -1993,6 +1990,7 @@ subroutine TransLitterNewPatch(currentSite, & real(r8) :: litter_stock0,litter_stock1 real(r8) :: burn_flux0,burn_flux1 real(r8) :: error + real(r8) :: frac_burnt ! fraction burnt of current fuel type [0-1] do el = 1,num_elements @@ -2078,13 +2076,19 @@ subroutine TransLitterNewPatch(currentSite, & end if do c = 1,ncwd + + if (dist_type == dtype_ifall .and. currentPatch%fire == 1) then + frac_burnt = currentPatch%fuel%frac_burnt(c) + else + frac_burnt = 0.0_r8 + end if ! Transfer above ground CWD donatable_mass = curr_litt%ag_cwd(c) * patch_site_areadis * & - (1._r8 - currentPatch%fuel%frac_burnt(c)) + (1._r8 - frac_burnt) burned_mass = curr_litt%ag_cwd(c) * patch_site_areadis * & - currentPatch%fuel%frac_burnt(c) + frac_burnt new_litt%ag_cwd(c) = new_litt%ag_cwd(c) + donatable_mass*donate_m2 curr_litt%ag_cwd(c) = curr_litt%ag_cwd(c) + donatable_mass*retain_m2 @@ -2100,15 +2104,22 @@ subroutine TransLitterNewPatch(currentSite, & end do enddo + + if (dist_type == dtype_ifall .and. currentPatch%fire == 1) then + frac_burnt = currentPatch%fuel%frac_burnt(fuel_classes%dead_leaves()) + else + frac_burnt = 0.0_r8 + end if + do dcmpy=1,ndcmpy ! Transfer leaf fines donatable_mass = curr_litt%leaf_fines(dcmpy) * patch_site_areadis * & - (1._r8 - currentPatch%fuel%frac_burnt(fuel_classes%dead_leaves())) + (1._r8 - frac_burnt) burned_mass = curr_litt%leaf_fines(dcmpy) * patch_site_areadis * & - currentPatch%fuel%frac_burnt(fuel_classes%dead_leaves()) + frac_burnt new_litt%leaf_fines(dcmpy) = new_litt%leaf_fines(dcmpy) + donatable_mass*donate_m2 curr_litt%leaf_fines(dcmpy) = curr_litt%leaf_fines(dcmpy) + donatable_mass*retain_m2 From e04e140cc4527fb8e60cc34558f05c728dbd2339 Mon Sep 17 00:00:00 2001 From: Adrianna Foster Date: Wed, 11 Dec 2024 09:57:40 -0700 Subject: [PATCH 102/111] add idisttype --- biogeochem/EDPatchDynamicsMod.F90 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/biogeochem/EDPatchDynamicsMod.F90 b/biogeochem/EDPatchDynamicsMod.F90 index a9734a0a56..b7df2fb413 100644 --- a/biogeochem/EDPatchDynamicsMod.F90 +++ b/biogeochem/EDPatchDynamicsMod.F90 @@ -769,7 +769,7 @@ subroutine spawn_patches( currentSite, bc_in) call CopyPatchMeansTimers(currentPatch, newPatch) - call TransLitterNewPatch( currentSite, currentPatch, newPatch, patch_site_areadis) + call TransLitterNewPatch( currentSite, currentPatch, newPatch, patch_site_areadis, i_disturbance_type) ! Transfer in litter fluxes from plants in various contexts of death and destruction select case(i_disturbance_type) @@ -1719,7 +1719,7 @@ subroutine split_patch(currentSite, currentPatch, new_patch, fraction_to_keep, a call CopyPatchMeansTimers(currentPatch, new_patch) - call TransLitterNewPatch( currentSite, currentPatch, new_patch, temp_area) + call TransLitterNewPatch( currentSite, currentPatch, new_patch, temp_area, 0) currentPatch%fuel%frac_burnt(:) = 0._r8 From f5119a37e88edea4b33d1a1c2257e13bc1f81cb4 Mon Sep 17 00:00:00 2001 From: Adrianna Foster Date: Wed, 11 Dec 2024 10:02:56 -0700 Subject: [PATCH 103/111] fix typo --- biogeochem/EDPatchDynamicsMod.F90 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/biogeochem/EDPatchDynamicsMod.F90 b/biogeochem/EDPatchDynamicsMod.F90 index b7df2fb413..38b9eba45e 100644 --- a/biogeochem/EDPatchDynamicsMod.F90 +++ b/biogeochem/EDPatchDynamicsMod.F90 @@ -2077,7 +2077,7 @@ subroutine TransLitterNewPatch(currentSite, & do c = 1,ncwd - if (dist_type == dtype_ifall .and. currentPatch%fire == 1) then + if (dist_type == dtype_ifire .and. currentPatch%fire == 1) then frac_burnt = currentPatch%fuel%frac_burnt(c) else frac_burnt = 0.0_r8 @@ -2105,7 +2105,7 @@ subroutine TransLitterNewPatch(currentSite, & enddo - if (dist_type == dtype_ifall .and. currentPatch%fire == 1) then + if (dist_type == dtype_ifire .and. currentPatch%fire == 1) then frac_burnt = currentPatch%fuel%frac_burnt(fuel_classes%dead_leaves()) else frac_burnt = 0.0_r8 From 8e919983bdc807700374d497d44f6afdef6aec6a Mon Sep 17 00:00:00 2001 From: Adrianna Foster Date: Wed, 11 Dec 2024 11:54:25 -0700 Subject: [PATCH 104/111] zero frac_burnt at top of fire mod --- fire/SFMainMod.F90 | 1 + 1 file changed, 1 insertion(+) diff --git a/fire/SFMainMod.F90 b/fire/SFMainMod.F90 index 97bbcb6d54..03b06aaba5 100644 --- a/fire/SFMainMod.F90 +++ b/fire/SFMainMod.F90 @@ -71,6 +71,7 @@ subroutine fire_model(currentSite, bc_in) do while(associated(currentPatch)) currentPatch%frac_burnt = 0.0_r8 currentPatch%fire = 0 + currentPatch%fuel%frac_burnt(:) = 0.0_r8 currentPatch => currentPatch%older end do From 4c565cc1ceba6b1a9d26372d22de8cddd7800689 Mon Sep 17 00:00:00 2001 From: Adrianna Foster Date: Wed, 11 Dec 2024 13:52:40 -0700 Subject: [PATCH 105/111] suggested changes --- biogeochem/EDPatchDynamicsMod.F90 | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/biogeochem/EDPatchDynamicsMod.F90 b/biogeochem/EDPatchDynamicsMod.F90 index 38b9eba45e..75173d200d 100644 --- a/biogeochem/EDPatchDynamicsMod.F90 +++ b/biogeochem/EDPatchDynamicsMod.F90 @@ -2074,13 +2074,11 @@ subroutine TransLitterNewPatch(currentSite, & litter_stock0 = curr_litt%GetTotalLitterMass()*currentPatch%area + & new_litt%GetTotalLitterMass()*newPatch%area end if - + do c = 1,ncwd - + frac_burnt = 0.0_r8 if (dist_type == dtype_ifire .and. currentPatch%fire == 1) then frac_burnt = currentPatch%fuel%frac_burnt(c) - else - frac_burnt = 0.0_r8 end if ! Transfer above ground CWD @@ -2105,13 +2103,11 @@ subroutine TransLitterNewPatch(currentSite, & enddo + frac_burnt = 0.0_r8 if (dist_type == dtype_ifire .and. currentPatch%fire == 1) then frac_burnt = currentPatch%fuel%frac_burnt(fuel_classes%dead_leaves()) - else - frac_burnt = 0.0_r8 end if - - + do dcmpy=1,ndcmpy ! Transfer leaf fines From 48e9fa477ea66d76602ea0e7c723a365134605d5 Mon Sep 17 00:00:00 2001 From: adrifoster Date: Thu, 12 Dec 2024 16:04:05 -0700 Subject: [PATCH 106/111] fix --- CMakeLists.txt | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9760a39c1d..4691cc26a2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,7 @@ cmake_minimum_required(VERSION 3.4) list(APPEND CMAKE_MODULE_PATH ${CIME_CMAKE_MODULE_DIRECTORY}) +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../../share/cmake") FIND_PATH(NETCDFC_FOUND libnetcdf.a ${NETCDF_C_DIR}/lib) FIND_PATH(NETCDFF_FOUND libnetcdff.a ${NETCDF_FORTRAN_DIR}/lib) @@ -45,16 +46,6 @@ foreach (sourcefile ${share_sources}) endforeach() # Remove shr_cal_mod from share_sources. -# -# shr_cal_mod depends on ESMF (or the lightweight esmf wrf timemgr, at -# least). Since CTSM doesn't currently use shr_cal_mod, we're avoiding -# the extra overhead of including esmf_wrf_timemgr sources in this -# build. -# -# TODO: like above, this should be moved into a general-purpose function -# in Sourcelist_utils. Then this block of code could be replaced with a -# single call, like: remove_source_file(${share_sources} -# "shr_cal_mod.F90") foreach (sourcefile ${share_sources}) string(REGEX MATCH "shr_cal_mod.F90" match_found ${sourcefile}) if(match_found) @@ -62,6 +53,14 @@ foreach (sourcefile ${share_sources}) endif() endforeach() +# Remove shr_pio_mod from share_sources. +foreach (sourcefile ${share_sources}) + string(REGEX MATCH "shr_pio_mod.F90" match_found ${sourcefile}) + if(match_found) + list(REMOVE_ITEM share_sources ${sourcefile}) + endif() +endforeach() + # Build libraries containing stuff needed for the unit tests. # Eventually, these add_library calls should probably be distributed into the correct location, rather than being in this top-level CMakeLists.txt file. add_library(csm_share ${share_sources}) @@ -81,10 +80,13 @@ include_directories(${NETCDF_C_DIR}/include ${NETCDF_FORTRAN_DIR}/include) link_directories(${NETCDF_C_DIR}/lib ${NETCDF_FORTRAN_DIR}/lib) + # Tell cmake to look for libraries & mod files here, because this is where we built libraries include_directories(${CMAKE_CURRENT_BINARY_DIR}) -link_directories(${CMAKE_CURRENT_BINARY_DIR}) +# Directories and libraries to include in the link step +link_directories(${CMAKE_CURRENT_BINARY_DIR}) + # Add the main test directory add_subdirectory(${HLM_ROOT}/src/fates/testing) From f11b673837aecdec614f7a1a38c55666cda25d16 Mon Sep 17 00:00:00 2001 From: Adrianna Foster Date: Mon, 16 Dec 2024 14:54:38 -0700 Subject: [PATCH 107/111] remove zero of frac_burnt in split_patch --- biogeochem/EDPatchDynamicsMod.F90 | 2 -- 1 file changed, 2 deletions(-) diff --git a/biogeochem/EDPatchDynamicsMod.F90 b/biogeochem/EDPatchDynamicsMod.F90 index 75173d200d..da979d87cc 100644 --- a/biogeochem/EDPatchDynamicsMod.F90 +++ b/biogeochem/EDPatchDynamicsMod.F90 @@ -1721,8 +1721,6 @@ subroutine split_patch(currentSite, currentPatch, new_patch, fraction_to_keep, a call TransLitterNewPatch( currentSite, currentPatch, new_patch, temp_area, 0) - currentPatch%fuel%frac_burnt(:) = 0._r8 - ! Next, we loop through the cohorts in the donor patch, copy them with ! area modified number density into the new-patch, and apply survivorship. ! ------------------------------------------------------------------------- From da4ed44a85849bcc3ce54627763dbc1799593b8a Mon Sep 17 00:00:00 2001 From: adrifoster Date: Mon, 16 Dec 2024 15:39:10 -0700 Subject: [PATCH 108/111] requested changes --- fire/SFEquationsMod.F90 | 49 ++++++++++++++----- fire/SFMainMod.F90 | 4 +- .../functional_testing/fire/ros/ros_test.py | 17 ++++--- 3 files changed, 48 insertions(+), 22 deletions(-) diff --git a/fire/SFEquationsMod.F90 b/fire/SFEquationsMod.F90 index 6daaafeb1e..16e8fedc8a 100644 --- a/fire/SFEquationsMod.F90 +++ b/fire/SFEquationsMod.F90 @@ -19,7 +19,7 @@ module SFEquationsMod public :: ReactionIntensity public :: HeatofPreignition public :: EffectiveHeatingNumber - public :: PhiWind + public :: WindFactor public :: PropagatingFlux public :: ForwardRateOfSpread public :: BackwardRateOfSpread @@ -55,8 +55,7 @@ real(r8) function MaximumReactionVelocity(SAV) ! ! DESCRIPTION: ! Calculates maximum reaction velocity in /min - ! Reaction velocity that would exist if fuel were free of moisture and - ! contained mineral and alpha cellulose + ! ! From Equation 36 in Rothermel 1972; Fig. 12 ! @@ -73,6 +72,11 @@ real(r8) function OptimumReactionVelocity(max_reaction_vel, SAV, beta_ratio) ! ! DESCRIPTION: ! Calculates optimum reaction velocity in /min + ! + ! Reaction velocity (i.e. rate of fuel consumption) that would exist if the + ! fuel were free of moisture and contained minerals at the same reaction + ! concentration as alpha cellulose + ! ! From Equation 38 in Rothermel 1972; Fig. 11 ! @@ -111,10 +115,11 @@ real(r8) function MoistureCoefficient(moisture, MEF) ! average values for litter pools (dead leaves, twigs, small and large branches), plus grass mw_weight = moisture/MEF - ! moist_damp is unitless - MoistureCoefficient = max(0.0_r8, (1.0_r8 - (2.59_r8*mw_weight) + & - (5.11_r8*(mw_weight**2.0_r8)) - (3.52_r8*(mw_weight**3.0_r8)))) - + ! MoistureCoefficient is unitless + MoistureCoefficient = 1.0_r8 - (2.59_r8*mw_weight) + (5.11_r8*(mw_weight**2.0_r8)) - & + (3.52_r8*(mw_weight**3.0_r8)) + + if (MoistureCoefficient < nearzero) MoistureCoefficient = 0.0_r8 if (MoistureCoefficient > 1.0_r8) MoistureCoefficient = 1.0_r8 end function MoistureCoefficient @@ -125,6 +130,8 @@ real(r8) function ReactionIntensity(fuel_loading, SAV, beta_ratio, moisture, ME ! ! DESCRIPTION: ! Calculates reaction intensity in kJ/m2/min + ! + ! Rate of energy release per unit area within the flaming front ! ! USES @@ -163,8 +170,11 @@ real(r8) function HeatofPreignition(fuel_moisture) ! DESCRIPTION: ! Calculates heat of pre-ignition in kJ/kg ! + ! Heat of pre-ignition is the heat required to bring a unit weight of fuel to + ! ignition + ! ! Equation A4 in Thonicke et al. 2010 - ! Rothermel EQ12= 250 Btu/lb + 1116 Btu/lb * average_moisture + ! Rothermel EQ12 = 250 Btu/lb + 1116 Btu/lb * average_moisture ! conversion of Rothermel (1972) EQ12 in BTU/lb to current kJ/kg ! @@ -185,7 +195,11 @@ real(r8) function EffectiveHeatingNumber(SAV) ! DESCRIPTION: ! Calculates effective heating number [unitless] ! + ! Proportion of a fuel particle that is heated to ignition temperature at the time + ! flaming combustion starts + ! ! Equation A3 in Thonicke et al. 2010 + ! ! ARGUMENTS: real(r8), intent(in) :: SAV ! fuel surface area to volume ratio [/cm] @@ -200,10 +214,12 @@ end function EffectiveHeatingNumber !------------------------------------------------------------------------------------- - real(r8) function PhiWind(wind_speed, beta_ratio, SAV) + real(r8) function WindFactor(wind_speed, beta_ratio, SAV) ! ! DESCRIPTION: - ! Calculates wind factor [unitless] + ! Calculates wind factor for the rate of spread equation [unitless] + ! + ! Accounts for effect of wind speed increasing ROS ! ! ARGUMENTS: @@ -225,16 +241,19 @@ real(r8) function PhiWind(wind_speed, beta_ratio, SAV) ! Equation A5 in Thonicke et al. 2010 ! convert wind_speed (wind at elev relevant to fire) from m/min to ft/min for Rothermel ROS Eq. - PhiWind = c*((3.281_r8*wind_speed)**b)*(beta_ratio**(-e)) + WindFactor = c*((3.281_r8*wind_speed)**b)*(beta_ratio**(-e)) - end function PhiWind + end function WindFactor !------------------------------------------------------------------------------------- real(r8) function PropagatingFlux(beta, SAV) ! ! DESCRIPTION: - ! Calculates propagating flux [unitless] + ! Calculates propagating flux ratio [unitless] + ! + ! Proportion of reaction intensity that heats adjacent fuel particles to ignition + ! ! Equation A2 in Thonicke et al. 2010 and Eq. 42 Rothermel 1972 ! @@ -253,6 +272,9 @@ real(r8) function ForwardRateOfSpread(bulk_density, eps, q_ig, i_r, xi, phi_wind ! ! DESCRIPTION: ! Calculates forward rate of spread [m/min] + ! + ! Flaming front of a surface fire + ! ! Equation 9. Thonicke et al. 2010 ! @@ -278,6 +300,7 @@ real(r8) function BackwardRateOfSpread(ros_front, wind_speed) ! ! DESCRIPTION: ! Calculates backwards rate of spread [m/min] + ! ! Equation 10 in Thonicke et al. 2010 ! backward ROS from Can FBP System (1992) ! backward ROS wind not changed by vegetation diff --git a/fire/SFMainMod.F90 b/fire/SFMainMod.F90 index fbe878ec06..17660b0550 100644 --- a/fire/SFMainMod.F90 +++ b/fire/SFMainMod.F90 @@ -214,7 +214,7 @@ subroutine CalculateSurfaceRateOfSpread(currentSite) use SFParamsMod, only : SF_val_miner_total, SF_val_part_dens use SFEquationsMod, only : OptimumPackingRatio, ReactionIntensity use SFEquationsMod, only : HeatofPreignition, EffectiveHeatingNumber - use SFEquationsMod, only : PhiWind, PropagatingFlux + use SFEquationsMod, only : WindFactor, PropagatingFlux use SFEquationsMod, only : ForwardRateOfSpread, BackwardRateOfSpread ! ARGUMENTS: @@ -265,7 +265,7 @@ subroutine CalculateSurfaceRateOfSpread(currentSite) eps = EffectiveHeatingNumber(currentPatch%fuel%SAV_notrunks) ! wind factor [unitless] - phi_wind = PhiWind(currentSite%fireWeather%effective_windspeed, beta_ratio, & + phi_wind = WindFactor(currentSite%fireWeather%effective_windspeed, beta_ratio, & currentPatch%fuel%SAV_notrunks) ! propagating flux [unitless] diff --git a/testing/functional_testing/fire/ros/ros_test.py b/testing/functional_testing/fire/ros/ros_test.py index bcc34cef96..e96e6f46c5 100644 --- a/testing/functional_testing/fire/ros/ros_test.py +++ b/testing/functional_testing/fire/ros/ros_test.py @@ -9,6 +9,10 @@ from functional_class import FunctionalTest from utils import blank_plot +COLORS = ['#793922', '#6B8939', '#99291F','#CC9728', '#2C778A'] +CM_TO_FT = 30.48 +KJKG_TO_BTULB = 0.947817/2.20462 + class ROSTest(FunctionalTest): """ROS test class""" @@ -61,7 +65,7 @@ def plot_prop_flux(data: xr.Dataset, save_fig: bool, plot_dir: str = None): } ) - data_frame['SAV_ft'] = data_frame.SAV*30.48 + data_frame['SAV_ft'] = data_frame.SAV*CM_TO_FT # covert to ft to compare with original Rothermel equations max_SAV = data_frame["SAV_ft"].max() max_prop_flux = 0.14 @@ -84,7 +88,6 @@ def plot_prop_flux(data: xr.Dataset, save_fig: bool, plot_dir: str = None): plt.grid(True) packing_ratio = np.unique(data_frame.packing_ratio.values) - colors = ['#6B8939', '#99291F','#CC9728', '#2C778A'] for i, beta in enumerate(packing_ratio): dat = data_frame[data_frame.packing_ratio == beta] @@ -92,7 +95,7 @@ def plot_prop_flux(data: xr.Dataset, save_fig: bool, plot_dir: str = None): dat.SAV_ft.values, dat['prop_flux'].values, lw=2, - color=colors[i], + color=COLORS[i], label=beta, ) @@ -121,7 +124,7 @@ def plot_reaction_vel(data: xr.Dataset, save_fig: bool, plot_dir: str = None): } ) - data_frame['SAV_ft'] = data_frame.SAV*30.48 + data_frame['SAV_ft'] = data_frame.SAV*CM_TO_FT max_beta = data_frame["beta_ratio"].max() max_reaction_vel = 18 @@ -129,7 +132,7 @@ def plot_reaction_vel(data: xr.Dataset, save_fig: bool, plot_dir: str = None): blank_plot(max_beta, 0.0, max_reaction_vel, 0.0, draw_horizontal_lines=True) SAV_vals = np.unique(data_frame.SAV_ft.values) - colors = ['#793922', '#6B8939', '#99291F','#CC9728', '#2C778A'] + colors = COLORS colors.reverse() for i, sav in enumerate(SAV_vals): @@ -167,7 +170,7 @@ def plot_qig(data: xr.Dataset, save_fig: bool, plot_dir: str = None): ) data_frame['fuel_moisture_perc'] = data_frame.fuel_moisture*100.0 - data_frame['q_ig_btu'] = data_frame.q_ig*0.947817/2.20462 + data_frame['q_ig_btu'] = data_frame.q_ig*KJKG_TO_BTULB # match rothermel graph units max_moist = 200.0 max_qig = 2500.0 @@ -204,7 +207,7 @@ def plot_eps(data: xr.Dataset, save_fig: bool, plot_dir: str = None): } ) - data_frame['SAV_ft'] = data_frame.SAV*30.48 + data_frame['SAV_ft'] = data_frame.SAV*CM_TO_FT max_SAV = 3500.0 max_eps = 1.0 From fe1d2e82444da5948123c033779c1da4e56d836f Mon Sep 17 00:00:00 2001 From: adrifoster Date: Mon, 16 Dec 2024 15:42:56 -0700 Subject: [PATCH 109/111] pylint --- .../functional_testing/fire/ros/ros_test.py | 80 ++++++++++--------- 1 file changed, 42 insertions(+), 38 deletions(-) diff --git a/testing/functional_testing/fire/ros/ros_test.py b/testing/functional_testing/fire/ros/ros_test.py index e96e6f46c5..e845040fdb 100644 --- a/testing/functional_testing/fire/ros/ros_test.py +++ b/testing/functional_testing/fire/ros/ros_test.py @@ -1,5 +1,5 @@ """ -Concrete class for running the fuel functional test for FATES. +Concrete class for running the ros functional test for FATES. """ import os import numpy as np @@ -9,9 +9,9 @@ from functional_class import FunctionalTest from utils import blank_plot -COLORS = ['#793922', '#6B8939', '#99291F','#CC9728', '#2C778A'] +COLORS = ["#793922", "#6B8939", "#99291F", "#CC9728", "#2C778A"] CM_TO_FT = 30.48 -KJKG_TO_BTULB = 0.947817/2.20462 +KJKG_TO_BTULB = 0.947817 / 2.20462 class ROSTest(FunctionalTest): @@ -39,21 +39,21 @@ def plot_output(self, run_dir: str, save_figs: bool, plot_dir: str): save_figs (bool): whether or not to save the figures plot_dir (str): plot directory """ - + # read in ros data ros_dat = xr.open_dataset(os.path.join(run_dir, self.out_file)) - + self.plot_prop_flux(ros_dat, save_figs, plot_dir) self.plot_reaction_vel(ros_dat, save_figs, plot_dir) self.plot_qig(ros_dat, save_figs, plot_dir) self.plot_eps(ros_dat, save_figs, plot_dir) - - @staticmethod + + @staticmethod def plot_prop_flux(data: xr.Dataset, save_fig: bool, plot_dir: str = None): """Plot propagating flux Args: - data (xarray DataSet): the data set + data (xarray DataSet): the data set save_fig (bool): whether or not to write out plot plot_dir (str): if saving figure, where to write to """ @@ -64,8 +64,10 @@ def plot_prop_flux(data: xr.Dataset, save_fig: bool, plot_dir: str = None): "prop_flux": data.prop_flux.values.flatten(), } ) - - data_frame['SAV_ft'] = data_frame.SAV*CM_TO_FT # covert to ft to compare with original Rothermel equations + + data_frame["SAV_ft"] = ( + data_frame.SAV * CM_TO_FT + ) # covert to ft to compare with original Rothermel equations max_SAV = data_frame["SAV_ft"].max() max_prop_flux = 0.14 @@ -88,12 +90,12 @@ def plot_prop_flux(data: xr.Dataset, save_fig: bool, plot_dir: str = None): plt.grid(True) packing_ratio = np.unique(data_frame.packing_ratio.values) - + for i, beta in enumerate(packing_ratio): dat = data_frame[data_frame.packing_ratio == beta] plt.plot( dat.SAV_ft.values, - dat['prop_flux'].values, + dat["prop_flux"].values, lw=2, color=COLORS[i], label=beta, @@ -106,13 +108,13 @@ def plot_prop_flux(data: xr.Dataset, save_fig: bool, plot_dir: str = None): if save_fig: fig_name = os.path.join(plot_dir, "prop_flux_plot.png") plt.savefig(fig_name) - - @staticmethod + + @staticmethod def plot_reaction_vel(data: xr.Dataset, save_fig: bool, plot_dir: str = None): """Plot reaction velocity Args: - data (xarray DataSet): the data set + data (xarray DataSet): the data set save_fig (bool): whether or not to write out plot plot_dir (str): if saving figure, where to write to """ @@ -123,23 +125,23 @@ def plot_reaction_vel(data: xr.Dataset, save_fig: bool, plot_dir: str = None): "reaction_vel": data.reaction_velocity.values.flatten(), } ) - - data_frame['SAV_ft'] = data_frame.SAV*CM_TO_FT + + data_frame["SAV_ft"] = data_frame.SAV * CM_TO_FT max_beta = data_frame["beta_ratio"].max() max_reaction_vel = 18 - + blank_plot(max_beta, 0.0, max_reaction_vel, 0.0, draw_horizontal_lines=True) SAV_vals = np.unique(data_frame.SAV_ft.values) colors = COLORS colors.reverse() - + for i, sav in enumerate(SAV_vals): dat = data_frame[data_frame.SAV_ft == sav] plt.plot( dat.beta_ratio.values, - dat['reaction_vel'].values, + dat["reaction_vel"].values, lw=2, color=colors[i], label=sav, @@ -152,13 +154,13 @@ def plot_reaction_vel(data: xr.Dataset, save_fig: bool, plot_dir: str = None): if save_fig: fig_name = os.path.join(plot_dir, "reaction_vel_plot.png") plt.savefig(fig_name) - - @staticmethod + + @staticmethod def plot_qig(data: xr.Dataset, save_fig: bool, plot_dir: str = None): """Plot heat of preignition Args: - data (xarray DataSet): the data set + data (xarray DataSet): the data set save_fig (bool): whether or not to write out plot plot_dir (str): if saving figure, where to write to """ @@ -168,20 +170,22 @@ def plot_qig(data: xr.Dataset, save_fig: bool, plot_dir: str = None): "q_ig": data.q_ig.values.flatten(), } ) - - data_frame['fuel_moisture_perc'] = data_frame.fuel_moisture*100.0 - data_frame['q_ig_btu'] = data_frame.q_ig*KJKG_TO_BTULB # match rothermel graph units + + data_frame["fuel_moisture_perc"] = data_frame.fuel_moisture * 100.0 + data_frame["q_ig_btu"] = ( + data_frame.q_ig * KJKG_TO_BTULB + ) # match Rothermel graph units max_moist = 200.0 max_qig = 2500.0 - + blank_plot(max_moist, 0.0, max_qig, 0.0, draw_horizontal_lines=True) plt.plot( data_frame.fuel_moisture_perc.values, - data_frame['q_ig_btu'].values, + data_frame["q_ig_btu"].values, lw=2, - color='k', + color="k", ) plt.xlabel("Fuel moisture (%)", fontsize=11) @@ -190,13 +194,13 @@ def plot_qig(data: xr.Dataset, save_fig: bool, plot_dir: str = None): if save_fig: fig_name = os.path.join(plot_dir, "qig_plot.png") plt.savefig(fig_name) - - @staticmethod + + @staticmethod def plot_eps(data: xr.Dataset, save_fig: bool, plot_dir: str = None): """Plot effective heating number Args: - data (xarray DataSet): the data set + data (xarray DataSet): the data set save_fig (bool): whether or not to write out plot plot_dir (str): if saving figure, where to write to """ @@ -206,19 +210,19 @@ def plot_eps(data: xr.Dataset, save_fig: bool, plot_dir: str = None): "eps": data.eps.values.flatten(), } ) - - data_frame['SAV_ft'] = data_frame.SAV*CM_TO_FT + + data_frame["SAV_ft"] = data_frame.SAV * CM_TO_FT max_SAV = 3500.0 max_eps = 1.0 - + blank_plot(max_SAV, 0.0, max_eps, 0.0, draw_horizontal_lines=True) plt.plot( data_frame.SAV_ft.values, - data_frame['eps'].values, + data_frame["eps"].values, lw=2, - color='k', + color="k", ) plt.xlabel("Surface-area-to-volume ratio (ft$^{-1}$)", fontsize=11) @@ -226,4 +230,4 @@ def plot_eps(data: xr.Dataset, save_fig: bool, plot_dir: str = None): if save_fig: fig_name = os.path.join(plot_dir, "eps_plot.png") - plt.savefig(fig_name) \ No newline at end of file + plt.savefig(fig_name) From 6a44ef4c27e1060a14d302338240c3bde4ae75c2 Mon Sep 17 00:00:00 2001 From: Marcos Longo Date: Tue, 17 Dec 2024 13:59:45 -0300 Subject: [PATCH 110/111] Removing print statement added for debugging. --- tools/ncvarsort.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tools/ncvarsort.py b/tools/ncvarsort.py index 255475b910..92c8c85fc9 100755 --- a/tools/ncvarsort.py +++ b/tools/ncvarsort.py @@ -129,7 +129,6 @@ def main(): varin = dsin.variables.get(v_name) v_type = dsin.variables[v_name].typecode() v_dims = varin.dimensions - print(" V_NAME = ",v_name,"; V_TYPE = ",v_type,"; V_DIMS = ",v_dims) outVar = dsout.createVariable(v_name, v_type, v_dims) n_dims = len(v_dims) From 56ecefdbde152852a5ad892b71976d310da2c863 Mon Sep 17 00:00:00 2001 From: adrifoster Date: Mon, 30 Dec 2024 12:01:07 -0700 Subject: [PATCH 111/111] remove greater than one check --- fire/SFEquationsMod.F90 | 1 - 1 file changed, 1 deletion(-) diff --git a/fire/SFEquationsMod.F90 b/fire/SFEquationsMod.F90 index 16e8fedc8a..b56b151e47 100644 --- a/fire/SFEquationsMod.F90 +++ b/fire/SFEquationsMod.F90 @@ -120,7 +120,6 @@ real(r8) function MoistureCoefficient(moisture, MEF) (3.52_r8*(mw_weight**3.0_r8)) if (MoistureCoefficient < nearzero) MoistureCoefficient = 0.0_r8 - if (MoistureCoefficient > 1.0_r8) MoistureCoefficient = 1.0_r8 end function MoistureCoefficient