From b47072f0239ad884f520234708c88f0649c9ee7c Mon Sep 17 00:00:00 2001 From: Adrianna Foster Date: Wed, 20 Mar 2024 10:06:33 -0600 Subject: [PATCH 01/46] 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 02/46] 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 03/46] 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 04/46] 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 05/46] 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 06/46] 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 07/46] 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 08/46] 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 09/46] 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 10/46] 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 11/46] 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 12/46] 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 13/46] 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 14/46] 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 15/46] 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 57df197813f3b868a61bb396cb5c0d04ad6f679c Mon Sep 17 00:00:00 2001 From: adrifoster Date: Mon, 3 Jun 2024 13:16:42 -0600 Subject: [PATCH 16/46] 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 17/46] 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 ce55f1ccf61b261e0d330925a46e61d0948cb697 Mon Sep 17 00:00:00 2001 From: adrifoster Date: Fri, 30 Aug 2024 12:56:03 -0600 Subject: [PATCH 18/46] 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 19/46] 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 20/46] 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 21/46] 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 22/46] 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 23/46] 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 24/46] 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 20ceac215e7bea5ae18bc421c29def977d1bf53e Mon Sep 17 00:00:00 2001 From: adrifoster Date: Mon, 9 Sep 2024 09:59:47 -0600 Subject: [PATCH 25/46] 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 26/46] 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 27/46] 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 28/46] 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 29/46] 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 30/46] 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 31/46] 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 32/46] 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 33/46] 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 34/46] 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 35/46] 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 36/46] 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 b5f39560228a8de2caf203b7098c0a271c729b9c Mon Sep 17 00:00:00 2001 From: adrifoster Date: Tue, 29 Oct 2024 14:52:37 -0600 Subject: [PATCH 37/46] 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 38/46] 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 39/46] 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 40/46] 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 41/46] 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 42/46] 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 e0c705804511eb9945517c93333e5f8a5b8a7794 Mon Sep 17 00:00:00 2001 From: adrifoster Date: Thu, 31 Oct 2024 10:14:49 -0600 Subject: [PATCH 43/46] 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 52842b37843522995796fa7a1144e31ee2054130 Mon Sep 17 00:00:00 2001 From: Adrianna Foster Date: Mon, 4 Nov 2024 10:06:17 -0700 Subject: [PATCH 44/46] 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 45/46] 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 46/46] 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]